samruk-ai-agent/index.html

323 lines
61 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);cursor:pointer;user-select:none;white-space:nowrap}th:hover{color:var(--ink)}td{border-bottom:1px solid var(--gray-200)}
tr.deadline-warn td{background:#FFFDF5}tr.deadline-late td{background:#FFF5F5}
.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 .hint{font-size:12px;color:var(--gray-500);margin-top:-12px;margin-bottom:16px;text-align:left}
#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 .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 24px;height:56px;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:14px}.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:52px;right:24px;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;max-width:1400px}
.stats-row{display:grid;grid-template-columns:repeat(auto-fit,minmax(190px,1fr));gap:14px;margin-bottom:20px}
.stat-card{background:var(--white);border-radius:12px;padding:18px 22px;border:1px solid var(--gray-200)}.stat-card .lbl{font-size:12px;color:var(--gray-500)}.stat-card .num{font-size:26px;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:10px;margin-bottom:14px;flex-wrap:wrap;align-items:center}.filters select,.filters input{padding:9px 12px;border:1px solid var(--gray-200);border-radius:8px;font-size:13px;background:var(--white)}.filters input{min-width:220px}.filters select{min-width:140px}
.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:28px}
.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:-4px -4px 0 0}
.modal .field{margin-bottom:12px}.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:9px 12px;border:1px solid var(--gray-200);border-radius:8px;font-size:14px}.modal .field textarea{min-height:64px;resize:vertical}
.modal .meta-row{display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-bottom:10px}.modal .meta-row .fld{font-size:12px;color:var(--gray-500)}.modal .meta-row .fld strong{display:block;font-size:13px;color:var(--ink);margin-top:2px}
.ai-block{background:var(--cyan-50);border-radius:8px;padding:12px;margin:14px 0;font-size:13px}.ai-block h4{font-size:14px;margin-bottom:4px}
.history-item{display:flex;gap:8px;font-size:12px;padding:2px 0;color:var(--gray-500);align-items:baseline}.history-item .dot{width:6px;height:6px;border-radius:50%;background:var(--cyan);flex-shrink:0;margin-top:5px}
.sub-item{display:flex;align-items:center;gap:10px;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:16px;height:16px;cursor:pointer;accent-color:var(--cyan)}
.month-tabs{display:flex;gap:5px;flex-wrap:wrap;margin-bottom:12px}.month-tab{padding:5px 12px;border:1px solid var(--gray-200);border-radius:100px;font-size:12px;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:8px;padding:8px 12px;background:var(--gray-100);border-radius:8px;margin-bottom:4px;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-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:15px;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:6px}.upload-row input[type=text]{flex:1;min-width:140px;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:200px}
.sort-arrow{font-size:10px;margin-left:2px;color:var(--cyan)}
@media(max-width:768px){.main{padding:12px}.topbar{padding:0 12px}.modal{padding:20px}.stats-row{grid-template-columns:1fr 1fr}.notif-drop{right:8px;width:calc(100vw-16px)}.filters input{min-width:140px}.filters select{min-width:100px}}
</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 / serikov@telecom.kz / ahmetov@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:"Просрочено"};
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"};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={},cache={},sortCol=null,sortDir=1;
function getMy(){return events||[]}
// ===== EVENTS DATA (embedded, no async load) =====
function loadEvents(){
var s=localStorage.getItem("samruk_ev");
if(s){try{events=JSON.parse(s);return}catch(e){}}
events=getDefaultEvents();saveEv();
}
function getDefaultEvents(){return [
{id:1,sec:0,b:6,s:"warn",p:45,due:"31.12.2026",done:"\u2014",dname:"Протоколы обучения / Электронная ведомость",r:"Генеральный директор КУ",t:"Продолжить проведение обучения и повышения квалификации руководителей и работников компании в соответствии с лучшими международными практиками, ориентированными на специфику условий труда, работы повышенной опасности и требований промышленной безопасности, а также развитие культуры безопасности, включая обучение производственного персонала по курсу \u00abКультура безопасного труда\u00bb, в том числе с применением VR, AR \u2013 технологий и цифровых симуляторов аварийных ситуаций по различным направлениям производственной безопасности (с правом выдачи сертификатов).",ai:"Обучение ведётся по графику. Охвачено 45% персонала. VR-тренажёры развёрнуты в 3 филиалах.",h:["15.01 — Создано","01.03 — Запущено обучение","15.05 — VR-симуляторы установлены"]},
{id:2,sec:0,b:0,s:"done",p:100,due:"31.03.2026",done:"28.03.2026",dname:"Отчёт / Утверждённый ВНД",r:"Директор ДПБ, Генеральный директор ДИТ",t:"Провести анализ, в том числе с использованием аналитических платформ, и осуществить пересмотр внутренних нормативных документов филиалов/ДАО Общества в соответствии со Стратегией развития производственной безопасности на 2024-2028 гг., включая установку значений ключевых показателей производственной безопасности.",ai:"Анализ завершён в срок. Ключевые показатели установлены.",h:["10.01 — Создано","28.03 — Утверждён"]},
{id:3,sec:0,b:0,s:"warn",p:50,due:"31.12.2026",done:"\u2014",dname:"Протоколы совещаний",r:"Главный административный директор, Директор ДПБ",t:"Организовывать тематические совещания по вопросам производственной безопасности.",ai:"Проведено 2 квартальных совещания.",h:["10.01 — Создано","15.02 — Q1","15.05 — Q2"],sub:[{l:"a",t:"Руководство Общества с филиалами/ДАО, не менее 1 раза в квартал"},{l:"b",t:"Руководство филиалов/ДАО со структурными подразделениями, не менее 1 раза в месяц"},{l:"c",t:"Руководство с подрядными организациями, не менее 1 раза в квартал"}]},
{id:4,sec:0,b:6,s:"warn",p:55,due:"31.12.2026",done:"\u2014",dname:"Отчёты / Тесты",r:"Генеральные директора филиалов и ДАО",t:"Продолжить практику проверки знаний в формате тестирования после проведения инструктажей по охране труда в филиалах/ДАО Общества.",ai:"Тестирование в 6 филиалах. Средний результат \u2014 82%.",h:["01.02 — Создано","01.06 — Отчёт"]},
{id:5,sec:0,b:0,s:"done",p:100,due:"31.03.2026",done:"25.03.2026",dname:"Информация о поощрении",r:"Директор ДПБ",t:"Рассмотреть возможность нематериального поощрения филиалов и ДАО Общества.",ai:"Положение утверждено.",h:["15.01 — Проект","25.03 — Утверждено"]},
{id:6,sec:0,b:6,s:"warn",p:60,due:"30.06.2026",done:"\u2014",dname:"ВНД по тренерам",r:"Генеральный директор КУ, Управляющий директор по персоналу",t:"Разработать и утвердить ВНД, регламентирующий процедуру работы внутренних тренеров по производственной безопасности.",ai:"Проект на финальном согласовании.",h:["01.03 — Создано","01.06 — Проект"]},
{id:7,sec:0,b:1,s:"warn",p:40,due:"31.12.2026",done:"\u2014",dname:"Материалы обмена опытом",r:"Директор ДПБ",t:"Проводить мероприятия по обмену опытом в области производственной безопасности.",ai:"1 выезд на KEGOC.",h:["15.02 — Создано","01.04 — Выезд"],sub:[{l:"a",t:"Обмен опытом на площадке Комитета HSE"},{l:"b",t:"Организация обмена опытом с иностранными компаниями"}]},
{id:8,sec:0,b:4,s:"warn",p:15,due:"30.09.2026",done:"\u2014",dname:"Акт / Well-being / Скрининг",r:"Директор ДПБ, Управляющий директор по персоналу",t:"Провести анализ эффективности мероприятий по охране здоровья.",ai:"Медосмотры \u2014 Q3.",h:["01.04 — Создано"],sub:[{l:"a",t:"100% прохождение медосмотров"},{l:"b",t:"Неделя благополучия (Well-being Week)"},{l:"c",t:"Медицинский скрининг работников"},{l:"d",t:"Алгоритм учёта микротравм"}]},
{id:9,sec:0,b:6,s:"warn",p:20,due:"31.12.2026",done:"\u2014",dname:"Результаты конкурсов",r:"Директор ДПБ",t:"Рассмотреть возможность участия в международных/национальных конкурсах профессионального мастерства по ПБ.",ai:"Определены 2 конкурса.",h:["01.05 — Создано"]},
{id:10,sec:1,b:1,s:"warn",p:55,due:"31.12.2026",done:"\u2014",dname:"Аналитическая справка",r:"Генеральный директор ОДС, СФ, ДУП, ДИТ",t:"Проводить работы по техническому перевооружению изношенного оборудования, зданий и сооружений.",ai:"Заменено 55%.",h:["01.01 — Переходящее","01.06 — 55%"]},
{id:11,sec:1,b:1,s:"warn",p:70,due:"30.06.2026",done:"\u2014",dname:"Процедура / Фотоотчёт",r:"Директор ДПБ",t:"Пересмотреть и актуализировать внутренний порядок нарядно-допускной системы с внедрением сертификатов безопасности.",ai:"Пилот запущен.",h:["01.02 — Создано","15.05 — Пилот"]},
{id:12,sec:1,b:8,s:"warn",p:8,due:"30.09.2026",done:"\u2014",dname:"Справка / Фотоотчёт",r:"Генеральные директора филиалов и ДАО",t:"Рассмотреть возможность внедрения системы цифровой маркировки опасных техустройств (QR-коды).",ai:"ТЭО в разработке.",h:["01.05 — Создано"]},
{id:13,sec:1,b:0,s:"warn",p:50,due:"31.12.2026",done:"\u2014",dname:"Акты / График",r:"Директор ДПБ",t:"Филиалам/ДАО не реже 1 раза в квартал проводить проверку по проверочным листам БиОТ, пром- и пожарной безопасности.",ai:"Q1 завершены. Q2 — по графику.",h:["01.01 — Создано","01.06 — Q2"]},
{id:14,sec:1,b:0,s:"warn",p:40,due:"31.12.2026",done:"\u2014",dname:"Письмо",r:"Директор ДПБ",t:"Продолжить практику участия в перекрёстных аудитах ПК согласно графику.",ai:"Назначены 4 аудитора.",h:["15.01 — Аудиторы"]},
{id:15,sec:1,b:0,s:"warn",p:48,due:"31.12.2026",done:"\u2014",dname:"Справка / Журнал",r:"Директор ДПБ",t:"Усилить контроль за проактивными инструментами: поведенческие аудиты, Near Miss, право приостановки.",ai:"147 Near Miss.",h:["01.01 — Создано"]},
{id:16,sec:1,b:1,s:"done",p:85,due:"31.12.2026",done:"\u2014",dname:"План-график / Акты",r:"Генеральные директора филиалов и ДАО",t:"Провести работу по повышению эффективности управления подрядными организациями.",ai:"Q1 — 12 подрядчиков.",h:["15.01 — План"],sub:[{l:"a",t:"Аудит подрядчиков по чек-листу Фонда"},{l:"b",t:"Стартовые совещания с подрядчиками"}]},
{id:17,sec:1,b:0,s:"warn",p:35,due:"31.12.2026",done:"\u2014",dname:"Отчёты / Фотоотчёт",r:"Главный административный директор, Директор ДПБ",t:"Обеспечить контроль состояния ПБ на объектах (CEO-1, первые руководители).",ai:"CEO-1: 2 филиала.",h:["01.02 — Создано"],sub:[{l:"a",t:"CEO-1 лично проверять филиал 1 раз в квартал"},{l:"b",t:"Первые руководители — внутренний контроль"}]},
{id:18,sec:1,b:1,s:"done",p:90,due:"31.12.2026",done:"\u2014",dname:"Ежемесячный отчёт",r:"Генеральные директора филиалов, Директор ДПБ",t:"Обеспечить контроль транспортной безопасности (ежемесячный мониторинг нарушений).",ai:"34 нарушения. Тренд — снижение.",h:["01.01 — Создано"]},
{id:19,sec:2,b:1,s:"warn",p:30,due:"31.12.2026",done:"\u2014",dname:"Акты тренировок",r:"Управляющий директор по безопасности",t:"Обеспечить проведение учебных тревог и тренировок (аварии, пожары, первая помощь).",ai:"1 учение. Пожарные: 1 из 2.",h:["01.02 — Создано"],sub:[{l:"a",t:"Одна учебная тревога по ликвидации аварии"},{l:"b",t:"Две тренировки по тушению пожара"},{l:"c",t:"Одно занятие по первой помощи"}]},
{id:20,sec:2,b:0,s:"warn",p:65,due:"30.06.2026",done:"\u2014",dname:"Приказ CMS / Акты штабов",r:"Управляющий директор по безопасности",t:"Усилить работу по реагированию на ЧС: Crisis Management System, обучение, заседания штабов.",ai:"CMS внедрена.",h:["01.03 — Создано"],sub:[{l:"a",t:"Внедрить Crisis Management System"},{l:"b",t:"Обучение по действиям в ЧС"},{l:"c",t:"Два заседания штабов"}]},
{id:21,sec:3,b:0,s:"done",p:100,due:"31.12.2026",done:"15.02.2026",dname:"Публикация",r:"Директор ДПБ, Пресс-секретарь",t:"Обеспечить выпуск обращения Председателя Правления о важности соблюдения требований ПБ.",ai:"Опубликовано.",h:["15.02 — Опубликовано"]},
{id:22,sec:3,b:0,s:"warn",p:15,due:"31.12.2026",done:"\u2014",dname:"Протоколы",r:"Директор ДПБ, Департамент коммуникаций",t:"Проведение стратсессий/Форумов для руководителей и семинаров для подрядчиков.",ai:"Форум — октябрь.",h:["01.05 — Создано"],sub:[{l:"a",t:"Стратсессии/Форумы для первых руководителей"},{l:"b",t:"Семинары для подрядных организаций"}]},
{id:23,sec:3,b:6,s:"warn",p:10,due:"30.09.2026",done:"\u2014",dname:"Протокол Олимпиады",r:"Директор ДПБ",t:"Проведение Олимпиады по ПБ среди специалистов Общества и подрядных организаций.",ai:"Положение на согласовании.",h:["01.05 — Создано"]},
{id:24,sec:3,b:0,s:"done",p:92,due:"31.12.2026",done:"\u2014",dname:"Бюллетени",r:"Директор ДПБ",t:"Обеспечить ознакомление работников с обстоятельствами НС через информационные бюллетени.",ai:"3 бюллетеня, 92%.",h:["01.01 — Создано"]},
{id:25,sec:3,b:6,s:"warn",p:40,due:"31.12.2026",done:"\u2014",dname:"Публикации / Материалы",r:"Управляющий директор по персоналу, Директор ДПБ",t:"Проведение молодежных проектных инициатив Центра молодых работников по ПБ.",ai:"2 истории в SK News.",h:["01.02 — Создано"],sub:[{l:"a",t:"Публикация историй в SK News"},{l:"b",t:"Посещение мест НС 2022-2025"},{l:"c",t:"Молодые специалисты в аудитах"},{l:"d",t:"Онлайн-семинары/эфиры"}]},
{id:26,sec:3,b:2,s:"warn",p:50,due:"31.12.2026",done:"\u2014",dname:"Видео / Постеры",r:"Директор ДПБ, Департамент коммуникаций",t:"Усилить наглядную агитацию: видеоролики, подкасты, постеры, брошюры.",ai:"2 видеоролика снято.",h:["01.02 — Создано"],sub:[{l:"a",t:"Видеоролики с участием работников"},{l:"b",t:"Серия \u00abБезопасность будущего\u00bb"},{l:"c",t:"Подкаст с трудовыми династиями"},{l:"d",t:"Постеры, брошюры, рассылки"}]},
{id:27,sec:3,b:3,s:"warn",p:30,due:"31.12.2026",done:"\u2014",dname:"Фотофиксация",r:"Директор ДПБ",t:"Организация встреч коллектива с получившими производственные травмы работниками.",ai:"1 встреча проведена.",h:["01.03 — Создано"]},
{id:28,sec:3,b:0,s:"warn",p:25,due:"31.12.2026",done:"\u2014",dname:"Письма / Пресс-релизы",r:"Директор ДПБ, Департамент коммуникаций",t:"Проведение мероприятий по пропаганде безопасности через семейные ценности.",ai:"5 писем семьям.",h:["01.04 — Создано"],sub:[{l:"a",t:"Письма членам семьи отличившихся"},{l:"b",t:"Семейные дни охраны труда"},{l:"c",t:"Конкурс рисунков \u00abСпецодежда будущего!\u00bb"}]},
{id:29,sec:3,b:6,s:"late",p:40,due:"30.06.2026",done:"\u2014",dname:"Сборник практик",r:"Директор ДПБ",t:"Разработать корпоративный сборник лучших практик по производственной безопасности.",ai:"Риск срыва Q2.",h:["01.03 — Создано","01.06 — Эскалация"]},
{id:30,sec:3,b:7,s:"warn",p:60,due:"31.12.2026",done:"\u2014",dname:"Предложения / План",r:"Директор ДПБ",t:"Сбор предложений по цифровым решениям для системы управления ПБ.",ai:"18 предложений.",h:["01.01 — Создано"]},
{id:31,sec:3,b:0,s:"warn",p:75,due:"30.06.2026",done:"\u2014",dname:"Видеообзор",r:"Директор ДПБ",t:"Разработка видеообзора кейсов происшествий в ПК.",ai:"Монтаж 75%.",h:["01.03 — Создано"]},
{id:32,sec:4,b:8,s:"warn",p:70,due:"30.06.2026",done:"\u2014",dname:"Справка / Скриншоты",r:"Директор ДПБ",t:"Применение чат-бота ИИ-ассистента по ПБ для доступа к НПА и ВНД.",ai:"Чат-бот тестируется.",h:["01.02 — Создано","01.06 — Тест"]},
{id:33,sec:4,b:8,s:"warn",p:15,due:"31.12.2026",done:"\u2014",dname:"Справка / Скриншоты",r:"Директор ДПБ",t:"Применение системы анализа и предупреждения НС, платформы идентификации рисков.",ai:"ТЗ согласовывается.",h:["01.04 — Создано"]},
{id:34,sec:4,b:8,s:"warn",p:10,due:"31.12.2026",done:"\u2014",dname:"Справка / Скриншоты",r:"Директор ДПБ",t:"Запуск электронного HSE паспорта с интеграцией в корпоративную систему.",ai:"Концепция утверждена.",h:["01.05 — Создано"]},
{id:35,sec:4,b:5,s:"warn",p:8,due:"31.12.2026",done:"\u2014",dname:"Справка / Скриншоты",r:"Директор ДПБ",t:"Внедрение системы электронных нарядов-допусков на работы повышенной опасности.",ai:"Предпроект.",h:["01.05 — Создано"]}
];}
function saveEv(){localStorage.setItem("samruk_ev",JSON.stringify(events||[]))}
// ===== 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,45)+"...",t:e.due});if(e.s==="warn"&&e.p<30)n.push({m:"🟡 "+e.t.slice(0,45)+"...",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 with caching =====
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(!cache[n]){if(n==="dashboard")renderDashboard();else if(n==="myevents")renderMyEvents();else if(n==="analytics")renderAnalytics();else if(n==="journal")renderJournal();cache[n]=true}}
function invalidateCache(){cache={}}
// ===== DEADLINE CLASS =====
function deadlineClass(e){if(e.s==="late")return"deadline-late";if(e.s==="done")return"";var p=e.due.split(".");if(p.length===3){var due=new Date(parseInt(p[2]),parseInt(p[1])-1,parseInt(p[0]));var days=(due-new Date())/86400000;if(days<=30)return"deadline-warn"}return""}
// ===== DASHBOARD =====
function renderDashboard(){var my=getMy(),done=0,late=0,warn=0;my.forEach(function(e){if(e.s==="done")done++;else if(e.s==="late")late++;else warn++});var dp=my.length?Math.round(done/my.length*100):0;
var h='<div class="stats-row"><div class="stat-card"><div class="lbl">Мероприятий</div><div class="num">'+my.length+'</div></div><div class="stat-card green"><div class="lbl">Исполнено</div><div class="num">'+done+'</div></div><div class="stat-card amber"><div class="lbl">На контроле</div><div class="num">'+warn+'</div></div><div class="stat-card red"><div class="lbl">Просрочено</div><div class="num">'+late+'</div></div></div>';
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>';
h+='<div class="panel"><h3>Динамика</h3><div style="height:120px;background:var(--gray-100);border-radius:8px;display:flex;align-items:flex-end;gap:8px;padding:16px 16px 8px"><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></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>';
h+='<div class="panel"><h3>📥 Сводный отчёт</h3><div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin-bottom:10px"><select id="rptFrom">'+months.map(function(m,i){return'<option value="'+i+'">'+M(i)+'</option>'}).join("")+'</select><span>—</span><select id="rptTo">'+months.map(function(m,i){return'<option value="'+i+'"'+(i===11?" selected":"")+'>'+M(i)+'</option>'}).join("")+'</select><button class="btn btn-sm" onclick="downloadCSV()">CSV</button><button class="btn btn-sm btn-outline" onclick="downloadHTML()">HTML</button></div><div style="font-size:12px;color:var(--gray-500);margin-bottom:8px">Хранилище: '+fmtStorage()+'</div><button class="btn btn-sm" style="background:var(--green);color:#fff;margin-right:4px" onclick="exportAll()">💾 Сохранить всё</button><button class="btn btn-sm btn-outline" style="margin-right:4px" onclick="document.getElementById(\'impF\').click()">📥 Загрузить</button><input type="file" id="impF" accept=".json" style="display:none" onchange="importAll(this)"><button class="btn btn-sm" style="background:var(--red);color:#fff" onclick="clearAll()">🗑 Очистить</button></div>';
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 =====
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();addLog("скачал CSV")}
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}.month{background:#f5f5f5;padding:8px 12px;border-radius:4px;margin:6px 0}ul{list-style:"📄 ";margin-left:20px}</style></head><body><h1>Сводный отчёт ПБ</h1><p>Период: '+M(from)+' \u2014 '+M(to)+'</p>';my.forEach(function(e){var cl={done:"g",warn:"a",late:"r"}[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><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();addLog("скачал HTML")}catch(e){alert("Отчёт слишком большой. Попробуйте меньший период.")}}
// ===== MY EVENTS with search, filter, sort, colors =====
function toggleExpand(eid){expandedEvents[eid]=!expandedEvents[eid];renderMyEvents()}
function renderMyEvents(){invalidateCache();
var my=getMy(),sf=document.getElementById("mySF");sf=sf?sf.value:"";
var search=document.getElementById("mySearch");search=search?search.value.toLowerCase():"";
var bf=document.getElementById("myBF");bf=bf?bf.value:"";
var list=my;
if(sf)list=list.filter(function(e){return e.s===sf});
if(search)list=list.filter(function(e){return e.t.toLowerCase().indexOf(search)>=0||branches[e.b].toLowerCase().indexOf(search)>=0});
if(bf)list=list.filter(function(e){return e.b===parseInt(bf)});
// Sort
if(sortCol){list.sort(function(a,b){var va=a[sortCol],vb=b[sortCol];if(typeof va==="string")va=va.toLowerCase(),vb=vb.toLowerCase();return va>vb?sortDir:va<vb?-sortDir:0})}
var h='<div class="panel" style="border-radius:0 0 12px 12px"><div class="filters"><input id="mySearch" placeholder="Поиск по названию или филиалу..." oninput="renderMyEvents()"><select id="mySF" onchange="renderMyEvents()"><option value="">Все статусы</option><option value="done">Исполнено</option><option value="warn">На контроле</option><option value="late">Просрочено</option></select><select id="myBF" onchange="renderMyEvents()"><option value="">Все филиалы</option>'+branches.map(function(b,i){return'<option value="'+i+'">'+b+'</option>'}).join("")+'</select><span style="font-size:12px;color:var(--gray-500);margin-left:auto">Найдено: '+list.length+'</span></div>';
h+='<div style="overflow-x:auto"><table><tr>';
var cols=[{k:null,l:"№"},{k:null,l:"Мероприятие"},{k:"b",l:"Филиал"},{k:"sec",l:"Раздел"},{k:"due",l:"Срок"},{k:"p",l:"Прогресс"},{k:"s",l:"Статус"},{k:null,l:""}];
cols.forEach(function(c){h+='<th onclick="sortCol=\''+c.k+'\';sortDir=sortCol===c.k?-sortDir:1;renderMyEvents()">'+c.l+(sortCol===c.k?(sortDir===1?' <span class="sort-arrow">▲</span>':' <span class="sort-arrow">▼</span>'):'')+'</th>'});
h+='</tr>';
list.forEach(function(e){
var hs=e.sub&&e.sub.length,sc2=getSC(e.id),sd2=hs?sc2.length:0,st=hs?e.sub.length:0,dcls=deadlineClass(e);
h+='<tr class="'+dcls+'"><td>'+e.id+'</td><td style="font-size:12px;max-width:300px">';
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:240px;display:inline-block;vertical-align:middle">'+esc(e.t)+'</span>';
if(hs)h+=' <span style="font-size:11px;color:var(--gray-500)">('+sd2+'/'+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=sc2.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></div>';
document.getElementById("tab-myevents").innerHTML=h;
cache.myevents=true;
}
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";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||[],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>';
// Aggregate quantities
var totalQty=0,regQty={};regions.forEach(function(r,ri){regQty[ri]=0});
all.forEach(function(e){regions.forEach(function(ri){var d=getMD(e.id,ri,-1);for(var k in d){if(d.hasOwnProperty(k)&&d[k])totalQty+=d[k].qty||0;regQty[ri]+=d[k].qty||0}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]){totalQty+=sd[sk].qty||0;regQty[ri]+=sd[sk].qty||0}}})})});
h+='<div class="panel"><h3>📊 Количественные показатели</h3><div class="stats-row"><div class="stat-card blue"><div class="lbl">Всего единиц</div><div class="num">'+totalQty+'</div></div>';
regions.forEach(function(r,ri){if(regQty[ri])h+='<div class="stat-card"><div class="lbl">'+r+'</div><div class="num">'+regQty[ri]+'</div></div>'});
h+='</div></div>';
// Branch ranking
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:240px;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;cache.analytics=true}
// ===== 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></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)+(l.detail?' — '+esc(l.detail):'')+'</td></tr>'});h+='</table>'}h+='</div>';document.getElementById("tab-journal").innerHTML=h;cache.journal=true}
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 =====
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("Очищено");invalidateCache();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),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><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="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>';
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:10px"><b style="font-size:12px;margin-right:6px">Регион:</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>';
h+='<div style="border-top:1px solid var(--gray-200);padding-top:14px;margin-top:10px"><b>📎 '+regions[curRegion]+'</b>'+mh+rh;
h+='<div class="field"><label>Текст за '+M(curMonth)+'</label><textarea id="mr" style="min-height:76px">'+esc(cd.report||"")+'</textarea></div>';
h+='<div class="field"><label>Количество (совещаний / работников / объектов)</label><input type="number" id="mq" min="0" value="'+(cd.qty||0)+'" style="width:140px"></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:4px">Формы: '+esc(e.dname)+'</p></div>';
if(e.sub&&e.sub.length){h+='<div style="border-top:2px solid var(--cyan);padding-top:14px;margin-top:14px"><b>📋 Подпункты</b><p style="font-size:11px;color:var(--gray-500);margin:4px 0 10px">Нажмите 📎 для управления файлами</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:10px 14px;margin-bottom:6px;'+(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:6px">Файлов: '+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 14px 20px;padding:14px;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:56px">'+esc(scd.report||"")+'</textarea></div>';h+='<div class="field"><label>Количество</label><input type="number" id="mq_s'+i+'" min="0" value="'+(scd.qty||0)+'" style="width:120px"></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" id="ub_s'+i+'" 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:18px;display:flex;gap:10px"><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"),mq=document.getElementById("mq");if(mr){var ad=getMD(id,curRegion,-1);if(!ad[mk])ad[mk]={report:"",files:[]};ad[mk].report=mr.value;if(mq)ad[mk].qty=parseInt(mq.value)||0;setMD(id,ad,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),sq=document.getElementById("mq_s"+i);if(sr){var sd=getMD(id,curRegion,i);if(!sd[mk])sd[mk]={report:"",files:[]};sd[mk].report=sr.value;if(sq)sd[mk].qty=parseInt(sq.value)||0;setMD(id,sd,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==="\u2014")e.done=now;saveEv();addLog("изменил",id,sm[e.s]);closeEM();invalidateCache();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);if(btn){btn.textContent="...";btn.disabled=true}var ad=getMD(eid,ri,si);if(!ad[mk])ad[mk]={report:"",files:[]};var arr=ad[mk].files,pr=0,sk=0,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>