v60: чистый перезапуск — минимум кода, проверенный JS
This commit is contained in:
parent
a8c443a5c9
commit
9bfc5c2054
322
index.html
322
index.html
@ -11,290 +11,110 @@ body{font:14px/1.4 Arial,sans-serif;color:var(--b);background:var(--g1)}
|
||||
input,select,textarea,button{font:inherit;outline:none}
|
||||
.btn{background:var(--c);color:var(--b);padding:10px 20px;border-radius:8px;font-weight:700;font-size:14px;border:none;cursor:pointer}.btn:hover{opacity:.85}
|
||||
.btn-sm{padding:6px 12px;font-size:12px}.btn-red{background:var(--rd);color:#fff}.btn-gn{background:var(--gn);color:#fff}
|
||||
#login{display:flex;align-items:center;justify-content:center;min-height:100vh;background:var(--b)}
|
||||
#login>div{background:var(--w);border-radius:16px;padding:40px 36px;width:400px;max-width:90vw;text-align:center}
|
||||
#login h1{font-size:22px;font-weight:800;margin-bottom:4px}#login h1 span{color:var(--c)}
|
||||
#login p{color:var(--g5);font-size:13px;margin-bottom:28px}
|
||||
#login input{display:block;width:100%;padding:12px 14px;border:1px solid var(--g2);border-radius:8px;font-size:14px;margin-bottom:14px}
|
||||
#loginBox{display:flex;align-items:center;justify-content:center;min-height:100vh;background:var(--b)}
|
||||
#loginBox>div{background:var(--w);border-radius:16px;padding:40px 36px;width:400px;max-width:90vw;text-align:center}
|
||||
#loginBox h1{font-size:22px;font-weight:800;margin-bottom:4px}
|
||||
#loginBox h1 span{color:var(--c)}
|
||||
#loginBox p{color:var(--g5);font-size:13px;margin-bottom:28px}
|
||||
#loginBox input{display:block;width:100%;padding:12px 14px;border:1px solid var(--g2);border-radius:8px;font-size:14px;margin-bottom:14px}
|
||||
#app{display:none;max-width:1200px;margin:0 auto;padding:16px}
|
||||
.top{display:flex;justify-content:space-between;align-items:center;padding:12px 0;border-bottom:2px solid var(--g2);margin-bottom:16px}
|
||||
.top b{font-size:18px}.top b span{color:var(--c)}
|
||||
.top .r{display:flex;align-items:center;gap:12px;font-size:13px;color:var(--g5)}
|
||||
.notif-btn{position:relative;background:none;border:none;font-size:20px;cursor:pointer}
|
||||
.notif-btn .cnt{position:absolute;top:-4px;right:-6px;background:var(--rd);color:#fff;border-radius:100px;font-size:9px;padding:1px 5px;font-weight:700}
|
||||
.notif-drop{position:absolute;top:48px;right:0;width:380px;max-width:90vw;background:var(--w);border:1px solid var(--g2);border-radius:12px;box-shadow:0 8px 32px rgba(0,0,0,.12);z-index:300;display:none;max-height:400px;overflow-y:auto}
|
||||
.notif-drop.on{display:block}
|
||||
.notif-drop .it{padding:12px 16px;border-bottom:1px solid var(--g1);font-size:12px}.notif-drop .it b{display:block;margin-bottom:2px}.notif-drop .it span{font-size:10px;color:var(--g5)}
|
||||
|
||||
.tabs{display:flex;gap:4px;margin-bottom:16px}
|
||||
.tab{padding:10px 20px;border:none;background:var(--w);cursor:pointer;font-size:14px;font-weight:600;color:var(--g5);border-radius:8px 8px 0 0}.tab.on{color:var(--b);border-bottom:3px solid var(--c)}
|
||||
.tab{padding:10px 20px;border:none;background:var(--w);cursor:pointer;font-size:14px;font-weight:600;color:var(--g5);border-radius:8px 8px 0 0}
|
||||
.tab.on{color:var(--b);border-bottom:3px solid var(--c)}
|
||||
.pg{display:none}.pg.on{display:block}
|
||||
.card{background:var(--w);border-radius:12px;padding:20px;margin-bottom:14px;border:1px solid var(--g2)}
|
||||
.card h3{font-size:16px;margin-bottom:8px}
|
||||
.row{display:flex;gap:12px;flex-wrap:wrap;margin-bottom:12px}
|
||||
.stat{background:var(--w);border-radius:10px;padding:16px 20px;border:1px solid var(--g2);min-width:120px;flex:1;text-align:center}
|
||||
.stat .n{font-size:26px;font-weight:800}.stat .l{font-size:12px;color:var(--g5)}.stat.r .n{color:var(--rd)}.stat.g .n{color:var(--gn)}.stat.a .n{color:var(--am)}
|
||||
.stat .n{font-size:26px;font-weight:800}.stat .l{font-size:12px;color:var(--g5)}
|
||||
.stat.r .n{color:var(--rd)}.stat.g .n{color:var(--gn)}.stat.a .n{color:var(--am)}
|
||||
table{width:100%;border-collapse:collapse}th,td{padding:8px 12px;text-align:left;font-size:13px}
|
||||
th{font-weight:600;color:var(--g5);font-size:11px;text-transform:uppercase;border-bottom:2px solid var(--g2);cursor:pointer}th:hover{color:var(--b)}
|
||||
td{border-bottom:1px solid var(--g2)}
|
||||
th{font-weight:600;color:var(--g5);font-size:11px;text-transform:uppercase;border-bottom:2px solid var(--g2);cursor:pointer}
|
||||
th:hover{color:var(--b)}td{border-bottom:1px solid var(--g2)}
|
||||
tr.rd td{background:#FFF5F5}tr.am td{background:#FFFDF5}
|
||||
.badge{display:inline-block;padding:3px 8px;border-radius:100px;font-size:11px;font-weight:700}
|
||||
.badge.g{background:#D1FAE5;color:#065F46}.badge.a{background:#FEF3C7;color:#92400E}.badge.r{background:#FEE2E2;color:#991B1B}.badge.b{background:#DBEAFE;color:#1E40AF}.badge.w{background:#eee;color:#666}
|
||||
.badge.g{background:#D1FAE5;color:#065F46}.badge.a{background:#FEF3C7;color:#92400E}
|
||||
.badge.r{background:#FEE2E2;color:#991B1B}.badge.b{background:#DBEAFE;color:#1E40AF}.badge.w{background:#eee;color:#666}
|
||||
.fr{display:flex;gap:8px;margin-bottom:10px;flex-wrap:wrap;align-items:center}
|
||||
.fr input,.fr select{padding:8px 12px;border:1px solid var(--g2);border-radius:6px;font-size:13px;background:var(--w)}
|
||||
.fr input{min-width:200px}
|
||||
.modal-o{position:fixed;inset:0;background:rgba(0,0,0,.5);z-index:99;display:none;align-items:center;justify-content:center}.modal-o.on{display:flex}
|
||||
.modal{background:var(--w);border-radius:14px;max-width:700px;width:94vw;max-height:90vh;overflow-y:auto;padding:28px}
|
||||
.modal .x{float:right;border:none;background:none;font-size:24px;cursor:pointer;color:var(--g5)}
|
||||
.modal label{display:block;font-size:12px;font-weight:600;color:var(--g5);margin-bottom:3px;margin-top:10px}
|
||||
.modal input,.modal select,.modal textarea{width:100%;padding:8px 12px;border:1px solid var(--g2);border-radius:6px;font-size:13px;margin-bottom:6px}
|
||||
.modal textarea{min-height:60px;resize:vertical}
|
||||
.meta{display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-bottom:8px;font-size:12px;color:var(--g5)}.meta strong{display:block;color:var(--b);font-size:13px}
|
||||
.mt{display:flex;gap:4px;flex-wrap:wrap;margin-bottom:10px}.mt span{padding:4px 10px;border:1px solid var(--g2);border-radius:100px;font-size:11px;font-weight:600;cursor:pointer}.mt span.on{background:var(--c);color:var(--b)}
|
||||
.si{display:flex;align-items:center;gap:8px;padding:8px 12px;background:var(--g1);border-radius:6px;margin-bottom:4px;font-size:12px}.si .n{font-weight:700;color:var(--c);font-size:14px;min-width:18px}
|
||||
.fl{display:flex;align-items:center;gap:6px;padding:6px 10px;background:var(--g1);border-radius:6px;margin-bottom:3px;font-size:12px}.fl .nm{font-weight:600;cursor:pointer}.fl .nm:hover{color:var(--c)}.fl .sz{font-size:10px;color:var(--g5)}
|
||||
.up{border:2px dashed var(--g2);border-radius:8px;padding:12px;margin-top:6px;text-align:center}
|
||||
.up p{font-size:12px;color:var(--g5);margin-bottom:8px}
|
||||
.up input[type=file]{font-size:12px}
|
||||
.ai-block{background:#E8FCFF;border-radius:6px;padding:10px;margin:10px 0;font-size:12px}
|
||||
.hi{font-size:11px;color:var(--g5);padding:2px 0}.hi .d{display:inline-block;width:5px;height:5px;border-radius:50%;background:var(--c);margin-right:4px;vertical-align:middle}
|
||||
.ai-chat{border:1px solid var(--g2);border-radius:8px;padding:12px;max-height:300px;overflow-y:auto;margin-bottom:10px;background:var(--w)}
|
||||
.ai-chat .msg{margin-bottom:8px;padding:8px 12px;border-radius:8px;font-size:13px;max-width:90%}
|
||||
.ai-chat .user{background:var(--c);color:var(--b);margin-left:auto;text-align:right}
|
||||
.ai-chat .bot{background:var(--g1);color:var(--b)}
|
||||
.ai-input{display:flex;gap:8px}.ai-input input{flex:1;padding:8px 12px;border:1px solid var(--g2);border-radius:6px;font-size:13px}
|
||||
@media(max-width:600px){#app{padding:8px}.row{flex-direction:column}.stat{min-width:auto}.meta{grid-template-columns:1fr}}
|
||||
.mt{display:flex;gap:4px;flex-wrap:wrap;margin-bottom:10px}
|
||||
.mt span{padding:4px 10px;border:1px solid var(--g2);border-radius:100px;font-size:11px;font-weight:600;cursor:pointer}
|
||||
.mt span.on{background:var(--c);color:var(--b)}
|
||||
@media(max-width:600px){#app{padding:8px}.row{flex-direction:column}.stat{min-width:auto}}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div id="login"><div><h1><span>План ПБ</span> 2026</h1><p>АО «Казахтелеком»</p><input id="lem" placeholder="curator@telecom.kz"><input id="lpw" type="password" placeholder="Пароль (любой)"><p class="err" id="lerr" style="color:var(--rd);font-size:12px;display:none">Неверная почта</p><button class="btn" style="width:100%" onclick="doLogin()">Войти</button></div></div>
|
||||
<div id="loginBox"><div>
|
||||
<h1><span>План ПБ</span> 2026</h1>
|
||||
<p>АО «Казахтелеком»</p>
|
||||
<input id="lem" placeholder="curator@telecom.kz">
|
||||
<input id="lpw" type="password" placeholder="Пароль (любой)">
|
||||
<p id="lerr" style="color:var(--rd);font-size:12px;display:none">Неверная почта</p>
|
||||
<button class="btn" style="width:100%" onclick="doLogin()">Войти</button>
|
||||
</div></div>
|
||||
|
||||
<div id="app">
|
||||
<div class="top"><b><span>План ПБ</span> 2026</b>
|
||||
<div class="r"><span id="ul"></span>
|
||||
<div style="position:relative"><button class="notif-btn" onclick="toggleN()">🔔<span class="cnt" id="nc">0</span></button><div class="notif-drop" id="nd"></div></div>
|
||||
<button class="btn btn-sm btn-red" onclick="doLogout()">Выйти</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="top"><b><span>План ПБ</span> 2026</b><div class="r"><span id="ul"></span><button class="btn btn-sm btn-red" onclick="doLogout()">Выйти</button></div></div>
|
||||
<div class="tabs">
|
||||
<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>
|
||||
<button class="tab on" data-pg="ev" onclick="switchPg('ev')">Мероприятия</button>
|
||||
<button class="tab" data-pg="rp" onclick="switchPg('rp')">Отчёты</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 class="pg on" id="pg-ev"></div>
|
||||
<div class="pg" id="pg-rp"></div>
|
||||
</div>
|
||||
|
||||
<div class="modal-o" id="mo"><div class="modal" id="mc"></div></div>
|
||||
|
||||
<script>
|
||||
var sec=["I. Люди. Повышение культуры безопасности","II. Безопасность при эксплуатации оборудования","III. Предупреждение и готовность к ликвидации аварий и ЧС","IV. Информационно-разъяснительная работа","V. Внедрение ИИ и цифровизации"];
|
||||
var br=["Дирекция ПБ","Дивизион «Сеть»","Корпоративный бизнес","Розничный бизнес","Сервисная фабрика","Телеком Комплект","Корпоративный университет","Управление проектами","Цифровой бизнес"];
|
||||
var reg=["Центральный","Алматинский","Южный","Северный","Восточный","Западный"];
|
||||
var st={wait:"Не начато",warn:"В процессе",late:"Просрочено",done:"Исполнено"};
|
||||
var ms=["2026-01","2026-02","2026-03","2026-04","2026-05","2026-06","2026-07","2026-08","2026-09","2026-10","2026-11","2026-12"];
|
||||
var mn=["Янв","Фев","Мар","Апр","Май","Июн","Июл","Авг","Сен","Окт","Ноя","Дек"];
|
||||
function M(i){return mn[parseInt(ms[i].split("-")[1])-1]+" "+ms[i].split("-")[0]}
|
||||
function esc(s){return s.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">")}
|
||||
function nl(s){return esc(s).replace(/\n/g,"<br>")}
|
||||
function sb(s){var m={done:"g",warn:"a",late:"r",wait:"w"};return'<span class="badge '+m[s]+'">'+st[s]+'</span>'}
|
||||
function daysLeft(e){if(e.s==="done")return"";if(e.s==="late")return'<span style="color:var(--rd);font-weight:700">ПРОСРОЧЕНО</span>';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-new Date())/86400000);if(days<0)return'<span style="color:var(--rd);font-weight:700">'+Math.abs(days)+' дн. просрочки</span>';if(days<=7)return'<span style="color:var(--rd);font-weight:700">'+days+' дн.</span>';if(days<=14)return'<span style="color:var(--am);font-weight:700">'+days+' дн.</span>';if(days<=30)return'<span style="color:var(--am)">'+days+' дн.</span>';return days+' дн.'}return""}
|
||||
function rowClass(e){if(e.s==="late")return"rd";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]));var days=Math.round((d-new Date())/86400000);if(days<=14)return"rd";if(days<=30)return"am"}return""}
|
||||
var U={"curator@telecom.kz":{n:"Куратор",b:0,r:"cur"},"dpp@telecom.kz":{n:"Директор ДПБ",b:0,r:"br"}};
|
||||
var cu=null,ev=null;
|
||||
|
||||
var U={"curator@telecom.kz":{n:"Куратор",b:0,r:"cur"},"dpp@telecom.kz":{n:"Директор ДПБ",b:0,r:"br"},"ahmetov@telecom.kz":{n:"Ахметов К.Т.",b:6,r:"br"},"serikov@telecom.kz":{n:"Сериков А.М.",b:1,r:"br"},"nurlanov@telecom.kz":{n:"Нурланов Д.С.",b:8,r:"br"},"aliev@telecom.kz":{n:"Алиев Г.С.",b:4,r:"br"},"tulegenov@telecom.kz":{n:"Тулегенов Е.А.",b:2,r:"br"},"saparov@telecom.kz":{n:"Сапаров А.Д.",b:3,r:"br"},"maratov@telecom.kz":{n:"Маратов Ж.К.",b:5,r:"br"},"iskakov@telecom.kz":{n:"Искаков Р.Н.",b:7,r:"br"}};
|
||||
var cu=null,cm=5,cr=0,esi=-1,ex={},sc2=null,sd2=1;
|
||||
|
||||
function getMD(id,ri,si){ri=ri||0;var k=si>=0?"sf_"+id+"_s"+si+"_r"+ri:"sf_"+id+"_r"+ri;var r=localStorage.getItem(k);return r?JSON.parse(r):{}}
|
||||
function setMD(id,o,ri,si){ri=ri||0;var k=si>=0?"sf_"+id+"_s"+si+"_r"+ri:"sf_"+id+"_r"+ri;localStorage.setItem(k,JSON.stringify(o))}
|
||||
function gsc(id){var r=localStorage.getItem("ss_"+id);return r?JSON.parse(r):[]}
|
||||
function ssc(id,a){localStorage.setItem("ss_"+id,JSON.stringify(a))}
|
||||
|
||||
var ev=null;
|
||||
function le(){var s=localStorage.getItem("se2");if(s){try{ev=JSON.parse(s);return}catch(e){}}ev=[];se()}
|
||||
function se(){localStorage.setItem("se2",JSON.stringify(ev||[]))}
|
||||
function ld(){
|
||||
function doLogin(){
|
||||
var e=document.getElementById("lem").value.trim().toLowerCase();
|
||||
if(U[e]){cu={em:e,n:U[e].n,b:U[e].b,r:U[e].r};localStorage.setItem("su",JSON.stringify(cu));showApp()}else{document.getElementById("lerr").style.display="block"}
|
||||
}
|
||||
function doLogout(){localStorage.removeItem("su");cu=null;document.getElementById("loginBox").style.display="flex";document.getElementById("app").style.display="none"}
|
||||
function showApp(){
|
||||
document.getElementById("loginBox").style.display="none";
|
||||
document.getElementById("app").style.display="block";
|
||||
document.getElementById("ul").innerHTML="<b>"+cu.n+"</b>";
|
||||
loadEvents();
|
||||
}
|
||||
function loadEvents(){
|
||||
var s=localStorage.getItem("se3");
|
||||
if(s){try{ev=JSON.parse(s);renderEv();return}catch(e){}}
|
||||
var x=new XMLHttpRequest();
|
||||
x.open("GET","data.json",true);
|
||||
x.onload=function(){if(x.status===200){try{ev=JSON.parse(x.responseText);se()}catch(e){}};if(ev&&ev.length&&cu)switchPg("ev")};
|
||||
x.onerror=function(){};
|
||||
x.onload=function(){if(x.status===200){try{ev=JSON.parse(x.responseText);localStorage.setItem("se3",JSON.stringify(ev))}catch(e){ev=[]}};renderEv()};
|
||||
x.onerror=function(){ev=[];renderEv()};
|
||||
x.send();
|
||||
}
|
||||
|
||||
function doLogin(){var e=document.getElementById("lem").value.trim().toLowerCase();if(U[e]){cu={em:e,n:U[e].n,b:U[e].b,r:U[e].r};localStorage.setItem("su",JSON.stringify(cu));show()}else document.getElementById("lerr").style.display="block"}
|
||||
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();else if(n==="ai")rAI()}
|
||||
document.querySelectorAll(".tab").forEach(function(t){t.addEventListener("click",function(){switchPg(this.dataset.pg)})});
|
||||
|
||||
// ===== NOTIFICATIONS =====
|
||||
function toggleN(){nu();document.getElementById("nd").classList.toggle("on")}
|
||||
function nu(){var all=ev||[],n=[],now=new Date();
|
||||
all.forEach(function(e){
|
||||
if(e.s==="late"){n.push({c:"🔴",m:"Просрочено: №"+e.id+" — "+e.t.slice(0,50)+"...",t:e.due});return}
|
||||
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]));var days=Math.round((d-now)/86400000);
|
||||
if(days<=1&&days>=0)n.push({c:"🔴",m:"СРОЧНО! 1 день: №"+e.id,ti:e.due});
|
||||
else if(days<=7)n.push({c:"🟠",m:"7 дн: №"+e.id+" — "+e.t.slice(0,40)+"...",ti:e.due});
|
||||
else if(days<=14)n.push({c:"🟡",m:"14 дн: №"+e.id+" — "+e.t.slice(0,40)+"...",ti:e.due});
|
||||
else if(days<=30)n.push({c:"🔵",m:"30 дн: №"+e.id+" — "+e.t.slice(0,40)+"...",ti:e.due});
|
||||
}
|
||||
});
|
||||
n.sort(function(a,b){var o={"🔴":0,"🟠":1,"🟡":2,"🔵":3};return(o[a.c]||9)-(o[b.c]||9)});
|
||||
var el=document.getElementById("nd"),c=document.getElementById("nc");c.textContent=n.length;c.style.display=n.length?"inline-block":"none";
|
||||
el.innerHTML=n.length?n.map(function(x){return'<div class="it"><b>'+x.c+' '+esc(x.m)+'</b><span>Срок: '+x.ti+'</span></div>'}).join(""):'<div class="it" style="text-align:center;color:var(--g5)">Нет уведомлений</div>'}
|
||||
|
||||
// ===== EVENTS =====
|
||||
function toggleEx(id){ex[id]=!ex[id];re()}
|
||||
function ts(id,si,chk){var s=gsc(id);if(chk&&s.indexOf(si)<0)s.push(si);else if(!chk)s=s.filter(function(x){return x!==si});ssc(id,s);var e=null;for(var i=0;i<ev.length;i++)if(ev[i].id===id){e=ev[i];break}if(e&&e.sub){var p=Math.round(s.length/e.sub.length*100);if(s.length===e.sub.length&&e.s!=="done")e.s="done";e.p=Math.max(e.p,p);e.h.push(new Date().toLocaleDateString()+" — "+cu.n+": подпункты "+s.length+"/"+e.sub.length);se()}re()}
|
||||
|
||||
function re(){
|
||||
var sf=document.getElementById("sf2");sf=sf?sf.value:"";
|
||||
var sr2=document.getElementById("sr2");sr2=sr2?sr2.value.toLowerCase():"";
|
||||
var bf=document.getElementById("bf2");bf=bf?bf.value:"";
|
||||
var list=ev||[];
|
||||
if(sf)list=list.filter(function(e){return e.s===sf});
|
||||
if(sr2)list=list.filter(function(e){return e.t.toLowerCase().indexOf(sr2)>=0||br[e.b].toLowerCase().indexOf(sr2)>=0});
|
||||
if(bf)list=list.filter(function(e){return e.b===parseInt(bf)});
|
||||
if(sc2){list.sort(function(a,b){var va=a[sc2],vb=b[sc2];if(typeof va==="string")va=va.toLowerCase(),vb=vb.toLowerCase();return va>vb?sd2:va<vb?-sd2:0})}
|
||||
nu();
|
||||
|
||||
var h='<div class="card"><div class="fr"><input id="sr2" placeholder="Поиск..." oninput="re()"><select id="sf2" onchange="re()"><option value="">Все статусы</option><option value="wait">Не начато</option><option value="warn">В процессе</option><option value="done">Исполнено</option><option value="late">Просрочено</option></select><select id="bf2" onchange="re()"><option value="">Все филиалы</option>'+br.map(function(b,i){return'<option value="'+i+'">'+b+'</option>'}).join("")+'</select><span style="font-size:12px;color:var(--g5);margin-left:auto">'+list.length+' из '+ev.length+'</span></div>';
|
||||
h+='<table><tr><th>№</th><th>Мероприятие</th><th>Ответственные</th><th>Раздел</th><th>Срок</th><th class="srt">Осталось</th><th>Статус</th><th></th></tr>';
|
||||
list.forEach(function(e){
|
||||
var hs=e.sub&&e.sub.length,sc=gsc(e.id),sdd=hs?sc.length:0,stt=hs?e.sub.length:0,cl=rowClass(e);
|
||||
h+='<tr class="'+cl+'"><td>'+e.id+'</td><td style="font-size:12px;max-width:300px">';
|
||||
if(hs)h+='<span onclick="event.stopPropagation();toggleEx('+e.id+')" style="cursor:pointer;margin-right:4px">'+(ex[e.id]?'▼':'▶')+'</span>';
|
||||
h+=esc(e.t);if(hs)h+=' <span style="font-size:10px;color:var(--g5)">('+sdd+'/'+stt+')</span>';
|
||||
h+='</td><td style="font-size:11px">'+nl(e.r)+'</td><td style="font-size:11px">'+sec[e.sec]+'</td><td>'+e.due+'</td><td style="font-size:12px">'+daysLeft(e)+'</td><td>'+sb(e.s)+'</td><td><button class="btn btn-sm" onclick="oe('+e.id+')">📝</button></td></tr>';
|
||||
if(hs&&ex[e.id])e.sub.forEach(function(sb,i){var ch=sc.indexOf(i)>=0;h+='<tr style="background:var(--g1)"><td></td><td style="font-size:11px;padding-left:40px"><input type="checkbox" '+(ch?"checked":"")+' onchange="ts('+e.id+','+i+',this.checked)"> '+sb.l+') '+esc(sb.t)+'</td><td></td><td></td><td></td><td></td><td></td><td></td></tr>'});
|
||||
});
|
||||
h+='</table></div>';
|
||||
function saveEv(){localStorage.setItem("se3",JSON.stringify(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")renderEv();
|
||||
}
|
||||
function renderEv(){
|
||||
var h="<div class='card'><p>Загрузка...</p></div>";
|
||||
if(ev&&ev.length){
|
||||
h="<div class='card'><div class='fr'><span>Всего: "+ev.length+" мероприятий</span></div><table><tr><th>№</th><th>Мероприятие</th><th>Филиал</th><th>Срок</th><th>Статус</th></tr>";
|
||||
ev.forEach(function(e){
|
||||
var scl={done:"g",warn:"a",late:"r",wait:"w"}[e.s]||"w";
|
||||
var stt={done:"Исполнено",warn:"В процессе",late:"Просрочено",wait:"Не начато"}[e.s]||"—";
|
||||
h+="<tr><td>"+e.id+"</td><td style='font-size:12px'>"+escHtml(e.t)+"</td><td style='font-size:11px'>"+e.b+"</td><td>"+e.due+"</td><td><span class='badge "+scl+"'>"+stt+"</span></td></tr>";
|
||||
});
|
||||
h+="</table></div>";
|
||||
}
|
||||
document.getElementById("pg-ev").innerHTML=h;
|
||||
}
|
||||
function escHtml(s){return s.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">")}
|
||||
|
||||
// ===== ANALYTICS =====
|
||||
function ra(){
|
||||
var all=ev||[],done=all.filter(function(e){return e.s==="done"}),late=all.filter(function(e){return e.s==="late"}),warn=all.filter(function(e){return e.s==="warn"}),wait=all.filter(function(e){return e.s==="wait"});
|
||||
var dp=all.length?Math.round(done.length/all.length*100):0;
|
||||
var h='<div class="row"><div class="stat"><div class="l">Всего</div><div class="n">'+all.length+'</div></div><div class="stat g"><div class="l">Исполнено</div><div class="n">'+done.length+'</div><div class="n" style="font-size:14px">'+dp+'%</div></div><div class="stat a"><div class="l">В процессе</div><div class="n">'+warn.length+'</div></div><div class="stat r"><div class="l">Просрочено</div><div class="n">'+late.length+'</div></div><div class="stat"><div class="l">Не начато</div><div class="n">'+wait.length+'</div></div></div>';
|
||||
|
||||
// Branch ranking — TOP
|
||||
var brData=[];br.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,l=it.filter(function(e){return e.s==="late"}).length;brData.push({name:b,pct:p,done:d,total:it.length,late:l})});
|
||||
brData.sort(function(a,b){return a.pct-b.pct});
|
||||
h+='<div class="card"><h3>🏆 Рейтинг филиалов</h3><table><tr><th>Филиал</th><th>Исполнено</th><th>%</th><th>Просрочено</th></tr>';
|
||||
brData.forEach(function(b){h+='<tr><td><b>'+b.name+'</b></td><td>'+b.done+'/'+b.total+'</td><td><span style="color:'+(b.pct>=70?'var(--gn)':b.pct>=40?'var(--am)':'var(--rd)')+';font-weight:700">'+b.pct+'%</span></td><td>'+(b.late?b.late:'—')+'</td></tr>'});
|
||||
h+='</table></div>';
|
||||
|
||||
// TOP problem events
|
||||
var problems=all.filter(function(e){return e.s==="late"}).concat(all.filter(function(e){return e.s==="warn"&&e.p<30})).sort(function(a,b){return a.p-b.p}).slice(0,10);
|
||||
if(problems.length){h+='<div class="card"><h3>⚠️ ТОП проблемных мероприятий</h3><table><tr><th>№</th><th>Мероприятие</th><th>Филиал</th><th>Статус</th><th>Срок</th></tr>';
|
||||
problems.forEach(function(e){h+='<tr><td>'+e.id+'</td><td style="font-size:12px">'+esc(e.t.slice(0,80))+'...</td><td>'+br[e.b]+'</td><td>'+sb(e.s)+'</td><td>'+daysLeft(e)+'</td></tr>'});
|
||||
h+='</table></div>'}
|
||||
|
||||
// Quantities
|
||||
var tq=0,rq={};reg.forEach(function(r,ri){rq[ri]=0});all.forEach(function(e){reg.forEach(function(ri){var d=getMD(e.id,ri,-1);for(var k in d){if(d.hasOwnProperty(k)&&d[k]){tq+=d[k].qty||0;rq[ri]+=d[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]){tq+=sd[sk].qty||0;rq[ri]+=sd[sk].qty||0}}})}})});
|
||||
h+='<div class="card"><h3>📊 Количественные показатели</h3><div class="row"><div class="stat" style="background:var(--c);color:var(--b)"><div class="l">Всего единиц</div><div class="n">'+tq+'</div></div>';reg.forEach(function(r,ri){if(rq[ri])h+='<div class="stat"><div class="l">'+r+'</div><div class="n">'+rq[ri]+'</div></div>'});h+='</div></div>';
|
||||
document.getElementById("pg-an").innerHTML=h;
|
||||
}
|
||||
|
||||
// ===== REPORTS =====
|
||||
function rr(){var h='<div class="card"><h3>Сводный отчёт</h3><div class="fr"><select id="rf">'+ms.map(function(m,i){return'<option value="'+i+'">'+M(i)+'</option>'}).join("")+'</select><span>—</span><select id="rt">'+ms.map(function(m,i){return'<option value="'+i+'"'+(i===11?" selected":"")+'>'+M(i)+'</option>'}).join("")+'</select><button class="btn btn-sm" onclick="dCSV()">CSV</button><button class="btn btn-sm" onclick="dHTML()">HTML</button></div>';
|
||||
var b=0;for(var i=0;i<localStorage.length;i++){var k=localStorage.key(i);if(k.indexOf("sf_")===0)b+=localStorage.getItem(k).length*2}
|
||||
h+='<p style="font-size:12px;color:var(--g5);margin-bottom:8px">Хранилище: '+(b>1048576?(b/1048576).toFixed(1)+" МБ":(b/1024).toFixed(0)+" КБ")+'</p>';
|
||||
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>';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(_,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,'""')+'";'+reg[ri]+';'+st[e.s]+';'+daysLeft(e).replace(/<[^>]*>/g,'')+';'+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}a.flink{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>📎 Файлы: <br>'+d[m].files.map(function(f2){return'<a class="flink" href="'+f2.data+'" download="'+esc(f2.name)+'">📄 '+esc(f2.name)+'</a> ('+(f2.size/1024).toFixed(0)+' КБ) — загружен: '+(f2.date||'')}).join("<br>")+'</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>📎 Файлы: <br>'+sd[m].files.map(function(f2){return'<a class="flink" href="'+f2.data+'" download="'+esc(f2.name)+'">📄 '+esc(f2.name)+'</a> ('+(f2.size/1024).toFixed(0)+' КБ)'}).join("<br>")+'</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(evt){try{var d=JSON.parse(evt.target.result);ev=d.events;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()}
|
||||
|
||||
// ===== AI AGENT =====
|
||||
var chatHistory=[];
|
||||
function rAI(){
|
||||
var all=ev||[],done=all.filter(function(e){return e.s==="done"}),late=all.filter(function(e){return e.s==="late"}),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">Задайте вопрос по исполнению Плана мероприятий</p></div>';
|
||||
h+='<div class="card"><h3>📊 Сводка</h3><div class="row"><div class="stat g"><div class="l">Выполнено</div><div class="n">'+dp+'%</div></div><div class="stat r"><div class="l">Просрочено</div><div class="n">'+late.length+'</div></div><div class="stat"><div class="l">Всего</div><div class="n">'+all.length+'</div></div></div></div>';
|
||||
// Risk prediction
|
||||
var now=new Date(),risks=[];
|
||||
all.forEach(function(e){if(e.s!=="done"&&e.s!=="late"){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);var risk="низкий";if(days<=7)risk="критический";else if(days<=14)risk="высокий";else if(days<=30)risk="средний";if(risk!=="низкий")risks.push({e:e,risk:risk,days:days})}}});
|
||||
risks.sort(function(a,b){return a.days-b.days});
|
||||
if(risks.length){h+='<div class="card" style="border-left:4px solid var(--rd)"><h3>⚠️ Прогноз рисков</h3><table><tr><th>№</th><th>Мероприятие</th><th>Риск</th><th>Осталось</th></tr>';
|
||||
risks.slice(0,10).forEach(function(r){var cl=r.risk==="критический"?"r":r.risk==="высокий"?"a":"w";h+='<tr><td>'+r.e.id+'</td><td style="font-size:12px">'+esc(r.e.t.slice(0,60))+'...</td><td><span class="badge '+cl+'">'+r.risk+'</span></td><td>'+r.days+' дн.</td></tr>'});
|
||||
h+='</table></div>'}
|
||||
h+='<p style="font-size:12px;color:var(--g5);margin:8px 0">Примеры: «просроченные», «риски», «отчёт за квартал», «статус пункта 25», «сводка для руководства»</p>';
|
||||
// Chat
|
||||
h+='<div class="ai-chat" id="aiChat">'+chatHistory.map(function(m){return'<div class="msg '+m.role+'">'+m.text+'</div>'}).join("")+'</div>';
|
||||
h+='<div class="ai-input"><input id="aiQ" placeholder="Ваш вопрос..." onkeydown="if(event.key===\'Enter\')aiAsk()"><button class="btn btn-sm" onclick="aiAsk()">Спросить</button></div>';
|
||||
document.getElementById("pg-ai").innerHTML=h;
|
||||
}
|
||||
|
||||
function aiAsk(){
|
||||
var q=(document.getElementById("aiQ").value||"").trim().toLowerCase();if(!q)return;document.getElementById("aiQ").value="";
|
||||
chatHistory.push({role:"user",text:"<b>Вы:</b> "+esc(q)});
|
||||
var ans=aiAnswer(q);chatHistory.push({role:"bot",text:"<b>🤖 ИИ:</b> "+ans});
|
||||
rAI();var el=document.getElementById("aiChat");if(el)el.scrollTop=el.scrollHeight;
|
||||
}
|
||||
|
||||
function aiAnswer(q){
|
||||
var all=ev||[],now=new Date(),late=all.filter(function(e){return e.s==="late"}),done=all.filter(function(e){return e.s==="done"});
|
||||
if(q.indexOf("просрочен")>=0||q.indexOf("просрочк")>=0){if(!late.length)return "Просроченных мероприятий нет.";return "Просрочено <b>"+late.length+"</b> мероприятий: "+late.map(function(e){return "№"+e.id+" ("+br[e.b]+", срок "+e.due+")"}).join("; ")+"."}
|
||||
if(q.indexOf("риск")>=0||q.indexOf("невыполнен")>=0){var risks=[];all.forEach(function(e){if(e.s!=="done"&&e.s!=="late"){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<=30)risks.push({e:e,days:days})}}});risks.sort(function(a,b){return a.days-b.days});if(!risks.length)return "Мероприятий с высоким риском невыполнения не выявлено.";return "Выявлено <b>"+risks.length+"</b> мероприятий с риском: "+risks.slice(0,5).map(function(r){return "№"+r.e.id+" ("+r.days+" дн)"}).join("; ")+"."}
|
||||
if(q.indexOf("статус")>=0||q.indexOf("пункт")>=0){var num=q.match(/\d+/);if(num){var e=null;for(var i=0;i<all.length;i++)if(all[i].id===parseInt(num[0])){e=all[i];break}if(e)return "Пункт №"+e.id+": <b>"+st[e.s]+"</b>. "+esc(e.t.slice(0,100))+"... Филиал: "+br[e.b]+". Срок: "+e.due+".";return "Пункт №"+num[0]+" не найден."}}
|
||||
if(q.indexOf("сводка")>=0||q.indexOf("руководств")>=0||q.indexOf("правлен")>=0){var dp=all.length?Math.round(done.length/all.length*100):0;return "<b>Краткая сводка для руководства:</b><br>• План выполнен на "+dp+"% ("+done.length+"/"+all.length+")<br>• Просрочено: "+late.length+" мероприятий<br>• Требует внимания руководства: "+(late.length?"эскалация просрочек по филиалам":"текущая ситуация под контролем")}
|
||||
if(q.indexOf("филиал")>=0||q.indexOf("отста")>=0){var brLate={};late.forEach(function(e){brLate[e.b]=(brLate[e.b]||0)+1});var worst=Object.keys(brLate).sort(function(a,b){return brLate[b]-brLate[a]})[0];if(worst>=0)return "Филиал с наибольшим числом просрочек: <b>"+br[parseInt(worst)]+"</b> ("+brLate[worst]+" просрочек). Рекомендуется усилить контроль.";return "Все филиалы работают без просрочек."}
|
||||
if(q.indexOf("отчёт")>=0||q.indexOf("квартал")>=0){return "Для формирования отчёта перейдите на вкладку «Отчёты», выберите период и нажмите CSV или HTML. Также доступен полный бекап через кнопку «💾 Сохранить»."}
|
||||
return "Я могу ответить на вопросы:<br>• «просроченные» — список просрочек<br>• «риски» — прогноз рисков<br>• «статус пункта N» — статус конкретного мероприятия<br>• «сводка» — краткая сводка для руководства<br>• «филиалы» — какой филиал отстаёт<br>• «отчёт» — как сформировать отчётность";
|
||||
}
|
||||
|
||||
// ===== EDIT =====
|
||||
function oe(id,mi,ri,si){
|
||||
if(typeof mi==="number")cm=mi;if(typeof ri==="number")cr=ri;esi=(typeof si==="number")?si:-1;
|
||||
var e=null;for(var i=0;i<ev.length;i++)if(ev[i].id===id){e=ev[i];break}if(!e)return;
|
||||
var m=ms[cm],sc=gsc(e.id),md=getMD(e.id,cr,-1),cd=md[m]||{report:"",files:[]},cfs=cd.files||[];
|
||||
var h='<span class="x" onclick="closeM()">×</span><span class="badge b">'+["I","II","III","IV","V"][e.sec]+'</span>';
|
||||
h+='<h3 style="margin:6px 0">'+esc(e.t)+'</h3>';
|
||||
h+='<div class="meta"><div>Филиал<strong>'+br[e.b]+'</strong></div><div>Срок<strong>'+e.due+' ('+daysLeft(e)+')</strong></div></div>';
|
||||
|
||||
if(e.sub&&e.sub.length){h+='<label>Подпункты</label>';e.sub.forEach(function(sb,i){var isA=esi===i;h+='<div class="si" style="'+(isA?'border:2px solid var(--c)':'')+'"><span class="n">'+sb.l+')</span> <span style="flex:1;font-size:11px">'+esc(sb.t)+'</span><button class="btn btn-sm" onclick="oe('+e.id+','+cm+','+cr+','+i+')" style="'+(isA?'background:var(--c);font-weight:700':'')+'">'+(isA?'📂':'📎')+'</button></div>';
|
||||
if(isA){var sd=getMD(e.id,cr,i),scd=sd[m]||{report:"",files:[]},scfs=scd.files||[];h+='<div style="margin:0 0 10px 12px;padding:12px;background:#E8FCFF;border-radius:6px;border:2px solid var(--c)"><b>'+sb.l+') '+esc(sb.t)+'</b><p style="font-size:10px;color:var(--g5)">'+reg[cr]+' · '+M(cm)+'</p>';h+='<label>Описание</label><textarea id="mr2_s'+i+'">'+esc(scd.report||"")+'</textarea>';h+='<div style="display:flex;gap:8px"><div style="flex:1"><label>Количество</label><input type="number" id="mq2_s'+i+'" min="0" value="'+(scd.qty||0)+'"></div></div>';scfs.forEach(function(f,fi){h+='<div class="fl"><span class="nm" onclick="dlF2('+e.id+','+cm+','+fi+','+cr+','+i+')">📄 '+esc(f.name)+'</span><span class="sz">'+(f.size/1024).toFixed(0)+' КБ · '+f.date+'</span><button onclick="rmF2('+e.id+','+cm+','+fi+','+cr+','+i+')" style="border:none;color:var(--rd);cursor:pointer">×</button></div>'});h+='<div class="up"><p>📤 Загрузить документы (PDF, DOC, XLS, фото, презентации)</p><input type="file" id="fi2_s'+i+'" multiple><button class="btn btn-sm" id="ub2_s'+i+'" onclick="upF2('+e.id+','+cm+','+cr+','+i+')" style="margin-top:6px">Загрузить</button></div></div>'}})}
|
||||
}else{
|
||||
h+='<label>Месяц</label><div class="mt">';ms.forEach(function(_,i){h+='<span class="'+(i===cm?"on":"")+'" onclick="oe('+e.id+','+i+','+cr+')">'+M(i)+'</span>'});h+='</div>';
|
||||
h+='<label>Регион</label><div class="mt">';reg.forEach(function(r,i){h+='<span class="'+(i===cr?"on":"")+'" onclick="oe('+e.id+','+cm+','+i+')">'+r+'</span>'});h+='</div>';
|
||||
h+='<label>Статус</label><select id="es2"><option value="wait"'+(e.s==="wait"?" selected":"")+'>Не начато</option><option value="warn"'+(e.s==="warn"?" selected":"")+'>В процессе</option><option value="done"'+(e.s==="done"?" selected":"")+'>Выполнено</option></select>';
|
||||
h+='<label>Описание</label><textarea id="mr2" placeholder="Опишите проведённую работу...">'+esc(cd.report||"")+'</textarea>';
|
||||
h+='<label>Количество</label><input type="number" id="mq2" min="0" value="'+(cd.qty||0)+'">';
|
||||
cfs.forEach(function(f,i){h+='<div class="fl"><span class="nm" onclick="dlF2('+e.id+','+cm+','+i+','+cr+',-1)">📄 '+esc(f.name)+'</span><span class="sz">'+(f.size/1024).toFixed(0)+' КБ · '+f.date+'</span><button onclick="rmF2('+e.id+','+cm+','+i+','+cr+',-1)" style="border:none;color:var(--rd);cursor:pointer">×</button></div>'});
|
||||
h+='<div class="up"><p>📤 Загрузить документы</p><input type="file" id="fi2" multiple><button class="btn btn-sm" id="ub2" onclick="upF2('+e.id+','+cm+','+cr+',-1)" style="margin-top:6px">Загрузить</button><div class="types" style="font-size:10px;color:var(--g5);margin-top:4px">PDF, DOC/DOCX, XLS/XLSX, фото, презентации</div></div>';
|
||||
}
|
||||
|
||||
if(e.sub&&e.sub.length){h+='<label>Месяц</label><div class="mt">';ms.forEach(function(_,i){h+='<span class="'+(i===cm?"on":"")+'" onclick="oe('+e.id+','+i+','+cr+','+esi+')">'+M(i)+'</span>'});h+='</div>';h+='<label>Регион</label><div class="mt">';reg.forEach(function(r,i){h+='<span class="'+(i===cr?"on":"")+'" onclick="oe('+e.id+','+cm+','+i+','+esi+')">'+r+'</span>'});h+='</div>';h+='<label>Статус</label><select id="es2"><option value="wait"'+(e.s==="wait"?" selected":"")+'>Не начато</option><option value="warn"'+(e.s==="warn"?" selected":"")+'>В процессе</option><option value="done"'+(e.s==="done"?" selected":"")+'>Выполнено</option></select>'}
|
||||
h+='<div class="ai-block"><b>🤖 ИИ:</b> '+esc(e.ai)+'</div>';
|
||||
h+='<div><b>История изменений:</b>';e.h.forEach(function(x){h+='<div class="hi"><span class="d"></span>'+esc(x)+'</div>'});h+='</div>';
|
||||
h+='<div style="margin-top:14px;display:flex;gap:8px"><button class="btn" onclick="sv('+e.id+','+cm+')">Сохранить</button><button class="btn" style="background:var(--g2)" onclick="closeM()">Отмена</button></div>';
|
||||
document.getElementById("mc").innerHTML=h;document.getElementById("mo").classList.add("on");
|
||||
}
|
||||
|
||||
function sv(id,mk){mk=ms[mk];var e=null;for(var i=0;i<ev.length;i++)if(ev[i].id===id){e=ev[i];break}if(!e)return;e.s=document.getElementById("es2").value;var mr=document.getElementById("mr2"),mq=document.getElementById("mq2");if(mr){var ad=getMD(id,cr,-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,cr,-1)}if(e.sub&&e.sub.length){e.sub.forEach(function(_,i){var sr=document.getElementById("mr2_s"+i),sq=document.getElementById("mq2_s"+i);if(sr){var sd=getMD(id,cr,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,cr,i)}})}var now=new Date().toLocaleDateString();e.h.push(now+" — "+cu.n+": "+st[e.s]);if(e.s==="done"&&e.done==="—")e.done=now;se();closeM();re()}
|
||||
function closeM(){document.getElementById("mo").classList.remove("on")}
|
||||
|
||||
function upF2(eid,mk,ri,si){mk=ms[mk];var pfx=si>=0?"_s"+si:"",fi=document.getElementById("fi2"+pfx);if(!fi||!fi.files.length)return;var btn=document.getElementById("ub2"+pfx);if(btn){btn.textContent="...";btn.disabled=true}var ad=getMD(eid,ri,si);if(!ad[mk])ad[mk]={report:"",files:[]};var arr=ad[mk].files,pr=0,sk=0;function fin(){try{setMD(eid,ad,ri,si)}catch(e){alert("Хранилище заполнено")}if(sk)alert(sk+" файлов >3 МБ");closeM();oe(eid,cm,ri,si>=0?si:undefined)}for(var i=0;i<fi.files.length;i++){(function(f){if(f.size>3072*1024){sk++;pr++;if(pr===fi.files.length)fin();return}var r=new FileReader();r.onload=function(evt){arr.push({name:f.name,size:f.size,type:f.type,date:new Date().toLocaleDateString(),user:cu?cu.n:"?",data:evt.target.result});pr++;if(pr===fi.files.length)fin()};r.onerror=function(){pr++;if(pr===fi.files.length)fin()};r.readAsDataURL(f)})(fi.files[i])}}
|
||||
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)}
|
||||
|
||||
document.getElementById("mo").addEventListener("click",function(e){if(e.target===this)closeM()});
|
||||
document.addEventListener("keydown",function(e){if(e.key==="Escape")closeM()});
|
||||
document.addEventListener("click",function(e){if(!e.target.closest(".notif-btn")&&!e.target.closest(".notif-drop"))document.getElementById("nd").classList.remove("on")});
|
||||
|
||||
le();ld();var su=localStorage.getItem("su");if(su){try{cu=JSON.parse(su);if(cu)show()}catch(e){}}
|
||||
var su=localStorage.getItem("su");if(su){try{cu=JSON.parse(su);if(cu)showApp()}catch(e){}}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user