v41: количественные показатели — поле «Количество» в отчётах и аналитике
This commit is contained in:
parent
26e483874d
commit
7ebb2950d0
15
index.html
15
index.html
@ -252,7 +252,15 @@ function renderMyEvents(){invalidateCache();
|
||||
function toggleSub(eid,si,chk){var sc=getSC(eid);if(chk&&sc.indexOf(si)<0)sc.push(si);else if(!chk)sc=sc.filter(function(x){return x!==si});setSC(eid,sc);var e=null;for(var i=0;i<events.length;i++)if(events[i].id===eid){e=events[i];break}if(e&&e.sub){var p=Math.round(sc.length/e.sub.length*100);if(sc.length===e.sub.length&&e.s!=="done")e.s="done";e.p=Math.max(e.p,p);e.h.push(new Date().toLocaleDateString()+" — подпункты "+sc.length+"/"+e.sub.length);saveEv()}renderMyEvents()}
|
||||
|
||||
// ===== ANALYTICS =====
|
||||
function renderAnalytics(){var all=events||[],h='<div class="stats-row"><div class="stat-card"><div class="lbl">Всего</div><div class="num">'+all.length+'</div></div><div class="stat-card green"><div class="lbl">Исполнено</div><div class="num">'+all.filter(function(e){return e.s==="done"}).length+'</div></div><div class="stat-card amber"><div class="lbl">На контроле</div><div class="num">'+all.filter(function(e){return e.s==="warn"}).length+'</div></div><div class="stat-card red"><div class="lbl">Просрочено</div><div class="num">'+all.filter(function(e){return e.s==="late"}).length+'</div></div></div>';h+='<div class="panel"><h3>Дивизионы</h3>';branches.forEach(function(b,i){var it=all.filter(function(e){return e.b===i}),d=it.filter(function(e){return e.s==="done"}).length,p=it.length?Math.round(d/it.length*100):0;h+='<div class="pct-bar" style="margin-bottom:6px"><span style="width:240px;font-size:13px;font-weight:600">'+b+'</span><div class="track" style="flex:1"><div class="fill" style="width:'+p+'%;background:'+(p>=50?"var(--green)":p>=25?"var(--amber)":"var(--red)")+'"></div></div><span style="font-weight:700;font-size:13px">'+p+'% ('+d+'/'+it.length+')</span></div>'});h+='</div>';h+='<div class="panel"><h3>Просрочено</h3><table><tr><th>№</th><th>Мероприятие</th><th>Филиал</th><th>Срок</th></tr>';all.filter(function(e){return e.s==="late"}).forEach(function(e){h+='<tr><td>'+e.id+'</td><td>'+esc(e.t.slice(0,80))+'...</td><td>'+branches[e.b]+'</td><td style="color:var(--red);font-weight:700">'+e.due+'</td></tr>'});h+='</table></div>';document.getElementById("tab-analytics").innerHTML=h;cache.analytics=true}
|
||||
function renderAnalytics(){var all=events||[],h='<div class="stats-row"><div class="stat-card"><div class="lbl">Всего</div><div class="num">'+all.length+'</div></div><div class="stat-card green"><div class="lbl">Исполнено</div><div class="num">'+all.filter(function(e){return e.s==="done"}).length+'</div></div><div class="stat-card amber"><div class="lbl">На контроле</div><div class="num">'+all.filter(function(e){return e.s==="warn"}).length+'</div></div><div class="stat-card red"><div class="lbl">Просрочено</div><div class="num">'+all.filter(function(e){return e.s==="late"}).length+'</div></div></div>';
|
||||
// Aggregate quantities
|
||||
var totalQty=0,regQty={};regions.forEach(function(r,ri){regQty[ri]=0});
|
||||
all.forEach(function(e){regions.forEach(function(ri){var d=getMD(e.id,ri,-1);for(var k in d){if(d.hasOwnProperty(k)&&d[k])totalQty+=d[k].qty||0;regQty[ri]+=d[k].qty||0}if(e.sub)e.sub.forEach(function(s,si){var sd=getMD(e.id,ri,si);for(var sk in sd){if(sd.hasOwnProperty(sk)&&sd[sk]){totalQty+=sd[sk].qty||0;regQty[ri]+=sd[sk].qty||0}}})})});
|
||||
h+='<div class="panel"><h3>📊 Количественные показатели</h3><div class="stats-row"><div class="stat-card blue"><div class="lbl">Всего единиц</div><div class="num">'+totalQty+'</div></div>';
|
||||
regions.forEach(function(r,ri){if(regQty[ri])h+='<div class="stat-card"><div class="lbl">'+r+'</div><div class="num">'+regQty[ri]+'</div></div>'});
|
||||
h+='</div></div>';
|
||||
// Branch ranking
|
||||
h+='<div class="panel"><h3>Дивизионы</h3>';branches.forEach(function(b,i){var it=all.filter(function(e){return e.b===i}),d=it.filter(function(e){return e.s==="done"}).length,p=it.length?Math.round(d/it.length*100):0;h+='<div class="pct-bar" style="margin-bottom:6px"><span style="width:240px;font-size:13px;font-weight:600">'+b+'</span><div class="track" style="flex:1"><div class="fill" style="width:'+p+'%;background:'+(p>=50?"var(--green)":p>=25?"var(--amber)":"var(--red)")+'"></div></div><span style="font-weight:700;font-size:13px">'+p+'% ('+d+'/'+it.length+')</span></div>'});h+='</div>';h+='<div class="panel"><h3>Просрочено</h3><table><tr><th>№</th><th>Мероприятие</th><th>Филиал</th><th>Срок</th></tr>';all.filter(function(e){return e.s==="late"}).forEach(function(e){h+='<tr><td>'+e.id+'</td><td>'+esc(e.t.slice(0,80))+'...</td><td>'+branches[e.b]+'</td><td style="color:var(--red);font-weight:700">'+e.due+'</td></tr>'});h+='</table></div>';document.getElementById("tab-analytics").innerHTML=h;cache.analytics=true}
|
||||
|
||||
// ===== JOURNAL =====
|
||||
function renderJournal(){var log=JSON.parse(localStorage.getItem("samruk_log")||"[]").reverse(),h='<div class="panel"><h3>Журнал действий</h3>';if(!log.length)h+='<p style="color:var(--gray-500)">Записей пока нет</p>';else{h+='<table><tr><th>Время</th><th>Пользователь</th><th>Действие</th></tr>';log.forEach(function(l){h+='<tr><td style="font-size:11px">'+new Date(l.ts).toLocaleString()+'</td><td>'+esc(l.user)+'</td><td>'+esc(l.action)+(l.detail?' — '+esc(l.detail):'')+'</td></tr>'});h+='</table>'}h+='</div>';document.getElementById("tab-journal").innerHTML=h;cache.journal=true}
|
||||
@ -280,11 +288,12 @@ function openEdit(id,mi,ri,si){
|
||||
|
||||
h+='<div style="border-top:1px solid var(--gray-200);padding-top:14px;margin-top:10px"><b>📎 '+regions[curRegion]+'</b>'+mh+rh;
|
||||
h+='<div class="field"><label>Текст за '+M(curMonth)+'</label><textarea id="mr" style="min-height:76px">'+esc(cd.report||"")+'</textarea></div>';
|
||||
h+='<div class="field"><label>Количество (совещаний / работников / объектов)</label><input type="number" id="mq" min="0" value="'+(cd.qty||0)+'" style="width:140px"></div>';
|
||||
cfs.forEach(function(f,i){h+='<div class="file-row"><span class="file-info"><span class="file-name" onclick="dlF('+e.id+','+curMonth+','+i+','+curRegion+',-1)">📄 '+esc(f.name)+'</span>'+(f.desc?'<span class="file-desc">'+esc(f.desc)+'</span>':'')+'</span><span class="file-meta">'+(f.size/1024).toFixed(0)+' КБ</span><button class="file-del" onclick="rmF('+e.id+','+curMonth+','+i+','+curRegion+',-1)">×</button></div>'});
|
||||
h+='<div class="upload-row"><input type="text" id="fd" placeholder="Описание"><input type="file" id="fi" multiple><button class="btn btn-sm" id="ub" onclick="upF('+e.id+','+curMonth+','+curRegion+',-1)">📤 Загрузить</button></div>';
|
||||
h+='<p style="font-size:11px;color:var(--gray-500);margin-top:4px">Формы: '+esc(e.dname)+'</p></div>';
|
||||
|
||||
if(e.sub&&e.sub.length){h+='<div style="border-top:2px solid var(--cyan);padding-top:14px;margin-top:14px"><b>📋 Подпункты</b><p style="font-size:11px;color:var(--gray-500);margin:4px 0 10px">Нажмите 📎 для управления файлами</p>';e.sub.forEach(function(s,i){var ch=sc.indexOf(i)>=0,sd=getMD(e.id,curRegion,i),scd=sd[cm]||{report:"",files:[]},scfs=scd.files||[],isA=editSubIdx===i;h+='<div class="sub-item" style="flex-wrap:wrap;padding:10px 14px;margin-bottom:6px;'+(isA?'border:2px solid var(--cyan)':'')+'"><input type="checkbox" id="sc_'+i+'" '+(ch?"checked":"")+'><span class="sub-label">'+s.l+')</span><span class="sub-text" style="flex:1">'+esc(s.t)+'</span><span style="font-size:11px;color:var(--gray-500);margin-right:6px">Файлов: '+scfs.length+'</span><button class="btn btn-sm" onclick="openEdit('+e.id+','+curMonth+','+curRegion+','+i+')" style="font-size:12px;'+(isA?'background:var(--cyan);font-weight:700':'')+'">'+(isA?'📂':'📎')+'</button></div>';if(isA){h+='<div style="margin:0 0 14px 20px;padding:14px;background:var(--cyan-50);border-radius:8px;border:2px solid var(--cyan)"><b>'+s.l+') '+esc(s.t)+'</b><p style="font-size:11px;color:var(--gray-500);margin-bottom:8px">'+regions[curRegion]+' · '+M(curMonth)+'</p>';h+='<div class="field"><label>Текст</label><textarea id="mr_s'+i+'" style="min-height:56px">'+esc(scd.report||"")+'</textarea></div>';scfs.forEach(function(f,fi){h+='<div class="file-row"><span class="file-name" onclick="dlF('+e.id+','+curMonth+','+fi+','+curRegion+','+i+')">📄 '+esc(f.name)+'</span><span class="file-meta">'+(f.size/1024).toFixed(0)+' КБ</span><button class="file-del" onclick="rmF('+e.id+','+curMonth+','+fi+','+curRegion+','+i+')">×</button></div>'});h+='<div class="upload-row"><input type="text" id="fd_s'+i+'" placeholder="Описание"><input type="file" id="fi_s'+i+'" multiple><button class="btn btn-sm" id="ub_s'+i+'" onclick="upF('+e.id+','+curMonth+','+curRegion+','+i+')">📤</button></div></div>'}});h+='</div>'}
|
||||
if(e.sub&&e.sub.length){h+='<div style="border-top:2px solid var(--cyan);padding-top:14px;margin-top:14px"><b>📋 Подпункты</b><p style="font-size:11px;color:var(--gray-500);margin:4px 0 10px">Нажмите 📎 для управления файлами</p>';e.sub.forEach(function(s,i){var ch=sc.indexOf(i)>=0,sd=getMD(e.id,curRegion,i),scd=sd[cm]||{report:"",files:[]},scfs=scd.files||[],isA=editSubIdx===i;h+='<div class="sub-item" style="flex-wrap:wrap;padding:10px 14px;margin-bottom:6px;'+(isA?'border:2px solid var(--cyan)':'')+'"><input type="checkbox" id="sc_'+i+'" '+(ch?"checked":"")+'><span class="sub-label">'+s.l+')</span><span class="sub-text" style="flex:1">'+esc(s.t)+'</span><span style="font-size:11px;color:var(--gray-500);margin-right:6px">Файлов: '+scfs.length+'</span><button class="btn btn-sm" onclick="openEdit('+e.id+','+curMonth+','+curRegion+','+i+')" style="font-size:12px;'+(isA?'background:var(--cyan);font-weight:700':'')+'">'+(isA?'📂':'📎')+'</button></div>'; if(isA){h+='<div style="margin:0 0 14px 20px;padding:14px;background:var(--cyan-50);border-radius:8px;border:2px solid var(--cyan)"><b>'+s.l+') '+esc(s.t)+'</b><p style="font-size:11px;color:var(--gray-500);margin-bottom:8px">'+regions[curRegion]+' · '+M(curMonth)+'</p>';h+='<div class="field"><label>Текст</label><textarea id="mr_s'+i+'" style="min-height:56px">'+esc(scd.report||"")+'</textarea></div>';h+='<div class="field"><label>Количество</label><input type="number" id="mq_s'+i+'" min="0" value="'+(scd.qty||0)+'" style="width:120px"></div>';scfs.forEach(function(f,fi){h+='<div class="file-row"><span class="file-name" onclick="dlF('+e.id+','+curMonth+','+fi+','+curRegion+','+i+')">📄 '+esc(f.name)+'</span><span class="file-meta">'+(f.size/1024).toFixed(0)+' КБ</span><button class="file-del" onclick="rmF('+e.id+','+curMonth+','+fi+','+curRegion+','+i+')">×</button></div>'});h+='<div class="upload-row"><input type="text" id="fd_s'+i+'" placeholder="Описание"><input type="file" id="fi_s'+i+'" multiple><button class="btn btn-sm" id="ub_s'+i+'" onclick="upF('+e.id+','+curMonth+','+curRegion+','+i+')">📤</button></div></div>'}});h+='</div>'}
|
||||
|
||||
h+='<div class="ai-block"><h4>🤖 ИИ</h4>'+esc(e.ai)+'</div>';
|
||||
h+='<div><b>История:</b><div>';e.h.forEach(function(x){h+='<div class="history-item"><div class="dot"></div>'+esc(x)+'</div>'});h+='</div></div>';
|
||||
@ -293,7 +302,7 @@ function openEdit(id,mi,ri,si){
|
||||
document.getElementById("editModalOverlay").classList.add("open");
|
||||
}
|
||||
|
||||
function saveEdit(id,mk){mk=months[mk];var e=null;for(var i=0;i<events.length;i++)if(events[i].id===id){e=events[i];break}if(!e)return;e.s=document.getElementById("es").value;e.p=parseInt(document.getElementById("ep").value);var cmt=(document.getElementById("ec").value||"").trim(),mr=document.getElementById("mr");if(mr){var ad=getMD(id,curRegion,-1);if(!ad[mk])ad[mk]={report:"",files:[]};ad[mk].report=mr.value;setMD(id,ad,curRegion,-1)}if(e.sub&&e.sub.length){var cks=[];e.sub.forEach(function(_,i){var el=document.getElementById("sc_"+i);if(el&&el.checked)cks.push(i);var sr=document.getElementById("mr_s"+i);if(sr){var sd=getMD(id,curRegion,i);if(!sd[mk])sd[mk]={report:"",files:[]};sd[mk].report=sr.value;setMD(id,sd,curRegion,i)}});setSC(id,cks)}var now=new Date().toLocaleDateString();e.h.push(now+" — "+curUser.name+": "+sm[e.s]+", "+e.p+"%");if(e.s==="done"&&e.done==="\u2014")e.done=now;saveEv();addLog("изменил",id,sm[e.s]);closeEM();invalidateCache();renderAll()}
|
||||
function saveEdit(id,mk){mk=months[mk];var e=null;for(var i=0;i<events.length;i++)if(events[i].id===id){e=events[i];break}if(!e)return;e.s=document.getElementById("es").value;e.p=parseInt(document.getElementById("ep").value);var cmt=(document.getElementById("ec").value||"").trim(),mr=document.getElementById("mr"),mq=document.getElementById("mq");if(mr){var ad=getMD(id,curRegion,-1);if(!ad[mk])ad[mk]={report:"",files:[]};ad[mk].report=mr.value;if(mq)ad[mk].qty=parseInt(mq.value)||0;setMD(id,ad,curRegion,-1)}if(e.sub&&e.sub.length){var cks=[];e.sub.forEach(function(_,i){var el=document.getElementById("sc_"+i);if(el&&el.checked)cks.push(i);var sr=document.getElementById("mr_s"+i),sq=document.getElementById("mq_s"+i);if(sr){var sd=getMD(id,curRegion,i);if(!sd[mk])sd[mk]={report:"",files:[]};sd[mk].report=sr.value;if(sq)sd[mk].qty=parseInt(sq.value)||0;setMD(id,sd,curRegion,i)}});setSC(id,cks)}var now=new Date().toLocaleDateString();e.h.push(now+" — "+curUser.name+": "+sm[e.s]+", "+e.p+"%");if(e.s==="done"&&e.done==="\u2014")e.done=now;saveEv();addLog("изменил",id,sm[e.s]);closeEM();invalidateCache();renderAll()}
|
||||
function closeEM(){document.getElementById("editModalOverlay").classList.remove("open")}
|
||||
|
||||
// ===== FILE OPS =====
|
||||
|
||||
Loading…
Reference in New Issue
Block a user