v4: 35 мероприятий из реального плана Казахтелеком ПБ-2026

This commit is contained in:
Dauren777 2026-06-04 10:04:21 +00:00
parent ad4c3efc89
commit fe8388f47c

View File

@ -3,15 +3,13 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>ИИ-агент мониторинга ПБ — АО «Самрук-Казына»</title>
<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;--purple:#8B5CF6;--sidebar-w:260px;--topbar-h:60px}
: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);display:flex;min-height:100vh}
a{color:var(--cyan);text-decoration:none}
/* SIDEBAR */
.sidebar{width:var(--sidebar-w);background:var(--ink);color:var(--white);position:fixed;top:0;left:0;bottom:0;z-index:100;display:flex;flex-direction:column;overflow-y:auto}
.sidebar .logo{padding:20px 24px;font-size:18px;font-weight:800;border-bottom:1px solid rgba(255,255,255,.1);line-height:1.3}
.sidebar .logo{padding:20px 24px;font-size:17px;font-weight:800;border-bottom:1px solid rgba(255,255,255,.1);line-height:1.3}
.sidebar .logo span{color:var(--cyan)}
.sidebar nav{padding:16px 0;flex:1}
.sidebar nav a{display:flex;align-items:center;gap:12px;padding:12px 24px;color:#9aa3b2;font-weight:500;transition:.15s;font-size:15px;cursor:pointer}
@ -19,86 +17,60 @@ a{color:var(--cyan);text-decoration:none}
.sidebar nav a.active{border-right:3px solid var(--cyan)}
.sidebar nav a .ico{font-size:18px;width:24px;text-align:center}
.sidebar .user{padding:20px 24px;border-top:1px solid rgba(255,255,255,.1);font-size:13px;color:#6b7280}
/* MAIN */
.main{margin-left:var(--sidebar-w);flex:1;padding:24px 32px;max-width:calc(100vw - var(--sidebar-w))}
/* PAGE */
.page{display:none}
.page.active{display:block}
.page h2{font-size:24px;font-weight:700;margin-bottom:20px}
/* STATS ROW */
.stats-row{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:16px;margin-bottom:24px}
.stats-row{display:grid;grid-template-columns:repeat(auto-fit,minmax(210px,1fr));gap:16px;margin-bottom:24px}
.stat-card{background:var(--white);border-radius:12px;padding:20px 24px;border:1px solid var(--gray-200)}
.stat-card .stat-label{font-size:13px;color:var(--gray-500);margin-bottom:4px}
.stat-card .stat-num{font-size:32px;font-weight:800;line-height:1}
.stat-card .stat-sub{font-size:13px;color:var(--gray-500);margin-top:4px}
.stat-card.red .stat-num{color:var(--red)}
.stat-card.green .stat-num{color:var(--green)}
.stat-card.amber .stat-num{color:var(--amber)}
.stat-card.blue .stat-num{color:var(--blue)}
/* GRID 2col */
.stat-card .lbl{font-size:13px;color:var(--gray-500);margin-bottom:4px}
.stat-card .num{font-size:32px;font-weight:800;line-height:1}
.stat-card .sub{font-size:13px;color:var(--gray-500);margin-top:4px}
.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)}
.grid2{display:grid;grid-template-columns:1fr 1fr;gap:24px;margin-bottom:24px}
.grid3{display:grid;grid-template-columns:1fr 1fr 1fr;gap:24px;margin-bottom:24px}
/* PANEL */
.panel{background:var(--white);border-radius:12px;border:1px solid var(--gray-200);padding:24px}
.panel h3{font-size:17px;font-weight:700;margin-bottom:16px}
/* CHART STUB */
.chart-stub{height:200px;background:var(--gray-100);border-radius:8px;display:flex;align-items:flex-end;gap:8px;padding:16px 16px 8px}
.chart-stub .bar{flex:1;background:var(--cyan);border-radius:4px 4px 0 0;min-height:4px;transition:.3s}
.chart-stub .bar.danger{background:var(--red)}
.chart-stub .bar.warn{background:var(--amber)}
.chart-stub .bar.good{background:var(--green)}
.chart-stub .bar.blue{background:var(--blue)}
.chart-stub .bar{flex:1;background:var(--cyan);border-radius:4px 4px 0 0;min-height:4px}
.chart-stub .bar.red{background:var(--red)}.chart-stub .bar.amber{background:var(--amber)}.chart-stub .bar.green{background:var(--green)}
.chart-labels{display:flex;gap:8px;padding:8px 16px 0;font-size:11px;color:var(--gray-500);text-align:center}
.chart-labels span{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* RING */
.ring{position:relative;width:120px;height:120px;margin:0 auto 12px}
.ring svg{transform:rotate(-90deg)}
.ring .pct{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;font-size:28px;font-weight:800}
.ring-label{text-align:center;font-size:14px;color:var(--gray-500)}
/* TABLE */
table{width:100%;border-collapse:collapse}
th,td{padding:12px 16px;text-align:left;font-size:14px}
th{font-weight:600;color:var(--gray-500);font-size:12px;text-transform:uppercase;letter-spacing:.5px;border-bottom:2px solid var(--gray-200)}
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)}
/* BADGE */
.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)}
/* FILTERS */
.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)}
.filters{display:flex;gap:12px;margin-bottom:20px;flex-wrap:wrap;align-items:center}
.filters select,.filters input{padding:10px 14px;border:1px solid var(--gray-200);border-radius:8px;font-size:14px;background:var(--white);min-width:160px}
.filters input{min-width:260px}
.filters input{min-width:280px}
.filters .count{font-size:13px;color:var(--gray-500);margin-left:auto}
/* AI BADGE */
.ai-tag{display:inline-flex;align-items:center;gap:6px;background:var(--cyan-50);color:var(--ink);padding:4px 10px;border-radius:6px;font-size:12px;font-weight:600}
/* 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:800px;width:90vw;max-height:85vh;overflow-y:auto;padding:32px}
.modal h2{font-size:22px;margin-bottom:8px}
.modal .close{float:right;background:none;border:none;font-size:24px;cursor:pointer;line-height:1;color:var(--gray-500)}
.modal{background:var(--white);border-radius:16px;max-width:860px;width:92vw;max-height:85vh;overflow-y:auto;padding:32px}
.modal h2{font-size:21px;margin-bottom:8px;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 .meta{display:grid;grid-template-columns:1fr 1fr;gap:12px;margin:20px 0}
.modal .meta .field{font-size:13px;color:var(--gray-500)}
.modal .meta .field strong{display:block;font-size:15px;color:var(--ink);margin-top:2px}
.modal .meta .fld{font-size:13px;color:var(--gray-500)}
.modal .meta .fld strong{display:block;font-size:15px;color:var(--ink);margin-top:2px}
.modal .docs{display:flex;gap:8px;flex-wrap:wrap;margin:12px 0}
.modal .docs span{background:var(--gray-100);padding:6px 12px;border-radius:6px;font-size:13px}
.modal .ai-block{background:var(--cyan-50);border-radius:8px;padding:16px;margin:16px 0;font-size:14px}
.modal .ai-block h4{font-size:14px;margin-bottom:6px;color:var(--ink)}
.modal .ai-block h4{font-size:14px;margin-bottom:6px}
.modal .timeline{font-size:13px;color:var(--gray-500);line-height:2}
.modal .timeline strong{color:var(--ink)}
/* RATINGS */
.rating-bar{display:flex;align-items:center;gap:12px;padding:8px 0}
.rating-bar .name{width:180px;font-size:14px;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.rating-bar .name{width:200px;font-size:14px;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.rating-bar .track{flex:1;height:10px;background:var(--gray-200);border-radius:10px;overflow:hidden}
.rating-bar .fill{height:100%;border-radius:10px;transition:.5s}
/* RESPONSIVE */
.rating-bar .fill{height:100%;border-radius:10px}
.timeline-item{display:flex;gap:8px;font-size:13px;padding:3px 0;color:var(--gray-500);align-items:baseline}
.timeline-item .dot{width:8px;height:8px;border-radius:50%;background:var(--cyan);flex-shrink:0;margin-top:6px}
.section-tag{display:inline-block;padding:4px 8px;border-radius:4px;font-size:11px;font-weight:600;margin-right:6px;background:var(--gray-100);color:var(--gray-700)}
@media(max-width:900px){
.sidebar{width:60px}
.sidebar .logo,.sidebar nav a span,.sidebar .user{display:none}
.sidebar{width:60px}.sidebar .logo,.sidebar nav a span,.sidebar .user{display:none}
.sidebar nav a{justify-content:center;padding:12px 0;font-size:20px}
.main{margin-left:60px;max-width:calc(100vw - 60px);padding:16px}
.grid2,.grid3{grid-template-columns:1fr}
@ -107,283 +79,439 @@ tr:hover td{background:var(--cyan-50)}
</style>
</head>
<body>
<!-- SIDEBAR -->
<aside class="sidebar">
<div class="logo"><span>ИИ-Агент</span><br>Самрук-Казына</div>
<div class="logo"><span>ИИ-Агент</span><br>Казахтелеком</div>
<nav>
<a class="active" data-page="dashboard"><span class="ico">📊</span> <span>Дашборд</span></a>
<a data-page="register"><span class="ico">📋</span> <span>Реестр мероприятий</span></a>
<a data-page="register"><span class="ico">📋</span> <span>Реестр (35)</span></a>
<a data-page="analytics"><span class="ico">📈</span> <span>Аналитика</span></a>
<a data-page="reports"><span class="ico">📑</span> <span>Отчёты</span></a>
<a data-page="risks"><span class="ico">⚠️</span> <span>Карта рисков</span></a>
</nav>
<div class="user">Инспектор ПБ • АО «Самрук-Казына»</div>
<div class="user">Директор ДПБ • АО «Казахтелеком»</div>
</aside>
<!-- MAIN CONTENT -->
<main class="main" id="mainContent"></main>
<!-- MODAL -->
<div class="modal-overlay" id="modalOverlay">
<div class="modal" id="modalContent"></div>
</div>
<div class="modal-overlay" id="modalOverlay"><div class="modal" id="modalContent"></div></div>
<script>
// ===== MOCK DATA =====
const branches = ['АО "КазТрансОйл"','АО "KEGOC"','АО "Казпочта"','АО "НК КТЖ"','АО "Казатомпром"','АО "Эйр Астана"','АО "QazaqGaz"','ТОО "Самрук-Энерго"']
const statuses = {done:'Исполнено',warn:'На контроле',late:'Просрочено',wait:'Не начато'}
const directions = ['Охрана труда','Пожарная безопасность','Промбезопасность','Экология','ГО и ЧС','Транспортная безопасность']
const events = [
{id:1, title:'Проведение внепланового инструктажа по охране труда', branch:branches[0], dir:directions[0], status:'done', progress:100, due:'15.02.2026', done:'12.02.2026', resp:'Ахметов К.Т.', docs:['Приказ №45','Журнал инструктажа','Фотоотчёт'], ai:'Все документы в наличии. Инструктаж проведён в срок. Замечаний нет.', history:['10.02 — Мероприятие создано','12.02 — Загружен журнал инструктажа','12.02 — Статус: Исполнено'] },
{id:2, title:'Замена средств пожаротушения на складе ГСМ', branch:branches[1], dir:directions[1], status:'warn', progress:65, due:'30.06.2026', done:'—', resp:'Сериков А.М.', docs:['Смета','Договор поставки'], ai:'Отсутствует акт выполненных работ. Рекомендуется ускорить монтаж. Срок под риском.', history:['05.03 — Мероприятие создано','20.04 — Загружен договор','01.06 — ИИ: риск срыва срока'] },
{id:3, title:'Аттестация сварщиков по промышленной безопасности', branch:branches[4], dir:directions[2], status:'late', progress:30, due:'01.04.2026', done:'—', resp:'Касымов Б.Е.', docs:[], ai:'Просрочка 64 дня. Подтверждающие документы отсутствуют. Эскалация руководителю.', history:['01.01 — Мероприятие создано','01.04 — Срок истёк','05.04 — Эскалация руководителю'] },
{id:4, title:'Экологический аудит очистных сооружений', branch:branches[6], dir:directions[3], status:'done', progress:100, due:'01.03.2026', done:'26.02.2026', resp:'Нурланов Д.С.', docs:['Акт аудита','Лабораторные пробы','Фото'], ai:'Аудит пройден. Лабораторные показатели в норме. Рекомендации выполнены.', history:['10.01 — Мероприятие создано','20.02 — Загружены пробы','26.02 — Статус: Исполнено'] },
{id:5, title:'Обучение персонала по ГО и ЧС', branch:branches[7], dir:directions[4], status:'warn', progress:45, due:'01.08.2026', done:'—', resp:'Тулегенов Е.А.', docs:['Программа обучения'], ai:'Утверждена программа. Не загружены списки групп. Рекомендуется начать обучение.', history:['01.04 — Мероприятие создано','15.05 — Утверждена программа','01.06 — ИИ: ожидается список групп'] },
{id:6, title:'Проверка состояния ж/д переездов', branch:branches[3], dir:directions[5], status:'done', progress:100, due:'10.05.2026', done:'05.05.2026', resp:'Искаков Р.Н.', docs:['Акт осмотра','Предписание','Фото'], ai:'Проверка проведена. Выявлено 3 замечания. Устранены в срок.', history:['01.04 — Мероприятие создано','28.04 — Акт осмотра','05.05 — Статус: Исполнено'] },
{id:7, title:'Монтаж системы оповещения на производстве', branch:branches[2], dir:directions[1], status:'wait', progress:0, due:'01.12.2026', done:'—', resp:'Сапаров А.Д.', docs:[], ai:'Мероприятие не начато. До срока 180 дней. Рекомендуется назначить поставщика.', history:['01.06 — Мероприятие создано'] },
{id:8, title:'Медицинский осмотр работников вредных производств', branch:branches[5], dir:directions[0], status:'done', progress:100, due:'01.04.2026', done:'28.03.2026', resp:'Алиева Г.С.', docs:['Приказ','Заключения врачей','Отчёт'], ai:'Осмотр пройден. 100% охват. Противопоказаний не выявлено.', history:['01.03 — Мероприятие создано','25.03 — Загружены заключения','28.03 — Статус: Исполнено'] },
{id:9, title:'Ремонт ограждения опасной зоны на участке №3', branch:branches[0], dir:directions[2], status:'late', progress:20, due:'15.03.2026', done:'—', resp:'Маратов Ж.К.', docs:[], ai:'Просрочено 81 день. Отсутствует проектная документация. Требуется вмешательство.', history:['15.01 — Мероприятие создано','15.03 — Срок истёк','20.03 — Эскалация'] },
{id:10, title:'Закуп СИЗ для полевых бригад', branch:branches[1], dir:directions[0], status:'warn', progress:80, due:'15.07.2026', done:'—', resp:'Омарова Д.Т.', docs:['Спецификация','Счёт-фактура'], ai:'СИЗ закуплены. Ожидается поставка. Рисков срыва нет.', history:['01.05 — Мероприятие создано','01.06 — Счёт оплачен'] },
{id:11, title:'Пересмотр плана ликвидации аварий', branch:branches[4], dir:directions[4], status:'warn', progress:50, due:'01.09.2026', done:'—', resp:'Касымов Б.Е.', docs:['Проект ПЛА'], ai:'Проект ПЛА на согласовании. Замечаний от проверяющих нет.', history:['01.04 — Мероприятие создано','20.05 — Проект загружен'] },
{id:12, title:'Проверка дымовых извещателей в админздании', branch:branches[7], dir:directions[1], status:'done', progress:100, due:'01.05.2026', done:'28.04.2026', resp:'Тулегенов Е.А.', docs:['Акт проверки','Сертификаты'], ai:'Все извещатели исправны. Сертификаты действительны.', history:['01.04 — Мероприятие создано','28.04 — Статус: Исполнено'] },
// ===== 35 МЕРОПРИЯТИЙ ПЛАНА ПБ КАЗАХТЕЛЕКОМ НА 2026 =====
const sections = [
'I. Люди. Повышение культуры безопасности',
'II. Безопасность при эксплуатации оборудования',
'III. Предупреждение и готовность к ликвидации аварий и ЧС',
'IV. Информационно-разъяснительная работа',
'V. Внедрение ИИ и цифровизации'
]
const branches = [
'Центральный аппарат','Алматинский филиал','Актюбинский филиал',
'Карагандинский филиал','ЮКО филиал','ВКО филиал','ЗКО филиал','Атырауский филиал'
]
const statusMap = {done:'Исполнено',warn:'На контроле',late:'Просрочено',wait:'Не начато'}
// real plan data — 35 events
const E = [
// I. ЛЮДИ (1-9)
{id:1, sec:0, t:'Обучение и повышение квалификации работников (VR, AR, цифровые симуляторы)', b:0, d:sections[0], s:'warn', p:45, due:'31.12.2026', done:'—',
r:'Генеральный директор КУ / Директора филиалов и ДАО',
docs:['Протоколы обучения','Электронная ведомость'],
ai:'Обучение ведётся по графику. Охвачено 45% персонала. VR-тренажёры развёрнуты в 3 филиалах. Рисков срыва нет.',
h:['15.01 — Мероприятие создано','01.03 — Запущено обучение в Алматинском филиале','15.05 — VR-симуляторы установлены в 3 филиалах']},
{id:2, sec:0, t:'Анализ и пересмотр ВНД согласно Стратегии развития ПБ на 2024-2028 гг.', b:0, d:sections[0], s:'done', p:100, due:'31.03.2026', done:'28.03.2026',
r:'Директор ДПБ / Директор ДИТ / Директора филиалов',
docs:['Отчёт о проведённом анализе','Утверждённый ВНД'],
ai:'Анализ завершён в срок. ВНД пересмотрены. Ключевые показатели ПБ установлены. Замечаний нет.',
h:['10.01 — Мероприятие создано','15.02 — Проведён анализ ВНД','28.03 — Отчёт утверждён']},
{id:3, sec:0, t:'Тематические совещания по вопросам ПБ (руководство–филиалы, филиалы–СП, филиалы–подрядчики)', b:0, d:sections[0], s:'warn', p:50, due:'31.12.2026', done:'—',
r:'Главный административный директор / Директор ДПБ',
docs:['Протоколы совещаний (a, b, c)'],
ai:'Проведено 2 квартальных совещания руководства с филиалами. Ежемесячные совещания филиалов со СП — выполняется. Совещания с подрядчиками — график соблюдается.',
h:['10.01 — Мероприятие создано','15.02 — Совещание Q1 проведено','15.05 — Совещание Q2 проведено']},
{id:4, sec:0, t:'Проверка знаний в формате тестирования после инструктажей по ОТ', b:1, d:sections[0], s:'warn', p:55, due:'31.12.2026', done:'—',
r:'Директора филиалов и ДАО',
docs:['Отчёты о проделанной работе','Тесты для оценки знаний'],
ai:'Тестирование внедрено в 6 филиалах. Средний результат — 82%. Рекомендуется усилить тесты по пожарной безопасности.',
h:['01.02 — Мероприятие создано','01.04 — Внедрено тестирование','01.06 — Промежуточный отчёт']},
{id:5, sec:0, t:'Нематериальное поощрение филиалов/ДАО со снижением НС, пожаров и аварий', b:0, d:sections[0], s:'done', p:100, due:'31.03.2026', done:'25.03.2026',
r:'Директор ДПБ / Директора филиалов и ДАО',
docs:['Информация о нематериальном поощрении'],
ai:'Положение о поощрении утверждено. Определены 3 филиала-лидера за 2023-2025 гг. Поощрение доведено до коллективов.',
h:['15.01 — Проект положения','01.03 — Согласование','25.03 — Утверждено']},
{id:6, sec:0, t:'Разработка ВНД по внутренним тренерам (отбор, подготовка, доплата)', b:0, d:sections[0], s:'warn', p:60, due:'30.06.2026', done:'—',
r:'Генеральный директор КУ / Управляющий директор по персоналу',
docs:['ВНД по внутренним тренерам','Перечень внутренних тренеров'],
ai:'Проект ВНД на финальном согласовании. Перечень тренеров сформирован (12 чел). Риск: доплаты требуют бюджета.',
h:['01.03 — Мероприятие создано','15.04 — Проект ВНД','01.06 — Перечень тренеров']},
{id:7, sec:0, t:'Обмен опытом в области ПБ (Комитет HSE, выезды, онлайн-семинары)', b:2, d:sections[0], s:'warn', p:40, due:'31.12.2026', done:'—',
r:'Директор ДПБ / Директора филиалов и ДАО',
docs:['Материалы обмена опытом'],
ai:'Проведён 1 выезд на площадку KEGOC. Онлайн-семинар с иностранной компанией запланирован на июль. Активность средняя.',
h:['15.02 — Мероприятие создано','01.04 — Выезд на KEGOC','15.05 — План семинаров']},
{id:8, sec:0, t:'Анализ эффективности охраны здоровья (медосмотры, Well-being Week, скрининг, микротравмы)', b:0, d:sections[0], s:'wait', p:15, due:'30.09.2026', done:'—',
r:'Директор ДПБ / Управляющий директор по персоналу',
docs:['Заключительный Акт','Программа Well-being','Отчёт о скрининге','Отчёт о микротравмах'],
ai:'Медосмотры по графику — Q3. Well-being Week запланирован на сентябрь. Алгоритм микротравм в разработке. Ранний этап.',
h:['01.04 — Мероприятие создано','01.06 — Проект алгоритма микротравм']},
{id:9, sec:0, t:'Участие в международных/национальных конкурсах профмастерства по ПБ', b:0, d:sections[0], s:'wait', p:20, due:'31.12.2026', done:'—',
r:'Директор ДПБ / Директора филиалов и ДАО',
docs:['Результаты конкурсов','Пакет материалов'],
ai:'Определены 2 конкурса для участия. Заявки готовятся. Срок — до конца года.',
h:['01.05 — Мероприятие создано','01.06 — Отобраны конкурсы']},
// II. БЕЗОПАСНОСТЬ ПРИ ЭКСПЛУАТАЦИИ ОБОРУДОВАНИЯ (10-18)
{id:10, sec:1, t:'Техническое перевооружение изношенного оборудования, зданий и сооружений', b:3, d:sections[1], s:'warn', p:55, due:'31.12.2026', done:'—',
r:'Генеральный директор ОДС / Директор СФ / Директор ДУП / Директор ДИТ',
docs:['Аналитическая справка по филиалам/ДАО'],
ai:'По плану 2024-2027. Заменено 55% единиц оборудования. Карагандинский филиал отстаёт на 12%. Рекомендуется усилить контроль.',
h:['01.01 — Переходящее с 2024','01.04 — Промежуточный отчёт','01.06 — 55% исполнения']},
{id:11, sec:1, t:'Пересмотр и актуализация нарядно-допускной системы (сертификаты безопасности)', b:5, d:sections[1], s:'warn', p:70, due:'30.06.2026', done:'—',
r:'Директор ДПБ / Директора филиалов и ДАО',
docs:['Переутверждённая процедура наряд-допусков','Фотоотчёт','Протоколы обучения'],
ai:'Процедура пересмотрена. Пилот сертификатов безопасности запущен в ВКО филиале. Обучение новых процедур — 85% персонала.',
h:['01.02 — Мероприятие создано','01.04 — Проект процедуры','15.05 — Пилот в ВКО']},
{id:12, sec:1, t:'Внедрение цифровой маркировки опасных техустройств (QR-коды)', b:4, d:sections[1], s:'wait', p:8, due:'30.09.2026', done:'—',
r:'Директора филиалов и ДАО',
docs:['Справка о внедрении','Фотоотчёт'],
ai:'Проект на стадии ТЭО. QR-коды не заказаны. Риск срыва срока — низкий, начало работ запланировано на июль.',
h:['01.05 — Мероприятие создано','01.06 — ТЭО в разработке']},
{id:13, sec:1, t:'Ежеквартальные проверки по проверочным листам БиОТ, пром- и пожарной безопасности', b:0, d:sections[1], s:'warn', p:50, due:'31.12.2026', done:'—',
r:'Директор ДПБ',
docs:['Акты проверок','График проверок'],
ai:'Q1 проверки завершены в 7 из 8 филиалов. Q2 — идёт по графику. Выявлено 23 нарушения, устранено 18.',
h:['01.01 — Мероприятие создано','31.03 — Q1 проверки','01.06 — Q2 начаты']},
{id:14, sec:1, t:'Участие в перекрёстных аудитах ПК согласно Плану-графику', b:0, d:sections[1], s:'warn', p:40, due:'31.12.2026', done:'—',
r:'Директор ДПБ',
docs:['Письмо о предоставлении кандидата'],
ai:'Назначены 4 аудитора от Казахтелеком. Участвовали в 2 аудитах KEGOC и КазТрансОйл. График соблюдается.',
h:['15.01 — Назначены аудиторы','01.03 — Аудит KEGOC','15.04 — Аудит КазТрансОйл']},
{id:15, sec:1, t:'Проактивные инструменты (поведенческие аудиты, Near Miss, право приостановки)', b:0, d:sections[1], s:'warn', p:48, due:'31.12.2026', done:'—',
r:'Директор ДПБ / Директора филиалов и ДАО',
docs:['Аналитическая справка','Журнал опережающих индикаторов'],
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, t:'Повышение эффективности управления подрядными организациями (аудиты, стартовые совещания)', b:1, d:sections[1], s:'done', p:85, due:'31.12.2026', done:'—',
r:'Директора филиалов и ДАО',
docs:['План-график перекрёстных аудитов','Акты проверки','Протоколы совещаний'],
ai:'Q1 — 12 подрядчиков проверено. Стартовые совещания — 100% охват перед допуском. Рекомендации: усилить контроль подрядчиков в Алматинском филиале.',
h:['15.01 — План-график','01.03 — Q1 проверки','01.06 — Q2 проверки начаты']},
{id:17, sec:1, t:'Контроль состояния ПБ на объектах (CEO-1, первые руководители филиалов)', b:6, d:sections[1], s:'warn', p:35, due:'31.12.2026', done:'—',
r:'Главный административный директор / Директор ДПБ',
docs:['Отчёты по результатам проверок','График проверок','Фотоотчёт'],
ai:'CEO-1 проверил 2 филиала (ЗКО, Атырау). Первые руководители филиалов — 6 выездов. Активность ниже плана. Рекомендуется график CEO-1 на Q3.',
h:['01.02 — Мероприятие создано','15.03 — Проверка ЗКО','01.05 — Проверка Атырау']},
{id:18, sec:1, t:'Контроль транспортной безопасности (ежемесячный мониторинг нарушений)', b:0, d:sections[1], s:'done', p:90, due:'31.12.2026', done:'—',
r:'Директор ДПБ / Директора филиалов и ДАО',
docs:['Ежемесячный сводный отчёт о нарушениях'],
ai:'Январь-май — 34 нарушения. Применены штрафные санкции к 12 водителям подрядчиков. Тренд на снижение (-15% к 2025).',
h:['01.01 — Мероприятие создано','01.02 — Отчёт январь','01.06 — Отчёт май']},
// III. ПРЕДУПРЕЖДЕНИЕ И ГОТОВНОСТЬ К ЛИКВИДАЦИИ АВАРИЙ И ЧС (19-20)
{id:19, sec:2, t:'Учебные тревоги и тренировки (аварии, пожары, первая помощь)', b:5, d:sections[2], s:'warn', p:30, due:'31.12.2026', done:'—',
r:'Управляющий директор по безопасности / Руководители ДАО',
docs:['Акты о проведении тренировок','Пресс-релизы'],
ai:'Проведено 1 учение по ликвидации аварии (ВКО). Пожарные тренировки: 1 из 2. Занятия по первой помощи запланированы на Q3. Необходимо ускорить.',
h:['01.02 — Мероприятие создано','15.03 — Учение ВКО','15.05 — Пожарная тренировка №1']},
{id:20, sec:2, t:'Усиление реагирования на ЧС (Crisis Management System, обучение, штабы)', b:0, d:sections[2], s:'warn', p:65, due:'30.06.2026', done:'—',
r:'Управляющий директор по безопасности / Директора филиалов',
docs:['Приказ о внедрении CMS','Материалы обучения','Акты заседаний штабов'],
ai:'Приказ о CMS подписан. Обучение проведено для 60% ответственных. Одно заседание штаба проведено, второе — в июне.',
h:['01.03 — Мероприятие создано','01.04 — Приказ CMS','15.05 — Заседание штаба №1']},
// IV. ИНФОРМАЦИОННО-РАЗЪЯСНИТЕЛЬНАЯ РАБОТА (21-31)
{id:21, sec:3, t:'Выпуск обращения Председателя Правления о важности соблюдения требований ПБ', b:0, d:sections[3], s:'done', p:100, due:'31.12.2026', done:'15.02.2026',
r:'Директор ДПБ / Пресс-секретарь ЦА',
docs:['Публикация на информационных порталах'],
ai:'Обращение опубликовано на корпоративном портале и в SK News. Охват — 100% персонала. Задача выполнена досрочно.',
h:['15.01 — Проект обращения','01.02 — Подписание','15.02 — Публикация']},
{id:22, sec:3, t:'Стратегические сессии/Форумы для первых руководителей и семинары для подрядчиков', b:0, d:sections[3], s:'wait', p:15, due:'31.12.2026', done:'—',
r:'Директор ДПБ / Департамент по коммуникациям',
docs:['Протоколы форумов/сессий','Протоколы семинаров'],
ai:'Форум запланирован на октябрь. Семинары для подрядчиков — 2 площадки определены. Начало подготовки.',
h:['01.05 — Мероприятие создано','01.06 — Определены площадки']},
{id:23, sec:3, t:'Олимпиада по производственной безопасности среди специалистов ПБ', b:0, d:sections[3], s:'wait', p:10, due:'30.09.2026', done:'—',
r:'Директор ДПБ',
docs:['Протокол итогов Олимпиады'],
ai:'Положение об Олимпиаде на согласовании. Задания не разработаны. Рекомендуется начать подготовку в июне.',
h:['01.05 — Мероприятие создано','01.06 — Проект положения']},
{id:24, sec:3, t:'Ознакомление работников с обстоятельствами НС с тяжёлым/летальным исходом', b:0, d:sections[3], s:'done', p:92, due:'31.12.2026', done:'—',
r:'Директор ДПБ / Директора филиалов и ДАО',
docs:['Информационные бюллетени','Листы ознакомления'],
ai:'За полугодие разослано 3 бюллетеня по НС в ПК Фонда. Ознакомление — 92% персонала. Рекомендация: догнать до 100% к Q3.',
h:['01.01 — Мероприятие создано','15.02 — Бюллетень №1','01.05 — Бюллетень №3']},
{id:25, sec:3, t:'Молодёжные проектные инициативы (SK News, выезды, аудиты, онлайн-эфиры)', b:1, d:sections[3], s:'warn', p:40, due:'31.12.2026', done:'—',
r:'Управляющий директор по персоналу / Директор ДПБ',
docs:['Публикации в SK News','Материалы мероприятий'],
ai:'Опубликовано 2 истории в SK News. Проведён 1 выезд молодых специалистов. 3 молодых специалиста привлечены в перекрёстные аудиты.',
h:['01.02 — Мероприятие создано','15.03 — Публикация №1','01.05 — Выезд']},
{id:26, sec:3, t:'Наглядная агитация (видеоролики, подкасты, постеры, брошюры)', b:2, d:sections[3], s:'warn', p:50, due:'31.12.2026', done:'—',
r:'Директор ДПБ / Департамент по коммуникациям',
docs:['Видеоролики','Постеры и брошюры'],
ai:'Снято 2 видеоролика («Безопасность будущего», профилактика ДТП). Постеры распространены. Подкаст в разработке. Хороший темп.',
h:['01.02 — Мероприятие создано','01.04 — Видеоролик №1','01.06 — Видеоролик №2']},
{id:27, sec:3, t:'Организация встреч коллектива с получившими производственные травмы', b:3, d:sections[3], s:'warn', p:30, due:'31.12.2026', done:'—',
r:'Директор ДПБ / Директора филиалов и ДАО',
docs:['Фотофиксация'],
ai:'Проведена 1 встреча в Карагандинском филиале. Получены согласия от 3 работников. Рекомендуется расширить на другие филиалы.',
h:['01.03 — Мероприятие создано','01.05 — Встреча в Караганде']},
{id:28, sec:3, t:'Пропаганда безопасности через семейные ценности (письма, Семейный день, конкурс рисунков)', b:4, d:sections[3], s:'warn', p:25, due:'31.12.2026', done:'—',
r:'Директор ДПБ / Департамент по коммуникациям',
docs:['Информационное письмо','Пресс-релизы'],
ai:'Направлено 5 писем семьям отличившихся. Семейный день ОТ запланирован на август. Конкурс рисунков анонсирован.',
h:['01.04 — Мероприятие создано','15.05 — Письма семьям']},
{id:29, sec:3, t:'Разработка корпоративного сборника лучших практик по ПБ', b:0, d:sections[3], s:'late', p:40, due:'30.06.2026', done:'—',
r:'Директор ДПБ / Директора филиалов и ДАО',
docs:['Корпоративный сборник лучших практик'],
ai:'Сборник в разработке. Опрошено 4 из 8 филиалов. Есть риск срыва срока Q2 — осталось 26 дней. Требуется эскалация.',
h:['01.03 — Мероприятие создано','01.05 — Опрошены 4 филиала','01.06 — Эскалация: риск срыва']},
{id:30, sec:3, t:'Сбор предложений по цифровым решениям для системы управления ПБ', b:0, d:sections[3], s:'warn', p:60, due:'31.12.2026', done:'—',
r:'Директор ДПБ / Директора филиалов и ДАО',
docs:['Предложения по улучшению','План реализации'],
ai:'Собрано 18 предложений. 5 приняты в реализацию. Идёт консолидация в ДПБ. Активность хорошая.',
h:['01.01 — Мероприятие создано','01.04 — 10 предложений','01.06 — 18 предложений']},
{id:31, sec:3, t:'Разработка видеообзора кейсов происшествий в ПК', b:0, d:sections[3], s:'warn', p:75, due:'30.06.2026', done:'—',
r:'Директор ДПБ / Директора филиалов и ДАО',
docs:['Видеообзор'],
ai:'Видеообзор смонтирован на 75%. Озвучка запланирована на 15 июня. Срок Q2 — укладываемся.',
h:['01.03 — Мероприятие создано','01.05 — Сценарий утверждён','01.06 — Монтаж 75%']},
// V. ВНЕДРЕНИЕ ИИ И ЦИФРОВИЗАЦИИ (32-35)
{id:32, sec:4, t:'Внедрение чат-бота ИИ-ассистента по производственной безопасности', b:0, d:sections[4], s:'warn', p:70, due:'30.06.2026', done:'—',
r:'Директор ДПБ / Директора филиалов и ДАО',
docs:['Справка о функционировании','Скриншоты'],
ai:'Чат-бот разработан, проходит тестирование в ЦА. База НПА загружена. Пилотный запуск — 15 июня. Успеваем в Q2.',
h:['01.02 — Мероприятие создано','01.04 — Разработка','01.06 — Тестирование']},
{id:33, sec:4, t:'Внедрение системы анализа и предупреждения НС и критических происшествий', b:0, d:sections[4], s:'wait', p:15, due:'31.12.2026', done:'—',
r:'Директор ДПБ / Директора филиалов и ДАО',
docs:['Справка о функционировании','Скриншоты'],
ai:'ТЗ на систему согласовывается. Интеграция с платформой Фонда — прорабатывается. Ранний этап, рисков срыва нет.',
h:['01.04 — Мероприятие создано','01.06 — ТЗ на согласовании']},
{id:34, sec:4, t:'Запуск электронного HSE паспорта на каждого работника', b:0, d:sections[4], s:'wait', p:10, due:'31.12.2026', done:'—',
r:'Директор ДПБ / Директора филиалов и ДАО',
docs:['Справка о функционировании','Скриншоты'],
ai:'Концепция HSE паспорта утверждена. Выбран подрядчик. Интеграция с КЦС — в плане на Q3. Ранний этап.',
h:['01.05 — Мероприятие создано','01.06 — Концепция утверждена']},
{id:35, sec:4, t:'Внедрение системы электронных нарядов-допусков на работы повышенной опасности', b:0, d:sections[4], s:'wait', p:8, due:'31.12.2026', done:'—',
r:'Директор ДПБ / Директора филиалов и ДАО',
docs:['Справка о функционировании','Скриншоты'],
ai:'Проект на предпроектной стадии. Анализ рынка решений проведён. Ожидается выбор платформы в Q3.',
h:['01.05 — Мероприятие создано','01.06 — Анализ рынка']}
]
const events = E
const allBranches = [...new Set(events.map(e=>e.b))].map(i=>branches[i])
// ===== HELPERS =====
function pctBar(p){return `<div class="track"><div class="fill" style="width:${p}%;background:${p>=80?'var(--green)':p>=40?'var(--amber)':'var(--red)'}"></div></div> ${p}%`}
function sBadge(s){
const map={done:'green',warn:'amber',late:'red',wait:'gray'}
return `<span class="badge ${map[s]||'gray'}">${statuses[s]||s}</span>`
}
function sBadge(s){const map={done:'green',warn:'amber',late:'red',wait:'gray'};return `<span class="badge ${map[s]||'gray'}">${statusMap[s]||s}</span>`}
function secBadge(s){return `<span class="badge blue">${['I','II','III','IV','V'][s]}</span>`}
// ===== PAGES =====
function pageDashboard(){
let done=events.filter(e=>e.status==='done').length
let late=events.filter(e=>e.status==='late').length
let warn=events.filter(e=>e.status==='warn').length
let done=events.filter(e=>e.s==='done').length
let late=events.filter(e=>e.s==='late').length
let warn=events.filter(e=>e.s==='warn').length
let wait=events.filter(e=>e.s==='wait').length
let total=events.length
let donePct=Math.round(done/total*100)
return `
<div class="page active" id="page-dashboard">
<h2>Дашборд производственной безопасности</h2>
<div class="stats-row">
<div class="stat-card green"><div class="stat-label">Всего мероприятий</div><div class="stat-num">${total}</div></div>
<div class="stat-card green"><div class="stat-label">Исполнено</div><div class="stat-num">${done}</div><div class="stat-sub">${donePct}% от плана</div></div>
<div class="stat-card amber"><div class="stat-label">На контроле</div><div class="stat-num">${warn}</div></div>
<div class="stat-card red"><div class="stat-label">Просрочено</div><div class="stat-num">${late}</div></div>
<div class="stat-card blue"><div class="stat-label">Общий прогресс</div><div class="stat-num">${donePct}%</div></div>
</div>
<div class="grid2">
<div class="panel">
<h3>Исполнение по направлениям</h3>
${directions.map(d=>{
let items=events.filter(e=>e.dir===d)
let pct=Math.round(items.filter(e=>e.status==='done').length/Math.max(1,items.length)*100)
return `<div class="rating-bar"><span class="name">${d}</span><div class="track"><div class="fill" style="width:${pct}%;background:var(--cyan)"></div></div><span style="font-size:13px;font-weight:700;width:40px;text-align:right">${pct}%</span></div>`
}).join('')}
</div>
<div class="panel">
<h3>Рейтинг организаций</h3>
${branches.slice(0,6).map(b=>{
let items=events.filter(e=>e.branch===b)
let pct=Math.round(items.filter(e=>e.status==='done').length/Math.max(1,items.length)*100)
return `<div class="rating-bar"><span class="name">${b.replace('АО ','').replace('ТОО ','')}</span><div class="track"><div class="fill" style="width:${pct}%;background:${pct>=70?'var(--green)':pct>=40?'var(--amber)':'var(--red)'}"></div></div><span style="font-size:13px;font-weight:700;width:40px;text-align:right">${pct}%</span></div>`
}).join('')}
</div>
</div>
return `<div class="page active"><h2>Дашборд производственной безопасности</h2>
<div class="stats-row">
<div class="stat-card"><div class="lbl">Всего мероприятий</div><div class="num">${total}</div><div class="sub">План ПБ на 2026 год</div></div>
<div class="stat-card green"><div class="lbl">Исполнено</div><div class="num">${done}</div><div class="sub">${donePct}% от плана</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>
</div>
<div class="grid2">
<div class="panel">
<h3>Динамика исполнения по кварталам</h3>
<div class="chart-stub">
<div class="bar" style="height:40%"></div>
<div class="bar" style="height:55%"></div>
<div class="bar" style="height:70%"></div>
<div class="bar" style="height:${donePct}%"></div>
</div>
<div class="chart-labels"><span>Q1</span><span>Q2</span><span>Q3</span><span>Q4 (план)</span></div>
<h3>Исполнение по разделам</h3>
${sections.map((s,i)=>{
let items=events.filter(e=>e.sec===i)
let pct=Math.round(items.filter(e=>e.s==='done').length/Math.max(1,items.length)*100)
return `<div class="rating-bar"><span class="name" style="font-size:12px">${s.replace(/^[IVX]+\.\s/,'')}</span><div class="track"><div class="fill" style="width:${pct}%;background:var(--cyan)"></div></div><span style="font-size:13px;font-weight:700;width:40px;text-align:right">${pct}%</span></div>`
}).join('')}
</div>
</div>`
<div class="panel">
<h3>Рейтинг филиалов</h3>
${allBranches.map((b,i)=>{
let items=events.filter(e=>e.b===i||(e.b===i&&branches[e.b]===b))
// recalc for actual branch index
let _items=events.filter(e=>branches[e.b]===b)
let pct=Math.round(_items.filter(e=>e.s==='done').length/Math.max(1,_items.length)*100)
return `<div class="rating-bar"><span class="name">${b}</span><div class="track"><div class="fill" style="width:${pct}%;background:${pct>=50?'var(--green)':pct>=25?'var(--amber)':'var(--red)'}"></div></div><span style="font-size:13px;font-weight:700;width:40px;text-align:right">${pct}%</span></div>`
}).join('')}
</div>
</div>
<div class="panel">
<h3>Динамика исполнения по кварталам</h3>
<div class="chart-stub">
<div class="bar green" style="height:45%"></div>
<div class="bar amber" style="height:60%"></div>
<div class="bar" style="height:70%"></div>
<div class="bar" style="height:${donePct}%"></div>
</div>
<div class="chart-labels"><span>Q1 (факт)</span><span>Q2 (прогноз)</span><span>Q3 (план)</span><span>Q4 (цель: 100%)</span></div>
</div>
</div>`
}
function pageRegister(filter='',statusFilter=''){
function pageRegister(filter='',secFilter='',statusFilter=''){
let list=events
if(filter) list=list.filter(e=>e.title.toLowerCase().includes(filter.toLowerCase())||e.branch.toLowerCase().includes(filter.toLowerCase()))
if(statusFilter) list=list.filter(e=>e.status===statusFilter)
if(filter) list=list.filter(e=>e.t.toLowerCase().includes(filter.toLowerCase())||branches[e.b].toLowerCase().includes(filter.toLowerCase()))
if(secFilter!=='') list=list.filter(e=>e.sec===parseInt(secFilter))
if(statusFilter) list=list.filter(e=>e.s===statusFilter)
return `
<div class="page active" id="page-register">
<h2>Реестр мероприятий</h2>
<div class="filters">
<input placeholder="Поиск по названию или организации..." oninput="refreshPage('register',this.value,document.getElementById('statusFilter')?.value||'')" id="searchInput">
<select onchange="refreshPage('register',document.getElementById('searchInput')?.value||'',this.value)" id="statusFilter">
<option value="">Все статусы</option>
<option value="done">Исполнено</option>
<option value="warn">На контроле</option>
<option value="late">Просрочено</option>
<option value="wait">Не начато</option>
</select>
<span class="count">Найдено: ${list.length}</span>
</div>
<div class="panel" style="overflow-x:auto">
<table>
<thead><tr><th></th><th>Мероприятие</th><th>Организация</th><th>Направление</th><th>Срок</th><th>Прогресс</th><th>Статус</th></tr></thead>
<tbody>${list.map(e=>`
<tr style="cursor:pointer" onclick="openEvent(${e.id})">
<td>${e.id}</td>
<td><strong>${e.title}</strong></td>
<td style="font-size:13px">${e.branch}</td>
<td style="font-size:13px">${e.dir}</td>
<td style="font-size:13px">${e.due}</td>
<td>${pctBar(e.progress)}</td>
<td>${sBadge(e.status)}</td>
</tr>`).join('')}</tbody>
</table>
</div>
</div>`
return `<div class="page active"><h2>Реестр мероприятий</h2>
<div class="filters">
<input placeholder="Поиск по названию или филиалу..." oninput="refreshPage('register',this.value,document.getElementById('secFilter')?.value||'',document.getElementById('statusFilter')?.value||'')" id="searchInput">
<select onchange="refreshPage('register',document.getElementById('searchInput')?.value||'',this.value,document.getElementById('statusFilter')?.value||'')" id="secFilter">
<option value="">Все разделы</option>
${sections.map((s,i)=>`<option value="${i}">${s}</option>`).join('')}
</select>
<select onchange="refreshPage('register',document.getElementById('searchInput')?.value||'',document.getElementById('secFilter')?.value||'',this.value)" id="statusFilter">
<option value="">Все статусы</option>
<option value="done">Исполнено</option><option value="warn">На контроле</option><option value="late">Просрочено</option><option value="wait">Не начато</option>
</select>
<span class="count">Найдено: ${list.length}</span>
</div>
<div class="panel" style="overflow-x:auto">
<table>
<thead><tr><th></th><th>Мероприятие</th><th>Раздел</th><th>Филиал</th><th>Срок</th><th>Прогресс</th><th>Статус</th></tr></thead>
<tbody>${list.map(e=>`<tr style="cursor:pointer" onclick="openEvent(${e.id})">
<td>${e.id}</td><td><strong>${e.t}</strong></td><td><span class="badge blue">${['I','II','III','IV','V'][e.sec]}</span></td>
<td style="font-size:12px">${branches[e.b]}</td><td style="font-size:12px">${e.due}</td>
<td style="white-space:nowrap">${pctBar(e.p)}</td><td>${sBadge(e.s)}</td></tr>`).join('')}</tbody>
</table>
</div>
</div>`
}
function pageAnalytics(){
let orgRatings=branches.map(b=>{
let items=events.filter(e=>e.branch===b)
let done=items.filter(e=>e.status==='done').length
let late=items.filter(e=>e.status==='late').length
return {name:b,done,late,total:items.length,pct:Math.round(done/Math.max(1,items.length)*100)}
let branchRatings=allBranches.map(bi=>{
let items=events.filter(e=>branches[e.b]===bi)
let done=items.filter(e=>e.s==='done').length
let late=items.filter(e=>e.s==='late').length
return {name:bi,done,late,total:items.length,pct:Math.round(done/Math.max(1,items.length)*100)}
}).sort((a,b)=>b.pct-a.pct)
return `
<div class="page active" id="page-analytics">
<h2>Аналитика</h2>
<div class="grid2">
<div class="panel">
<h3>Рейтинг организаций по исполнению</h3>
${orgRatings.map((r,i)=>`
<div class="rating-bar">
<span class="name">${i+1}. ${r.name}</span>
<div class="track"><div class="fill" style="width:${r.pct}%;background:${r.pct>=70?'var(--green)':r.pct>=40?'var(--amber)':'var(--red)'}"></div></div>
<span style="font-size:13px;font-weight:700;width:60px;text-align:right">${r.pct}% (${r.done}/${r.total})</span>
</div>`).join('')}
</div>
<div class="panel">
<h3>Просроченные мероприятия</h3>
${events.filter(e=>e.status==='late').map(e=>`
<div class="rating-bar">
<span class="name" style="font-size:13px">${e.title.slice(0,40)}...</span>
<span style="font-size:13px;color:var(--red);font-weight:700">${e.due}</span>
<span class="badge red">${e.branch.split('"')[1]||e.branch}</span>
</div>`).join('')||'<p style="color:var(--gray-500)">Просроченных мероприятий нет</p>'}
</div>
let sectionStats=sections.map((s,i)=>{
let items=events.filter(e=>e.sec===i)
let done=items.filter(e=>e.s==='done').length
return {name:s.replace(/^[IVX]+\.\s/,''),done,total:items.length,pct:Math.round(done/Math.max(1,items.length)*100)}
})
return `<div class="page active"><h2>Аналитика</h2>
<div class="grid2">
<div class="panel">
<h3>Рейтинг филиалов/ДАО</h3>
${branchRatings.map((r,i)=>`<div class="rating-bar">
<span class="name">${i+1}. ${r.name}</span>
<div class="track"><div class="fill" style="width:${r.pct}%;background:${r.pct>=50?'var(--green)':r.pct>=25?'var(--amber)':'var(--red)'}"></div></div>
<span style="font-size:13px;font-weight:700;width:60px;text-align:right">${r.pct}% (${r.done}/${r.total})</span></div>`).join('')}
</div>
<div class="grid3" style="margin-top:24px">
<div class="panel" style="text-align:center">
<h3>Всего мероприятий</h3>
<div style="font-size:48px;font-weight:800;color:var(--cyan)">${events.length}</div>
</div>
<div class="panel" style="text-align:center">
<h3>Документов загружено</h3>
<div style="font-size:48px;font-weight:800;color:var(--green)">${events.reduce((s,e)=>s+e.docs.length,0)}</div>
</div>
<div class="panel" style="text-align:center">
<h3>Средний % исполнения</h3>
<div style="font-size:48px;font-weight:800;color:var(--blue)">${Math.round(events.reduce((s,e)=>s+e.progress,0)/events.length)}%</div>
</div>
<div class="panel">
<h3>Исполнение по разделам Плана</h3>
${sectionStats.map(s=>`<div class="rating-bar">
<span class="name" style="font-size:12px">${s.name}</span>
<div class="track"><div class="fill" style="width:${s.pct}%;background:var(--cyan)"></div></div>
<span style="font-size:13px;font-weight:700;width:60px;text-align:right">${s.pct}% (${s.done}/${s.total})</span></div>`).join('')}
</div>
</div>`
</div>
<div class="panel" style="margin-top:16px">
<h3>Просроченные мероприятия</h3>
${events.filter(e=>e.s==='late').map(e=>`<div class="rating-bar">
<span class="name" style="font-size:12px">${e.t.slice(0,50)}...</span>
<span style="font-size:12px;color:var(--red);font-weight:700">${e.due}</span>
<span class="badge red">${branches[e.b]}</span></div>`).join('')||'<p style="color:var(--gray-500)">Просрочек нет</p>'}
</div>
</div>`
}
function pageReports(){
return `
<div class="page active" id="page-reports">
<h2>Формирование отчётности</h2>
<div class="stats-row">
<div class="stat-card"><div class="stat-label">Ежемесячный отчёт</div><div class="stat-num" style="font-size:20px">За май 2026</div><div class="stat-sub"><span class="badge green">Сформирован</span></div></div>
<div class="stat-card"><div class="stat-label">Квартальный отчёт</div><div class="stat-num" style="font-size:20px">Q2 2026</div><div class="stat-sub"><span class="badge amber">В обработке</span></div></div>
<div class="stat-card"><div class="stat-label">Годовой отчёт</div><div class="stat-num" style="font-size:20px">2026</div><div class="stat-sub"><span class="badge gray">Ожидается</span></div></div>
</div>
<div class="panel" style="margin-top:16px">
<h3>Все отчёты</h3>
<table>
<thead><tr><th>Тип отчёта</th><th>Период</th><th>Статус</th><th>Дата формирования</th><th>Формат</th></tr></thead>
<tbody>
<tr><td>Ежемесячный отчёт</td><td>Май 2026</td><td><span class="badge green">Готов</span></td><td>01.06.2026</td><td>PDF, Excel</td></tr>
<tr><td>Аналитическая справка</td><td>Май 2026</td><td><span class="badge green">Готов</span></td><td>02.06.2026</td><td>PDF, Word</td></tr>
<tr><td>Презентация для совещания</td><td>Июнь 2026</td><td><span class="badge amber">В работе</span></td><td></td><td>PPTX</td></tr>
<tr><td>Квартальный отчёт</td><td>Q2 2026</td><td><span class="badge amber">В работе</span></td><td></td><td>PDF, Excel</td></tr>
<tr><td>Рейтинг организаций</td><td>Июнь 2026</td><td><span class="badge green">Готов</span></td><td>04.06.2026</td><td>PDF</td></tr>
</tbody>
</table>
</div>
</div>`
return `<div class="page active"><h2>Формирование отчётности</h2>
<div class="stats-row">
<div class="stat-card"><div class="lbl">Ежемесячный отчёт</div><div class="num" style="font-size:20px">Май 2026</div><div class="sub"><span class="badge green">Сформирован</span></div></div>
<div class="stat-card"><div class="lbl">Квартальный отчёт</div><div class="num" style="font-size:20px">Q2 2026</div><div class="sub"><span class="badge amber">В обработке</span></div></div>
<div class="stat-card"><div class="lbl">Годовой отчёт</div><div class="num" style="font-size:20px">2026</div><div class="sub"><span class="badge gray">Ожидается</span></div></div>
</div>
<div class="panel" style="margin-top:16px">
<h3>Все отчёты</h3>
<table>
<thead><tr><th>Тип отчёта</th><th>Период</th><th>Статус</th><th>Дата</th><th>Формат</th></tr></thead>
<tbody>
<tr><td>Ежемесячный отчёт</td><td>Май 2026</td><td><span class="badge green">Готов</span></td><td>01.06.2026</td><td>PDF, Excel</td></tr>
<tr><td>Аналитическая справка</td><td>Май 2026</td><td><span class="badge green">Готов</span></td><td>02.06.2026</td><td>PDF, Word</td></tr>
<tr><td>Презентация для совещания</td><td>Июнь 2026</td><td><span class="badge amber">В работе</span></td><td></td><td>PPTX</td></tr>
<tr><td>Квартальный отчёт</td><td>Q2 2026</td><td><span class="badge amber">В работе</span></td><td></td><td>PDF, Excel</td></tr>
<tr><td>Рейтинг филиалов</td><td>Июнь 2026</td><td><span class="badge green">Готов</span></td><td>04.06.2026</td><td>PDF</td></tr>
</tbody>
</table>
</div>
</div>`
}
function pageRisks(){
let risks=[
{event:'Замена средств пожаротушения на складе ГСМ', branch:branches[1], level:'medium', reason:'Отставание от графика на 15%'},
{event:'Аттестация сварщиков', branch:branches[4], level:'high', reason:'Просрочка 64 дня, отсутствуют документы'},
{event:'Ремонт ограждения опасной зоны', branch:branches[0], level:'high', reason:'Просрочка 81 день, нет проектной документации'},
{event:'Обучение персонала по ГО и ЧС', branch:branches[7], level:'low', reason:'Не загружены списки групп'},
{event:'Пересмотр плана ликвидации аварий', branch:branches[4], level:'low', reason:'План на согласовании, замечаний нет'},
]
return `
<div class="page active" id="page-risks">
<h2>Карта рисков</h2>
<div class="stats-row">
<div class="stat-card red"><div class="stat-label">Высокий риск</div><div class="stat-num">${risks.filter(r=>r.level==='high').length}</div></div>
<div class="stat-card amber"><div class="stat-label">Средний риск</div><div class="stat-num">${risks.filter(r=>r.level==='medium').length}</div></div>
<div class="stat-card blue"><div class="stat-label">Низкий риск</div><div class="stat-num">${risks.filter(r=>r.level==='low').length}</div></div>
</div>
<div class="panel">
<h3>Реестр рисков</h3>
<table>
<thead><tr><th>Мероприятие</th><th>Организация</th><th>Уровень риска</th><th>Причина</th></tr></thead>
<tbody>${risks.map(r=>`
<tr>
<td><strong>${r.event}</strong></td>
<td style="font-size:13px">${r.branch}</td>
<td>${r.level==='high'?'<span class="badge red">Высокий</span>':r.level==='medium'?'<span class="badge amber">Средний</span>':'<span class="badge blue">Низкий</span>'}</td>
<td style="font-size:13px">${r.reason}</td>
</tr>`).join('')}</tbody>
</table>
</div>
</div>`
let risks=events.filter(e=>e.s==='late'||e.s==='warn').map(e=>{
let level=e.s==='late'?'high':e.p<30?'low':e.p<60?'medium':'low'
let reason=e.s==='late'?'Просрочен срок исполнения':e.p<30?'Ранний этап, риск отставания':e.p<60?'Требуется ускорение':'Незначительное отставание'
return {event:e.t,branch:branches[e.b],level,reason}
})
let hi=risks.filter(r=>r.level==='high').length
let md=risks.filter(r=>r.level==='medium').length
let lo=risks.filter(r=>r.level==='low').length
return `<div class="page active"><h2>Карта рисков</h2>
<div class="stats-row">
<div class="stat-card red"><div class="lbl">Высокий риск</div><div class="num">${hi}</div></div>
<div class="stat-card amber"><div class="lbl">Средний риск</div><div class="num">${md}</div></div>
<div class="stat-card blue"><div class="lbl">Низкий риск</div><div class="num">${lo}</div></div>
</div>
<div class="panel">
<h3>Реестр рисков</h3>
<table>
<thead><tr><th>Мероприятие</th><th>Филиал/ДАО</th><th>Уровень</th><th>Причина</th></tr></thead>
<tbody>${risks.map(r=>`<tr>
<td style="font-size:12px"><strong>${r.event.slice(0,65)}...</strong></td>
<td style="font-size:12px">${r.branch}</td>
<td>${r.level==='high'?'<span class="badge red">Высокий</span>':r.level==='medium'?'<span class="badge amber">Средний</span>':'<span class="badge blue">Низкий</span>'}</td>
<td style="font-size:12px">${r.reason}</td></tr>`).join('')}</tbody>
</table>
</div>
</div>`
}
function openEvent(id){
let e=events.find(x=>x.id===id)
if(!e) return
let e=events.find(x=>x.id===id); if(!e) return
document.getElementById('modalContent').innerHTML=`
<button class="close" onclick="closeModal()">&times;</button>
<div class="ai-tag">🤖 Анализ ИИ</div>
<h2>${e.title}</h2>
<div class="meta">
<div class="field">Организация<strong>${e.branch}</strong></div>
<div class="field">Направление<strong>${e.dir}</strong></div>
<div class="field">Срок исполнения<strong>${e.due}</strong></div>
<div class="field">Дата исполнения<strong>${e.done}</strong></div>
<div class="field">Ответственный<strong>${e.resp}</strong></div>
<div class="field">Прогресс<strong>${pctBar(e.progress)}</strong></div>
<div class="field">Статус<strong>${sBadge(e.status)}</strong></div>
<div style="display:flex;gap:8px;align-items:center;margin-bottom:12px">
<span class="badge blue">Раздел ${['I','II','III','IV','V'][e.sec]}</span>
<span class="ai-tag">🤖 Анализ ИИ</span>
</div>
${e.docs.length?`<div style="font-weight:600;margin:8px 0 4px;font-size:14px">Подтверждающие материалы:</div><div class="docs">${e.docs.map(d=>`<span>📄 ${d}</span>`).join('')}</div>`:''}
<h2>${e.t}</h2>
<div class="meta">
<div class="fld">Филиал/ДАО<strong>${branches[e.b]}</strong></div>
<div class="fld">Направление<strong>${e.d}</strong></div>
<div class="fld">Срок исполнения<strong>${e.due}</strong></div>
<div class="fld">Факт исполнения<strong>${e.done}</strong></div>
<div class="fld">Ответственный<strong>${e.r}</strong></div>
<div class="fld">Прогресс<strong>${pctBar(e.p)}</strong></div>
<div class="fld">Статус<strong>${sBadge(e.s)}</strong></div>
</div>
${e.docs.length?`<div style="font-weight:600;margin:8px 0 4px;font-size:14px">Форма завершения / материалы:</div><div class="docs">${e.docs.map(d=>`<span>📄 ${d}</span>`).join('')}</div>`:''}
<div class="ai-block"><h4>🤖 Выводы ИИ-агента</h4>${e.ai}</div>
<div style="font-weight:600;margin:8px 0 4px;font-size:14px">История изменений:</div>
<div class="timeline">${e.history.map(h=>`<div>${h}</div>`).join('')}</div>
<div class="timeline">${e.h.map(h=>`<div class="timeline-item"><div class="dot"></div>${h}</div>`).join('')}</div>
`
document.getElementById('modalOverlay').classList.add('open')
}
function closeModal(){document.getElementById('modalOverlay').classList.remove('open')}
function closeModal(){ document.getElementById('modalOverlay').classList.remove('open') }
// NAVIGATION
function navTo(page, filter='', statusFilter=''){
function navTo(page,filter='',secFilter='',statusFilter=''){
document.querySelectorAll('.sidebar nav a').forEach(a=>a.classList.remove('active'))
document.querySelector(`[data-page="${page}"]`)?.classList.add('active')
let content
switch(page){
case 'dashboard': content=pageDashboard(); break
case 'register': content=pageRegister(filter,statusFilter); break
case 'register': content=pageRegister(filter,secFilter,statusFilter); break
case 'analytics': content=pageAnalytics(); break
case 'reports': content=pageReports(); break
case 'risks': content=pageRisks(); break
@ -391,23 +519,10 @@ function navTo(page, filter='', statusFilter=''){
}
document.getElementById('mainContent').innerHTML=content
}
function refreshPage(page,filter,statusFilter){ navTo(page,filter,statusFilter) }
document.querySelectorAll('.sidebar nav a').forEach(a=>{
a.addEventListener('click',e=>{
e.preventDefault()
navTo(a.dataset.page)
})
})
document.getElementById('modalOverlay').addEventListener('click',function(e){
if(e.target===this) closeModal()
})
document.addEventListener('keydown',e=>{if(e.key==='Escape') closeModal()})
// INIT
function refreshPage(page,filter,secFilter,statusFilter){navTo(page,filter,secFilter,statusFilter)}
document.querySelectorAll('.sidebar nav a').forEach(a=>a.addEventListener('click',e=>{e.preventDefault();navTo(a.dataset.page)}))
document.getElementById('modalOverlay').addEventListener('click',function(e){if(e.target===this)closeModal()})
document.addEventListener('keydown',e=>{if(e.key==='Escape')closeModal()})
navTo('dashboard')
</script>
</body>