v11: фикс загрузки, текст отчёта без файлов, статус «В процессе»

This commit is contained in:
Dauren777 2026-06-04 12:04:57 +00:00
parent 02f4e4a472
commit b9bdf6aef3

View File

@ -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="stats-row" id="myStats"></div>
<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>
</div>
<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="filters">
<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>
</div>
<table id="allEventsTable"></table>
@ -179,7 +179,7 @@ td{border-bottom:1px solid var(--gray-200)}tr:hover td{background:var(--cyan-50)
<script>
const sections=['I. Люди. Повышение культуры безопасности','II. Безопасность при эксплуатации оборудования','III. Предупреждение и готовность к ликвидации аварий и ЧС','IV. Информационно-разъяснительная работа','V. Внедрение ИИ и цифровизации']
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 monthNames=['Янв','Фев','Мар','Апр','Май','Июн','Июл','Авг','Сен','Окт','Ноя','Дек']
@ -415,7 +415,7 @@ function renderMyEvents(){
const sf=document.getElementById('myStatusFilter').value
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
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('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('')}`
}
// ===== FILE STORAGE: month-keyed =====
function getFiles(eventId){ return JSON.parse(localStorage.getItem('samruk_files_'+eventId)||'{}') }
function setFiles(eventId,obj){ localStorage.setItem('samruk_files_'+eventId,JSON.stringify(obj)) }
// ===== FILE STORAGE: month-keyed with reports =====
// { "2026-01": { report: "текст отчёта", files: [{name,size,type,desc,date,data},...] }, ... }
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 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
const e=events.find(x=>x.id===id);if(!e)return
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 curMonth=months[editMonthIdx]
const curFiles=filesObj[curMonth]||[]
const totalFiles=Object.values(filesObj).reduce((s,a)=>s+a.length,0)
const curData=allData[curMonth]||{report:'',files:[]}
const curFiles=curData.files||[]
let totalFiles=0;Object.values(allData).forEach(d=>{totalFiles+=(d.files||[]).length})
// Sub-items HTML
let subHtml=''
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>`
}
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()">&times;</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
let filesHtml=''
if(curFiles.length){
@ -473,7 +531,7 @@ function openEdit(id,monthIdx){
<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><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>
@ -509,46 +567,63 @@ function openEdit(id,monthIdx){
}
function uploadFiles(eventId, monthKey){
const fi=document.getElementById('editFileInput'),desc=document.getElementById('fileDesc').value.trim()
if(!fi.files.length)return
const MAX=4*1024*1024,obj=getFiles(eventId),arr=obj[monthKey]||[]
const fi=document.getElementById('editFileInput')
const desc=document.getElementById('fileDesc').value.trim()
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
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){
if(f.size>MAX){skipped++;processed++;if(processed===fi.files.length)finish();continue}
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)
}
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){
const obj=getFiles(eventId),arr=obj[monthKey];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)
const allData=getMonthData(eventId),arr=allData[monthKey]?.files
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){
const obj=getFiles(eventId);if(!obj[monthKey])return
obj[monthKey].splice(idx,1);if(!obj[monthKey].length)delete obj[monthKey];setFiles(eventId,obj)
const allData=getMonthData(eventId)
if(!allData[monthKey]||!allData[monthKey].files) return
allData[monthKey].files.splice(idx,1);setMonthData(eventId,allData)
closeEditModal();openEdit(eventId)
}
function autoProgress(){
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){
function saveEdit(id, monthKey){
const e=events.find(x=>x.id===id);if(!e)return
e.s=document.getElementById('editStatus').value
e.p=parseInt(document.getElementById('editProgress').value)
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){
const checks=[]
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 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.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')}})