414 lines
29 KiB
HTML
414 lines
29 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="ru">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||
<title>ИИ-агент мониторинга ПБ — АО «Самрук-Казына»</title>
|
||
<style>
|
||
:root{--ink:#0F1218;--cyan:#00E5FF;--cyan-50:#E8FCFF;--white:#fff;--gray-500:#5B6573;--gray-100:#F2F4F7;--gray-200:#E5E7EB;--gray-700:#374151;--green:#10B981;--red:#EF4444;--amber:#F59E0B;--blue:#3B82F6;--purple:#8B5CF6;--sidebar-w:260px;--topbar-h:60px}
|
||
*{box-sizing:border-box;margin:0;padding:0}
|
||
body{font:14px/1.5 -apple-system,BlinkMacSystemFont,"Segoe UI",Inter,system-ui,sans-serif;color:var(--ink);background:var(--gray-100);display:flex;min-height:100vh}
|
||
a{color:var(--cyan);text-decoration:none}
|
||
/* SIDEBAR */
|
||
.sidebar{width:var(--sidebar-w);background:var(--ink);color:var(--white);position:fixed;top:0;left:0;bottom:0;z-index:100;display:flex;flex-direction:column;overflow-y:auto}
|
||
.sidebar .logo{padding:20px 24px;font-size:18px;font-weight:800;border-bottom:1px solid rgba(255,255,255,.1);line-height:1.3}
|
||
.sidebar .logo span{color:var(--cyan)}
|
||
.sidebar nav{padding:16px 0;flex:1}
|
||
.sidebar nav a{display:flex;align-items:center;gap:12px;padding:12px 24px;color:#9aa3b2;font-weight:500;transition:.15s;font-size:15px;cursor:pointer}
|
||
.sidebar nav a:hover,.sidebar nav a.active{color:var(--white);background:rgba(0,229,255,.08)}
|
||
.sidebar nav a.active{border-right:3px solid var(--cyan)}
|
||
.sidebar nav a .ico{font-size:18px;width:24px;text-align:center}
|
||
.sidebar .user{padding:20px 24px;border-top:1px solid rgba(255,255,255,.1);font-size:13px;color:#6b7280}
|
||
/* MAIN */
|
||
.main{margin-left:var(--sidebar-w);flex:1;padding:24px 32px;max-width:calc(100vw - var(--sidebar-w))}
|
||
/* PAGE */
|
||
.page{display:none}
|
||
.page.active{display:block}
|
||
.page h2{font-size:24px;font-weight:700;margin-bottom:20px}
|
||
/* STATS ROW */
|
||
.stats-row{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:16px;margin-bottom:24px}
|
||
.stat-card{background:var(--white);border-radius:12px;padding:20px 24px;border:1px solid var(--gray-200)}
|
||
.stat-card .stat-label{font-size:13px;color:var(--gray-500);margin-bottom:4px}
|
||
.stat-card .stat-num{font-size:32px;font-weight:800;line-height:1}
|
||
.stat-card .stat-sub{font-size:13px;color:var(--gray-500);margin-top:4px}
|
||
.stat-card.red .stat-num{color:var(--red)}
|
||
.stat-card.green .stat-num{color:var(--green)}
|
||
.stat-card.amber .stat-num{color:var(--amber)}
|
||
.stat-card.blue .stat-num{color:var(--blue)}
|
||
/* GRID 2col */
|
||
.grid2{display:grid;grid-template-columns:1fr 1fr;gap:24px;margin-bottom:24px}
|
||
.grid3{display:grid;grid-template-columns:1fr 1fr 1fr;gap:24px;margin-bottom:24px}
|
||
/* PANEL */
|
||
.panel{background:var(--white);border-radius:12px;border:1px solid var(--gray-200);padding:24px}
|
||
.panel h3{font-size:17px;font-weight:700;margin-bottom:16px}
|
||
/* CHART STUB */
|
||
.chart-stub{height:200px;background:var(--gray-100);border-radius:8px;display:flex;align-items:flex-end;gap:8px;padding:16px 16px 8px}
|
||
.chart-stub .bar{flex:1;background:var(--cyan);border-radius:4px 4px 0 0;min-height:4px;transition:.3s}
|
||
.chart-stub .bar.danger{background:var(--red)}
|
||
.chart-stub .bar.warn{background:var(--amber)}
|
||
.chart-stub .bar.good{background:var(--green)}
|
||
.chart-stub .bar.blue{background:var(--blue)}
|
||
.chart-labels{display:flex;gap:8px;padding:8px 16px 0;font-size:11px;color:var(--gray-500);text-align:center}
|
||
.chart-labels span{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
||
/* RING */
|
||
.ring{position:relative;width:120px;height:120px;margin:0 auto 12px}
|
||
.ring svg{transform:rotate(-90deg)}
|
||
.ring .pct{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;font-size:28px;font-weight:800}
|
||
.ring-label{text-align:center;font-size:14px;color:var(--gray-500)}
|
||
/* TABLE */
|
||
table{width:100%;border-collapse:collapse}
|
||
th,td{padding:12px 16px;text-align:left;font-size:14px}
|
||
th{font-weight:600;color:var(--gray-500);font-size:12px;text-transform:uppercase;letter-spacing:.5px;border-bottom:2px solid var(--gray-200)}
|
||
td{border-bottom:1px solid var(--gray-200)}
|
||
tr:hover td{background:var(--cyan-50)}
|
||
/* BADGE */
|
||
.badge{display:inline-block;padding:4px 10px;border-radius:100px;font-size:12px;font-weight:600}
|
||
.badge.green{background:#D1FAE5;color:#065F46}
|
||
.badge.amber{background:#FEF3C7;color:#92400E}
|
||
.badge.red{background:#FEE2E2;color:#991B1B}
|
||
.badge.blue{background:#DBEAFE;color:#1E40AF}
|
||
.badge.gray{background:var(--gray-100);color:var(--gray-700)}
|
||
/* FILTERS */
|
||
.filters{display:flex;gap:12px;margin-bottom:20px;flex-wrap:wrap;align-items:center}
|
||
.filters select,.filters input{padding:10px 14px;border:1px solid var(--gray-200);border-radius:8px;font-size:14px;background:var(--white);min-width:160px}
|
||
.filters input{min-width:260px}
|
||
.filters .count{font-size:13px;color:var(--gray-500);margin-left:auto}
|
||
/* AI BADGE */
|
||
.ai-tag{display:inline-flex;align-items:center;gap:6px;background:var(--cyan-50);color:var(--ink);padding:4px 10px;border-radius:6px;font-size:12px;font-weight:600}
|
||
/* MODAL */
|
||
.modal-overlay{position:fixed;inset:0;background:rgba(0,0,0,.5);z-index:200;display:none;align-items:center;justify-content:center}
|
||
.modal-overlay.open{display:flex}
|
||
.modal{background:var(--white);border-radius:16px;max-width:800px;width:90vw;max-height:85vh;overflow-y:auto;padding:32px}
|
||
.modal h2{font-size:22px;margin-bottom:8px}
|
||
.modal .close{float:right;background:none;border:none;font-size:24px;cursor:pointer;line-height:1;color:var(--gray-500)}
|
||
.modal .meta{display:grid;grid-template-columns:1fr 1fr;gap:12px;margin:20px 0}
|
||
.modal .meta .field{font-size:13px;color:var(--gray-500)}
|
||
.modal .meta .field strong{display:block;font-size:15px;color:var(--ink);margin-top:2px}
|
||
.modal .docs{display:flex;gap:8px;flex-wrap:wrap;margin:12px 0}
|
||
.modal .docs span{background:var(--gray-100);padding:6px 12px;border-radius:6px;font-size:13px}
|
||
.modal .ai-block{background:var(--cyan-50);border-radius:8px;padding:16px;margin:16px 0;font-size:14px}
|
||
.modal .ai-block h4{font-size:14px;margin-bottom:6px;color:var(--ink)}
|
||
.modal .timeline{font-size:13px;color:var(--gray-500);line-height:2}
|
||
.modal .timeline strong{color:var(--ink)}
|
||
/* RATINGS */
|
||
.rating-bar{display:flex;align-items:center;gap:12px;padding:8px 0}
|
||
.rating-bar .name{width:180px;font-size:14px;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
||
.rating-bar .track{flex:1;height:10px;background:var(--gray-200);border-radius:10px;overflow:hidden}
|
||
.rating-bar .fill{height:100%;border-radius:10px;transition:.5s}
|
||
/* RESPONSIVE */
|
||
@media(max-width:900px){
|
||
.sidebar{width:60px}
|
||
.sidebar .logo,.sidebar nav a span,.sidebar .user{display:none}
|
||
.sidebar nav a{justify-content:center;padding:12px 0;font-size:20px}
|
||
.main{margin-left:60px;max-width:calc(100vw - 60px);padding:16px}
|
||
.grid2,.grid3{grid-template-columns:1fr}
|
||
.stats-row{grid-template-columns:1fr 1fr}
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
|
||
<!-- SIDEBAR -->
|
||
<aside class="sidebar">
|
||
<div class="logo"><span>ИИ-Агент</span><br>Самрук-Казына</div>
|
||
<nav>
|
||
<a class="active" data-page="dashboard"><span class="ico">📊</span> <span>Дашборд</span></a>
|
||
<a data-page="register"><span class="ico">📋</span> <span>Реестр мероприятий</span></a>
|
||
<a data-page="analytics"><span class="ico">📈</span> <span>Аналитика</span></a>
|
||
<a data-page="reports"><span class="ico">📑</span> <span>Отчёты</span></a>
|
||
<a data-page="risks"><span class="ico">⚠️</span> <span>Карта рисков</span></a>
|
||
</nav>
|
||
<div class="user">Инспектор ПБ • АО «Самрук-Казына»</div>
|
||
</aside>
|
||
|
||
<!-- MAIN CONTENT -->
|
||
<main class="main" id="mainContent"></main>
|
||
|
||
<!-- MODAL -->
|
||
<div class="modal-overlay" id="modalOverlay">
|
||
<div class="modal" id="modalContent"></div>
|
||
</div>
|
||
|
||
<script>
|
||
// ===== MOCK DATA =====
|
||
const branches = ['АО "КазТрансОйл"','АО "KEGOC"','АО "Казпочта"','АО "НК КТЖ"','АО "Казатомпром"','АО "Эйр Астана"','АО "QazaqGaz"','ТОО "Самрук-Энерго"']
|
||
const statuses = {done:'Исполнено',warn:'На контроле',late:'Просрочено',wait:'Не начато'}
|
||
const directions = ['Охрана труда','Пожарная безопасность','Промбезопасность','Экология','ГО и ЧС','Транспортная безопасность']
|
||
|
||
const events = [
|
||
{id:1, title:'Проведение внепланового инструктажа по охране труда', branch:branches[0], dir:directions[0], status:'done', progress:100, due:'15.02.2026', done:'12.02.2026', resp:'Ахметов К.Т.', docs:['Приказ №45','Журнал инструктажа','Фотоотчёт'], ai:'Все документы в наличии. Инструктаж проведён в срок. Замечаний нет.', history:['10.02 — Мероприятие создано','12.02 — Загружен журнал инструктажа','12.02 — Статус: Исполнено'] },
|
||
{id:2, title:'Замена средств пожаротушения на складе ГСМ', branch:branches[1], dir:directions[1], status:'warn', progress:65, due:'30.06.2026', done:'—', resp:'Сериков А.М.', docs:['Смета','Договор поставки'], ai:'Отсутствует акт выполненных работ. Рекомендуется ускорить монтаж. Срок под риском.', history:['05.03 — Мероприятие создано','20.04 — Загружен договор','01.06 — ИИ: риск срыва срока'] },
|
||
{id:3, title:'Аттестация сварщиков по промышленной безопасности', branch:branches[4], dir:directions[2], status:'late', progress:30, due:'01.04.2026', done:'—', resp:'Касымов Б.Е.', docs:[], ai:'Просрочка 64 дня. Подтверждающие документы отсутствуют. Эскалация руководителю.', history:['01.01 — Мероприятие создано','01.04 — Срок истёк','05.04 — Эскалация руководителю'] },
|
||
{id:4, title:'Экологический аудит очистных сооружений', branch:branches[6], dir:directions[3], status:'done', progress:100, due:'01.03.2026', done:'26.02.2026', resp:'Нурланов Д.С.', docs:['Акт аудита','Лабораторные пробы','Фото'], ai:'Аудит пройден. Лабораторные показатели в норме. Рекомендации выполнены.', history:['10.01 — Мероприятие создано','20.02 — Загружены пробы','26.02 — Статус: Исполнено'] },
|
||
{id:5, title:'Обучение персонала по ГО и ЧС', branch:branches[7], dir:directions[4], status:'warn', progress:45, due:'01.08.2026', done:'—', resp:'Тулегенов Е.А.', docs:['Программа обучения'], ai:'Утверждена программа. Не загружены списки групп. Рекомендуется начать обучение.', history:['01.04 — Мероприятие создано','15.05 — Утверждена программа','01.06 — ИИ: ожидается список групп'] },
|
||
{id:6, title:'Проверка состояния ж/д переездов', branch:branches[3], dir:directions[5], status:'done', progress:100, due:'10.05.2026', done:'05.05.2026', resp:'Искаков Р.Н.', docs:['Акт осмотра','Предписание','Фото'], ai:'Проверка проведена. Выявлено 3 замечания. Устранены в срок.', history:['01.04 — Мероприятие создано','28.04 — Акт осмотра','05.05 — Статус: Исполнено'] },
|
||
{id:7, title:'Монтаж системы оповещения на производстве', branch:branches[2], dir:directions[1], status:'wait', progress:0, due:'01.12.2026', done:'—', resp:'Сапаров А.Д.', docs:[], ai:'Мероприятие не начато. До срока 180 дней. Рекомендуется назначить поставщика.', history:['01.06 — Мероприятие создано'] },
|
||
{id:8, title:'Медицинский осмотр работников вредных производств', branch:branches[5], dir:directions[0], status:'done', progress:100, due:'01.04.2026', done:'28.03.2026', resp:'Алиева Г.С.', docs:['Приказ','Заключения врачей','Отчёт'], ai:'Осмотр пройден. 100% охват. Противопоказаний не выявлено.', history:['01.03 — Мероприятие создано','25.03 — Загружены заключения','28.03 — Статус: Исполнено'] },
|
||
{id:9, title:'Ремонт ограждения опасной зоны на участке №3', branch:branches[0], dir:directions[2], status:'late', progress:20, due:'15.03.2026', done:'—', resp:'Маратов Ж.К.', docs:[], ai:'Просрочено 81 день. Отсутствует проектная документация. Требуется вмешательство.', history:['15.01 — Мероприятие создано','15.03 — Срок истёк','20.03 — Эскалация'] },
|
||
{id:10, title:'Закуп СИЗ для полевых бригад', branch:branches[1], dir:directions[0], status:'warn', progress:80, due:'15.07.2026', done:'—', resp:'Омарова Д.Т.', docs:['Спецификация','Счёт-фактура'], ai:'СИЗ закуплены. Ожидается поставка. Рисков срыва нет.', history:['01.05 — Мероприятие создано','01.06 — Счёт оплачен'] },
|
||
{id:11, title:'Пересмотр плана ликвидации аварий', branch:branches[4], dir:directions[4], status:'warn', progress:50, due:'01.09.2026', done:'—', resp:'Касымов Б.Е.', docs:['Проект ПЛА'], ai:'Проект ПЛА на согласовании. Замечаний от проверяющих нет.', history:['01.04 — Мероприятие создано','20.05 — Проект загружен'] },
|
||
{id:12, title:'Проверка дымовых извещателей в админздании', branch:branches[7], dir:directions[1], status:'done', progress:100, due:'01.05.2026', done:'28.04.2026', resp:'Тулегенов Е.А.', docs:['Акт проверки','Сертификаты'], ai:'Все извещатели исправны. Сертификаты действительны.', history:['01.04 — Мероприятие создано','28.04 — Статус: Исполнено'] },
|
||
]
|
||
|
||
// ===== HELPERS =====
|
||
function pctBar(p){return `<div class="track"><div class="fill" style="width:${p}%;background:${p>=80?'var(--green)':p>=40?'var(--amber)':'var(--red)'}"></div></div> ${p}%`}
|
||
function sBadge(s){
|
||
const map={done:'green',warn:'amber',late:'red',wait:'gray'}
|
||
return `<span class="badge ${map[s]||'gray'}">${statuses[s]||s}</span>`
|
||
}
|
||
|
||
// ===== PAGES =====
|
||
function pageDashboard(){
|
||
let done=events.filter(e=>e.status==='done').length
|
||
let late=events.filter(e=>e.status==='late').length
|
||
let warn=events.filter(e=>e.status==='warn').length
|
||
let total=events.length
|
||
let donePct=Math.round(done/total*100)
|
||
|
||
return `
|
||
<div class="page active" id="page-dashboard">
|
||
<h2>Дашборд производственной безопасности</h2>
|
||
<div class="stats-row">
|
||
<div class="stat-card green"><div class="stat-label">Всего мероприятий</div><div class="stat-num">${total}</div></div>
|
||
<div class="stat-card green"><div class="stat-label">Исполнено</div><div class="stat-num">${done}</div><div class="stat-sub">${donePct}% от плана</div></div>
|
||
<div class="stat-card amber"><div class="stat-label">На контроле</div><div class="stat-num">${warn}</div></div>
|
||
<div class="stat-card red"><div class="stat-label">Просрочено</div><div class="stat-num">${late}</div></div>
|
||
<div class="stat-card blue"><div class="stat-label">Общий прогресс</div><div class="stat-num">${donePct}%</div></div>
|
||
</div>
|
||
|
||
<div class="grid2">
|
||
<div class="panel">
|
||
<h3>Исполнение по направлениям</h3>
|
||
${directions.map(d=>{
|
||
let items=events.filter(e=>e.dir===d)
|
||
let pct=Math.round(items.filter(e=>e.status==='done').length/Math.max(1,items.length)*100)
|
||
return `<div class="rating-bar"><span class="name">${d}</span><div class="track"><div class="fill" style="width:${pct}%;background:var(--cyan)"></div></div><span style="font-size:13px;font-weight:700;width:40px;text-align:right">${pct}%</span></div>`
|
||
}).join('')}
|
||
</div>
|
||
<div class="panel">
|
||
<h3>Рейтинг организаций</h3>
|
||
${branches.slice(0,6).map(b=>{
|
||
let items=events.filter(e=>e.branch===b)
|
||
let pct=Math.round(items.filter(e=>e.status==='done').length/Math.max(1,items.length)*100)
|
||
return `<div class="rating-bar"><span class="name">${b.replace('АО ','').replace('ТОО ','')}</span><div class="track"><div class="fill" style="width:${pct}%;background:${pct>=70?'var(--green)':pct>=40?'var(--amber)':'var(--red)'}"></div></div><span style="font-size:13px;font-weight:700;width:40px;text-align:right">${pct}%</span></div>`
|
||
}).join('')}
|
||
</div>
|
||
</div>
|
||
|
||
<div class="panel">
|
||
<h3>Динамика исполнения по кварталам</h3>
|
||
<div class="chart-stub">
|
||
<div class="bar" style="height:40%"></div>
|
||
<div class="bar" style="height:55%"></div>
|
||
<div class="bar" style="height:70%"></div>
|
||
<div class="bar" style="height:${donePct}%"></div>
|
||
</div>
|
||
<div class="chart-labels"><span>Q1</span><span>Q2</span><span>Q3</span><span>Q4 (план)</span></div>
|
||
</div>
|
||
</div>`
|
||
}
|
||
|
||
function pageRegister(filter='',statusFilter=''){
|
||
let list=events
|
||
if(filter) list=list.filter(e=>e.title.toLowerCase().includes(filter.toLowerCase())||e.branch.toLowerCase().includes(filter.toLowerCase()))
|
||
if(statusFilter) list=list.filter(e=>e.status===statusFilter)
|
||
|
||
return `
|
||
<div class="page active" id="page-register">
|
||
<h2>Реестр мероприятий</h2>
|
||
<div class="filters">
|
||
<input placeholder="Поиск по названию или организации..." oninput="refreshPage('register',this.value,document.getElementById('statusFilter')?.value||'')" id="searchInput">
|
||
<select onchange="refreshPage('register',document.getElementById('searchInput')?.value||'',this.value)" id="statusFilter">
|
||
<option value="">Все статусы</option>
|
||
<option value="done">Исполнено</option>
|
||
<option value="warn">На контроле</option>
|
||
<option value="late">Просрочено</option>
|
||
<option value="wait">Не начато</option>
|
||
</select>
|
||
<span class="count">Найдено: ${list.length}</span>
|
||
</div>
|
||
<div class="panel" style="overflow-x:auto">
|
||
<table>
|
||
<thead><tr><th>№</th><th>Мероприятие</th><th>Организация</th><th>Направление</th><th>Срок</th><th>Прогресс</th><th>Статус</th></tr></thead>
|
||
<tbody>${list.map(e=>`
|
||
<tr style="cursor:pointer" onclick="openEvent(${e.id})">
|
||
<td>${e.id}</td>
|
||
<td><strong>${e.title}</strong></td>
|
||
<td style="font-size:13px">${e.branch}</td>
|
||
<td style="font-size:13px">${e.dir}</td>
|
||
<td style="font-size:13px">${e.due}</td>
|
||
<td>${pctBar(e.progress)}</td>
|
||
<td>${sBadge(e.status)}</td>
|
||
</tr>`).join('')}</tbody>
|
||
</table>
|
||
</div>
|
||
</div>`
|
||
}
|
||
|
||
function pageAnalytics(){
|
||
let orgRatings=branches.map(b=>{
|
||
let items=events.filter(e=>e.branch===b)
|
||
let done=items.filter(e=>e.status==='done').length
|
||
let late=items.filter(e=>e.status==='late').length
|
||
return {name:b,done,late,total:items.length,pct:Math.round(done/Math.max(1,items.length)*100)}
|
||
}).sort((a,b)=>b.pct-a.pct)
|
||
|
||
return `
|
||
<div class="page active" id="page-analytics">
|
||
<h2>Аналитика</h2>
|
||
<div class="grid2">
|
||
<div class="panel">
|
||
<h3>Рейтинг организаций по исполнению</h3>
|
||
${orgRatings.map((r,i)=>`
|
||
<div class="rating-bar">
|
||
<span class="name">${i+1}. ${r.name}</span>
|
||
<div class="track"><div class="fill" style="width:${r.pct}%;background:${r.pct>=70?'var(--green)':r.pct>=40?'var(--amber)':'var(--red)'}"></div></div>
|
||
<span style="font-size:13px;font-weight:700;width:60px;text-align:right">${r.pct}% (${r.done}/${r.total})</span>
|
||
</div>`).join('')}
|
||
</div>
|
||
<div class="panel">
|
||
<h3>Просроченные мероприятия</h3>
|
||
${events.filter(e=>e.status==='late').map(e=>`
|
||
<div class="rating-bar">
|
||
<span class="name" style="font-size:13px">${e.title.slice(0,40)}...</span>
|
||
<span style="font-size:13px;color:var(--red);font-weight:700">${e.due}</span>
|
||
<span class="badge red">${e.branch.split('"')[1]||e.branch}</span>
|
||
</div>`).join('')||'<p style="color:var(--gray-500)">Просроченных мероприятий нет</p>'}
|
||
</div>
|
||
</div>
|
||
<div class="grid3" style="margin-top:24px">
|
||
<div class="panel" style="text-align:center">
|
||
<h3>Всего мероприятий</h3>
|
||
<div style="font-size:48px;font-weight:800;color:var(--cyan)">${events.length}</div>
|
||
</div>
|
||
<div class="panel" style="text-align:center">
|
||
<h3>Документов загружено</h3>
|
||
<div style="font-size:48px;font-weight:800;color:var(--green)">${events.reduce((s,e)=>s+e.docs.length,0)}</div>
|
||
</div>
|
||
<div class="panel" style="text-align:center">
|
||
<h3>Средний % исполнения</h3>
|
||
<div style="font-size:48px;font-weight:800;color:var(--blue)">${Math.round(events.reduce((s,e)=>s+e.progress,0)/events.length)}%</div>
|
||
</div>
|
||
</div>
|
||
</div>`
|
||
}
|
||
|
||
function pageReports(){
|
||
return `
|
||
<div class="page active" id="page-reports">
|
||
<h2>Формирование отчётности</h2>
|
||
<div class="stats-row">
|
||
<div class="stat-card"><div class="stat-label">Ежемесячный отчёт</div><div class="stat-num" style="font-size:20px">За май 2026</div><div class="stat-sub"><span class="badge green">Сформирован</span></div></div>
|
||
<div class="stat-card"><div class="stat-label">Квартальный отчёт</div><div class="stat-num" style="font-size:20px">Q2 2026</div><div class="stat-sub"><span class="badge amber">В обработке</span></div></div>
|
||
<div class="stat-card"><div class="stat-label">Годовой отчёт</div><div class="stat-num" style="font-size:20px">2026</div><div class="stat-sub"><span class="badge gray">Ожидается</span></div></div>
|
||
</div>
|
||
<div class="panel" style="margin-top:16px">
|
||
<h3>Все отчёты</h3>
|
||
<table>
|
||
<thead><tr><th>Тип отчёта</th><th>Период</th><th>Статус</th><th>Дата формирования</th><th>Формат</th></tr></thead>
|
||
<tbody>
|
||
<tr><td>Ежемесячный отчёт</td><td>Май 2026</td><td><span class="badge green">Готов</span></td><td>01.06.2026</td><td>PDF, Excel</td></tr>
|
||
<tr><td>Аналитическая справка</td><td>Май 2026</td><td><span class="badge green">Готов</span></td><td>02.06.2026</td><td>PDF, Word</td></tr>
|
||
<tr><td>Презентация для совещания</td><td>Июнь 2026</td><td><span class="badge amber">В работе</span></td><td>—</td><td>PPTX</td></tr>
|
||
<tr><td>Квартальный отчёт</td><td>Q2 2026</td><td><span class="badge amber">В работе</span></td><td>—</td><td>PDF, Excel</td></tr>
|
||
<tr><td>Рейтинг организаций</td><td>Июнь 2026</td><td><span class="badge green">Готов</span></td><td>04.06.2026</td><td>PDF</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>`
|
||
}
|
||
|
||
function pageRisks(){
|
||
let risks=[
|
||
{event:'Замена средств пожаротушения на складе ГСМ', branch:branches[1], level:'medium', reason:'Отставание от графика на 15%'},
|
||
{event:'Аттестация сварщиков', branch:branches[4], level:'high', reason:'Просрочка 64 дня, отсутствуют документы'},
|
||
{event:'Ремонт ограждения опасной зоны', branch:branches[0], level:'high', reason:'Просрочка 81 день, нет проектной документации'},
|
||
{event:'Обучение персонала по ГО и ЧС', branch:branches[7], level:'low', reason:'Не загружены списки групп'},
|
||
{event:'Пересмотр плана ликвидации аварий', branch:branches[4], level:'low', reason:'План на согласовании, замечаний нет'},
|
||
]
|
||
return `
|
||
<div class="page active" id="page-risks">
|
||
<h2>Карта рисков</h2>
|
||
<div class="stats-row">
|
||
<div class="stat-card red"><div class="stat-label">Высокий риск</div><div class="stat-num">${risks.filter(r=>r.level==='high').length}</div></div>
|
||
<div class="stat-card amber"><div class="stat-label">Средний риск</div><div class="stat-num">${risks.filter(r=>r.level==='medium').length}</div></div>
|
||
<div class="stat-card blue"><div class="stat-label">Низкий риск</div><div class="stat-num">${risks.filter(r=>r.level==='low').length}</div></div>
|
||
</div>
|
||
<div class="panel">
|
||
<h3>Реестр рисков</h3>
|
||
<table>
|
||
<thead><tr><th>Мероприятие</th><th>Организация</th><th>Уровень риска</th><th>Причина</th></tr></thead>
|
||
<tbody>${risks.map(r=>`
|
||
<tr>
|
||
<td><strong>${r.event}</strong></td>
|
||
<td style="font-size:13px">${r.branch}</td>
|
||
<td>${r.level==='high'?'<span class="badge red">Высокий</span>':r.level==='medium'?'<span class="badge amber">Средний</span>':'<span class="badge blue">Низкий</span>'}</td>
|
||
<td style="font-size:13px">${r.reason}</td>
|
||
</tr>`).join('')}</tbody>
|
||
</table>
|
||
</div>
|
||
</div>`
|
||
}
|
||
|
||
function openEvent(id){
|
||
let e=events.find(x=>x.id===id)
|
||
if(!e) return
|
||
document.getElementById('modalContent').innerHTML=`
|
||
<button class="close" onclick="closeModal()">×</button>
|
||
<div class="ai-tag">🤖 Анализ ИИ</div>
|
||
<h2>${e.title}</h2>
|
||
<div class="meta">
|
||
<div class="field">Организация<strong>${e.branch}</strong></div>
|
||
<div class="field">Направление<strong>${e.dir}</strong></div>
|
||
<div class="field">Срок исполнения<strong>${e.due}</strong></div>
|
||
<div class="field">Дата исполнения<strong>${e.done}</strong></div>
|
||
<div class="field">Ответственный<strong>${e.resp}</strong></div>
|
||
<div class="field">Прогресс<strong>${pctBar(e.progress)}</strong></div>
|
||
<div class="field">Статус<strong>${sBadge(e.status)}</strong></div>
|
||
</div>
|
||
${e.docs.length?`<div style="font-weight:600;margin:8px 0 4px;font-size:14px">Подтверждающие материалы:</div><div class="docs">${e.docs.map(d=>`<span>📄 ${d}</span>`).join('')}</div>`:''}
|
||
<div class="ai-block"><h4>🤖 Выводы ИИ-агента</h4>${e.ai}</div>
|
||
<div style="font-weight:600;margin:8px 0 4px;font-size:14px">История изменений:</div>
|
||
<div class="timeline">${e.history.map(h=>`<div>${h}</div>`).join('')}</div>
|
||
`
|
||
document.getElementById('modalOverlay').classList.add('open')
|
||
}
|
||
|
||
function closeModal(){ document.getElementById('modalOverlay').classList.remove('open') }
|
||
|
||
// NAVIGATION
|
||
function navTo(page, filter='', statusFilter=''){
|
||
document.querySelectorAll('.sidebar nav a').forEach(a=>a.classList.remove('active'))
|
||
document.querySelector(`[data-page="${page}"]`)?.classList.add('active')
|
||
let content
|
||
switch(page){
|
||
case 'dashboard': content=pageDashboard(); break
|
||
case 'register': content=pageRegister(filter,statusFilter); break
|
||
case 'analytics': content=pageAnalytics(); break
|
||
case 'reports': content=pageReports(); break
|
||
case 'risks': content=pageRisks(); break
|
||
default: content=pageDashboard()
|
||
}
|
||
document.getElementById('mainContent').innerHTML=content
|
||
}
|
||
|
||
function refreshPage(page,filter,statusFilter){ navTo(page,filter,statusFilter) }
|
||
|
||
document.querySelectorAll('.sidebar nav a').forEach(a=>{
|
||
a.addEventListener('click',e=>{
|
||
e.preventDefault()
|
||
navTo(a.dataset.page)
|
||
})
|
||
})
|
||
|
||
document.getElementById('modalOverlay').addEventListener('click',function(e){
|
||
if(e.target===this) closeModal()
|
||
})
|
||
|
||
document.addEventListener('keydown',e=>{if(e.key==='Escape') closeModal()})
|
||
|
||
// INIT
|
||
navTo('dashboard')
|
||
</script>
|
||
</body>
|
||
</html> |