v11: фикс загрузки, текст отчёта без файлов, статус «В процессе»
This commit is contained in:
parent
02f4e4a472
commit
b9bdf6aef3
146
index.html
146
index.html
@ -151,7 +151,7 @@ td{border-bottom:1px solid var(--gray-200)}tr:hover td{background:var(--cyan-50)
|
|||||||
<div class="panel" style="border-radius:0 0 12px 12px">
|
<div class="panel" style="border-radius:0 0 12px 12px">
|
||||||
<div class="stats-row" id="myStats"></div>
|
<div class="stats-row" id="myStats"></div>
|
||||||
<div class="filters">
|
<div class="filters">
|
||||||
<select id="myStatusFilter" onchange="renderMyEvents()"><option value="">Все статусы</option><option value="warn">На контроле</option><option value="late">Просрочено</option><option value="done">Исполнено</option><option value="wait">Не начато</option></select>
|
<select id="myStatusFilter" onchange="renderMyEvents()"><option value="">Все статусы</option><option value="warn">На контроле</option><option value="late">Просрочено</option><option value="done">Исполнено</option><option value="wait">В процессе</option></select>
|
||||||
<span class="user-info" id="myCount"></span>
|
<span class="user-info" id="myCount"></span>
|
||||||
</div>
|
</div>
|
||||||
<table id="myEventsTable"></table>
|
<table id="myEventsTable"></table>
|
||||||
@ -161,7 +161,7 @@ td{border-bottom:1px solid var(--gray-200)}tr:hover td{background:var(--cyan-50)
|
|||||||
<div class="panel" style="border-radius:0 0 12px 12px">
|
<div class="panel" style="border-radius:0 0 12px 12px">
|
||||||
<div class="filters">
|
<div class="filters">
|
||||||
<input id="allSearch" placeholder="Поиск..." oninput="renderAllEvents()">
|
<input id="allSearch" placeholder="Поиск..." oninput="renderAllEvents()">
|
||||||
<select id="allStatusFilter" onchange="renderAllEvents()"><option value="">Все статусы</option><option value="warn">На контроле</option><option value="late">Просрочено</option><option value="done">Исполнено</option><option value="wait">Не начато</option></select>
|
<select id="allStatusFilter" onchange="renderAllEvents()"><option value="">Все статусы</option><option value="warn">На контроле</option><option value="late">Просрочено</option><option value="done">Исполнено</option><option value="wait">В процессе</option></select>
|
||||||
<select id="allSecFilter" onchange="renderAllEvents()"><option value="">Все разделы</option><option value="0">I. Люди</option><option value="1">II. Оборудование</option><option value="2">III. Аварии и ЧС</option><option value="3">IV. Информ. работа</option><option value="4">V. ИИ и цифровизация</option></select>
|
<select id="allSecFilter" onchange="renderAllEvents()"><option value="">Все разделы</option><option value="0">I. Люди</option><option value="1">II. Оборудование</option><option value="2">III. Аварии и ЧС</option><option value="3">IV. Информ. работа</option><option value="4">V. ИИ и цифровизация</option></select>
|
||||||
</div>
|
</div>
|
||||||
<table id="allEventsTable"></table>
|
<table id="allEventsTable"></table>
|
||||||
@ -179,7 +179,7 @@ td{border-bottom:1px solid var(--gray-200)}tr:hover td{background:var(--cyan-50)
|
|||||||
<script>
|
<script>
|
||||||
const sections=['I. Люди. Повышение культуры безопасности','II. Безопасность при эксплуатации оборудования','III. Предупреждение и готовность к ликвидации аварий и ЧС','IV. Информационно-разъяснительная работа','V. Внедрение ИИ и цифровизации']
|
const sections=['I. Люди. Повышение культуры безопасности','II. Безопасность при эксплуатации оборудования','III. Предупреждение и готовность к ликвидации аварий и ЧС','IV. Информационно-разъяснительная работа','V. Внедрение ИИ и цифровизации']
|
||||||
const branches=['Дирекция производственной безопасности','Объединение «Дивизион «Сеть»»','Дивизион по корпоративному бизнесу','Дивизион по розничному бизнесу','Сервисная фабрика','Дирекция «Телеком Комплект»','Корпоративный университет','Дирекция управления проектами','Дивизион цифрового бизнеса']
|
const branches=['Дирекция производственной безопасности','Объединение «Дивизион «Сеть»»','Дивизион по корпоративному бизнесу','Дивизион по розничному бизнесу','Сервисная фабрика','Дирекция «Телеком Комплект»','Корпоративный университет','Дирекция управления проектами','Дивизион цифрового бизнеса']
|
||||||
const statusMap={done:'Исполнено',warn:'На контроле',late:'Просрочено',wait:'Не начато'}
|
const statusMap={done:'Исполнено',warn:'На контроле',late:'Просрочено',wait:'В процессе'}
|
||||||
const months=['2026-01','2026-02','2026-03','2026-04','2026-05','2026-06','2026-07','2026-08','2026-09','2026-10','2026-11','2026-12']
|
const months=['2026-01','2026-02','2026-03','2026-04','2026-05','2026-06','2026-07','2026-08','2026-09','2026-10','2026-11','2026-12']
|
||||||
const monthNames=['Янв','Фев','Мар','Апр','Май','Июн','Июл','Авг','Сен','Окт','Ноя','Дек']
|
const monthNames=['Янв','Фев','Мар','Апр','Май','Июн','Июл','Авг','Сен','Окт','Ноя','Дек']
|
||||||
|
|
||||||
@ -415,7 +415,7 @@ function renderMyEvents(){
|
|||||||
const sf=document.getElementById('myStatusFilter').value
|
const sf=document.getElementById('myStatusFilter').value
|
||||||
let list=getMyEvents();if(sf)list=list.filter(e=>e.s===sf)
|
let list=getMyEvents();if(sf)list=list.filter(e=>e.s===sf)
|
||||||
const done=list.filter(e=>e.s==='done').length,late=list.filter(e=>e.s==='late').length,warn=list.filter(e=>e.s==='warn').length,wait=list.filter(e=>e.s==='wait').length
|
const done=list.filter(e=>e.s==='done').length,late=list.filter(e=>e.s==='late').length,warn=list.filter(e=>e.s==='warn').length,wait=list.filter(e=>e.s==='wait').length
|
||||||
document.getElementById('myStats').innerHTML=`<div class="stat-card"><div class="lbl">Мои мероприятия</div><div class="num">${list.length}</div></div><div class="stat-card green"><div class="lbl">Исполнено</div><div class="num">${done}</div></div><div class="stat-card amber"><div class="lbl">На контроле</div><div class="num">${warn}</div></div><div class="stat-card red"><div class="lbl">Просрочено</div><div class="num">${late}</div></div><div class="stat-card blue"><div class="lbl">Не начато</div><div class="num">${wait}</div></div>`
|
document.getElementById('myStats').innerHTML=`<div class="stat-card"><div class="lbl">Мои мероприятия</div><div class="num">${list.length}</div></div><div class="stat-card green"><div class="lbl">Исполнено</div><div class="num">${done}</div></div><div class="stat-card amber"><div class="lbl">На контроле</div><div class="num">${warn}</div></div><div class="stat-card red"><div class="lbl">Просрочено</div><div class="num">${late}</div></div><div class="stat-card blue"><div class="lbl">В процессе</div><div class="num">${wait}</div></div>`
|
||||||
document.getElementById('myCount').textContent=`Найдено: ${list.length}`
|
document.getElementById('myCount').textContent=`Найдено: ${list.length}`
|
||||||
document.getElementById('myEventsTable').innerHTML=`<tr><th>№</th><th>Мероприятие</th><th>Раздел</th><th>Срок</th><th>Прогресс</th><th>Статус</th><th></th></tr>${list.map(e=>`<tr><td>${e.id}</td><td style="font-size:12px;max-width:320px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap" title="${e.t}">${e.t}</td><td><span class="badge blue">${['I','II','III','IV','V'][e.sec]}</span></td><td style="font-size:12px">${e.due}</td><td>${pctHtml(e.p)}</td><td>${sBadge(e.s)}</td><td><button class="btn btn-sm" onclick="openEdit(${e.id})">📝</button></td></tr>`).join('')}`
|
document.getElementById('myEventsTable').innerHTML=`<tr><th>№</th><th>Мероприятие</th><th>Раздел</th><th>Срок</th><th>Прогресс</th><th>Статус</th><th></th></tr>${list.map(e=>`<tr><td>${e.id}</td><td style="font-size:12px;max-width:320px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap" title="${e.t}">${e.t}</td><td><span class="badge blue">${['I','II','III','IV','V'][e.sec]}</span></td><td style="font-size:12px">${e.due}</td><td>${pctHtml(e.p)}</td><td>${sBadge(e.s)}</td><td><button class="btn btn-sm" onclick="openEdit(${e.id})">📝</button></td></tr>`).join('')}`
|
||||||
}
|
}
|
||||||
@ -430,9 +430,10 @@ function renderAnalytics(){
|
|||||||
document.getElementById('analyticsContent').innerHTML=`<h3>Рейтинг дивизионов</h3>${branches.map((b,i)=>{let items=events.filter(e=>e.b===i),d=items.filter(e=>e.s==='done').length,pct=Math.round(d/Math.max(1,items.length)*100);return`<div class="pct-bar" style="margin-bottom:8px"><span style="width:220px;font-size:13px;font-weight:600">${b}</span><div class="track" style="flex:1"><div class="fill" style="width:${pct}%;background:${pct>=50?'var(--green)':pct>=25?'var(--amber)':'var(--red)'}"></div></div><span style="font-weight:700;font-size:13px;width:60px;text-align:right">${pct}%</span></div>`}).join('')}`
|
document.getElementById('analyticsContent').innerHTML=`<h3>Рейтинг дивизионов</h3>${branches.map((b,i)=>{let items=events.filter(e=>e.b===i),d=items.filter(e=>e.s==='done').length,pct=Math.round(d/Math.max(1,items.length)*100);return`<div class="pct-bar" style="margin-bottom:8px"><span style="width:220px;font-size:13px;font-weight:600">${b}</span><div class="track" style="flex:1"><div class="fill" style="width:${pct}%;background:${pct>=50?'var(--green)':pct>=25?'var(--amber)':'var(--red)'}"></div></div><span style="font-weight:700;font-size:13px;width:60px;text-align:right">${pct}%</span></div>`}).join('')}`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===== FILE STORAGE: month-keyed =====
|
// ===== FILE STORAGE: month-keyed with reports =====
|
||||||
function getFiles(eventId){ return JSON.parse(localStorage.getItem('samruk_files_'+eventId)||'{}') }
|
// { "2026-01": { report: "текст отчёта", files: [{name,size,type,desc,date,data},...] }, ... }
|
||||||
function setFiles(eventId,obj){ localStorage.setItem('samruk_files_'+eventId,JSON.stringify(obj)) }
|
function getMonthData(eventId){ return JSON.parse(localStorage.getItem('samruk_files_'+eventId)||'{}') }
|
||||||
|
function setMonthData(eventId,obj){ localStorage.setItem('samruk_files_'+eventId,JSON.stringify(obj)) }
|
||||||
function getSubChecks(eventId){ return JSON.parse(localStorage.getItem('samruk_sub_'+eventId)||'[]') }
|
function getSubChecks(eventId){ return JSON.parse(localStorage.getItem('samruk_sub_'+eventId)||'[]') }
|
||||||
function setSubChecks(eventId,arr){ localStorage.setItem('samruk_sub_'+eventId,JSON.stringify(arr)) }
|
function setSubChecks(eventId,arr){ localStorage.setItem('samruk_sub_'+eventId,JSON.stringify(arr)) }
|
||||||
|
|
||||||
@ -443,18 +444,75 @@ function openEdit(id,monthIdx){
|
|||||||
if(typeof monthIdx==='number') editMonthIdx=monthIdx
|
if(typeof monthIdx==='number') editMonthIdx=monthIdx
|
||||||
const e=events.find(x=>x.id===id);if(!e)return
|
const e=events.find(x=>x.id===id);if(!e)return
|
||||||
const savedEdits=JSON.parse(localStorage.getItem('samruk_edits_'+e.id)||'{}')
|
const savedEdits=JSON.parse(localStorage.getItem('samruk_edits_'+e.id)||'{}')
|
||||||
const filesObj=getFiles(e.id)
|
const allData=getMonthData(e.id)
|
||||||
const subChecks=getSubChecks(e.id)
|
const subChecks=getSubChecks(e.id)
|
||||||
const curMonth=months[editMonthIdx]
|
const curMonth=months[editMonthIdx]
|
||||||
const curFiles=filesObj[curMonth]||[]
|
const curData=allData[curMonth]||{report:'',files:[]}
|
||||||
const totalFiles=Object.values(filesObj).reduce((s,a)=>s+a.length,0)
|
const curFiles=curData.files||[]
|
||||||
|
let totalFiles=0;Object.values(allData).forEach(d=>{totalFiles+=(d.files||[]).length})
|
||||||
|
|
||||||
// Sub-items HTML
|
|
||||||
let subHtml=''
|
let subHtml=''
|
||||||
if(e.sub&&e.sub.length){
|
if(e.sub&&e.sub.length){
|
||||||
subHtml=`<div style="font-weight:600;margin-bottom:8px;font-size:14px">Подпункты мероприятия</div><div class="sub-items">${e.sub.map((s,i)=>{let ch=subChecks.includes(i);return`<div class="sub-item"><input type="checkbox" id="subchk_${i}" ${ch?'checked':''}><span class="sub-label">${s.l})</span><span class="sub-text">${s.t}</span></div>`}).join('')}</div>`
|
subHtml=`<div style="font-weight:600;margin-bottom:8px;font-size:14px">Подпункты мероприятия</div><div class="sub-items">${e.sub.map((s,i)=>{let ch=subChecks.includes(i);return`<div class="sub-item"><input type="checkbox" id="subchk_${i}" ${ch?'checked':''}><span class="sub-label">${s.l})</span><span class="sub-text">${s.t}</span></div>`}).join('')}</div>`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let filesHtml=''
|
||||||
|
if(curFiles.length){
|
||||||
|
filesHtml=`<div style="font-weight:600;margin:12px 0 6px;font-size:13px">Файлы за ${M(editMonthIdx)} (${curFiles.length} шт.)</div>`
|
||||||
|
curFiles.forEach((f,i)=>{filesHtml+=`<div class="file-row"><span class="file-info"><span class="file-name" onclick="downloadFile(${e.id},'${curMonth}',${i})">📄 ${f.name}</span>${f.desc?`<span class="file-desc">${f.desc}</span>`:''}</span><span class="file-meta">${(f.size/1024).toFixed(0)} КБ · ${f.date}</span><button class="file-del" onclick="removeFile(${e.id},'${curMonth}',${i})">×</button></div>`})}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('editModalContent').innerHTML=`
|
||||||
|
<button class="close" onclick="closeEditModal()">×</button>
|
||||||
|
<span class="badge blue">Раздел ${['I','II','III','IV','V'][e.sec]}</span>
|
||||||
|
<h3 style="margin:8px 0">${e.t}</h3>
|
||||||
|
<div class="meta-row">
|
||||||
|
<div class="fld">Дивизион<strong>${branches[e.b]}</strong></div>
|
||||||
|
<div class="fld">Ответственный<strong>${e.r}</strong></div>
|
||||||
|
<div class="fld">Срок<strong>${e.due}</strong></div>
|
||||||
|
<div class="fld">Факт<strong>${e.done}</strong></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field"><label>Статус</label><select id="editStatus" onchange="autoProgress()"><option value="wait" ${e.s==='wait'?'selected':''}>В процессе</option><option value="warn" ${e.s==='warn'?'selected':''}>На контроле</option><option value="late" ${e.s==='late'?'selected':''}>Просрочено</option><option value="done" ${e.s==='done'?'selected':''}>Исполнено</option></select></div>
|
||||||
|
<div class="field"><label>Прогресс (%)</label><input type="range" id="editProgress" min="0" max="100" value="${e.p}" oninput="document.getElementById('pVal').textContent=this.value+'%'" style="width:100%"><span id="pVal" style="font-weight:700">${e.p}%</span></div>
|
||||||
|
<div class="field"><label>Комментарий</label><textarea id="editComment" placeholder="Комментарий к статусу...">${savedEdits.comment||''}</textarea></div>
|
||||||
|
|
||||||
|
${subHtml}
|
||||||
|
|
||||||
|
<div style="border-top:1px solid var(--gray-200);padding-top:16px;margin-top:12px">
|
||||||
|
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:12px">
|
||||||
|
<span style="font-weight:600;font-size:14px">📎 Отчётность по месяцам</span>
|
||||||
|
<span style="font-size:12px;color:var(--gray-500)">Файлов: ${totalFiles}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="month-tabs">${months.map((m,i)=>`<span class="month-tab${i===editMonthIdx?' active':''}" onclick="openEdit(${e.id},${i})">${M(i)}</span>`).join('')}</div>
|
||||||
|
|
||||||
|
<div class="field" style="margin-top:12px">
|
||||||
|
<label>Текст отчёта за ${M(editMonthIdx)}</label>
|
||||||
|
<textarea id="monthReport" placeholder="Опишите ход исполнения, результаты, проблемы... Можно без прикрепления файлов." style="min-height:80px">${curData.report||''}</textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
${filesHtml}
|
||||||
|
|
||||||
|
<div class="upload-row">
|
||||||
|
<input type="text" id="fileDesc" placeholder="Описание файла (акт, протокол, фото...)" style="flex:2">
|
||||||
|
<input type="file" id="editFileInput" multiple>
|
||||||
|
<button class="btn btn-sm" id="uploadBtn" onclick="uploadFiles(${e.id},'${curMonth}')">Загрузить</button>
|
||||||
|
</div>
|
||||||
|
<p style="font-size:11px;color:var(--gray-500);margin-top:6px">Формы завершения: ${e.dname}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="ai-block"><h4>🤖 Вывод ИИ-агента</h4>${e.ai}</div>
|
||||||
|
<div style="font-weight:600;margin:8px 0 4px;font-size:14px">История:</div>
|
||||||
|
<div>${e.h.map(h=>`<div class="history-item"><div class="dot"></div>${h}</div>`).join('')}</div>
|
||||||
|
|
||||||
|
<div style="margin-top:20px;display:flex;gap:12px">
|
||||||
|
<button class="btn" onclick="saveEdit(${e.id},'${curMonth}')">Сохранить</button>
|
||||||
|
<button class="btn btn-outline" onclick="closeEditModal()">Отмена</button>
|
||||||
|
</div>`
|
||||||
|
document.getElementById('editModalOverlay').classList.add('open')
|
||||||
|
}
|
||||||
|
|
||||||
// File list for current month
|
// File list for current month
|
||||||
let filesHtml=''
|
let filesHtml=''
|
||||||
if(curFiles.length){
|
if(curFiles.length){
|
||||||
@ -473,7 +531,7 @@ function openEdit(id,monthIdx){
|
|||||||
<div class="fld">Факт<strong>${e.done}</strong></div>
|
<div class="fld">Факт<strong>${e.done}</strong></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="field"><label>Статус</label><select id="editStatus" onchange="autoProgress()"><option value="wait" ${e.s==='wait'?'selected':''}>Не начато</option><option value="warn" ${e.s==='warn'?'selected':''}>На контроле</option><option value="late" ${e.s==='late'?'selected':''}>Просрочено</option><option value="done" ${e.s==='done'?'selected':''}>Исполнено</option></select></div>
|
<div class="field"><label>Статус</label><select id="editStatus" onchange="autoProgress()"><option value="wait" ${e.s==='wait'?'selected':''}>В процессе</option><option value="warn" ${e.s==='warn'?'selected':''}>На контроле</option><option value="late" ${e.s==='late'?'selected':''}>Просрочено</option><option value="done" ${e.s==='done'?'selected':''}>Исполнено</option></select></div>
|
||||||
<div class="field"><label>Прогресс (%)</label><input type="range" id="editProgress" min="0" max="100" value="${e.p}" oninput="document.getElementById('pVal').textContent=this.value+'%'" style="width:100%"><span id="pVal" style="font-weight:700">${e.p}%</span></div>
|
<div class="field"><label>Прогресс (%)</label><input type="range" id="editProgress" min="0" max="100" value="${e.p}" oninput="document.getElementById('pVal').textContent=this.value+'%'" style="width:100%"><span id="pVal" style="font-weight:700">${e.p}%</span></div>
|
||||||
<div class="field"><label>Комментарий</label><textarea id="editComment" placeholder="Комментарий к статусу...">${savedEdits.comment||''}</textarea></div>
|
<div class="field"><label>Комментарий</label><textarea id="editComment" placeholder="Комментарий к статусу...">${savedEdits.comment||''}</textarea></div>
|
||||||
|
|
||||||
@ -509,46 +567,63 @@ function openEdit(id,monthIdx){
|
|||||||
}
|
}
|
||||||
|
|
||||||
function uploadFiles(eventId, monthKey){
|
function uploadFiles(eventId, monthKey){
|
||||||
const fi=document.getElementById('editFileInput'),desc=document.getElementById('fileDesc').value.trim()
|
const fi=document.getElementById('editFileInput')
|
||||||
if(!fi.files.length)return
|
const desc=document.getElementById('fileDesc').value.trim()
|
||||||
const MAX=4*1024*1024,obj=getFiles(eventId),arr=obj[monthKey]||[]
|
const btn=document.getElementById('uploadBtn')
|
||||||
|
if(!fi||!fi.files||!fi.files.length) return
|
||||||
|
btn.textContent='Загружается...';btn.disabled=true
|
||||||
|
const MAX=4*1024*1024,allData=getMonthData(eventId)
|
||||||
|
if(!allData[monthKey]) allData[monthKey]={report:'',files:[]}
|
||||||
|
const arr=allData[monthKey].files
|
||||||
let processed=0,skipped=0
|
let processed=0,skipped=0
|
||||||
|
function finish(){
|
||||||
|
try{setMonthData(eventId,allData)}catch(e){alert('⚠️ Хранилище переполнено')}
|
||||||
|
if(skipped) alert(`⚠️ ${skipped} файл(ов) > 4 МБ пропущены`)
|
||||||
|
closeEditModal();openEdit(eventId)
|
||||||
|
}
|
||||||
|
if(!fi.files.length){finish();return}
|
||||||
for(const f of fi.files){
|
for(const f of fi.files){
|
||||||
if(f.size>MAX){skipped++;processed++;if(processed===fi.files.length)finish();continue}
|
if(f.size>MAX){skipped++;processed++;if(processed===fi.files.length)finish();continue}
|
||||||
const r=new FileReader()
|
const r=new FileReader()
|
||||||
r.onload=function(ev){arr.push({name:f.name,size:f.size,type:f.type,desc,date:new Date().toLocaleDateString(),data:ev.target.result});processed++;if(processed===fi.files.length)finish()}
|
r.onload=function(ev){
|
||||||
|
arr.push({name:f.name,size:f.size,type:f.type,desc,date:new Date().toLocaleDateString(),data:ev.target.result})
|
||||||
|
processed++
|
||||||
|
if(processed===fi.files.length) finish()
|
||||||
|
}
|
||||||
|
r.onerror=function(){processed++;if(processed===fi.files.length)finish()}
|
||||||
r.readAsDataURL(f)
|
r.readAsDataURL(f)
|
||||||
}
|
}
|
||||||
function finish(){
|
|
||||||
obj[monthKey]=arr;try{setFiles(eventId,obj)}catch(e){alert('⚠️ Хранилище переполнено')}
|
|
||||||
if(skipped)alert(`⚠️ ${skipped} файл(ов) > 4 МБ пропущены`)
|
|
||||||
closeEditModal();openEdit(eventId)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function downloadFile(eventId, monthKey, idx){
|
function downloadFile(eventId, monthKey, idx){
|
||||||
const obj=getFiles(eventId),arr=obj[monthKey];if(!arr||!arr[idx]||!arr[idx].data)return
|
const allData=getMonthData(eventId),arr=allData[monthKey]?.files
|
||||||
const f=arr[idx],a=document.createElement('a');a.href=f.data;a.download=f.name;document.body.appendChild(a);a.click();document.body.removeChild(a)
|
if(!arr||!arr[idx]||!arr[idx].data) return
|
||||||
|
const f=arr[idx],a=document.createElement('a');a.href=f.data;a.download=f.name
|
||||||
|
document.body.appendChild(a);a.click();document.body.removeChild(a)
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeFile(eventId, monthKey, idx){
|
function removeFile(eventId, monthKey, idx){
|
||||||
const obj=getFiles(eventId);if(!obj[monthKey])return
|
const allData=getMonthData(eventId)
|
||||||
obj[monthKey].splice(idx,1);if(!obj[monthKey].length)delete obj[monthKey];setFiles(eventId,obj)
|
if(!allData[monthKey]||!allData[monthKey].files) return
|
||||||
|
allData[monthKey].files.splice(idx,1);setMonthData(eventId,allData)
|
||||||
closeEditModal();openEdit(eventId)
|
closeEditModal();openEdit(eventId)
|
||||||
}
|
}
|
||||||
|
|
||||||
function autoProgress(){
|
function saveEdit(id, monthKey){
|
||||||
const s=document.getElementById('editStatus').value,p=document.getElementById('editProgress')
|
|
||||||
if(s==='done')p.value=100;else if(s==='wait')p.value=0;document.getElementById('pVal').textContent=p.value+'%'
|
|
||||||
}
|
|
||||||
|
|
||||||
function saveEdit(id){
|
|
||||||
const e=events.find(x=>x.id===id);if(!e)return
|
const e=events.find(x=>x.id===id);if(!e)return
|
||||||
e.s=document.getElementById('editStatus').value
|
e.s=document.getElementById('editStatus').value
|
||||||
e.p=parseInt(document.getElementById('editProgress').value)
|
e.p=parseInt(document.getElementById('editProgress').value)
|
||||||
const comment=document.getElementById('editComment').value.trim()
|
const comment=document.getElementById('editComment').value.trim()
|
||||||
|
const monthReport=document.getElementById('monthReport')?.value||''
|
||||||
|
|
||||||
|
// Save monthly report text
|
||||||
|
if(monthKey){
|
||||||
|
const allData=getMonthData(id)
|
||||||
|
if(!allData[monthKey]) allData[monthKey]={report:'',files:[]}
|
||||||
|
allData[monthKey].report=monthReport
|
||||||
|
setMonthData(id,allData)
|
||||||
|
}
|
||||||
|
|
||||||
// Save sub-item checks
|
|
||||||
if(e.sub&&e.sub.length){
|
if(e.sub&&e.sub.length){
|
||||||
const checks=[]
|
const checks=[]
|
||||||
e.sub.forEach((_,i)=>{if(document.getElementById('subchk_'+i)?.checked)checks.push(i)})
|
e.sub.forEach((_,i)=>{if(document.getElementById('subchk_'+i)?.checked)checks.push(i)})
|
||||||
@ -564,6 +639,13 @@ function saveEdit(id){
|
|||||||
|
|
||||||
function closeEditModal(){document.getElementById('editModalOverlay').classList.remove('open')}
|
function closeEditModal(){document.getElementById('editModalOverlay').classList.remove('open')}
|
||||||
|
|
||||||
|
function autoProgress(){
|
||||||
|
const s=document.getElementById('editStatus'),p=document.getElementById('editProgress')
|
||||||
|
if(!s||!p)return
|
||||||
|
if(s.value==='done')p.value=100;else if(s.value==='wait')p.value=0
|
||||||
|
document.getElementById('pVal').textContent=p.value+'%'
|
||||||
|
}
|
||||||
|
|
||||||
document.querySelectorAll('.tab-btn').forEach(btn=>{btn.addEventListener('click',function(){document.querySelectorAll('.tab-btn').forEach(b=>b.classList.remove('active'));document.querySelectorAll('.tab-content').forEach(c=>c.classList.remove('active'));this.classList.add('active');document.getElementById('tab-'+this.dataset.tab).classList.add('active');if(this.dataset.tab==='analytics')renderAnalytics()})})
|
document.querySelectorAll('.tab-btn').forEach(btn=>{btn.addEventListener('click',function(){document.querySelectorAll('.tab-btn').forEach(b=>b.classList.remove('active'));document.querySelectorAll('.tab-content').forEach(c=>c.classList.remove('active'));this.classList.add('active');document.getElementById('tab-'+this.dataset.tab).classList.add('active');if(this.dataset.tab==='analytics')renderAnalytics()})})
|
||||||
document.getElementById('editModalOverlay').addEventListener('click',function(e){if(e.target===this)closeEditModal()})
|
document.getElementById('editModalOverlay').addEventListener('click',function(e){if(e.target===this)closeEditModal()})
|
||||||
document.addEventListener('keydown',e=>{if(e.key==='Escape'){closeEditModal();document.getElementById('notifDrop').classList.remove('open')}})
|
document.addEventListener('keydown',e=>{if(e.key==='Escape'){closeEditModal();document.getElementById('notifDrop').classList.remove('open')}})
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user