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="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()">×</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')}})
|
||||
|
||||
Loading…
Reference in New Issue
Block a user