v13: полная пересборка JS без ошибок, data.json
This commit is contained in:
parent
0116112a3e
commit
871282be97
37
data.json
Normal file
37
data.json
Normal file
@ -0,0 +1,37 @@
|
||||
[
|
||||
{"id":1,"sec":0,"b":6,"s":"warn","p":45,"due":"31.12.2026","done":"\u2014","dname":"Протоколы обучения / Электронная ведомость","r":"Генеральный директор КУ, Генеральные директора филиалов и ДАО","t":"Продолжить проведение обучения и повышения квалификации руководителей и работников компании в соответствии с лучшими международными практиками, ориентированными на специфику условий труда, работы повышенной опасности и требований промышленной безопасности, а также развитие культуры безопасности, включая обучение производственного персонала по курсу \u00abКультура безопасного труда\u00bb, в том числе с применением VR, AR \u2013 технологий и цифровых симуляторов аварийных ситуаций по различным направлениям производственной безопасности (с правом выдачи сертификатов).","ai":"Обучение ведётся по графику. Охвачено 45% персонала. VR-тренажёры развёрнуты в 3 филиалах.","h":["15.01 — Мероприятие создано","01.03 — Запущено обучение","15.05 — VR-симуляторы установлены"]},
|
||||
{"id":2,"sec":0,"b":0,"s":"done","p":100,"due":"31.03.2026","done":"28.03.2026","dname":"Отчёт / Утверждённый ВНД","r":"Директор ДПБ, Генеральный директор ДИТ","t":"Провести анализ, в том числе с использованием аналитических платформ, и осуществить пересмотр внутренних нормативных документов филиалов/ДАО Общества в соответствии со Стратегией развития производственной безопасности на 2024-2028 гг., включая установку значений ключевых показателей производственной безопасности.","ai":"Анализ завершён в срок. Ключевые показатели установлены.","h":["10.01 — Создано","28.03 — Утверждён"]},
|
||||
{"id":3,"sec":0,"b":0,"s":"warn","p":50,"due":"31.12.2026","done":"\u2014","dname":"Протоколы совещаний","r":"Главный административный директор, Директор ДПБ","t":"Организовывать тематические совещания по вопросам производственной безопасности.","ai":"Проведено 2 квартальных совещания.","h":["10.01 — Создано","15.02 — Q1","15.05 — Q2"],"sub":[{"l":"a","t":"Руководство Общества с филиалами/ДАО, не менее 1 раза в квартал"},{"l":"b","t":"Руководство филиалов/ДАО со структурными подразделениями, не менее 1 раза в месяц"},{"l":"c","t":"Руководство с подрядными организациями, не менее 1 раза в квартал"}]},
|
||||
{"id":4,"sec":0,"b":6,"s":"warn","p":55,"due":"31.12.2026","done":"\u2014","dname":"Отчёты / Тесты","r":"Генеральные директора филиалов и ДАО","t":"Продолжить практику проверки знаний в формате тестирования после проведения инструктажей по охране труда в филиалах/ДАО Общества.","ai":"Тестирование в 6 филиалах. Средний результат — 82%.","h":["01.02 — Создано","01.06 — Отчёт"]},
|
||||
{"id":5,"sec":0,"b":0,"s":"done","p":100,"due":"31.03.2026","done":"25.03.2026","dname":"Информация о поощрении","r":"Директор ДПБ","t":"Рассмотреть возможность нематериального поощрения филиалов и ДАО Общества.","ai":"Положение утверждено.","h":["15.01 — Проект","25.03 — Утверждено"]},
|
||||
{"id":6,"sec":0,"b":6,"s":"warn","p":60,"due":"30.06.2026","done":"\u2014","dname":"ВНД по тренерам","r":"Генеральный директор КУ, Управляющий директор по персоналу","t":"Разработать и утвердить ВНД, регламентирующий процедуру работы внутренних тренеров по производственной безопасности.","ai":"Проект на финальном согласовании.","h":["01.03 — Создано","01.06 — Проект"]},
|
||||
{"id":7,"sec":0,"b":1,"s":"warn","p":40,"due":"31.12.2026","done":"\u2014","dname":"Материалы обмена опытом","r":"Директор ДПБ","t":"Проводить мероприятия по обмену опытом в области производственной безопасности.","ai":"1 выезд на KEGOC.","h":["15.02 — Создано","01.04 — Выезд"],"sub":[{"l":"a","t":"Обмен опытом на площадке Комитета HSE"},{"l":"b","t":"Организация обмена опытом с иностранными компаниями"}]},
|
||||
{"id":8,"sec":0,"b":4,"s":"wait","p":15,"due":"30.09.2026","done":"\u2014","dname":"Акт / Well-being / Скрининг","r":"Директор ДПБ, Управляющий директор по персоналу","t":"Провести анализ эффективности мероприятий по охране здоровья.","ai":"Медосмотры — Q3.","h":["01.04 — Создано"],"sub":[{"l":"a","t":"100% прохождение медосмотров"},{"l":"b","t":"Неделя благополучия (Well-being Week)"},{"l":"c","t":"Медицинский скрининг работников"},{"l":"d","t":"Алгоритм учёта микротравм"}]},
|
||||
{"id":9,"sec":0,"b":6,"s":"wait","p":20,"due":"31.12.2026","done":"\u2014","dname":"Результаты конкурсов","r":"Директор ДПБ","t":"Рассмотреть возможность участия в международных/национальных конкурсах профессионального мастерства по ПБ.","ai":"Определены 2 конкурса.","h":["01.05 — Создано"]},
|
||||
{"id":10,"sec":1,"b":1,"s":"warn","p":55,"due":"31.12.2026","done":"\u2014","dname":"Аналитическая справка","r":"Генеральный директор ОДС, СФ, ДУП, ДИТ","t":"Проводить работы по техническому перевооружению изношенного оборудования, зданий и сооружений.","ai":"Заменено 55%.","h":["01.01 — Переходящее","01.06 — 55%"]},
|
||||
{"id":11,"sec":1,"b":1,"s":"warn","p":70,"due":"30.06.2026","done":"\u2014","dname":"Процедура / Фотоотчёт","r":"Директор ДПБ","t":"Пересмотреть и актуализировать внутренний порядок нарядно-допускной системы с внедрением сертификатов безопасности.","ai":"Пилот запущен.","h":["01.02 — Создано","15.05 — Пилот"]},
|
||||
{"id":12,"sec":1,"b":8,"s":"wait","p":8,"due":"30.09.2026","done":"\u2014","dname":"Справка / Фотоотчёт","r":"Генеральные директора филиалов и ДАО","t":"Рассмотреть возможность внедрения системы цифровой маркировки опасных техустройств (QR-коды).","ai":"ТЭО в разработке.","h":["01.05 — Создано"]},
|
||||
{"id":13,"sec":1,"b":0,"s":"warn","p":50,"due":"31.12.2026","done":"\u2014","dname":"Акты / График","r":"Директор ДПБ","t":"Филиалам/ДАО не реже 1 раза в квартал проводить проверку по проверочным листам БиОТ, пром- и пожарной безопасности.","ai":"Q1 завершены. Q2 — по графику.","h":["01.01 — Создано","01.06 — Q2"]},
|
||||
{"id":14,"sec":1,"b":0,"s":"warn","p":40,"due":"31.12.2026","done":"\u2014","dname":"Письмо","r":"Директор ДПБ","t":"Продолжить практику участия в перекрёстных аудитах ПК согласно графику.","ai":"Назначены 4 аудитора.","h":["15.01 — Аудиторы"]},
|
||||
{"id":15,"sec":1,"b":0,"s":"warn","p":48,"due":"31.12.2026","done":"\u2014","dname":"Справка / Журнал","r":"Директор ДПБ","t":"Усилить контроль за проактивными инструментами: поведенческие аудиты, Near Miss, право приостановки.","ai":"147 Near Miss.","h":["01.01 — Создано"]},
|
||||
{"id":16,"sec":1,"b":1,"s":"done","p":85,"due":"31.12.2026","done":"\u2014","dname":"План-график / Акты","r":"Генеральные директора филиалов и ДАО","t":"Провести работу по повышению эффективности управления подрядными организациями.","ai":"Q1 — 12 подрядчиков.","h":["15.01 — План"],"sub":[{"l":"a","t":"Аудит подрядчиков по чек-листу Фонда"},{"l":"b","t":"Стартовые совещания с подрядчиками"}]},
|
||||
{"id":17,"sec":1,"b":0,"s":"warn","p":35,"due":"31.12.2026","done":"\u2014","dname":"Отчёты / Фотоотчёт","r":"Главный административный директор, Директор ДПБ","t":"Обеспечить контроль состояния ПБ на объектах (CEO-1, первые руководители).","ai":"CEO-1: 2 филиала.","h":["01.02 — Создано"],"sub":[{"l":"a","t":"CEO-1 лично проверять филиал 1 раз в квартал"},{"l":"b","t":"Первые руководители — внутренний контроль"}]},
|
||||
{"id":18,"sec":1,"b":1,"s":"done","p":90,"due":"31.12.2026","done":"\u2014","dname":"Ежемесячный отчёт","r":"Генеральные директора филиалов, Директор ДПБ","t":"Обеспечить контроль транспортной безопасности (ежемесячный мониторинг нарушений).","ai":"34 нарушения. Тренд — снижение.","h":["01.01 — Создано"]},
|
||||
{"id":19,"sec":2,"b":1,"s":"warn","p":30,"due":"31.12.2026","done":"\u2014","dname":"Акты тренировок","r":"Управляющий директор по безопасности","t":"Обеспечить проведение учебных тревог и тренировок (аварии, пожары, первая помощь).","ai":"1 учение. Пожарные: 1 из 2.","h":["01.02 — Создано"],"sub":[{"l":"a","t":"Одна учебная тревога по ликвидации аварии"},{"l":"b","t":"Две тренировки по тушению пожара"},{"l":"c","t":"Одно занятие по первой помощи"}]},
|
||||
{"id":20,"sec":2,"b":0,"s":"warn","p":65,"due":"30.06.2026","done":"\u2014","dname":"Приказ CMS / Акты штабов","r":"Управляющий директор по безопасности","t":"Усилить работу по реагированию на ЧС: Crisis Management System, обучение, заседания штабов.","ai":"CMS внедрена.","h":["01.03 — Создано"],"sub":[{"l":"a","t":"Внедрить Crisis Management System"},{"l":"b","t":"Обучение по действиям в ЧС"},{"l":"c","t":"Два заседания штабов"}]},
|
||||
{"id":21,"sec":3,"b":0,"s":"done","p":100,"due":"31.12.2026","done":"15.02.2026","dname":"Публикация","r":"Директор ДПБ, Пресс-секретарь","t":"Обеспечить выпуск обращения Председателя Правления о важности соблюдения требований ПБ.","ai":"Опубликовано.","h":["15.02 — Опубликовано"]},
|
||||
{"id":22,"sec":3,"b":0,"s":"wait","p":15,"due":"31.12.2026","done":"\u2014","dname":"Протоколы","r":"Директор ДПБ, Департамент коммуникаций","t":"Проведение стратсессий/Форумов для руководителей и семинаров для подрядчиков.","ai":"Форум — октябрь.","h":["01.05 — Создано"],"sub":[{"l":"a","t":"Стратсессии/Форумы для первых руководителей"},{"l":"b","t":"Семинары для подрядных организаций"}]},
|
||||
{"id":23,"sec":3,"b":6,"s":"wait","p":10,"due":"30.09.2026","done":"\u2014","dname":"Протокол Олимпиады","r":"Директор ДПБ","t":"Проведение Олимпиады по ПБ среди специалистов Общества и подрядных организаций.","ai":"Положение на согласовании.","h":["01.05 — Создано"]},
|
||||
{"id":24,"sec":3,"b":0,"s":"done","p":92,"due":"31.12.2026","done":"\u2014","dname":"Бюллетени","r":"Директор ДПБ","t":"Обеспечить ознакомление работников с обстоятельствами НС через информационные бюллетени.","ai":"3 бюллетеня, 92%.","h":["01.01 — Создано"]},
|
||||
{"id":25,"sec":3,"b":6,"s":"warn","p":40,"due":"31.12.2026","done":"\u2014","dname":"Публикации / Материалы","r":"Управляющий директор по персоналу, Директор ДПБ","t":"Проведение молодежных проектных инициатив Центра молодых работников по ПБ.","ai":"2 истории в SK News.","h":["01.02 — Создано"],"sub":[{"l":"a","t":"Публикация историй в SK News"},{"l":"b","t":"Посещение мест НС 2022-2025"},{"l":"c","t":"Молодые специалисты в аудитах"},{"l":"d","t":"Онлайн-семинары/эфиры"}]},
|
||||
{"id":26,"sec":3,"b":2,"s":"warn","p":50,"due":"31.12.2026","done":"\u2014","dname":"Видео / Постеры","r":"Директор ДПБ, Департамент коммуникаций","t":"Усилить наглядную агитацию: видеоролики, подкасты, постеры, брошюры.","ai":"2 видеоролика снято.","h":["01.02 — Создано"],"sub":[{"l":"a","t":"Видеоролики с участием работников"},{"l":"b","t":"Серия \u00abБезопасность будущего\u00bb"},{"l":"c","t":"Подкаст с трудовыми династиями"},{"l":"d","t":"Постеры, брошюры, рассылки"}]},
|
||||
{"id":27,"sec":3,"b":3,"s":"warn","p":30,"due":"31.12.2026","done":"\u2014","dname":"Фотофиксация","r":"Директор ДПБ","t":"Организация встреч коллектива с получившими производственные травмы работниками.","ai":"1 встреча проведена.","h":["01.03 — Создано"]},
|
||||
{"id":28,"sec":3,"b":0,"s":"warn","p":25,"due":"31.12.2026","done":"\u2014","dname":"Письма / Пресс-релизы","r":"Директор ДПБ, Департамент коммуникаций","t":"Проведение мероприятий по пропаганде безопасности через семейные ценности.","ai":"5 писем семьям.","h":["01.04 — Создано"],"sub":[{"l":"a","t":"Письма членам семьи отличившихся"},{"l":"b","t":"Семейные дни охраны труда"},{"l":"c","t":"Конкурс рисунков \u00abСпецодежда будущего!\u00bb"}]},
|
||||
{"id":29,"sec":3,"b":6,"s":"late","p":40,"due":"30.06.2026","done":"\u2014","dname":"Сборник практик","r":"Директор ДПБ","t":"Разработать корпоративный сборник лучших практик по производственной безопасности.","ai":"Риск срыва Q2.","h":["01.03 — Создано","01.06 — Эскалация"]},
|
||||
{"id":30,"sec":3,"b":7,"s":"warn","p":60,"due":"31.12.2026","done":"\u2014","dname":"Предложения / План","r":"Директор ДПБ","t":"Сбор предложений по цифровым решениям для системы управления ПБ.","ai":"18 предложений.","h":["01.01 — Создано"]},
|
||||
{"id":31,"sec":3,"b":0,"s":"warn","p":75,"due":"30.06.2026","done":"\u2014","dname":"Видеообзор","r":"Директор ДПБ","t":"Разработка видеообзора кейсов происшествий в ПК.","ai":"Монтаж 75%.","h":["01.03 — Создано"]},
|
||||
{"id":32,"sec":4,"b":8,"s":"warn","p":70,"due":"30.06.2026","done":"\u2014","dname":"Справка / Скриншоты","r":"Директор ДПБ","t":"Применение чат-бота ИИ-ассистента по ПБ для доступа к НПА и ВНД.","ai":"Чат-бот тестируется.","h":["01.02 — Создано","01.06 — Тест"]},
|
||||
{"id":33,"sec":4,"b":8,"s":"wait","p":15,"due":"31.12.2026","done":"\u2014","dname":"Справка / Скриншоты","r":"Директор ДПБ","t":"Применение системы анализа и предупреждения НС, платформы идентификации рисков.","ai":"ТЗ согласовывается.","h":["01.04 — Создано"]},
|
||||
{"id":34,"sec":4,"b":8,"s":"wait","p":10,"due":"31.12.2026","done":"\u2014","dname":"Справка / Скриншоты","r":"Директор ДПБ","t":"Запуск электронного HSE паспорта с интеграцией в корпоративную систему.","ai":"Концепция утверждена.","h":["01.05 — Создано"]},
|
||||
{"id":35,"sec":4,"b":5,"s":"wait","p":8,"due":"31.12.2026","done":"\u2014","dname":"Справка / Скриншоты","r":"Директор ДПБ","t":"Внедрение системы электронных нарядов-допусков на работы повышенной опасности.","ai":"Предпроект.","h":["01.05 — Создано"]}
|
||||
]
|
||||
820
index.html
820
index.html
@ -3,17 +3,16 @@
|
||||
<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;--green:#10B981;--red:#EF4444;--amber:#F59E0B;--blue:#3B82F6}
|
||||
*{box-sizing:border-box;margin:0;padding:0}
|
||||
body{font:14px/1.5 -apple-system,BlinkMacSystemFont,"Segoe UI",Inter,system-ui,sans-serif;color:var(--ink);background:var(--gray-100);min-height:100vh}
|
||||
input,select,textarea,button{font:inherit}
|
||||
.btn{display:inline-block;background:var(--cyan);color:var(--ink);padding:12px 24px;border-radius:8px;font-weight:700;font-size:15px;border:none;cursor:pointer;text-decoration:none}
|
||||
.btn{display:inline-block;background:var(--cyan);color:var(--ink);padding:12px 24px;border-radius:8px;font-weight:700;font-size:15px;border:none;cursor:pointer}
|
||||
.btn:hover{background:#1be5ff}
|
||||
.btn-sm{padding:7px 14px;font-size:13px}
|
||||
.btn-sm{padding:6px 12px;font-size:13px}
|
||||
.btn-outline{background:transparent;border:2px solid var(--ink);color:var(--ink)}
|
||||
.btn-outline:hover{background:var(--ink);color:var(--white)}
|
||||
.badge{display:inline-block;padding:4px 10px;border-radius:100px;font-size:12px;font-weight:600}
|
||||
.badge.green{background:#D1FAE5;color:#065F46}.badge.amber{background:#FEF3C7;color:#92400E}.badge.red{background:#FEE2E2;color:#991B1B}.badge.blue{background:#DBEAFE;color:#1E40AF}.badge.gray{background:var(--gray-100);color:var(--gray-700)}
|
||||
.panel{background:var(--white);border-radius:12px;border:1px solid var(--gray-200);padding:24px;margin-bottom:20px}
|
||||
@ -33,7 +32,6 @@ td{border-bottom:1px solid var(--gray-200)}tr:hover td{background:var(--cyan-50)
|
||||
.login-box label{display:block;text-align:left;font-size:13px;font-weight:600;margin-bottom:6px}
|
||||
.login-box input[type=email],.login-box input[type=password]{width:100%;padding:12px 16px;border:1px solid var(--gray-200);border-radius:8px;font-size:15px;margin-bottom:20px}
|
||||
.login-box .hint{font-size:12px;color:var(--gray-500);margin-top:-12px;margin-bottom:16px;text-align:left}
|
||||
.login-box .err{color:var(--red);font-size:13px;margin-bottom:12px;display:none}
|
||||
|
||||
#app{display:none}
|
||||
.topbar{background:var(--white);border-bottom:1px solid var(--gray-200);padding:0 32px;height:60px;display:flex;align-items:center;justify-content:space-between}
|
||||
@ -50,18 +48,14 @@ td{border-bottom:1px solid var(--gray-200)}tr:hover td{background:var(--cyan-50)
|
||||
.notif-drop .item .time{font-size:11px;color:var(--gray-500)}
|
||||
.notif-drop .empty{padding:24px;text-align:center;color:var(--gray-500)}
|
||||
|
||||
.tabs{display:flex;gap:0;margin-bottom:0;background:var(--white);border-radius:12px 12px 0 0;overflow:hidden}
|
||||
.tab-btn{padding:12px 24px;border:none;background:transparent;cursor:pointer;font-size:14px;font-weight:600;color:var(--gray-500);border-bottom:3px solid transparent}
|
||||
.tab-btn.active{color:var(--ink);border-bottom-color:var(--cyan)}
|
||||
.tab-content{display:none}.tab-content.active{display:block}
|
||||
|
||||
.main{padding:24px 32px;max-width:1400px}
|
||||
.stats-row{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:16px;margin-bottom:24px}
|
||||
.stat-card{background:var(--white);border-radius:12px;padding:20px 24px;border:1px solid var(--gray-200)}
|
||||
.stat-card .lbl{font-size:13px;color:var(--gray-500)}.stat-card .num{font-size:28px;font-weight:800;line-height:1.2}.stat-card .sub{font-size:12px;color:var(--gray-500)}
|
||||
.stat-card .lbl{font-size:13px;color:var(--gray-500)}.stat-card .num{font-size:28px;font-weight:800;line-height:1.2}
|
||||
.stat-card.red .num{color:var(--red)}.stat-card.green .num{color:var(--green)}.stat-card.amber .num{color:var(--amber)}.stat-card.blue .num{color:var(--blue)}
|
||||
.filters{display:flex;gap:12px;margin-bottom:16px;flex-wrap:wrap;align-items:center}
|
||||
.filters select,.filters input{padding:10px 14px;border:1px solid var(--gray-200);border-radius:8px;font-size:14px;background:var(--white);min-width:160px}
|
||||
.filters input{min-width:260px}
|
||||
|
||||
.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}
|
||||
@ -79,21 +73,15 @@ td{border-bottom:1px solid var(--gray-200)}tr:hover td{background:var(--cyan-50)
|
||||
.ai-block h4{font-size:14px;margin-bottom:4px}
|
||||
.history-item{display:flex;gap:8px;font-size:12px;padding:3px 0;color:var(--gray-500);align-items:baseline}
|
||||
.history-item .dot{width:7px;height:7px;border-radius:50%;background:var(--cyan);flex-shrink:0;margin-top:5px}
|
||||
|
||||
/* SUB-ITEMS */
|
||||
.sub-items{margin:12px 0}
|
||||
.sub-item{display:flex;align-items:center;gap:12px;padding:10px 14px;background:var(--gray-100);border-radius:8px;margin-bottom:6px;font-size:13px}
|
||||
.sub-item .sub-label{font-weight:700;color:var(--cyan);font-size:15px;min-width:20px}
|
||||
.sub-item .sub-text{flex:1;color:var(--gray-500);font-size:12px;line-height:1.3}
|
||||
.sub-item input[type=checkbox]{width:18px;height:18px;cursor:pointer;accent-color:var(--cyan)}
|
||||
|
||||
/* MONTH SELECTOR */
|
||||
.month-tabs{display:flex;gap:6px;flex-wrap:wrap;margin-bottom:14px}
|
||||
.month-tab{padding:6px 14px;border:1px solid var(--gray-200);border-radius:100px;font-size:13px;font-weight:600;cursor:pointer;background:var(--white);color:var(--gray-500);transition:.15s}
|
||||
.month-tab:hover{border-color:var(--cyan)}
|
||||
.month-tab.active{background:var(--cyan);color:var(--ink);border-color:var(--cyan)}
|
||||
|
||||
/* FILE ROW */
|
||||
.file-row{display:flex;align-items:center;gap:10px;padding:8px 12px;background:var(--gray-100);border-radius:8px;margin-bottom:6px;font-size:13px}
|
||||
.file-row .file-info{flex:1;min-width:0}
|
||||
.file-row .file-name{font-weight:600;cursor:pointer;color:var(--ink)}.file-row .file-name:hover{color:var(--cyan)}
|
||||
@ -116,15 +104,15 @@ td{border-bottom:1px solid var(--gray-200)}tr:hover td{background:var(--cyan-50)
|
||||
<body>
|
||||
|
||||
<div id="loginScreen">
|
||||
<form class="login-box" onsubmit="doLogin(event)">
|
||||
<form class="login-box" onsubmit="return doLogin(event)">
|
||||
<h1><span>ИИ-Агент</span> ПБ</h1>
|
||||
<p class="sub">АО «Казахтелеком» — мониторинг производственной безопасности</p>
|
||||
<label>Корпоративная почта</label>
|
||||
<input type="email" id="loginEmail" placeholder="surname@telecom.kz" required>
|
||||
<p class="hint">Например: ahmetov@telecom.kz, serikov@telecom.kz, admin@telecom.kz</p>
|
||||
<p class="hint">admin@telecom.kz / ahmetov@telecom.kz / serikov@telecom.kz — пароль любой</p>
|
||||
<label>Пароль</label>
|
||||
<input type="password" id="loginPass" placeholder="••••••••" required>
|
||||
<p class="err" id="loginErr">Неверная почта или пароль</p>
|
||||
<p style="color:var(--red);font-size:13px;margin-bottom:12px;display:none" id="loginErr">Неверная почта или пароль</p>
|
||||
<button type="submit" class="btn" style="width:100%;margin-top:8px">Войти</button>
|
||||
</form>
|
||||
</div>
|
||||
@ -141,465 +129,415 @@ td{border-bottom:1px solid var(--gray-200)}tr:hover td{background:var(--cyan-50)
|
||||
<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 class="main" id="mainContent"></div>
|
||||
</div>
|
||||
|
||||
<div class="modal-overlay" id="editModalOverlay"><div class="modal" id="editModalContent"></div></div>
|
||||
|
||||
<script>
|
||||
const sections=['I. Люди. Повышение культуры безопасности','II. Безопасность при эксплуатации оборудования','III. Предупреждение и готовность к ликвидации аварий и ЧС','IV. Информационно-разъяснительная работа','V. Внедрение ИИ и цифровизации']
|
||||
const branches=['Дирекция производственной безопасности','Объединение «Дивизион «Сеть»»','Дивизион по корпоративному бизнесу','Дивизион по розничному бизнесу','Сервисная фабрика','Дирекция «Телеком Комплект»','Корпоративный университет','Дирекция управления проектами','Дивизион цифрового бизнеса']
|
||||
const statusMap={done:'Исполнено',warn:'На контроле',late:'Просрочено',wait:'В процессе'}
|
||||
const months=['2026-01','2026-02','2026-03','2026-04','2026-05','2026-06','2026-07','2026-08','2026-09','2026-10','2026-11','2026-12']
|
||||
const monthNames=['Янв','Фев','Мар','Апр','Май','Июн','Июл','Авг','Сен','Окт','Ноя','Дек']
|
||||
"use strict";
|
||||
|
||||
function M(idx){ const [y,m]=months[idx].split('-'); return monthNames[parseInt(m)-1]+' '+y }
|
||||
var sections = [
|
||||
"I. Люди. Повышение культуры безопасности",
|
||||
"II. Безопасность при эксплуатации оборудования",
|
||||
"III. Предупреждение и готовность к ликвидации аварий и ЧС",
|
||||
"IV. Информационно-разъяснительная работа",
|
||||
"V. Внедрение ИИ и цифровизации"
|
||||
];
|
||||
var branches = [
|
||||
"Дирекция производственной безопасности",
|
||||
"Объединение «Дивизион «Сеть»»",
|
||||
"Дивизион по корпоративному бизнесу",
|
||||
"Дивизион по розничному бизнесу",
|
||||
"Сервисная фабрика",
|
||||
"Дирекция «Телеком Комплект»",
|
||||
"Корпоративный университет",
|
||||
"Дирекция управления проектами",
|
||||
"Дивизион цифрового бизнеса"
|
||||
];
|
||||
var statusMap = {done: "Исполнено", warn: "На контроле", late: "Просрочено", wait: "В процессе"};
|
||||
var months = ["2026-01","2026-02","2026-03","2026-04","2026-05","2026-06","2026-07","2026-08","2026-09","2026-10","2026-11","2026-12"];
|
||||
var monthNames = ["Янв","Фев","Мар","Апр","Май","Июн","Июл","Авг","Сен","Окт","Ноя","Дек"];
|
||||
|
||||
const baseEvents = [
|
||||
{id:1,sec:0,b:6,s:'warn',p:45,due:'31.12.2026',done:'—',dname:'Протоколы обучения / Электронная ведомость',
|
||||
r:'Генеральный директор КУ, Генеральные директора филиалов и ДАО',
|
||||
t:'Продолжить проведение обучения и повышения квалификации руководителей и работников компании в соответствии с лучшими международными практиками, ориентированными на специфику условий труда, работы повышенной опасности и требований промышленной безопасности, а также развитие культуры безопасности, включая обучение производственного персонала по курсу «Культура безопасного труда», в том числе с применением VR, AR – технологий и цифровых симуляторов аварийных ситуаций по различным направлениям производственной безопасности (с правом выдачи сертификатов).',
|
||||
ai:'Обучение ведётся по графику. Охвачено 45% персонала. VR-тренажёры развёрнуты в 3 филиалах.',
|
||||
h:['15.01 — Мероприятие создано','01.03 — Запущено обучение','15.05 — VR-симуляторы установлены']},
|
||||
{id:2,sec:0,b:0,s:'done',p:100,due:'31.03.2026',done:'28.03.2026',dname:'Отчёт о проведённом анализе / Утверждённый ВНД',
|
||||
r:'Директор ДПБ, Генеральный директор ДИТ, Генеральные директора филиалов и ДАО',
|
||||
t:'Провести анализ, в том числе с использованием аналитических платформ (Microsoft Teams, Power BI, Tableau, Qlik и др.), и в случае необходимости, осуществить пересмотр внутренних нормативных документов филиалов/ДАО Общества в соответствии со «Стратегией развития производственной безопасности АО «Самрук-Қазына» на 2024-2028 гг.», включая установку значений ключевых показателей производственной безопасности.',
|
||||
ai:'Анализ завершён в срок. ВНД пересмотрены. Ключевые показатели ПБ установлены.',
|
||||
h:['10.01 — Мероприятие создано','15.02 — Проведён анализ','28.03 — Отчёт утверждён']},
|
||||
{id:3,sec:0,b:0,s:'warn',p:50,due:'31.12.2026',done:'—',dname:'Протоколы совещаний (a, b, c)',
|
||||
r:'Главный административный директор, Директор ДПБ / Генеральные директора филиалов и ДАО',
|
||||
t:'Организовывать тематические совещания по вопросам производственной безопасности.',
|
||||
ai:'Проведено 2 квартальных совещания. Ежемесячные совещания — выполняется.',
|
||||
h:['10.01 — Мероприятие создано','15.02 — Совещание Q1','15.05 — Совещание Q2'],
|
||||
sub:[{l:'a',t:'Руководство Общества с филиалами/ДАО, не менее 1 раза в квартал, с личным мониторингом показателей эффективности ПБ и статуса исполнения Плана'},
|
||||
{l:'b',t:'Руководство филиалов/ДАО со структурными подразделениями, не менее 1 раза в месяц'},
|
||||
{l:'c',t:'Руководство региональных подразделений/филиалов/ДАО с подрядными организациями, не менее 1 раза в квартал'}]},
|
||||
{id:4,sec:0,b:6,s:'warn',p:55,due:'31.12.2026',done:'—',dname:'Отчёты о проделанной работе / Тесты',
|
||||
r:'Генеральные директора филиалов и ДАО',
|
||||
t:'Продолжить практику проверки знаний в формате тестирования после проведения инструктажей по охране труда в филиалах/ДАО Общества.',
|
||||
ai:'Тестирование внедрено в 6 филиалах. Средний результат — 82%.',h:['01.02 — Создано','01.04 — Внедрено','01.06 — Отчёт']},
|
||||
{id:5,sec:0,b:0,s:'done',p:100,due:'31.03.2026',done:'25.03.2026',dname:'Информация о нематериальном поощрении',
|
||||
r:'Директор ДПБ, Генеральные директора филиалов и ДАО',
|
||||
t:'Рассмотреть возможность нематериального поощрения филиалов и ДАО Общества, демонстрирующих устойчивое снижение количества несчастных случаев, пожаров и аварий по итогам нескольких и более лет.',
|
||||
ai:'Положение утверждено. Определены 3 филиала-лидера.',h:['15.01 — Проект','01.03 — Согласование','25.03 — Утверждено']},
|
||||
{id:6,sec:0,b:6,s:'warn',p:60,due:'30.06.2026',done:'—',dname:'ВНД по внутренним тренерам / Перечень тренеров',
|
||||
r:'Генеральный директор КУ, Управляющий директор по персоналу / Генеральные директора филиалов и ДАО',
|
||||
t:'Разработать/внести изменения в случае необходимости и утвердить внутренний нормативный документ, регламентирующий процедуру работы внутренних тренеров, в том числе по производственной безопасности, включая порядок их отбора, подготовки и привлечения, а также установление условий доплаты к основной заработной плате за выполнение тренерских функций.',
|
||||
ai:'Проект ВНД на финальном согласовании. Перечень — 12 чел.',h:['01.03 — Создано','15.04 — Проект ВНД','01.06 — Перечень']},
|
||||
{id:7,sec:0,b:1,s:'warn',p:40,due:'31.12.2026',done:'—',dname:'Материалы обмена опытом',
|
||||
r:'Директор ДПБ, Генеральные директора филиалов и ДАО',
|
||||
t:'Проводить мероприятия по обмену опытом в области производственной безопасности.',
|
||||
ai:'Проведён 1 выезд на площадку KEGOC. Онлайн-семинар — июль.',h:['15.02 — Создано','01.04 — Выезд','15.05 — План'],
|
||||
sub:[{l:'a',t:'Продолжить практику обмена передовым опытом на площадке Комитета HSE, в том числе путем выездов на производственные объекты ПК'},
|
||||
{l:'b',t:'Рассмотреть возможность организации обмена опытом с иностранными и казахстанскими компаниями, в том числе путем проведения онлайн-семинаров'}]},
|
||||
{id:8,sec:0,b:4,s:'wait',p:15,due:'30.09.2026',done:'—',dname:'Заключительный Акт / Программа Well-being / Отчёт / Отчёт о микротравмах',
|
||||
r:'a) Директор ДПБ / b) Ген. директор КУ, Упр. директор по персоналу / c,d) Ген. директора филиалов и ДАО',
|
||||
t:'Провести анализ эффективности реализуемых мероприятий по охране здоровья.',
|
||||
ai:'Медосмотры — Q3. Well-being Week — сентябрь. Ранний этап.',h:['01.04 — Создано','01.06 — Проект алгоритма'],
|
||||
sub:[{l:'a',t:'Организовать и обеспечить 100% прохождение обязательных периодических медицинских осмотров работниками, включая офисных работников'},
|
||||
{l:'b',t:'Организовать ежегодную «Неделю благополучия» (Well-being Week) для работников всех уровней'},
|
||||
{l:'c',t:'Создать условия и обеспечить контроль за прохождением медицинского скрининга работников'},
|
||||
{l:'d',t:'Внедрить алгоритм учета и расследования микротравм'}]},
|
||||
{id:9,sec:0,b:6,s:'wait',p:20,due:'31.12.2026',done:'—',dname:'Результаты конкурсов / Пакет материалов',
|
||||
r:'Директор ДПБ, Генеральные директора филиалов и ДАО',
|
||||
t:'Рассмотреть возможность участия ДПБ/филиалов/ДАО Общества в международных/национальных конкурсах и отраслевых соревнованиях профессионального мастерства в области производственной безопасности.',
|
||||
ai:'Определены 2 конкурса. Заявки готовятся.',h:['01.05 — Создано','01.06 — Отобраны конкурсы']},
|
||||
{id:10,sec:1,b:1,s:'warn',p:55,due:'31.12.2026',done:'—',dname:'Аналитическая справка по филиалам/ДАО',
|
||||
r:'Генеральный директор ОДС, Генеральный директор СФ, Генеральный директор ДУП, Генеральный директор ДИТ',
|
||||
t:'Проводить работы по техническому перевооружению морально и физически изношенного оборудования, зданий и сооружений, эксплуатация которых из-за их технического состояния сопровождается повышенными рисками возникновения аварий и несчастных случаев с тяжёлыми и летальными исходами, в соответствии с ранее утвержденными Планами на 2024-2027 годы.',
|
||||
ai:'Заменено 55% единиц. Отдельные филиалы отстают на 12%.',h:['01.01 — Переходящее','01.04 — Отчёт','01.06 — 55%']},
|
||||
{id:11,sec:1,b:1,s:'warn',p:70,due:'30.06.2026',done:'—',dname:'Переутверждённая процедура / Фотоотчёт / Протоколы обучения',
|
||||
r:'Директор ДПБ, Генеральные директора филиалов и ДАО',
|
||||
t:'Пересмотреть и актуализировать внутренний порядок выдачи нарядно-допускной системы, усилив законодательные требования РК путем внедрения в пилотном режиме практики применения сертификатов безопасности для одного из видов работ повышенной опасности.',
|
||||
ai:'Процедура пересмотрена. Пилот запущен. Обучение — 85%.',h:['01.02 — Создано','01.04 — Проект','15.05 — Пилот']},
|
||||
{id:12,sec:1,b:8,s:'wait',p:8,due:'30.09.2026',done:'—',dname:'Справка о внедрении / Фотоотчёт',
|
||||
r:'Генеральные директора филиалов и ДАО',
|
||||
t:'Рассмотреть возможность внедрения системы цифровой маркировки опасных технических устройств, предусматривающей присвоение каждому устройству QR-кода для быстрого доступа к паспорту, инструкции по эксплуатации и информации о проведенных технических освидетельствованиях.',
|
||||
ai:'Проект на стадии ТЭО. Начало — июль.',h:['01.05 — Создано','01.06 — ТЭО']},
|
||||
{id:13,sec:1,b:0,s:'warn',p:50,due:'31.12.2026',done:'—',dname:'Акты проверок / График',
|
||||
r:'Директор ДПБ',
|
||||
t:'Филиалам/ДАО Общества не реже 1 раза в квартал проводить проверку согласно адаптированным проверочным листам в области БиОТ, промышленной и пожарной безопасности в соответствии с требованиями законодательства Республики Казахстан.',
|
||||
ai:'Q1 завершены. Q2 — по графику. Выявлено 23 нарушения.',h:['01.01 — Создано','31.03 — Q1','01.06 — Q2']},
|
||||
{id:14,sec:1,b:0,s:'warn',p:40,due:'31.12.2026',done:'—',dname:'Письмо о предоставлении кандидата',
|
||||
r:'Директор ДПБ',
|
||||
t:'Продолжить практику участия в перекрёстных аудитах ПК, в том числе в соответствии с Планом-графиком проведения аудитов.',
|
||||
ai:'Назначены 4 аудитора. Участвовали в 2 аудитах.',h:['15.01 — Назначены','01.03 — Аудит 1','15.04 — Аудит 2']},
|
||||
{id:15,sec:1,b:0,s:'warn',p:48,due:'31.12.2026',done:'—',dname:'Аналитическая справка / Журнал опережающих индикаторов',
|
||||
r:'Директор ДПБ, Генеральные директора филиалов и ДАО',
|
||||
t:'Усилить контроль за применением проактивных инструментов: мониторинг поведенческих аудитов/наблюдений безопасности, регистрация и расследование опасных условий, опасных действий и потенциально опасных происшествий Near Miss; право приостановки работы.',
|
||||
ai:'147 Near Miss (+12% к 2025). Аудиты — 320 шт.',h:['01.01 — Создано','01.04 — Q1','01.06 — Q2']},
|
||||
{id:16,sec:1,b:1,s:'done',p:85,due:'31.12.2026',done:'—',dname:'План-график аудитов / Акты / Протоколы совещаний',
|
||||
r:'Генеральные директора филиалов и ДАО',
|
||||
t:'Провести работу по повышению эффективности управления подрядными организациями.',
|
||||
ai:'Q1 — 12 подрядчиков проверено. Стартовые совещания — 100%.',h:['15.01 — План','01.03 — Q1','01.06 — Q2'],
|
||||
sub:[{l:'a',t:'Обеспечить проведение аудита подрядчиков внутри филиалов/ДАО согласно типового чек-листа Фонда'},
|
||||
{l:'b',t:'Продолжить практику проведения стартовых/установочных совещаний с подрядными организациями перед допуском на объект'}]},
|
||||
{id:17,sec:1,b:0,s:'warn',p:35,due:'31.12.2026',done:'—',dname:'Отчёты / График проверок / Фотоотчёт',
|
||||
r:'a) Главный административный директор, Директор ДПБ / b) Генеральные директора филиалов и ДАО',
|
||||
t:'Обеспечить контроль за состоянием производственной безопасности на производственных объектах.',
|
||||
ai:'CEO-1 проверил 2 филиала. 6 выездов. Активность ниже плана.',h:['01.02 — Создано','15.03 — Проверка 1','01.05 — Проверка 2'],
|
||||
sub:[{l:'a',t:'Руководителям Общества уровня СЕО-1 не реже 1 раза в квартал лично проверять одно из филиалов/подрядных организаций'},
|
||||
{l:'b',t:'Первым руководителям филиалов лично принимать участие во внутреннем производственном контроле с посещением площадок не реже 1 раза в квартал'}]},
|
||||
{id:18,sec:1,b:1,s:'done',p:90,due:'31.12.2026',done:'—',dname:'Ежемесячный сводный отчёт',
|
||||
r:'Генеральные директора филиалов и ДАО, Директор ДПБ',
|
||||
t:'Обеспечить контроль за состоянием транспортной безопасности, в том числе путем ежемесячного мониторинга нарушений требований транспортной безопасности со стороны штатных водителей и водителей подрядных организаций, с последующим применением предусмотренных договорами мер воздействия.',
|
||||
ai:'34 нарушения. Штрафы — 12 водителей. Тренд — снижение.',h:['01.01 — Создано','01.02 — Отчёт янв','01.06 — Отчёт май']},
|
||||
{id:19,sec:2,b:1,s:'warn',p:30,due:'31.12.2026',done:'—',dname:'Акты тренировок / Пресс-релизы',
|
||||
r:'a) Управляющий директор по безопасности / b) Генеральный директор СФ / c) Генеральные директора филиалов и ДАО',
|
||||
t:'Обеспечить проведение учебных тревог и тренировок.',
|
||||
ai:'1 учение. Пожарные: 1 из 2. Первая помощь — Q3.',h:['01.02 — Создано','15.03 — Учение','15.05 — Пожарная №1'],
|
||||
sub:[{l:'a',t:'Не менее одной учебной тревоги и/или противоаварийной тренировки по ликвидации крупной аварии, ЧС на опасном производственном объекте'},
|
||||
{l:'b',t:'Не менее двух тренировок по тушению пожара в административных зданиях (офисах)'},
|
||||
{l:'c',t:'Не менее одного практического занятия по оказанию первой помощи с применением симуляционного оборудования'}]},
|
||||
{id:20,sec:2,b:0,s:'warn',p:65,due:'30.06.2026',done:'—',dname:'Приказ CMS / Материалы обучения / Акты штабов',
|
||||
r:'Управляющий директор по безопасности, Генеральные директора филиалов и ДАО',
|
||||
t:'Усилить работу по реагированию на ЧС.',
|
||||
ai:'CMS подписана. Обучение — 60%. 1 заседание штаба.',h:['01.03 — Создано','01.04 — Приказ','15.05 — Штаб №1'],
|
||||
sub:[{l:'a',t:'Внедрить процедуру «Crisis Management System» для своевременной реакции на кризисные события'},
|
||||
{l:'b',t:'Рассмотреть возможность проведения обучения для ответственных работников по действиям в условиях ЧС'},
|
||||
{l:'c',t:'Провести не менее двух заседаний штабов с целью отработки действий на практике'}]},
|
||||
{id:21,sec:3,b:0,s:'done',p:100,due:'31.12.2026',done:'15.02.2026',dname:'Публикация на информационных порталах',
|
||||
r:'Директор ДПБ, Пресс-секретарь ЦА',
|
||||
t:'Обеспечить выпуск обращения от Председателя Правления ПК о важности соблюдения требований по производственной безопасности.',
|
||||
ai:'Опубликовано. Охват — 100%.',h:['15.01 — Проект','01.02 — Подписание','15.02 — Публикация']},
|
||||
{id:22,sec:3,b:0,s:'wait',p:15,due:'31.12.2026',done:'—',dname:'Протоколы форумов / Протоколы семинаров',
|
||||
r:'Директор ДПБ, Генеральные директора филиалов и ДАО / Департамент по коммуникациям',
|
||||
t:'Проведение мероприятий по производственной безопасности.',
|
||||
ai:'Форум — октябрь. Семинары — 2 площадки.',h:['01.05 — Создано','01.06 — Площадки'],
|
||||
sub:[{l:'a',t:'Стратегические сессии/Форумы для первых руководителей филиалов/ДАО'},
|
||||
{l:'b',t:'Семинары для подрядных организаций ПК на отдельных площадках филиалов/ДАО'}]},
|
||||
{id:23,sec:3,b:6,s:'wait',p:10,due:'30.09.2026',done:'—',dname:'Протокол итогов Олимпиады',
|
||||
r:'Директор ДПБ',
|
||||
t:'Проведение Олимпиады по производственной безопасности среди специалистов производственной безопасности Общества и подрядных организаций на уровне Общества.',
|
||||
ai:'Положение на согласовании.',h:['01.05 — Создано','01.06 — Проект']},
|
||||
{id:24,sec:3,b:0,s:'done',p:92,due:'31.12.2026',done:'—',dname:'Бюллетени / Листы ознакомления',
|
||||
r:'Директор ДПБ, Генеральные директора филиалов и ДАО',
|
||||
t:'Обеспечить ознакомление всех работников филиалов/ДАО Общества с обстоятельствами несчастных случаев с тяжелым и летальным исходами, произошедших в ПК Фонда, посредством направления информационных бюллетеней, в том числе с использованием цифровых решений либо в рамках внеплановых инструктажей.',
|
||||
ai:'3 бюллетеня. Ознакомление — 92%.',h:['01.01 — Создано','15.02 — №1','01.05 — №3']},
|
||||
{id:25,sec:3,b:6,s:'warn',p:40,due:'31.12.2026',done:'—',dname:'Публикации SK News / Материалы мероприятий',
|
||||
r:'a) Управляющий директор по персоналу, Департамент по коммуникациям / b,c,d) Директор ДПБ, Генеральные директора филиалов и ДАО',
|
||||
t:'Проведение молодежных проектных инициатив в рамках работы Центра молодых работников по производственной безопасности.',
|
||||
ai:'2 истории. 1 выезд. 3 специалиста в аудитах.',h:['01.02 — Создано','15.03 — Публикация','01.05 — Выезд'],
|
||||
sub:[{l:'a',t:'Публикация реальных историй из трудовой жизни работников в корпоративном журнале «SK News»'},
|
||||
{l:'b',t:'Посещение рабочих мест, где в 2022-2025 гг. произошли несчастные случаи с летальным или тяжелым исходом'},
|
||||
{l:'c',t:'Привлечение молодых специалистов по ПБ в перекрёстные аудиты ДЗО ПК'},
|
||||
{l:'d',t:'Онлайн-семинары/прямые эфиры на различные темы по вопросам производственной безопасности'}]},
|
||||
{id:26,sec:3,b:2,s:'warn',p:50,due:'31.12.2026',done:'—',dname:'Видеоролики / Постеры / Брошюры',
|
||||
r:'a) Генеральные директора филиалов и ДАО / b,c) Директор ДПБ, Департамент по коммуникациям / d) Директор ДПБ',
|
||||
t:'Усилить наглядную агитацию по производственной безопасности.',
|
||||
ai:'2 видеоролика. Постеры распространены.',h:['01.02 — Создано','01.04 — Ролик №1','01.06 — Ролик №2'],
|
||||
sub:[{l:'a',t:'Разработка и использование видеороликов/презентаций по ПБ, в т.ч. с участием получивших травму работников'},
|
||||
{l:'b',t:'Разработка серий видеороликов «Безопасность будущего» и по профилактике травматизма'},
|
||||
{l:'c',t:'Рассмотреть возможность выпуска подкаста с участием трудовых династий'},
|
||||
{l:'d',t:'Разработка и распространение постеров, брошюр, информационных рассылок'}]},
|
||||
{id:27,sec:3,b:3,s:'warn',p:30,due:'31.12.2026',done:'—',dname:'Фотофиксация',
|
||||
r:'Директор ДПБ, Генеральные директора филиалов и ДАО',
|
||||
t:'Рассмотрение возможности организации встреч коллектива с получившими производственные травмы работниками (с их согласия) с целью предупреждения аналогичных случаев травматизма.',
|
||||
ai:'1 встреча. Согласия от 3 работников.',h:['01.03 — Создано','01.05 — Встреча']},
|
||||
{id:28,sec:3,b:0,s:'warn',p:25,due:'31.12.2026',done:'—',dname:'Информационное письмо / Пресс-релизы',
|
||||
r:'Директор ДПБ, Генеральные директора филиалов и ДАО / Департамент по коммуникациям',
|
||||
t:'Проведение мероприятий, направленных на пропаганду безопасного выполнения работ через семейные ценности.',
|
||||
ai:'5 писем. Семейный день — август.',h:['01.04 — Создано','15.05 — Письма'],
|
||||
sub:[{l:'a',t:'Направление информационного письма членам семьи работника, положительно отличившегося в вопросах ПБ'},
|
||||
{l:'b',t:'Проведение Семейных дней охраны труда и дней открытых дверей для семей работников'},
|
||||
{l:'c',t:'Проведение конкурса рисунков среди работников и их детей на тему «Спецодежда будущего!»'}]},
|
||||
{id:29,sec:3,b:6,s:'late',p:40,due:'30.06.2026',done:'—',dname:'Корпоративный сборник лучших практик',
|
||||
r:'Директор ДПБ, Генеральные директора филиалов и ДАО',
|
||||
t:'Разработать корпоративный сборник лучших практик по производственной безопасности в формате методического пособия или интерактивного PDF документа, отражающий меры по снижению производственного травматизма и управлению критическими рисками.',
|
||||
ai:'Сборник в разработке. Риск срыва Q2.',h:['01.03 — Создано','01.05 — 4 филиала','01.06 — Эскалация']},
|
||||
{id:30,sec:3,b:7,s:'warn',p:60,due:'31.12.2026',done:'—',dname:'Предложения / План реализации',
|
||||
r:'Директор ДПБ, Генеральные директора филиалов и ДАО',
|
||||
t:'Сбор предложений по совершенствованию системы управления производственной безопасности посредством применения цифровых решений с консолидацией в ДПБ.',
|
||||
ai:'18 предложений. 5 в реализацию.',h:['01.01 — Создано','01.04 — 10','01.06 — 18']},
|
||||
{id:31,sec:3,b:0,s:'warn',p:75,due:'30.06.2026',done:'—',dname:'Видеообзор',
|
||||
r:'Директор ДПБ, Генеральные директора филиалов и ДАО',
|
||||
t:'Разработка видеообзора кейсов происшествий в ПК с учетом специфики деятельности (из доступных на открытых медиа источниках) для наглядной демонстрации и разъяснения работникам о необходимости и важности соблюдения требований безопасности.',
|
||||
ai:'Монтаж 75%. Озвучка — 15 июня.',h:['01.03 — Создано','01.05 — Сценарий','01.06 — 75%']},
|
||||
{id:32,sec:4,b:8,s:'warn',p:70,due:'30.06.2026',done:'—',dname:'Справка / Скриншоты чат-бота',
|
||||
r:'Директор ДПБ, Генеральные директора филиалов и ДАО',
|
||||
t:'Обеспечить применение в филиалах/ДАО Общества чат-бот ИИ ассистент по производственной безопасности с целью упрощения доступа к нормативно-правовым актам Республики Казахстан и ВНД группы Фонда.',
|
||||
ai:'Чат-бот тестируется. Пилот — 15 июня.',h:['01.02 — Создано','01.04 — Разработка','01.06 — Тест']},
|
||||
{id:33,sec:4,b:8,s:'wait',p:15,due:'31.12.2026',done:'—',dname:'Справка / Скриншоты системы',
|
||||
r:'Директор ДПБ, Генеральные директора филиалов и ДАО',
|
||||
t:'Обеспечить применение в филиалах/ДАО Общества интегрированную систему анализа и предупреждения несчастных случаев и критических происшествий, а также платформу по идентификации и оценке рисков перед началом проведения работ на опасных производственных объектах.',
|
||||
ai:'ТЗ согласовывается. Интеграция прорабатывается.',h:['01.04 — Создано','01.06 — ТЗ']},
|
||||
{id:34,sec:4,b:8,s:'wait',p:10,due:'31.12.2026',done:'—',dname:'Справка / Скриншоты HSE паспорта',
|
||||
r:'Директор ДПБ, Генеральные директора филиалов и ДАО',
|
||||
t:'Рассмотреть возможность запуска в филиалах/ДАО Общества электронного HSE паспорта на каждого работника с последующей интеграцией в корпоративную цифровую систему.',
|
||||
ai:'Концепция утверждена. Подрядчик выбран.',h:['01.05 — Создано','01.06 — Концепция']},
|
||||
{id:35,sec:4,b:5,s:'wait',p:8,due:'31.12.2026',done:'—',dname:'Справка / Скриншоты системы',
|
||||
r:'Директор ДПБ, Генеральные директора филиалов и ДАО',
|
||||
t:'Рассмотреть возможность внедрения системы оформления нарядов-допусков на проведение работ повышенной опасности в электронном виде в филиалах/ДАО Общества.',
|
||||
ai:'Предпроект. Анализ рынка — Q3.',h:['01.05 — Создано','01.06 — Анализ']}
|
||||
]
|
||||
function M(idx) { var parts = months[idx].split("-"); return monthNames[parseInt(parts[1])-1] + " " + parts[0]; }
|
||||
|
||||
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'}
|
||||
var 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"}
|
||||
};
|
||||
|
||||
var currentUser = null;
|
||||
var editMonthIdx = 5;
|
||||
|
||||
// Load events from localStorage or use defaults
|
||||
var eventsStr = localStorage.getItem("samruk_events");
|
||||
var events = eventsStr ? JSON.parse(eventsStr) : null;
|
||||
|
||||
// inline events data (35 items)
|
||||
function getDefaultEvents() { return [
|
||||
{id:1,sec:0,b:6,s:"warn",p:45,due:"31.12.2026",done:"\u2014",dname:"Протоколы обучения / Электронная ведомость",
|
||||
r:"Генеральный директор КУ, Генеральные директора филиалов и ДАО",
|
||||
t:"Продолжить проведение обучения и повышения квалификации руководителей и работников компании в соответствии с лучшими международными практиками, ориентированными на специфику условий труда, работы повышенной опасности и требований промышленной безопасности, а также развитие культуры безопасности, включая обучение производственного персонала по курсу \u00abКультура безопасного труда\u00bb, в том числе с применением VR, AR \u2013 технологий и цифровых симуляторов аварийных ситуаций по различным направлениям производственной безопасности (с правом выдачи сертификатов).",
|
||||
ai:"Обучение ведётся по графику. Охвачено 45% персонала. VR-тренажёры развёрнуты в 3 филиалах.",
|
||||
h:["15.01 — Мероприятие создано","01.03 — Запущено обучение","15.05 — VR-симуляторы установлены"]},
|
||||
{id:2,sec:0,b:0,s:"done",p:100,due:"31.03.2026",done:"28.03.2026",dname:"Отчёт о проведённом анализе / Утверждённый ВНД",
|
||||
r:"Директор ДПБ, Генеральный директор ДИТ, Генеральные директора филиалов и ДАО",
|
||||
t:"Провести анализ, в том числе с использованием аналитических платформ (Microsoft Teams, Power BI, Tableau, Qlik и др.), и в случае необходимости, осуществить пересмотр внутренних нормативных документов филиалов/ДАО Общества в соответствии со \u00abСтратегией развития производственной безопасности АО \u00abСамрук-Қазына\u00bb на 2024-2028 гг.\u00bb, включая установку значений ключевых показателей производственной безопасности.",
|
||||
ai:"Анализ завершён в срок. ВНД пересмотрены. Ключевые показатели ПБ установлены.",
|
||||
h:["10.01 — Мероприятие создано","15.02 — Проведён анализ","28.03 — Отчёт утверждён"]},
|
||||
{id:3,sec:0,b:0,s:"warn",p:50,due:"31.12.2026",done:"\u2014",dname:"Протоколы совещаний (a, b, c)",
|
||||
r:"Главный административный директор, Директор ДПБ / Генеральные директора филиалов и ДАО",
|
||||
t:"Организовывать тематические совещания по вопросам производственной безопасности.",
|
||||
ai:"Проведено 2 квартальных совещания. Ежемесячные совещания — выполняется.",
|
||||
h:["10.01 — Мероприятие создано","15.02 — Совещание Q1","15.05 — Совещание Q2"],
|
||||
sub:[{l:"a",t:"Руководство Общества с филиалами/ДАО, не менее 1 раза в квартал, с личным мониторингом показателей эффективности ПБ и статуса исполнения Плана"},
|
||||
{l:"b",t:"Руководство филиалов/ДАО со структурными подразделениями, не менее 1 раза в месяц"},
|
||||
{l:"c",t:"Руководство региональных подразделений/филиалов/ДАО с подрядными организациями, не менее 1 раза в квартал"}]},
|
||||
{id:4,sec:0,b:6,s:"warn",p:55,due:"31.12.2026",done:"\u2014",dname:"Отчёты о проделанной работе / Тесты",
|
||||
r:"Генеральные директора филиалов и ДАО",
|
||||
t:"Продолжить практику проверки знаний в формате тестирования после проведения инструктажей по охране труда в филиалах/ДАО Общества.",
|
||||
ai:"Тестирование внедрено в 6 филиалах. Средний результат — 82%.",h:["01.02 — Создано","01.04 — Внедрено","01.06 — Отчёт"]},
|
||||
{id:5,sec:0,b:0,s:"done",p:100,due:"31.03.2026",done:"25.03.2026",dname:"Информация о нематериальном поощрении",
|
||||
r:"Директор ДПБ, Генеральные директора филиалов и ДАО",
|
||||
t:"Рассмотреть возможность нематериального поощрения филиалов и ДАО Общества, демонстрирующих устойчивое снижение количества несчастных случаев, пожаров и аварий по итогам нескольких и более лет.",
|
||||
ai:"Положение утверждено. Определены 3 филиала-лидера.",h:["15.01 — Проект","01.03 — Согласование","25.03 — Утверждено"]}
|
||||
]; } // end getDefaultEvents — real data loaded from data.json on server
|
||||
|
||||
// Load events: try data.json first, fallback to inline defaults
|
||||
(function loadEvents() {
|
||||
if (events) return; // already loaded from localStorage
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open("GET", "data.json", true);
|
||||
xhr.onload = function() {
|
||||
if (xhr.status === 200) {
|
||||
try { events = JSON.parse(xhr.responseText); } catch(e) { events = getDefaultEvents(); }
|
||||
} else { events = getDefaultEvents(); }
|
||||
localStorage.setItem("samruk_events", JSON.stringify(events));
|
||||
if (currentUser) renderMain();
|
||||
};
|
||||
xhr.onerror = function() {
|
||||
events = getDefaultEvents();
|
||||
localStorage.setItem("samruk_events", JSON.stringify(events));
|
||||
if (currentUser) renderMain();
|
||||
};
|
||||
xhr.send();
|
||||
})();
|
||||
function doLogin(e) {
|
||||
e.preventDefault();
|
||||
var email = document.getElementById("loginEmail").value.trim().toLowerCase();
|
||||
var pass = document.getElementById("loginPass").value;
|
||||
if (users[email] && pass.length >= 1) {
|
||||
currentUser = {email: email, name: users[email].name, branch: users[email].branch, role: users[email].role};
|
||||
localStorage.setItem("samruk_user", JSON.stringify(currentUser));
|
||||
showApp();
|
||||
} else {
|
||||
document.getElementById("loginErr").style.display = "block";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
let currentUser=null, events=JSON.parse(JSON.stringify(baseEvents))
|
||||
function loadState(){
|
||||
const s=localStorage.getItem('samruk_events');if(s) events=JSON.parse(s)
|
||||
const u=localStorage.getItem('samruk_user');if(u){currentUser=JSON.parse(u);showApp()}
|
||||
}
|
||||
function saveState(){localStorage.setItem('samruk_events',JSON.stringify(events))}
|
||||
function saveUser(){localStorage.setItem('samruk_user',JSON.stringify(currentUser))}
|
||||
function doLogin(e){
|
||||
e.preventDefault()
|
||||
const email=document.getElementById('loginEmail').value.trim().toLowerCase()
|
||||
const pass=document.getElementById('loginPass').value
|
||||
if(users[email]&&pass.length>=1){currentUser={email,...users[email]};saveUser();showApp()}
|
||||
else document.getElementById('loginErr').style.display='block'
|
||||
}
|
||||
function doLogout(){localStorage.removeItem('samruk_user');currentUser=null;document.getElementById('loginScreen').style.display='flex';document.getElementById('app').style.display='none'}
|
||||
function showApp(){document.getElementById('loginScreen').style.display='none';document.getElementById('app').style.display='block';document.getElementById('userLabel').innerHTML=`<strong>${currentUser.name}</strong> · ${branches[currentUser.branch]}`;renderAll()}
|
||||
|
||||
function getNotifs(){
|
||||
const my=currentUser.role==='admin'||currentUser.role==='director'?events:events.filter(e=>e.b===currentUser.branch)
|
||||
const n=[]
|
||||
my.forEach(e=>{if(e.s==='late')n.push({type:'danger',msg:`Просрочено: ${e.t.slice(0,60)}...`,time:e.due});if(e.s==='warn'&&e.p<30)n.push({type:'warn',msg:`Низкий прогресс (${e.p}%): ${e.t.slice(0,50)}...`,time:'Сейчас'})})
|
||||
return n
|
||||
}
|
||||
function renderNotifs(){
|
||||
const n=getNotifs(),el=document.getElementById('notifDrop'),cnt=document.getElementById('notifCount')
|
||||
cnt.textContent=n.length;cnt.style.display=n.length?'inline-block':'none'
|
||||
el.innerHTML=n.length?n.map(n=>`<div class="item"><div class="title">${n.type==='danger'?'🔴':'🟡'} ${n.msg}</div><div class="time">${n.time}</div></div>`).join(''):'<div class="empty">Новых уведомлений нет</div>'
|
||||
}
|
||||
function toggleNotif(){renderNotifs();document.getElementById('notifDrop').classList.toggle('open')}
|
||||
|
||||
function sBadge(s){const m={done:'green',warn:'amber',late:'red',wait:'gray'};return`<span class="badge ${m[s]}">${statusMap[s]}</span>`}
|
||||
function pctHtml(p){return`<div class="pct-bar"><div class="track"><div class="fill" style="width:${p}%;background:${p>=80?'var(--green)':p>=40?'var(--amber)':'var(--red)'}"></div></div>${p}%</div>`}
|
||||
|
||||
function getMyEvents(){if(!currentUser)return[];if(currentUser.role==='admin'||currentUser.role==='director')return events;return events.filter(e=>e.b===currentUser.branch)}
|
||||
|
||||
function renderMyEvents(){
|
||||
const sf=document.getElementById('myStatusFilter').value
|
||||
let list=getMyEvents();if(sf)list=list.filter(e=>e.s===sf)
|
||||
const done=list.filter(e=>e.s==='done').length,late=list.filter(e=>e.s==='late').length,warn=list.filter(e=>e.s==='warn').length,wait=list.filter(e=>e.s==='wait').length
|
||||
document.getElementById('myStats').innerHTML=`<div class="stat-card"><div class="lbl">Мои мероприятия</div><div class="num">${list.length}</div></div><div class="stat-card green"><div class="lbl">Исполнено</div><div class="num">${done}</div></div><div class="stat-card amber"><div class="lbl">На контроле</div><div class="num">${warn}</div></div><div class="stat-card red"><div class="lbl">Просрочено</div><div class="num">${late}</div></div><div class="stat-card blue"><div class="lbl">В процессе</div><div class="num">${wait}</div></div>`
|
||||
document.getElementById('myCount').textContent=`Найдено: ${list.length}`
|
||||
document.getElementById('myEventsTable').innerHTML=`<tr><th>№</th><th>Мероприятие</th><th>Раздел</th><th>Срок</th><th>Прогресс</th><th>Статус</th><th></th></tr>${list.map(e=>`<tr><td>${e.id}</td><td style="font-size:12px;max-width:320px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap" title="${e.t}">${e.t}</td><td><span class="badge blue">${['I','II','III','IV','V'][e.sec]}</span></td><td style="font-size:12px">${e.due}</td><td>${pctHtml(e.p)}</td><td>${sBadge(e.s)}</td><td><button class="btn btn-sm" onclick="openEdit(${e.id})">📝</button></td></tr>`).join('')}`
|
||||
}
|
||||
function renderAllEvents(){
|
||||
const search=document.getElementById('allSearch').value.toLowerCase(),sf=document.getElementById('allStatusFilter').value,secF=document.getElementById('allSecFilter').value
|
||||
let list=events;if(search)list=list.filter(e=>e.t.toLowerCase().includes(search)||branches[e.b].toLowerCase().includes(search));if(sf)list=list.filter(e=>e.s===sf);if(secF!=='')list=list.filter(e=>e.sec===parseInt(secF))
|
||||
document.getElementById('allEventsTable').innerHTML=`<tr><th>№</th><th>Мероприятие</th><th>Дивизион</th><th>Раздел</th><th>Срок</th><th>Прогресс</th><th>Статус</th></tr>${list.map(e=>`<tr><td>${e.id}</td><td style="font-size:12px;max-width:300px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap" title="${e.t}">${e.t}</td><td style="font-size:11px">${branches[e.b]}</td><td><span class="badge blue">${['I','II','III','IV','V'][e.sec]}</span></td><td style="font-size:12px">${e.due}</td><td>${pctHtml(e.p)}</td><td>${sBadge(e.s)}</td></tr>`).join('')}`
|
||||
}
|
||||
function renderAnalytics(){
|
||||
let done=events.filter(e=>e.s==='done').length,total=events.length
|
||||
document.getElementById('globalStats').innerHTML=`<div class="stat-card"><div class="lbl">Всего</div><div class="num">${total}</div></div><div class="stat-card green"><div class="lbl">Исполнено</div><div class="num">${done}</div><div class="sub">${Math.round(done/total*100)}%</div></div><div class="stat-card amber"><div class="lbl">На контроле</div><div class="num">${events.filter(e=>e.s==='warn').length}</div></div><div class="stat-card red"><div class="lbl">Просрочено</div><div class="num">${events.filter(e=>e.s==='late').length}</div></div>`
|
||||
document.getElementById('analyticsContent').innerHTML=`<h3>Рейтинг дивизионов</h3>${branches.map((b,i)=>{let items=events.filter(e=>e.b===i),d=items.filter(e=>e.s==='done').length,pct=Math.round(d/Math.max(1,items.length)*100);return`<div class="pct-bar" style="margin-bottom:8px"><span style="width:220px;font-size:13px;font-weight:600">${b}</span><div class="track" style="flex:1"><div class="fill" style="width:${pct}%;background:${pct>=50?'var(--green)':pct>=25?'var(--amber)':'var(--red)'}"></div></div><span style="font-weight:700;font-size:13px;width:60px;text-align:right">${pct}%</span></div>`}).join('')}`
|
||||
function doLogout() {
|
||||
localStorage.removeItem("samruk_user");
|
||||
currentUser = null;
|
||||
document.getElementById("loginScreen").style.display = "flex";
|
||||
document.getElementById("app").style.display = "none";
|
||||
}
|
||||
|
||||
// ===== FILE STORAGE: month-keyed with reports =====
|
||||
// { "2026-01": { report: "текст отчёта", files: [{name,size,type,desc,date,data},...] }, ... }
|
||||
function getMonthData(eventId){ return JSON.parse(localStorage.getItem('samruk_files_'+eventId)||'{}') }
|
||||
function setMonthData(eventId,obj){ localStorage.setItem('samruk_files_'+eventId,JSON.stringify(obj)) }
|
||||
function getSubChecks(eventId){ return JSON.parse(localStorage.getItem('samruk_sub_'+eventId)||'[]') }
|
||||
function setSubChecks(eventId,arr){ localStorage.setItem('samruk_sub_'+eventId,JSON.stringify(arr)) }
|
||||
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];
|
||||
renderMain();
|
||||
}
|
||||
|
||||
// ===== EDIT MODAL =====
|
||||
let editMonthIdx=5 // default June
|
||||
// === MAIN VIEW ===
|
||||
function renderMain() {
|
||||
var myEvs = getMyEvents();
|
||||
var done = 0, late = 0, warn = 0, wait = 0;
|
||||
myEvs.forEach(function(e) {
|
||||
if (e.s === "done") done++;
|
||||
else if (e.s === "late") late++;
|
||||
else if (e.s === "warn") warn++;
|
||||
else wait++;
|
||||
});
|
||||
|
||||
function openEdit(id,monthIdx){
|
||||
if(typeof monthIdx==='number') editMonthIdx=monthIdx
|
||||
const e=events.find(x=>x.id===id);if(!e)return
|
||||
const savedEdits=JSON.parse(localStorage.getItem('samruk_edits_'+e.id)||'{}')
|
||||
const allData=getMonthData(e.id)
|
||||
const subChecks=getSubChecks(e.id)
|
||||
const curMonth=months[editMonthIdx]
|
||||
const curData=allData[curMonth]||{report:'',files:[]}
|
||||
const curFiles=curData.files||[]
|
||||
let totalFiles=0;Object.values(allData).forEach(d=>{totalFiles+=(d.files||[]).length})
|
||||
var html = "";
|
||||
html += '<div class="stats-row">';
|
||||
html += '<div class="stat-card"><div class="lbl">Мои мероприятия</div><div class="num">'+myEvs.length+'</div></div>';
|
||||
html += '<div class="stat-card green"><div class="lbl">Исполнено</div><div class="num">'+done+'</div></div>';
|
||||
html += '<div class="stat-card amber"><div class="lbl">На контроле</div><div class="num">'+warn+'</div></div>';
|
||||
html += '<div class="stat-card red"><div class="lbl">Просрочено</div><div class="num">'+late+'</div></div>';
|
||||
html += '<div class="stat-card blue"><div class="lbl">В процессе</div><div class="num">'+wait+'</div></div>';
|
||||
html += '</div>';
|
||||
|
||||
let subHtml=''
|
||||
if(e.sub&&e.sub.length){
|
||||
subHtml=`<div style="font-weight:600;margin-bottom:8px;font-size:14px">Подпункты мероприятия</div><div class="sub-items">${e.sub.map((s,i)=>{let ch=subChecks.includes(i);return`<div class="sub-item"><input type="checkbox" id="subchk_${i}" ${ch?'checked':''}><span class="sub-label">${s.l})</span><span class="sub-text">${s.t}</span></div>`}).join('')}</div>`
|
||||
html += '<div class="panel"><h3>Реестр мероприятий</h3>';
|
||||
html += '<table><tr><th>№</th><th>Мероприятие</th><th>Раздел</th><th>Срок</th><th>Прогресс</th><th>Статус</th><th></th></tr>';
|
||||
myEvs.forEach(function(e) {
|
||||
var pctColor = e.p >= 80 ? "var(--green)" : (e.p >= 40 ? "var(--amber)" : "var(--red)");
|
||||
var sClass = {done:"green",warn:"amber",late:"red",wait:"gray"}[e.s];
|
||||
html += '<tr><td>'+e.id+'</td>';
|
||||
html += '<td style="font-size:12px;max-width:320px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap" title="'+escHtml(e.t)+'">'+escHtml(e.t)+'</td>';
|
||||
html += '<td><span class="badge blue">'+["I","II","III","IV","V"][e.sec]+'</span></td>';
|
||||
html += '<td style="font-size:12px">'+e.due+'</td>';
|
||||
html += '<td><div class="pct-bar"><div class="track"><div class="fill" style="width:'+e.p+'%;background:'+pctColor+'"></div></div>'+e.p+'%</div></td>';
|
||||
html += '<td><span class="badge '+sClass+'">'+statusMap[e.s]+'</span></td>';
|
||||
html += '<td><button class="btn btn-sm" onclick="openEdit('+e.id+')">📝</button></td></tr>';
|
||||
});
|
||||
html += '</table></div>';
|
||||
|
||||
document.getElementById("mainContent").innerHTML = html;
|
||||
updateNotifs();
|
||||
}
|
||||
|
||||
function getMyEvents() {
|
||||
if (!currentUser || !events) return [];
|
||||
if (currentUser.role === "admin" || currentUser.role === "director") return events;
|
||||
return events.filter(function(e) { return e.b === currentUser.branch; });
|
||||
}
|
||||
|
||||
function escHtml(s) { return s.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,"""); }
|
||||
|
||||
// === NOTIFICATIONS ===
|
||||
function updateNotifs() {
|
||||
var myEvs = getMyEvents();
|
||||
var notifs = [];
|
||||
myEvs.forEach(function(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:"Сейчас"});
|
||||
});
|
||||
var el = document.getElementById("notifDrop");
|
||||
var cnt = document.getElementById("notifCount");
|
||||
cnt.textContent = notifs.length;
|
||||
cnt.style.display = notifs.length ? "inline-block" : "none";
|
||||
el.innerHTML = notifs.length
|
||||
? notifs.map(function(n) { return '<div class="item"><div class="title">'+(n.type==="danger"?"🔴":"🟡")+' '+escHtml(n.msg)+'</div><div class="time">'+n.time+'</div></div>'; }).join("")
|
||||
: '<div class="empty">Новых уведомлений нет</div>';
|
||||
}
|
||||
|
||||
function toggleNotif() {
|
||||
updateNotifs();
|
||||
document.getElementById("notifDrop").classList.toggle("open");
|
||||
}
|
||||
|
||||
// === EDIT MODAL ===
|
||||
function openEdit(id, monthIdx) {
|
||||
if (typeof monthIdx === "number") editMonthIdx = monthIdx;
|
||||
var e = null;
|
||||
for (var i = 0; i < events.length; i++) { if (events[i].id === id) { e = events[i]; break; } }
|
||||
if (!e) return;
|
||||
|
||||
var allData = getMonthData(e.id);
|
||||
var subChecks = getSubChecks(e.id);
|
||||
var curMonth = months[editMonthIdx];
|
||||
var curData = allData[curMonth] || {report: "", files: []};
|
||||
var curFiles = curData.files || [];
|
||||
var totalFiles = 0;
|
||||
for (var k in allData) { if (allData.hasOwnProperty(k)) totalFiles += (allData[k].files || []).length; }
|
||||
|
||||
// Sub-items
|
||||
var subHtml = "";
|
||||
if (e.sub && e.sub.length) {
|
||||
subHtml = '<div style="font-weight:600;margin-bottom:8px;font-size:14px">Подпункты мероприятия</div><div class="sub-items">';
|
||||
e.sub.forEach(function(s, i) {
|
||||
var ch = subChecks.indexOf(i) >= 0;
|
||||
subHtml += '<div class="sub-item"><input type="checkbox" id="subchk_'+i+'" '+(ch?"checked":"")+'><span class="sub-label">'+s.l+')</span><span class="sub-text">'+escHtml(s.t)+'</span></div>';
|
||||
});
|
||||
subHtml += '</div>';
|
||||
}
|
||||
|
||||
let filesHtml=''
|
||||
if(curFiles.length){
|
||||
filesHtml=`<div style="font-weight:600;margin:12px 0 6px;font-size:13px">Файлы за ${M(editMonthIdx)} (${curFiles.length} шт.)</div>`
|
||||
curFiles.forEach((f,i)=>{filesHtml+=`<div class="file-row"><span class="file-info"><span class="file-name" onclick="downloadFile(${e.id},'${curMonth}',${i})">📄 ${f.name}</span>${f.desc?`<span class="file-desc">${f.desc}</span>`:''}</span><span class="file-meta">${(f.size/1024).toFixed(0)} КБ · ${f.date}</span><button class="file-del" onclick="removeFile(${e.id},'${curMonth}',${i})">×</button></div>`})}
|
||||
// Files
|
||||
var filesHtml = "";
|
||||
if (curFiles.length) {
|
||||
filesHtml = '<div style="font-weight:600;margin:12px 0 6px;font-size:13px">Файлы за '+M(editMonthIdx)+' ('+curFiles.length+' шт.)</div>';
|
||||
curFiles.forEach(function(f, i) {
|
||||
filesHtml += '<div class="file-row"><span class="file-info"><span class="file-name" onclick="downloadFile('+e.id+',\''+curMonth+'\','+i+')">📄 '+escHtml(f.name)+'</span>'+(f.desc?'<span class="file-desc">'+escHtml(f.desc)+'</span>':'')+'</span><span class="file-meta">'+(f.size/1024).toFixed(0)+' КБ · '+f.date+'</span><button class="file-del" onclick="removeFile('+e.id+',\''+curMonth+'\','+i+')">×</button></div>';
|
||||
});
|
||||
}
|
||||
|
||||
document.getElementById('editModalContent').innerHTML=`
|
||||
<button class="close" onclick="closeEditModal()">×</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>
|
||||
// Month tabs
|
||||
var monthTabsHtml = '<div class="month-tabs">';
|
||||
months.forEach(function(m, i) {
|
||||
monthTabsHtml += '<span class="month-tab'+(i===editMonthIdx?' active':'')+'" onclick="openEdit('+e.id+','+i+')">'+M(i)+'</span>';
|
||||
});
|
||||
monthTabsHtml += '</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>
|
||||
var html = '';
|
||||
html += '<button class="close" onclick="closeEditModal()">×</button>';
|
||||
html += '<span class="badge blue">Раздел '+["I","II","III","IV","V"][e.sec]+'</span>';
|
||||
html += '<h3 style="margin:8px 0">'+escHtml(e.t)+'</h3>';
|
||||
html += '<div class="meta-row">';
|
||||
html += '<div class="fld">Дивизион<strong>'+escHtml(branches[e.b])+'</strong></div>';
|
||||
html += '<div class="fld">Ответственный<strong>'+escHtml(e.r)+'</strong></div>';
|
||||
html += '<div class="fld">Срок<strong>'+e.due+'</strong></div>';
|
||||
html += '<div class="fld">Факт<strong>'+e.done+'</strong></div>';
|
||||
html += '</div>';
|
||||
|
||||
${subHtml}
|
||||
html += '<div class="field"><label>Статус</label><select id="editStatus" onchange="autoProgress()">';
|
||||
["wait","warn","late","done"].forEach(function(s) {
|
||||
html += '<option value="'+s+'"'+(e.s===s?' selected':'')+'>'+statusMap[s]+'</option>';
|
||||
});
|
||||
html += '</select></div>';
|
||||
html += '<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>';
|
||||
html += '<div class="field"><label>Комментарий</label><textarea id="editComment" placeholder="Комментарий к статусу..."></textarea></div>';
|
||||
|
||||
<div style="border-top:1px solid var(--gray-200);padding-top:16px;margin-top:12px">
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:12px">
|
||||
<span style="font-weight:600;font-size:14px">📎 Отчётность по месяцам</span>
|
||||
<span style="font-size:12px;color:var(--gray-500)">Файлов: ${totalFiles}</span>
|
||||
</div>
|
||||
html += subHtml;
|
||||
|
||||
<div class="month-tabs">${months.map((m,i)=>`<span class="month-tab${i===editMonthIdx?' active':''}" onclick="openEdit(${e.id},${i})">${M(i)}</span>`).join('')}</div>
|
||||
html += '<div style="border-top:1px solid var(--gray-200);padding-top:16px;margin-top:12px">';
|
||||
html += '<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:12px"><span style="font-weight:600;font-size:14px">📎 Отчётность по месяцам</span><span style="font-size:12px;color:var(--gray-500)">Файлов: '+totalFiles+'</span></div>';
|
||||
html += monthTabsHtml;
|
||||
html += '<div class="field" style="margin-top:12px"><label>Текст отчёта за '+M(editMonthIdx)+'</label><textarea id="monthReport" placeholder="Опишите ход исполнения, результаты, проблемы... Можно без прикрепления файлов." style="min-height:80px">'+escHtml(curData.report||"")+'</textarea></div>';
|
||||
html += filesHtml;
|
||||
html += '<div class="upload-row"><input type="text" id="fileDesc" placeholder="Описание файла (акт, протокол, фото...)" style="flex:2"><input type="file" id="editFileInput" multiple><button class="btn btn-sm" id="uploadBtn" onclick="uploadFiles('+e.id+',\''+curMonth+'\')">Загрузить</button></div>';
|
||||
html += '<p style="font-size:11px;color:var(--gray-500);margin-top:6px">Формы завершения: '+escHtml(e.dname)+'</p>';
|
||||
html += '</div>';
|
||||
|
||||
<div class="field" style="margin-top:12px">
|
||||
<label>Текст отчёта за ${M(editMonthIdx)}</label>
|
||||
<textarea id="monthReport" placeholder="Опишите ход исполнения, результаты, проблемы... Можно без прикрепления файлов." style="min-height:80px">${curData.report||''}</textarea>
|
||||
</div>
|
||||
html += '<div class="ai-block"><h4>🤖 Вывод ИИ-агента</h4>'+escHtml(e.ai)+'</div>';
|
||||
html += '<div style="font-weight:600;margin:8px 0 4px;font-size:14px">История:</div><div>';
|
||||
e.h.forEach(function(h) { html += '<div class="history-item"><div class="dot"></div>'+escHtml(h)+'</div>'; });
|
||||
html += '</div>';
|
||||
|
||||
${filesHtml}
|
||||
html += '<div style="margin-top:20px;display:flex;gap:12px"><button class="btn" onclick="saveEdit('+e.id+',\''+curMonth+'\')">Сохранить</button><button class="btn btn-outline" onclick="closeEditModal()">Отмена</button></div>';
|
||||
|
||||
<div class="upload-row">
|
||||
<input type="text" id="fileDesc" placeholder="Описание файла (акт, протокол, фото...)" style="flex:2">
|
||||
<input type="file" id="editFileInput" multiple>
|
||||
<button class="btn btn-sm" id="uploadBtn" onclick="uploadFiles(${e.id},'${curMonth}')">Загрузить</button>
|
||||
</div>
|
||||
<p style="font-size:11px;color:var(--gray-500);margin-top:6px">Формы завершения: ${e.dname}</p>
|
||||
</div>
|
||||
|
||||
<div class="ai-block"><h4>🤖 Вывод ИИ-агента</h4>${e.ai}</div>
|
||||
<div style="font-weight:600;margin:8px 0 4px;font-size:14px">История:</div>
|
||||
<div>${e.h.map(h=>`<div class="history-item"><div class="dot"></div>${h}</div>`).join('')}</div>
|
||||
|
||||
<div style="margin-top:20px;display:flex;gap:12px">
|
||||
<button class="btn" onclick="saveEdit(${e.id},'${curMonth}')">Сохранить</button>
|
||||
<button class="btn btn-outline" onclick="closeEditModal()">Отмена</button>
|
||||
</div>`
|
||||
document.getElementById('editModalOverlay').classList.add('open')
|
||||
document.getElementById("editModalContent").innerHTML = html;
|
||||
document.getElementById("editModalOverlay").classList.add("open");
|
||||
}
|
||||
|
||||
function uploadFiles(eventId, monthKey){
|
||||
const fi=document.getElementById('editFileInput')
|
||||
const desc=document.getElementById('fileDesc').value.trim()
|
||||
const btn=document.getElementById('uploadBtn')
|
||||
if(!fi||!fi.files||!fi.files.length) return
|
||||
btn.textContent='Загружается...';btn.disabled=true
|
||||
const MAX=4*1024*1024,allData=getMonthData(eventId)
|
||||
if(!allData[monthKey]) allData[monthKey]={report:'',files:[]}
|
||||
const arr=allData[monthKey].files
|
||||
let processed=0,skipped=0
|
||||
function finish(){
|
||||
try{setMonthData(eventId,allData)}catch(e){alert('⚠️ Хранилище переполнено')}
|
||||
if(skipped) alert(`⚠️ ${skipped} файл(ов) > 4 МБ пропущены`)
|
||||
closeEditModal();openEdit(eventId)
|
||||
function autoProgress() {
|
||||
var s = document.getElementById("editStatus");
|
||||
var p = document.getElementById("editProgress");
|
||||
if (!s || !p) return;
|
||||
if (s.value === "done") p.value = 100;
|
||||
else if (s.value === "wait") p.value = 0;
|
||||
document.getElementById("pVal").textContent = p.value + "%";
|
||||
}
|
||||
|
||||
function saveEdit(id, monthKey) {
|
||||
var e = null;
|
||||
for (var i = 0; i < events.length; i++) { if (events[i].id === id) { e = events[i]; break; } }
|
||||
if (!e) return;
|
||||
|
||||
e.s = document.getElementById("editStatus").value;
|
||||
e.p = parseInt(document.getElementById("editProgress").value);
|
||||
var comment = (document.getElementById("editComment").value || "").trim();
|
||||
var monthReport = document.getElementById("monthReport");
|
||||
monthReport = monthReport ? monthReport.value : "";
|
||||
|
||||
// Save monthly report
|
||||
if (monthKey) {
|
||||
var allData = getMonthData(id);
|
||||
if (!allData[monthKey]) allData[monthKey] = {report: "", files: []};
|
||||
allData[monthKey].report = monthReport;
|
||||
setMonthData(id, allData);
|
||||
}
|
||||
if(!fi.files.length){finish();return}
|
||||
for(const f of fi.files){
|
||||
if(f.size>MAX){skipped++;processed++;if(processed===fi.files.length)finish();continue}
|
||||
const r=new FileReader()
|
||||
r.onload=function(ev){
|
||||
arr.push({name:f.name,size:f.size,type:f.type,desc,date:new Date().toLocaleDateString(),data:ev.target.result})
|
||||
processed++
|
||||
if(processed===fi.files.length) finish()
|
||||
}
|
||||
r.onerror=function(){processed++;if(processed===fi.files.length)finish()}
|
||||
r.readAsDataURL(f)
|
||||
|
||||
// Save sub checks
|
||||
if (e.sub && e.sub.length) {
|
||||
var checks = [];
|
||||
e.sub.forEach(function(_, i) {
|
||||
var el = document.getElementById("subchk_"+i);
|
||||
if (el && el.checked) checks.push(i);
|
||||
});
|
||||
setSubChecks(id, checks);
|
||||
}
|
||||
|
||||
var now = new Date().toLocaleDateString();
|
||||
e.h.push(now + " — " + currentUser.name + ": статус " + statusMap[e.s] + ", прогресс " + e.p + "%" + (comment ? " — комм.: " + comment : ""));
|
||||
if (e.s === "done" && e.done === "\u2014") e.done = now;
|
||||
|
||||
localStorage.setItem("samruk_events", JSON.stringify(events));
|
||||
closeEditModal();
|
||||
renderMain();
|
||||
updateNotifs();
|
||||
}
|
||||
|
||||
function closeEditModal() { document.getElementById("editModalOverlay").classList.remove("open"); }
|
||||
|
||||
// === FILE STORAGE ===
|
||||
function getMonthData(eventId) {
|
||||
var raw = localStorage.getItem("samruk_files_"+eventId);
|
||||
return raw ? JSON.parse(raw) : {};
|
||||
}
|
||||
function setMonthData(eventId, obj) {
|
||||
localStorage.setItem("samruk_files_"+eventId, JSON.stringify(obj));
|
||||
}
|
||||
function getSubChecks(eventId) {
|
||||
var raw = localStorage.getItem("samruk_sub_"+eventId);
|
||||
return raw ? JSON.parse(raw) : [];
|
||||
}
|
||||
function setSubChecks(eventId, arr) {
|
||||
localStorage.setItem("samruk_sub_"+eventId, JSON.stringify(arr));
|
||||
}
|
||||
|
||||
function uploadFiles(eventId, monthKey) {
|
||||
var fi = document.getElementById("editFileInput");
|
||||
if (!fi || !fi.files || !fi.files.length) return;
|
||||
|
||||
var desc = (document.getElementById("fileDesc").value || "").trim();
|
||||
var btn = document.getElementById("uploadBtn");
|
||||
btn.textContent = "Загружается..."; btn.disabled = true;
|
||||
|
||||
var MAX = 4 * 1024 * 1024;
|
||||
var allData = getMonthData(eventId);
|
||||
if (!allData[monthKey]) allData[monthKey] = {report: "", files: []};
|
||||
var arr = allData[monthKey].files;
|
||||
var processed = 0, skipped = 0;
|
||||
|
||||
function finish() {
|
||||
try { setMonthData(eventId, allData); } catch(e) { alert("Хранилище переполнено"); }
|
||||
if (skipped) alert(skipped + " файл(ов) > 4 МБ пропущены");
|
||||
closeEditModal(); openEdit(eventId);
|
||||
}
|
||||
|
||||
for (var i = 0; i < fi.files.length; i++) {
|
||||
(function(f) {
|
||||
if (f.size > MAX) { skipped++; processed++; if (processed === fi.files.length) finish(); return; }
|
||||
var reader = new FileReader();
|
||||
reader.onload = function(ev) {
|
||||
arr.push({name: f.name, size: f.size, type: f.type, desc: desc, date: new Date().toLocaleDateString(), data: ev.target.result});
|
||||
processed++;
|
||||
if (processed === fi.files.length) finish();
|
||||
};
|
||||
reader.onerror = function() { processed++; if (processed === fi.files.length) finish(); };
|
||||
reader.readAsDataURL(f);
|
||||
})(fi.files[i]);
|
||||
}
|
||||
}
|
||||
|
||||
function downloadFile(eventId, monthKey, idx){
|
||||
const allData=getMonthData(eventId),arr=allData[monthKey]?.files
|
||||
if(!arr||!arr[idx]||!arr[idx].data) return
|
||||
const f=arr[idx],a=document.createElement('a');a.href=f.data;a.download=f.name
|
||||
document.body.appendChild(a);a.click();document.body.removeChild(a)
|
||||
function downloadFile(eventId, monthKey, idx) {
|
||||
var allData = getMonthData(eventId);
|
||||
var arr = allData[monthKey] ? allData[monthKey].files : null;
|
||||
if (!arr || !arr[idx] || !arr[idx].data) return;
|
||||
var f = arr[idx];
|
||||
var a = document.createElement("a");
|
||||
a.href = f.data; a.download = f.name;
|
||||
document.body.appendChild(a); a.click(); document.body.removeChild(a);
|
||||
}
|
||||
|
||||
function removeFile(eventId, monthKey, idx){
|
||||
const allData=getMonthData(eventId)
|
||||
if(!allData[monthKey]||!allData[monthKey].files) return
|
||||
allData[monthKey].files.splice(idx,1);setMonthData(eventId,allData)
|
||||
closeEditModal();openEdit(eventId)
|
||||
function removeFile(eventId, monthKey, idx) {
|
||||
var allData = getMonthData(eventId);
|
||||
if (!allData[monthKey] || !allData[monthKey].files) return;
|
||||
allData[monthKey].files.splice(idx, 1);
|
||||
setMonthData(eventId, allData);
|
||||
closeEditModal(); openEdit(eventId);
|
||||
}
|
||||
|
||||
function saveEdit(id, monthKey){
|
||||
const e=events.find(x=>x.id===id);if(!e)return
|
||||
e.s=document.getElementById('editStatus').value
|
||||
e.p=parseInt(document.getElementById('editProgress').value)
|
||||
const comment=document.getElementById('editComment').value.trim()
|
||||
const monthReport=document.getElementById('monthReport')?.value||''
|
||||
// === INIT ===
|
||||
document.getElementById("editModalOverlay").addEventListener("click", function(e) { if (e.target === this) closeEditModal(); });
|
||||
document.addEventListener("keydown", function(e) { if (e.key === "Escape") { closeEditModal(); document.getElementById("notifDrop").classList.remove("open"); } });
|
||||
document.addEventListener("click", function(e) { if (!e.target.closest(".notif-btn") && !e.target.closest(".notif-drop")) document.getElementById("notifDrop").classList.remove("open"); });
|
||||
|
||||
// Save monthly report text
|
||||
if(monthKey){
|
||||
const allData=getMonthData(id)
|
||||
if(!allData[monthKey]) allData[monthKey]={report:'',files:[]}
|
||||
allData[monthKey].report=monthReport
|
||||
setMonthData(id,allData)
|
||||
}
|
||||
|
||||
if(e.sub&&e.sub.length){
|
||||
const checks=[]
|
||||
e.sub.forEach((_,i)=>{if(document.getElementById('subchk_'+i)?.checked)checks.push(i)})
|
||||
setSubChecks(id,checks)
|
||||
}
|
||||
|
||||
const now=new Date().toLocaleDateString()
|
||||
e.h.push(`${now} — ${currentUser.name}: статус ${statusMap[e.s]}, прогресс ${e.p}%${comment?' — комм.: '+comment:''}`)
|
||||
if(e.s==='done'&&e.done==='—')e.done=now
|
||||
localStorage.setItem('samruk_edits_'+id,JSON.stringify({comment,editedBy:currentUser.name,editedAt:now}))
|
||||
saveState();closeEditModal();renderAll();renderNotifs()
|
||||
var savedUser = localStorage.getItem("samruk_user");
|
||||
if (savedUser) {
|
||||
try { currentUser = JSON.parse(savedUser); showApp(); } catch(e) {}
|
||||
}
|
||||
|
||||
function closeEditModal(){document.getElementById('editModalOverlay').classList.remove('open')}
|
||||
|
||||
function autoProgress(){
|
||||
const s=document.getElementById('editStatus'),p=document.getElementById('editProgress')
|
||||
if(!s||!p)return
|
||||
if(s.value==='done')p.value=100;else if(s.value==='wait')p.value=0
|
||||
document.getElementById('pVal').textContent=p.value+'%'
|
||||
}
|
||||
|
||||
document.querySelectorAll('.tab-btn').forEach(btn=>{btn.addEventListener('click',function(){document.querySelectorAll('.tab-btn').forEach(b=>b.classList.remove('active'));document.querySelectorAll('.tab-content').forEach(c=>c.classList.remove('active'));this.classList.add('active');document.getElementById('tab-'+this.dataset.tab).classList.add('active');if(this.dataset.tab==='analytics')renderAnalytics()})})
|
||||
document.getElementById('editModalOverlay').addEventListener('click',function(e){if(e.target===this)closeEditModal()})
|
||||
document.addEventListener('keydown',e=>{if(e.key==='Escape'){closeEditModal();document.getElementById('notifDrop').classList.remove('open')}})
|
||||
document.addEventListener('click',e=>{if(!e.target.closest('.notif-btn')&&!e.target.closest('.notif-drop'))document.getElementById('notifDrop').classList.remove('open')})
|
||||
|
||||
function renderAll(){renderMyEvents();renderAllEvents();renderNotifs()}
|
||||
loadState();if(currentUser)showApp()
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Loading…
Reference in New Issue
Block a user