v96.2: fix orphaned saveUsers, balanced braces
This commit is contained in:
parent
452f1a3752
commit
aeb614e854
848
index.html
848
index.html
@ -270,7 +270,855 @@ function subResp(r,letter){
|
|||||||
}
|
}
|
||||||
}else{
|
}else{
|
||||||
if(found)cur+=String.fromCharCode(10)+p
|
if(found)cur+=String.fromCharCode(10)+p
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return cur||""
|
||||||
|
}
|
||||||
|
|
||||||
|
function init(){
|
||||||
|
try{
|
||||||
|
var su=localStorage.getItem("su");
|
||||||
|
if(su)cu=JSON.parse(su);
|
||||||
|
}catch(e){}
|
||||||
|
loadEv();
|
||||||
|
var fb=document.getElementById("fb");
|
||||||
|
if(fb){
|
||||||
|
for(var i=0;i<brs.length;i++){
|
||||||
|
var o=document.createElement("option");
|
||||||
|
o.value=i;o.textContent=brs[i];
|
||||||
|
fb.appendChild(o)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(cu)showApp()
|
||||||
|
}
|
||||||
|
|
||||||
|
function doLogin(){
|
||||||
|
var e=document.getElementById("lem").value.trim().toLowerCase();
|
||||||
|
var k=e.split("@")[0];
|
||||||
|
var u=USR[k];
|
||||||
|
if(u){
|
||||||
|
cu={n:u.n,bg:u.bg};
|
||||||
|
localStorage.setItem("su",JSON.stringify(cu));
|
||||||
|
showApp()
|
||||||
|
}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 showApp(){
|
||||||
|
document.getElementById("login").style.display="none";
|
||||||
|
document.getElementById("app").style.display="block";
|
||||||
|
document.getElementById("su_name").textContent=cu?cu.n:"";
|
||||||
|
var unav=document.getElementById("snav_users");
|
||||||
|
if(unav)unav.style.display=cu&&cu.bg===0?"":"none";
|
||||||
|
var hsenav=document.getElementById("snav_hse");
|
||||||
|
if(hsenav)hsenav.style.display=cu&&cu.bg===0?"":"none";
|
||||||
|
switchTab("events")
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadEv(){
|
||||||
|
evs=[];
|
||||||
|
if(typeof ALL_EVENTS!=="undefined"&&ALL_EVENTS&&ALL_EVENTS.length){
|
||||||
|
var saved=localStorage.getItem("se5");
|
||||||
|
var smap={};
|
||||||
|
if(saved){
|
||||||
|
try{
|
||||||
|
var sd=JSON.parse(saved);
|
||||||
|
for(var i=0;i<sd.length;i++){
|
||||||
|
if(sd[i]&&sd[i].id)smap[sd[i].id]=sd[i]
|
||||||
|
}
|
||||||
|
}catch(e){}
|
||||||
|
}
|
||||||
|
for(var i=0;i<ALL_EVENTS.length;i++){
|
||||||
|
var e=ALL_EVENTS[i];
|
||||||
|
if(smap[e.id]){
|
||||||
|
e.s=smap[e.id].s||e.s;
|
||||||
|
if(smap[e.id].p!==undefined)e.p=smap[e.id].p;
|
||||||
|
if(smap[e.id].done)e.done=smap[e.id].done;
|
||||||
|
if(smap[e.id].h)e.h=smap[e.id].h;
|
||||||
|
if(smap[e.id].q!==undefined)e.q=smap[e.id].q;
|
||||||
|
if(smap[e.id].n!==undefined)e.n=smap[e.id].n
|
||||||
|
}
|
||||||
|
evs.push(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
storCheck()
|
||||||
|
}
|
||||||
|
function saveEv(){
|
||||||
|
var out=[];
|
||||||
|
for(var i=0;i<evs.length;i++){
|
||||||
|
out.push({id:evs[i].id,s:evs[i].s,p:evs[i].p,done:evs[i].done,h:evs[i].h,q:evs[i].q,n:evs[i].n})
|
||||||
|
}
|
||||||
|
try{localStorage.setItem("se5",JSON.stringify(out))}catch(e){}
|
||||||
|
storCheck()
|
||||||
|
}
|
||||||
|
|
||||||
|
function switchTab(t){
|
||||||
|
tab=t;
|
||||||
|
var tabs=["events","analytics","reports","ai","users","hse"];
|
||||||
|
var tn={events:"Мероприятия",analytics:"Аналитика",reports:"Отчётность",ai:"Джарвис",users:"Учётные записи",hse:"HSE.sk.kz"};
|
||||||
|
for(var i=0;i<tabs.length;i++){
|
||||||
|
var el=document.getElementById("tab_"+tabs[i]);
|
||||||
|
if(el)el.style.display="none";
|
||||||
|
var nav=document.getElementById("snav_"+tabs[i]);
|
||||||
|
if(nav)nav.className=""
|
||||||
|
}
|
||||||
|
var sel=document.getElementById("tab_"+t);
|
||||||
|
if(sel)sel.style.display="block";
|
||||||
|
document.getElementById("page_title").textContent=tn[t];
|
||||||
|
if(t==="events")renderEv();
|
||||||
|
if(t==="analytics")renderAnalytics();
|
||||||
|
if(t==="reports")renderReports();
|
||||||
|
if(t==="ai")renderAI()
|
||||||
|
if(t==="hse"){var hm=document.getElementById("hse_month");if(hm&&!hm.value)hm.value=new Date().toISOString().slice(0,7)}
|
||||||
|
if(t==="users")renderUsers()
|
||||||
|
}
|
||||||
|
|
||||||
|
function daysRem(due){
|
||||||
|
if(!due||due==="\u2014")return 999;
|
||||||
|
var p=due.split(".");
|
||||||
|
if(p.length!==3)return 999;
|
||||||
|
var d=new Date(parseInt(p[2],10),parseInt(p[1],10)-1,parseInt(p[0],10));
|
||||||
|
var now=new Date();
|
||||||
|
now.setHours(0,0,0,0);
|
||||||
|
return Math.floor((d-now)/86400000)
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderEv(){
|
||||||
|
var sea=document.getElementById("sea").value.toLowerCase().trim();
|
||||||
|
var fs=document.getElementById("fs").value;
|
||||||
|
var fb=document.getElementById("fb").value;
|
||||||
|
var fl=evs;
|
||||||
|
if(sea){
|
||||||
|
fl=fl.filter(function(e){
|
||||||
|
return e.t.toLowerCase().indexOf(sea)!==-1||
|
||||||
|
e.r.toLowerCase().indexOf(sea)!==-1||
|
||||||
|
e.dname.toLowerCase().indexOf(sea)!==-1||
|
||||||
|
String(e.id).indexOf(sea)!==-1
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if(fs)fl=fl.filter(function(e){return e.s===fs});
|
||||||
|
if(fb!=="")fl=fl.filter(function(e){return String(e.b)===fb});
|
||||||
|
document.getElementById("sc").textContent="Показано: "+fl.length+" из "+evs.length;
|
||||||
|
var h="";
|
||||||
|
var lastSec=-1;
|
||||||
|
for(var i=0;i<fl.length;i++){
|
||||||
|
var e=fl[i];
|
||||||
|
if(e.sec!==lastSec){
|
||||||
|
if(lastSec!==-1)h+="<tr><td colspan='7' style='padding:4px;border:none'></td></tr>";
|
||||||
|
h+="<tr><td colspan='7' style='padding:0;border:none'><div class='sec-h'>"+esc(secs[e.sec])+"</div></td></tr>";
|
||||||
|
lastSec=e.sec
|
||||||
|
}
|
||||||
|
var dr=daysRem(e.due);
|
||||||
|
var rowCl=e.s==="done"?"tr-green":dr<=0&&e.s!=="done"?"tr-red":dr<=14?"tr-amber":"";
|
||||||
|
var cl=stc[e.s]||"w";
|
||||||
|
var drText=dr<=0&&e.s!=="done"?"Просрочено на "+Math.abs(dr)+" дн.":e.s==="done"?"Готово":dr===999?"\u2014":dr+" дн.";
|
||||||
|
var hasSub=e.sub&&e.sub.length>0;
|
||||||
|
h+="<tr class='"+rowCl+"'>";
|
||||||
|
h+="<td style='font-weight:700;font-size:12px'>"+e.id+"</td>";
|
||||||
|
h+="<td><div style='font-size:12px;line-height:1.3'>"+esc(e.t)+"</div><div style='font-size:10px;color:#64748B;margin-top:2px'>"+esc(e.dname)+"</div></td>";
|
||||||
|
h+="<td style='font-size:11px'>"+esc(nl2c(e.r))+"</td>";
|
||||||
|
h+="<td style='font-size:12px;white-space:nowrap'>"+e.due+" <span style='font-size:10px;color:#64748B'>("+drText+")</span></td>";
|
||||||
|
if(hasSub){h+="<td></td><td></td>"}
|
||||||
|
else{
|
||||||
|
h+="<td><span class='badge "+cl+"'>"+stn[e.s]+"</span></td>";
|
||||||
|
h+="<td><button class='btn btn-sm' style='padding:4px 12px;font-size:11px' onclick='openEv("+e.id+")'>Открыть</button></td>"
|
||||||
|
}
|
||||||
|
h+="</tr>";
|
||||||
|
if(hasSub){
|
||||||
|
for(var si=0;si<e.sub.length;si++){
|
||||||
|
var sr=subResp(e.r,e.sub[si].l)||nl2c(e.r);
|
||||||
|
var ss=e.s==="wait"?"warn":e.s;
|
||||||
|
var scl=stc[ss]||"a";
|
||||||
|
h+="<tr class='"+rowCl+" sub-item-row'><td style='padding-left:24px;font-size:11px;color:#64748B'>"+e.id+"."+esc(e.sub[si].l)+"</td>";
|
||||||
|
h+="<td style='font-size:11px'>"+esc(e.sub[si].t)+"</td>";
|
||||||
|
h+="<td style='font-size:11px'>"+esc(sr)+"</td>";
|
||||||
|
h+="<td style='font-size:11px'>"+e.due+"</td>";
|
||||||
|
h+="<td><span class='badge "+scl+"' style='font-size:10px'>"+stn[ss]+"</span></td>";
|
||||||
|
h+="<td><button class='btn btn-sm' style='padding:4px 10px;font-size:11px' onclick='openEv("+e.id+","+si+")'>Открыть</button></td></tr>"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(!h)h="<p style='color:#64748B;padding:20px;text-align:center'>Нет мероприятий</p>";
|
||||||
|
document.getElementById("ev_content").innerHTML="<table><tr><th>N</th><th>Мероприятие</th><th>Ответственные</th><th>Срок</th><th>Статус</th><th></th></tr>"+h+"</table>"
|
||||||
|
}
|
||||||
|
|
||||||
|
function togSub(id){
|
||||||
|
var el=document.getElementById("sub_"+id);
|
||||||
|
if(!el)return;
|
||||||
|
var arr=document.getElementById("arr_"+id);
|
||||||
|
if(el.style.display==="none"){
|
||||||
|
el.style.display="block";
|
||||||
|
if(arr)arr.innerHTML="▼"
|
||||||
|
}else{
|
||||||
|
el.style.display="none";
|
||||||
|
if(arr)arr.innerHTML="▸"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function chkSub(id,idx,val){
|
||||||
|
var key="ss_"+id;
|
||||||
|
var ss=localStorage.getItem(key);
|
||||||
|
var arr=[];
|
||||||
|
if(ss){try{arr=JSON.parse(ss)}catch(e){}}
|
||||||
|
arr[idx]=val?true:false;
|
||||||
|
try{localStorage.setItem(key,JSON.stringify(arr))}catch(e){}
|
||||||
|
renderEv()
|
||||||
|
}
|
||||||
|
|
||||||
|
function openEv(id,subIdx){
|
||||||
|
curSub=subIdx!==undefined?subIdx:null;
|
||||||
|
var e=null;
|
||||||
|
for(var i=0;i<evs.length;i++){if(evs[i].id===id){e=evs[i];break}}
|
||||||
|
if(!e)return;
|
||||||
|
var fk=subIdx!==undefined?"sf_"+id+"_s"+subIdx:"sf_"+id;
|
||||||
|
var h="<div style='max-width:700px'>";
|
||||||
|
var titlePre=subIdx!==undefined?"N"+id+"."+e.sub[subIdx].l+" ":"N"+id+". ";
|
||||||
|
h+="<h3 style='margin-bottom:8px;padding-right:30px'>"+titlePre+esc(e.t)+"</h3>";
|
||||||
|
h+="<div style='font-size:12px;color:#64748B;margin-bottom:6px'><strong>Ответственный:</strong> "+esc(nl2c(e.r))+"</div>";
|
||||||
|
h+="<div style='font-size:12px;color:#64748B;margin-bottom:6px'><strong>Филиал:</strong> "+brs[e.b]+" | <strong>Срок:</strong> "+e.due;
|
||||||
|
if(e.done&&e.done!=="\u2014")h+=" | <strong>Исполнено:</strong> "+e.done;
|
||||||
|
h+="</div>";
|
||||||
|
h+="<div style='margin-bottom:12px'><strong>Статус:</strong> <select id='evs_"+e.id+"' onchange='chgSt("+e.id+")'>";
|
||||||
|
var sk=["warn","late","done"];
|
||||||
|
for(var si=0;si<sk.length;si++){
|
||||||
|
h+="<option value='"+sk[si]+"'";
|
||||||
|
if(e.s===sk[si])h+=" selected";
|
||||||
|
h+=">"+stn[sk[si]]+"</option>"
|
||||||
|
}
|
||||||
|
h+="</select></div>";
|
||||||
|
if(e.h&&e.h.length){
|
||||||
|
h+="<div style='margin-bottom:12px'><strong>История:</strong><ul style='font-size:12px;margin:4px 0 0 16px'>";
|
||||||
|
for(var hi=0;hi<e.h.length;hi++){h+="<li>"+esc(e.h[hi])+"</li>"}
|
||||||
|
h+="</ul></div>"
|
||||||
|
}
|
||||||
|
h+="<div style='margin-bottom:12px;padding:10px;background:#F0F9FF;border-radius:8px'><strong>AI-анализ:</strong> <span style='font-size:13px;color:#64748B'>"+esc(e.ai||"\u2014")+"</span></div>";
|
||||||
|
h+="<div style='margin-bottom:12px'><strong>Отчётность:</strong></div>";
|
||||||
|
h+="<div style='margin-bottom:8px;display:flex;gap:8px;flex-wrap:wrap;align-items:center'>";
|
||||||
|
h+="<span style='font-size:12px'>Количество:</span> <input type='number' id='evq_"+e.id+"' value='"+(e.q||"")+"' min='0' style='width:80px;padding:4px;border:1px solid #E2E8F0;border-radius:4px;font-size:12px'>";
|
||||||
|
var now=new Date();
|
||||||
|
var curMonth=now.getMonth();
|
||||||
|
h+="<span style='font-size:12px;margin-left:8px'>Месяц:</span> <select id='evm_"+e.id+"' style='padding:4px;border:1px solid #E2E8F0;border-radius:4px;font-size:12px'>";
|
||||||
|
var mnames=["Янв","Фев","Мар","Апр","Май","Июн","Июл","Авг","Сен","Окт","Ноя","Дек"];
|
||||||
|
for(var mi=0;mi<12;mi++){
|
||||||
|
h+="<option value='"+mi+"'";
|
||||||
|
if(mi===curMonth)h+=" selected";
|
||||||
|
h+=">"+mnames[mi]+"</option>"
|
||||||
|
}
|
||||||
|
h+="</select></div>";
|
||||||
|
h+="<div style='margin-bottom:12px'><textarea id='evn_"+e.id+"' placeholder='Примечание / описание выполнения...' style='width:100%;padding:8px;border:1px solid #E2E8F0;border-radius:6px;font-size:12px;resize:vertical;min-height:50px'>"+esc(subIdx!==undefined?((localStorage.getItem("sn_"+e.id+"_s"+subIdx)||"")):(e.n||""))+"</textarea></div>";
|
||||||
|
h+="<div style='margin-bottom:12px'><strong>Файлы:</strong>";
|
||||||
|
if(cu&&cu.bg===0){
|
||||||
|
for(var bi=0;bi<brs.length;bi++){
|
||||||
|
var bk=fk+"_b"+bi;
|
||||||
|
var fd=localStorage.getItem(bk);
|
||||||
|
if(fd){
|
||||||
|
try{
|
||||||
|
var fa=JSON.parse(fd);
|
||||||
|
if(fa.length)h+="<div style='font-size:11px;color:#64748B;margin-top:4px'><strong>"+esc(brs[bi])+":</strong></div>";
|
||||||
|
for(var fi=0;fi<fa.length;fi++){
|
||||||
|
var f=fa[fi];
|
||||||
|
h+="<div class='file-item'><span class='fn'>"+esc(f.n)+"</span><span class='fs'>("+(f.s?Math.round(f.s/1024)+"KB":"")+", "+esc(f.u||"")+" "+esc(f.d||"")+")</span><a onclick='dlFile("+e.id+","+fi+")'>Скачать</a><a style='color:#EF4444;margin-left:4px' onclick='delFile("+e.id+","+fi+")'>Удалить</a></div>"
|
||||||
|
}
|
||||||
|
}catch(ex){}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
var bk=fk+"_b"+(cu?cu.bg:0);
|
||||||
|
var fd=localStorage.getItem(bk);
|
||||||
|
if(fd){
|
||||||
|
try{
|
||||||
|
var fa=JSON.parse(fd);
|
||||||
|
for(var fi=0;fi<fa.length;fi++){
|
||||||
|
var f=fa[fi];
|
||||||
|
h+="<div class='file-item'><span class='fn'>"+esc(f.n)+"</span><span class='fs'>("+(f.s?Math.round(f.s/1024)+"KB":"")+", "+esc(f.u||"")+" "+esc(f.d||"")+")</span><a onclick='dlFile("+e.id+","+fi+")'>Скачать</a><a style='color:#EF4444;margin-left:4px' onclick='delFile("+e.id+","+fi+")'>Удалить</a></div>"
|
||||||
|
}
|
||||||
|
}catch(ex){}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
h+="<div style='margin-top:6px'><input type='file' id='fu_"+e.id+"' style='font-size:12px' onchange='upFile("+e.id+(subIdx!==undefined?","+subIdx:"")+")'></div>";
|
||||||
|
h+="</div>";
|
||||||
|
if(subIdx===undefined&&e.sub&&e.sub.length>0){
|
||||||
|
h+="<div style='margin-bottom:12px'><strong>Подпункты:</strong>";
|
||||||
|
var ss=localStorage.getItem("ss_"+e.id);
|
||||||
|
for(var si=0;si<e.sub.length;si++){
|
||||||
|
var checked="";
|
||||||
|
if(ss){try{var sp=JSON.parse(ss);if(sp[si])checked="checked"}catch(ex){}}
|
||||||
|
h+="<div style='font-size:12px;padding:4px 0'><input type='checkbox' "+checked+" onchange='chkSub("+e.id+","+si+",this.checked)'> "+esc(e.sub[si].l)+") "+esc(e.sub[si].t)+"</div>"
|
||||||
|
}
|
||||||
|
h+="</div>"
|
||||||
|
}
|
||||||
|
h+="<div style='margin-top:16px;text-align:right;border-top:1px solid #E2E8F0;padding-top:12px'><button class='btn btn-sm btn-g' onclick='saveEvModal("+e.id+")'>Сохранить</button>";
|
||||||
|
h+="<button class='btn btn-sm' style='margin-left:8px;background:#E2E8F0;color:#0B1A2E' onclick='closeModal()'>Отмена</button></div>";
|
||||||
|
h+="</div>";
|
||||||
|
showModal(h)
|
||||||
|
}
|
||||||
|
|
||||||
|
function showModal(html){
|
||||||
|
var mb=document.getElementById("modal_body");
|
||||||
|
var m=document.getElementById("modal");
|
||||||
|
if(!mb||!m)return;
|
||||||
|
mb.innerHTML=html;
|
||||||
|
m.style.display="flex"
|
||||||
|
}
|
||||||
|
function closeModal(){
|
||||||
|
document.getElementById("modal").style.display="none"
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveEvModal(id){
|
||||||
|
var sel=document.getElementById("evs_"+id);
|
||||||
|
var inq=document.getElementById("evq_"+id);
|
||||||
|
var inn=document.getElementById("evn_"+id);
|
||||||
|
for(var i=0;i<evs.length;i++){
|
||||||
|
if(evs[i].id===id){
|
||||||
|
if(sel)evs[i].s=sel.value;
|
||||||
|
if(inq)evs[i].q=parseInt(inq.value,10)||0;
|
||||||
|
if(inn){if(curSub!==null){try{localStorage.setItem("sn_"+id+"_s"+curSub,inn.value.trim())}catch(e){}}else{evs[i].n=inn.value.trim()}}
|
||||||
|
if(sel&&sel.value==="done"&&(evs[i].done==="\u2014"||!evs[i].done)){
|
||||||
|
var d=new Date();
|
||||||
|
evs[i].done=d.getDate()+"."+String(d.getMonth()+1).padStart(2,"0")+"."+d.getFullYear()
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
saveEv();
|
||||||
|
closeModal();
|
||||||
|
renderEv()
|
||||||
|
}
|
||||||
|
function chgSt(id){
|
||||||
|
var sel=document.getElementById("evs_"+id);
|
||||||
|
if(!sel)return;
|
||||||
|
for(var i=0;i<evs.length;i++){
|
||||||
|
if(evs[i].id===id){
|
||||||
|
evs[i].s=sel.value;
|
||||||
|
if(sel.value==="done"&&(evs[i].done==="\u2014"||!evs[i].done)){
|
||||||
|
var d=new Date();
|
||||||
|
evs[i].done=d.getDate()+"."+String(d.getMonth()+1).padStart(2,"0")+"."+d.getFullYear()
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function upFile(id){
|
||||||
|
var inp=document.getElementById("fu_"+id);
|
||||||
|
if(!inp||!inp.files||!inp.files[0])return;
|
||||||
|
var f=inp.files[0];
|
||||||
|
if(f.size>3145728){alert("\u0424\u0430\u0439\u043B \u0431\u043E\u043B\u044C\u0448\u0435 3MB");return}
|
||||||
|
var fr=new FileReader();
|
||||||
|
var subKey=curSub!==null?"_s"+curSub:"";
|
||||||
|
var brKey="_b"+(cu?cu.bg:0);
|
||||||
|
fr.onload=function(){
|
||||||
|
var key="sf_"+id+subKey+brKey;
|
||||||
|
var arr=[];
|
||||||
|
var ex=localStorage.getItem(key);
|
||||||
|
if(ex){try{arr=JSON.parse(ex)}catch(e){}}
|
||||||
|
var d=new Date();
|
||||||
|
arr.push({n:f.name,s:f.size,d:d.getDate()+"."+String(d.getMonth()+1).padStart(2,"0")+"."+d.getFullYear(),u:cu?cu.n:"",data:fr.result});
|
||||||
|
try{localStorage.setItem(key,JSON.stringify(arr))}catch(e){alert("\u041E\u0448\u0438\u0431\u043A\u0430 \u0441\u043E\u0445\u0440\u0430\u043D\u0435\u043D\u0438\u044F")}
|
||||||
|
openEv(id,curSub!==null?curSub:undefined)
|
||||||
|
};
|
||||||
|
fr.readAsDataURL(f)
|
||||||
|
}
|
||||||
|
function dlFile(id,idx){
|
||||||
|
var key="sf_"+id+(curSub!==null?"_s"+curSub:"")+"_b"+(cu?cu.bg:0);
|
||||||
|
var ex=localStorage.getItem(key);
|
||||||
|
if(!ex)return;
|
||||||
|
try{
|
||||||
|
var arr=JSON.parse(ex);
|
||||||
|
var f=arr[idx];
|
||||||
|
if(!f||!f.data)return;
|
||||||
|
var a=document.createElement("a");
|
||||||
|
a.href=f.data;
|
||||||
|
a.download=f.n;
|
||||||
|
document.body.appendChild(a);
|
||||||
|
a.click();
|
||||||
|
document.body.removeChild(a)
|
||||||
|
}catch(e){}
|
||||||
|
}
|
||||||
|
function delFile(id,idx){
|
||||||
|
if(!confirm("\u0423\u0434\u0430\u043B\u0438\u0442\u044C \u0444\u0430\u0439\u043B?"))return;
|
||||||
|
var key="sf_"+id+(curSub!==null?"_s"+curSub:"")+"_b"+(cu?cu.bg:0);
|
||||||
|
var ex=localStorage.getItem(key);
|
||||||
|
if(!ex)return;
|
||||||
|
try{
|
||||||
|
var arr=JSON.parse(ex);
|
||||||
|
arr.splice(idx,1);
|
||||||
|
if(arr.length){localStorage.setItem(key,JSON.stringify(arr))}
|
||||||
|
else{localStorage.removeItem(key)}
|
||||||
|
openEv(id,curSub!==null?curSub:undefined)
|
||||||
|
}catch(e){}
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveBackup(){
|
||||||
|
saveEv();
|
||||||
|
var blob=new Blob([JSON.stringify(evs,null,2)],{type:"application/json"});
|
||||||
|
var a=document.createElement("a");
|
||||||
|
a.href=URL.createObjectURL(blob);
|
||||||
|
a.download="backup_"+new Date().toISOString().slice(0,10)+".json";
|
||||||
|
a.click()
|
||||||
|
}
|
||||||
|
function loadBackup(inp){
|
||||||
|
if(!inp.files||!inp.files[0])return;
|
||||||
|
var fr=new FileReader();
|
||||||
|
fr.onload=function(){
|
||||||
|
try{
|
||||||
|
var d=JSON.parse(fr.result);
|
||||||
|
if(!d||!d.length){alert("\u041D\u0435\u0432\u0435\u0440\u043D\u044B\u0439 \u0444\u043E\u0440\u043C\u0430\u0442");return}
|
||||||
|
var out=[];
|
||||||
|
for(var i=0;i<d.length;i++){
|
||||||
|
out.push({id:d[i].id,s:d[i].s||"warn",p:d[i].p||0,done:d[i].done||"\u2014",h:d[i].h||[]})
|
||||||
|
}
|
||||||
|
localStorage.setItem("se5",JSON.stringify(out));
|
||||||
|
loadEv();
|
||||||
|
renderEv();
|
||||||
|
alert("\u0412\u043E\u0441\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u043E "+out.length+" \u0437\u0430\u043F\u0438\u0441\u0435\u0439")
|
||||||
|
}catch(e){alert("\u041E\u0448\u0438\u0431\u043A\u0430 \u0437\u0430\u0433\u0440\u0443\u0437\u043A\u0438")}
|
||||||
|
};
|
||||||
|
fr.readAsText(inp.files[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
function storCheck(){
|
||||||
|
var el=document.getElementById("stor_ind");
|
||||||
|
if(!el)return;
|
||||||
|
try{
|
||||||
|
var used=0;
|
||||||
|
for(var k in localStorage){
|
||||||
|
if(localStorage.hasOwnProperty(k)){
|
||||||
|
used+=((localStorage[k].length||0)*2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var max=5242880;
|
||||||
|
var pct=Math.round(used/max*100);
|
||||||
|
el.textContent="\u0425\u0440\u0430\u043D\u0438\u043B\u0438\u0449\u0435: "+pct+"% ("+Math.round(used/1024)+"KB)"
|
||||||
|
}catch(e){el.textContent=""}
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderAnalytics(){
|
||||||
|
if(cu&&cu.bg===0)loadLTIF();
|
||||||
|
var total=evs.length;
|
||||||
|
var done=0,late=0,warn=0;
|
||||||
|
for(var i=0;i<evs.length;i++){
|
||||||
|
var s=evs[i].s;
|
||||||
|
if(s==="done")done++;
|
||||||
|
else if(s==="late")late++;
|
||||||
|
else if(s==="warn")warn++
|
||||||
|
}
|
||||||
|
var html="";
|
||||||
|
html+="<div class='stat-card sb'><div class='lb'>\u0412\u0441\u0435\u0433\u043E \u043C\u0435\u0440\u043E\u043F\u0440\u0438\u044F\u0442\u0438\u0439</div><div class='num'>"+total+"</div></div>";
|
||||||
|
html+="<div class='stat-card sg'><div class='lb'>\u0418\u0441\u043F\u043E\u043B\u043D\u0435\u043D\u043E</div><div class='num'>"+done+"</div><div class='lb'>"+Math.round(done/total*100)+"%</div></div>";
|
||||||
|
html+="<div class='stat-card sr'><div class='lb'>\u041F\u0440\u043E\u0441\u0440\u043E\u0447\u0435\u043D\u043E</div><div class='num'>"+late+"</div></div>";
|
||||||
|
html+="<div class='stat-card'><div class='lb'>В процессе</div><div class='num'>"+warn+"</div></div>";
|
||||||
|
document.getElementById("an_stats").innerHTML=html;
|
||||||
|
|
||||||
|
var problem=[];
|
||||||
|
for(var i=0;i<evs.length;i++){
|
||||||
|
if(evs[i].s==="late"||evs[i].s==="warn"){
|
||||||
|
var dr=daysRem(evs[i].due);
|
||||||
|
problem.push({id:evs[i].id,t:evs[i].t,b:evs[i].b,s:evs[i].s,dr:dr,dn:evs[i].dname})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
problem.sort(function(a,b){return a.dr-b.dr});
|
||||||
|
var pt=problem.slice(0,10);
|
||||||
|
if(pt.length){
|
||||||
|
var ph="<table><tr><th>N</th><th>\u041C\u0435\u0440\u043E\u043F\u0440\u0438\u044F\u0442\u0438\u0435</th><th>\u0424\u0438\u043B\u0438\u0430\u043B</th><th>\u0421\u0442\u0430\u0442\u0443\u0441</th><th>\u0414\u043D\u0435\u0439</th></tr>";
|
||||||
|
for(var i=0;i<pt.length;i++){
|
||||||
|
var pc=pt[i].s==="late"?"r":"w";
|
||||||
|
ph+="<tr><td>"+pt[i].id+"</td><td style='font-size:12px'>"+esc(pt[i].t)+"</td><td>"+brs[pt[i].b]+"</td><td><span class='badge "+pc+"'>"+stn[pt[i].s]+"</span></td><td>"+(pt[i].dr<=0?"\u041F\u0440\u043E\u0441\u0440\u043E\u0447\u0435\u043D\u043E":pt[i].dr+" \u0434\u043D.")+"</td></tr>"
|
||||||
|
}
|
||||||
|
ph+="</table>";
|
||||||
|
document.getElementById("an_top").innerHTML=ph
|
||||||
|
}else{
|
||||||
|
document.getElementById("an_top").innerHTML="<p style='color:#64748B'>\u041F\u0440\u043E\u0431\u043B\u0435\u043C \u043D\u0435\u0442</p>"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFilteredEvs(){
|
||||||
|
var period=document.getElementById("rp_period").value;
|
||||||
|
var statusF=document.getElementById("rp_status").value;
|
||||||
|
var year=parseInt(document.getElementById("rp_year").value,10)||2026;
|
||||||
|
var month=parseInt(document.getElementById("rp_month").value,10)||0;
|
||||||
|
var months=[];
|
||||||
|
if(period==="month"){months=[month]}
|
||||||
|
else if(period==="q1"){months=[0,1,2]}
|
||||||
|
else if(period==="q2"){months=[3,4,5]}
|
||||||
|
else if(period==="q3"){months=[6,7,8]}
|
||||||
|
else if(period==="q4"){months=[9,10,11]}
|
||||||
|
else if(period==="h1"){months=[0,1,2,3,4,5]}
|
||||||
|
else if(period==="h2"){months=[6,7,8,9,10,11]}
|
||||||
|
else if(period==="year"){months=[0,1,2,3,4,5,6,7,8,9,10,11]}
|
||||||
|
var r=[];
|
||||||
|
for(var i=0;i<evs.length;i++){
|
||||||
|
var e=evs[i];
|
||||||
|
if(statusF&&e.s!==statusF)continue;
|
||||||
|
var dp=e.due.split(".");
|
||||||
|
if(dp.length===3){
|
||||||
|
var em=parseInt(dp[1],10)-1;
|
||||||
|
var ey=parseInt(dp[2],10);
|
||||||
|
if(ey===year&&months.indexOf(em)!==-1)r.push(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
function rpPeriodChange(){
|
||||||
|
var v=document.getElementById("rp_period").value;
|
||||||
|
var mSel=document.getElementById("rp_month");
|
||||||
|
if(v==="month"){mSel.style.display="inline-block"}else{mSel.style.display="none"}
|
||||||
|
renderReports()
|
||||||
|
}
|
||||||
|
function renderReports(){
|
||||||
|
var fl=getFilteredEvs();
|
||||||
|
var cnt=document.getElementById("rp_count");
|
||||||
|
if(cnt)cnt.textContent="Выбрано мероприятий: "+fl.length;
|
||||||
|
document.getElementById("rp_preview").innerHTML=""
|
||||||
|
}
|
||||||
|
function dlCSV(){
|
||||||
|
var fl=getFilteredEvs();
|
||||||
|
var csv="\uFEFFN;\u041C\u0435\u0440\u043E\u043F\u0440\u0438\u044F\u0442\u0438\u0435;\u0424\u0438\u043B\u0438\u0430\u043B;\u0421\u0440\u043E\u043A;\u0421\u0442\u0430\u0442\u0443\u0441;\u041F\u0440\u043E\u0433\u0440\u0435\u0441\u0441;\u041A\u043E\u043B-\u0432\u043E;\u041F\u0440\u0438\u043C\u0435\u0447\u0430\u043D\u0438\u0435;\u041E\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0435\u043D\u043D\u044B\u0439\n";
|
||||||
|
for(var i=0;i<fl.length;i++){
|
||||||
|
var e=fl[i];
|
||||||
|
csv+=e.id+";\""+esc(e.t)+"\";\""+brs[e.b]+"\";"+e.due+";"+stn[e.s]+";"+(e.p||0)+"%;"+(e.q||"")+";\""+esc(e.n||"")+"\";\""+esc(nl2c(e.r))+"\"\n"
|
||||||
|
}
|
||||||
|
var blob=new Blob([csv],{type:"text/csv;charset=utf-8"});
|
||||||
|
var a=document.createElement("a");
|
||||||
|
a.href=URL.createObjectURL(blob);
|
||||||
|
a.download="report_"+document.getElementById("rp_year").value+"_"+(parseInt(document.getElementById("rp_month").value,10)+1)+".csv";
|
||||||
|
a.click()
|
||||||
|
}
|
||||||
|
function dlHTML(){
|
||||||
|
var fl=getFilteredEvs();
|
||||||
|
var month=parseInt(document.getElementById("rp_month").value,10)+1;
|
||||||
|
var year=document.getElementById("rp_year").value;
|
||||||
|
var hh="<!DOCTYPE html><html><head><meta charset='utf-8'><title>Отчёт План ПБ "+month+"."+year+"</title><style>body{font:14px Arial;padding:20px}table{border-collapse:collapse;width:100%}th,td{border:1px solid #ccc;padding:6px 10px;font-size:12px;text-align:left;vertical-align:top}th{background:#0B1A2E;color:#fff}.files{margin-top:4px;font-size:11px}.files a{color:#005BAA;display:block}</style></head><body><h2>План производственной безопасности</h2><p>QAZAQtelecom HSE за "+month+"."+year+"</p><br><table><tr><th>N</th><th>Мероприятие</th><th>Срок</th><th>Статус</th><th>Кол-во</th><th>Примечание / Файлы</th></tr>";
|
||||||
|
for(var i=0;i<fl.length;i++){
|
||||||
|
var e=fl[i];
|
||||||
|
var notes=esc(e.n||"");
|
||||||
|
var fhtml="";
|
||||||
|
var keysToTry=[];
|
||||||
|
for(var si=-1;si<(e.sub?e.sub.length:0);si++){
|
||||||
|
var sk=si>=0?"_s"+si:"";
|
||||||
|
for(var bk=0;bk<brs.length;bk++){
|
||||||
|
keysToTry.push("sf_"+e.id+sk+"_b"+bk)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
keysToTry.push("sf_"+e.id);
|
||||||
|
for(var ki=0;ki<keysToTry.length;ki++){
|
||||||
|
var key=keysToTry[ki];
|
||||||
|
var fd=localStorage.getItem(key);
|
||||||
|
if(fd){
|
||||||
|
try{var arr=JSON.parse(fd);
|
||||||
|
for(var fi=0;fi<arr.length;fi++){
|
||||||
|
var f=arr[fi];
|
||||||
|
if(!f.n)continue;
|
||||||
|
fhtml+="<a href='"+f.data+"' download='"+esc(f.n)+"'>"+esc(f.n)+" ("+Math.round((f.s||0)/1024)+" KB"+", "+esc(f.u||"")+")</a>"
|
||||||
|
}}catch(ex){}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(fhtml)fhtml="<div class='files'>"+fhtml+"</div>";
|
||||||
|
hh+="<tr><td>"+e.id+"</td><td>"+esc(e.t)+"</td><td>"+e.due+"</td><td>"+stn[e.s]+"</td><td>"+(e.q||"")+"</td><td>"+notes+fhtml+"</td></tr>"
|
||||||
|
}
|
||||||
|
hh+="</table><p><br><em>Отчёт сформирован: "+new Date().toLocaleDateString("ru-RU")+"</em></p></body></html>";
|
||||||
|
var blob=new Blob([hh],{type:"text/html"});
|
||||||
|
var a=document.createElement("a");
|
||||||
|
a.href=URL.createObjectURL(blob);
|
||||||
|
a.download="report_pb_"+year+"_"+month+".html";
|
||||||
|
a.click()
|
||||||
|
}
|
||||||
|
function dlWord(){
|
||||||
|
var fl=getFilteredEvs();
|
||||||
|
var month=parseInt(document.getElementById("rp_month").value,10)+1;
|
||||||
|
var year=document.getElementById("rp_year").value;
|
||||||
|
var hh="<html xmlns:o='urn:schemas-microsoft-com:office:office' xmlns:w='urn:schemas-microsoft-com:office:word' xmlns='http://www.w3.org/TR/REC-html40'><head><meta charset='utf-8'><title>Отчёт План ПБ "+month+"."+year+"</title><style>@page{size:A4;margin:20mm}body{font:12pt 'Times New Roman'}h2{font-size:16pt;text-align:center}table{border-collapse:collapse;width:100%}th,td{border:1px solid #000;padding:4px 8px;font-size:11pt}th{background:#ddd}</style></head><body><h2>План производственной безопасности</h2><p style='text-align:center'>AO «Казахтелеком» за "+month+"."+year+"</p><br><table><tr><th>N</th><th>Мероприятие</th><th>Филиал</th><th>Срок</th><th>Статус</th><th>Прогресс</th><th>Кол-во</th><th>Примечание</th></tr>";
|
||||||
|
for(var i=0;i<fl.length;i++){
|
||||||
|
var e=fl[i];
|
||||||
|
hh+="<tr><td>"+e.id+"</td><td>"+esc(e.t)+"</td><td>"+brs[e.b]+"</td><td>"+e.due+"</td><td>"+stn[e.s]+"</td><td>"+(e.p||0)+"%</td><td>"+(e.q||"")+"</td><td>"+esc(e.n||"")+"</td></tr>"
|
||||||
|
}
|
||||||
|
hh+="</table><p><br><em>Отчёт сформирован: "+new Date().toLocaleDateString("ru-RU")+"</em></p></body></html>";
|
||||||
|
var blob=new Blob([hh],{type:"application/msword"});
|
||||||
|
var a=document.createElement("a");
|
||||||
|
a.href=URL.createObjectURL(blob);
|
||||||
|
a.download="report_pb_"+year+"_"+month+".doc";
|
||||||
|
a.click()
|
||||||
|
}
|
||||||
|
function dlPdf(){
|
||||||
|
var fl=getFilteredEvs();
|
||||||
|
var month=parseInt(document.getElementById("rp_month").value,10)+1;
|
||||||
|
var year=document.getElementById("rp_year").value;
|
||||||
|
var hh="<!DOCTYPE html><html><head><meta charset='utf-8'><title>Отчёт План ПБ "+month+"."+year+"</title><style>body{font:14px Arial;padding:20px}table{border-collapse:collapse;width:100%}th,td{border:1px solid #ccc;padding:6px 10px;font-size:12px;text-align:left}th{background:#0B1A2E;color:#fff}@media print{body{padding:10mm}table{page-break-inside:auto}tr{page-break-inside:avoid}}</style></head><body><h2>План производственной безопасности</h2><p>AO «Казахтелеком» за "+month+"."+year+"</p><br><table><tr><th>N</th><th>Мероприятие</th><th>Филиал</th><th>Срок</th><th>Статус</th><th>Прогресс</th><th>Кол-во</th><th>Примечание</th></tr>";
|
||||||
|
for(var i=0;i<fl.length;i++){
|
||||||
|
var e=fl[i];
|
||||||
|
hh+="<tr><td>"+e.id+"</td><td>"+esc(e.t)+"</td><td>"+brs[e.b]+"</td><td>"+e.due+"</td><td>"+stn[e.s]+"</td><td>"+(e.p||0)+"%</td><td>"+(e.q||"")+"</td><td>"+esc(e.n||"")+"</td></tr>"
|
||||||
|
}
|
||||||
|
hh+="</table><p><br><em>Отчёт сформирован: "+new Date().toLocaleDateString("ru-RU")+"</em></p><script>window.onload=function(){window.print()}<\/script></body></html>";
|
||||||
|
var w=window.open("","_blank","width=900,height=700");
|
||||||
|
w.document.write(hh);
|
||||||
|
w.document.close()
|
||||||
|
}
|
||||||
|
function hseSend(){
|
||||||
|
var btn=document.getElementById("hse_btn");
|
||||||
|
var result=document.getElementById("hse_result");
|
||||||
|
btn.disabled=true;btn.textContent="Отправка...";result.innerHTML="";
|
||||||
|
var month=document.getElementById("hse_month").value;
|
||||||
|
var fmt=document.getElementById("hse_fmt").value;
|
||||||
|
var apiKey=document.getElementById("hse_key").value;
|
||||||
|
if(!month){result.innerHTML="<span style='color:#EF4444'>Выберите месяц</span>";btn.disabled=false;btn.textContent="Отправить отчёт в HSE.sk.kz";return}
|
||||||
|
if(!apiKey){result.innerHTML="<span style='color:#EF4444'>Введите API ключ</span>";btn.disabled=false;btn.textContent="Отправить отчёт в HSE.sk.kz";return}
|
||||||
|
var fl=getFilteredEvs();
|
||||||
|
var total=fl.length;
|
||||||
|
var done=0;for(var i=0;i<fl.length;i++){if(fl[i].s==="done")done++}
|
||||||
|
var pct=total?Math.round(done/total*100):0;
|
||||||
|
var payload={month:month,events:fl.map(function(e){return{id:e.id,title:e.t,branch:brs[e.b],deadline:e.due,status:e.s,progress:e.p||0,quantity:e.q||"",note:e.n||""}}),summary:{total:total,done:done,pct:pct}};
|
||||||
|
try{
|
||||||
|
fetch("http://localhost:5000/api/hse/send",{method:"POST",headers:{"Content-Type":"application/json","Authorization":"Bearer hse-integration"},body:JSON.stringify({month:month,api_key:apiKey,format:fmt,report:payload})}).then(function(r){return r.json()}).then(function(d){
|
||||||
|
if(d.ok){result.innerHTML="<span style='color:#10B981'>Отчёт за "+month+" отправлен в HSE.sk.kz</span>"}
|
||||||
|
else{result.innerHTML="<span style='color:#EF4444'>Ошибка: "+(d.error||"соединение")+"</span>"}
|
||||||
|
}).catch(function(e){result.innerHTML="<span style='color:#EF4444'>Сервер не запущен. Запустите <code>python3 server.py</code></span>"}).finally(function(){btn.disabled=false;btn.textContent="Отправить отчёт в HSE.sk.kz"})
|
||||||
|
}catch(e){}
|
||||||
|
}
|
||||||
|
|
||||||
|
var aiGreeted=false;
|
||||||
|
function renderAI(){
|
||||||
|
if(!aiGreeted){
|
||||||
|
aiGreeted=true;
|
||||||
|
var box=document.getElementById("ai_chat");
|
||||||
|
if(box){
|
||||||
|
box.innerHTML="";
|
||||||
|
addMsg("b","Джарвис к вашим услугам. Я анализирую 35 мероприятий ПБ по 9 филиалам. Спросите: сводка, просроченные, риски, рейтинг, аудит, прогноз, советник.","Джарвис")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function addMsg(role,text,name){
|
||||||
|
var box=document.getElementById("ai_chat");
|
||||||
|
if(!box)return;
|
||||||
|
var nm=role==="u"?"\u0412\u044B":(name||"\u0411\u043E\u0442");
|
||||||
|
box.innerHTML+="<div class='msg "+role+"'><div class='nm'>"+esc(nm)+"</div><div>"+esc(text)+"</div></div>";
|
||||||
|
box.scrollTop=box.scrollHeight
|
||||||
|
}
|
||||||
|
function aiAsk(q){
|
||||||
|
addMsg("u",q,"\u0412\u044B");
|
||||||
|
setTimeout(function(){aiResp(q)},300)
|
||||||
|
}
|
||||||
|
function aiSend(){
|
||||||
|
var inp=document.getElementById("ai_inp");
|
||||||
|
if(!inp||!inp.value.trim())return;
|
||||||
|
var q=inp.value.trim();
|
||||||
|
inp.value="";
|
||||||
|
aiAsk(q)
|
||||||
|
}
|
||||||
|
function aiResp(q){
|
||||||
|
var total=evs.length;
|
||||||
|
var done=0,late=0,warn=0;
|
||||||
|
for(var i=0;i<evs.length;i++){
|
||||||
|
var s=evs[i].s;
|
||||||
|
if(s==="done")done++;
|
||||||
|
else if(s==="late")late++;
|
||||||
|
else if(s==="warn")warn++
|
||||||
|
}
|
||||||
|
var ql=q.toLowerCase();
|
||||||
|
var ans="";
|
||||||
|
|
||||||
|
if(ql.indexOf("свод")!==-1||ql.indexOf("общ")!==-1||ql.indexOf("статус")!==-1||ql.indexOf("все")!==-1){
|
||||||
|
ans="Общая сводка по плану ПБ:";
|
||||||
|
ans+="\n- Всего: "+total+" мероприятий";
|
||||||
|
ans+="\n- Исполнено: "+done+" ("+Math.round(done/total*100)+"%)";
|
||||||
|
ans+="\n- В процессе: "+warn;
|
||||||
|
ans+="\n- Просрочено: "+late
|
||||||
|
|
||||||
|
}else if(ql.indexOf("просроч")!==-1||ql.indexOf("срочн")!==-1||ql.indexOf("критич")!==-1){
|
||||||
|
var lateList=[];
|
||||||
|
for(var i=0;i<evs.length;i++){if(evs[i].s==="late"){lateList.push(evs[i])}}
|
||||||
|
if(lateList.length){
|
||||||
|
ans="Просроченные мероприятия ("+lateList.length+"):";
|
||||||
|
for(var i=0;i<lateList.length;i++){
|
||||||
|
ans+="\nN"+lateList[i].id+" - "+lateList[i].t.slice(0,80)+"... ("+lateList[i].due+", "+brs[lateList[i].b]+")"
|
||||||
|
}
|
||||||
|
}else{ans="Просроченных нет"}
|
||||||
|
|
||||||
|
}else if(ql.indexOf("рик")!==-1||ql.indexOf("risk")!==-1||ql.indexOf("пробл")!==-1||ql.indexOf("срыв")!==-1){
|
||||||
|
var risk=[];
|
||||||
|
for(var i=0;i<evs.length;i++){
|
||||||
|
var dr=daysRem(evs[i].due);
|
||||||
|
if(evs[i].s!=="done"&&dr<=30&&dr>0){risk.push(evs[i])}
|
||||||
|
}
|
||||||
|
if(risk.length){
|
||||||
|
ans="Менее 30 дней до срока ("+risk.length+"):";
|
||||||
|
for(var i=0;i<risk.length;i++){
|
||||||
|
ans+="\nN"+risk[i].id+" - "+risk[i].t.slice(0,60)+"... ("+daysRem(risk[i].due)+" дн.)"
|
||||||
|
}
|
||||||
|
}else{ans="Рисков нет"}
|
||||||
|
|
||||||
|
}else if(ql.indexOf("рейт")!==-1||ql.indexOf("филиал")!==-1||ql.indexOf("лучш")!==-1||ql.indexOf("худш")!==-1){
|
||||||
|
var brd=[];
|
||||||
|
for(var i=0;i<brs.length;i++){brd.push({n:brs[i],t:0,d:0})}
|
||||||
|
for(var i=0;i<evs.length;i++){var e=evs[i];brd[e.b].t++;if(e.s==="done")brd[e.b].d++}
|
||||||
|
brd.sort(function(a,b){return(b.d/b.t||0)-(a.d/a.t||0)});
|
||||||
|
ans="Рейтинг филиалов:";
|
||||||
|
for(var i=0;i<brd.length;i++){
|
||||||
|
var pct=brd[i].t?Math.round(brd[i].d/brd[i].t*100):0;
|
||||||
|
ans+="\n"+(i+1)+". "+brd[i].n+": "+brd[i].d+"/"+brd[i].t+" ("+pct+"%)"
|
||||||
|
}
|
||||||
|
|
||||||
|
}else if(ql.indexOf("прогноз")!==-1||ql.indexOf("прогн")!==-1){
|
||||||
|
var atRisk=0,crit=0,onTrack=0;
|
||||||
|
for(var i=0;i<evs.length;i++){
|
||||||
|
var dr=daysRem(evs[i].due);
|
||||||
|
if(evs[i].s==="done")onTrack++;
|
||||||
|
else if(dr<=0)crit++;
|
||||||
|
else if(dr<=30)atRisk++;
|
||||||
|
else onTrack++
|
||||||
|
}
|
||||||
|
ans="Прогноз выполнения плана ПБ:\n- Выполнено: "+onTrack+"\n- В зоне риска (<30 дн): "+atRisk+"\n- Критические (просрочено): "+crit;
|
||||||
|
ans+="\n\nПрогнозируемый % выполнения к концу года: "+Math.round((onTrack+atRisk*0.5)/evs.length*100)+"%";
|
||||||
|
if(crit>3)ans+="\n\nРекомендация: срочный штаб по "+crit+" просроченным пунктам."
|
||||||
|
|
||||||
|
}else if(ql.indexOf("статус")!==-1||ql.indexOf("состоян")!==-1||ql.indexOf("обстан")!==-1){
|
||||||
|
var bySec=[];
|
||||||
|
for(var si=0;si<secs.length;si++){bySec.push({n:secs[si].split(".")[0],t:0,d:0,l:0})}
|
||||||
|
for(var i=0;i<evs.length;i++){
|
||||||
|
var e=evs[i];bySec[e.sec].t++;
|
||||||
|
if(e.s==="done")bySec[e.sec].d++;
|
||||||
|
else if(e.s==="late")bySec[e.sec].l++
|
||||||
|
}
|
||||||
|
ans="Состояние по разделам:";
|
||||||
|
for(var i=0;i<bySec.length;i++){
|
||||||
|
ans+="\n"+bySec[i].n+": "+bySec[i].d+"/"+bySec[i].t+" ("+Math.round(bySec[i].d/bySec[i].t*100)+"%)"+(bySec[i].l?" просрочено:"+bySec[i].l:"")
|
||||||
|
}
|
||||||
|
|
||||||
|
}else if(ql.indexOf("план")!==-1||ql.indexOf("действ")!==-1||ql.indexOf("рекоменд")!==-1||ql.indexOf("совет")!==-1){
|
||||||
|
var pct=Math.round(done/total*100);
|
||||||
|
if(pct<30)ans="Рекомендация: выполнено менее 30%. Рекомендуется усилить контроль за просроченными и провести штаб с ответственными лицами";
|
||||||
|
else if(pct<60)ans="Рекомендация: выполнено "+pct+"%. Обратить внимание на процент выполнения в филиалах с низким показателем";
|
||||||
|
else ans="Хороший прогресс: "+pct+"%. Рекомендуется продолжать работу в том же темпе"
|
||||||
|
|
||||||
|
}else if(ql.indexOf("аудит")!==-1||ql.indexOf("провер")!==-1||ql.indexOf("контрол")!==-1){
|
||||||
|
ans="Аудит плана ПБ:";
|
||||||
|
ans+="\n- выполнено: "+done+"/"+total+" ("+Math.round(done/total*100)+"%)";
|
||||||
|
ans+="\n- просрочено: "+late;
|
||||||
|
var riskCount=0;
|
||||||
|
for(var i=0;i<evs.length;i++){var dr=daysRem(evs[i].due);if(evs[i].s!=="done"&&dr<=30&&dr>0)riskCount++}
|
||||||
|
ans+="\n- в риске (<30 дней): "+riskCount;
|
||||||
|
if(done/total>0.7)ans+="\nОбщая оценка: хорошо";
|
||||||
|
else if(done/total>0.4)ans+="\nОбщая оценка: удовлетворительно";
|
||||||
|
else ans+="\nОбщая оценка: требует внимания"
|
||||||
|
|
||||||
|
}else if(ql.indexOf("пункт")!==-1||ql.indexOf("номер")!==-1){
|
||||||
|
var match=ql.match(/\d+/);
|
||||||
|
if(match){
|
||||||
|
var num=parseInt(match[0],10);
|
||||||
|
var found=null;
|
||||||
|
for(var i=0;i<evs.length;i++){if(evs[i].id===num){found=evs[i];break}}
|
||||||
|
if(found){
|
||||||
|
ans="N"+found.id+" "+found.t.slice(0,80)+"...";
|
||||||
|
ans+="\nСтатус: "+stn[found.s];
|
||||||
|
ans+="\nФилиал: "+brs[found.b];
|
||||||
|
ans+="\nСрок: "+found.due;
|
||||||
|
ans+="\nПрогресс: "+(found.p||0)+"%"
|
||||||
|
}else{ans="Пункт N"+num+" не найден"}
|
||||||
|
}else{ans="Напиши номер пункта, например: пункт 5"}
|
||||||
|
|
||||||
|
}else{
|
||||||
|
ans="Я — Джарвис, ваш аналитический ассистент. Могу ответить:\n\n• сводка — общая статистика\n• просроченные — список просрочек\n• риски — зона риска (<30 дней)\n• рейтинг — рейтинг филиалов\n• аудит — полный аудит\n• прогноз — прогноз исполнения\n• статус — состояние по разделам\n• план — план действий и рекомендации\n• пункт N — детали конкретного мероприятия"
|
||||||
|
}
|
||||||
|
|
||||||
|
addMsg("b",ans,"Джарвис")
|
||||||
|
}
|
||||||
|
function renderUsers(){
|
||||||
|
if(!cu||cu.bg!==0){document.getElementById("tab_users").innerHTML="<div class='card'><p style='color:#EF4444'>Доступ запрещён</p></div>";return}
|
||||||
|
var ex=localStorage.getItem("ext_users");
|
||||||
|
if(ex){try{var eu=JSON.parse(ex);for(var k in eu){if(eu.hasOwnProperty(k)&&!USR[k])USR[k]=eu[k]}}catch(e){}}
|
||||||
|
var h="<table><tr><th>Логин</th><th>ФИО</th><th>Телефон</th><th>Филиал</th><th></th></tr>";
|
||||||
|
for(var k in USR){
|
||||||
|
if(!USR.hasOwnProperty(k))continue;
|
||||||
|
var u=USR[k];
|
||||||
|
h+="<tr><td>"+esc(k)+"@telecom.kz</td><td>"+esc(u.n)+"</td><td>"+esc(u.ph||"")+"</td><td>"+esc(brs[u.bg]||"")+"</td>";
|
||||||
|
h+="<td><button class='btn btn-sm btn-o' style='padding:3px 8px;margin-right:4px' onclick=\"resetPw('"+esc(k)+"')\">Сброс</button><button class='btn btn-sm btn-r' style='padding:3px 10px' onclick=\"delUser('"+esc(k)+"')\">Удалить</button></td></tr>"
|
||||||
|
}
|
||||||
|
h+="</table>";
|
||||||
|
document.getElementById("users_list").innerHTML=h
|
||||||
|
}
|
||||||
|
function resetPw(k){
|
||||||
|
var np=prompt("Новый пароль для "+k+":","0000");
|
||||||
|
if(np&&USR[k]){USR[k].pw=np;saveUsers();renderUsers()}
|
||||||
|
}
|
||||||
|
function addUser(){
|
||||||
|
var em=document.getElementById("reg_email").value.trim().toLowerCase();
|
||||||
|
var nm=document.getElementById("reg_name").value.trim();
|
||||||
|
var ph=document.getElementById("reg_phone").value.trim();
|
||||||
|
var bg=parseInt(document.getElementById("reg_branch").value,10);
|
||||||
|
var pw=document.getElementById("reg_pass").value.trim();
|
||||||
|
if(!em||!nm){alert("Заполните логин и ФИО");return}
|
||||||
|
USR[em]={n:nm,bg:bg,ph:ph};
|
||||||
|
if(pw)USR[em].pw=pw;
|
||||||
|
saveUsers();
|
||||||
|
closeRegModal();
|
||||||
|
renderUsers()
|
||||||
|
}
|
||||||
|
function showRegModal(){
|
||||||
|
var rb=document.getElementById("reg_branch");
|
||||||
|
if(rb&&!rb.options.length){
|
||||||
|
for(var i=0;i<brs.length;i++){
|
||||||
|
var o=document.createElement("option");
|
||||||
|
o.value=i;o.textContent=brs[i];
|
||||||
|
rb.appendChild(o)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
document.getElementById("regModal").style.display="flex"
|
||||||
|
}
|
||||||
|
function closeRegModal(){
|
||||||
|
document.getElementById("regModal").style.display="none";
|
||||||
|
document.getElementById("reg_email").value="";
|
||||||
|
document.getElementById("reg_name").value="";
|
||||||
|
document.getElementById("reg_phone").value="";
|
||||||
|
document.getElementById("reg_pass").value=""
|
||||||
|
}
|
||||||
|
function delUser(k){
|
||||||
|
if(!confirm("Удалить "+k+"?"))return;
|
||||||
|
delete USR[k];
|
||||||
|
saveUsers();
|
||||||
|
renderUsers()
|
||||||
|
}
|
||||||
|
function saveLTIF(){
|
||||||
|
var m=parseInt(document.getElementById("ltif_month").value,10);
|
||||||
|
var h=parseFloat(document.getElementById("ltif_hours").value)||0;
|
||||||
|
var a=parseInt(document.getElementById("ltif_inj").value)||0;
|
||||||
|
if(!h){alert("Введите человеко-часы");return}
|
||||||
|
var data=localStorage.getItem("ltif_data");
|
||||||
|
var ltif=[];
|
||||||
|
if(data){try{ltif=JSON.parse(data)}catch(e){}}
|
||||||
|
ltif[m]={h:h,a:a};
|
||||||
|
try{localStorage.setItem("ltif_data",JSON.stringify(ltif))}catch(e){}
|
||||||
|
loadLTIF()
|
||||||
|
}
|
||||||
|
function loadLTIF(){
|
||||||
|
var data=localStorage.getItem("ltif_data");
|
||||||
|
var ltif=[];
|
||||||
|
if(data){try{ltif=JSON.parse(data)}catch(e){}}
|
||||||
|
var mnames=["Янв","Фев","Мар","Апр","Май","Июн","Июл","Авг","Сен","Окт","Ноя","Дек"];
|
||||||
|
var m=parseInt(document.getElementById("ltif_month").value,10);
|
||||||
|
var cur=ltif[m];
|
||||||
|
if(cur){document.getElementById("ltif_hours").value=cur.h;document.getElementById("ltif_inj").value=cur.a}
|
||||||
|
else{document.getElementById("ltif_hours").value="";document.getElementById("ltif_inj").value=""}
|
||||||
|
var res=document.getElementById("ltif_result");
|
||||||
|
var totalH=0,totalA=0;
|
||||||
|
var tbl="<table><tr><th>Месяц</th><th>Чел-часы</th><th>Пострадавшие</th><th>LTIF</th></tr>";
|
||||||
|
for(var i=0;i<12;i++){
|
||||||
|
if(ltif[i]&<if[i].h){
|
||||||
|
totalH+=ltif[i].h;totalA+=ltif[i].a;
|
||||||
|
var lt=ltif[i].a*1000000/ltif[i].h;
|
||||||
|
tbl+="<tr><td>"+mnames[i]+"</td><td>"+ltif[i].h.toLocaleString()+"</td><td>"+ltif[i].a+"</td><td>"+lt.toFixed(2)+"</td></tr>"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tbl+="</table>";
|
||||||
|
if(totalH>0){
|
||||||
|
var totalLT=totalA*1000000/totalH;
|
||||||
|
res.innerHTML="<strong>Годовой LTIF: "+totalLT.toFixed(2)+"</strong> (пострадавших: "+totalA+", чел-часов: "+totalH.toLocaleString()+")"
|
||||||
|
}else{res.innerHTML=""}
|
||||||
|
document.getElementById("ltif_table").innerHTML=tbl;
|
||||||
|
document.getElementById("ltif_card").style.display=cu&&cu.bg===0?"":"none"
|
||||||
}
|
}
|
||||||
function saveUsers(){
|
function saveUsers(){
|
||||||
var ex={};
|
var ex={};
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user