samruk-ai-agent/index.html

543 lines
34 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>ИИ-агент ПБ — Казахтелеком</title>
<style>
:root{--ink:#0F1218;--cyan:#00E5FF;--cyan-50:#E8FCFF;--white:#fff;--gray-500:#5B6573;--gray-100:#F2F4F7;--gray-200:#E5E7EB;--green:#10B981;--red:#EF4444;--amber:#F59E0B;--blue:#3B82F6}
*{box-sizing:border-box;margin:0;padding:0}
body{font:14px/1.5 -apple-system,BlinkMacSystemFont,"Segoe UI",Inter,system-ui,sans-serif;color:var(--ink);background:var(--gray-100);min-height:100vh}
input,select,textarea,button{font:inherit}
.btn{display:inline-block;background:var(--cyan);color:var(--ink);padding:12px 24px;border-radius:8px;font-weight:700;font-size:15px;border:none;cursor:pointer}
.btn:hover{background:#1be5ff}
.btn-sm{padding:6px 12px;font-size:13px}
.btn-outline{background:transparent;border:2px solid var(--ink);color:var(--ink)}
.badge{display:inline-block;padding:4px 10px;border-radius:100px;font-size:12px;font-weight:600}
.badge.green{background:#D1FAE5;color:#065F46}.badge.amber{background:#FEF3C7;color:#92400E}.badge.red{background:#FEE2E2;color:#991B1B}.badge.blue{background:#DBEAFE;color:#1E40AF}.badge.gray{background:var(--gray-100);color:var(--gray-700)}
.panel{background:var(--white);border-radius:12px;border:1px solid var(--gray-200);padding:24px;margin-bottom:20px}
.panel h3{font-size:17px;font-weight:700;margin-bottom:12px}
table{width:100%;border-collapse:collapse}
th,td{padding:10px 14px;text-align:left;font-size:13px}
th{font-weight:600;color:var(--gray-500);font-size:11px;text-transform:uppercase;letter-spacing:.5px;border-bottom:2px solid var(--gray-200)}
td{border-bottom:1px solid var(--gray-200)}tr:hover td{background:var(--cyan-50)}
.pct-bar{display:flex;align-items:center;gap:8px;font-size:13px}
.pct-bar .track{width:80px;height:7px;background:var(--gray-200);border-radius:10px;overflow:hidden}
.pct-bar .fill{height:100%;border-radius:10px}
#loginScreen{display:flex;align-items:center;justify-content:center;min-height:100vh;background:var(--ink)}
.login-box{background:var(--white);border-radius:16px;padding:48px 40px;width:440px;max-width:90vw;text-align:center}
.login-box h1{font-size:24px;font-weight:800;margin-bottom:4px}.login-box h1 span{color:var(--cyan)}
.login-box .sub{color:var(--gray-500);font-size:14px;margin-bottom:32px}
.login-box label{display:block;text-align:left;font-size:13px;font-weight:600;margin-bottom:6px}
.login-box input[type=email],.login-box input[type=password]{width:100%;padding:12px 16px;border:1px solid var(--gray-200);border-radius:8px;font-size:15px;margin-bottom:20px}
.login-box .hint{font-size:12px;color:var(--gray-500);margin-top:-12px;margin-bottom:16px;text-align:left}
#app{display:none}
.topbar{background:var(--white);border-bottom:1px solid var(--gray-200);padding:0 32px;height:60px;display:flex;align-items:center;justify-content:space-between}
.topbar .brand{font-weight:800;font-size:16px}.topbar .brand span{color:var(--cyan)}
.topbar .right{display:flex;align-items:center;gap:16px}
.topbar .user-info{font-size:13px;color:var(--gray-500)}.topbar .user-info strong{color:var(--ink)}
.notif-btn{position:relative;background:none;border:none;font-size:22px;cursor:pointer;padding:4px}
.notif-btn .badge-count{position:absolute;top:-2px;right:-4px;background:var(--red);color:#fff;border-radius:100px;font-size:10px;padding:1px 6px;font-weight:700}
.logout-btn{font-size:13px;color:var(--gray-500);cursor:pointer;border:none;background:none}.logout-btn:hover{color:var(--red)}
.notif-drop{position:absolute;top:56px;right:32px;width:380px;max-width:90vw;background:var(--white);border:1px solid var(--gray-200);border-radius:12px;box-shadow:0 8px 32px rgba(0,0,0,.12);z-index:300;display:none;max-height:400px;overflow-y:auto}
.notif-drop.open{display:block}
.notif-drop .item{padding:14px 18px;border-bottom:1px solid var(--gray-100);font-size:13px}
.notif-drop .item .title{font-weight:600;margin-bottom:2px}
.notif-drop .item .time{font-size:11px;color:var(--gray-500)}
.notif-drop .empty{padding:24px;text-align:center;color:var(--gray-500)}
.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.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}
.modal{background:var(--white);border-radius:16px;max-width:760px;width:94vw;max-height:90vh;overflow-y:auto;padding:32px}
.modal h3{font-size:20px;margin-bottom:6px;padding-right:30px}
.modal .close{float:right;background:none;border:none;font-size:28px;cursor:pointer;line-height:1;color:var(--gray-500);margin:-8px -8px 0 0}
.modal .field{margin-bottom:14px}
.modal .field label{display:block;font-size:13px;font-weight:600;margin-bottom:4px;color:var(--gray-500)}
.modal .field input,.modal .field select,.modal .field textarea{width:100%;padding:10px 14px;border:1px solid var(--gray-200);border-radius:8px;font-size:14px}
.modal .field textarea{min-height:70px;resize:vertical}
.modal .meta-row{display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-bottom:12px}
.modal .meta-row .fld{font-size:13px;color:var(--gray-500)}
.modal .meta-row .fld strong{display:block;font-size:14px;color:var(--ink);margin-top:2px}
.ai-block{background:var(--cyan-50);border-radius:8px;padding:14px;margin:16px 0;font-size:14px}
.ai-block h4{font-size:14px;margin-bottom:4px}
.history-item{display:flex;gap:8px;font-size:12px;padding:3px 0;color:var(--gray-500);align-items:baseline}
.history-item .dot{width:7px;height:7px;border-radius:50%;background:var(--cyan);flex-shrink:0;margin-top:5px}
.sub-items{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-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{display:flex;align-items:center;gap:10px;padding:8px 12px;background:var(--gray-100);border-radius:8px;margin-bottom:6px;font-size:13px}
.file-row .file-info{flex:1;min-width:0}
.file-row .file-name{font-weight:600;cursor:pointer;color:var(--ink)}.file-row .file-name:hover{color:var(--cyan)}
.file-row .file-desc{font-size:11px;color:var(--gray-500)}
.file-row .file-meta{font-size:11px;color:var(--gray-500);white-space:nowrap}
.file-row .file-del{background:none;border:none;color:var(--red);cursor:pointer;font-size:16px;padding:2px}
.upload-row{display:flex;gap:8px;align-items:flex-end;flex-wrap:wrap;padding:12px;border:2px dashed var(--gray-200);border-radius:8px}
.upload-row input[type=text]{flex:1;min-width:150px;padding:8px 12px;border:1px solid var(--gray-200);border-radius:6px;font-size:13px}
.upload-row input[type=file]{font-size:12px;max-width:220px}
@media(max-width:768px){
.main{padding:16px}.topbar{padding:0 16px}
.modal .meta-row{grid-template-columns:1fr}
.stats-row{grid-template-columns:1fr 1fr}
.notif-drop{right:8px;width:calc(100vw - 16px)}
.modal{padding:20px}
}
</style>
</head>
<body>
<div id="loginScreen">
<form class="login-box" onsubmit="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">admin@telecom.kz / ahmetov@telecom.kz / serikov@telecom.kz — пароль любой</p>
<label>Пароль</label>
<input type="password" id="loginPass" placeholder="••••••••" required>
<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>
<div id="app">
<div class="topbar">
<div class="brand"><span>ИИ-Агент</span> ПБ</div>
<div class="right">
<span class="user-info" id="userLabel"></span>
<div style="position:relative">
<button class="notif-btn" onclick="toggleNotif()">🔔<span class="badge-count" id="notifCount">0</span></button>
<div class="notif-drop" id="notifDrop"></div>
</div>
<span class="logout-btn" onclick="doLogout()">Выйти</span>
</div>
</div>
<div class="main" id="mainContent"></div>
</div>
<div class="modal-overlay" id="editModalOverlay"><div class="modal" id="editModalContent"></div></div>
<script>
"use strict";
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 = ["Янв","Фев","Мар","Апр","Май","Июн","Июл","Авг","Сен","Окт","Ноя","Дек"];
function M(idx) { var parts = months[idx].split("-"); return monthNames[parseInt(parts[1])-1] + " " + parts[0]; }
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;
}
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];
renderMain();
}
// === 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++;
});
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>';
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,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;"); }
// === 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>';
}
// 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>';
});
}
// 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>';
var html = '';
html += '<button class="close" onclick="closeEditModal()">&times;</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>';
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>';
html += subHtml;
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>';
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>';
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>';
document.getElementById("editModalContent").innerHTML = html;
document.getElementById("editModalOverlay").classList.add("open");
}
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);
}
// 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) {
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) {
var allData = getMonthData(eventId);
if (!allData[monthKey] || !allData[monthKey].files) return;
allData[monthKey].files.splice(idx, 1);
setMonthData(eventId, allData);
closeEditModal(); openEdit(eventId);
}
// === 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"); });
var savedUser = localStorage.getItem("samruk_user");
if (savedUser) {
try { currentUser = JSON.parse(savedUser); showApp(); } catch(e) {}
}
</script>
</body>
</html>