samruk-ai-agent/index.html

464 lines
41 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>ИИ-агент ПБ — Казахтелеком</title>
<style>
:root{--ink:#0F1218;--cyan:#00E5FF;--cyan-50:#E8FCFF;--white:#fff;--gray-500:#5B6573;--gray-100:#F2F4F7;--gray-200:#E5E7EB;--green:#10B981;--red:#EF4444;--amber:#F59E0B;--blue:#3B82F6}
*{box-sizing:border-box;margin:0;padding:0}
body{font:14px/1.5 Arial,sans-serif;color:var(--ink);background:var(--gray-100);min-height:100vh}
input,select,textarea,button{font:inherit}
.btn{display:inline-block;background:var(--cyan);color:var(--ink);padding:12px 24px;border-radius:8px;font-weight:700;font-size:15px;border:none;cursor:pointer}.btn:hover{background:#1be5ff}
.btn-sm{padding:7px 14px;font-size:13px}.btn-outline{background:transparent;border:2px solid var(--ink);color:var(--ink)}.btn-outline:hover{background:var(--ink);color:var(--white)}
.badge{display:inline-block;padding:4px 10px;border-radius:100px;font-size:12px;font-weight:600}
.badge.green{background:#D1FAE5;color:#065F46}.badge.amber{background:#FEF3C7;color:#92400E}.badge.red{background:#FEE2E2;color:#991B1B}.badge.blue{background:#DBEAFE;color:#1E40AF}
.panel{background:var(--white);border-radius:12px;border:1px solid var(--gray-200);padding:24px;margin-bottom:20px}
.panel h3{font-size:17px;font-weight:700;margin-bottom:12px}
table{width:100%;border-collapse:collapse}th,td{padding:10px 14px;text-align:left;font-size:13px}th{font-weight:600;color:var(--gray-500);font-size:11px;text-transform:uppercase;border-bottom:2px solid var(--gray-200)}td{border-bottom:1px solid var(--gray-200)}tr:hover td{background:var(--cyan-50)}
.pct-bar{display:flex;align-items:center;gap:8px;font-size:13px}.pct-bar .track{width:80px;height:7px;background:var(--gray-200);border-radius:10px;overflow:hidden}.pct-bar .fill{height:100%;border-radius:10px}
#loginBox{display:flex;align-items:center;justify-content:center;min-height:100vh;background:var(--ink)}
#loginBox>div{background:var(--white);border-radius:16px;padding:48px 40px;width:440px;max-width:90vw;text-align:center}
#loginBox h1{font-size:24px;font-weight:800;margin-bottom:4px}#loginBox h1 span{color:var(--cyan)}
#loginBox .sub{color:var(--gray-500);font-size:14px;margin-bottom:32px}
#loginBox label{display:block;text-align:left;font-size:13px;font-weight:600;margin-bottom:6px}
#loginBox input{padding:12px 16px;border:1px solid var(--gray-200);border-radius:8px;font-size:15px;margin-bottom:20px;width:100%}
#loginBox .hint{font-size:12px;color:var(--gray-500);margin-top:-12px;margin-bottom:16px;text-align:left}
#loginBox .err{color:var(--red);font-size:13px;margin-bottom:12px;display:none}
#app{display:none}
.topbar{background:var(--white);border-bottom:1px solid var(--gray-200);padding:0 32px;height:60px;display:flex;align-items:center;justify-content:space-between}
.topbar .brand{font-weight:800;font-size:16px}.topbar .brand span{color:var(--cyan)}
.topbar .right{display:flex;align-items:center;gap:16px}
.topbar .user-info{font-size:13px;color:var(--gray-500)}.topbar .user-info strong{color:var(--ink)}
.notif-btn{position:relative;background:none;border:none;font-size:22px;cursor:pointer;padding:4px}
.notif-btn .badge-count{position:absolute;top:-2px;right:-4px;background:var(--red);color:#fff;border-radius:100px;font-size:10px;padding:1px 6px;font-weight:700}
.logout-btn{font-size:13px;color:var(--gray-500);cursor:pointer;border:none;background:none}.logout-btn:hover{color:var(--red)}
.notif-drop{position:absolute;top:56px;right:32px;width:380px;max-width:90vw;background:var(--white);border:1px solid var(--gray-200);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.open{display:block}.notif-drop .item{padding:14px 18px;border-bottom:1px solid var(--gray-100);font-size:13px}.notif-drop .item .title{font-weight:600;margin-bottom:2px}.notif-drop .item .time{font-size:11px;color:var(--gray-500)}.notif-drop .empty{padding:24px;text-align:center;color:var(--gray-500)}
.tabs{display:flex;gap:0;margin-bottom:0;background:var(--white);border-radius:12px 12px 0 0;overflow:hidden}
.tab-btn{padding:12px 24px;border:none;background:transparent;cursor:pointer;font-size:14px;font-weight:600;color:var(--gray-500);border-bottom:3px solid transparent}.tab-btn.active{color:var(--ink);border-bottom-color:var(--cyan)}
.tab-content{display:none}.tab-content.active{display:block}
.main{padding:24px 32px;max-width:1400px}
.stats-row{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:16px;margin-bottom:24px}
.stat-card{background:var(--white);border-radius:12px;padding:20px 24px;border:1px solid var(--gray-200)}.stat-card .lbl{font-size:13px;color:var(--gray-500)}.stat-card .num{font-size:28px;font-weight:800;line-height:1.2}.stat-card.red .num{color:var(--red)}.stat-card.green .num{color:var(--green)}.stat-card.amber .num{color:var(--amber)}.stat-card.blue .num{color:var(--blue)}
.filters{display:flex;gap:12px;margin-bottom:16px;flex-wrap:wrap;align-items:center}.filters select,.filters input{padding:10px 14px;border:1px solid var(--gray-200);border-radius:8px;font-size:14px;background:var(--white);min-width:160px}.filters input{min-width:260px}
.modal-overlay{position:fixed;inset:0;background:rgba(0,0,0,.5);z-index:200;display:none;align-items:center;justify-content:center}.modal-overlay.open{display:flex}
.modal{background:var(--white);border-radius:16px;max-width:760px;width:94vw;max-height:90vh;overflow-y:auto;padding:32px}
.modal h3{font-size:20px;margin-bottom:6px;padding-right:30px}.modal .close{float:right;background:none;border:none;font-size:28px;cursor:pointer;color:var(--gray-500);margin:-8px -8px 0 0}
.modal .field{margin-bottom:14px}.modal .field label{display:block;font-size:13px;font-weight:600;margin-bottom:4px;color:var(--gray-500)}.modal .field input,.modal .field select,.modal .field textarea{width:100%;padding:10px 14px;border:1px solid var(--gray-200);border-radius:8px;font-size:14px}.modal .field textarea{min-height:70px;resize:vertical}
.modal .meta-row{display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-bottom:12px}.modal .meta-row .fld{font-size:13px;color:var(--gray-500)}.modal .meta-row .fld strong{display:block;font-size:14px;color:var(--ink);margin-top:2px}
.ai-block{background:var(--cyan-50);border-radius:8px;padding:14px;margin:16px 0;font-size:14px}.ai-block h4{font-size:14px;margin-bottom:4px}
.history-item{display:flex;gap:8px;font-size:12px;padding:3px 0;color:var(--gray-500);align-items:baseline}.history-item .dot{width:7px;height:7px;border-radius:50%;background:var(--cyan);flex-shrink:0;margin-top:5px}
.sub-item{display:flex;align-items:center;gap:12px;padding:10px 14px;background:var(--gray-100);border-radius:8px;margin-bottom:6px;font-size:13px}.sub-item .sub-label{font-weight:700;color:var(--cyan);font-size:15px;min-width:20px}.sub-item .sub-text{flex:1;color:var(--gray-500);font-size:12px;line-height:1.3}.sub-item input[type=checkbox]{width:18px;height:18px;cursor:pointer;accent-color:var(--cyan)}
.month-tabs{display:flex;gap:6px;flex-wrap:wrap;margin-bottom:14px}.month-tab{padding:6px 14px;border:1px solid var(--gray-200);border-radius:100px;font-size:13px;font-weight:600;cursor:pointer;background:var(--white);color:var(--gray-500)}.month-tab:hover{border-color:var(--cyan)}.month-tab.active{background:var(--cyan);color:var(--ink)}
.file-row{display:flex;align-items:center;gap:10px;padding:8px 12px;background:var(--gray-100);border-radius:8px;margin-bottom:6px;font-size:13px}.file-row .file-info{flex:1;min-width:0}.file-row .file-name{font-weight:600;cursor:pointer;color:var(--ink)}.file-row .file-name:hover{color:var(--cyan)}.file-row .file-desc{font-size:11px;color:var(--gray-500)}.file-row .file-meta{font-size:11px;color:var(--gray-500);white-space:nowrap}.file-row .file-del{background:none;border:none;color:var(--red);cursor:pointer;font-size:16px;padding:2px}
.upload-row{display:flex;gap:8px;align-items:flex-end;flex-wrap:wrap;padding:12px;border:2px dashed var(--gray-200);border-radius:8px;margin-top:8px}.upload-row input[type=text]{flex:1;min-width:150px;padding:8px 12px;border:1px solid var(--gray-200);border-radius:6px;font-size:13px}.upload-row input[type=file]{font-size:12px;max-width:220px}
@media(max-width:768px){.main{padding:16px}.topbar{padding:0 16px}.modal{padding:20px}.stats-row{grid-template-columns:1fr 1fr}.notif-drop{right:8px;width:calc(100vw-16px)}}
</style>
</head>
<body>
<div id="loginBox">
<div>
<h1><span>ИИ-Агент</span> ПБ</h1>
<p class="sub">АО «Казахтелеком» — мониторинг производственной безопасности</p>
<label>Корпоративная почта</label>
<input id="loginEmail" placeholder="curator@telecom.kz">
<p class="hint">curator@telecom.kz / dpp@telecom.kz / ahmetov@telecom.kz / serikov@telecom.kz</p>
<label>Пароль</label>
<input id="loginPass" type="password" placeholder="любой">
<p class="err" id="loginErr">Неверная почта или пароль</p>
<button class="btn" style="width:100%;margin-top:8px" onclick="doLogin()">Войти</button>
</div>
</div>
<div id="app">
<div class="topbar">
<div class="brand"><span>ИИ-Агент</span> ПБ</div>
<div class="right">
<span class="user-info" id="userLabel"></span>
<div style="position:relative">
<button class="notif-btn" onclick="toggleNotif()">🔔<span class="badge-count" id="notifCount">0</span></button>
<div class="notif-drop" id="notifDrop"></div>
</div>
<button class="logout-btn" onclick="doLogout()">Выйти</button>
</div>
</div>
<div class="main">
<div class="tabs">
<button class="tab-btn active" data-tab="dashboard" onclick="switchTab('dashboard')">📊 Дашборд</button>
<button class="tab-btn" data-tab="myevents" onclick="switchTab('myevents')">📋 Мероприятия</button>
<button class="tab-btn" data-tab="analytics" onclick="switchTab('analytics')">📈 Аналитика</button>
<button class="tab-btn" data-tab="journal" onclick="switchTab('journal')">📝 Журнал</button>
</div>
<div class="tab-content active" id="tab-dashboard"></div>
<div class="tab-content" id="tab-myevents"></div>
<div class="tab-content" id="tab-analytics"></div>
<div class="tab-content" id="tab-journal"></div>
</div>
</div>
<div class="modal-overlay" id="editModalOverlay"><div class="modal" id="editModalContent"></div></div>
<script>
var sections=["I. Люди","II. Оборудование","III. Аварии и ЧС","IV. Информ. работа","V. ИИ и цифровизация"];
var branches=["Дирекция ПБ","Дивизион «Сеть»","Корпоративный бизнес","Розничный бизнес","Сервисная фабрика","Телеком Комплект","Корпоративный университет","Управление проектами","Цифровой бизнес"];
var regions=["Центральный регион","Алматинский регион","Южный регион","Северный регион","Восточный регион","Западный регион"];
var sm={done:"Исполнено",warn:"На контроле",late:"Просрочено",wait:"В процессе"};
var months=["2026-01","2026-02","2026-03","2026-04","2026-05","2026-06","2026-07","2026-08","2026-09","2026-10","2026-11","2026-12"];
var mn=["Янв","Фев","Мар","Апр","Май","Июн","Июл","Авг","Сен","Окт","Ноя","Дек"];
function M(i){var p=months[i].split("-");return mn[parseInt(p[1])-1]+" "+p[0]}
function esc(s){return s.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;")}
function sBadge(s){var m={done:"green",warn:"amber",late:"red",wait:"gray"};return'<span class="badge '+m[s]+'">'+sm[s]+'</span>'}
function pctHtml(p){var c=p>=80?"var(--green)":p>=40?"var(--amber)":"var(--red)";return'<div class="pct-bar"><div class="track"><div class="fill" style="width:'+p+'%;background:'+c+'"></div></div>'+p+'%</div>'}
var users={
"curator@telecom.kz":{name:"Куратор Плана",branch:0,role:"curator"},
"dpp@telecom.kz":{name:"Директор ДПБ",branch:0,role:"branch"},
"admin@telecom.kz":{name:"Администратор",branch:0,role:"admin"},
"ahmetov@telecom.kz":{name:"Ахметов К.Т.",branch:6,role:"branch"},
"serikov@telecom.kz":{name:"Сериков А.М.",branch:1,role:"branch"},
"nurlanov@telecom.kz":{name:"Нурланов Д.С.",branch:8,role:"branch"},
"aliev@telecom.kz":{name:"Алиев Г.С.",branch:4,role:"branch"},
"tulegenov@telecom.kz":{name:"Тулегенов Е.А.",branch:2,role:"branch"},
"saparov@telecom.kz":{name:"Сапаров А.Д.",branch:3,role:"branch"},
"maratov@telecom.kz":{name:"Маратов Ж.К.",branch:5,role:"branch"},
"iskakov@telecom.kz":{name:"Искаков Р.Н.",branch:7,role:"branch"}
};
var curUser=null,curMonth=5,curRegion=0,editSubIdx=-1;
var expandedEvents={};
var events=null;
function loadEvents(){
var s=localStorage.getItem("samruk_ev");
if(s){try{events=JSON.parse(s)}catch(e){events=[]}}
if(!events||!events.length){
var x=new XMLHttpRequest();
x.open("GET","data.json",true);
x.onload=function(){if(x.status===200)try{events=JSON.parse(x.responseText)}catch(e){events=[]};if(!events)events=[];saveEv()};
x.onerror=function(){events=[];saveEv()};
x.send();
}
}
function saveEv(){localStorage.setItem("samruk_ev",JSON.stringify(events||[]))}
function getMy(){
if(!curUser||!events)return[];
if(curUser.role==="admin"||curUser.role==="curator")return events;
return events.filter(function(e){return e.b===curUser.branch});
}
// ===== FILE STORAGE =====
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 getSC(id){var r=localStorage.getItem("ss_"+id);return r?JSON.parse(r):[]}
function setSC(id,a){localStorage.setItem("ss_"+id,JSON.stringify(a))}
function storageUsed(){var t=0;for(var i=0;i<localStorage.length;i++){var k=localStorage.key(i);if(k.indexOf("sf_")===0||k.indexOf("ss_")===0)t+=localStorage.getItem(k).length*2}return t}
function fmtStorage(){var b=storageUsed();return b>1048576?(b/1048576).toFixed(1)+" МБ":(b/1024).toFixed(0)+" КБ"}
// ===== AUTH =====
function doLogin(){
var em=document.getElementById("loginEmail").value.trim().toLowerCase();
if(users[em]){
curUser={email:em,name:users[em].name,branch:users[em].branch,role:users[em].role};
localStorage.setItem("samruk_u",JSON.stringify(curUser));
showApp();
}else{document.getElementById("loginErr").style.display="block"}
}
function doLogout(){
localStorage.removeItem("samruk_u");curUser=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";
var label=curUser.name+" · "+(curUser.role==="curator"||curUser.role==="admin"?"Все":branches[curUser.branch]);
document.getElementById("userLabel").innerHTML="<strong>"+label+"</strong>";
renderAll();
}
// ===== NOTIFS =====
function notifsUpdate(){
var my=getMy(),n=[],now=new Date();
my.forEach(function(e){
if(e.s==="late")n.push({m:"🔴 "+e.t.slice(0,50)+"...",t:e.due});
if(e.s==="warn"&&e.p<30)n.push({m:"🟡 "+e.t.slice(0,50)+"...",t:"Сейчас"});
if(e.s!=="done"&&e.s!=="late"){
var parts=e.due.split(".");if(parts.length===3){
var due=new Date(parseInt(parts[2]),parseInt(parts[1])-1,parseInt(parts[0]));
var days=(due-now)/86400000;
if(days>0&&days<=7)n.push({m:"⏰ "+Math.round(days)+" дн: "+e.t.slice(0,40)+"...",t:e.due});
}
}
});
var el=document.getElementById("notifDrop"),c=document.getElementById("notifCount");
c.textContent=n.length;c.style.display=n.length?"inline-block":"none";
el.innerHTML=n.length?n.map(function(x){return'<div class="item"><div class="title">'+esc(x.m)+'</div><div class="time">'+x.t+'</div></div>'}).join(""):'<div class="empty">Нет уведомлений</div>';
}
function toggleNotif(){notifsUpdate();document.getElementById("notifDrop").classList.toggle("open")}
// ===== TABS =====
function switchTab(n){
document.querySelectorAll(".tab-btn").forEach(function(b){b.classList.remove("active")});
document.querySelector('[data-tab="'+n+'"]').classList.add("active");
document.querySelectorAll(".tab-content").forEach(function(c){c.classList.remove("active")});
document.getElementById("tab-"+n).classList.add("active");
if(n==="dashboard")renderDashboard();else if(n==="myevents")renderMyEvents();else if(n==="analytics")renderAnalytics();else if(n==="journal")renderJournal();
}
// ===== DASHBOARD =====
function renderDashboard(){
var my=getMy(),done=0,late=0,warn=0,wait=0;
my.forEach(function(e){if(e.s==="done")done++;else if(e.s==="late")late++;else if(e.s==="warn")warn++;else wait++});
var dp=my.length?Math.round(done/my.length*100):0;
var h='<div class="stats-row">';
h+='<div class="stat-card"><div class="lbl">Мероприятий</div><div class="num">'+my.length+'</div></div>';
h+='<div class="stat-card green"><div class="lbl">Исполнено</div><div class="num">'+done+'</div></div>';
h+='<div class="stat-card amber"><div class="lbl">На контроле</div><div class="num">'+warn+'</div></div>';
h+='<div class="stat-card red"><div class="lbl">Просрочено</div><div class="num">'+late+'</div></div>';
h+='<div class="stat-card blue"><div class="lbl">В процессе</div><div class="num">'+wait+'</div></div>';
h+='</div>';
// Section breakdown
h+='<div class="panel"><h3>По разделам</h3>';
var sc=[0,0,0,0,0],sd=[0,0,0,0,0];
my.forEach(function(e){sc[e.sec]++;if(e.s==="done")sd[e.sec]++});
sections.forEach(function(s,i){var p=sc[i]?Math.round(sd[i]/sc[i]*100):0;
h+='<div class="pct-bar" style="margin-bottom:6px"><span style="width:160px;font-size:13px;font-weight:600">'+s+'</span><div class="track" style="flex:1"><div class="fill" style="width:'+p+'%;background:var(--cyan)"></div></div><span style="font-weight:700;font-size:13px">'+p+'%</span></div>';
});
h+='</div>';
// Chart
h+='<div class="panel"><h3>Динамика</h3><div style="height:140px;background:var(--gray-100);border-radius:8px;display:flex;align-items:flex-end;gap:8px;padding:16px 16px 8px">';
h+='<div style="flex:1;background:var(--green);border-radius:4px 4px 0 0;height:50%"></div><div style="flex:1;background:var(--green);border-radius:4px 4px 0 0;height:65%"></div><div style="flex:1;background:var(--cyan);border-radius:4px 4px 0 0;height:75%"></div><div style="flex:1;background:var(--cyan);border-radius:4px 4px 0 0;height:'+dp+'%"></div>';
h+='</div><div style="display:flex;gap:8px;padding:8px 16px 0;font-size:11px;color:var(--gray-500);text-align:center"><span style="flex:1">Q1</span><span style="flex:1">Q2</span><span style="flex:1">Q3</span><span style="flex:1">Q4</span></div></div>';
// Report download
h+='<div class="panel"><h3>📥 Сводный отчёт</h3><div style="display:flex;gap:10px;align-items:center;flex-wrap:wrap;margin-bottom:12px">';
h+='<select id="rptFrom">'+months.map(function(m,i){return'<option value="'+i+'">'+M(i)+'</option>'}).join("")+'</select><span>—</span>';
h+='<select id="rptTo">'+months.map(function(m,i){return'<option value="'+i+'"'+(i===11?" selected":"")+'>'+M(i)+'</option>'}).join("")+'</select>';
h+='<button class="btn btn-sm" onclick="downloadCSV()">Скачать CSV</button>';
h+='<button class="btn btn-sm btn-outline" onclick="downloadHTML()">Скачать HTML</button></div>';
h+='<div style="font-size:12px;color:var(--gray-500);margin-bottom:8px">Хранилище: '+fmtStorage()+'</div>';
h+='<button class="btn btn-sm" style="background:var(--green);color:#fff;margin-right:4px" onclick="exportAll()">💾 Сохранить всё</button>';
h+='<button class="btn btn-sm btn-outline" style="margin-right:4px" onclick="document.getElementById(\'impF\').click()">📥 Загрузить</button>';
h+='<input type="file" id="impF" accept=".json" style="display:none" onchange="importAll(this)">';
h+='<button class="btn btn-sm" style="background:var(--red);color:#fff" onclick="clearAll()">🗑 Очистить</button></div>';
// Curator branch summary
if(curUser.role==="curator"||curUser.role==="admin"){
h+='<div class="panel"><h3>📋 Сводка по филиалам</h3><table><tr><th>Филиал</th><th>Меропр.</th><th>С отчётами</th><th>Файлов</th></tr>';
branches.forEach(function(b,bi){var its=events.filter(function(e){return e.b===bi}),wr=0,tf=0;
its.forEach(function(e){var has=false;regions.forEach(function(r,ri){var d=getMD(e.id,ri,-1);for(var k in d){if(d.hasOwnProperty(k)&&d[k]){if(d[k].report)has=true;tf+=(d[k].files||[]).length}}if(e.sub)e.sub.forEach(function(s,si){var sd=getMD(e.id,ri,si);for(var sk in sd){if(sd.hasOwnProperty(sk)&&sd[sk]){if(sd[sk].report)has=true;tf+=(sd[sk].files||[]).length}}});});if(has)wr++});
h+='<tr><td><strong>'+b+'</strong></td><td>'+its.length+'</td><td>'+(wr?wr+' ✅':"—")+'</td><td>'+(tf||"—")+'</td></tr>';
});
h+='</table></div>';
}
document.getElementById("tab-dashboard").innerHTML=h;
}
// ===== CSV/HTML DOWNLOAD =====
function downloadCSV(){
var from=parseInt(document.getElementById("rptFrom").value),to=parseInt(document.getElementById("rptTo").value);
var my=getMy(),csv="\uFEFF№;Филиал;Мероприятие;Подпункт;Регион;Раздел;Статус;Прогресс;Срок;Факт;Отчёт;Файлы\n";
my.forEach(function(e){
function add(subL,subI){regions.forEach(function(r,ri){var rep="",fls="",d=getMD(e.id,ri,subI);
for(var i=from;i<=to;i++){var m=months[i];if(d[m]){if(d[m].report)rep+=M(i)+": "+d[m].report.replace(/"/g,'""')+"; ";if(d[m].files&&d[m].files.length)fls+=M(i)+": "+d[m].files.map(function(f){return f.name}).join(", ")+"; "}}
csv+=e.id+';'+branches[e.b]+';"'+e.t.replace(/"/g,'""')+'";'+(subL||"общ")+';'+r+';'+sections[e.sec]+';'+sm[e.s]+';'+e.p+'%;'+e.due+';'+(e.done||"—")+';"'+rep+'";"'+fls+'"\n'})}
add("",-1);if(e.sub)e.sub.forEach(function(s,i){add(s.l,i)});
});
var a=document.createElement("a");a.href=URL.createObjectURL(new Blob([csv],{type:"text/csv"}));a.download="otchet_"+M(from)+"-"+M(to)+".csv";a.click()
}
function downloadHTML(){
var from=parseInt(document.getElementById("rptFrom").value),to=parseInt(document.getElementById("rptTo").value);
var my=getMy(),h='<!DOCTYPE html><html><head><meta charset="utf-8"><title>Отчёт ПБ</title><style>body{font:14px/1.5 Arial;max-width:1100px;margin:0 auto;padding:24px}.ev{border:1px solid #ddd;border-radius:8px;padding:16px;margin-bottom:16px}.badge{display:inline-block;padding:2px 8px;border-radius:4px;font-size:11px;font-weight:700}.g{background:#D1FAE5;color:#065F46}.a{background:#FEF3C7;color:#92400E}.r{background:#FEE2E2;color:#991B1B}.w{background:#eee;color:#666}.month{background:#f5f5f5;padding:8px 12px;border-radius:4px;margin:6px 0}ul{list-style:"📄 ";margin-left:20px}</style></head><body>';
h+='<h1>Сводный отчёт ПБ</h1><p>Период: '+M(from)+' — '+M(to)+'</p>';
my.forEach(function(e){var cl={done:"g",warn:"a",late:"r",wait:"w"}[e.s];
h+='<div class="ev"><h3>'+e.id+'. '+esc(e.t)+'</h3><p>Филиал: '+branches[e.b]+' | Раздел: '+sections[e.sec]+' | Срок: '+e.due+' | Прогресс: '+e.p+'% <span class="badge '+cl+'">'+sm[e.s]+'</span></p>';
regions.forEach(function(r,ri){var d=getMD(e.id,ri,-1);
for(var i=from;i<=to;i++){var m=months[i];if(d[m]&&(d[m].report||(d[m].files&&d[m].files.length))){
h+='<div class="month"><strong>'+M(i)+' — '+r+'</strong>';
if(d[m].report)h+='<p>'+esc(d[m].report)+'</p>';
if(d[m].files&&d[m].files.length)h+='<ul>'+d[m].files.map(function(f){return'<li>'+esc(f.name)+(f.desc?' — '+esc(f.desc):'')+' ('+(f.size/1024).toFixed(0)+' КБ)</li>'}).join("")+'</ul>';
h+='</div>'}}});
if(e.sub)e.sub.forEach(function(s,i){regions.forEach(function(r,ri){var sd=getMD(e.id,ri,i);
for(var j=from;j<=to;j++){var m=months[j];if(sd[m]&&(sd[m].report||(sd[m].files&&sd[m].files.length))){
h+='<div style="border-left:3px solid #00E5FF;padding-left:12px;margin:6px 0"><strong>'+s.l+') '+esc(s.t)+'</strong>';
h+='<div class="month"><strong>'+M(j)+' — '+r+'</strong>';
if(sd[m].report)h+='<p>'+esc(sd[m].report)+'</p>';
if(sd[m].files&&sd[m].files.length)h+='<ul>'+sd[m].files.map(function(f){return'<li>'+esc(f.name)+(f.desc?' — '+esc(f.desc):'')+' ('+(f.size/1024).toFixed(0)+' КБ)</li>'}).join("")+'</ul>';
h+='</div></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_"+M(from)+"-"+M(to)+".html";a.click()}catch(e){alert("Слишком большой отчёт. Попробуйте меньший период.")}
}
// ===== MY EVENTS =====
function toggleExpand(eid){expandedEvents[eid]=!expandedEvents[eid];renderMyEvents()}
function renderMyEvents(){
var my=getMy(),sf=document.getElementById("mySF");sf=sf?sf.value:"";
var list=my;if(sf)list=list.filter(function(e){return e.s===sf});
var h='<div class="panel" style="border-radius:0 0 12px 12px"><div class="filters"><select id="mySF" onchange="renderMyEvents()"><option value="">Все</option><option value="done">Исполнено</option><option value="warn">На контроле</option><option value="late">Просрочено</option><option value="wait">В процессе</option></select></div>';
h+='<table><tr><th>№</th><th>Мероприятие</th><th>Филиал</th><th>Раздел</th><th>Срок</th><th>Прогресс</th><th>Статус</th><th></th></tr>';
list.forEach(function(e){
var hs=e.sub&&e.sub.length,sc=getSC(e.id),sd=hs?sc.length:0,st=hs?e.sub.length:0;
h+='<tr><td>'+e.id+'</td><td style="font-size:12px;max-width:280px">';
if(hs)h+='<span onclick="toggleExpand('+e.id+')" style="cursor:pointer;margin-right:6px">'+(expandedEvents[e.id]?'▼':'▶')+'</span>';
h+='<span title="'+esc(e.t)+'" style="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:220px;display:inline-block;vertical-align:middle">'+esc(e.t)+'</span>';
if(hs)h+=' <span style="font-size:11px;color:var(--gray-500)">('+sd+'/'+st+')</span>';
h+='</td><td style="font-size:11px">'+branches[e.b]+'</td><td><span class="badge blue">'+["I","II","III","IV","V"][e.sec]+'</span></td><td>'+e.due+'</td><td>'+pctHtml(e.p)+'</td><td>'+sBadge(e.s)+'</td><td><button class="btn btn-sm" onclick="openEdit('+e.id+')">📝</button></td></tr>';
if(hs&&expandedEvents[e.id])e.sub.forEach(function(s,i){var ch=sc.indexOf(i)>=0;h+='<tr style="background:var(--gray-100)"><td></td><td style="font-size:12px;padding-left:44px"><input type="checkbox" '+(ch?"checked":"")+' onchange="toggleSub('+e.id+','+i+',this.checked)" style="margin-right:6px">'+s.l+') '+esc(s.t)+'</td><td></td><td></td><td></td><td></td><td></td><td></td></tr>'});
});
h+='</table></div>';
document.getElementById("tab-myevents").innerHTML=h;
}
function toggleSub(eid,si,chk){
var sc=getSC(eid);if(chk&&sc.indexOf(si)<0)sc.push(si);else if(!chk)sc=sc.filter(function(x){return x!==si});setSC(eid,sc);
var e=null;for(var i=0;i<events.length;i++)if(events[i].id===eid){e=events[i];break}
if(e&&e.sub){var p=Math.round(sc.length/e.sub.length*100);if(sc.length===e.sub.length&&e.s!=="done")e.s="done";if(sc.length===0&&e.s==="done")e.s="warn";e.p=Math.max(e.p,p);e.h.push(new Date().toLocaleDateString()+" — подпункты "+sc.length+"/"+e.sub.length);saveEv()}
renderMyEvents();
}
// ===== ANALYTICS =====
function renderAnalytics(){
var all=events||[];
var h='<div class="stats-row"><div class="stat-card"><div class="lbl">Всего</div><div class="num">'+all.length+'</div></div><div class="stat-card green"><div class="lbl">Исполнено</div><div class="num">'+all.filter(function(e){return e.s==="done"}).length+'</div></div><div class="stat-card amber"><div class="lbl">На контроле</div><div class="num">'+all.filter(function(e){return e.s==="warn"}).length+'</div></div><div class="stat-card red"><div class="lbl">Просрочено</div><div class="num">'+all.filter(function(e){return e.s==="late"}).length+'</div></div></div>';
h+='<div class="panel"><h3>Дивизионы</h3>';
branches.forEach(function(b,i){var it=all.filter(function(e){return e.b===i}),d=it.filter(function(e){return e.s==="done"}).length,p=it.length?Math.round(d/it.length*100):0;
h+='<div class="pct-bar" style="margin-bottom:6px"><span style="width:200px;font-size:13px;font-weight:600">'+b+'</span><div class="track" style="flex:1"><div class="fill" style="width:'+p+'%;background:'+(p>=50?"var(--green)":p>=25?"var(--amber)":"var(--red)")+'"></div></div><span style="font-weight:700;font-size:13px">'+p+'% ('+d+'/'+it.length+')</span></div>';
});
h+='</div>';
h+='<div class="panel"><h3>Просрочено</h3><table><tr><th>№</th><th>Мероприятие</th><th>Филиал</th><th>Срок</th></tr>';
all.filter(function(e){return e.s==="late"}).forEach(function(e){h+='<tr><td>'+e.id+'</td><td>'+esc(e.t.slice(0,80))+'...</td><td>'+branches[e.b]+'</td><td style="color:var(--red);font-weight:700">'+e.due+'</td></tr>'});
h+='</table></div>';
document.getElementById("tab-analytics").innerHTML=h;
}
// ===== JOURNAL =====
function renderJournal(){
var log=JSON.parse(localStorage.getItem("samruk_log")||"[]").reverse(),h='<div class="panel"><h3>Журнал действий</h3>';
if(!log.length)h+='<p style="color:var(--gray-500)">Записей пока нет</p>';
else{h+='<table><tr><th>Время</th><th>Пользователь</th><th>Действие</th><th>Мероприятие</th></tr>';
log.forEach(function(l){h+='<tr><td style="font-size:11px">'+new Date(l.ts).toLocaleString()+'</td><td>'+esc(l.user)+'</td><td>'+esc(l.action)+'</td><td>'+esc(l.detail||"")+'</td></tr>'});
h+='</table>'}
h+='</div>';
document.getElementById("tab-journal").innerHTML=h;
}
function addLog(action,eid,detail){
var l=JSON.parse(localStorage.getItem("samruk_log")||"[]");
l.push({ts:new Date().toISOString(),user:curUser?curUser.name:"?",action:action,eid:eid||0,detail:detail||""});
if(l.length>500)l=l.slice(-500);localStorage.setItem("samruk_log",JSON.stringify(l));
}
// Export/Import/Clear
function exportAll(){
var data={events:events,date:new Date().toISOString(),files:{},subChecks:{}};
for(var i=0;i<localStorage.length;i++){var k=localStorage.key(i);if(k.indexOf("sf_")===0)data.files[k]=localStorage.getItem(k);if(k.indexOf("ss_")===0)data.subChecks[k]=localStorage.getItem(k)}
var a=document.createElement("a");a.href=URL.createObjectURL(new Blob([JSON.stringify(data)],{type:"application/json"}));a.download="backup_"+new Date().toISOString().slice(0,10)+".json";a.click();addLog("сохранил бекап")
}
function importAll(inp){if(!inp.files.length)return;var r=new FileReader();r.onload=function(ev){try{var d=JSON.parse(ev.target.result);if(!d.events||!d.files)return alert("Неверный формат");events=d.events;saveEv();for(var k in d.files)localStorage.setItem(k,d.files[k]);for(var k in d.subChecks)localStorage.setItem(k,d.subChecks[k]);alert("Восстановлено. Обновите страницу.");location.reload()}catch(e){alert("Ошибка: "+e.message)}};r.readAsText(inp.files[0])}
function clearAll(){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("Очищено");renderDashboard()}
// ===== EDIT MODAL =====
function openEdit(id,mi,ri,si){
if(typeof mi==="number")curMonth=mi;if(typeof ri==="number")curRegion=ri;editSubIdx=(typeof si==="number")?si:-1;
var e=null;for(var i=0;i<events.length;i++)if(events[i].id===id){e=events[i];break}if(!e)return;
var cm=months[curMonth],sc=getSC(e.id);
var md=getMD(e.id,curRegion,-1),cd=md[cm]||{report:"",files:[]},cfs=cd.files||[];
var h='<button class="close" onclick="closeEM()">&times;</button><span class="badge blue">Раздел '+["I","II","III","IV","V"][e.sec]+'</span>';
h+='<h3 style="margin:8px 0">'+esc(e.t)+'</h3>';
h+='<div class="meta-row"><div class="fld">Филиал<strong>'+branches[e.b]+'</strong></div><div class="fld">Ответственный<strong>'+esc(e.r)+'</strong></div><div class="fld">Срок<strong>'+e.due+'</strong></div><div class="fld">Факт<strong>'+e.done+'</strong></div></div>';
h+='<div class="field"><label>Статус</label><select id="es"><option value="wait"'+(e.s==="wait"?" selected":"")+'>В процессе</option><option value="warn"'+(e.s==="warn"?" selected":"")+'>На контроле</option><option value="late"'+(e.s==="late"?" selected":"")+'>Просрочено</option><option value="done"'+(e.s==="done"?" selected":"")+'>Исполнено</option></select></div>';
h+='<div class="field"><label>Прогресс (%)</label><input type="range" id="ep" min="0" max="100" value="'+e.p+'" oninput="document.getElementById(\'pv\').textContent=this.value+\'%\'"><span id="pv" style="font-weight:700">'+e.p+'%</span></div>';
h+='<div class="field"><label>Комментарий</label><textarea id="ec"></textarea></div>';
// Month + Region tabs
var mh='<div class="month-tabs">';months.forEach(function(m,i){mh+='<span class="month-tab'+(i===curMonth?" active":"")+'" onclick="openEdit('+e.id+','+i+','+curRegion+','+editSubIdx+')">'+M(i)+'</span>'});mh+='</div>';
var rh='<div class="month-tabs" style="margin-bottom:12px"><b style="font-size:13px;margin-right:8px">Регион:</b>';
regions.forEach(function(r,i){rh+='<span class="month-tab'+(i===curRegion?" active":"")+'" onclick="openEdit('+e.id+','+curMonth+','+i+','+editSubIdx+')">'+r+'</span>'});rh+='</div>';
// Main files
h+='<div style="border-top:1px solid var(--gray-200);padding-top:16px;margin-top:12px"><b>📎 '+regions[curRegion]+'</b>';
h+=mh+rh;
h+='<div class="field"><label>Текст за '+M(curMonth)+'</label><textarea id="mr" style="min-height:80px">'+esc(cd.report||"")+'</textarea></div>';
cfs.forEach(function(f,i){h+='<div class="file-row"><span class="file-info"><span class="file-name" onclick="dlF('+e.id+','+curMonth+','+i+','+curRegion+',-1)">📄 '+esc(f.name)+'</span>'+(f.desc?'<span class="file-desc">'+esc(f.desc)+'</span>':'')+'</span><span class="file-meta">'+(f.size/1024).toFixed(0)+' КБ</span><button class="file-del" onclick="rmF('+e.id+','+curMonth+','+i+','+curRegion+',-1)">×</button></div>'});
h+='<div class="upload-row"><input type="text" id="fd" placeholder="Описание"><input type="file" id="fi" multiple><button class="btn btn-sm" id="ub" onclick="upF('+e.id+','+curMonth+','+curRegion+',-1)">📤 Загрузить</button></div>';
h+='<p style="font-size:11px;color:var(--gray-500);margin-top:6px">Формы: '+esc(e.dname)+'</p></div>';
// Sub-items
if(e.sub&&e.sub.length){
h+='<div style="border-top:2px solid var(--cyan);padding-top:16px;margin-top:16px"><b>📋 Подпункты</b><p style="font-size:12px;color:var(--gray-500);margin:4px 0 12px">Нажмите 📎 для управления файлами</p>';
e.sub.forEach(function(s,i){
var ch=sc.indexOf(i)>=0,sd=getMD(e.id,curRegion,i),scd=sd[cm]||{report:"",files:[]},scfs=scd.files||[],isA=editSubIdx===i;
h+='<div class="sub-item" style="flex-wrap:wrap;padding:12px 14px;margin-bottom:8px;'+(isA?'border:2px solid var(--cyan)':'')+'"><input type="checkbox" id="sc_'+i+'" '+(ch?"checked":"")+'><span class="sub-label">'+s.l+')</span><span class="sub-text" style="flex:1">'+esc(s.t)+'</span><span style="font-size:11px;color:var(--gray-500);margin-right:8px">Файлов: '+scfs.length+'</span><button class="btn btn-sm" onclick="openEdit('+e.id+','+curMonth+','+curRegion+','+i+')" style="font-size:12px;'+(isA?'background:var(--cyan);font-weight:700':'')+'">'+(isA?'📂 Открыто':'📎')+'</button></div>';
if(isA){
h+='<div style="margin:0 0 16px 20px;padding:16px;background:var(--cyan-50);border-radius:8px;border:2px solid var(--cyan)"><b>'+s.l+') '+esc(s.t)+'</b><p style="font-size:11px;color:var(--gray-500);margin-bottom:8px">'+regions[curRegion]+' · '+M(curMonth)+'</p>';
h+='<div class="field"><label>Текст</label><textarea id="mr_s'+i+'" style="min-height:60px">'+esc(scd.report||"")+'</textarea></div>';
scfs.forEach(function(f,fi){h+='<div class="file-row"><span class="file-name" onclick="dlF('+e.id+','+curMonth+','+fi+','+curRegion+','+i+')">📄 '+esc(f.name)+'</span><span class="file-meta">'+(f.size/1024).toFixed(0)+' КБ</span><button class="file-del" onclick="rmF('+e.id+','+curMonth+','+fi+','+curRegion+','+i+')">×</button></div>'});
h+='<div class="upload-row"><input type="text" id="fd_s'+i+'" placeholder="Описание"><input type="file" id="fi_s'+i+'" multiple><button class="btn btn-sm" onclick="upF('+e.id+','+curMonth+','+curRegion+','+i+')">📤</button></div></div>';
}
});
h+='</div>';
}
h+='<div class="ai-block"><h4>🤖 ИИ</h4>'+esc(e.ai)+'</div>';
h+='<div><b>История:</b><div>';e.h.forEach(function(x){h+='<div class="history-item"><div class="dot"></div>'+esc(x)+'</div>'});h+='</div></div>';
h+='<div style="margin-top:20px;display:flex;gap:12px"><button class="btn" onclick="saveEdit('+e.id+','+curMonth+')">Сохранить</button><button class="btn btn-outline" onclick="closeEM()">Отмена</button></div>';
document.getElementById("editModalContent").innerHTML=h;
document.getElementById("editModalOverlay").classList.add("open");
}
function saveEdit(id,mk){
mk=months[mk];var e=null;for(var i=0;i<events.length;i++)if(events[i].id===id){e=events[i];break}if(!e)return;
e.s=document.getElementById("es").value;e.p=parseInt(document.getElementById("ep").value);
var cmt=(document.getElementById("ec").value||"").trim(),mr=document.getElementById("mr");
if(mr){var ad=getMD(id,curRegion,-1);if(!ad[mk])ad[mk]={report:"",files:[]};ad[mk].report=mr.value;setMD(id,ad,curRegion,-1)}
if(e.sub&&e.sub.length){var cks=[];e.sub.forEach(function(_,i){var el=document.getElementById("sc_"+i);if(el&&el.checked)cks.push(i);var sr=document.getElementById("mr_s"+i);if(sr){var sd=getMD(id,curRegion,i);if(!sd[mk])sd[mk]={report:"",files:[]};sd[mk].report=sr.value;setMD(id,sd,curRegion,i)}});setSC(id,cks)}
var now=new Date().toLocaleDateString();e.h.push(now+" — "+curUser.name+": "+sm[e.s]+", "+e.p+"%");
if(e.s==="done"&&e.done==="—")e.done=now;saveEv();addLog("изменил",id,sm[e.s]);closeEM();renderAll();
}
function closeEM(){document.getElementById("editModalOverlay").classList.remove("open")}
// ===== FILE OPS =====
function upF(eid,mk,ri,si){
mk=months[mk];var pfx=si>=0?"_s"+si:"",fi=document.getElementById("fi"+pfx);
if(!fi||!fi.files.length)return;var desc=(document.getElementById("fd"+pfx)||{}).value;desc=(desc||"").trim();
var btn=document.getElementById("ub"+pfx);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,MAX=3072*1024;
function fin(){try{setMD(eid,ad,ri,si)}catch(e){alert("Хранилище заполнено")}if(sk)alert(sk+" файлов >3 МБ пропущено");addLog("файлы",eid,regions[ri]);closeEM();openEdit(eid,curMonth,ri,si>=0?si:undefined)}
for(var i=0;i<fi.files.length;i++){(function(f){if(f.size>MAX){sk++;pr++;if(pr===fi.files.length)fin();return}
var r=new FileReader();r.onload=function(ev){arr.push({name:f.name,size:f.size,type:f.type,desc:desc,date:new Date().toLocaleDateString(),data:ev.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 dlF(eid,mk,idx,ri,si){si=si||-1;mk=months[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 rmF(eid,mk,idx,ri,si){si=si||-1;mk=months[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);closeEM();openEdit(eid,curMonth,ri,si>=0?si:undefined)}
// ===== INIT =====
function renderAll(){notifsUpdate();switchTab(document.querySelector(".tab-btn.active").dataset.tab)}
document.getElementById("editModalOverlay").addEventListener("click",function(e){if(e.target===this)closeEM()});
document.addEventListener("keydown",function(e){if(e.key==="Escape"){closeEM();document.getElementById("notifDrop").classList.remove("open")}});
document.addEventListener("click",function(e){if(!e.target.closest(".notif-btn")&&!e.target.closest(".notif-drop"))document.getElementById("notifDrop").classList.remove("open")});
loadEvents();
var su=localStorage.getItem("samruk_u");if(su){try{curUser=JSON.parse(su);showApp()}catch(e){}}
</script>
</body>
</html>