samruk-ai-agent/index.html

576 lines
63 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;letter-spacing:.5px;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(200px,1fr));gap:16px;margin-bottom:24px}
.stat-card{background:var(--white);border-radius:12px;padding:20px 24px;border:1px solid var(--gray-200)}
.stat-card .lbl{font-size:13px;color:var(--gray-500)}.stat-card .num{font-size:28px;font-weight:800;line-height:1.2}.stat-card .sub{font-size:12px;color:var(--gray-500)}
.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}
.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 */
.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 SELECTOR */
.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);transition:.15s}
.month-tab:hover{border-color:var(--cyan)}
.month-tab.active{background:var(--cyan);color:var(--ink);border-color:var(--cyan)}
/* FILE ROW */
.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}
.upload-row input[type=text]{flex:1;min-width:150px;padding:8px 12px;border:1px solid var(--gray-200);border-radius:6px;font-size:13px}
.upload-row input[type=file]{font-size:12px;max-width:220px}
@media(max-width:768px){
.main{padding:16px}.topbar{padding:0 16px}
.modal .meta-row{grid-template-columns:1fr}
.stats-row{grid-template-columns:1fr 1fr}
.notif-drop{right:8px;width:calc(100vw - 16px)}
.modal{padding:20px}
}
</style>
</head>
<body>
<div id="loginScreen">
<form class="login-box" onsubmit="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">Например: ahmetov@telecom.kz, serikov@telecom.kz, admin@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="myevents">Мои мероприятия</button>
<button class="tab-btn" data-tab="allevents">Весь реестр</button>
<button class="tab-btn" data-tab="analytics">Аналитика</button>
</div>
<div class="tab-content active" id="tab-myevents">
<div class="panel" style="border-radius:0 0 12px 12px">
<div class="stats-row" id="myStats"></div>
<div class="filters">
<select id="myStatusFilter" onchange="renderMyEvents()"><option value="">Все статусы</option><option value="warn">На контроле</option><option value="late">Просрочено</option><option value="done">Исполнено</option><option value="wait">Не начато</option></select>
<span class="user-info" id="myCount"></span>
</div>
<table id="myEventsTable"></table>
</div>
</div>
<div class="tab-content" id="tab-allevents">
<div class="panel" style="border-radius:0 0 12px 12px">
<div class="filters">
<input id="allSearch" placeholder="Поиск..." oninput="renderAllEvents()">
<select id="allStatusFilter" onchange="renderAllEvents()"><option value="">Все статусы</option><option value="warn">На контроле</option><option value="late">Просрочено</option><option value="done">Исполнено</option><option value="wait">Не начато</option></select>
<select id="allSecFilter" onchange="renderAllEvents()"><option value="">Все разделы</option><option value="0">I. Люди</option><option value="1">II. Оборудование</option><option value="2">III. Аварии и ЧС</option><option value="3">IV. Информ. работа</option><option value="4">V. ИИ и цифровизация</option></select>
</div>
<table id="allEventsTable"></table>
</div>
</div>
<div class="tab-content" id="tab-analytics">
<div class="stats-row" id="globalStats"></div>
<div class="panel" id="analyticsContent"></div>
</div>
</div>
</div>
<div class="modal-overlay" id="editModalOverlay"><div class="modal" id="editModalContent"></div></div>
<script>
const sections=['I. Люди. Повышение культуры безопасности','II. Безопасность при эксплуатации оборудования','III. Предупреждение и готовность к ликвидации аварий и ЧС','IV. Информационно-разъяснительная работа','V. Внедрение ИИ и цифровизации']
const branches=['Дирекция производственной безопасности','Объединение «Дивизион «Сеть»»','Дивизион по корпоративному бизнесу','Дивизион по розничному бизнесу','Сервисная фабрика','Дирекция «Телеком Комплект»','Корпоративный университет','Дирекция управления проектами','Дивизион цифрового бизнеса']
const statusMap={done:'Исполнено',warn:'На контроле',late:'Просрочено',wait:'Не начато'}
const 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']
const monthNames=['Янв','Фев','Мар','Апр','Май','Июн','Июл','Авг','Сен','Окт','Ноя','Дек']
function M(idx){ const [y,m]=months[idx].split('-'); return monthNames[parseInt(m)-1]+' '+y }
const baseEvents = [
{id:1,sec:0,b:6,s:'warn',p:45,due:'31.12.2026',done:'—',dname:'Протоколы обучения / Электронная ведомость',
r:'Генеральный директор КУ, Генеральные директора филиалов и ДАО',
t:'Продолжить проведение обучения и повышения квалификации руководителей и работников компании в соответствии с лучшими международными практиками, ориентированными на специфику условий труда, работы повышенной опасности и требований промышленной безопасности, а также развитие культуры безопасности, включая обучение производственного персонала по курсу «Культура безопасного труда», в том числе с применением VR, AR технологий и цифровых симуляторов аварийных ситуаций по различным направлениям производственной безопасности (с правом выдачи сертификатов).',
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:'Провести анализ, в том числе с использованием аналитических платформ (Microsoft Teams, Power BI, Tableau, Qlik и др.), и в случае необходимости, осуществить пересмотр внутренних нормативных документов филиалов/ДАО Общества в соответствии со «Стратегией развития производственной безопасности АО «Самрук-Қазына» на 2024-2028 гг.», включая установку значений ключевых показателей производственной безопасности.',
ai:'Анализ завершён в срок. ВНД пересмотрены. Ключевые показатели ПБ установлены.',
h:['10.01 — Мероприятие создано','15.02 — Проведён анализ','28.03 — Отчёт утверждён']},
{id:3,sec:0,b:0,s:'warn',p:50,due:'31.12.2026',done:'—',dname:'Протоколы совещаний (a, b, c)',
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:'—',dname:'Отчёты о проделанной работе / Тесты',
r:'Генеральные директора филиалов и ДАО',
t:'Продолжить практику проверки знаний в формате тестирования после проведения инструктажей по охране труда в филиалах/ДАО Общества.',
ai:'Тестирование внедрено в 6 филиалах. Средний результат — 82%.',h:['01.02 — Создано','01.04 — Внедрено','01.06 — Отчёт']},
{id:5,sec:0,b:0,s:'done',p:100,due:'31.03.2026',done:'25.03.2026',dname:'Информация о нематериальном поощрении',
r:'Директор ДПБ, Генеральные директора филиалов и ДАО',
t:'Рассмотреть возможность нематериального поощрения филиалов и ДАО Общества, демонстрирующих устойчивое снижение количества несчастных случаев, пожаров и аварий по итогам нескольких и более лет.',
ai:'Положение утверждено. Определены 3 филиала-лидера.',h:['15.01 — Проект','01.03 — Согласование','25.03 — Утверждено']},
{id:6,sec:0,b:6,s:'warn',p:60,due:'30.06.2026',done:'—',dname:'ВНД по внутренним тренерам / Перечень тренеров',
r:'Генеральный директор КУ, Управляющий директор по персоналу / Генеральные директора филиалов и ДАО',
t:'Разработать/внести изменения в случае необходимости и утвердить внутренний нормативный документ, регламентирующий процедуру работы внутренних тренеров, в том числе по производственной безопасности, включая порядок их отбора, подготовки и привлечения, а также установление условий доплаты к основной заработной плате за выполнение тренерских функций.',
ai:'Проект ВНД на финальном согласовании. Перечень — 12 чел.',h:['01.03 — Создано','15.04 — Проект ВНД','01.06 — Перечень']},
{id:7,sec:0,b:1,s:'warn',p:40,due:'31.12.2026',done:'—',dname:'Материалы обмена опытом',
r:'Директор ДПБ, Генеральные директора филиалов и ДАО',
t:'Проводить мероприятия по обмену опытом в области производственной безопасности.',
ai:'Проведён 1 выезд на площадку KEGOC. Онлайн-семинар — июль.',h:['15.02 — Создано','01.04 — Выезд','15.05 — План'],
sub:[{l:'a',t:'Продолжить практику обмена передовым опытом на площадке Комитета HSE, в том числе путем выездов на производственные объекты ПК'},
{l:'b',t:'Рассмотреть возможность организации обмена опытом с иностранными и казахстанскими компаниями, в том числе путем проведения онлайн-семинаров'}]},
{id:8,sec:0,b:4,s:'wait',p:15,due:'30.09.2026',done:'—',dname:'Заключительный Акт / Программа Well-being / Отчёт / Отчёт о микротравмах',
r:'a) Директор ДПБ / b) Ген. директор КУ, Упр. директор по персоналу / c,d) Ген. директора филиалов и ДАО',
t:'Провести анализ эффективности реализуемых мероприятий по охране здоровья.',
ai:'Медосмотры — Q3. Well-being Week — сентябрь. Ранний этап.',h:['01.04 — Создано','01.06 — Проект алгоритма'],
sub:[{l:'a',t:'Организовать и обеспечить 100% прохождение обязательных периодических медицинских осмотров работниками, включая офисных работников'},
{l:'b',t:'Организовать ежегодную «Неделю благополучия» (Well-being Week) для работников всех уровней'},
{l:'c',t:'Создать условия и обеспечить контроль за прохождением медицинского скрининга работников'},
{l:'d',t:'Внедрить алгоритм учета и расследования микротравм'}]},
{id:9,sec:0,b:6,s:'wait',p:20,due:'31.12.2026',done:'—',dname:'Результаты конкурсов / Пакет материалов',
r:'Директор ДПБ, Генеральные директора филиалов и ДАО',
t:'Рассмотреть возможность участия ДПБ/филиалов/ДАО Общества в международных/национальных конкурсах и отраслевых соревнованиях профессионального мастерства в области производственной безопасности.',
ai:'Определены 2 конкурса. Заявки готовятся.',h:['01.05 — Создано','01.06 — Отобраны конкурсы']},
{id:10,sec:1,b:1,s:'warn',p:55,due:'31.12.2026',done:'—',dname:'Аналитическая справка по филиалам/ДАО',
r:'Генеральный директор ОДС, Генеральный директор СФ, Генеральный директор ДУП, Генеральный директор ДИТ',
t:'Проводить работы по техническому перевооружению морально и физически изношенного оборудования, зданий и сооружений, эксплуатация которых из-за их технического состояния сопровождается повышенными рисками возникновения аварий и несчастных случаев с тяжёлыми и летальными исходами, в соответствии с ранее утвержденными Планами на 2024-2027 годы.',
ai:'Заменено 55% единиц. Отдельные филиалы отстают на 12%.',h:['01.01 — Переходящее','01.04 — Отчёт','01.06 — 55%']},
{id:11,sec:1,b:1,s:'warn',p:70,due:'30.06.2026',done:'—',dname:'Переутверждённая процедура / Фотоотчёт / Протоколы обучения',
r:'Директор ДПБ, Генеральные директора филиалов и ДАО',
t:'Пересмотреть и актуализировать внутренний порядок выдачи нарядно-допускной системы, усилив законодательные требования РК путем внедрения в пилотном режиме практики применения сертификатов безопасности для одного из видов работ повышенной опасности.',
ai:'Процедура пересмотрена. Пилот запущен. Обучение — 85%.',h:['01.02 — Создано','01.04 — Проект','15.05 — Пилот']},
{id:12,sec:1,b:8,s:'wait',p:8,due:'30.09.2026',done:'—',dname:'Справка о внедрении / Фотоотчёт',
r:'Генеральные директора филиалов и ДАО',
t:'Рассмотреть возможность внедрения системы цифровой маркировки опасных технических устройств, предусматривающей присвоение каждому устройству QR-кода для быстрого доступа к паспорту, инструкции по эксплуатации и информации о проведенных технических освидетельствованиях.',
ai:'Проект на стадии ТЭО. Начало — июль.',h:['01.05 — Создано','01.06 — ТЭО']},
{id:13,sec:1,b:0,s:'warn',p:50,due:'31.12.2026',done:'—',dname:'Акты проверок / График',
r:'Директор ДПБ',
t:'Филиалам/ДАО Общества не реже 1 раза в квартал проводить проверку согласно адаптированным проверочным листам в области БиОТ, промышленной и пожарной безопасности в соответствии с требованиями законодательства Республики Казахстан.',
ai:'Q1 завершены. Q2 — по графику. Выявлено 23 нарушения.',h:['01.01 — Создано','31.03 — Q1','01.06 — Q2']},
{id:14,sec:1,b:0,s:'warn',p:40,due:'31.12.2026',done:'—',dname:'Письмо о предоставлении кандидата',
r:'Директор ДПБ',
t:'Продолжить практику участия в перекрёстных аудитах ПК, в том числе в соответствии с Планом-графиком проведения аудитов.',
ai:'Назначены 4 аудитора. Участвовали в 2 аудитах.',h:['15.01 — Назначены','01.03 — Аудит 1','15.04 — Аудит 2']},
{id:15,sec:1,b:0,s:'warn',p:48,due:'31.12.2026',done:'—',dname:'Аналитическая справка / Журнал опережающих индикаторов',
r:'Директор ДПБ, Генеральные директора филиалов и ДАО',
t:'Усилить контроль за применением проактивных инструментов: мониторинг поведенческих аудитов/наблюдений безопасности, регистрация и расследование опасных условий, опасных действий и потенциально опасных происшествий Near Miss; право приостановки работы.',
ai:'147 Near Miss (+12% к 2025). Аудиты — 320 шт.',h:['01.01 — Создано','01.04 — Q1','01.06 — Q2']},
{id:16,sec:1,b:1,s:'done',p:85,due:'31.12.2026',done:'—',dname:'План-график аудитов / Акты / Протоколы совещаний',
r:'Генеральные директора филиалов и ДАО',
t:'Провести работу по повышению эффективности управления подрядными организациями.',
ai:'Q1 — 12 подрядчиков проверено. Стартовые совещания — 100%.',h:['15.01 — План','01.03 — Q1','01.06 — Q2'],
sub:[{l:'a',t:'Обеспечить проведение аудита подрядчиков внутри филиалов/ДАО согласно типового чек-листа Фонда'},
{l:'b',t:'Продолжить практику проведения стартовых/установочных совещаний с подрядными организациями перед допуском на объект'}]},
{id:17,sec:1,b:0,s:'warn',p:35,due:'31.12.2026',done:'—',dname:'Отчёты / График проверок / Фотоотчёт',
r:'a) Главный административный директор, Директор ДПБ / b) Генеральные директора филиалов и ДАО',
t:'Обеспечить контроль за состоянием производственной безопасности на производственных объектах.',
ai:'CEO-1 проверил 2 филиала. 6 выездов. Активность ниже плана.',h:['01.02 — Создано','15.03 — Проверка 1','01.05 — Проверка 2'],
sub:[{l:'a',t:'Руководителям Общества уровня СЕО-1 не реже 1 раза в квартал лично проверять одно из филиалов/подрядных организаций'},
{l:'b',t:'Первым руководителям филиалов лично принимать участие во внутреннем производственном контроле с посещением площадок не реже 1 раза в квартал'}]},
{id:18,sec:1,b:1,s:'done',p:90,due:'31.12.2026',done:'—',dname:'Ежемесячный сводный отчёт',
r:'Генеральные директора филиалов и ДАО, Директор ДПБ',
t:'Обеспечить контроль за состоянием транспортной безопасности, в том числе путем ежемесячного мониторинга нарушений требований транспортной безопасности со стороны штатных водителей и водителей подрядных организаций, с последующим применением предусмотренных договорами мер воздействия.',
ai:'34 нарушения. Штрафы — 12 водителей. Тренд — снижение.',h:['01.01 — Создано','01.02 — Отчёт янв','01.06 — Отчёт май']},
{id:19,sec:2,b:1,s:'warn',p:30,due:'31.12.2026',done:'—',dname:'Акты тренировок / Пресс-релизы',
r:'a) Управляющий директор по безопасности / b) Генеральный директор СФ / c) Генеральные директора филиалов и ДАО',
t:'Обеспечить проведение учебных тревог и тренировок.',
ai:'1 учение. Пожарные: 1 из 2. Первая помощь — Q3.',h:['01.02 — Создано','15.03 — Учение','15.05 — Пожарная №1'],
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:'—',dname:'Приказ CMS / Материалы обучения / Акты штабов',
r:'Управляющий директор по безопасности, Генеральные директора филиалов и ДАО',
t:'Усилить работу по реагированию на ЧС.',
ai:'CMS подписана. Обучение — 60%. 1 заседание штаба.',h:['01.03 — Создано','01.04 — Приказ','15.05 — Штаб №1'],
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:'Опубликовано. Охват — 100%.',h:['15.01 — Проект','01.02 — Подписание','15.02 — Публикация']},
{id:22,sec:3,b:0,s:'wait',p:15,due:'31.12.2026',done:'—',dname:'Протоколы форумов / Протоколы семинаров',
r:'Директор ДПБ, Генеральные директора филиалов и ДАО / Департамент по коммуникациям',
t:'Проведение мероприятий по производственной безопасности.',
ai:'Форум — октябрь. Семинары — 2 площадки.',h:['01.05 — Создано','01.06 — Площадки'],
sub:[{l:'a',t:'Стратегические сессии/Форумы для первых руководителей филиалов/ДАО'},
{l:'b',t:'Семинары для подрядных организаций ПК на отдельных площадках филиалов/ДАО'}]},
{id:23,sec:3,b:6,s:'wait',p:10,due:'30.09.2026',done:'—',dname:'Протокол итогов Олимпиады',
r:'Директор ДПБ',
t:'Проведение Олимпиады по производственной безопасности среди специалистов производственной безопасности Общества и подрядных организаций на уровне Общества.',
ai:'Положение на согласовании.',h:['01.05 — Создано','01.06 — Проект']},
{id:24,sec:3,b:0,s:'done',p:92,due:'31.12.2026',done:'—',dname:'Бюллетени / Листы ознакомления',
r:'Директор ДПБ, Генеральные директора филиалов и ДАО',
t:'Обеспечить ознакомление всех работников филиалов/ДАО Общества с обстоятельствами несчастных случаев с тяжелым и летальным исходами, произошедших в ПК Фонда, посредством направления информационных бюллетеней, в том числе с использованием цифровых решений либо в рамках внеплановых инструктажей.',
ai:'3 бюллетеня. Ознакомление — 92%.',h:['01.01 — Создано','15.02 — №1','01.05 — №3']},
{id:25,sec:3,b:6,s:'warn',p:40,due:'31.12.2026',done:'—',dname:'Публикации SK News / Материалы мероприятий',
r:'a) Управляющий директор по персоналу, Департамент по коммуникациям / b,c,d) Директор ДПБ, Генеральные директора филиалов и ДАО',
t:'Проведение молодежных проектных инициатив в рамках работы Центра молодых работников по производственной безопасности.',
ai:'2 истории. 1 выезд. 3 специалиста в аудитах.',h:['01.02 — Создано','15.03 — Публикация','01.05 — Выезд'],
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:'—',dname:'Видеоролики / Постеры / Брошюры',
r:'a) Генеральные директора филиалов и ДАО / b,c) Директор ДПБ, Департамент по коммуникациям / d) Директор ДПБ',
t:'Усилить наглядную агитацию по производственной безопасности.',
ai:'2 видеоролика. Постеры распространены.',h:['01.02 — Создано','01.04 — Ролик №1','01.06 — Ролик №2'],
sub:[{l:'a',t:'Разработка и использование видеороликов/презентаций по ПБ, в т.ч. с участием получивших травму работников'},
{l:'b',t:'Разработка серий видеороликов «Безопасность будущего» и по профилактике травматизма'},
{l:'c',t:'Рассмотреть возможность выпуска подкаста с участием трудовых династий'},
{l:'d',t:'Разработка и распространение постеров, брошюр, информационных рассылок'}]},
{id:27,sec:3,b:3,s:'warn',p:30,due:'31.12.2026',done:'—',dname:'Фотофиксация',
r:'Директор ДПБ, Генеральные директора филиалов и ДАО',
t:'Рассмотрение возможности организации встреч коллектива с получившими производственные травмы работниками (с их согласия) с целью предупреждения аналогичных случаев травматизма.',
ai:'1 встреча. Согласия от 3 работников.',h:['01.03 — Создано','01.05 — Встреча']},
{id:28,sec:3,b:0,s:'warn',p:25,due:'31.12.2026',done:'—',dname:'Информационное письмо / Пресс-релизы',
r:'Директор ДПБ, Генеральные директора филиалов и ДАО / Департамент по коммуникациям',
t:'Проведение мероприятий, направленных на пропаганду безопасного выполнения работ через семейные ценности.',
ai:'5 писем. Семейный день — август.',h:['01.04 — Создано','15.05 — Письма'],
sub:[{l:'a',t:'Направление информационного письма членам семьи работника, положительно отличившегося в вопросах ПБ'},
{l:'b',t:'Проведение Семейных дней охраны труда и дней открытых дверей для семей работников'},
{l:'c',t:'Проведение конкурса рисунков среди работников и их детей на тему «Спецодежда будущего!»'}]},
{id:29,sec:3,b:6,s:'late',p:40,due:'30.06.2026',done:'—',dname:'Корпоративный сборник лучших практик',
r:'Директор ДПБ, Генеральные директора филиалов и ДАО',
t:'Разработать корпоративный сборник лучших практик по производственной безопасности в формате методического пособия или интерактивного PDF документа, отражающий меры по снижению производственного травматизма и управлению критическими рисками.',
ai:'Сборник в разработке. Риск срыва Q2.',h:['01.03 — Создано','01.05 — 4 филиала','01.06 — Эскалация']},
{id:30,sec:3,b:7,s:'warn',p:60,due:'31.12.2026',done:'—',dname:'Предложения / План реализации',
r:'Директор ДПБ, Генеральные директора филиалов и ДАО',
t:'Сбор предложений по совершенствованию системы управления производственной безопасности посредством применения цифровых решений с консолидацией в ДПБ.',
ai:'18 предложений. 5 в реализацию.',h:['01.01 — Создано','01.04 — 10','01.06 — 18']},
{id:31,sec:3,b:0,s:'warn',p:75,due:'30.06.2026',done:'—',dname:'Видеообзор',
r:'Директор ДПБ, Генеральные директора филиалов и ДАО',
t:'Разработка видеообзора кейсов происшествий в ПК с учетом специфики деятельности (из доступных на открытых медиа источниках) для наглядной демонстрации и разъяснения работникам о необходимости и важности соблюдения требований безопасности.',
ai:'Монтаж 75%. Озвучка — 15 июня.',h:['01.03 — Создано','01.05 — Сценарий','01.06 — 75%']},
{id:32,sec:4,b:8,s:'warn',p:70,due:'30.06.2026',done:'—',dname:'Справка / Скриншоты чат-бота',
r:'Директор ДПБ, Генеральные директора филиалов и ДАО',
t:'Обеспечить применение в филиалах/ДАО Общества чат-бот ИИ ассистент по производственной безопасности с целью упрощения доступа к нормативно-правовым актам Республики Казахстан и ВНД группы Фонда.',
ai:'Чат-бот тестируется. Пилот — 15 июня.',h:['01.02 — Создано','01.04 — Разработка','01.06 — Тест']},
{id:33,sec:4,b:8,s:'wait',p:15,due:'31.12.2026',done:'—',dname:'Справка / Скриншоты системы',
r:'Директор ДПБ, Генеральные директора филиалов и ДАО',
t:'Обеспечить применение в филиалах/ДАО Общества интегрированную систему анализа и предупреждения несчастных случаев и критических происшествий, а также платформу по идентификации и оценке рисков перед началом проведения работ на опасных производственных объектах.',
ai:'ТЗ согласовывается. Интеграция прорабатывается.',h:['01.04 — Создано','01.06 — ТЗ']},
{id:34,sec:4,b:8,s:'wait',p:10,due:'31.12.2026',done:'—',dname:'Справка / Скриншоты HSE паспорта',
r:'Директор ДПБ, Генеральные директора филиалов и ДАО',
t:'Рассмотреть возможность запуска в филиалах/ДАО Общества электронного HSE паспорта на каждого работника с последующей интеграцией в корпоративную цифровую систему.',
ai:'Концепция утверждена. Подрядчик выбран.',h:['01.05 — Создано','01.06 — Концепция']},
{id:35,sec:4,b:5,s:'wait',p:8,due:'31.12.2026',done:'—',dname:'Справка / Скриншоты системы',
r:'Директор ДПБ, Генеральные директора филиалов и ДАО',
t:'Рассмотреть возможность внедрения системы оформления нарядов-допусков на проведение работ повышенной опасности в электронном виде в филиалах/ДАО Общества.',
ai:'Предпроект. Анализ рынка — Q3.',h:['01.05 — Создано','01.06 — Анализ']}
]
const users = {
'dpp@telecom.kz':{name:'Директор ДПБ',branch:0,role:'director'},
'ahmetov@telecom.kz':{name:'Ахметов К.Т.',branch:6,role:'responsible'},
'serikov@telecom.kz':{name:'Сериков А.М.',branch:1,role:'responsible'},
'nurlanov@telecom.kz':{name:'Нурланов Д.С.',branch:8,role:'responsible'},
'aliev@telecom.kz':{name:'Алиев Г.С.',branch:4,role:'responsible'},
'tulegenov@telecom.kz':{name:'Тулегенов Е.А.',branch:2,role:'responsible'},
'saparov@telecom.kz':{name:'Сапаров А.Д.',branch:3,role:'responsible'},
'maratov@telecom.kz':{name:'Маратов Ж.К.',branch:5,role:'responsible'},
'iskakov@telecom.kz':{name:'Искаков Р.Н.',branch:7,role:'responsible'},
'admin@telecom.kz':{name:'Администратор',branch:0,role:'admin'}
}
let currentUser=null, events=JSON.parse(JSON.stringify(baseEvents))
function loadState(){
const s=localStorage.getItem('samruk_events');if(s) events=JSON.parse(s)
const u=localStorage.getItem('samruk_user');if(u){currentUser=JSON.parse(u);showApp()}
}
function saveState(){localStorage.setItem('samruk_events',JSON.stringify(events))}
function saveUser(){localStorage.setItem('samruk_user',JSON.stringify(currentUser))}
function doLogin(e){
e.preventDefault()
const email=document.getElementById('loginEmail').value.trim().toLowerCase()
const pass=document.getElementById('loginPass').value
if(users[email]&&pass.length>=1){currentUser={email,...users[email]};saveUser();showApp()}
else document.getElementById('loginErr').style.display='block'
}
function doLogout(){localStorage.removeItem('samruk_user');currentUser=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';document.getElementById('userLabel').innerHTML=`<strong>${currentUser.name}</strong> · ${branches[currentUser.branch]}`;renderAll()}
function getNotifs(){
const my=currentUser.role==='admin'||currentUser.role==='director'?events:events.filter(e=>e.b===currentUser.branch)
const n=[]
my.forEach(e=>{if(e.s==='late')n.push({type:'danger',msg:`Просрочено: ${e.t.slice(0,60)}...`,time:e.due});if(e.s==='warn'&&e.p<30)n.push({type:'warn',msg:`Низкий прогресс (${e.p}%): ${e.t.slice(0,50)}...`,time:'Сейчас'})})
return n
}
function renderNotifs(){
const n=getNotifs(),el=document.getElementById('notifDrop'),cnt=document.getElementById('notifCount')
cnt.textContent=n.length;cnt.style.display=n.length?'inline-block':'none'
el.innerHTML=n.length?n.map(n=>`<div class="item"><div class="title">${n.type==='danger'?'🔴':'🟡'} ${n.msg}</div><div class="time">${n.time}</div></div>`).join(''):'<div class="empty">Новых уведомлений нет</div>'
}
function toggleNotif(){renderNotifs();document.getElementById('notifDrop').classList.toggle('open')}
function sBadge(s){const m={done:'green',warn:'amber',late:'red',wait:'gray'};return`<span class="badge ${m[s]}">${statusMap[s]}</span>`}
function pctHtml(p){return`<div class="pct-bar"><div class="track"><div class="fill" style="width:${p}%;background:${p>=80?'var(--green)':p>=40?'var(--amber)':'var(--red)'}"></div></div>${p}%</div>`}
function getMyEvents(){if(!currentUser)return[];if(currentUser.role==='admin'||currentUser.role==='director')return events;return events.filter(e=>e.b===currentUser.branch)}
function renderMyEvents(){
const sf=document.getElementById('myStatusFilter').value
let list=getMyEvents();if(sf)list=list.filter(e=>e.s===sf)
const done=list.filter(e=>e.s==='done').length,late=list.filter(e=>e.s==='late').length,warn=list.filter(e=>e.s==='warn').length,wait=list.filter(e=>e.s==='wait').length
document.getElementById('myStats').innerHTML=`<div class="stat-card"><div class="lbl">Мои мероприятия</div><div class="num">${list.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 class="stat-card blue"><div class="lbl">Не начато</div><div class="num">${wait}</div></div>`
document.getElementById('myCount').textContent=`Найдено: ${list.length}`
document.getElementById('myEventsTable').innerHTML=`<tr><th>№</th><th>Мероприятие</th><th>Раздел</th><th>Срок</th><th>Прогресс</th><th>Статус</th><th></th></tr>${list.map(e=>`<tr><td>${e.id}</td><td style="font-size:12px;max-width:320px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap" title="${e.t}">${e.t}</td><td><span class="badge blue">${['I','II','III','IV','V'][e.sec]}</span></td><td style="font-size:12px">${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>`).join('')}`
}
function renderAllEvents(){
const search=document.getElementById('allSearch').value.toLowerCase(),sf=document.getElementById('allStatusFilter').value,secF=document.getElementById('allSecFilter').value
let list=events;if(search)list=list.filter(e=>e.t.toLowerCase().includes(search)||branches[e.b].toLowerCase().includes(search));if(sf)list=list.filter(e=>e.s===sf);if(secF!=='')list=list.filter(e=>e.sec===parseInt(secF))
document.getElementById('allEventsTable').innerHTML=`<tr><th>№</th><th>Мероприятие</th><th>Дивизион</th><th>Раздел</th><th>Срок</th><th>Прогресс</th><th>Статус</th></tr>${list.map(e=>`<tr><td>${e.id}</td><td style="font-size:12px;max-width:300px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap" title="${e.t}">${e.t}</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 style="font-size:12px">${e.due}</td><td>${pctHtml(e.p)}</td><td>${sBadge(e.s)}</td></tr>`).join('')}`
}
function renderAnalytics(){
let done=events.filter(e=>e.s==='done').length,total=events.length
document.getElementById('globalStats').innerHTML=`<div class="stat-card"><div class="lbl">Всего</div><div class="num">${total}</div></div><div class="stat-card green"><div class="lbl">Исполнено</div><div class="num">${done}</div><div class="sub">${Math.round(done/total*100)}%</div></div><div class="stat-card amber"><div class="lbl">На контроле</div><div class="num">${events.filter(e=>e.s==='warn').length}</div></div><div class="stat-card red"><div class="lbl">Просрочено</div><div class="num">${events.filter(e=>e.s==='late').length}</div></div>`
document.getElementById('analyticsContent').innerHTML=`<h3>Рейтинг дивизионов</h3>${branches.map((b,i)=>{let items=events.filter(e=>e.b===i),d=items.filter(e=>e.s==='done').length,pct=Math.round(d/Math.max(1,items.length)*100);return`<div class="pct-bar" style="margin-bottom:8px"><span style="width:220px;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;width:60px;text-align:right">${pct}%</span></div>`}).join('')}`
}
// ===== FILE STORAGE: month-keyed =====
function getFiles(eventId){ return JSON.parse(localStorage.getItem('samruk_files_'+eventId)||'{}') }
function setFiles(eventId,obj){ localStorage.setItem('samruk_files_'+eventId,JSON.stringify(obj)) }
function getSubChecks(eventId){ return JSON.parse(localStorage.getItem('samruk_sub_'+eventId)||'[]') }
function setSubChecks(eventId,arr){ localStorage.setItem('samruk_sub_'+eventId,JSON.stringify(arr)) }
// ===== EDIT MODAL =====
let editMonthIdx=5 // default June
function openEdit(id,monthIdx){
if(typeof monthIdx==='number') editMonthIdx=monthIdx
const e=events.find(x=>x.id===id);if(!e)return
const savedEdits=JSON.parse(localStorage.getItem('samruk_edits_'+e.id)||'{}')
const filesObj=getFiles(e.id)
const subChecks=getSubChecks(e.id)
const curMonth=months[editMonthIdx]
const curFiles=filesObj[curMonth]||[]
const totalFiles=Object.values(filesObj).reduce((s,a)=>s+a.length,0)
// Sub-items HTML
let subHtml=''
if(e.sub&&e.sub.length){
subHtml=`<div style="font-weight:600;margin-bottom:8px;font-size:14px">Подпункты мероприятия</div><div class="sub-items">${e.sub.map((s,i)=>{let ch=subChecks.includes(i);return`<div class="sub-item"><input type="checkbox" id="subchk_${i}" ${ch?'checked':''}><span class="sub-label">${s.l})</span><span class="sub-text">${s.t}</span></div>`}).join('')}</div>`
}
// File list for current month
let filesHtml=''
if(curFiles.length){
filesHtml=`<div style="font-weight:600;margin:12px 0 6px;font-size:13px">Загруженные файлы за ${M(editMonthIdx)} (${curFiles.length} шт.)</div>`
curFiles.forEach((f,i)=>{filesHtml+=`<div class="file-row"><span class="file-info"><span class="file-name" onclick="downloadFile(${e.id},'${curMonth}',${i})">📄 ${f.name}</span><span class="file-desc">${f.desc||''}</span></span><span class="file-meta">${(f.size/1024).toFixed(0)} КБ · ${f.date}</span><button class="file-del" onclick="removeFile(${e.id},'${curMonth}',${i})">×</button></div>`})
}else{filesHtml=`<p style="font-size:13px;color:var(--gray-500);margin:12px 0 6px">Нет файлов за ${M(editMonthIdx)}</p>`}
document.getElementById('editModalContent').innerHTML=`
<button class="close" onclick="closeEditModal()">&times;</button>
<span class="badge blue">Раздел ${['I','II','III','IV','V'][e.sec]}</span>
<h3 style="margin:8px 0">${e.t}</h3>
<div class="meta-row">
<div class="fld">Дивизион<strong>${branches[e.b]}</strong></div>
<div class="fld">Ответственный<strong>${e.r}</strong></div>
<div class="fld">Срок<strong>${e.due}</strong></div>
<div class="fld">Факт<strong>${e.done}</strong></div>
</div>
<div class="field"><label>Статус</label><select id="editStatus" onchange="autoProgress()"><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>
<div class="field"><label>Прогресс (%)</label><input type="range" id="editProgress" min="0" max="100" value="${e.p}" oninput="document.getElementById('pVal').textContent=this.value+'%'" style="width:100%"><span id="pVal" style="font-weight:700">${e.p}%</span></div>
<div class="field"><label>Комментарий</label><textarea id="editComment" placeholder="Комментарий к статусу...">${savedEdits.comment||''}</textarea></div>
${subHtml}
<div style="border-top:1px solid var(--gray-200);padding-top:16px;margin-top:12px">
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:12px">
<span style="font-weight:600;font-size:14px">📎 Подтверждающие материалы</span>
<span style="font-size:12px;color:var(--gray-500)">Всего файлов: ${totalFiles}</span>
</div>
<div class="month-tabs">${months.map((m,i)=>`<span class="month-tab${i===editMonthIdx?' active':''}" onclick="openEdit(${e.id},${i})">${M(i)}</span>`).join('')}</div>
${filesHtml}
<div class="upload-row">
<input type="text" id="fileDesc" placeholder="Описание (акт, протокол, фото...)" style="flex:2">
<input type="file" id="editFileInput" multiple>
<button class="btn btn-sm" onclick="uploadFiles(${e.id},'${curMonth}')">Загрузить</button>
</div>
<p style="font-size:11px;color:var(--gray-500);margin-top:6px">Формы завершения: ${e.dname}</p>
</div>
<div class="ai-block"><h4>🤖 Вывод ИИ-агента</h4>${e.ai}</div>
<div style="font-weight:600;margin:8px 0 4px;font-size:14px">История:</div>
<div>${e.h.map(h=>`<div class="history-item"><div class="dot"></div>${h}</div>`).join('')}</div>
<div style="margin-top:20px;display:flex;gap:12px">
<button class="btn" onclick="saveEdit(${e.id})">Сохранить</button>
<button class="btn btn-outline" onclick="closeEditModal()">Отмена</button>
</div>`
document.getElementById('editModalOverlay').classList.add('open')
}
function uploadFiles(eventId, monthKey){
const fi=document.getElementById('editFileInput'),desc=document.getElementById('fileDesc').value.trim()
if(!fi.files.length)return
const MAX=4*1024*1024,obj=getFiles(eventId),arr=obj[monthKey]||[]
let processed=0,skipped=0
for(const f of fi.files){
if(f.size>MAX){skipped++;processed++;if(processed===fi.files.length)finish();continue}
const r=new FileReader()
r.onload=function(ev){arr.push({name:f.name,size:f.size,type:f.type,desc,date:new Date().toLocaleDateString(),data:ev.target.result});processed++;if(processed===fi.files.length)finish()}
r.readAsDataURL(f)
}
function finish(){
obj[monthKey]=arr;try{setFiles(eventId,obj)}catch(e){alert('⚠️ Хранилище переполнено')}
if(skipped)alert(`⚠️ ${skipped} файл(ов) > 4 МБ пропущены`)
closeEditModal();openEdit(eventId)
}
}
function downloadFile(eventId, monthKey, idx){
const obj=getFiles(eventId),arr=obj[monthKey];if(!arr||!arr[idx]||!arr[idx].data)return
const 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 removeFile(eventId, monthKey, idx){
const obj=getFiles(eventId);if(!obj[monthKey])return
obj[monthKey].splice(idx,1);if(!obj[monthKey].length)delete obj[monthKey];setFiles(eventId,obj)
closeEditModal();openEdit(eventId)
}
function autoProgress(){
const s=document.getElementById('editStatus').value,p=document.getElementById('editProgress')
if(s==='done')p.value=100;else if(s==='wait')p.value=0;document.getElementById('pVal').textContent=p.value+'%'
}
function saveEdit(id){
const e=events.find(x=>x.id===id);if(!e)return
e.s=document.getElementById('editStatus').value
e.p=parseInt(document.getElementById('editProgress').value)
const comment=document.getElementById('editComment').value.trim()
// Save sub-item checks
if(e.sub&&e.sub.length){
const checks=[]
e.sub.forEach((_,i)=>{if(document.getElementById('subchk_'+i)?.checked)checks.push(i)})
setSubChecks(id,checks)
}
const now=new Date().toLocaleDateString()
e.h.push(`${now}${currentUser.name}: статус ${statusMap[e.s]}, прогресс ${e.p}%${comment?' — комм.: '+comment:''}`)
if(e.s==='done'&&e.done==='—')e.done=now
localStorage.setItem('samruk_edits_'+id,JSON.stringify({comment,editedBy:currentUser.name,editedAt:now}))
saveState();closeEditModal();renderAll();renderNotifs()
}
function closeEditModal(){document.getElementById('editModalOverlay').classList.remove('open')}
document.querySelectorAll('.tab-btn').forEach(btn=>{btn.addEventListener('click',function(){document.querySelectorAll('.tab-btn').forEach(b=>b.classList.remove('active'));document.querySelectorAll('.tab-content').forEach(c=>c.classList.remove('active'));this.classList.add('active');document.getElementById('tab-'+this.dataset.tab).classList.add('active');if(this.dataset.tab==='analytics')renderAnalytics()})})
document.getElementById('editModalOverlay').addEventListener('click',function(e){if(e.target===this)closeEditModal()})
document.addEventListener('keydown',e=>{if(e.key==='Escape'){closeEditModal();document.getElementById('notifDrop').classList.remove('open')}})
document.addEventListener('click',e=>{if(!e.target.closest('.notif-btn')&&!e.target.closest('.notif-drop'))document.getElementById('notifDrop').classList.remove('open')})
function renderAll(){renderMyEvents();renderAllEvents();renderNotifs()}
loadState();if(currentUser)showApp()
</script>
</body>
</html>