v56: AI-ассистент + файлы в HTML-отчёте кликабельны

This commit is contained in:
Dauren777 2026-06-09 11:51:02 +00:00
parent bf140a793d
commit 7ba30210e8

View File

@ -90,10 +90,12 @@ tr.warn td{background:#FFFDF5}tr.late td{background:#FFF5F5}
<button class="tab on" data-pg="ev">📋 Мероприятия</button>
<button class="tab" data-pg="an">📊 Аналитика</button>
<button class="tab" data-pg="rp">📥 Отчёты</button>
<button class="tab" data-pg="ai">🤖 AI</button>
</div>
<div class="pg on" id="pg-ev"></div>
<div class="pg" id="pg-an"></div>
<div class="pg" id="pg-rp"></div>
<div class="pg" id="pg-ai"></div>
</div>
<div class="modal-o" id="mo"><div class="modal fm" id="mc"></div></div>
@ -128,7 +130,7 @@ function doLogin(){var e=document.getElementById("lem").value.trim().toLowerCase
function doLogout(){localStorage.removeItem("su");cu=null;document.getElementById("login").style.display="flex";document.getElementById("app").style.display="none"}
function show(){document.getElementById("login").style.display="none";document.getElementById("app").style.display="block";document.getElementById("ul").innerHTML="<b>"+cu.n+"</b> · "+(cu.r==="cur"?"Все":br[cu.b]);switchPg("ev")}
function switchPg(n){document.querySelectorAll(".tab").forEach(function(t){t.classList.remove("on")});document.querySelector('[data-pg="'+n+'"]').classList.add("on");document.querySelectorAll(".pg").forEach(function(p){p.classList.remove("on")});document.getElementById("pg-"+n).classList.add("on");if(n==="ev")re();else if(n==="an")ra();else if(n==="rp")rr()}
function switchPg(n){document.querySelectorAll(".tab").forEach(function(t){t.classList.remove("on")});document.querySelector('[data-pg="'+n+'"]').classList.add("on");document.querySelectorAll(".pg").forEach(function(p){p.classList.remove("on")});document.getElementById("pg-"+n).classList.add("on");if(n==="ev")re();else if(n==="an")ra();else if(n==="rp")rr();else if(n==="ai")rAI()}
document.querySelectorAll(".tab").forEach(function(t){t.addEventListener("click",function(){switchPg(this.dataset.pg)})});
function dls(e){if(e.s==="late")return"late";if(e.s==="done")return"";var p=e.due.split(".");if(p.length===3){var d=new Date(parseInt(p[2]),parseInt(p[1])-1,parseInt(p[0]));if((d-new Date())/86400000<=30)return"warn"}return""}
@ -218,7 +220,7 @@ function rr(){var h='<div class="card"><h3>Сводный отчёт</h3><div cl
h+='<button class="btn btn-sm btn-gn" onclick="exp()">💾 Сохранить</button> <button class="btn btn-sm" onclick="document.getElementById(\'if\').click()">📥 Загрузить</button> <input type="file" id="if" accept=".json" style="display:none" onchange="imp(this)"> <button class="btn btn-sm btn-red" onclick="clr()">🗑 Очистить</button></div></div>';
document.getElementById("pg-rp").innerHTML=h}
function dCSV(){var f=parseInt(document.getElementById("rf").value),t=parseInt(document.getElementById("rt").value);var csv="\uFEFF№;Филиал;Мероприятие;Регион;Статус;Срок\n";(ev||[]).forEach(function(e){reg.forEach(function(r,ri){var d=getMD(e.id,ri,-1),rep="";for(var i=f;i<=t;i++){var m=ms[i];if(d[m]&&d[m].report)rep+=M(i)+": "+d[m].report.replace(/"/g,'""')+"; "}csv+=e.id+';'+br[e.b]+';"'+e.t.replace(/"/g,'""')+'";'+r+';'+st[e.s]+';'+e.due+';"'+rep+'"\n'})});var a=document.createElement("a");a.href=URL.createObjectURL(new Blob([csv]));a.download="otchet.csv";a.click()}
function dHTML(){var f=parseInt(document.getElementById("rf").value),t=parseInt(document.getElementById("rt").value);var h='<!DOCTYPE html><html><head><meta charset="utf-8"><title>Отчёт</title><style>body{font:13px/1.4 Arial;max-width:1000px;margin:0 auto;padding:20px}.ev{border:1px solid #ddd;border-radius:8px;padding:14px;margin-bottom:12px}.badge{display:inline-block;padding:2px 6px;border-radius:4px;font-size:10px;font-weight:700}.g{background:#D1FAE5;color:#065F46}.a{background:#FEF3C7;color:#92400E}.r{background:#FEE2E2;color:#991B1B}.m{background:#f5f5f5;padding:6px 10px;border-radius:4px;margin:4px 0}</style></head><body><h2>Сводный отчёт</h2>';ev.forEach(function(e){var cl={done:"g",warn:"a",late:"r",wait:""}[e.s];h+='<div class="ev"><h3>'+e.id+'. '+esc(e.t)+'</h3><p>'+br[e.b]+' | '+sec[e.sec]+' | Срок: '+e.due+' | <span class="badge '+cl+'">'+st[e.s]+'</span></p>';reg.forEach(function(r,ri){var d=getMD(e.id,ri,-1);for(var i=f;i<=t;i++){var m=ms[i];if(d[m]&&d[m].report){h+='<div class="m"><b>'+M(i)+' — '+r+'</b><p>'+esc(d[m].report)+'</p></div>'}}});h+='</div>'});h+='</body></html>';try{var a=document.createElement("a");a.href=URL.createObjectURL(new Blob(["\uFEFF"+h]));a.download="otchet.html";a.click()}catch(e){alert("Слишком большой")}}
function dHTML(){var f=parseInt(document.getElementById("rf").value),t=parseInt(document.getElementById("rt").value);var h='<!DOCTYPE html><html><head><meta charset="utf-8"><title>Отчёт</title><style>body{font:13px/1.4 Arial;max-width:1000px;margin:0 auto;padding:20px}.ev{border:1px solid #ddd;border-radius:8px;padding:14px;margin-bottom:12px}.badge{display:inline-block;padding:2px 6px;border-radius:4px;font-size:10px;font-weight:700}.g{background:#D1FAE5;color:#065F46}.a{background:#FEF3C7;color:#92400E}.r{background:#FEE2E2;color:#991B1B}.m{background:#f5f5f5;padding:6px 10px;border-radius:4px;margin:4px 0}a.file{color:#0F1218;font-weight:600}</style></head><body><h2>Сводный отчёт</h2>';ev.forEach(function(e){var cl={done:"g",warn:"a",late:"r",wait:""}[e.s];h+='<div class="ev"><h3>'+e.id+'. '+esc(e.t)+'</h3><p>'+br[e.b]+' | '+sec[e.sec]+' | Срок: '+e.due+' | <span class="badge '+cl+'">'+st[e.s]+'</span></p>';reg.forEach(function(ri){var d=getMD(e.id,ri,-1);for(var i=f;i<=t;i++){var m=ms[i];if(d[m]&&(d[m].report||(d[m].files&&d[m].files.length))){h+='<div class="m"><b>'+M(i)+' — '+reg[ri]+'</b>';if(d[m].report)h+='<p>'+esc(d[m].report)+'</p>';if(d[m].qty)h+='<p>Количество: <b>'+d[m].qty+'</b></p>';if(d[m].files&&d[m].files.length)h+='<p>📎 Файлы: '+d[m].files.map(function(f2){return f2.data?'<a class="file" href="'+f2.data+'" download="'+esc(f2.name)+'">📄 '+esc(f2.name)+'</a> ('+(f2.size/1024).toFixed(0)+' КБ)':'📄 '+esc(f2.name)}).join("; ")+'</p>';h+='</div>'}}});if(e.sub)e.sub.forEach(function(s,si){reg.forEach(function(ri){var sd=getMD(e.id,ri,si);for(var i=f;i<=t;i++){var m=ms[i];if(sd[m]&&(sd[m].report||(sd[m].files&&sd[m].files.length))){h+='<div class="m" style="border-left:3px solid #00E5FF;padding-left:8px"><b>'+s.l+') '+esc(s.t.slice(0,50))+'... — '+reg[ri]+' | '+M(i)+'</b>';if(sd[m].report)h+='<p>'+esc(sd[m].report)+'</p>';if(sd[m].qty)h+='<p>Количество: <b>'+sd[m].qty+'</b></p>';if(sd[m].files&&sd[m].files.length)h+='<p>📎 Файлы: '+sd[m].files.map(function(f2){return f2.data?'<a class="file" href="'+f2.data+'" download="'+esc(f2.name)+'">📄 '+esc(f2.name)+'</a> ('+(f2.size/1024).toFixed(0)+' КБ)':'📄 '+esc(f2.name)}).join("; ")+'</p>';h+='</div>'}}})});h+='</div>'});h+='</body></html>';try{var a=document.createElement("a");a.href=URL.createObjectURL(new Blob(["\uFEFF"+h],{type:"text/html"}));a.download="otchet.html";a.click()}catch(e){alert("Отчёт слишком большой. Попробуйте меньший период.")}}
function exp(){var d={events:ev,date:new Date().toISOString(),files:{},sc:{}};for(var i=0;i<localStorage.length;i++){var k=localStorage.key(i);if(k.indexOf("sf_")===0)d.files[k]=localStorage.getItem(k);if(k.indexOf("ss_")===0)d.sc[k]=localStorage.getItem(k)}var a=document.createElement("a");a.href=URL.createObjectURL(new Blob([JSON.stringify(d)]));a.download="backup.json";a.click()}
function imp(inp){if(!inp.files.length)return;var r=new FileReader();r.onload=function(ev){try{var d=JSON.parse(ev.target.result);ev=d.events;ne();se();for(var k in d.files)localStorage.setItem(k,d.files[k]);for(var k in d.sc)localStorage.setItem(k,d.sc[k]);alert("OK. Обновите.");location.reload()}catch(e){alert("Ошибка")}};r.readAsText(inp.files[0])}
function clr(){if(!confirm("Удалить все файлы?"))return;var ks=[];for(var i=0;i<localStorage.length;i++){var k=localStorage.key(i);if(k.indexOf("sf_")===0||k.indexOf("ss_")===0)ks.push(k)}ks.forEach(function(k){localStorage.removeItem(k)});alert("Очищено");rr()}
@ -270,6 +272,15 @@ function upF2(eid,mk,ri,si){mk=ms[mk];var pfx=si>=0?"_s"+si:"",fi=document.getEl
function dlF2(eid,mk,idx,ri,si){si=si||-1;mk=ms[mk];var ad=getMD(eid,ri,si),arr=ad[mk]?ad[mk].files:null;if(!arr||!arr[idx]||!arr[idx].data)return;var 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 rmF2(eid,mk,idx,ri,si){si=si||-1;mk=ms[mk];var ad=getMD(eid,ri,si);if(!ad[mk]||!ad[mk].files)return;ad[mk].files.splice(idx,1);setMD(eid,ad,ri,si);closeM();oe(eid,cm,ri,si>=0?si:undefined)}
// ===== AI AGENT =====
function rAI(){var all=ev||[],now=new Date(),late=all.filter(function(e){return e.s==="late"}),warn=all.filter(function(e){return e.s==="warn"}),done=all.filter(function(e){return e.s==="done"}),near=[],totalFiles=0,totalQty=0;
all.forEach(function(e){if(e.s==="warn"||e.s==="wait"){var p=e.due.split(".");if(p.length===3){var d=new Date(parseInt(p[2]),parseInt(p[1])-1,parseInt(p[0]));var days=Math.round((d-now)/86400000);if(days>0&&days<=30)near.push({e:e,days:days})}}reg.forEach(function(ri){var md=getMD(e.id,ri,-1);for(var k in md){if(md.hasOwnProperty(k)&&md[k]){totalFiles+=(md[k].files||[]).length;totalQty+=md[k].qty||0}if(e.sub)e.sub.forEach(function(si){var sd=getMD(e.id,ri,si);for(var sk in sd){if(sd.hasOwnProperty(sk)&&sd[sk]){totalFiles+=(sd[sk].files||[]).length;totalQty+=sd[sk].qty||0}}})}})});near.sort(function(a,b){return a.days-b.days});var dp=all.length?Math.round(done.length/all.length*100):0;
var h='<div class="card" style="background:var(--b);color:var(--w);border-radius:14px"><h3 style="color:var(--c);font-size:20px">🤖 ИИ-ассистент</h3><p style="color:#9aa3b2;font-size:13px">Автоматический анализ Плана мероприятий на 2026 год</p></div>';
h+='<div class="card"><h3>📊 Общий статус</h3><p style="font-size:14px;margin-bottom:8px">План выполнен на <b style="color:var(--c);font-size:22px">'+dp+'%</b> ('+done.length+' из '+all.length+').</p>';if(late.length)h+='<p style="color:var(--rd);font-weight:700">⚠️ Просрочено: '+late.length+'</p>';if(warn.length)h+='<p style="color:var(--am);font-weight:600">🟡 На контроле: '+warn.length+'</p>';h+='</div>';
if(late.length||near.length){h+='<div class="card" style="border-left:4px solid var(--rd)"><h3>🚨 Критические предупреждения</h3>';if(late.length){h+='<p style="font-weight:700;color:var(--rd)">Просрочено ('+late.length+'):</p>';late.slice(0,5).forEach(function(e){h+='<div style="font-size:12px;margin-bottom:3px">🔴 <b>№'+e.id+'</b> — '+esc(e.t.slice(0,70))+'... <span style="color:var(--rd)">'+e.due+'</span></div>'});if(late.length>5)h+='<p style="font-size:11px;color:var(--g5)">... и ещё '+(late.length-5)+'</p>'}if(near.length){h+='<p style="font-weight:700;color:var(--am);margin-top:10px">Близкие сроки ('+near.length+'):</p>';near.slice(0,5).forEach(function(n){h+='<div style="font-size:12px;margin-bottom:3px">🟡 <b>№'+n.e.id+'</b> — '+esc(n.e.t.slice(0,60))+'... <span style="color:var(--am)">'+n.days+' дн.</span></div>'});if(near.length>5)h+='<p style="font-size:11px;color:var(--g5)">... и ещё '+(near.length-5)+'</p>'}h+='</div>'}
h+='<div class="card" style="border-left:4px solid var(--c)"><h3>💡 Рекомендации</h3><ol style="font-size:13px;line-height:1.8;padding-left:20px">';if(late.length)h+='<li><b>Эскалировать просроченные мероприятия руководителям.</b> Провести совещание с филиалами.</li>';if(near.length>3)h+='<li><b>Усилить контроль за '+near.length+' мероприятиями</b> с приближающимися сроками.</li>';if(dp<50)h+='<li><b>Прогресс ниже 50%.</b> Пересмотреть график Q3-Q4.</li>';if(totalFiles<5)h+='<li><b>Низкая активность по загрузке документов</b> ('+totalFiles+' файлов). Активизировать работу филиалов.</li>';if(totalQty===0)h+='<li><b>Не указаны количественные показатели.</b> Заполнить поля «Количество».</li>';if(dp>=70)h+='<li><b>Положительная динамика.</b> Поддерживать текущий темп.</li>';h+='<li>Использовать фильтр по месяцам на вкладке «Отчёты» для контроля.</li></ol></div>';
document.getElementById("pg-ai").innerHTML=h}
// ===== INIT =====
document.getElementById("mo").addEventListener("click",function(e){if(e.target===this)closeM()});
document.addEventListener("keydown",function(e){if(e.key==="Escape")closeM()});