samruk-ai-agent/index.html

577 lines
48 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 -apple-system,BlinkMacSystemFont,"Segoe UI",Inter,system-ui,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;text-decoration:none}
.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}.badge.gray{background:var(--gray-100);color:var(--gray-700)}
.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}
#loginScreen{display:flex;align-items:center;justify-content:center;min-height:100vh;background:var(--ink)}
.login-box{background:var(--white);border-radius:16px;padding:48px 40px;width:440px;max-width:90vw;text-align:center}
.login-box h1{font-size:24px;font-weight:800;margin-bottom:4px}.login-box h1 span{color:var(--cyan)}
.login-box .sub{color:var(--gray-500);font-size:14px;margin-bottom:32px}
.login-box label{display:block;text-align:left;font-size:13px;font-weight:600;margin-bottom:6px}
.login-box input[type=email],.login-box input[type=password]{width:100%;padding:12px 16px;border:1px solid var(--gray-200);border-radius:8px;font-size:15px;margin-bottom:20px}
.login-box .hint{font-size:12px;color:var(--gray-500);margin-top:-12px;margin-bottom:16px;text-align:left}
.login-box .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(210px,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}
.chart-stub{height:180px;background:var(--gray-100);border-radius:8px;display:flex;align-items:flex-end;gap:8px;padding:16px 16px 8px}
.chart-stub .bar{flex:1;background:var(--cyan);border-radius:4px 4px 0 0;min-height:4px}
.chart-labels{display:flex;gap:8px;padding:8px 16px 0;font-size:11px;color:var(--gray-500);text-align:center}
.chart-labels span{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.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;line-height:1;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-items{margin:12px 0}
.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}
.report-bar{display:flex;gap:10px;align-items:center;margin-bottom:16px;flex-wrap:wrap}
.report-bar select,.report-bar input{padding:8px 14px;border:1px solid var(--gray-200);border-radius:8px;font-size:13px}
@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="loginScreen">
<form class="login-box" onsubmit="return doLogin(event)">
<h1><span>ИИ-Агент</span> ПБ</h1>
<p class="sub">АО «Казахтелеком» — мониторинг производственной безопасности</p>
<label>Корпоративная почта</label>
<input type="email" id="loginEmail" placeholder="surname@telecom.kz" required>
<p class="hint">curator@telecom.kz (куратор, всё) / dpp@telecom.kz (ДПБ, ваш филиал) / пароль любой</p>
<label>Пароль</label>
<input type="password" id="loginPass" placeholder="••••••••" required>
<p class="err" id="loginErr">Неверная почта или пароль</p>
<button type="submit" class="btn" style="width:100%;margin-top:8px">Войти</button>
</form>
</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>
<span class="logout-btn" onclick="doLogout()">Выйти</span>
</div>
</div>
<div class="main">
<div class="tabs">
<button class="tab-btn active" data-tab="dashboard">📊 Дашборд</button>
<button class="tab-btn" data-tab="myevents">📋 Мероприятия</button>
<button class="tab-btn" data-tab="analytics">📈 Аналитика</button>
<button class="tab-btn" data-tab="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>
"use strict";
var sections=["I. Люди","II. Оборудование","III. Аварии и ЧС","IV. Информ. работа","V. ИИ и цифровизация"];
var branches=["Дирекция производственной безопасности","Дивизион «Сеть»","Дивизион по корпоративному бизнесу","Дивизион по розничному бизнесу","Сервисная фабрика","Дирекция «Телеком Комплект»","Корпоративный университет","Дирекция управления проектами","Дивизион цифрового бизнеса"];
var regions=["Центральный регион","Алматинский регион","Южный регион","Северный регион","Восточный регион","Западный регион"];
var statusMap={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 monthNames=["Янв","Фев","Мар","Апр","Май","Июн","Июл","Авг","Сен","Окт","Ноя","Дек"];
function M(idx){return monthNames[parseInt(months[idx].split("-")[1])-1]+" "+months[idx].split("-")[0]}
function esc(s){return s.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;")}
function sb(s){var m={done:"green",warn:"amber",late:"red",wait:"gray"};return'<span class="badge '+m[s]+'">'+statusMap[s]+'</span>'}
function pct(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;
// Load events
var events=null;
(function(){
var s=localStorage.getItem("samruk_ev");if(s){events=JSON.parse(s);return}
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){}}if(!events)events=[];localStorage.setItem("samruk_ev",JSON.stringify(events));if(curUser)renderAll()};
x.onerror=function(){events=[];localStorage.setItem("samruk_ev",JSON.stringify(events));if(curUser)renderAll()};
x.send();
})();
function saveEvents(){localStorage.setItem("samruk_ev",JSON.stringify(events))}
// Action log
function addLog(action,eventId,detail){var l=JSON.parse(localStorage.getItem("samruk_log")||"[]");l.push({ts:new Date().toISOString(),user:curUser.name,role:curUser.role,action:action,eid:eventId,detail:detail||""});if(l.length>500)l=l.slice(-500);localStorage.setItem("samruk_log",JSON.stringify(l))}
function getLog(){return JSON.parse(localStorage.getItem("samruk_log")||"[]")}
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})}
// Auth
function doLogin(e){e.preventDefault();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));addLog("вошёл");showApp()}else{document.getElementById("loginErr").style.display="block"}return false}
function doLogout(){addLog("вышел");localStorage.removeItem("samruk_u");curUser=null;document.getElementById("loginScreen").style.display="flex";document.getElementById("app").style.display="none"}
function showApp(){document.getElementById("loginScreen").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:"Сейчас"});
// Deadline approaching (within 7 days)
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,50)+"...",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(name){
document.querySelectorAll(".tab-btn").forEach(function(b){b.classList.remove("active")});
document.querySelector('[data-tab="'+name+'"]').classList.add("active");
document.querySelectorAll(".tab-content").forEach(function(c){c.classList.remove("active")});
document.getElementById("tab-"+name).classList.add("active");
if(name==="dashboard")renderDashboard();else if(name==="myevents")renderMyEvents();else if(name==="analytics")renderAnalytics();else if(name==="journal")renderJournal();
}
// ===== DASHBOARD =====
function renderDashboard(){
var my=getMy(),done=my.filter(function(e){return e.s==="done"}).length,late=my.filter(function(e){return e.s==="late"}).length,warn=my.filter(function(e){return e.s==="warn"}).length,wait=my.filter(function(e){return e.s==="wait"}).length,donePct=my.length?Math.round(done/my.length*100):0;
var h='<div class="stats-row">';
h+='<div class="stat-card"><div class="lbl">Мероприятий ('+(curUser.role==="admin"?"все":"дивизион")+')</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 secCounts=[0,0,0,0,0],secDone=[0,0,0,0,0];
my.forEach(function(e){secCounts[e.sec]++;if(e.s==="done")secDone[e.sec]++});
sections.forEach(function(s,i){var pct=secCounts[i]?Math.round(secDone[i]/secCounts[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:'+pct+'%;background:var(--cyan)"></div></div><span style="font-weight:700;font-size:13px">'+pct+'%</span></div>';
});
h+='</div>';
// Progress chart
h+='<div class="panel"><h3>Динамика по кварталам</h3><div class="chart-stub">';
h+='<div class="bar" style="height:50%;background:var(--green)"></div><div class="bar" style="height:65%;background:var(--green)"></div><div class="bar" style="height:75%"></div><div class="bar" style="height:'+donePct+'%"></div>';
h+='</div><div class="chart-labels"><span>Q1 (факт)</span><span>Q2 (прогноз)</span><span>Q3 (план)</span><span>Q4 (цель)</span></div></div>';
// Report download
h+='<div class="panel"><h3>📥 Скачать сводный отчёт</h3><div class="report-bar">';
h+='<select id="rptFrom">'+months.map(function(m,i){return'<option value="'+i+'">'+M(i)+'</option>'}).join("")+'</select>';
h+='<span>—</span><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="downloadReport()">Скачать CSV</button>';
h+='<button class="btn btn-sm btn-outline" onclick="downloadHTML()" style="margin-left:8px">Скачать HTML</button>';
h+='<div style="margin-top:12px;font-size:12px;color:var(--gray-500)">Хранилище: '+fmtStorage()+'</div>';
h+='<button class="btn btn-sm" style="margin-top:6px;background:var(--green);color:#fff" onclick="exportAll()">💾 Сохранить все данные</button>';
h+='<button class="btn btn-sm btn-outline" style="margin-top:6px;margin-left: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="margin-top:6px;margin-left:4px;background:var(--red);color:#fff" onclick="clearAllFiles()">🗑 Очистить файлы</button></div></div>';
if(curUser.role==="curator"||curUser.role==="admin"){
h+='<div class="panel"><h3>📋 Сводка по филиалам — что загружено</h3><table><tr><th>Филиал</th><th>Меропр.</th><th>С отчётами</th><th>Файлов</th><th>Прогресс</th></tr>';
branches.forEach(function(b,bi){var items=events.filter(function(e){return e.b===bi}),withRpt=0,totalFiles=0;
items.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]&&(d[k].report||(d[k].files&&d[k].files.length))){has=true;totalFiles+=(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]&&(sd[sk].report||(sd[sk].files&&sd[sk].files.length))){has=true;totalFiles+=(sd[sk].files||[]).length}}})});if(has)withRpt++});
h+='<tr><td><strong>'+b+'</strong></td><td>'+items.length+'</td><td>'+(withRpt?withRpt+' ✅':'—')+'</td><td>'+(totalFiles||'—')+'</td><td>'+pct(items.length?Math.round(withRpt/items.length*100):0)+'</td></tr>';
});
h+='</table></div>';
}
document.getElementById("tab-dashboard").innerHTML=h;
}
function downloadReport(){
var from=parseInt(document.getElementById("rptFrom").value),to=parseInt(document.getElementById("rptTo").value);
var my=getMy(),csv="№;Филиал;Мероприятие;Подпункт;Регион;Раздел;Статус;Прогресс;Срок;Факт;Отчёт (текст);Файлы\n";
my.forEach(function(e){
function addRow(subLabel,subIdx){regions.forEach(function(r,ri){var rep="",fls="",d=getMD(e.id,ri,subIdx);
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,'""')+'";'+(subLabel||"общее")+';'+r+';'+sections[e.sec]+';'+statusMap[e.s]+';'+e.p+'%;'+e.due+';'+(e.done||"—")+';"'+rep+'";"'+fls+'"\n'})}
addRow("",-1);
if(e.sub) e.sub.forEach(function(s,i){ addRow(s.l,i); });
});
addRow("",-1);
if(e.sub) e.sub.forEach(function(s,i){ addRow(s.l,i); });
});
var blob=new Blob(["\uFEFF"+csv],{type:"text/csv;charset=utf-8"}),a=document.createElement("a");a.href=URL.createObjectURL(blob);a.download="otchet_pb_"+M(from)+"-"+M(to)+".csv";a.click();addLog("скачал CSV-отчёт",null,M(from)+"-"+M(to))
}
function downloadHTML(){
var from=parseInt(document.getElementById("rptFrom").value),to=parseInt(document.getElementById("rptTo").value);
var my=getMy(),h='<!DOCTYPE html><html lang="ru"><head><meta charset="utf-8"><title>Сводный отчёт ПБ</title><style>body{font:14px/1.5 Arial,sans-serif;max-width:1100px;margin:0 auto;padding:24px}h1{font-size:22px;margin-bottom:4px}h1 span{color:#00E5FF}h2{font-size:18px;margin:24px 0 8px}.ev{border:1px solid #ddd;border-radius:8px;padding:16px;margin-bottom:16px}.ev h3{font-size:15px;margin:0 0 8px}.meta{display:flex;gap:16px;flex-wrap:wrap;font-size:12px;color:#666;margin-bottom:8px}.meta strong{color:#111}.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}.sub-blk{border-left:3px solid #00E5FF;padding-left:12px;margin:8px 0}.sub-blk strong{font-size:12px}.files{font-size:12px;color:#555;margin-top:2px}ul{list-style:"📄 ";margin-left:20px}@media print{.ev{border-color:#999}}</style></head><body>';
h+='<h1><span>ИИ-Агент</span> ПБ — Сводный отчёт</h1><p style="color:#666">Период: '+M(from)+' — '+M(to)+' · Дивизион: '+branches[curUser.branch]+' · Сформирован: '+new Date().toLocaleDateString()+'</p>';
h+='<p style="font-size:12px;color:#888;background:#fffbe6;padding:8px 12px;border-radius:4px;">💡 Файлы перечислены названиями. Чтобы получить сами файлы — используйте кнопку «💾 Сохранить все данные» на дашборде.</p>';
my.forEach(function(e){
var scls={done:"g",warn:"a",late:"r",wait:"w"}[e.s];
h+='<div class="ev"><h3>'+e.id+'. '+esc(e.t)+'</h3>';
h+='<div class="meta"><span>Филиал: <strong>'+branches[e.b]+'</strong></span><span>Раздел: <strong>'+sections[e.sec]+'</strong></span><span>Срок: <strong>'+e.due+'</strong></span><span>Факт: <strong>'+(e.done||"—")+'</strong></span><span>Прогресс: <strong>'+e.p+'%</strong></span><span class="badge '+scls+'">'+statusMap[e.s]+'</span></div>';
h+='<div class="meta"><span>Ответственный: <strong>'+esc(e.r)+'</strong></span></div>';
// Main event data — all regions
regions.forEach(function(r,ri){h+=renderMonthBlock(e.id,ri,-1,r,from,to)});
// Sub-items — all regions
if(e.sub) e.sub.forEach(function(s,i){ h+='<div class="sub-blk"><strong>'+s.l+') '+esc(s.t)+'</strong>';regions.forEach(function(r,ri){h+=renderMonthBlock(e.id,ri,i,"",from,to)});h+='</div>'});
h+='<div style="font-size:11px;color:#666;margin-top:8px">🤖 ИИ: '+esc(e.ai)+'</div></div>';
});
h+='</body></html>';
try{
var blob=new Blob(["\uFEFF"+h],{type:"text/html;charset=utf-8"}),a=document.createElement("a");
a.href=URL.createObjectURL(blob);a.download="otchet_pb_"+M(from)+"-"+M(to)+".html";a.click();addLog("скачал HTML-отчёт",null,M(from)+"-"+M(to));
setTimeout(function(){URL.revokeObjectURL(a.href)},60000);
}catch(e){
alert("⚠️ Отчёт слишком большой для скачивания. Попробуйте выбрать меньший период или очистить часть файлов.");
}
}
function renderMonthBlock(id,ri,si,label,from,to){
var d=getMD(id,ri,si),has=false,html='';
for(var i=from;i<=to;i++){var m=months[i];if(d[m]&&(d[m].report||(d[m].files&&d[m].files.length))){
has=true;html+='<div class="month"><strong>'+M(i)+(label?' — '+label:'')+'</strong>';
if(d[m].report)html+='<p style="font-size:13px;margin:4px 0">'+esc(d[m].report)+'</p>';
if(d[m].files&&d[m].files.length)html+='<div class="files"><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></div>';
html+='</div>'}}
if(!has)html='<p style="font-size:12px;color:#999">Нет отчётов</p>';
return html;
}
// ===== MY EVENTS =====
var expandedEvents = {};
function toggleExpand(eid) { expandedEvents[eid] = !expandedEvents[eid]; renderMyEvents(); }
function renderMyEvents(){
var my=getMy(),h='<div class="panel" style="border-radius:0 0 12px 12px">';
h+='<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>';
var sf=document.getElementById("mySF");sf=sf?sf.value:"";
var list=my;if(sf)list=list.filter(function(e){return e.s===sf});
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 hasSub = e.sub && e.sub.length;
var sc = getSC(e.id);
var subDone = hasSub ? sc.length : 0;
var subTotal = hasSub ? e.sub.length : 0;
h+='<tr><td>'+e.id+'</td>';
h+='<td style="font-size:12px;max-width:300px">';
if(hasSub) 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:260px;display:inline-block;vertical-align:middle">'+esc(e.t)+'</span>';
if(hasSub) h+=' <span style="font-size:11px;color:var(--gray-500)">('+subDone+'/'+subTotal+')</span>';
h+='</td>';
h+='<td style="font-size:11px">'+branches[e.b]+'</td>';
h+='<td><span class="badge blue">'+["I","II","III","IV","V"][e.sec]+'</span></td>';
h+='<td>'+e.due+'</td><td>'+pct(e.p)+'</td><td>'+sb(e.s)+'</td>';
h+='<td><button class="btn btn-sm" onclick="openEdit('+e.id+')">📝</button></td></tr>';
// Sub-items
if(hasSub && expandedEvents[e.id]){
e.sub.forEach(function(s,i){
var ch = sc.indexOf(i) >= 0;
h+='<tr style="background:var(--gray-100)"><td></td>';
h+='<td style="font-size:12px;padding-left:44px"><input type="checkbox" id="si_'+e.id+'_'+i+'" '+(ch?'checked':'')+' onchange="toggleSubItem('+e.id+','+i+',this.checked)" style="margin-right:6px"><span style="color:var(--gray-500)">'+s.l+') '+esc(s.t)+'</span></td>';
h+='<td></td><td></td><td></td><td></td><td></td></tr>';
});
}
});
h+='</table></div>';
document.getElementById("tab-myevents").innerHTML=h;
}
function toggleSubItem(eid, subIdx, checked) {
var sc = getSC(eid);
if(checked) { if(sc.indexOf(subIdx) < 0) sc.push(subIdx); }
else { sc = sc.filter(function(x){ return x !== subIdx; }); }
setSC(eid, sc);
// Update event progress based on sub-items
var e = null; for(var i=0;i<events.length;i++){if(events[i].id===eid){e=events[i];break}}
if(e && e.sub && e.sub.length) {
var pct = Math.round(sc.length / e.sub.length * 100);
if(sc.length === e.sub.length && e.s === 'warn') e.s = 'done';
else if(sc.length === 0 && e.s === 'done') e.s = 'warn';
e.p = Math.max(e.p, pct);
e.h.push(new Date().toLocaleDateString()+' — '+curUser.name+': подпункты '+sc.length+'/'+e.sub.length);
saveEvents();addLog("подпункты",eid,sc.length+"/"+e.sub.length);
}
renderMyEvents();
}
// ===== ANALYTICS =====
function renderAnalytics(){
var h='<div class="stats-row">';
var all=events||[],done=all.filter(function(e){return e.s==="done"}).length,total=all.length;
h+='<div class="stat-card"><div class="lbl">Всего</div><div class="num">'+total+'</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">'+all.filter(function(e){return e.s==="warn"}).length+'</div></div>';
h+='<div class="stat-card red"><div class="lbl">Просрочено</div><div class="num">'+all.filter(function(e){return e.s==="late"}).length+'</div></div>';
h+='</div>';
h+='<div class="panel"><h3>Рейтинг дивизионов</h3>';
branches.forEach(function(b,i){var items=all.filter(function(e){return e.b===i}),d=items.filter(function(e){return e.s==="done"}).length,pct=items.length?Math.round(d/items.length*100):0;
h+='<div class="pct-bar" style="margin-bottom:6px"><span style="width:240px;font-size:13px;font-weight:600">'+b+'</span><div class="track" style="flex:1"><div class="fill" style="width:'+pct+'%;background:'+(pct>=50?"var(--green)":pct>=25?"var(--amber)":"var(--red)")+'"></div></div><span style="font-weight:700;font-size:13px">'+pct+'% ('+d+'/'+items.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 style="font-size:12px">'+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;
}
// ===== EDIT MODAL =====
var editSubIdx = -1; // -1=none, click 📎 to expand
function openEdit(id, mi, ri, si){
if(typeof mi==="number")curMonth=mi;
if(typeof ri==="number")curRegion=ri;
editSubIdx = (typeof si==="number") ? si : -1; // reset to -1 if not specified
var e=null;for(var i=0;i<events.length;i++){if(events[i].id===id){e=events[i];break}}if(!e)return;
var hasSub = e.sub && e.sub.length;
var cm=months[curMonth];
var sc=getSC(e.id);
var md=getMD(e.id,curRegion,-1), cd=md[cm]||{report:"",files:[]}, cfs=cd.files||[];
var html='<button class="close" onclick="closeEM()">&times;</button>';
html+='<span class="badge blue">Раздел '+["I","II","III","IV","V"][e.sec]+'</span>';
html+='<h3 style="margin:8px 0">'+esc(e.t)+'</h3>';
html+='<div class="meta-row"><div class="fld">Дивизион<strong>'+esc(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>';
html+='<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>';
html+='<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>';
html+='<div class="field"><label>Комментарий</label><textarea id="ec" placeholder="Комментарий..."></textarea></div>';
// Month tabs + 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"><span style="font-weight:600;font-size:13px;margin-right:8px">Регион:</span>';
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 event files (for selected region)
html+='<div style="border-top:1px solid var(--gray-200);padding-top:16px;margin-top:12px"><div style="font-weight:600;font-size:14px;margin-bottom:8px">📎 Отчётность — '+regions[curRegion]+'</div>';
html+=mh;html+=rh;
html+='<div class="field"><label>Текст отчёта за '+M(curMonth)+' ('+regions[curRegion]+')</label><textarea id="mr" placeholder="Опишите ход исполнения... Можно без файлов." style="min-height:80px">'+esc(cd.report||"")+'</textarea></div>';
cfs.forEach(function(f,i){html+='<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)+' КБ · '+f.date+'</span><button class="file-del" onclick="rmF('+e.id+','+curMonth+','+i+','+curRegion+',-1)">×</button></div>'});
html+='<div class="upload-row"><input type="text" id="fd" placeholder="Описание файла"><input type="file" id="fi" multiple style="max-width:220px"><button class="btn btn-sm" id="ub" onclick="uploadFiles('+e.id+','+curMonth+','+curRegion+',-1)">📤 Загрузить</button></div>';
html+='<p style="font-size:11px;color:var(--gray-500);margin-top:6px">Формы завершения: '+esc(e.dname)+'</p></div>';
// Sub-items
var hasSub = e.sub && e.sub.length;
if(hasSub){
html+='<div style="border-top:2px solid var(--cyan);padding-top:16px;margin-top:16px"><div style="font-weight:700;font-size:15px;margin-bottom:4px">📋 Подпункты</div><p style="font-size:12px;color:var(--gray-500);margin-bottom: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||[];
var isActive=editSubIdx===i;
html+='<div class="sub-item" style="flex-wrap:wrap;padding:12px 14px;margin-bottom:8px;'+(isActive?'border:2px solid var(--cyan)':'')+'">';
html+='<input type="checkbox" id="sc_'+i+'" '+(ch?"checked":"")+'><span class="sub-label" style="font-size:16px">'+s.l+')</span><span class="sub-text" style="flex:1">'+esc(s.t)+'</span>';
html+='<span style="font-size:11px;color:var(--gray-500);margin-right:8px">Файлов: '+scfs.length+'</span>';
html+='<button class="btn btn-sm" onclick="openEdit('+e.id+','+curMonth+','+curRegion+','+i+')" style="font-size:12px;'+(isActive?'background:var(--cyan);font-weight:700':'')+'">'+(isActive?'📂 Открыто':'📎 Файлы')+'</button>';
html+='</div>';
if(isActive){
html+='<div style="margin-left:20px;margin-bottom:16px;padding:16px;background:var(--cyan-50);border-radius:8px;border:2px solid var(--cyan)">';
html+='<div style="font-weight:700;font-size:14px;margin-bottom:4px">'+s.l+') '+esc(s.t)+'</div>';
html+='<div style="font-size:11px;color:var(--gray-500);margin-bottom:12px">Файлы подпункта: '+regions[curRegion]+' · '+M(curMonth)+'</div>';
html+='<div class="field"><label>Текст отчёта</label><textarea id="mr_s'+i+'" placeholder="Опишите ход исполнения..." style="min-height:60px">'+esc(scd.report||"")+'</textarea></div>';
if(scfs.length)html+='<div style="font-weight:600;font-size:13px;margin:8px 0">Файлы ('+scfs.length+'):</div>';
scfs.forEach(function(f,fi){html+='<div class="file-row"><span class="file-info"><span class="file-name" onclick="dlF('+e.id+','+curMonth+','+fi+','+curRegion+','+i+')">📄 '+esc(f.name)+'</span>'+(f.desc?'<span class="file-desc">'+esc(f.desc)+'</span>':'')+'</span><span class="file-meta">'+(f.size/1024).toFixed(0)+' КБ · '+f.date+'</span><button class="file-del" onclick="rmF('+e.id+','+curMonth+','+fi+','+curRegion+','+i+')">×</button></div>'});
html+='<div class="upload-row"><input type="text" id="fd_s'+i+'" placeholder="Описание файла"><input type="file" id="fi_s'+i+'" multiple style="max-width:180px"><button class="btn btn-sm" id="ub_s'+i+'" onclick="uploadFiles('+e.id+','+curMonth+','+curRegion+','+i+')">📤 Загрузить</button></div>';
html+='</div>';
}
});
html+='</div>';
}
html+='<div class="ai-block"><h4>🤖 Вывод ИИ-агента</h4>'+esc(e.ai)+'</div>';
html+='<div style="font-weight:600;margin:8px 0 4px">История:</div><div>';e.h.forEach(function(h){html+='<div class="history-item"><div class="dot"></div>'+esc(h)+'</div>'});html+='</div>';
html+='<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=html;
document.getElementById("editModalOverlay").classList.add("open");
}
function saveEdit(id, mk){
mk=months[mk]; // convert index to month key
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();
// Save main event report
var 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+": "+statusMap[e.s]+", "+e.p+"%"+(cmt?" — "+cmt:""));
if(e.s==="done"&&e.done==="\u2014")e.done=now;
saveEvents();addLog("изменил статус",id,statusMap[e.s]+" "+e.p+"%");closeEM();renderAll();
}
function closeEM(){document.getElementById("editModalOverlay").classList.remove("open")}
// File storage: sf_<eventId>_r<region> for main, sf_<eventId>_s<sub>_r<region> for sub-item
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)+' КБ'}
function clearAllFiles(){if(!confirm('Удалить ВСЕ загруженные файлы и отчёты?'))return;var keys=[];for(var i=0;i<localStorage.length;i++){var k=localStorage.key(i);if(k.indexOf('sf_')===0||k.indexOf('ss_')===0)keys.push(k)}keys.forEach(function(k){localStorage.removeItem(k)});alert('Очищено');renderDashboard()}
function exportAll(){
var data={events:events,user:curUser,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 blob=new Blob([JSON.stringify(data)],{type:"application/json"}),a=document.createElement("a");
a.href=URL.createObjectURL(blob);a.download="backup_pb_"+new Date().toISOString().slice(0,10)+".json";a.click();
alert("✅ Данные сохранены. Файл backup_pb_....json скачан. Его можно загрузить обратно кнопкой «Загрузить данные».")
}
function importAll(input){
if(!input.files.length)return;
var reader=new FileReader();
reader.onload=function(ev){
try{
var data=JSON.parse(ev.target.result);
if(!data.events||!data.files){alert("❌ Неверный формат файла");return}
if(!confirm("Загрузить данные из бекапа? Текущие данные будут заменены."))return;
// Save events
events=data.events;saveEvents();
// Save files and sub-checks
for(var k in data.files){if(data.files.hasOwnProperty(k))localStorage.setItem(k,data.files[k])}
for(var k in data.subChecks){if(data.subChecks.hasOwnProperty(k))localStorage.setItem(k,data.subChecks[k])}
alert("✅ Данные восстановлены. Обновите страницу.");
location.reload();
}catch(e){alert("❌ Ошибка чтения файла")}
};
reader.readAsText(input.files[0]);
}
function countFiles(id){
var total=0,main=getMD(id);
for(var k in main){if(main.hasOwnProperty(k))total+=(main[k].files||[]).length}
var e=null;for(var i=0;i<events.length;i++){if(events[i].id===id){e=events[i];break}}
if(e&&e.sub)for(var j=0;j<e.sub.length;j++){var sd=getMD(id,j);for(var sk in sd){if(sd.hasOwnProperty(sk))total+=(sd[sk].files||[]).length}}
return total;
}
function uploadFiles(eid,mk,ri,si){
mk=months[mk];var prefix=si>=0?'_s'+si:'',fi=document.getElementById('fi'+prefix);
if(!fi||!fi.files.length)return;
var desc=(document.getElementById('fd'+prefix)||{}).value;desc=(desc||'').trim();
var btn=document.getElementById('ub'+prefix);btn.textContent="Загружается...";btn.disabled=true;
var MAX=3072*1024,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){
var bak=JSON.parse(JSON.stringify(ad));bak[mk].files=bak[mk].files.slice(0,-(pr-sk)||0);try{setMD(eid,bak,ri,si)}catch(e2){}
alert("⚠️ Хранилище заполнено. Очистите файлы на дашборде.");
}
addLog("загрузил файлы",eid,(pr-sk)+" файл(ов) за "+M(mk)+" ("+regions[ri]+")");
if(sk)alert(sk+" файл(ов) > 3 МБ пропущены");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)}
function renderJournal(){
var log=getLog().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><th>Мероприятие</th><th>Детали</th></tr>';
log.forEach(function(l){var e=null;if(l.eid)for(var i=0;i<events.length;i++){if(events[i].id===l.eid){e=events[i];break}}
h+='<tr><td style="font-size:11px">'+new Date(l.ts).toLocaleString()+'</td><td>'+esc(l.user)+'</td><td>'+(l.role==="curator"?"Куратор":l.role==="admin"?"Админ":l.role==="region"?"Регион":"Филиал")+'</td><td>'+esc(l.action)+'</td><td>'+(e?e.id+". "+esc(e.t.slice(0,40))+"...":"—")+'</td><td style="font-size:12px">'+esc(l.detail)+'</td></tr>';
});
h+='</table>'}
h+='</div>';
document.getElementById("tab-journal").innerHTML=h;
}
document.querySelectorAll(".tab-btn").forEach(function(b){b.addEventListener("click",function(){switchTab(this.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")});
var su=localStorage.getItem("samruk_u");if(su){try{curUser=JSON.parse(su);showApp()}catch(e){}}
</script>
</body>
</html>