v42: упрощённая версия — чище, меньше кода
This commit is contained in:
parent
7ebb2950d0
commit
de294ed219
450
index.html
450
index.html
@ -3,321 +3,237 @@
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>ИИ-агент ПБ — Казахтелеком</title>
|
||||
<title>План ПБ — Казахтелеком 2026</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}
|
||||
:root{--b:#0F1218;--c:#00E5FF;--w:#fff;--g5:#5B6573;--g1:#F2F4F7;--g2:#E5E7EB;--gn:#10B981;--rd:#EF4444;--am:#F59E0B}
|
||||
*{box-sizing:border-box;margin:0;padding:0}
|
||||
body{font:14px/1.5 Arial,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:7px 14px;font-size:13px}.btn-outline{background:transparent;border:2px solid var(--ink);color:var(--ink)}.btn-outline:hover{background:var(--ink);color:var(--white)}
|
||||
.badge{display:inline-block;padding:4px 10px;border-radius:100px;font-size:12px;font-weight:600}
|
||||
.badge.green{background:#D1FAE5;color:#065F46}.badge.amber{background:#FEF3C7;color:#92400E}.badge.red{background:#FEE2E2;color:#991B1B}.badge.blue{background:#DBEAFE;color:#1E40AF}
|
||||
.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;border-bottom:2px solid var(--gray-200);cursor:pointer;user-select:none;white-space:nowrap}th:hover{color:var(--ink)}td{border-bottom:1px solid var(--gray-200)}
|
||||
tr.deadline-warn td{background:#FFFDF5}tr.deadline-late td{background:#FFF5F5}
|
||||
.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}
|
||||
body{font:14px/1.4 Arial,sans-serif;color:var(--b);background:var(--g1)}
|
||||
input,select,textarea,button{font:inherit;outline:none}
|
||||
.btn{background:var(--c);color:var(--b);padding:10px 20px;border-radius:8px;font-weight:700;font-size:14px;border:none;cursor:pointer}.btn:hover{opacity:.85}
|
||||
.btn-sm{padding:6px 12px;font-size:12px}.btn-red{background:var(--rd);color:#fff}.btn-gn{background:var(--gn);color:#fff}
|
||||
|
||||
#loginBox{display:flex;align-items:center;justify-content:center;min-height:100vh;background:var(--ink)}
|
||||
#loginBox>div{background:var(--white);border-radius:16px;padding:48px 40px;width:440px;max-width:90vw;text-align:center}
|
||||
#loginBox h1{font-size:24px;font-weight:800;margin-bottom:4px}#loginBox h1 span{color:var(--cyan)}
|
||||
#loginBox .sub{color:var(--gray-500);font-size:14px;margin-bottom:32px}#loginBox .hint{font-size:12px;color:var(--gray-500);margin-top:-12px;margin-bottom:16px;text-align:left}
|
||||
#loginBox label{display:block;text-align:left;font-size:13px;font-weight:600;margin-bottom:6px}
|
||||
#loginBox input{padding:12px 16px;border:1px solid var(--gray-200);border-radius:8px;font-size:15px;margin-bottom:20px;width:100%}
|
||||
#loginBox .err{color:var(--red);font-size:13px;margin-bottom:12px;display:none}
|
||||
#login{display:flex;align-items:center;justify-content:center;min-height:100vh;background:var(--b)}
|
||||
#login>div{background:var(--w);border-radius:16px;padding:40px 36px;width:400px;max-width:90vw;text-align:center}
|
||||
#login h1{font-size:22px;font-weight:800;margin-bottom:4px}#login h1 span{color:var(--c)}
|
||||
#login p{color:var(--g5);font-size:13px;margin-bottom:28px}
|
||||
#login input{display:block;width:100%;padding:12px 14px;border:1px solid var(--g2);border-radius:8px;font-size:14px;margin-bottom:14px}
|
||||
#login .err{color:var(--rd);font-size:12px;margin-bottom:10px;display:none}
|
||||
|
||||
#app{display:none}
|
||||
.topbar{background:var(--white);border-bottom:1px solid var(--gray-200);padding:0 24px;height:56px;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:14px}.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:52px;right:24px;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)}
|
||||
#app{display:none;max-width:1200px;margin:0 auto;padding:16px}
|
||||
.top{display:flex;justify-content:space-between;align-items:center;padding:12px 0;border-bottom:2px solid var(--g2);margin-bottom:16px}
|
||||
.top b{font-size:18px}.top b span{color:var(--c)}
|
||||
.top .r{display:flex;align-items:center;gap:12px;font-size:13px;color:var(--g5)}
|
||||
|
||||
.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;max-width:1400px}
|
||||
.stats-row{display:grid;grid-template-columns:repeat(auto-fit,minmax(190px,1fr));gap:14px;margin-bottom:20px}
|
||||
.stat-card{background:var(--white);border-radius:12px;padding:18px 22px;border:1px solid var(--gray-200)}.stat-card .lbl{font-size:12px;color:var(--gray-500)}.stat-card .num{font-size:26px;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:10px;margin-bottom:14px;flex-wrap:wrap;align-items:center}.filters select,.filters input{padding:9px 12px;border:1px solid var(--gray-200);border-radius:8px;font-size:13px;background:var(--white)}.filters input{min-width:220px}.filters select{min-width:140px}
|
||||
.tabs{display:flex;gap:4px;margin-bottom:16px}
|
||||
.tab{padding:10px 20px;border:none;background:var(--w);cursor:pointer;font-size:14px;font-weight:600;color:var(--g5);border-radius:8px 8px 0 0}.tab.on{color:var(--b);border-bottom:3px solid var(--c)}
|
||||
.pg{display:none}.pg.on{display:block}
|
||||
|
||||
.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:28px}
|
||||
.modal h3{font-size:20px;margin-bottom:6px;padding-right:30px}.modal .close{float:right;background:none;border:none;font-size:28px;cursor:pointer;color:var(--gray-500);margin:-4px -4px 0 0}
|
||||
.modal .field{margin-bottom:12px}.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:9px 12px;border:1px solid var(--gray-200);border-radius:8px;font-size:14px}.modal .field textarea{min-height:64px;resize:vertical}
|
||||
.modal .meta-row{display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-bottom:10px}.modal .meta-row .fld{font-size:12px;color:var(--gray-500)}.modal .meta-row .fld strong{display:block;font-size:13px;color:var(--ink);margin-top:2px}
|
||||
.ai-block{background:var(--cyan-50);border-radius:8px;padding:12px;margin:14px 0;font-size:13px}.ai-block h4{font-size:14px;margin-bottom:4px}
|
||||
.history-item{display:flex;gap:8px;font-size:12px;padding:2px 0;color:var(--gray-500);align-items:baseline}.history-item .dot{width:6px;height:6px;border-radius:50%;background:var(--cyan);flex-shrink:0;margin-top:5px}
|
||||
.sub-item{display:flex;align-items:center;gap:10px;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:16px;height:16px;cursor:pointer;accent-color:var(--cyan)}
|
||||
.month-tabs{display:flex;gap:5px;flex-wrap:wrap;margin-bottom:12px}.month-tab{padding:5px 12px;border:1px solid var(--gray-200);border-radius:100px;font-size:12px;font-weight:600;cursor:pointer;background:var(--white);color:var(--gray-500)}.month-tab:hover{border-color:var(--cyan)}.month-tab.active{background:var(--cyan);color:var(--ink)}
|
||||
.file-row{display:flex;align-items:center;gap:8px;padding:8px 12px;background:var(--gray-100);border-radius:8px;margin-bottom:4px;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-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:15px;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;margin-top:6px}.upload-row input[type=text]{flex:1;min-width:140px;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:200px}
|
||||
.sort-arrow{font-size:10px;margin-left:2px;color:var(--cyan)}
|
||||
@media(max-width:768px){.main{padding:12px}.topbar{padding:0 12px}.modal{padding:20px}.stats-row{grid-template-columns:1fr 1fr}.notif-drop{right:8px;width:calc(100vw-16px)}.filters input{min-width:140px}.filters select{min-width:100px}}
|
||||
.card{background:var(--w);border-radius:12px;padding:20px;margin-bottom:14px;border:1px solid var(--g2)}
|
||||
.card h3{font-size:16px;margin-bottom:8px}
|
||||
.row{display:flex;gap:12px;flex-wrap:wrap;margin-bottom:12px}
|
||||
.stat{background:var(--w);border-radius:10px;padding:16px 20px;border:1px solid var(--g2);min-width:120px;flex:1;text-align:center}
|
||||
.stat .n{font-size:26px;font-weight:800}.stat .l{font-size:12px;color:var(--g5)}.stat.r .n{color:var(--rd)}.stat.g .n{color:var(--gn)}.stat.a .n{color:var(--am)}
|
||||
|
||||
table{width:100%;border-collapse:collapse}
|
||||
th,td{padding:8px 12px;text-align:left;font-size:13px}
|
||||
th{font-weight:600;color:var(--g5);font-size:11px;text-transform:uppercase;border-bottom:2px solid var(--g2);cursor:pointer}th:hover{color:var(--b)}
|
||||
td{border-bottom:1px solid var(--g2)}
|
||||
tr.warn td{background:#FFFDF5}tr.late td{background:#FFF5F5}
|
||||
.badge{display:inline-block;padding:3px 8px;border-radius:100px;font-size:11px;font-weight:700}
|
||||
.badge.g{background:#D1FAE5;color:#065F46}.badge.a{background:#FEF3C7;color:#92400E}.badge.r{background:#FEE2E2;color:#991B1B}.badge.b{background:#DBEAFE;color:#1E40AF}
|
||||
|
||||
.fr{display:flex;gap:8px;margin-bottom:10px;flex-wrap:wrap;align-items:center}
|
||||
.fr input,.fr select{padding:8px 12px;border:1px solid var(--g2);border-radius:6px;font-size:13px;background:var(--w)}
|
||||
.fr input{min-width:200px}
|
||||
|
||||
.modal-o{position:fixed;inset:0;background:rgba(0,0,0,.5);z-index:99;display:none;align-items:center;justify-content:center}.modal-o.on{display:flex}
|
||||
.modal{background:var(--w);border-radius:14px;max-width:700px;width:94vw;max-height:90vh;overflow-y:auto;padding:24px}
|
||||
.modal .x{float:right;border:none;background:none;font-size:24px;cursor:pointer;color:var(--g5)}
|
||||
.modal label{display:block;font-size:12px;font-weight:600;color:var(--g5);margin-bottom:3px}
|
||||
.modal input,.modal select,.modal textarea{width:100%;padding:8px 12px;border:1px solid var(--g2);border-radius:6px;font-size:13px;margin-bottom:10px}
|
||||
.modal textarea{min-height:60px;resize:vertical}
|
||||
.meta{display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-bottom:8px;font-size:12px;color:var(--g5)}.meta strong{display:block;color:var(--b);font-size:13px}
|
||||
.mt{display:flex;gap:4px;flex-wrap:wrap;margin-bottom:10px}.mt span{padding:4px 10px;border:1px solid var(--g2);border-radius:100px;font-size:11px;font-weight:600;cursor:pointer}.mt span.on{background:var(--c);color:var(--b)}.mt span:hover{border-color:var(--c)}
|
||||
.si{display:flex;align-items:center;gap:8px;padding:8px 12px;background:var(--g1);border-radius:6px;margin-bottom:4px;font-size:12px}.si .n{font-weight:700;color:var(--c);font-size:14px;min-width:18px}
|
||||
.fl{display:flex;align-items:center;gap:6px;padding:6px 10px;background:var(--g1);border-radius:6px;margin-bottom:3px;font-size:12px}.fl .nm{font-weight:600;cursor:pointer}.fl .nm:hover{color:var(--c)}.fl .sz{font-size:10px;color:var(--g5);white-space:nowrap}
|
||||
.up{display:flex;gap:6px;align-items:flex-end;flex-wrap:wrap;padding:10px;border:2px dashed var(--g2);border-radius:6px;margin-top:6px}.up input[type=text]{flex:1;min-width:120px;padding:6px 10px;border:1px solid var(--g2);border-radius:4px;font-size:12px}
|
||||
.ai{background:#E8FCFF;border-radius:6px;padding:10px;margin:10px 0;font-size:12px}
|
||||
.hi{font-size:11px;color:var(--g5);padding:2px 0}.hi .d{display:inline-block;width:5px;height:5px;border-radius:50%;background:var(--c);margin-right:4px;vertical-align:middle}
|
||||
|
||||
@media(max-width:600px){#app{padding:8px}.row{flex-direction:column}.stat{min-width:auto}.meta{grid-template-columns:1fr}}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div id="loginBox">
|
||||
<div id="login">
|
||||
<div>
|
||||
<h1><span>ИИ-Агент</span> ПБ</h1>
|
||||
<p class="sub">АО «Казахтелеком» — мониторинг производственной безопасности</p>
|
||||
<label>Корпоративная почта</label>
|
||||
<input id="loginEmail" placeholder="curator@telecom.kz">
|
||||
<p class="hint">curator@telecom.kz / dpp@telecom.kz / serikov@telecom.kz / ahmetov@telecom.kz</p>
|
||||
<label>Пароль</label>
|
||||
<input id="loginPass" type="password" placeholder="любой">
|
||||
<p class="err" id="loginErr">Неверная почта или пароль</p>
|
||||
<button class="btn" style="width:100%;margin-top:8px" onclick="doLogin()">Войти</button>
|
||||
<h1><span>План ПБ</span> 2026</h1>
|
||||
<p>АО «Казахтелеком»</p>
|
||||
<input id="lem" placeholder="curator@telecom.kz">
|
||||
<input id="lpw" type="password" placeholder="Пароль (любой)">
|
||||
<p class="err" id="lerr">Неверная почта</p>
|
||||
<button class="btn" style="width:100%" onclick="doLogin()">Войти</button>
|
||||
</div>
|
||||
</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>
|
||||
<button class="logout-btn" onclick="doLogout()">Выйти</button>
|
||||
</div>
|
||||
<div class="top">
|
||||
<b><span>План ПБ</span> 2026</b>
|
||||
<div class="r"><span id="ul"></span> <button class="btn btn-sm btn-red" onclick="doLogout()">Выйти</button></div>
|
||||
</div>
|
||||
<div class="main">
|
||||
<div class="tabs">
|
||||
<button class="tab-btn active" data-tab="dashboard" onclick="switchTab('dashboard')">📊 Дашборд</button>
|
||||
<button class="tab-btn" data-tab="myevents" onclick="switchTab('myevents')">📋 Мероприятия</button>
|
||||
<button class="tab-btn" data-tab="analytics" onclick="switchTab('analytics')">📈 Аналитика</button>
|
||||
<button class="tab-btn" data-tab="journal" onclick="switchTab('journal')">📝 Журнал</button>
|
||||
</div>
|
||||
<div class="tab-content active" id="tab-dashboard"></div>
|
||||
<div class="tab-content" id="tab-myevents"></div>
|
||||
<div class="tab-content" id="tab-analytics"></div>
|
||||
<div class="tab-content" id="tab-journal"></div>
|
||||
|
||||
<div class="tabs">
|
||||
<button class="tab on" data-pg="ev">📋 Мероприятия</button>
|
||||
<button class="tab" data-pg="an">📊 Аналитика</button>
|
||||
<button class="tab" data-pg="rp">📥 Отчёты</button>
|
||||
</div>
|
||||
|
||||
<div class="pg on" id="pg-ev"></div>
|
||||
<div class="pg" id="pg-an"></div>
|
||||
<div class="pg" id="pg-rp"></div>
|
||||
</div>
|
||||
|
||||
<div class="modal-overlay" id="editModalOverlay"><div class="modal" id="editModalContent"></div></div>
|
||||
<div class="modal-o" id="mo"><div class="modal" id="mc"></div></div>
|
||||
|
||||
<script>
|
||||
var sections=["I. Люди","II. Оборудование","III. Аварии и ЧС","IV. Информ. работа","V. ИИ и цифровизация"];
|
||||
var branches=["Дирекция производственной безопасности","Объединение «Дивизион «Сеть»»","Дивизион по корпоративному бизнесу","Дивизион по розничному бизнесу","Сервисная фабрика","Дирекция «Телеком Комплект»","Корпоративный университет","Дирекция управления проектами","Дивизион цифрового бизнеса"];
|
||||
var regions=["Центральный регион","Алматинский регион","Южный регион","Северный регион","Восточный регион","Западный регион"];
|
||||
var sm={done:"Исполнено",warn:"На контроле",late:"Просрочено"};
|
||||
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 sec=["I. Люди","II. Оборудование","III. Аварии и ЧС","IV. Информ. работа","V. ИИ"];
|
||||
var br=["Дирекция ПБ","Дивизион «Сеть»","Корпоративный бизнес","Розничный бизнес","Сервисная фабрика","Телеком Комплект","Корпоративный университет","Управление проектами","Цифровой бизнес"];
|
||||
var reg=["Центральный","Алматинский","Южный","Северный","Восточный","Западный"];
|
||||
var st={done:"Исполнено",warn:"На контроле",late:"Просрочено"};
|
||||
var ms=["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 mn=["Янв","Фев","Мар","Апр","Май","Июн","Июл","Авг","Сен","Окт","Ноя","Дек"];
|
||||
function M(i){var p=months[i].split("-");return mn[parseInt(p[1])-1]+" "+p[0]}
|
||||
function M(i){return mn[parseInt(ms[i].split("-")[1])-1]+" "+ms[i].split("-")[0]}
|
||||
function esc(s){return s.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">")}
|
||||
function sBadge(s){var m={done:"green",warn:"amber",late:"red"};return'<span class="badge '+m[s]+'">'+sm[s]+'</span>'}
|
||||
function pctHtml(p){var c=p>=80?"var(--green)":p>=40?"var(--amber)":"var(--red)";return'<div class="pct-bar"><div class="track"><div class="fill" style="width:'+p+'%;background:'+c+'"></div></div>'+p+'%</div>'}
|
||||
function sb(s){var m={done:"g",warn:"a",late:"r"};return'<span class="badge '+m[s]+'">'+st[s]+'</span>'}
|
||||
function ph(p){var c=p>=80?"var(--gn)":p>=40?"var(--am)":"var(--rd)";return'<div class="fl"><div style="width:60px;height:6px;background:var(--g2);border-radius:6px;overflow:hidden"><div style="width:'+p+'%;height:100%;background:'+c+';border-radius:6px"></div></div>'+p+'%</div>'}
|
||||
|
||||
var users={
|
||||
"curator@telecom.kz":{name:"Куратор Плана",branch:0,role:"curator"},
|
||||
"dpp@telecom.kz":{name:"Директор ДПБ",branch:0,role:"branch"},
|
||||
"admin@telecom.kz":{name:"Администратор",branch:0,role:"admin"},
|
||||
"ahmetov@telecom.kz":{name:"Ахметов К.Т.",branch:6,role:"branch"},
|
||||
"serikov@telecom.kz":{name:"Сериков А.М.",branch:1,role:"branch"},
|
||||
"nurlanov@telecom.kz":{name:"Нурланов Д.С.",branch:8,role:"branch"},
|
||||
"aliev@telecom.kz":{name:"Алиев Г.С.",branch:4,role:"branch"},
|
||||
"tulegenov@telecom.kz":{name:"Тулегенов Е.А.",branch:2,role:"branch"},
|
||||
"saparov@telecom.kz":{name:"Сапаров А.Д.",branch:3,role:"branch"},
|
||||
"maratov@telecom.kz":{name:"Маратов Ж.К.",branch:5,role:"branch"},
|
||||
"iskakov@telecom.kz":{name:"Искаков Р.Н.",branch:7,role:"branch"}
|
||||
};
|
||||
var U={"curator@telecom.kz":{n:"Куратор",b:0,r:"cur"},"dpp@telecom.kz":{n:"Директор ДПБ",b:0,r:"br"},"ahmetov@telecom.kz":{n:"Ахметов К.Т.",b:6,r:"br"},"serikov@telecom.kz":{n:"Сериков А.М.",b:1,r:"br"},"nurlanov@telecom.kz":{n:"Нурланов Д.С.",b:8,r:"br"},"aliev@telecom.kz":{n:"Алиев Г.С.",b:4,r:"br"},"tulegenov@telecom.kz":{n:"Тулегенов Е.А.",b:2,r:"br"},"saparov@telecom.kz":{n:"Сапаров А.Д.",b:3,r:"br"},"maratov@telecom.kz":{n:"Маратов Ж.К.",b:5,r:"br"},"iskakov@telecom.kz":{n:"Искаков Р.Н.",b:7,r:"br"}};
|
||||
var cu=null,cm=5,cr=0,esi=-1,ex={},sc=null,sd=1;
|
||||
|
||||
var curUser=null,curMonth=5,curRegion=0,editSubIdx=-1;
|
||||
var expandedEvents={},cache={},sortCol=null,sortDir=1;
|
||||
|
||||
function getMy(){return events||[]}
|
||||
|
||||
// ===== EVENTS DATA (embedded, no async load) =====
|
||||
function loadEvents(){
|
||||
var s=localStorage.getItem("samruk_ev");
|
||||
if(s){try{events=JSON.parse(s);return}catch(e){}}
|
||||
events=getDefaultEvents();saveEv();
|
||||
}
|
||||
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:"Провести анализ, в том числе с использованием аналитических платформ, и осуществить пересмотр внутренних нормативных документов филиалов/ДАО Общества в соответствии со Стратегией развития производственной безопасности на 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 филиалах. Средний результат \u2014 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:"warn",p:15,due:"30.09.2026",done:"\u2014",dname:"Акт / Well-being / Скрининг",r:"Директор ДПБ, Управляющий директор по персоналу",t:"Провести анализ эффективности мероприятий по охране здоровья.",ai:"Медосмотры \u2014 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:"warn",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:"warn",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:"warn",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:"warn",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:"warn",p:15,due:"31.12.2026",done:"\u2014",dname:"Справка / Скриншоты",r:"Директор ДПБ",t:"Применение системы анализа и предупреждения НС, платформы идентификации рисков.",ai:"ТЗ согласовывается.",h:["01.04 — Создано"]},
|
||||
{id:34,sec:4,b:8,s:"warn",p:10,due:"31.12.2026",done:"\u2014",dname:"Справка / Скриншоты",r:"Директор ДПБ",t:"Запуск электронного HSE паспорта с интеграцией в корпоративную систему.",ai:"Концепция утверждена.",h:["01.05 — Создано"]},
|
||||
{id:35,sec:4,b:5,s:"warn",p:8,due:"31.12.2026",done:"\u2014",dname:"Справка / Скриншоты",r:"Директор ДПБ",t:"Внедрение системы электронных нарядов-допусков на работы повышенной опасности.",ai:"Предпроект.",h:["01.05 — Создано"]}
|
||||
];}
|
||||
|
||||
function saveEv(){localStorage.setItem("samruk_ev",JSON.stringify(events||[]))}
|
||||
|
||||
// ===== FILE STORAGE =====
|
||||
function getMD(id,ri,si){ri=ri||0;var k=si>=0?"sf_"+id+"_s"+si+"_r"+ri:"sf_"+id+"_r"+ri;var r=localStorage.getItem(k);return r?JSON.parse(r):{}}
|
||||
function setMD(id,o,ri,si){ri=ri||0;var k=si>=0?"sf_"+id+"_s"+si+"_r"+ri:"sf_"+id+"_r"+ri;localStorage.setItem(k,JSON.stringify(o))}
|
||||
function getSC(id){var r=localStorage.getItem("ss_"+id);return r?JSON.parse(r):[]}
|
||||
function setSC(id,a){localStorage.setItem("ss_"+id,JSON.stringify(a))}
|
||||
function storageUsed(){var t=0;for(var i=0;i<localStorage.length;i++){var k=localStorage.key(i);if(k.indexOf("sf_")===0||k.indexOf("ss_")===0)t+=localStorage.getItem(k).length*2}return t}
|
||||
function fmtStorage(){var b=storageUsed();return b>1048576?(b/1048576).toFixed(1)+" МБ":(b/1024).toFixed(0)+" КБ"}
|
||||
function gsc(id){var r=localStorage.getItem("ss_"+id);return r?JSON.parse(r):[]}
|
||||
function ssc(id,a){localStorage.setItem("ss_"+id,JSON.stringify(a))}
|
||||
|
||||
// ===== AUTH =====
|
||||
function doLogin(){var em=document.getElementById("loginEmail").value.trim().toLowerCase();if(users[em]){curUser={email:em,name:users[em].name,branch:users[em].branch,role:users[em].role};localStorage.setItem("samruk_u",JSON.stringify(curUser));showApp()}else{document.getElementById("loginErr").style.display="block"}}
|
||||
function doLogout(){localStorage.removeItem("samruk_u");curUser=null;document.getElementById("loginBox").style.display="flex";document.getElementById("app").style.display="none"}
|
||||
function showApp(){document.getElementById("loginBox").style.display="none";document.getElementById("app").style.display="block";var label=curUser.name+" · "+(curUser.role==="curator"||curUser.role==="admin"?"Все":branches[curUser.branch]);document.getElementById("userLabel").innerHTML="<strong>"+label+"</strong>";renderAll()}
|
||||
var ev=null;
|
||||
function le(){var s=localStorage.getItem("se");if(s){try{ev=JSON.parse(s);return}catch(e){}}ev=[{id:1,sec:0,b:6,s:"warn",p:45,due:"31.12.2026",dn:"—",dnm:"Протоколы / Ведомость",r:"Генеральный директор КУ",t:"Обучение и повышение квалификации (VR, AR, симуляторы)",ai:"Охвачено 45% персонала.",h:["15.01 — Создано","01.03 — Запущено"]},{id:2,sec:0,b:0,s:"done",p:100,due:"31.03.2026",dn:"28.03.2026",dnm:"Отчёт / ВНД",r:"Директор ДПБ, ДИТ",t:"Анализ и пересмотр ВНД согласно Стратегии ПБ",ai:"Завершён в срок.",h:["10.01 — Создано","28.03 — Утверждён"]},{id:3,sec:0,b:0,s:"warn",p:50,due:"31.12.2026",dn:"—",dnm:"Протоколы",r:"Главный админ. директор, Директор ДПБ",t:"Тематические совещания по вопросам ПБ",ai:"2 квартальных совещания.",h:["10.01 — Создано"],sub:[{l:"a",t:"С филиалами/ДАО, не менее 1 раза в квартал"},{l:"b",t:"Со структурными подразделениями, ежемесячно"},{l:"c",t:"С подрядными организациями, ежеквартально"}]},{id:4,sec:0,b:6,s:"warn",p:55,due:"31.12.2026",dn:"—",dnm:"Отчёты / Тесты",r:"Ген. директора филиалов",t:"Проверка знаний в формате тестирования",ai:"82% средний результат.",h:["01.02 — Создано"]},{id:5,sec:0,b:0,s:"done",p:100,due:"31.03.2026",dn:"25.03.2026",dnm:"Информация",r:"Директор ДПБ",t:"Нематериальное поощрение филиалов",ai:"Положение утверждено.",h:["25.03 — Утверждено"]}];se()}
|
||||
function se(){localStorage.setItem("se",JSON.stringify(ev||[]))}
|
||||
|
||||
// ===== NOTIFS =====
|
||||
function notifsUpdate(){var my=getMy(),n=[],now=new Date();my.forEach(function(e){if(e.s==="late")n.push({m:"🔴 "+e.t.slice(0,45)+"...",t:e.due});if(e.s==="warn"&&e.p<30)n.push({m:"🟡 "+e.t.slice(0,45)+"...",t:"Сейчас"});if(e.s!=="done"&&e.s!=="late"){var parts=e.due.split(".");if(parts.length===3){var due=new Date(parseInt(parts[2]),parseInt(parts[1])-1,parseInt(parts[0]));var days=(due-now)/86400000;if(days>0&&days<=7)n.push({m:"⏰ "+Math.round(days)+" дн: "+e.t.slice(0,40)+"...",t:e.due})}}});var el=document.getElementById("notifDrop"),c=document.getElementById("notifCount");c.textContent=n.length;c.style.display=n.length?"inline-block":"none";el.innerHTML=n.length?n.map(function(x){return'<div class="item"><div class="title">'+esc(x.m)+'</div><div class="time">'+x.t+'</div></div>'}).join(""):'<div class="empty">Нет уведомлений</div>'}
|
||||
function toggleNotif(){notifsUpdate();document.getElementById("notifDrop").classList.toggle("open")}
|
||||
function doLogin(){var e=document.getElementById("lem").value.trim().toLowerCase();if(U[e]){cu={em:e,n:U[e].n,b:U[e].b,r:U[e].r};localStorage.setItem("su",JSON.stringify(cu));show()}else document.getElementById("lerr").style.display="block"}
|
||||
function doLogout(){localStorage.removeItem("su");cu=null;document.getElementById("login").style.display="flex";document.getElementById("app").style.display="none"}
|
||||
function show(){document.getElementById("login").style.display="none";document.getElementById("app").style.display="block";document.getElementById("ul").innerHTML="<b>"+cu.n+"</b> · "+(cu.r==="cur"?"Все":br[cu.b]);renderAll()}
|
||||
|
||||
// ===== TABS with caching =====
|
||||
function switchTab(n){document.querySelectorAll(".tab-btn").forEach(function(b){b.classList.remove("active")});document.querySelector('[data-tab="'+n+'"]').classList.add("active");document.querySelectorAll(".tab-content").forEach(function(c){c.classList.remove("active")});document.getElementById("tab-"+n).classList.add("active");if(!cache[n]){if(n==="dashboard")renderDashboard();else if(n==="myevents")renderMyEvents();else if(n==="analytics")renderAnalytics();else if(n==="journal")renderJournal();cache[n]=true}}
|
||||
function invalidateCache(){cache={}}
|
||||
function switchPg(n){document.querySelectorAll(".tab").forEach(function(t){t.classList.remove("on")});document.querySelector('[data-pg="'+n+'"]').classList.add("on");document.querySelectorAll(".pg").forEach(function(p){p.classList.remove("on")});document.getElementById("pg-"+n).classList.add("on");if(n==="ev")re();else if(n==="an")ra();else if(n==="rp")rr()}
|
||||
document.querySelectorAll(".tab").forEach(function(t){t.addEventListener("click",function(){switchPg(this.dataset.pg)})});
|
||||
|
||||
// ===== DEADLINE CLASS =====
|
||||
function deadlineClass(e){if(e.s==="late")return"deadline-late";if(e.s==="done")return"";var p=e.due.split(".");if(p.length===3){var due=new Date(parseInt(p[2]),parseInt(p[1])-1,parseInt(p[0]));var days=(due-new Date())/86400000;if(days<=30)return"deadline-warn"}return""}
|
||||
function dls(e){if(e.s==="late")return"late";if(e.s==="done")return"";var p=e.due.split(".");if(p.length===3){var d=new Date(parseInt(p[2]),parseInt(p[1])-1,parseInt(p[0]));if((d-new Date())/86400000<=30)return"warn"}return""}
|
||||
|
||||
// ===== DASHBOARD =====
|
||||
function renderDashboard(){var my=getMy(),done=0,late=0,warn=0;my.forEach(function(e){if(e.s==="done")done++;else if(e.s==="late")late++;else warn++});var dp=my.length?Math.round(done/my.length*100):0;
|
||||
var h='<div class="stats-row"><div class="stat-card"><div class="lbl">Мероприятий</div><div class="num">'+my.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>';
|
||||
h+='<div class="panel"><h3>По разделам</h3>';var sc=[0,0,0,0,0],sd=[0,0,0,0,0];my.forEach(function(e){sc[e.sec]++;if(e.s==="done")sd[e.sec]++});sections.forEach(function(s,i){var p=sc[i]?Math.round(sd[i]/sc[i]*100):0;h+='<div class="pct-bar" style="margin-bottom:6px"><span style="width:160px;font-size:13px;font-weight:600">'+s+'</span><div class="track" style="flex:1"><div class="fill" style="width:'+p+'%;background:var(--cyan)"></div></div><span style="font-weight:700;font-size:13px">'+p+'%</span></div>'});h+='</div>';
|
||||
h+='<div class="panel"><h3>Динамика</h3><div style="height:120px;background:var(--gray-100);border-radius:8px;display:flex;align-items:flex-end;gap:8px;padding:16px 16px 8px"><div style="flex:1;background:var(--green);border-radius:4px 4px 0 0;height:50%"></div><div style="flex:1;background:var(--green);border-radius:4px 4px 0 0;height:65%"></div><div style="flex:1;background:var(--cyan);border-radius:4px 4px 0 0;height:75%"></div><div style="flex:1;background:var(--cyan);border-radius:4px 4px 0 0;height:'+dp+'%"></div></div><div style="display:flex;gap:8px;padding:8px 16px 0;font-size:11px;color:var(--gray-500);text-align:center"><span style="flex:1">Q1</span><span style="flex:1">Q2</span><span style="flex:1">Q3</span><span style="flex:1">Q4</span></div></div>';
|
||||
h+='<div class="panel"><h3>📥 Сводный отчёт</h3><div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin-bottom:10px"><select id="rptFrom">'+months.map(function(m,i){return'<option value="'+i+'">'+M(i)+'</option>'}).join("")+'</select><span>—</span><select id="rptTo">'+months.map(function(m,i){return'<option value="'+i+'"'+(i===11?" selected":"")+'>'+M(i)+'</option>'}).join("")+'</select><button class="btn btn-sm" onclick="downloadCSV()">CSV</button><button class="btn btn-sm btn-outline" onclick="downloadHTML()">HTML</button></div><div style="font-size:12px;color:var(--gray-500);margin-bottom:8px">Хранилище: '+fmtStorage()+'</div><button class="btn btn-sm" style="background:var(--green);color:#fff;margin-right:4px" onclick="exportAll()">💾 Сохранить всё</button><button class="btn btn-sm btn-outline" style="margin-right:4px" onclick="document.getElementById(\'impF\').click()">📥 Загрузить</button><input type="file" id="impF" accept=".json" style="display:none" onchange="importAll(this)"><button class="btn btn-sm" style="background:var(--red);color:#fff" onclick="clearAll()">🗑 Очистить</button></div>';
|
||||
if(curUser.role==="curator"||curUser.role==="admin"){h+='<div class="panel"><h3>📋 Сводка по филиалам</h3><table><tr><th>Филиал</th><th>Меропр.</th><th>С отчётами</th><th>Файлов</th></tr>';branches.forEach(function(b,bi){var its=events.filter(function(e){return e.b===bi}),wr=0,tf=0;its.forEach(function(e){var has=false;regions.forEach(function(r,ri){var d=getMD(e.id,ri,-1);for(var k in d){if(d.hasOwnProperty(k)&&d[k]){if(d[k].report)has=true;tf+=(d[k].files||[]).length}}if(e.sub)e.sub.forEach(function(s,si){var sd=getMD(e.id,ri,si);for(var sk in sd){if(sd.hasOwnProperty(sk)&&sd[sk]){if(sd[sk].report)has=true;tf+=(sd[sk].files||[]).length}}});});if(has)wr++});h+='<tr><td><strong>'+b+'</strong></td><td>'+its.length+'</td><td>'+(wr?wr+' ✅':"—")+'</td><td>'+(tf||"—")+'</td></tr>'});h+='</table></div>'}document.getElementById("tab-dashboard").innerHTML=h}
|
||||
|
||||
// ===== CSV/HTML =====
|
||||
function downloadCSV(){var from=parseInt(document.getElementById("rptFrom").value),to=parseInt(document.getElementById("rptTo").value);var my=getMy(),csv="\uFEFF№;Филиал;Мероприятие;Подпункт;Регион;Раздел;Статус;Прогресс;Срок;Факт;Отчёт;Файлы\n";my.forEach(function(e){function add(subL,subI){regions.forEach(function(r,ri){var rep="",fls="",d=getMD(e.id,ri,subI);for(var i=from;i<=to;i++){var m=months[i];if(d[m]){if(d[m].report)rep+=M(i)+": "+d[m].report.replace(/"/g,'""')+"; ";if(d[m].files&&d[m].files.length)fls+=M(i)+": "+d[m].files.map(function(f){return f.name}).join(", ")+"; "}}csv+=e.id+';'+branches[e.b]+';"'+e.t.replace(/"/g,'""')+'";'+(subL||"общ")+';'+r+';'+sections[e.sec]+';'+(sm[e.s]||"—")+';'+e.p+'%;'+e.due+';'+(e.done||"—")+';"'+rep+'";"'+fls+'"\n'})}add("",-1);if(e.sub)e.sub.forEach(function(s,i){add(s.l,i)})});var a=document.createElement("a");a.href=URL.createObjectURL(new Blob([csv],{type:"text/csv"}));a.download="otchet_"+M(from)+"-"+M(to)+".csv";a.click();addLog("скачал CSV")}
|
||||
function downloadHTML(){var from=parseInt(document.getElementById("rptFrom").value),to=parseInt(document.getElementById("rptTo").value);var my=getMy(),h='<!DOCTYPE html><html><head><meta charset="utf-8"><title>Отчёт ПБ</title><style>body{font:14px/1.5 Arial;max-width:1100px;margin:0 auto;padding:24px}.ev{border:1px solid #ddd;border-radius:8px;padding:16px;margin-bottom:16px}.badge{display:inline-block;padding:2px 8px;border-radius:4px;font-size:11px;font-weight:700}.g{background:#D1FAE5;color:#065F46}.a{background:#FEF3C7;color:#92400E}.r{background:#FEE2E2;color:#991B1B}.month{background:#f5f5f5;padding:8px 12px;border-radius:4px;margin:6px 0}ul{list-style:"📄 ";margin-left:20px}</style></head><body><h1>Сводный отчёт ПБ</h1><p>Период: '+M(from)+' \u2014 '+M(to)+'</p>';my.forEach(function(e){var cl={done:"g",warn:"a",late:"r"}[e.s];h+='<div class="ev"><h3>'+e.id+'. '+esc(e.t)+'</h3><p>Филиал: '+branches[e.b]+' | Раздел: '+sections[e.sec]+' | Срок: '+e.due+' | Прогресс: '+e.p+'% <span class="badge '+cl+'">'+(sm[e.s]||"—")+'</span></p>';regions.forEach(function(r,ri){var d=getMD(e.id,ri,-1);for(var i=from;i<=to;i++){var m=months[i];if(d[m]&&(d[m].report||(d[m].files&&d[m].files.length))){h+='<div class="month"><strong>'+M(i)+' — '+r+'</strong>';if(d[m].report)h+='<p>'+esc(d[m].report)+'</p>';if(d[m].files&&d[m].files.length)h+='<ul>'+d[m].files.map(function(f){return'<li>'+esc(f.name)+(f.desc?' — '+esc(f.desc):'')+' ('+(f.size/1024).toFixed(0)+' КБ)</li>'}).join("")+'</ul>';h+='</div>'}}});if(e.sub)e.sub.forEach(function(s,i){regions.forEach(function(r,ri){var sd=getMD(e.id,ri,i);for(var j=from;j<=to;j++){var m=months[j];if(sd[m]&&(sd[m].report||(sd[m].files&&sd[m].files.length))){h+='<div style="border-left:3px solid #00E5FF;padding-left:12px;margin:6px 0"><strong>'+s.l+') '+esc(s.t)+'</strong><div class="month"><strong>'+M(j)+' — '+r+'</strong>';if(sd[m].report)h+='<p>'+esc(sd[m].report)+'</p>';if(sd[m].files&&sd[m].files.length)h+='<ul>'+sd[m].files.map(function(f){return'<li>'+esc(f.name)+(f.desc?' — '+esc(f.desc):'')+' ('+(f.size/1024).toFixed(0)+' КБ)</li>'}).join("")+'</ul>';h+='</div></div>'}}})});h+='</div>'});h+='</body></html>';try{var a=document.createElement("a");a.href=URL.createObjectURL(new Blob(["\uFEFF"+h],{type:"text/html"}));a.download="otchet_"+M(from)+"-"+M(to)+".html";a.click();addLog("скачал HTML")}catch(e){alert("Отчёт слишком большой. Попробуйте меньший период.")}}
|
||||
|
||||
// ===== MY EVENTS with search, filter, sort, colors =====
|
||||
function toggleExpand(eid){expandedEvents[eid]=!expandedEvents[eid];renderMyEvents()}
|
||||
|
||||
function renderMyEvents(){invalidateCache();
|
||||
var my=getMy(),sf=document.getElementById("mySF");sf=sf?sf.value:"";
|
||||
var search=document.getElementById("mySearch");search=search?search.value.toLowerCase():"";
|
||||
var bf=document.getElementById("myBF");bf=bf?bf.value:"";
|
||||
var list=my;
|
||||
// ===== EVENTS PAGE =====
|
||||
function re(){
|
||||
var sf=document.getElementById("sf2");sf=sf?sf.value:"";
|
||||
var sr=document.getElementById("sr2");sr=sr?sr.value.toLowerCase():"";
|
||||
var bf=document.getElementById("bf2");bf=bf?bf.value:"";
|
||||
var list=ev||[];
|
||||
if(sf)list=list.filter(function(e){return e.s===sf});
|
||||
if(search)list=list.filter(function(e){return e.t.toLowerCase().indexOf(search)>=0||branches[e.b].toLowerCase().indexOf(search)>=0});
|
||||
if(sr)list=list.filter(function(e){return e.t.toLowerCase().indexOf(sr)>=0||br[e.b].toLowerCase().indexOf(sr)>=0});
|
||||
if(bf)list=list.filter(function(e){return e.b===parseInt(bf)});
|
||||
// Sort
|
||||
if(sortCol){list.sort(function(a,b){var va=a[sortCol],vb=b[sortCol];if(typeof va==="string")va=va.toLowerCase(),vb=vb.toLowerCase();return va>vb?sortDir:va<vb?-sortDir:0})}
|
||||
if(sc){list.sort(function(a,b){var va=a[sc],vb=b[sc];if(typeof va==="string")va=va.toLowerCase(),vb=vb.toLowerCase();return va>vb?sd:va<vb?-sd:0})}
|
||||
|
||||
var h='<div class="panel" style="border-radius:0 0 12px 12px"><div class="filters"><input id="mySearch" placeholder="Поиск по названию или филиалу..." oninput="renderMyEvents()"><select id="mySF" onchange="renderMyEvents()"><option value="">Все статусы</option><option value="done">Исполнено</option><option value="warn">На контроле</option><option value="late">Просрочено</option></select><select id="myBF" onchange="renderMyEvents()"><option value="">Все филиалы</option>'+branches.map(function(b,i){return'<option value="'+i+'">'+b+'</option>'}).join("")+'</select><span style="font-size:12px;color:var(--gray-500);margin-left:auto">Найдено: '+list.length+'</span></div>';
|
||||
h+='<div style="overflow-x:auto"><table><tr>';
|
||||
var cols=[{k:null,l:"№"},{k:null,l:"Мероприятие"},{k:"b",l:"Филиал"},{k:"sec",l:"Раздел"},{k:"due",l:"Срок"},{k:"p",l:"Прогресс"},{k:"s",l:"Статус"},{k:null,l:""}];
|
||||
cols.forEach(function(c){h+='<th onclick="sortCol=\''+c.k+'\';sortDir=sortCol===c.k?-sortDir:1;renderMyEvents()">'+c.l+(sortCol===c.k?(sortDir===1?' <span class="sort-arrow">▲</span>':' <span class="sort-arrow">▼</span>'):'')+'</th>'});
|
||||
var h='<div class="card"><div class="fr"><input id="sr2" placeholder="Поиск..." oninput="re()"><select id="sf2" onchange="re()"><option value="">Все статусы</option><option value="done">Исполнено</option><option value="warn">На контроле</option><option value="late">Просрочено</option></select><select id="bf2" onchange="re()"><option value="">Все филиалы</option>'+br.map(function(b,i){return'<option value="'+i+'">'+b+'</option>'}).join("")+'</select><span style="font-size:12px;color:var(--g5)">Найдено: '+list.length+'</span></div>';
|
||||
h+='<table><tr>';
|
||||
[{k:null,l:"№"},{k:null,l:"Мероприятие"},{k:"b",l:"Филиал"},{k:"sec",l:"Раздел"},{k:"due",l:"Срок"},{k:"p",l:"Прогресс"},{k:"s",l:"Статус"},{k:null,l:""}].forEach(function(c){h+='<th onclick="sc=\''+c.k+'\';sd=sc===c.k?-sd:1;re()">'+c.l+(sc===c.k?(sd===1?' ▲':' ▼'):'')+'</th>'});
|
||||
h+='</tr>';
|
||||
list.forEach(function(e){
|
||||
var hs=e.sub&&e.sub.length,sc2=getSC(e.id),sd2=hs?sc2.length:0,st=hs?e.sub.length:0,dcls=deadlineClass(e);
|
||||
h+='<tr class="'+dcls+'"><td>'+e.id+'</td><td style="font-size:12px;max-width:300px">';
|
||||
if(hs)h+='<span onclick="toggleExpand('+e.id+')" style="cursor:pointer;margin-right:6px">'+(expandedEvents[e.id]?'▼':'▶')+'</span>';
|
||||
h+='<span title="'+esc(e.t)+'" style="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:240px;display:inline-block;vertical-align:middle">'+esc(e.t)+'</span>';
|
||||
if(hs)h+=' <span style="font-size:11px;color:var(--gray-500)">('+sd2+'/'+st+')</span>';
|
||||
h+='</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>'+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>';
|
||||
if(hs&&expandedEvents[e.id])e.sub.forEach(function(s,i){var ch=sc2.indexOf(i)>=0;h+='<tr style="background:var(--gray-100)"><td></td><td style="font-size:12px;padding-left:44px"><input type="checkbox" '+(ch?"checked":"")+' onchange="toggleSub('+e.id+','+i+',this.checked)" style="margin-right:6px">'+s.l+') '+esc(s.t)+'</td><td></td><td></td><td></td><td></td><td></td><td></td></tr>'});
|
||||
var hs=e.sub&&e.sub.length,s=getSC(e.id),sd2=hs?s.length:0,st2=hs?e.sub.length:0,cl=dls(e);
|
||||
h+='<tr class="'+cl+'"><td>'+e.id+'</td><td style="font-size:12px;max-width:300px">';
|
||||
if(hs)h+='<span onclick="toggleEx('+e.id+')" style="cursor:pointer;margin-right:4px">'+(ex[e.id]?'▼':'▶')+'</span>';
|
||||
h+=esc(e.t);if(hs)h+=' <span style="font-size:10px;color:var(--g5)">('+sd2+'/'+st2+')</span>';
|
||||
h+='</td><td style="font-size:11px">'+br[e.b]+'</td><td><span class="badge b">'+["I","II","III","IV","V"][e.sec]+'</span></td><td>'+e.due+'</td><td>'+ph(e.p)+'</td><td>'+sb(e.s)+'</td><td><button class="btn btn-sm" onclick="oe('+e.id+')">📝</button></td></tr>';
|
||||
if(hs&&ex[e.id])e.sub.forEach(function(s,i){var ch=s.indexOf(i)>=0;h+='<tr style="background:var(--g1)"><td></td><td style="font-size:11px;padding-left:40px"><input type="checkbox" '+(ch?"checked":"")+' onchange="ts('+e.id+','+i+',this.checked)"> '+s.l+') '+esc(s.t)+'</td><td></td><td></td><td></td><td></td><td></td><td></td></tr>'});
|
||||
});
|
||||
h+='</table></div></div>';
|
||||
document.getElementById("tab-myevents").innerHTML=h;
|
||||
cache.myevents=true;
|
||||
h+='</table></div>';
|
||||
document.getElementById("pg-ev").innerHTML=h;
|
||||
}
|
||||
|
||||
function toggleSub(eid,si,chk){var sc=getSC(eid);if(chk&&sc.indexOf(si)<0)sc.push(si);else if(!chk)sc=sc.filter(function(x){return x!==si});setSC(eid,sc);var e=null;for(var i=0;i<events.length;i++)if(events[i].id===eid){e=events[i];break}if(e&&e.sub){var p=Math.round(sc.length/e.sub.length*100);if(sc.length===e.sub.length&&e.s!=="done")e.s="done";e.p=Math.max(e.p,p);e.h.push(new Date().toLocaleDateString()+" — подпункты "+sc.length+"/"+e.sub.length);saveEv()}renderMyEvents()}
|
||||
function toggleEx(id){ex[id]=!ex[id];re()}
|
||||
function ts(id,si,chk){var s=gsc(id);if(chk&&s.indexOf(si)<0)s.push(si);else if(!chk)s=s.filter(function(x){return x!==si});ssc(id,s);var e=null;for(var i=0;i<ev.length;i++)if(ev[i].id===id){e=ev[i];break}if(e&&e.sub){var p=Math.round(s.length/e.sub.length*100);if(s.length===e.sub.length&&e.s!=="done")e.s="done";e.p=Math.max(e.p,p);e.h.push(new Date().toLocaleDateString()+" — подпункты "+s.length+"/"+e.sub.length);se()}re()}
|
||||
function getSC(id){var r=localStorage.getItem("ss_"+id);return r?JSON.parse(r):[]}
|
||||
|
||||
// ===== ANALYTICS =====
|
||||
function renderAnalytics(){var all=events||[],h='<div class="stats-row"><div class="stat-card"><div class="lbl">Всего</div><div class="num">'+all.length+'</div></div><div class="stat-card green"><div class="lbl">Исполнено</div><div class="num">'+all.filter(function(e){return e.s==="done"}).length+'</div></div><div class="stat-card amber"><div class="lbl">На контроле</div><div class="num">'+all.filter(function(e){return e.s==="warn"}).length+'</div></div><div class="stat-card red"><div class="lbl">Просрочено</div><div class="num">'+all.filter(function(e){return e.s==="late"}).length+'</div></div></div>';
|
||||
// Aggregate quantities
|
||||
var totalQty=0,regQty={};regions.forEach(function(r,ri){regQty[ri]=0});
|
||||
all.forEach(function(e){regions.forEach(function(ri){var d=getMD(e.id,ri,-1);for(var k in d){if(d.hasOwnProperty(k)&&d[k])totalQty+=d[k].qty||0;regQty[ri]+=d[k].qty||0}if(e.sub)e.sub.forEach(function(s,si){var sd=getMD(e.id,ri,si);for(var sk in sd){if(sd.hasOwnProperty(sk)&&sd[sk]){totalQty+=sd[sk].qty||0;regQty[ri]+=sd[sk].qty||0}}})})});
|
||||
h+='<div class="panel"><h3>📊 Количественные показатели</h3><div class="stats-row"><div class="stat-card blue"><div class="lbl">Всего единиц</div><div class="num">'+totalQty+'</div></div>';
|
||||
regions.forEach(function(r,ri){if(regQty[ri])h+='<div class="stat-card"><div class="lbl">'+r+'</div><div class="num">'+regQty[ri]+'</div></div>'});
|
||||
h+='</div></div>';
|
||||
// Branch ranking
|
||||
h+='<div class="panel"><h3>Дивизионы</h3>';branches.forEach(function(b,i){var it=all.filter(function(e){return e.b===i}),d=it.filter(function(e){return e.s==="done"}).length,p=it.length?Math.round(d/it.length*100):0;h+='<div class="pct-bar" style="margin-bottom:6px"><span style="width:240px;font-size:13px;font-weight:600">'+b+'</span><div class="track" style="flex:1"><div class="fill" style="width:'+p+'%;background:'+(p>=50?"var(--green)":p>=25?"var(--amber)":"var(--red)")+'"></div></div><span style="font-weight:700;font-size:13px">'+p+'% ('+d+'/'+it.length+')</span></div>'});h+='</div>';h+='<div class="panel"><h3>Просрочено</h3><table><tr><th>№</th><th>Мероприятие</th><th>Филиал</th><th>Срок</th></tr>';all.filter(function(e){return e.s==="late"}).forEach(function(e){h+='<tr><td>'+e.id+'</td><td>'+esc(e.t.slice(0,80))+'...</td><td>'+branches[e.b]+'</td><td style="color:var(--red);font-weight:700">'+e.due+'</td></tr>'});h+='</table></div>';document.getElementById("tab-analytics").innerHTML=h;cache.analytics=true}
|
||||
|
||||
// ===== JOURNAL =====
|
||||
function renderJournal(){var log=JSON.parse(localStorage.getItem("samruk_log")||"[]").reverse(),h='<div class="panel"><h3>Журнал действий</h3>';if(!log.length)h+='<p style="color:var(--gray-500)">Записей пока нет</p>';else{h+='<table><tr><th>Время</th><th>Пользователь</th><th>Действие</th></tr>';log.forEach(function(l){h+='<tr><td style="font-size:11px">'+new Date(l.ts).toLocaleString()+'</td><td>'+esc(l.user)+'</td><td>'+esc(l.action)+(l.detail?' — '+esc(l.detail):'')+'</td></tr>'});h+='</table>'}h+='</div>';document.getElementById("tab-journal").innerHTML=h;cache.journal=true}
|
||||
function addLog(action,eid,detail){var l=JSON.parse(localStorage.getItem("samruk_log")||"[]");l.push({ts:new Date().toISOString(),user:curUser?curUser.name:"?",action:action,eid:eid||0,detail:detail||""});if(l.length>500)l=l.slice(-500);localStorage.setItem("samruk_log",JSON.stringify(l))}
|
||||
|
||||
// ===== EXPORT/IMPORT =====
|
||||
function exportAll(){var data={events:events,date:new Date().toISOString(),files:{},subChecks:{}};for(var i=0;i<localStorage.length;i++){var k=localStorage.key(i);if(k.indexOf("sf_")===0)data.files[k]=localStorage.getItem(k);if(k.indexOf("ss_")===0)data.subChecks[k]=localStorage.getItem(k)}var a=document.createElement("a");a.href=URL.createObjectURL(new Blob([JSON.stringify(data)],{type:"application/json"}));a.download="backup_"+new Date().toISOString().slice(0,10)+".json";a.click();addLog("сохранил бекап")}
|
||||
function importAll(inp){if(!inp.files.length)return;var r=new FileReader();r.onload=function(ev){try{var d=JSON.parse(ev.target.result);if(!d.events||!d.files)return alert("Неверный формат");events=d.events;saveEv();for(var k in d.files)localStorage.setItem(k,d.files[k]);for(var k in d.subChecks)localStorage.setItem(k,d.subChecks[k]);alert("Восстановлено. Обновите страницу.");location.reload()}catch(e){alert("Ошибка: "+e.message)}};r.readAsText(inp.files[0])}
|
||||
function clearAll(){if(!confirm("Удалить все файлы?"))return;var ks=[];for(var i=0;i<localStorage.length;i++){var k=localStorage.key(i);if(k.indexOf("sf_")===0||k.indexOf("ss_")===0)ks.push(k)}ks.forEach(function(k){localStorage.removeItem(k)});alert("Очищено");invalidateCache();renderDashboard()}
|
||||
|
||||
// ===== EDIT MODAL =====
|
||||
function openEdit(id,mi,ri,si){
|
||||
if(typeof mi==="number")curMonth=mi;if(typeof ri==="number")curRegion=ri;editSubIdx=(typeof si==="number")?si:-1;
|
||||
var e=null;for(var i=0;i<events.length;i++)if(events[i].id===id){e=events[i];break}if(!e)return;
|
||||
var cm=months[curMonth],sc=getSC(e.id),md=getMD(e.id,curRegion,-1),cd=md[cm]||{report:"",files:[]},cfs=cd.files||[];
|
||||
|
||||
var h='<button class="close" onclick="closeEM()">×</button><span class="badge blue">Раздел '+["I","II","III","IV","V"][e.sec]+'</span><h3 style="margin:8px 0">'+esc(e.t)+'</h3>';
|
||||
h+='<div class="meta-row"><div class="fld">Филиал<strong>'+branches[e.b]+'</strong></div><div class="fld">Ответственный<strong>'+esc(e.r)+'</strong></div><div class="fld">Срок<strong>'+e.due+'</strong></div><div class="fld">Факт<strong>'+e.done+'</strong></div></div>';
|
||||
h+='<div class="field"><label>Статус</label><select id="es"><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>';
|
||||
h+='<div class="field"><label>Прогресс (%)</label><input type="range" id="ep" min="0" max="100" value="'+e.p+'" oninput="document.getElementById(\'pv\').textContent=this.value+\'%\'"><span id="pv" style="font-weight:700">'+e.p+'%</span></div>';
|
||||
h+='<div class="field"><label>Комментарий</label><textarea id="ec"></textarea></div>';
|
||||
|
||||
var mh='<div class="month-tabs">';months.forEach(function(m,i){mh+='<span class="month-tab'+(i===curMonth?" active":"")+'" onclick="openEdit('+e.id+','+i+','+curRegion+','+editSubIdx+')">'+M(i)+'</span>'});mh+='</div>';
|
||||
var rh='<div class="month-tabs" style="margin-bottom:10px"><b style="font-size:12px;margin-right:6px">Регион:</b>';regions.forEach(function(r,i){rh+='<span class="month-tab'+(i===curRegion?" active":"")+'" onclick="openEdit('+e.id+','+curMonth+','+i+','+editSubIdx+')">'+r+'</span>'});rh+='</div>';
|
||||
|
||||
h+='<div style="border-top:1px solid var(--gray-200);padding-top:14px;margin-top:10px"><b>📎 '+regions[curRegion]+'</b>'+mh+rh;
|
||||
h+='<div class="field"><label>Текст за '+M(curMonth)+'</label><textarea id="mr" style="min-height:76px">'+esc(cd.report||"")+'</textarea></div>';
|
||||
h+='<div class="field"><label>Количество (совещаний / работников / объектов)</label><input type="number" id="mq" min="0" value="'+(cd.qty||0)+'" style="width:140px"></div>';
|
||||
cfs.forEach(function(f,i){h+='<div class="file-row"><span class="file-info"><span class="file-name" onclick="dlF('+e.id+','+curMonth+','+i+','+curRegion+',-1)">📄 '+esc(f.name)+'</span>'+(f.desc?'<span class="file-desc">'+esc(f.desc)+'</span>':'')+'</span><span class="file-meta">'+(f.size/1024).toFixed(0)+' КБ</span><button class="file-del" onclick="rmF('+e.id+','+curMonth+','+i+','+curRegion+',-1)">×</button></div>'});
|
||||
h+='<div class="upload-row"><input type="text" id="fd" placeholder="Описание"><input type="file" id="fi" multiple><button class="btn btn-sm" id="ub" onclick="upF('+e.id+','+curMonth+','+curRegion+',-1)">📤 Загрузить</button></div>';
|
||||
h+='<p style="font-size:11px;color:var(--gray-500);margin-top:4px">Формы: '+esc(e.dname)+'</p></div>';
|
||||
|
||||
if(e.sub&&e.sub.length){h+='<div style="border-top:2px solid var(--cyan);padding-top:14px;margin-top:14px"><b>📋 Подпункты</b><p style="font-size:11px;color:var(--gray-500);margin:4px 0 10px">Нажмите 📎 для управления файлами</p>';e.sub.forEach(function(s,i){var ch=sc.indexOf(i)>=0,sd=getMD(e.id,curRegion,i),scd=sd[cm]||{report:"",files:[]},scfs=scd.files||[],isA=editSubIdx===i;h+='<div class="sub-item" style="flex-wrap:wrap;padding:10px 14px;margin-bottom:6px;'+(isA?'border:2px solid var(--cyan)':'')+'"><input type="checkbox" id="sc_'+i+'" '+(ch?"checked":"")+'><span class="sub-label">'+s.l+')</span><span class="sub-text" style="flex:1">'+esc(s.t)+'</span><span style="font-size:11px;color:var(--gray-500);margin-right:6px">Файлов: '+scfs.length+'</span><button class="btn btn-sm" onclick="openEdit('+e.id+','+curMonth+','+curRegion+','+i+')" style="font-size:12px;'+(isA?'background:var(--cyan);font-weight:700':'')+'">'+(isA?'📂':'📎')+'</button></div>'; if(isA){h+='<div style="margin:0 0 14px 20px;padding:14px;background:var(--cyan-50);border-radius:8px;border:2px solid var(--cyan)"><b>'+s.l+') '+esc(s.t)+'</b><p style="font-size:11px;color:var(--gray-500);margin-bottom:8px">'+regions[curRegion]+' · '+M(curMonth)+'</p>';h+='<div class="field"><label>Текст</label><textarea id="mr_s'+i+'" style="min-height:56px">'+esc(scd.report||"")+'</textarea></div>';h+='<div class="field"><label>Количество</label><input type="number" id="mq_s'+i+'" min="0" value="'+(scd.qty||0)+'" style="width:120px"></div>';scfs.forEach(function(f,fi){h+='<div class="file-row"><span class="file-name" onclick="dlF('+e.id+','+curMonth+','+fi+','+curRegion+','+i+')">📄 '+esc(f.name)+'</span><span class="file-meta">'+(f.size/1024).toFixed(0)+' КБ</span><button class="file-del" onclick="rmF('+e.id+','+curMonth+','+fi+','+curRegion+','+i+')">×</button></div>'});h+='<div class="upload-row"><input type="text" id="fd_s'+i+'" placeholder="Описание"><input type="file" id="fi_s'+i+'" multiple><button class="btn btn-sm" id="ub_s'+i+'" onclick="upF('+e.id+','+curMonth+','+curRegion+','+i+')">📤</button></div></div>'}});h+='</div>'}
|
||||
|
||||
h+='<div class="ai-block"><h4>🤖 ИИ</h4>'+esc(e.ai)+'</div>';
|
||||
h+='<div><b>История:</b><div>';e.h.forEach(function(x){h+='<div class="history-item"><div class="dot"></div>'+esc(x)+'</div>'});h+='</div></div>';
|
||||
h+='<div style="margin-top:18px;display:flex;gap:10px"><button class="btn" onclick="saveEdit('+e.id+','+curMonth+')">Сохранить</button><button class="btn btn-outline" onclick="closeEM()">Отмена</button></div>';
|
||||
document.getElementById("editModalContent").innerHTML=h;
|
||||
document.getElementById("editModalOverlay").classList.add("open");
|
||||
function ra(){
|
||||
var all=ev||[],done=all.filter(function(e){return e.s==="done"}).length,warn=all.filter(function(e){return e.s==="warn"}).length,late=all.filter(function(e){return e.s==="late"}).length;
|
||||
var h='<div class="row"><div class="stat"><div class="l">Всего</div><div class="n">'+all.length+'</div></div><div class="stat g"><div class="l">Исполнено</div><div class="n">'+done+'</div></div><div class="stat a"><div class="l">На контроле</div><div class="n">'+warn+'</div></div><div class="stat r"><div class="l">Просрочено</div><div class="n">'+late+'</div></div></div>';
|
||||
var tq=0,rq={};reg.forEach(function(r,ri){rq[ri]=0});all.forEach(function(e){reg.forEach(function(ri){var d=getMD(e.id,ri,-1);for(var k in d){if(d.hasOwnProperty(k)&&d[k]){tq+=d[k].qty||0;rq[ri]+=d[k].qty||0}if(e.sub)e.sub.forEach(function(s,si){var sd=getMD(e.id,ri,si);for(var sk in sd){if(sd.hasOwnProperty(sk)&&sd[sk]){tq+=sd[sk].qty||0;rq[ri]+=sd[sk].qty||0}}})}})});
|
||||
h+='<div class="card"><h3>Количественные показатели</h3><div class="row"><div class="stat" style="background:var(--c);color:var(--b)"><div class="l">Всего единиц</div><div class="n">'+tq+'</div></div>';reg.forEach(function(r,ri){if(rq[ri])h+='<div class="stat"><div class="l">'+r+'</div><div class="n">'+rq[ri]+'</div></div>'});h+='</div></div>';
|
||||
h+='<div class="card"><h3>По филиалам</h3>';br.forEach(function(b,i){var it=all.filter(function(e){return e.b===i}),d=it.filter(function(e){return e.s==="done"}).length,p=it.length?Math.round(d/it.length*100):0;h+='<div class="fl" style="margin-bottom:4px"><span style="width:180px;font-size:12px;font-weight:600">'+b+'</span><div style="flex:1;height:6px;background:var(--g2);border-radius:6px;overflow:hidden"><div style="width:'+p+'%;height:100%;background:'+(p>=50?"var(--gn)":p>=25?"var(--am)":"var(--rd)")+';border-radius:6px"></div></div><span style="font-weight:700;font-size:11px">'+p+'%</span></div>'});h+='</div>';
|
||||
document.getElementById("pg-an").innerHTML=h;
|
||||
}
|
||||
|
||||
function saveEdit(id,mk){mk=months[mk];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("es").value;e.p=parseInt(document.getElementById("ep").value);var cmt=(document.getElementById("ec").value||"").trim(),mr=document.getElementById("mr"),mq=document.getElementById("mq");if(mr){var ad=getMD(id,curRegion,-1);if(!ad[mk])ad[mk]={report:"",files:[]};ad[mk].report=mr.value;if(mq)ad[mk].qty=parseInt(mq.value)||0;setMD(id,ad,curRegion,-1)}if(e.sub&&e.sub.length){var cks=[];e.sub.forEach(function(_,i){var el=document.getElementById("sc_"+i);if(el&&el.checked)cks.push(i);var sr=document.getElementById("mr_s"+i),sq=document.getElementById("mq_s"+i);if(sr){var sd=getMD(id,curRegion,i);if(!sd[mk])sd[mk]={report:"",files:[]};sd[mk].report=sr.value;if(sq)sd[mk].qty=parseInt(sq.value)||0;setMD(id,sd,curRegion,i)}});setSC(id,cks)}var now=new Date().toLocaleDateString();e.h.push(now+" — "+curUser.name+": "+sm[e.s]+", "+e.p+"%");if(e.s==="done"&&e.done==="\u2014")e.done=now;saveEv();addLog("изменил",id,sm[e.s]);closeEM();invalidateCache();renderAll()}
|
||||
function closeEM(){document.getElementById("editModalOverlay").classList.remove("open")}
|
||||
// ===== REPORTS PAGE =====
|
||||
function rr(){
|
||||
var h='<div class="card"><h3>Сводный отчёт</h3><div class="fr"><select id="rf">'+ms.map(function(m,i){return'<option value="'+i+'">'+M(i)+'</option>'}).join("")+'</select><span>—</span><select id="rt">'+ms.map(function(m,i){return'<option value="'+i+'"'+(i===11?" selected":"")+'>'+M(i)+'</option>'}).join("")+'</select><button class="btn btn-sm" onclick="dCSV()">CSV</button><button class="btn btn-sm" onclick="dHTML()">HTML</button></div>';
|
||||
var b=0;for(var i=0;i<localStorage.length;i++){var k=localStorage.key(i);if(k.indexOf("sf_")===0)b+=localStorage.getItem(k).length*2}
|
||||
h+='<p style="font-size:12px;color:var(--g5);margin-bottom:8px">Хранилище: '+(b>1048576?(b/1048576).toFixed(1)+" МБ":(b/1024).toFixed(0)+" КБ")+'</p>';
|
||||
h+='<button class="btn btn-sm btn-gn" onclick="exp()">💾 Сохранить всё</button> ';
|
||||
h+='<button class="btn btn-sm" onclick="document.getElementById(\'if\').click()">📥 Загрузить</button> ';
|
||||
h+='<input type="file" id="if" accept=".json" style="display:none" onchange="imp(this)"> ';
|
||||
h+='<button class="btn btn-sm btn-red" onclick="clr()">🗑 Очистить</button></div>';
|
||||
document.getElementById("pg-rp").innerHTML=h;
|
||||
}
|
||||
|
||||
// ===== FILE OPS =====
|
||||
function upF(eid,mk,ri,si){mk=months[mk];var pfx=si>=0?"_s"+si:"",fi=document.getElementById("fi"+pfx);if(!fi||!fi.files.length)return;var desc=(document.getElementById("fd"+pfx)||{}).value;desc=(desc||"").trim();var btn=document.getElementById("ub"+pfx);if(btn){btn.textContent="...";btn.disabled=true}var ad=getMD(eid,ri,si);if(!ad[mk])ad[mk]={report:"",files:[]};var arr=ad[mk].files,pr=0,sk=0,MAX=3072*1024;function fin(){try{setMD(eid,ad,ri,si)}catch(e){alert("Хранилище заполнено")}if(sk)alert(sk+" файлов >3 МБ пропущено");addLog("файлы",eid,regions[ri]);closeEM();openEdit(eid,curMonth,ri,si>=0?si:undefined)}for(var i=0;i<fi.files.length;i++){(function(f){if(f.size>MAX){sk++;pr++;if(pr===fi.files.length)fin();return}var r=new FileReader();r.onload=function(ev){arr.push({name:f.name,size:f.size,type:f.type,desc:desc,date:new Date().toLocaleDateString(),data:ev.target.result});pr++;if(pr===fi.files.length)fin()};r.onerror=function(){pr++;if(pr===fi.files.length)fin()};r.readAsDataURL(f)})(fi.files[i])}}
|
||||
function dlF(eid,mk,idx,ri,si){si=si||-1;mk=months[mk];var ad=getMD(eid,ri,si),arr=ad[mk]?ad[mk].files:null;if(!arr||!arr[idx]||!arr[idx].data)return;var 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 rmF(eid,mk,idx,ri,si){si=si||-1;mk=months[mk];var ad=getMD(eid,ri,si);if(!ad[mk]||!ad[mk].files)return;ad[mk].files.splice(idx,1);setMD(eid,ad,ri,si);closeEM();openEdit(eid,curMonth,ri,si>=0?si:undefined)}
|
||||
function dCSV(){var f=parseInt(document.getElementById("rf").value),t=parseInt(document.getElementById("rt").value);var csv="\uFEFF№;Филиал;Мероприятие;Регион;Статус;Прогресс;Срок\n";(ev||[]).forEach(function(e){reg.forEach(function(r,ri){var d=getMD(e.id,ri,-1),rep="";for(var i=f;i<=t;i++){var m=ms[i];if(d[m]&&d[m].report)rep+=M(i)+": "+d[m].report.replace(/"/g,'""')+"; "}csv+=e.id+';'+br[e.b]+';"'+e.t.replace(/"/g,'""')+'";'+r+';'+st[e.s]+';'+e.p+'%;'+e.due+';"'+rep+'"\n'})});var a=document.createElement("a");a.href=URL.createObjectURL(new Blob([csv]));a.download="otchet.csv";a.click()}
|
||||
function dHTML(){var f=parseInt(document.getElementById("rf").value),t=parseInt(document.getElementById("rt").value);var h='<!DOCTYPE html><html><head><meta charset="utf-8"><title>Отчёт</title><style>body{font:13px/1.4 Arial;max-width:1000px;margin:0 auto;padding:20px}.ev{border:1px solid #ddd;border-radius:8px;padding:14px;margin-bottom:12px}.badge{display:inline-block;padding:2px 6px;border-radius:4px;font-size:10px;font-weight:700}.g{background:#D1FAE5;color:#065F46}.a{background:#FEF3C7;color:#92400E}.r{background:#FEE2E2;color:#991B1B}.m{background:#f5f5f5;padding:6px 10px;border-radius:4px;margin:4px 0}</style></head><body><h2>Сводный отчёт</h2>';ev.forEach(function(e){var cl={done:"g",warn:"a",late:"r"}[e.s];h+='<div class="ev"><h3>'+e.id+'. '+esc(e.t)+'</h3><p>'+br[e.b]+' | '+sec[e.sec]+' | Срок: '+e.due+' | Прогресс: '+e.p+'% <span class="badge '+cl+'">'+st[e.s]+'</span></p>';reg.forEach(function(r,ri){var d=getMD(e.id,ri,-1);for(var i=f;i<=t;i++){var m=ms[i];if(d[m]&&d[m].report){h+='<div class="m"><b>'+M(i)+' — '+r+'</b><p>'+esc(d[m].report)+'</p></div>'}}});h+='</div>'});h+='</body></html>';try{var a=document.createElement("a");a.href=URL.createObjectURL(new Blob(["\uFEFF"+h]));a.download="otchet.html";a.click()}catch(e){alert("Слишком большой")}}
|
||||
|
||||
function exp(){var d={events:ev,date:new Date().toISOString(),files:{},sc:{}};for(var i=0;i<localStorage.length;i++){var k=localStorage.key(i);if(k.indexOf("sf_")===0)d.files[k]=localStorage.getItem(k);if(k.indexOf("ss_")===0)d.sc[k]=localStorage.getItem(k)}var a=document.createElement("a");a.href=URL.createObjectURL(new Blob([JSON.stringify(d)]));a.download="backup.json";a.click()}
|
||||
function imp(inp){if(!inp.files.length)return;var r=new FileReader();r.onload=function(ev){try{var d=JSON.parse(ev.target.result);ev=d.events;se();for(var k in d.files)localStorage.setItem(k,d.files[k]);for(var k in d.sc)localStorage.setItem(k,d.sc[k]);alert("OK. Обновите страницу.");location.reload()}catch(e){alert("Ошибка")}};r.readAsText(inp.files[0])}
|
||||
function clr(){if(!confirm("Удалить все файлы?"))return;var ks=[];for(var i=0;i<localStorage.length;i++){var k=localStorage.key(i);if(k.indexOf("sf_")===0||k.indexOf("ss_")===0)ks.push(k)}ks.forEach(function(k){localStorage.removeItem(k)});alert("Очищено");rr()}
|
||||
|
||||
// ===== EDIT MODAL =====
|
||||
function oe(id,mi,ri,si){
|
||||
if(typeof mi==="number")cm=mi;if(typeof ri==="number")cr=ri;esi=(typeof si==="number")?si:-1;
|
||||
var e=null;for(var i=0;i<ev.length;i++)if(ev[i].id===id){e=ev[i];break}if(!e)return;
|
||||
var m=ms[cm],s=getSC(e.id),md=getMD(e.id,cr,-1),cd=md[m]||{report:"",files:[]},cfs=cd.files||[];
|
||||
|
||||
var h='<span class="x" onclick="closeM()">×</span><span class="badge b">'+["I","II","III","IV","V"][e.sec]+'</span>';
|
||||
h+='<h3 style="margin:6px 0">'+esc(e.t)+'</h3>';
|
||||
h+='<div class="meta"><div>Филиал<strong>'+br[e.b]+'</strong></div><div>Ответственный<strong>'+esc(e.r)+'</strong></div><div>Срок<strong>'+e.due+'</strong></div><div>Факт<strong>'+e.dn+'</strong></div></div>';
|
||||
h+='<label>Статус</label><select id="es2"><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>';
|
||||
h+='<label>Прогресс (%)</label><input type="range" id="ep2" min="0" max="100" value="'+e.p+'" oninput="document.getElementById(\'pv2\').textContent=this.value+\'%\'"><span id="pv2" style="font-weight:700">'+e.p+'%</span>';
|
||||
h+='<label>Комментарий</label><textarea id="ec2"></textarea>';
|
||||
|
||||
var mh='<div class="mt">';ms.forEach(function(_,i){mh+='<span class="'+(i===cm?"on":"")+'" onclick="oe('+e.id+','+i+','+cr+','+esi+')">'+M(i)+'</span>'});mh+='</div>';
|
||||
var rh='<div class="mt"><b style="font-size:11px;margin-right:4px">Регион:</b>';reg.forEach(function(r,i){rh+='<span class="'+(i===cr?"on":"")+'" onclick="oe('+e.id+','+cm+','+i+','+esi+')">'+r+'</span>'});rh+='</div>';
|
||||
|
||||
h+='<div style="border-top:1px solid var(--g2);padding-top:12px;margin-top:10px"><b>📎 '+reg[cr]+'</b><br>'+mh+rh;
|
||||
h+='<label>Текст за '+M(cm)+'</label><textarea id="mr2">'+esc(cd.report||"")+'</textarea>';
|
||||
h+='<div style="display:flex;gap:8px"><div style="flex:1"><label>Количество</label><input type="number" id="mq2" min="0" value="'+(cd.qty||0)+'"></div></div>';
|
||||
cfs.forEach(function(f,i){h+='<div class="fl"><span class="nm" onclick="dlF2('+e.id+','+cm+','+i+','+cr+',-1)">📄 '+esc(f.name)+'</span><span class="sz">'+(f.size/1024).toFixed(0)+' КБ</span><button onclick="rmF2('+e.id+','+cm+','+i+','+cr+',-1)" style="border:none;color:var(--rd);cursor:pointer;font-size:14px">×</button></div>'});
|
||||
h+='<div class="up"><input type="text" id="fd2" placeholder="Описание"><input type="file" id="fi2" multiple><button class="btn btn-sm" id="ub2" onclick="upF2('+e.id+','+cm+','+cr+',-1)">Загрузить</button></div>';
|
||||
h+='<p style="font-size:10px;color:var(--g5);margin-top:2px">'+esc(e.dnm)+'</p></div>';
|
||||
|
||||
if(e.sub&&e.sub.length){h+='<div style="border-top:2px solid var(--c);padding-top:12px;margin-top:12px"><b>📋 Подпункты</b>';e.sub.forEach(function(s,i){var ch=s.indexOf(i)>=0,sd=getMD(e.id,cr,i),scd=sd[m]||{report:"",files:[]},scfs=scd.files||[],isA=esi===i;h+='<div class="si" style="'+(isA?'border:2px solid var(--c)':'')+'"><input type="checkbox" id="sc2_'+i+'" '+(ch?"checked":"")+'><span class="n">'+s.l+')</span><span style="flex:1;font-size:11px">'+esc(s.t)+'</span><span style="font-size:10px;color:var(--g5)">'+scfs.length+' файлов</span><button class="btn btn-sm" onclick="oe('+e.id+','+cm+','+cr+','+i+')" style="'+(isA?'background:var(--c);font-weight:700':'')+'">'+(isA?'📂':'📎')+'</button></div>';if(isA){h+='<div style="margin:0 0 10px 12px;padding:12px;background:#E8FCFF;border-radius:6px;border:2px solid var(--c)"><b>'+s.l+') '+esc(s.t)+'</b><p style="font-size:10px;color:var(--g5)">'+reg[cr]+' · '+M(cm)+'</p>';h+='<label>Текст</label><textarea id="mr2_s'+i+'">'+esc(scd.report||"")+'</textarea>';h+='<label>Количество</label><input type="number" id="mq2_s'+i+'" min="0" value="'+(scd.qty||0)+'">';scfs.forEach(function(f,fi){h+='<div class="fl"><span class="nm" onclick="dlF2('+e.id+','+cm+','+fi+','+cr+','+i+')">📄 '+esc(f.name)+'</span><span class="sz">'+(f.size/1024).toFixed(0)+' КБ</span><button onclick="rmF2('+e.id+','+cm+','+fi+','+cr+','+i+')" style="border:none;color:var(--rd);cursor:pointer">×</button></div>'});h+='<div class="up"><input type="text" id="fd2_s'+i+'" placeholder="Описание"><input type="file" id="fi2_s'+i+'" multiple><button class="btn btn-sm" id="ub2_s'+i+'" onclick="upF2('+e.id+','+cm+','+cr+','+i+')">Загрузить</button></div></div>'}});h+='</div>'}
|
||||
|
||||
h+='<div class="ai"><b>🤖 ИИ:</b> '+esc(e.ai)+'</div>';
|
||||
h+='<div><b>История:</b>';e.h.forEach(function(x){h+='<div class="hi"><span class="d"></span>'+esc(x)+'</div>'});h+='</div>';
|
||||
h+='<div style="margin-top:14px;display:flex;gap:8px"><button class="btn" onclick="sv('+e.id+','+cm+')">Сохранить</button><button class="btn" style="background:var(--g2)" onclick="closeM()">Отмена</button></div>';
|
||||
|
||||
document.getElementById("mc").innerHTML=h;
|
||||
document.getElementById("mo").classList.add("on");
|
||||
}
|
||||
|
||||
function sv(id,mk){mk=ms[mk];var e=null;for(var i=0;i<ev.length;i++)if(ev[i].id===id){e=ev[i];break}if(!e)return;e.s=document.getElementById("es2").value;e.p=parseInt(document.getElementById("ep2").value);var mr=document.getElementById("mr2"),mq=document.getElementById("mq2");if(mr){var ad=getMD(id,cr,-1);if(!ad[mk])ad[mk]={report:"",files:[]};ad[mk].report=mr.value;if(mq)ad[mk].qty=parseInt(mq.value)||0;setMD(id,ad,cr,-1)}if(e.sub&&e.sub.length){var cks=[];e.sub.forEach(function(_,i){var el=document.getElementById("sc2_"+i);if(el&&el.checked)cks.push(i);var sr=document.getElementById("mr2_s"+i),sq=document.getElementById("mq2_s"+i);if(sr){var sd=getMD(id,cr,i);if(!sd[mk])sd[mk]={report:"",files:[]};sd[mk].report=sr.value;if(sq)sd[mk].qty=parseInt(sq.value)||0;setMD(id,sd,cr,i)}});ssc(id,cks)}var now=new Date().toLocaleDateString();e.h.push(now+" — "+cu.n+": "+st[e.s]+", "+e.p+"%");if(e.s==="done"&&e.dn==="—")e.dn=now;se();closeM();re()}
|
||||
function closeM(){document.getElementById("mo").classList.remove("on")}
|
||||
|
||||
function upF2(eid,mk,ri,si){mk=ms[mk];var pfx=si>=0?"_s"+si:"",fi=document.getElementById("fi2"+pfx);if(!fi||!fi.files.length)return;var desc=(document.getElementById("fd2"+pfx)||{}).value;desc=(desc||"").trim();var btn=document.getElementById("ub2"+pfx);if(btn){btn.textContent="...";btn.disabled=true}var ad=getMD(eid,ri,si);if(!ad[mk])ad[mk]={report:"",files:[]};var arr=ad[mk].files,pr=0,sk=0;function fin(){try{setMD(eid,ad,ri,si)}catch(e){alert("Хранилище заполнено")}if(sk)alert(sk+" файлов >3 МБ");closeM();oe(eid,cm,ri,si>=0?si:undefined)}for(var i=0;i<fi.files.length;i++){(function(f){if(f.size>3072*1024){sk++;pr++;if(pr===fi.files.length)fin();return}var r=new FileReader();r.onload=function(ev){arr.push({name:f.name,size:f.size,type:f.type,desc:desc,date:new Date().toLocaleDateString(),data:ev.target.result});pr++;if(pr===fi.files.length)fin()};r.onerror=function(){pr++;if(pr===fi.files.length)fin()};r.readAsDataURL(f)})(fi.files[i])}}
|
||||
function dlF2(eid,mk,idx,ri,si){si=si||-1;mk=ms[mk];var ad=getMD(eid,ri,si),arr=ad[mk]?ad[mk].files:null;if(!arr||!arr[idx]||!arr[idx].data)return;var 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 rmF2(eid,mk,idx,ri,si){si=si||-1;mk=ms[mk];var ad=getMD(eid,ri,si);if(!ad[mk]||!ad[mk].files)return;ad[mk].files.splice(idx,1);setMD(eid,ad,ri,si);closeM();oe(eid,cm,ri,si>=0?si:undefined)}
|
||||
|
||||
// ===== INIT =====
|
||||
function renderAll(){notifsUpdate();switchTab(document.querySelector(".tab-btn.active").dataset.tab)}
|
||||
document.getElementById("editModalOverlay").addEventListener("click",function(e){if(e.target===this)closeEM()});
|
||||
document.addEventListener("keydown",function(e){if(e.key==="Escape"){closeEM();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")});
|
||||
function renderAll(){switchPg("ev")}
|
||||
document.getElementById("mo").addEventListener("click",function(e){if(e.target===this)closeM()});
|
||||
document.addEventListener("keydown",function(e){if(e.key==="Escape")closeM()});
|
||||
|
||||
loadEvents();
|
||||
var su=localStorage.getItem("samruk_u");if(su){try{curUser=JSON.parse(su);showApp()}catch(e){}}
|
||||
le();var su=localStorage.getItem("su");if(su){try{cu=JSON.parse(su);show()}catch(e){}}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Loading…
Reference in New Issue
Block a user