samruk-ai-agent/index.html

725 lines
62 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;--gray-700:#374151;--green:#10B981;--red:#EF4444;--amber:#F59E0B;--blue:#3B82F6;--sidebar-w:260px}
*{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)}
.btn-red{background:var(--red);color:#fff}.btn-red:hover{background:#dc2626}
.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}
/* LOGIN SCREEN */
#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}
/* MAIN APP (hidden) */
#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 DROPDOWN */
.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;cursor:default}
.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);font-size:14px}
/* TABS */
.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}
/* LAYOUT */
.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 */
.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:700px;width:92vw;max-height:88vh;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:16px}
.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:80px;resize:vertical}
.modal .meta-row{display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-bottom:16px}
.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}
.modal .docs{display:flex;gap:8px;flex-wrap:wrap;margin:8px 0}
.modal .docs .doc-chip{background:var(--gray-100);padding:6px 12px;border-radius:6px;font-size:13px;display:flex;align-items:center;gap:6px}
.modal .docs .doc-chip button{background:none;border:none;cursor:pointer;color:var(--red);font-size:14px;line-height:1}
.file-upload{border:2px dashed var(--gray-200);border-radius:8px;padding:20px;text-align:center;color:var(--gray-500);cursor:pointer;margin-bottom:12px;font-size:14px;transition:.15s}
.file-upload:hover{border-color:var(--cyan);color:var(--cyan)}
.file-upload .icon{font-size:28px;display:block;margin-bottom:4px}
.ai-block{background:var(--cyan-50);border-radius:8px;padding:16px;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}
@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)}
}
</style>
</head>
<body>
<!-- LOGIN SCREEN -->
<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, dpt@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>
<!-- MAIN APP -->
<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()" title="Уведомления">🔔<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>
<!-- EDIT MODAL -->
<div class="modal-overlay" id="editModalOverlay">
<div class="modal" id="editModalContent"></div>
</div>
<script>
// ===== DATA =====
const sections = [
'I. Люди. Повышение культуры безопасности',
'II. Безопасность при эксплуатации оборудования',
'III. Предупреждение и готовность к ликвидации аварий и ЧС',
'IV. Информационно-разъяснительная работа',
'V. Внедрение ИИ и цифровизации'
]
const branches = [
'Дирекция производственной безопасности',
'Объединение «Дивизион «Сеть»»',
'Дивизион по корпоративному бизнесу',
'Дивизион по розничному бизнесу',
'Сервисная фабрика',
'Дирекция «Телеком Комплект»',
'Корпоративный университет',
'Дирекция управления проектами',
'Дивизион цифрового бизнеса'
]
const statusMap = {done:'Исполнено',warn:'На контроле',late:'Просрочено',wait:'Не начато'}
// 35 events — exact titles from approved plan
const baseEvents = [
{id:1,sec:0,b:6,s:'warn',p:45,due:'31.12.2026',done:'—',
r:'Генеральный директор КУ, Генеральные директора филиалов и ДАО',
t:'Продолжить проведение обучения и повышения квалификации руководителей и работников компании в соответствии с лучшими международными практиками, ориентированными на специфику условий труда, работы повышенной опасности и требований промышленной безопасности, а также развитие культуры безопасности, включая обучение производственного персонала по курсу «Культура безопасного труда», в том числе с применением VR, AR технологий и цифровых симуляторов аварийных ситуаций по различным направлениям производственной безопасности (с правом выдачи сертификатов).',
docs:[],dname:'Протоколы обучения / Электронная ведомость',
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',
r:'Директор ДПБ, Генеральный директор ДИТ, Генеральные директора филиалов и ДАО',
t:'Провести анализ, в том числе с использованием аналитических платформ (Microsoft Teams, Power BI, Tableau, Qlik и др.), и в случае необходимости, осуществить пересмотр внутренних нормативных документов филиалов/ДАО Общества в соответствии со «Стратегией развития производственной безопасности АО «Самрук-Қазына» на 2024-2028 гг.», включая установку значений ключевых показателей производственной безопасности.',
docs:[],dname:'Отчёт о проведённом анализе / Утверждённый ВНД',
ai:'Анализ завершён в срок. ВНД пересмотрены. Ключевые показатели ПБ установлены. Замечаний нет.',
h:['10.01 — Мероприятие создано','15.02 — Проведён анализ ВНД','28.03 — Отчёт утверждён']},
{id:3,sec:0,b:0,s:'warn',p:50,due:'31.12.2026',done:'—',
r:'Главный административный директор, Директор ДПБ / Генеральные директора филиалов и ДАО',
t:'Организовывать тематические совещания по вопросам производственной безопасности: a) руководство Общества с филиалами/ДАО, не менее 1 раза в квартал; b) руководство филиалов/ДАО со структурными подразделениями, не менее 1 раза в месяц; c) руководство региональных подразделений с подрядными организациями, не менее 1 раза в квартал.',
docs:[],dname:'Протоколы совещаний (a, b, c)',
ai:'Проведено 2 квартальных совещания. Ежемесячные совещания филиалов — выполняется. Совещания с подрядчиками — график соблюдается.',
h:['10.01 — Мероприятие создано','15.02 — Совещание Q1','15.05 — Совещание Q2']},
{id:4,sec:0,b:6,s:'warn',p:55,due:'31.12.2026',done:'—',
r:'Генеральные директора филиалов и ДАО',
t:'Продолжить практику проверки знаний в формате тестирования после проведения инструктажей по охране труда в филиалах/ДАО Общества.',
docs:[],dname:'Отчёты о проделанной работе / Тесты',
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',
r:'Директор ДПБ, Генеральные директора филиалов и ДАО',
t:'Рассмотреть возможность нематериального поощрения филиалов и ДАО Общества, демонстрирующих устойчивое снижение количества несчастных случаев, пожаров и аварий по итогам нескольких и более лет.',
docs:[],dname:'Информация о нематериальном поощрении',
ai:'Положение утверждено. Определены 3 филиала-лидера за 2023-2025 гг. Поощрение доведено до коллективов.',
h:['15.01 — Проект положения','01.03 — Согласование','25.03 — Утверждено']},
{id:6,sec:0,b:6,s:'warn',p:60,due:'30.06.2026',done:'—',
r:'Генеральный директор КУ, Управляющий директор по персоналу / Генеральные директора филиалов и ДАО',
t:'Разработать/внести изменения в случае необходимости и утвердить внутренний нормативный документ, регламентирующий процедуру работы внутренних тренеров, в том числе по производственной безопасности, включая порядок их отбора, подготовки и привлечения, а также установление условий доплаты к основной заработной плате за выполнение тренерских функций.',
docs:[],dname:'ВНД по внутренним тренерам / Перечень тренеров',
ai:'Проект ВНД на финальном согласовании. Перечень тренеров сформирован (12 чел). Риск: доплаты требуют бюджета.',
h:['01.03 — Мероприятие создано','15.04 — Проект ВНД','01.06 — Перечень тренеров']},
{id:7,sec:0,b:1,s:'warn',p:40,due:'31.12.2026',done:'—',
r:'Директор ДПБ, Генеральные директора филиалов и ДАО',
t:'Проводить мероприятия по обмену опытом в области производственной безопасности: a) продолжить практику обмена передовым опытом на площадке Комитета HSE, в том числе путем выездов на производственные объекты ПК; b) рассмотреть возможность организации обмена опытом с иностранными и казахстанскими компаниями, в том числе путем проведения онлайн-семинаров.',
docs:[],dname:'Материалы обмена опытом',
ai:'Проведён 1 выезд на площадку KEGOC. Онлайн-семинар с иностранной компанией запланирован на июль.',
h:['15.02 — Мероприятие создано','01.04 — Выезд на KEGOC','15.05 — План семинаров']},
{id:8,sec:0,b:4,s:'wait',p:15,due:'30.09.2026',done:'—',
r:'a) Директор ДПБ / b) Генеральный директор КУ, Управляющий директор по персоналу / c,d) Генеральные директора филиалов и ДАО',
t:'Провести анализ эффективности реализуемых мероприятий по охране здоровья: a) организовать и обеспечить 100% прохождение обязательных периодических медицинских осмотров; b) организовать ежегодную «Неделю благополучия» (Well-being Week); c) создать условия для прохождения медицинского скрининга; d) внедрить алгоритм учета и расследования микротравм.',
docs:[],dname:'Заключительный Акт / Программа Well-being / Отчёт о скрининге / Отчёт о микротравмах',
ai:'Медосмотры по графику — Q3. Well-being Week запланирован на сентябрь. Ранний этап.',
h:['01.04 — Мероприятие создано','01.06 — Проект алгоритма микротравм']},
{id:9,sec:0,b:6,s:'wait',p:20,due:'31.12.2026',done:'—',
r:'Директор ДПБ, Генеральные директора филиалов и ДАО',
t:'Рассмотреть возможность участия ДПБ/филиалов/ДАО Общества в международных/национальных конкурсах и отраслевых соревнованиях профессионального мастерства в области производственной безопасности.',
docs:[],dname:'Результаты конкурсов / Пакет материалов',
ai:'Определены 2 конкурса для участия. Заявки готовятся. Срок — до конца года.',
h:['01.05 — Мероприятие создано','01.06 — Отобраны конкурсы']},
{id:10,sec:1,b:1,s:'warn',p:55,due:'31.12.2026',done:'—',
r:'Генеральный директор ОДС, Генеральный директор СФ, Генеральный директор ДУП, Генеральный директор ДИТ',
t:'Проводить работы по техническому перевооружению морально и физически изношенного оборудования, зданий и сооружений, эксплуатация которых из-за их технического состояния сопровождается повышенными рисками возникновения аварий и несчастных случаев с тяжёлыми и летальными исходами, в соответствии с ранее утвержденными Планами на 2024-2027 годы.',
docs:[],dname:'Аналитическая справка по филиалам/ДАО',
ai:'По плану 2024-2027. Заменено 55% единиц. Отдельные филиалы отстают на 12%. Рекомендуется усилить контроль.',
h:['01.01 — Переходящее с 2024','01.04 — Промежуточный отчёт','01.06 — 55% исполнения']},
{id:11,sec:1,b:1,s:'warn',p:70,due:'30.06.2026',done:'—',
r:'Директор ДПБ, Генеральные директора филиалов и ДАО',
t:'Пересмотреть и актуализировать внутренний порядок выдачи нарядно-допускной системы, усилив законодательные требования РК путем внедрения в пилотном режиме практики применения сертификатов безопасности для одного из видов работ повышенной опасности.',
docs:[],dname:'Переутверждённая процедура / Фотоотчёт / Протоколы обучения',
ai:'Процедура пересмотрена. Пилот сертификатов запущен. Обучение — 85% персонала. Успеваем в Q2.',
h:['01.02 — Мероприятие создано','01.04 — Проект процедуры','15.05 — Пилот запущен']},
{id:12,sec:1,b:8,s:'wait',p:8,due:'30.09.2026',done:'—',
r:'Генеральные директора филиалов и ДАО',
t:'Рассмотреть возможность внедрения системы цифровой маркировки опасных технических устройств, предусматривающей присвоение каждому устройству QR-кода для быстрого доступа к паспорту, инструкции по эксплуатации и информации о проведенных технических освидетельствованиях.',
docs:[],dname:'Справка о внедрении / Фотоотчёт',
ai:'Проект на стадии ТЭО. QR-коды не заказаны. Начало работ — июль. Риск срыва низкий.',
h:['01.05 — Мероприятие создано','01.06 — ТЭО в разработке']},
{id:13,sec:1,b:0,s:'warn',p:50,due:'31.12.2026',done:'—',
r:'Директор ДПБ',
t:'Филиалам/ДАО Общества не реже 1 раза в квартал проводить проверку согласно адаптированным проверочным листам в области БиОТ, промышленной и пожарной безопасности в соответствии с требованиями законодательства Республики Казахстан.',
docs:[],dname:'Акты проверок / График проверок',
ai:'Q1 проверки завершены. Q2 — идёт по графику. Выявлено 23 нарушения, устранено 18.',
h:['01.01 — Мероприятие создано','31.03 — Q1 проверки','01.06 — Q2 начаты']},
{id:14,sec:1,b:0,s:'warn',p:40,due:'31.12.2026',done:'—',
r:'Директор ДПБ',
t:'Продолжить практику участия в перекрёстных аудитах ПК, в том числе в соответствии с Планом-графиком проведения аудитов.',
docs:[],dname:'Письмо о предоставлении кандидата',
ai:'Назначены 4 аудитора. Участвовали в 2 аудитах. График соблюдается.',
h:['15.01 — Назначены аудиторы','01.03 — Аудит KEGOC','15.04 — Аудит КазТрансОйл']},
{id:15,sec:1,b:0,s:'warn',p:48,due:'31.12.2026',done:'—',
r:'Директор ДПБ, Генеральные директора филиалов и ДАО',
t:'Усилить контроль за применением проактивных инструментов: мониторинг поведенческих аудитов/наблюдений безопасности, регистрация и расследование опасных условий, опасных действий и потенциально опасных происшествий Near Miss; право приостановки работы.',
docs:[],dname:'Аналитическая справка / Журнал опережающих индикаторов',
ai:'Зарегистрировано 147 Near Miss (+12% к 2025). Поведенческие аудиты — 320 шт.',
h:['01.01 — Мероприятие создано','01.04 — Q1: 68 Near Miss','01.06 — Q2: 79 Near Miss']},
{id:16,sec:1,b:1,s:'done',p:85,due:'31.12.2026',done:'—',
r:'Генеральные директора филиалов и ДАО',
t:'Провести работу по повышению эффективности управления подрядными организациями: a) аудит подрядчиков внутри филиалов/ДАО согласно типового чек-листа Фонда; b) проведение стартовых/установочных совещаний с подрядными организациями перед допуском на территорию объекта.',
docs:[],dname:'План-график аудитов / Акты / Протоколы совещаний',
ai:'Q1 — 12 подрядчиков проверено. Стартовые совещания — 100% охват.',
h:['15.01 — План-график','01.03 — Q1 проверки','01.06 — Q2 проверки']},
{id:17,sec:1,b:0,s:'warn',p:35,due:'31.12.2026',done:'—',
r:'a) Главный административный директор, Директор ДПБ / b) Генеральные директора филиалов и ДАО',
t:'Обеспечить контроль за состоянием производственной безопасности на производственных объектах: a) руководителям Общества уровня СЕО-1 не реже 1 раза в квартал лично проверять одно из филиалов/подрядных организаций; b) первым руководителям филиалов лично принимать участие во внутреннем производственном контроле с посещением площадок не реже 1 раза в квартал.',
docs:[],dname:'Отчёты / График проверок / Фотоотчёт',
ai:'CEO-1 проверил 2 филиала. Первые руководители — 6 выездов. Активность ниже плана.',
h:['01.02 — Мероприятие создано','15.03 — Проверка 1','01.05 — Проверка 2']},
{id:18,sec:1,b:1,s:'done',p:90,due:'31.12.2026',done:'—',
r:'Генеральные директора филиалов и ДАО, Директор ДПБ',
t:'Обеспечить контроль за состоянием транспортной безопасности, в том числе путем ежемесячного мониторинга нарушений требований транспортной безопасности со стороны штатных водителей и водителей подрядных организаций, с последующим применением предусмотренных договорами мер воздействия.',
docs:[],dname:'Ежемесячный сводный отчёт',
ai:'Январь-май — 34 нарушения. Штрафы применены к 12 водителям. Тренд на снижение (-15% к 2025).',
h:['01.01 — Мероприятие создано','01.02 — Отчёт январь','01.06 — Отчёт май']},
{id:19,sec:2,b:1,s:'warn',p:30,due:'31.12.2026',done:'—',
r:'a) Управляющий директор по безопасности / b) Генеральный директор СФ / c) Генеральные директора филиалов и ДАО',
t:'Обеспечить проведение: a) не менее одной учебной тревоги и/или противоаварийной тренировки по ликвидации крупной аварии, ЧС на опасном производственном объекте; b) не менее двух тренировок по тушению пожара в административных зданиях; c) не менее одного практического занятия по оказанию первой помощи.',
docs:[],dname:'Акты тренировок / Пресс-релизы',
ai:'Проведено 1 учение. Пожарные тренировки: 1 из 2. Занятия по первой помощи — Q3.',
h:['01.02 — Мероприятие создано','15.03 — Учение','15.05 — Пожарная тренировка №1']},
{id:20,sec:2,b:0,s:'warn',p:65,due:'30.06.2026',done:'—',
r:'Управляющий директор по безопасности, Генеральные директора филиалов и ДАО',
t:'Усилить работу по реагированию на ЧС: a) внедрить процедуру «Crisis Management System»; b) провести обучение ответственных работников по действиям в условиях ЧС; c) провести не менее двух заседаний штабов.',
docs:[],dname:'Приказ CMS / Материалы обучения / Акты штабов',
ai:'Приказ CMS подписан. Обучение — 60%. Одно заседание штаба проведено.',
h:['01.03 — Мероприятие создано','01.04 — Приказ CMS','15.05 — Заседание штаба №1']},
{id:21,sec:3,b:0,s:'done',p:100,due:'31.12.2026',done:'15.02.2026',
r:'Директор ДПБ, Пресс-секретарь ЦА',
t:'Обеспечить выпуск обращения от Председателя Правления ПК о важности соблюдения требований по производственной безопасности.',
docs:[],dname:'Публикация на информационных порталах',
ai:'Обращение опубликовано. Охват — 100% персонала. Задача выполнена досрочно.',
h:['15.01 — Проект обращения','01.02 — Подписание','15.02 — Публикация']},
{id:22,sec:3,b:0,s:'wait',p:15,due:'31.12.2026',done:'—',
r:'Директор ДПБ, Генеральные директора филиалов и ДАО / Департамент по коммуникациям',
t:'Проведение мероприятий по производственной безопасности: a) стратегических сессий/Форумов для первых руководителей филиалов/ДАО; b) семинаров для подрядных организаций ПК на отдельных площадках филиалов/ДАО.',
docs:[],dname:'Протоколы форумов / Протоколы семинаров',
ai:'Форум запланирован на октябрь. Семинары — 2 площадки определены.',
h:['01.05 — Мероприятие создано','01.06 — Определены площадки']},
{id:23,sec:3,b:6,s:'wait',p:10,due:'30.09.2026',done:'—',
r:'Директор ДПБ',
t:'Проведение Олимпиады по производственной безопасности среди специалистов производственной безопасности Общества и подрядных организаций на уровне Общества.',
docs:[],dname:'Протокол итогов Олимпиады',
ai:'Положение об Олимпиаде на согласовании. Рекомендуется начать подготовку.',
h:['01.05 — Мероприятие создано','01.06 — Проект положения']},
{id:24,sec:3,b:0,s:'done',p:92,due:'31.12.2026',done:'—',
r:'Директор ДПБ, Генеральные директора филиалов и ДАО',
t:'Обеспечить ознакомление всех работников филиалов/ДАО Общества с обстоятельствами несчастных случаев с тяжелым и летальным исходами, произошедших в ПК Фонда, посредством направления информационных бюллетеней, в том числе с использованием цифровых решений либо в рамках внеплановых инструктажей.',
docs:[],dname:'Бюллетени / Листы ознакомления',
ai:'Разослано 3 бюллетеня. Ознакомление — 92% персонала. Рекомендация: 100% к Q3.',
h:['01.01 — Мероприятие создано','15.02 — Бюллетень №1','01.05 — Бюллетень №3']},
{id:25,sec:3,b:6,s:'warn',p:40,due:'31.12.2026',done:'—',
r:'a) Управляющий директор по персоналу, Департамент по коммуникациям / b,c,d) Директор ДПБ, Генеральные директора филиалов и ДАО',
t:'Проведение молодежных проектных инициатив в рамках работы Центра молодых работников по производственной безопасности: a) публикация историй в журнале «SK News»; b) посещение рабочих мест с НС за 2022-2025 гг.; c) привлечение молодых специалистов в перекрёстные аудиты; d) онлайн-семинары/прямые эфиры.',
docs:[],dname:'Публикации SK News / Материалы мероприятий',
ai:'Опубликовано 2 истории. Проведён 1 выезд. 3 молодых специалиста в аудитах.',
h:['01.02 — Мероприятие создано','15.03 — Публикация №1','01.05 — Выезд']},
{id:26,sec:3,b:2,s:'warn',p:50,due:'31.12.2026',done:'—',
r:'a) Генеральные директора филиалов и ДАО / b,c) Директор ДПБ, Департамент по коммуникациям / d) Директор ДПБ',
t:'Усилить наглядную агитацию по производственной безопасности: a) разработка видеороликов/презентаций; b) серии видеороликов «Безопасность будущего»; c) выпуск подкаста с участием трудовых династий; d) разработка постеров, брошюр, информационных рассылок.',
docs:[],dname:'Видеоролики / Постеры / Брошюры',
ai:'Снято 2 видеоролика. Постеры распространены. Подкаст в разработке.',
h:['01.02 — Мероприятие создано','01.04 — Видеоролик №1','01.06 — Видеоролик №2']},
{id:27,sec:3,b:3,s:'warn',p:30,due:'31.12.2026',done:'—',
r:'Директор ДПБ, Генеральные директора филиалов и ДАО',
t:'Рассмотрение возможности организации встреч коллектива с получившими производственные травмы работниками (с их согласия) с целью предупреждения аналогичных случаев травматизма.',
docs:[],dname:'Фотофиксация',
ai:'Проведена 1 встреча. Получены согласия от 3 работников. Рекомендуется расширить.',
h:['01.03 — Мероприятие создано','01.05 — Встреча проведена']},
{id:28,sec:3,b:0,s:'warn',p:25,due:'31.12.2026',done:'—',
r:'Директор ДПБ, Генеральные директора филиалов и ДАО / Департамент по коммуникациям',
t:'Проведение мероприятий, направленных на пропаганду безопасного выполнения работ через семейные ценности: a) направление информационного письма членам семьи отличившегося работника; b) проведение Семейных дней охраны труда; c) проведение конкурса рисунков на тему «Спецодежда будущего!».',
docs:[],dname:'Информационное письмо / Пресс-релизы',
ai:'Направлено 5 писем семьям. Семейный день — август. Конкурс анонсирован.',
h:['01.04 — Мероприятие создано','15.05 — Письма семьям']},
{id:29,sec:3,b:6,s:'late',p:40,due:'30.06.2026',done:'—',
r:'Директор ДПБ, Генеральные директора филиалов и ДАО',
t:'Разработать корпоративный сборник лучших практик по производственной безопасности в формате методического пособия или интерактивного PDF документа, отражающий меры по снижению производственного травматизма и управлению критическими рисками.',
docs:[],dname:'Корпоративный сборник лучших практик',
ai:'Сборник в разработке. Опрошено 4 из 8 филиалов. Риск срыва срока Q2 — 26 дней.',
h:['01.03 — Мероприятие создано','01.05 — Опрошены 4 филиала','01.06 — Эскалация']},
{id:30,sec:3,b:7,s:'warn',p:60,due:'31.12.2026',done:'—',
r:'Директор ДПБ, Генеральные директора филиалов и ДАО',
t:'Сбор предложений по совершенствованию системы управления производственной безопасности посредством применения цифровых решений с консолидацией в ДПБ.',
docs:[],dname:'Предложения / План реализации',
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:'—',
r:'Директор ДПБ, Генеральные директора филиалов и ДАО',
t:'Разработка видеообзора кейсов происшествий в ПК с учетом специфики деятельности (из доступных на открытых медиа источниках) для наглядной демонстрации и разъяснения работникам о необходимости и важности соблюдения требований безопасности.',
docs:[],dname:'Видеообзор',
ai:'Видеообзор смонтирован на 75%. Озвучка — 15 июня. Укладываемся в Q2.',
h:['01.03 — Мероприятие создано','01.05 — Сценарий','01.06 — Монтаж 75%']},
{id:32,sec:4,b:8,s:'warn',p:70,due:'30.06.2026',done:'—',
r:'Директор ДПБ, Генеральные директора филиалов и ДАО',
t:'Обеспечить применение в филиалах/ДАО Общества чат-бот ИИ ассистент по производственной безопасности с целью упрощения доступа к нормативно-правовым актам Республики Казахстан и ВНД группы Фонда.',
docs:[],dname:'Справка / Скриншоты чат-бота',
ai:'Чат-бот разработан, тестируется. База НПА загружена. Пилот — 15 июня.',
h:['01.02 — Мероприятие создано','01.04 — Разработка','01.06 — Тестирование']},
{id:33,sec:4,b:8,s:'wait',p:15,due:'31.12.2026',done:'—',
r:'Директор ДПБ, Генеральные директора филиалов и ДАО',
t:'Обеспечить применение в филиалах/ДАО Общества интегрированную систему анализа и предупреждения несчастных случаев и критических происшествий, а также платформу по идентификации и оценке рисков перед началом проведения работ на опасных производственных объектах.',
docs:[],dname:'Справка / Скриншоты системы',
ai:'ТЗ согласовывается. Интеграция с платформой Фонда прорабатывается.',
h:['01.04 — Мероприятие создано','01.06 — ТЗ на согласовании']},
{id:34,sec:4,b:8,s:'wait',p:10,due:'31.12.2026',done:'—',
r:'Директор ДПБ, Генеральные директора филиалов и ДАО',
t:'Рассмотреть возможность запуска в филиалах/ДАО Общества электронного HSE паспорта на каждого работника с последующей интеграцией в корпоративную цифровую систему.',
docs:[],dname:'Справка / Скриншоты HSE паспорта',
ai:'Концепция утверждена. Выбран подрядчик. Интеграция — Q3.',
h:['01.05 — Мероприятие создано','01.06 — Концепция утверждена']},
{id:35,sec:4,b:5,s:'wait',p:8,due:'31.12.2026',done:'—',
r:'Директор ДПБ, Генеральные директора филиалов и ДАО',
t:'Рассмотреть возможность внедрения системы оформления нарядов-допусков на проведение работ повышенной опасности в электронном виде в филиалах/ДАО Общества.',
docs:[],dname:'Справка / Скриншоты системы',
ai:'Предпроектная стадия. Анализ рынка проведён. Выбор платформы — Q3.',
h:['01.05 — Мероприятие создано','01.06 — Анализ рынка']}
]
// ===== USER SYSTEM =====
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
let events = JSON.parse(JSON.stringify(baseEvents))
function loadState(){
const saved = localStorage.getItem('samruk_events')
if(saved) events = JSON.parse(saved)
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()
}
// ===== NOTIFICATIONS =====
function getNotifs(){
const myEvs = currentUser.role === 'admin' || currentUser.role === 'director'
? events : events.filter(e => e.b === currentUser.branch)
const notifs = []
myEvs.forEach(e => {
if(e.s === 'late') notifs.push({type:'danger', msg:`Просрочено: ${e.t.slice(0,60)}...`, time: e.due})
if(e.s === 'warn' && e.p < 30) notifs.push({type:'warn', msg:`Низкий прогресс (${e.p}%): ${e.t.slice(0,50)}...`, time: 'Сейчас'})
})
return notifs
}
function renderNotifs(){
const notifs = getNotifs()
const el = document.getElementById('notifDrop')
const cnt = document.getElementById('notifCount')
cnt.textContent = notifs.length
cnt.style.display = notifs.length ? 'inline-block' : 'none'
if(!notifs.length){ el.innerHTML = '<div class="empty">Новых уведомлений нет</div>'; return }
el.innerHTML = notifs.map(n => `<div class="item">
<div class="title">${n.type==='danger'?'🔴':'🟡'} ${n.msg}</div>
<div class="time">${n.time}</div></div>`).join('')
}
function toggleNotif(){
renderNotifs()
document.getElementById('notifDrop').classList.toggle('open')
}
// ===== EVENTS HELPER =====
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>`}
// ===== MY EVENTS =====
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 statusF = document.getElementById('myStatusFilter').value
let list = getMyEvents()
if(statusF) list = list.filter(e => e.s === statusF)
const done = list.filter(e=>e.s==='done').length
const late = list.filter(e=>e.s==='late').length
const warn = list.filter(e=>e.s==='warn').length
const 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('')}`
}
// ===== ALL EVENTS =====
function renderAllEvents(){
const search = document.getElementById('allSearch').value.toLowerCase()
const statusF = document.getElementById('allStatusFilter').value
const 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(statusF) list = list.filter(e => e.s === statusF)
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('')}`
}
// ===== ANALYTICS =====
function renderAnalytics(){
let done = events.filter(e=>e.s==='done').length
let late = events.filter(e=>e.s==='late').length
let 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">${late}</div></div>`
document.getElementById('analyticsContent').innerHTML = `<h3>Рейтинг дивизионов</h3>
${branches.map((b,i) => {
let items = events.filter(e => e.b === i)
let d = items.filter(e=>e.s==='done').length
let 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('')}`
}
// ===== EDIT MODAL =====
function openEdit(id){
const e = events.find(x => x.id === id)
if(!e) return
// Load saved edits from this user
const savedEdits = JSON.parse(localStorage.getItem('samruk_edits_'+e.id) || '{}')
// Generate file list from saved file metadata
const savedFiles = JSON.parse(localStorage.getItem('samruk_files_'+e.id) || '[]')
const fileList = savedFiles.map((f,i) => `
<span class="doc-chip">📄 ${f.name} (${(f.size/1024).toFixed(0)} КБ)
<button onclick="removeFile(${e.id},${i})" title="Удалить">×</button></span>`).join('')
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>
<div style="font-weight:600;margin-bottom:4px;font-size:14px">Подтверждающие материалы</div>
${fileList ? `<div class="docs">${fileList}</div>` : '<p style="font-size:13px;color:var(--gray-500);margin-bottom:8px">Файлы не загружены</p>'}
<div class="field">
<label>Загрузить файл</label>
<input type="file" id="editFileInput" multiple style="width:100%;padding:8px;border:1px solid var(--gray-200);border-radius:8px;font-size:14px">
<p style="font-size:11px;color:var(--gray-500);margin-top:4px">Файлы сохраняются локально в браузере для демонстрации. Формы завершения: ${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 autoProgress(){
const s = document.getElementById('editStatus').value
const 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
const newStatus = document.getElementById('editStatus').value
const newProgress = parseInt(document.getElementById('editProgress').value)
const comment = document.getElementById('editComment').value.trim()
// Save files from input
const fileInput = document.getElementById('editFileInput')
if(fileInput.files.length > 0){
const existing = JSON.parse(localStorage.getItem('samruk_files_'+id) || '[]')
for(const f of fileInput.files){
existing.push({name: f.name, size: f.size, type: f.type, date: new Date().toLocaleDateString()})
}
localStorage.setItem('samruk_files_'+id, JSON.stringify(existing))
}
// Add history entry
const now = new Date().toLocaleDateString()
e.h.push(`${now}${currentUser.name}: статус ${statusMap[newStatus]}, прогресс ${newProgress}%${comment?' — комм.: '+comment:''}`)
e.s = newStatus
e.p = newProgress
if(newStatus === 'done' && e.done === '—') e.done = now
// Save edits
localStorage.setItem('samruk_edits_'+id, JSON.stringify({comment, editedBy: currentUser.name, editedAt: now}))
saveState()
closeEditModal()
renderAll()
renderNotifs()
}
function removeFile(eventId, fileIdx){
const files = JSON.parse(localStorage.getItem('samruk_files_'+eventId) || '[]')
files.splice(fileIdx, 1)
localStorage.setItem('samruk_files_'+eventId, JSON.stringify(files))
closeEditModal()
openEdit(eventId)
}
function closeEditModal(){ document.getElementById('editModalOverlay').classList.remove('open') }
// ===== TABS =====
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()
})
})
// ===== CLOSE MODAL =====
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') })
// ===== INIT =====
function renderAll(){
renderMyEvents()
renderAllEvents()
renderNotifs()
}
loadState()
if(currentUser) showApp()
</script>
</body>
</html>