safety-audit/index.html

517 lines
87 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

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

<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Поведенческий аудит безопасности (ПАБ)</title>
<script>
// Login logic — loaded first, guaranteed to work
function doLoginNow(){
var u = document.getElementById("loginUser").value.trim().toLowerCase();
var p = document.getElementById("loginPass").value.trim();
if (u === "admin" && p === "admin") {
document.getElementById("loginScreen").style.display = "none";
document.getElementById("appScreen").style.display = "block";
return;
}
var users = {};
try { users = JSON.parse(localStorage.getItem("pab_users") || "{}"); } catch(e) {}
if (users[u] && users[u].pass === p) {
document.getElementById("loginScreen").style.display = "none";
document.getElementById("appScreen").style.display = "block";
return;
}
document.getElementById("loginError").style.display = "block";
}
</script>
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.7/dist/chart.umd.min.js"></script>
<style>
:root{--ink:#0F1218;--cyan:#00B4D8;--cyan-light:#48CAE4;--cyan-bg:#E0F7FA;--white:#fff;--gray-500:#5B6573;--gray-100:#F2F4F7;--gray-200:#E2E6EB;--red:#E63946;--red-bg:#FFEBED;--green:#2D6A4F;--green-bg:#EDF7F0;--orange:#E76F51;--orange-bg:#FFF3EF;--radius:8px;--radius-lg:14px;--shadow:0 2px 12px rgba(0,0,0,.06)}
*{box-sizing:border-box;margin:0;padding:0}
body{font:15px/1.5 -apple-system,BlinkMacSystemFont,"Segoe UI",Inter,system-ui,sans-serif;color:var(--ink);background:var(--gray-100);min-height:100vh}
.login-screen{display:flex;align-items:flex-start;justify-content:center;min-height:100vh;background:linear-gradient(135deg,var(--ink)0%,#1a2332 100%);padding:40px 20px}
.login-card{background:var(--white);border-radius:var(--radius-lg);padding:40px 36px;width:100%;max-width:520px;box-shadow:0 8px 40px rgba(0,0,0,.2)}
.logo{text-align:center;margin-bottom:24px}
.logo .icon{font-size:40px;display:block;margin-bottom:6px}
.logo h1{font-size:20px;font-weight:800;color:var(--ink)}
.logo p{font-size:13px;color:var(--gray-500);margin-top:2px}
.fg{margin-bottom:14px}
.fg label{display:block;font-size:11px;font-weight:700;color:var(--gray-500);margin-bottom:4px;text-transform:uppercase;letter-spacing:.5px}
.fg input,.fg select,.fg textarea{width:100%;padding:9px 10px;border:2px solid var(--gray-200);border-radius:var(--radius);font-size:13px;font-family:inherit;color:var(--ink);background:var(--white);transition:border-color .2s;outline:none}
.fg input:focus,.fg select:focus,.fg textarea:focus{border-color:var(--cyan)}
.fg textarea{resize:vertical;min-height:60px}
.fg-row{display:grid;grid-template-columns:1fr 1fr;gap:10px}
.fg-row.col3{grid-template-columns:1fr 1fr 1fr}
.btn{display:inline-flex;align-items:center;justify-content:center;gap:6px;padding:10px 20px;border-radius:var(--radius);font-size:14px;font-weight:700;border:none;cursor:pointer;text-decoration:none;font-family:inherit;transition:all .2s;white-space:nowrap}
.btn-primary{background:var(--cyan);color:var(--white)}.btn-primary:hover{background:var(--cyan-light)}
.btn-danger{background:var(--red);color:var(--white)}.btn-danger:hover{background:#c1121f}
.btn-outline{background:transparent;color:var(--ink);border:2px solid var(--gray-200)}.btn-outline:hover{border-color:var(--cyan);color:var(--cyan)}
.btn-sm{padding:6px 14px;font-size:12px}
.btn-block{width:100%}
.err-msg{color:var(--red);font-size:12px;text-align:center;margin-top:10px;display:none}
.ok-msg{background:var(--green-bg);border:1px solid var(--green);border-radius:var(--radius);padding:10px 14px;color:var(--green);font-weight:600;text-align:center;margin-top:10px;display:none}
.login-tabs{display:flex;gap:0;margin-bottom:20px;border-radius:var(--radius);overflow:hidden;border:2px solid var(--gray-200)}
.login-tab{flex:1;padding:8px;text-align:center;font-size:13px;font-weight:700;cursor:pointer;background:var(--white);color:var(--gray-500);transition:all .2s;border:none;font-family:inherit}
.login-tab.active{background:var(--cyan);color:var(--white)}
.login-tab:first-child{border-right:1px solid var(--gray-200)}
.login-form{display:none}.login-form.active{display:block}
.app-screen{display:none}
.app-header{background:var(--ink);color:var(--white);padding:0 24px;display:flex;align-items:center;justify-content:space-between;height:56px;position:sticky;top:0;z-index:100;box-shadow:0 2px 8px rgba(0,0,0,.15)}
.app-header .logo-area{display:flex;align-items:center;gap:8px;font-weight:700;font-size:15px}
.app-header .logo-area .icon{font-size:22px}
.app-header nav{display:flex;gap:4px}
.app-header nav a{color:#9aa3b2;text-decoration:none;padding:7px 14px;border-radius:var(--radius);font-size:13px;font-weight:600;transition:all .2s}
.app-header nav a:hover,.app-header nav a.active{color:var(--white);background:rgba(255,255,255,.08)}
.app-header .user-area{display:flex;align-items:center;gap:10px;font-size:13px}
.app-header .user-area .role{color:var(--cyan-light);font-weight:600}
.app-content{max-width:1140px;margin:0 auto;padding:24px}
.panel{display:none}.panel.active{display:block}
.page-header{margin-bottom:24px}
.page-header h2{font-size:26px;font-weight:800;margin-bottom:6px}
.page-header p{color:var(--gray-500);font-size:15px}
.schedule-alert{background:var(--orange-bg);border:1px solid var(--orange);border-radius:var(--radius-lg);padding:16px 20px;margin-bottom:20px;display:none;box-shadow:var(--shadow)}
.schedule-alert.show{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:12px}
.schedule-alert .alert-text{font-size:14px;font-weight:600;color:var(--orange)}
.schedule-alert.danger{background:var(--red-bg);border-color:var(--red)}.schedule-alert.danger .alert-text{color:var(--red)}
.progress-card{background:var(--white);border-radius:var(--radius-lg);padding:20px;box-shadow:var(--shadow);margin-bottom:16px}
.progress-card h3{font-size:15px;font-weight:700;margin-bottom:4px}
.progress-card .quota-info{font-size:12px;color:var(--gray-500);margin-bottom:10px}
.progress-bar{height:16px;border-radius:8px;background:var(--gray-200);overflow:hidden;margin-bottom:6px}
.progress-fill{height:100%;border-radius:8px;transition:width .5s}
.progress-fill.good{background:var(--green)}.progress-fill.warn{background:var(--orange)}.progress-fill.bad{background:var(--red)}
.progress-card .progress-stats{font-size:12px;font-weight:600}
.audit-form{max-width:900px}
.form-header{background:var(--white);border-radius:var(--radius-lg);padding:24px 28px;box-shadow:var(--shadow);margin-bottom:16px}
.form-header h3{font-size:17px;font-weight:700;margin-bottom:16px}
.hgrid{display:grid;grid-template-columns:1fr 1fr 1fr;gap:14px}
.hgrid.col2{grid-template-columns:1fr 1fr}.hgrid.col4{grid-template-columns:1fr 1fr 1fr 1fr}
.hgrid .fg label{font-size:11px;font-weight:700;color:var(--gray-500);display:block;margin-bottom:3px;text-transform:uppercase}
.hgrid .fg input,.hgrid .fg select{width:100%;padding:8px 10px;border:2px solid var(--gray-200);border-radius:var(--radius);font-size:13px;font-family:inherit;outline:none;background:var(--white)}
.hgrid .fg input:focus,.hgrid .fg select:focus{border-color:var(--cyan)}
.overall-toggle{display:flex;gap:12px;margin-top:12px}
.toggle-btn{flex:1;padding:10px;border:2px solid var(--gray-200);border-radius:var(--radius);text-align:center;cursor:pointer;font-size:13px;font-weight:700;background:var(--white);transition:all .2s}
.toggle-btn.safe.selected{border-color:var(--green);background:var(--green-bg);color:var(--green)}
.toggle-btn.danger.selected{border-color:var(--red);background:var(--red-bg);color:var(--red)}
.cat-section{background:var(--white);border-radius:var(--radius-lg);box-shadow:var(--shadow);margin-bottom:12px;overflow:hidden}
.cat-header{display:flex;align-items:center;justify-content:space-between;padding:14px 20px;background:var(--gray-100);cursor:pointer;user-select:none;border-bottom:1px solid var(--gray-200);transition:background .2s}
.cat-header:hover{background:var(--gray-200)}
.cat-header .cat-title{font-size:15px;font-weight:700;display:flex;align-items:center;gap:8px}
.cat-badge{font-size:11px;font-weight:700;padding:3px 10px;border-radius:20px;background:var(--red-bg);color:var(--red)}
.cat-badge.all-safe{background:var(--green-bg);color:var(--green)}
.cat-arrow{font-size:12px;transition:transform .3s;color:var(--gray-500)}
.cat-header.open .cat-arrow{transform:rotate(180deg)}
.cat-body{display:none;padding:16px 20px}.cat-body.open{display:block}
.checklist{display:grid;grid-template-columns:1fr 1fr;gap:6px 24px}
.checklist.col3{grid-template-columns:1fr 1fr 1fr}.checklist.col1{grid-template-columns:1fr}
.check-item{display:flex;align-items:flex-start;gap:8px;padding:6px 0;font-size:13px;cursor:pointer}
.check-item input[type=checkbox]{margin-top:2px;width:16px;height:16px;accent-color:var(--red);cursor:pointer;flex-shrink:0}
.check-item.checked label{color:var(--red);font-weight:600}
.check-item label{cursor:pointer;flex:1}
.check-item .other-input{width:100%;margin-top:4px;padding:6px 8px;border:1px solid var(--gray-200);border-radius:4px;font-size:12px;display:none}
.check-item.checked .other-input.visible{display:block}
.cat-footer{display:flex;align-items:center;justify-content:space-between;padding:10px 20px;background:var(--gray-100);border-top:1px solid var(--gray-200);font-size:12px;color:var(--gray-500);font-weight:600}
.cat-footer .total-count{color:var(--red);font-weight:700}.cat-footer .total-count.zero{color:var(--green)}
.all-safe-toggle{display:flex;align-items:center;gap:6px;cursor:pointer;font-size:12px;font-weight:700;padding:4px 12px;border-radius:20px;transition:all .2s}
.all-safe-toggle.active{background:var(--green-bg);color:var(--green)}.all-safe-toggle input{display:none}
.violations-block{background:var(--white);border-radius:var(--radius-lg);padding:20px 24px;box-shadow:var(--shadow);margin-top:16px}
.violations-block h3{font-size:15px;font-weight:700;margin-bottom:14px}
.vio-grid{display:grid;grid-template-columns:40px 1.3fr 1fr .8fr 1fr 1fr 1fr .8fr 30px;gap:6px;margin-bottom:6px;align-items:end}
.vio-grid.header-row{font-size:11px;font-weight:700;color:var(--gray-500);text-transform:uppercase;margin-bottom:4px}
.vio-grid input,.vio-grid select{padding:7px 8px;border:1px solid var(--gray-200);border-radius:var(--radius);font-size:12px;font-family:inherit;outline:none;width:100%}
.vio-grid input:focus,.vio-grid select:focus{border-color:var(--cyan)}
.vio-row-num{font-size:12px;font-weight:700;color:var(--gray-500);text-align:center;padding-top:8px}
.remove-vio-btn{background:none;border:none;color:var(--red);cursor:pointer;font-size:18px;padding:4px}
.form-actions{display:flex;gap:10px;margin-top:20px}
.form-success{background:var(--green-bg);border:1px solid var(--green);border-radius:var(--radius);padding:16px 20px;color:var(--green);font-weight:600;margin-top:14px;display:none}
.stats-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:16px;margin-bottom:24px}
.stat-card{background:var(--white);border-radius:var(--radius-lg);padding:20px;box-shadow:var(--shadow)}
.stat-card .stat-label{font-size:12px;font-weight:700;color:var(--gray-500);text-transform:uppercase;margin-bottom:4px}
.stat-card .stat-value{font-size:32px;font-weight:800;line-height:1}
.stat-card.green .stat-value{color:var(--green)}.stat-card.red .stat-value{color:var(--red)}.stat-card.blue .stat-value{color:var(--cyan)}.stat-card.orange .stat-value{color:var(--orange)}
.charts-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(300px,1fr));gap:16px;margin-bottom:16px}
.chart-card{background:var(--white);border-radius:var(--radius-lg);padding:20px;box-shadow:var(--shadow)}
.chart-card h3{font-size:15px;font-weight:700;margin-bottom:14px}
.chart-card canvas{max-height:260px}
.filter-bar{display:flex;gap:10px;margin-bottom:20px;flex-wrap:wrap;align-items:center}
.filter-bar select,.filter-bar input{padding:8px 12px;border:2px solid var(--gray-200);border-radius:var(--radius);font-size:13px;font-family:inherit;outline:none;background:var(--white)}
.filter-bar select:focus,.filter-bar input:focus{border-color:var(--cyan)}
.table-wrap{overflow-x:auto}
.data-table{width:100%;border-collapse:collapse;background:var(--white);border-radius:var(--radius-lg);overflow:hidden;box-shadow:var(--shadow);font-size:13px}
.data-table th{background:var(--ink);color:var(--white);padding:11px 14px;text-align:left;font-size:12px;font-weight:700;text-transform:uppercase}
.data-table td{padding:10px 14px;border-bottom:1px solid var(--gray-100)}
.data-table tr:hover td{background:var(--gray-100)}
.badge{display:inline-block;padding:3px 10px;border-radius:20px;font-size:11px;font-weight:700}
.badge-safe{background:var(--green-bg);color:var(--green)}.badge-danger{background:var(--red-bg);color:var(--red)}.badge-warn{background:var(--orange-bg);color:var(--orange)}
.no-data{text-align:center;padding:40px 20px;color:var(--gray-500)}.no-data .icon{font-size:40px;display:block;margin-bottom:10px}
.risk-bar{display:flex;height:20px;border-radius:10px;overflow:hidden;margin-top:6px}
.risk-safe{background:var(--green);transition:width .5s}.risk-unsafe{background:var(--red);transition:width .5s}
.risk-labels{display:flex;justify-content:space-between;font-size:11px;color:var(--gray-500);margin-top:3px}
.view-link{color:var(--cyan);cursor:pointer;font-weight:600;text-decoration:none}.view-link:hover{text-decoration:underline}
@media(max-width:768px){
.login-card{padding:28px 20px;margin:0;max-width:100%}
.app-header{padding:0 12px;height:auto;flex-wrap:wrap;gap:6px;padding-top:8px;padding-bottom:8px}
.app-header nav{order:3;width:100%;overflow-x:auto}
.app-content{padding:16px 12px}
.hgrid{grid-template-columns:1fr 1fr}.hgrid.col4{grid-template-columns:1fr 1fr}
.checklist{grid-template-columns:1fr}.checklist.col3{grid-template-columns:1fr 1fr}
.charts-grid{grid-template-columns:1fr}.stats-grid{grid-template-columns:1fr 1fr}
.fg-row{grid-template-columns:1fr}.fg-row.col3{grid-template-columns:1fr 1fr}
.vio-grid{grid-template-columns:30px 1fr 1fr;row-gap:4px}.vio-grid.header-row{display:none}
}
</style>
</head>
<body>
<div id="loginScreen" class="login-screen">
<div class="login-card">
<div class="logo"><span class="icon">🛡️</span><h1>Поведенческий аудит безопасности</h1><p>Система учёта и аналитики ПАБ</p></div>
<div class="login-tabs"><button class="login-tab active" id="tabLogin">Вход</button><button class="login-tab" id="tabRegister">Регистрация</button></div>
<div id="formLogin" class="login-form active">
<div class="fg"><label>Логин</label><input type="text" id="loginUser" placeholder="Введите логин"></div>
<div class="fg"><label>Пароль</label><input type="password" id="loginPass" placeholder="Введите пароль"></div>
<button class="btn btn-primary btn-block" id="loginBtn" onclick="doLoginNow()">Войти</button>
<div class="err-msg" id="loginError">Неверный логин или пароль</div>
</div>
<div id="formRegister" class="login-form">
<div class="fg-row"><div class="fg"><label>Логин</label><input type="text" id="regLogin" placeholder="ivanov"></div><div class="fg"><label>Пароль</label><input type="password" id="regPass" placeholder="Минимум 3 символа"></div></div>
<div class="fg-row"><div class="fg"><label>ФИО</label><input type="text" id="regName" placeholder="Фамилия И.О."></div><div class="fg"><label>Email</label><input type="email" id="regEmail" placeholder="email@telecom.kz"></div></div>
<div class="fg"><label>Должность</label><select id="regRole"><option value="">-- Выберите --</option><option>Директор департамента ЦА</option><option>Директор департамента филиала</option><option>Региональный директор филиала</option><option>Директор ДЭСД</option><option>Начальник ТУСМ</option><option>Руководитель структурного подразделения</option><option>Начальник центра/службы/цеха</option><option>Начальник участка</option><option>Инженер БиОТ</option><option>Работник отдела БиОТ</option><option>Сотрудник</option></select><div id="roleHint" style="font-size:11px;color:var(--gray-500);margin-top:4px"></div></div>
<div class="fg-row"><div class="fg"><label>Филиал</label><input type="text" id="regBranch" placeholder="АО «Казахтелеком»"></div><div class="fg"><label>Подразделение</label><input type="text" id="regDept" placeholder="ЦТО МС"></div></div>
<div class="fg-row"><div class="fg"><label>Регион</label><input type="text" id="regRegion" placeholder="Алматинский"></div><div class="fg"><label>Область</label><input type="text" id="regOblast" placeholder="Алматинская обл."></div></div>
<div class="fg"><label>Город / село</label><input type="text" id="regCity" placeholder="г. Алматы"></div>
<button class="btn btn-primary btn-block" id="regBtn">Зарегистрироваться</button>
<div class="err-msg" id="regError"></div><div class="ok-msg" id="regSuccess">Регистрация успешна! Сейчас войдите.</div>
</div>
</div>
</div>
<div id="appScreen" class="app-screen">
<header class="app-header"><div class="logo-area"><span class="icon">🛡️</span> ПАБ Система</div><nav><a href="#" data-panel="newAudit" class="active">Новый аудит</a><a href="#" data-panel="mySchedule">Мой график</a><a href="#" data-panel="dashboard">Дашборд</a><a href="#" data-panel="violations">Нарушения</a><a href="#" data-panel="history">История</a></nav><div class="user-area"><span class="role" id="displayName"></span><button class="btn btn-outline btn-sm" style="color:#9aa3b2;border-color:#3a4452" id="logoutBtn">Выход</button></div></header>
<div class="app-content">
<div class="schedule-alert" id="scheduleAlert"><span class="alert-text" id="scheduleAlertText"></span><button class="btn btn-primary btn-sm alert-btn" id="scheduleRemindBtn">✉️ Напомнить себе</button></div>
<div id="panelNewAudit" class="panel active">
<div class="page-header"><h2>📋 Бланк поведенческого аудита безопасности</h2><p>Заполните все категории наблюдения</p></div>
<div class="audit-form">
<div class="form-header"><h3>📝 Данные аудита</h3>
<div class="hgrid col4"><div class="fg"><label>Бланк ПАБ №</label><input type="number" id="pabNumber" placeholder="Номер" min="1"></div><div class="fg"><label>Дата</label><input type="date" id="pabDate"></div><div class="fg"><label>Начало</label><input type="time" id="pabTimeStart"></div><div class="fg"><label>Конец</label><input type="time" id="pabTimeEnd"></div></div>
<div class="hgrid" style="margin-top:12px"><div class="fg"><label>Место проведения</label><input id="pabLocation" placeholder="Цех, участок"></div><div class="fg"><label>Тип работы</label><input id="pabWorkType" placeholder="Ремонт линий связи"></div><div class="fg"><label>Регион</label><select id="pabRegion"><option value="">-- Выберите --</option><option>Центральный</option><option>Алматинский</option><option>Восточный</option><option>Западный</option><option>Северный</option><option>Южный</option><option>Другое</option></select></div></div>
<div class="hgrid" style="margin-top:12px"><div class="fg"><label>Кол-во наблюдаемых</label><input type="number" id="pabWorkerCount" min="1" value="1"></div><div class="fg"></div><div class="fg"></div></div>
<div class="hgrid col2" style="margin-top:12px"><div class="fg"><label>ФИО наблюдателя</label><input id="pabObserver"></div><div class="fg"><label>Должность наблюдателя</label><input id="pabObserverRole"></div></div>
<div class="hgrid col2" style="margin-top:12px"><div class="fg"><label>ФИО руководителя</label><input id="pabSupervisor"></div><div class="fg"><label>Должность руководителя</label><input id="pabSupervisorRole"></div></div>
<div class="hgrid" style="margin-top:12px"><div class="fg"><label>Email для уведомления</label><input type="email" id="pabEmail" placeholder="email@telecom.kz"></div><div class="fg"></div><div class="fg"></div></div>
<div class="fg" style="margin-top:14px;margin-bottom:0"><label>Отметка для отдела БиОТ ДПБ</label><div class="overall-toggle"><div class="toggle-btn safe selected" id="overallSafe">ВСЕ БЕЗОПАСНО</div><div class="toggle-btn danger" id="overallDanger">ЕСТЬ ОПАСНО</div></div></div>
</div>
<div id="categorySections"></div>
<div class="violations-block"><h3>📄 Несоответствия и корректирующие меры</h3><div class="vio-grid header-row" style="display:grid"><span></span><span>Несоответствие</span><span>Исполнитель</span><span>Вид</span><span>Меры</span><span>Ответственный</span><span>Дата</span><span>Завершение</span><span></span></div><div id="vioRows"></div><button class="btn btn-outline btn-sm" id="addVioBtn" style="margin-top:8px">+ Добавить строку</button></div>
<div class="violations-block"><h3>💬 Итог диалога безопасности с работником</h3><p style="font-size:12px;color:var(--gray-500);margin-bottom:14px">Зафиксируйте конкретные результаты диалога</p><div class="checklist col1" id="dialogueChecks"><div class="check-item"><input type="checkbox" id="dcb-0"><div><label for="dcb-0">Работник привёл примеры безопасных действий</label></div></div><div class="check-item"><input type="checkbox" id="dcb-1"><div><label for="dcb-1">Были обсуждены риски / проблемы</label></div></div><div class="check-item"><input type="checkbox" id="dcb-2"><div><label for="dcb-2">Определены корректирующие меры</label></div></div><div class="check-item"><input type="checkbox" id="dcb-3"><div><label for="dcb-3">Предложения работника зафиксированы</label></div></div><div class="check-item"><input type="checkbox" id="dcb-4"><div><label for="dcb-4">Другое</label><input class="other-input" id="dialogueOther" placeholder="Укажите..."></div></div></div><div style="margin-top:14px;padding:12px;background:var(--gray-100);border-radius:var(--radius);font-size:12px;color:var(--gray-500)">📎 Фотографии и документы — приложите к бумажному бланку.</div></div>
<div class="form-actions"><button class="btn btn-primary" id="submitAuditBtn">💾 Сохранить аудит</button><button class="btn btn-outline" id="resetAuditBtn">🗑️ Очистить форму</button></div>
<div class="form-success" id="formSuccess"><div style="font-size:18px;margin-bottom:8px">✅ Аудит сохранён!</div><div id="successDetail" style="font-size:13px;color:var(--ink);margin-bottom:14px"></div><div style="display:flex;gap:10px;flex-wrap:wrap"><button class="btn btn-primary btn-sm" id="emailConfirmBtn">✉️ Отправить на email</button><button class="btn btn-outline btn-sm" id="printConfirmBtn">🖨️ Распечатать</button></div></div>
</div></div>
<div id="panelMySchedule" class="panel"><div class="page-header"><h2>📅 Мой график ПАБ</h2><p>Выполнение норматива по должности</p></div><div id="myScheduleContent"></div></div>
<div id="panelDashboard" class="panel">
<div class="page-header"><h2>📊 Дашборд статистики</h2><p>Аналитика по всем аудитам</p></div>
<div class="filter-bar"><div id="adminBar" style="display:none;gap:8px;flex-wrap:wrap"><button class="btn btn-primary btn-sm" id="dlFullCsv">📥 Все данные (CSV)</button><button class="btn btn-outline btn-sm" id="dlWorkerCsv">👥 По работникам (CSV)</button><button class="btn btn-outline btn-sm" id="dlSummaryHtml">📊 Сводный отчёт (HTML)</button><span style="color:var(--gray-200);margin:0 4px">|</span><button class="btn btn-danger btn-sm" id="clearAllBtn">🗑️ Очистить всё</button><button class="btn btn-outline btn-sm" id="clearPeriodBtn">📅 Очистить период</button><button class="btn btn-outline btn-sm" id="showUsersBtn">👥 Пользователи</button></div></div>
<div class="filter-bar"><select id="dfRegion"><option value="all">Все регионы</option></select><select id="dfOblast"><option value="all">Все области</option></select><select id="dfBranch"><option value="all">Все филиалы</option></select><select id="dfDept"><option value="all">Все подразделения</option></select><select id="dfCity"><option value="all">Все города</option></select><span style="font-size:12px;color:var(--gray-500)">с</span><input type="date" id="dfDateFrom" style="width:140px"><span style="font-size:12px;color:var(--gray-500)">по</span><input type="date" id="dfDateTo" style="width:140px"></div>
<div class="stats-grid"><div class="stat-card"><div class="stat-label">Всего аудитов</div><div class="stat-value" id="statTotal">0</div></div><div class="stat-card green"><div class="stat-label">Безопасно</div><div class="stat-value" id="statAllSafe">0</div></div><div class="stat-card red"><div class="stat-label">С нарушениями</div><div class="stat-value" id="statWithDanger">0</div></div><div class="stat-card blue"><div class="stat-label">Нарушений</div><div class="stat-value" id="statViolations">0</div></div><div class="stat-card orange"><div class="stat-label">Выполняют график</div><div class="stat-value" id="statOnTrack">0</div></div><div class="stat-card red"><div class="stat-label">Отстают</div><div class="stat-value" id="statBehind">0</div></div></div>
<div class="chart-card" style="margin-bottom:16px"><h3>🟢🔴 Соотношение аудитов</h3><div class="risk-bar"><div class="risk-safe" id="riskSafeBar" style="width:50%"></div><div class="risk-unsafe" id="riskUnsafeBar" style="width:50%"></div></div><div class="risk-labels"><span>Безопасные: <span id="riskSafeLabel">0</span></span><span>С нарушениями: <span id="riskUnsafeLabel">0</span></span></div></div>
<div class="charts-grid"><div class="chart-card"><h3>📂 Нарушения по категориям</h3><canvas id="chartCategories"></canvas></div><div class="chart-card"><h3>📅 Динамика по датам</h3><canvas id="chartTimeline"></canvas></div><div class="chart-card"><h3>🏢 По регионам</h3><canvas id="chartRegions"></canvas></div><div class="chart-card"><h3>🏭 По филиалам</h3><canvas id="chartBranches"></canvas></div><div class="chart-card"><h3>👤 График по должностям</h3><canvas id="chartRoles"></canvas></div><div class="chart-card"><h3>🔝 Топ-10 нарушений</h3><canvas id="chartTopItems"></canvas></div></div>
</div>
<div id="panelViolations" class="panel">
<div class="page-header"><h2>⚠️ Отслеживание несоответствий</h2><p>Контроль исполнения корректирующих мер</p></div>
<div class="stats-grid"><div class="stat-card"><div class="stat-label">Всего несоответствий</div><div class="stat-value" id="vTotal">0</div></div><div class="stat-card green"><div class="stat-label">Устранено</div><div class="stat-value" id="vDone">0</div></div><div class="stat-card red"><div class="stat-label">Просрочено</div><div class="stat-value" id="vOverdue">0</div></div><div class="stat-card orange"><div class="stat-label">В работе</div><div class="stat-value" id="vPending">0</div></div></div>
<div class="filter-bar"><select id="vfStatus"><option value="all">Все</option><option value="done">Устранено</option><option value="overdue">Просрочено</option><option value="pending">В работе</option></select><select id="vfType"><option value="all">Все виды</option><option>Нарушение</option><option>Замечание</option><option>Риск</option></select></div>
<div class="table-wrap"><table class="data-table"><thead><tr><th></th><th>Несоответствие</th><th>Аудит (дата)</th><th>Исполнитель</th><th>Вид</th><th>Меры</th><th>Ответственный</th><th>Срок</th><th>Завершение</th><th>Статус</th></tr></thead><tbody id="violationsBody"></tbody></table></div>
<div class="no-data" id="vioNoData" style="display:none"><span class="icon"></span><p>Несоответствий не найдено</p></div>
</div>
<div id="panelHistory" class="panel">
<div class="page-header"><h2>📁 История аудитов</h2><p>Архив всех проведённых ПАБ</p></div>
<div class="filter-bar"><select id="filterOverall"><option value="all">Все</option><option value="safe">Безопасно</option><option value="danger">С нарушениями</option></select><input type="date" id="filterDate"><input type="text" id="filterLocation" placeholder="Поиск по месту..."><button class="btn btn-outline btn-sm" id="exportCsvBtn">📥 Экспорт CSV</button></div>
<div class="table-wrap"><table class="data-table"><thead><tr><th>Бланк №</th><th>Дата</th><th>Время</th><th>Место</th><th>Наблюдатель</th><th>Статус</th><th>Нарушений</th><th></th></tr></thead><tbody id="historyBody"></tbody></table></div>
<div class="no-data" id="noDataRow" style="display:none"><span class="icon">📭</span><p>Нет записей</p></div>
</div>
</div></div>
<script>
"use strict";
// ===== DATA =====
var PREDEFINED = {admin:{pass:"admin",name:"Администратор",role:"Руководитель",email:"admin@telecom.kz",branch:"АО «Казахтелеком»",dept:"ЦА",region:"Центральный",oblast:"—",city:"г. Астана"}};
var PAB_QUOTA={"Директор департамента ЦА":{c:1,p:"halfyear",l:"1 раз в полгода"},"Директор департамента филиала":{c:1,p:"halfyear",l:"1 раз в полгода"},"Региональный директор филиала":{c:1,p:"quarter",l:"1 раз в квартал"},"Директор ДЭСД":{c:1,p:"quarter",l:"1 раз в квартал"},"Начальник ТУСМ":{c:1,p:"quarter",l:"1 раз в квартал"},"Руководитель структурного подразделения":{c:1,p:"quarter",l:"1 раз в квартал"},"Начальник центра/службы/цеха":{c:1,p:"month",l:"1 раз в месяц"},"Начальник участка":{c:2,p:"month",l:"2 раза в месяц"},"Инженер БиОТ":{c:1,p:"month",l:"1 раз в месяц"},"Работник отдела БиОТ":{c:1,p:"month",l:"1 раз в месяц"},"Аудитор":{c:1,p:"month",l:"1 раз в месяц"},"Бригадир":{c:1,p:"month",l:"1 раз в месяц"},"Руководитель":{c:1,p:"quarter",l:"1 раз в квартал"},"Работник":{c:1,p:"quarter",l:"1 раз в квартал"},"Сотрудник":{c:0,p:null,l:"Без графика"}};
var CATS=[{id:"reaction",title:"1. Реакция работника",items:["Приводит в порядок СИЗ","Меняет положение","Перестраивает работу","Прекращает работу","Наклоняется, прячется","Меняет инструмент","Подсоединяет или устанавливает необходимые защитные устройства","Другое"]},{id:"posture",title:"2. Положение/поза работника",items:["Столкновения и удары","Защемление предметом","Падение","Повторяющиеся движения","Статичные позы","Другое"]},{id:"ppe",title:"3. Отсутствие СИЗ",items:["Голова (каски, подшлемник и т.д.)","Уши (беруши, наушники)","Глаза и лицо (щитки, очки, маски и т.д.)","Органы дыхания (противогазы, респираторы, маски и т.п.)","Руки (перчатки, рукавицы и т.д.)","Тела (спецодежда, фартук, страховочный пояс)","Ноги (спец обувь)","Другое"]},{id:"tools",title:"4. Инструменты и оборудование",items:["Используется самодельный инструмент","Инструменты в ненадлежащем состоянии","Инструменты используются не по назначению","Оборудование находится в ненадлежащем состоянии","Лестницы и стремянки отсутствуют, используются неправильно или находятся в ненадлежащем состоянии","Ограждения отсутствуют, используются неправильно или находятся в ненадлежащем состоянии","Переносное освещение находится в ненадлежащем состоянии","Другое"]},{id:"rules",title:"5. Инструкции и правила",items:["Отсутствие наряда","Инструкции не соответствуют выполняемым работам","Требования инструкций и/или правил безопасности не соблюдаются","Инструктажи не проведены","В недостаточной степени прописаны и выполнены технические мероприятия","В недостаточной степени выполнены подготовка рабочего места и допуск","В недостаточной степени заполнен наряд","Отсутствие удостоверения у работника","Неприменение СИЗ при их наличии во время аудита","Другое"]},{id:"conditions",title:"6. Условия на рабочем месте",items:["Шум","Освещенность","Пыль","Задымленность","Беспорядок на рабочем месте","Загромождение путей прохода","Нерациональное размещение инструментов, приборов, оборудования","Повышенная температура/Пониженная температура","Другое"]},{id:"transport",title:"7. Транспорт",items:["Ремни безопасности отсутствуют, неисправны или не используются","Опасный стиль вождения","Состояние водителя не соответствует требованиям","Использование мобильного средства связи во время движения","Несоблюдение правил дорожного движения","Состояние транспортного средства не соответствует требованиям безопасности","Другое"]}];
var currentUser=null,currentPanel="newAudit",editId=null,charts={},lastSubmitted=null,vioRowCount=6;
// ===== HELPERS =====
function isAdmin(){return currentUser&&currentUser.login==="admin"}
function getUsers(){try{return JSON.parse(localStorage.getItem("pab_users")||"{}")}catch(e){return{}}}
function saveUsers(d){localStorage.setItem("pab_users",JSON.stringify(d))}
function allUsers(){var r=getUsers();for(var k in PREDEFINED)r[k]=PREDEFINED[k];return r}
function getAudits(){try{return JSON.parse(localStorage.getItem("pab_audits")||"[]")}catch(e){return[]}}
function saveAudits(d){localStorage.setItem("pab_audits",JSON.stringify(d))}
function getQuota(r){return PAB_QUOTA[r]||PAB_QUOTA["Сотрудник"]}
function getPeriod(p){var n=new Date();if(p==="month")return{s:new Date(n.getFullYear(),n.getMonth(),1),l:n.toLocaleString("ru",{month:"long",year:"numeric"})};if(p==="quarter"){var q=Math.floor(n.getMonth()/3);return{s:new Date(n.getFullYear(),q*3,1),l:(q+1)+"-й квартал "+n.getFullYear()}}if(p==="halfyear"){var h=n.getMonth()<6?0:1;return{s:new Date(n.getFullYear(),h*6,1),l:(h+1)+"-е полугодие "+n.getFullYear()}}return{s:new Date(2020,0,1),l:"весь период"}}
function esc(s){return(s||"").replace(/&/g,"&amp;").replace(/"/g,"&quot;").replace(/</g,"&lt;").replace(/>/g,"&gt;")}
// ===== LOGIN =====
function switchLoginTab(t){
var login=document.getElementById("formLogin"),reg=document.getElementById("formRegister");
var tl=document.getElementById("tabLogin"),tr=document.getElementById("tabRegister");
if(t==="login"){login.classList.add("active");reg.classList.remove("active");tl.classList.add("active");tr.classList.remove("active")}
else{login.classList.remove("active");reg.classList.add("active");tl.classList.remove("active");tr.classList.add("active")}
document.getElementById("loginError").style.display="none";document.getElementById("regError").style.display="none";document.getElementById("regSuccess").style.display="none";
}
function doLogin(){
var u=document.getElementById("loginUser").value.trim().toLowerCase();
var p=document.getElementById("loginPass").value.trim();
var err=document.getElementById("loginError");
var all=allUsers();
if(!all[u]||all[u].pass!==p){err.style.display="block";return}
err.style.display="none";currentUser={login:u,name:all[u].name,role:all[u].role,email:all[u].email||"",branch:all[u].branch||"",dept:all[u].dept||"",region:all[u].region||"",oblast:all[u].oblast||"",city:all[u].city||""};
localStorage.setItem("pab_current",JSON.stringify(currentUser));
showApp();
}
function doRegister(){
var login=document.getElementById("regLogin").value.trim().toLowerCase();
var pass=document.getElementById("regPass").value.trim();
var name=document.getElementById("regName").value.trim();
var email=document.getElementById("regEmail").value.trim();
var role=document.getElementById("regRole").value;
var branch=document.getElementById("regBranch").value.trim();
var dept=document.getElementById("regDept").value.trim();
var region=document.getElementById("regRegion").value.trim();
var oblast=document.getElementById("regOblast").value.trim();
var city=document.getElementById("regCity").value.trim();
var err=document.getElementById("regError"),ok=document.getElementById("regSuccess");ok.style.display="none";
if(!login||login.length<2){err.textContent="Логин минимум 2 символа";err.style.display="block";return}
if(!pass||pass.length<3){err.textContent="Пароль минимум 3 символа";err.style.display="block";return}
if(!name){err.textContent="Укажите ФИО";err.style.display="block";return}
if(!role){err.textContent="Выберите должность";err.style.display="block";return}
if(!email||email.indexOf("@")<0){err.textContent="Укажите Email";err.style.display="block";return}
if(!branch){err.textContent="Укажите филиал";err.style.display="block";return}
if(!region){err.textContent="Укажите регион";err.style.display="block";return}
if(!city){err.textContent="Укажите город";err.style.display="block";return}
if(!dept){err.textContent="Укажите подразделение";err.style.display="block";return}
var all=allUsers();if(all[login]){err.textContent="Логин занят";err.style.display="block";return}
err.style.display="none";
var users=getUsers();users[login]={pass:pass,name:name,email:email,role:role,branch:branch,dept:dept,region:region,oblast:oblast,city:city};saveUsers(users);
ok.style.display="block";document.getElementById("regLogin").value="";document.getElementById("regPass").value="";document.getElementById("regName").value="";document.getElementById("regEmail").value="";document.getElementById("regBranch").value="";document.getElementById("regDept").value="";document.getElementById("regRegion").value="";document.getElementById("regOblast").value="";document.getElementById("regCity").value="";
document.getElementById("loginUser").value=login;
setTimeout(function(){switchLoginTab("login");ok.style.display="none"},2000);
}
function doLogout(){localStorage.removeItem("pab_current");currentUser=null;document.getElementById("loginScreen").style.display="flex";document.getElementById("appScreen").style.display="none";document.getElementById("scheduleAlert").classList.remove("show","danger");document.getElementById("loginUser").value="";document.getElementById("loginPass").value=""}
function showApp(){document.getElementById("loginScreen").style.display="none";document.getElementById("appScreen").style.display="block";document.getElementById("displayName").textContent=currentUser.login+" ("+currentUser.role+")";document.getElementById("pabObserver").value=currentUser.name;document.getElementById("pabEmail").value=currentUser.email||"";document.getElementById("pabObserverRole").value=currentUser.role||"";document.getElementById("pabRegion").value=currentUser.region||"";switchPanel("newAudit");checkScheduleAlert()}
// ===== SCHEDULE =====
function checkScheduleAlert(){if(!currentUser||isAdmin()){document.getElementById("scheduleAlert").classList.remove("show","danger");return}var q=getQuota(currentUser.role);if(!q.p)return;var p=getPeriod(q.p);var audits=getAudits().filter(function(a){return a.createdBy===currentUser.login&&new Date(a.date)>=p.s});var done=audits.length,need=Math.max(0,q.c-done);var alert=document.getElementById("scheduleAlert"),text=document.getElementById("scheduleAlertText");alert.classList.remove("show","danger");if(need>0){text.textContent="⚠️ Отставание от графика ("+p.l+"): "+done+" из "+q.c+" ("+q.l+"). Осталось: "+need;alert.classList.add("show");if(need>=q.c)alert.classList.add("danger")}}
function sendScheduleReminder(){if(!currentUser)return;var q=getQuota(currentUser.role);var p=getPeriod(q.p);var audits=getAudits().filter(function(a){return a.createdBy===currentUser.login&&new Date(a.date)>=p.s});var done=audits.length,need=Math.max(0,q.c-done);var to=currentUser.email||"";if(!to||to.indexOf("@")<0){alert("Не указан email");return}var subj="Напоминание: график ПАБ — "+p.l;var body="Уважаемый(ая) "+currentUser.name+"!\n\nПо графику ПАБ необходимо провести "+(need>0?need:0)+" наблюдений до конца периода: "+p.l+".\nНорматив: "+q.l+".\nВыполнено: "+done+" из "+q.c+".\n"+(need>0?"Вы отстаёте на "+need+" ПАБ.":"График выполнен!")+"\n\nСистема ПАБ.";window.location.href="mailto:"+encodeURIComponent(to)+"?subject="+encodeURIComponent(subj)+"&body="+encodeURIComponent(body)}
// ===== CATEGORY UI =====
function buildCategorySections(){var c=document.getElementById("categorySections");var h="";CATS.forEach(function(cat){var col=(cat.items.length>7?"col3":(cat.items.length<=4?"col1":""));h+="<div class=\"cat-section\"><div class=\"cat-header\" data-cat=\""+cat.id+"\"><span class=\"cat-title\">"+cat.title+"</span><span class=\"cat-badge all-safe\" id=\"badge-"+cat.id+"\">ВСЕ БЕЗОПАСНО</span><span class=\"cat-arrow\">▼</span></div><div class=\"cat-body open\" id=\"body-"+cat.id+"\"><div class=\"all-safe-toggle active\" id=\"as-"+cat.id+"\"><input type=\"checkbox\" checked id=\"ascb-"+cat.id+"\"> ВСЕ БЕЗОПАСНО</div><div class=\"checklist "+col+"\">"+cat.items.map(function(item,i){return"<div class=\"check-item\" id=\"item-"+cat.id+"-"+i+"\"><input type=\"checkbox\" id=\"cb-"+cat.id+"-"+i+"\"><div><label for=\"cb-"+cat.id+"-"+i+"\">"+item+"</label>"+(item==="Другое"?"<input class=\"other-input\" id=\"other-"+cat.id+"\" placeholder=\"Укажите...\">":"")+"</div></div>"}).join("")+"</div></div><div class=\"cat-footer\"><span>Итого: <span class=\"total-count zero\" id=\"total-"+cat.id+"\">0</span></span></div></div>"});c.innerHTML=h}
function toggleCat(id){document.getElementById("body-"+id).classList.toggle("open");var h=document.querySelector("#cat-"+id+" .cat-header");if(h)h.classList.toggle("open")}
function toggleAllSafe(id){var cb=document.getElementById("ascb-"+id),tog=document.getElementById("as-"+id);var is=!cb.checked;cb.checked=is;if(is){tog.classList.add("active");var cat=CATS.find(function(c){return c.id===id});if(cat)cat.items.forEach(function(_,i){var e=document.getElementById("cb-"+id+"-"+i);if(e){e.checked=false;e.parentElement.classList.remove("checked")}var o=document.getElementById("other-"+id);if(o){o.style.display="none";o.classList.remove("visible")}})}else{tog.classList.remove("active")}updateCatTotal(id)}
function onCheckItem(id,idx){var cb=document.getElementById("cb-"+id+"-"+idx);if(!cb)return;if(cb.checked){cb.parentElement.classList.add("checked");document.getElementById("ascb-"+id).checked=false;document.getElementById("as-"+id).classList.remove("active")}else{cb.parentElement.classList.remove("checked")}var cat=CATS.find(function(c){return c.id===id});if(cat&&cat.items[idx]==="Другое"){var o=document.getElementById("other-"+id);if(o){if(cb.checked){o.style.display="block";o.classList.add("visible")}else{o.style.display="none";o.classList.remove("visible")}}}updateCatTotal(id)}
function updateCatTotal(id){var cat=CATS.find(function(c){return c.id===id});if(!cat)return;var cnt=0;cat.items.forEach(function(_,i){if(document.getElementById("cb-"+id+"-"+i)&&document.getElementById("cb-"+id+"-"+i).checked)cnt++});var te=document.getElementById("total-"+id);if(te){te.textContent=cnt;if(cnt===0)te.classList.add("zero");else te.classList.remove("zero")}var b=document.getElementById("badge-"+id);if(b){if(cnt===0){b.textContent="ВСЕ БЕЗОПАСНО";b.classList.add("all-safe")}else{b.textContent="Нарушений: "+cnt;b.classList.remove("all-safe")}}}
function setOverall(t){document.getElementById("overallSafe").classList.toggle("selected",t==="safe");document.getElementById("overallDanger").classList.toggle("selected",t==="danger")}
// ===== VIO ROWS =====
function initVioRows(){var h="";for(var i=0;i<vioRowCount;i++)h+=makeVioRow(i+1);document.getElementById("vioRows").innerHTML=h}
function makeVioRow(num,data){var d=data||{};return"<div class=\"vio-grid\" id=\"vioRow"+num+"\" style=\"display:grid\"><span class=\"vio-row-num\">"+num+"</span><input class=\"v-nc\" placeholder=\"Описание\" value=\""+esc(d.nc||"")+"\"><input class=\"v-exec\" placeholder=\"Исполнитель\" value=\""+esc(d.executor||"")+"\"><select class=\"v-type\"><option"+(d.type==="Нарушение"?" selected":"")+">Нарушение</option><option"+(d.type==="Замечание"?" selected":"")+">Замечание</option><option"+(d.type==="Риск"?" selected":"")+">Риск</option></select><input class=\"v-measure\" placeholder=\"Меры\" value=\""+esc(d.measure||"")+"\"><input class=\"v-resp\" placeholder=\"Ответственный\" value=\""+esc(d.responsible||"")+"\"><input type=\"date\" class=\"v-date\" value=\""+esc(d.date||"")+"\"><input class=\"v-done\" placeholder=\"Завершение\" value=\""+esc(d.done||"")+"\"><button class=\"remove-vio-btn\" data-row=\""+num+"\" title=\"Удалить\">×</button></div>"}
function addVioRow(){vioRowCount++;var d=document.getElementById("vioRows");d.insertAdjacentHTML("beforeend",makeVioRow(vioRowCount));d.lastElementChild.querySelector(".remove-vio-btn").addEventListener("click",function(){removeVioRow(this.getAttribute("data-row"))})}
function removeVioRow(num){if(vioRowCount<=1)return;var r=document.getElementById("vioRow"+num);if(r)r.remove();var rows=document.querySelectorAll("#vioRows .vio-grid");vioRowCount=rows.length;rows.forEach(function(row,i){row.id="vioRow"+(i+1);row.querySelector(".vio-row-num").textContent=i+1;var btn=row.querySelector(".remove-vio-btn");if(btn)btn.setAttribute("data-row",(i+1))})}
function getVioRows(){var rows=[];document.querySelectorAll("#vioRows .vio-grid").forEach(function(r){var nc=r.querySelector(".v-nc").value.trim();if(!nc)return;rows.push({nc:nc,executor:r.querySelector(".v-exec").value.trim()||"",type:r.querySelector(".v-type").value||"Нарушение",measure:r.querySelector(".v-measure").value.trim()||"",responsible:r.querySelector(".v-resp").value.trim()||"",date:r.querySelector(".v-date").value||"",done:r.querySelector(".v-done").value.trim()||""})});return rows}
// ===== DIALOGUE =====
function getDialogue(){var items=[];var labels=["Работник привёл примеры безопасных действий","Были обсуждены риски / проблемы","Определены корректирующие меры","Предложения работника зафиксированы","Другое"];labels.forEach(function(l,i){if(document.getElementById("dcb-"+i)&&document.getElementById("dcb-"+i).checked){items.push({item:l,other:l==="Другое"?(document.getElementById("dialogueOther").value.trim()||""):null})}});return items}
function setDialogue(items){for(var i=0;i<5;i++){var cb=document.getElementById("dcb-"+i);if(cb)cb.checked=false}var o=document.getElementById("dialogueOther");if(o){o.value="";o.style.display="none";o.classList.remove("visible")}items.forEach(function(di){var idx=["Работник привёл примеры безопасных действий","Были обсуждены риски / проблемы","Определены корректирующие меры","Предложения работника зафиксированы","Другое"].indexOf(di.item);if(idx>=0){var cb=document.getElementById("dcb-"+idx);if(cb){cb.checked=true;if(di.item==="Другое"&&di.other){var oo=document.getElementById("dialogueOther");oo.value=di.other;oo.style.display="block";oo.classList.add("visible")}}}})}
// ===== AUDIT SUBMIT =====
function submitAudit(){
if(editId&&!isAdmin()){alert("Только администратор может редактировать");return}
var loc=document.getElementById("pabLocation").value.trim();if(!loc){alert("Укажите место проведения");return}
var cats={};var tv=0;CATS.forEach(function(cat){var ch=[];cat.items.forEach(function(item,i){var cb=document.getElementById("cb-"+cat.id+"-"+i);if(cb&&cb.checked){var ov=item==="Другое"?(document.getElementById("other-"+cat.id).value.trim()||""):null;ch.push({item:item,other:ov})}});cats[cat.id]={items:ch,allSafe:ch.length===0};tv+=ch.length});
var overallSafe=document.getElementById("overallSafe").classList.contains("selected");
var entry={id:editId||Date.now(),number:document.getElementById("pabNumber").value.trim(),date:document.getElementById("pabDate").value,timeStart:document.getElementById("pabTimeStart").value,timeEnd:document.getElementById("pabTimeEnd").value,location:loc,region:document.getElementById("pabRegion").value,workType:document.getElementById("pabWorkType").value.trim(),workerCount:parseInt(document.getElementById("pabWorkerCount").value)||1,observer:document.getElementById("pabObserver").value.trim()||currentUser.name,observerRole:document.getElementById("pabObserverRole").value.trim(),supervisor:document.getElementById("pabSupervisor").value.trim(),supervisorRole:document.getElementById("pabSupervisorRole").value.trim(),email:document.getElementById("pabEmail").value.trim()||currentUser.email||"",overallSafe:overallSafe,categories:cats,totalViolations:tv,violations:getVioRows(),dialogue:getDialogue(),createdBy:currentUser.login,createdAt:new Date().toISOString()};
var audits=getAudits();if(editId){audits=audits.map(function(a){return a.id===editId?entry:a});editId=null}else{audits.unshift(entry)}
saveAudits(audits);resetAuditForm();lastSubmitted=entry;showSuccess(entry);if(!isAdmin())checkScheduleAlert();
}
function resetAuditForm(){
document.getElementById("pabNumber").value="";document.getElementById("pabDate").value=new Date().toISOString().split("T")[0];document.getElementById("pabTimeStart").value="";document.getElementById("pabTimeEnd").value="";document.getElementById("pabLocation").value="";document.getElementById("pabRegion").value=currentUser?currentUser.region||"":"";document.getElementById("pabWorkType").value="";document.getElementById("pabWorkerCount").value="1";document.getElementById("pabObserver").value=currentUser?currentUser.name:"";document.getElementById("pabObserverRole").value="";document.getElementById("pabSupervisor").value="";document.getElementById("pabSupervisorRole").value="";document.getElementById("pabEmail").value=currentUser?currentUser.email||"":"";setOverall("safe");editId=null;
CATS.forEach(function(cat){document.getElementById("ascb-"+cat.id).checked=true;document.getElementById("as-"+cat.id).classList.add("active");cat.items.forEach(function(_,i){var cb=document.getElementById("cb-"+cat.id+"-"+i);if(cb){cb.checked=false;cb.parentElement.classList.remove("checked")}var o=document.getElementById("other-"+cat.id);if(o){o.value="";o.style.display="none";o.classList.remove("visible")}});updateCatTotal(cat.id)});
document.getElementById("vioRows").innerHTML="";vioRowCount=6;initVioRows();document.getElementById("formSuccess").style.display="none";
for(var i=0;i<5;i++){var cb=document.getElementById("dcb-"+i);if(cb)cb.checked=false}var o=document.getElementById("dialogueOther");if(o){o.value="";o.style.display="none";o.classList.remove("visible")}
}
function showSuccess(entry){
var cv=CATS.filter(function(cat){var c=entry.categories&&entry.categories[cat.id];return c&&c.items.length>0}).map(function(c){return c.title}).join(", ")||"все категории безопасны";
document.getElementById("successDetail").innerHTML="<b>Бланк №"+(entry.number||"—")+"</b> | "+entry.date+" | "+(entry.timeStart||"—")+"—"+(entry.timeEnd||"—")+"<br>Место: "+entry.location+" | Статус: <b>"+(entry.overallSafe?"ВСЕ БЕЗОПАСНО":"ВЫЯВЛЕНО "+entry.totalViolations+" НАРУШЕНИЙ")+"</b><br>Категории с нарушениями: "+cv;document.getElementById("formSuccess").style.display="block";setTimeout(function(){document.getElementById("formSuccess").style.display="none"},30000)
}
function sendEmailConfirm(){
if(!lastSubmitted)return;var e=lastSubmitted;var to=e.email||currentUser.email||"";if(!to||to.indexOf("@")<0){alert("Не указан email");return}var subj="ПАБ №"+(e.number||"—")+" от "+e.date;var body="ПОДТВЕРЖДЕНИЕ ПАБ\nБланк №: "+(e.number||"—")+"\nДата: "+e.date+"\nМесто: "+e.location+"\nНаблюдатель: "+e.observer+"\nСтатус: "+(e.overallSafe?"БЕЗОПАСНО":"НАРУШЕНИЙ: "+e.totalViolations)+"\n\nСистема ПАБ.";window.location.href="mailto:"+encodeURIComponent(to)+"?subject="+encodeURIComponent(subj)+"&body="+encodeURIComponent(body)
}
function printConfirm(){if(!lastSubmitted)return;var e=lastSubmitted;var w=window.open("","_blank","width=700,height=800");w.document.write("<!DOCTYPE html><html><head><meta charset=\"utf-8\"><title>ПАБ №"+(e.number||"—")+"</title><style>body{font:14px/1.5 Arial;max-width:700px;margin:40px auto;padding:20px}h1{font-size:20px}h2{font-size:16px;border-bottom:1px solid #ccc}table{width:100%;border-collapse:collapse;margin:12px 0}td{padding:6px 8px;border:1px solid #ddd}@media print{body{margin:0;padding:10px}button{display:none}}</style></head><body><button onclick=\"window.print()\" style=\"padding:8px 20px;font-size:16px;margin-bottom:20px\">🖨️ Печать</button><h1>БЛАНК ПАБ №"+(e.number||"—")+" от "+e.date+"</h1><p><b>Место:</b> "+e.location+" | <b>Наблюдатель:</b> "+e.observer+" | <b>Статус:</b> "+(e.overallSafe?"БЕЗОПАСНО":"НАРУШЕНИЙ: "+e.totalViolations)+"</p></body></html>");w.document.close()}
// ===== MY SCHEDULE =====
function renderMySchedule(){
if(!currentUser)return;document.getElementById("scheduleAlert").classList.remove("show","danger");var q=getQuota(currentUser.role);var c=document.getElementById("myScheduleContent");if(q.c===0){c.innerHTML="<div class=\"progress-card\"><h3>📅 Без обязательного графика</h3><p style=\"color:var(--gray-500);font-size:13px\">Для должности «"+currentUser.role+"» график не установлен.</p></div>";return}
var p=getPeriod(q.p);var audits=getAudits().filter(function(a){return a.createdBy===currentUser.login&&new Date(a.date)>=p.s});var done=audits.length;var pct=Math.min(100,Math.round(done/q.c*100));var cls="good";if(pct<50)cls="bad";else if(pct<100)cls="warn";var need=Math.max(0,q.c-done);
var rh="";if(audits.length>0){rh="<div style=\"margin-top:16px\"><h4 style=\"font-size:13px;margin-bottom:8px\">📋 Проведённые в этом периоде:</h4>"+audits.slice(0,5).map(function(a){return"<div style=\"background:var(--gray-100);padding:8px 12px;border-radius:var(--radius);margin-bottom:6px;font-size:13px\">"+a.date+" — "+a.location+" — <span class=\"badge "+(a.overallSafe?"badge-safe":"badge-danger")+"\">"+(a.overallSafe?"Безопасно":"Нарушений: "+a.totalViolations)+"</span></div>"}).join("")+"</div>"}
c.innerHTML="<div class=\"progress-card\"><h3>📅 "+p.l+"</h3><div class=\"quota-info\">Норматив: <b>"+q.l+"</b> | Должность: "+currentUser.role+"</div><div class=\"progress-bar\"><div class=\"progress-fill "+cls+"\" style=\"width:"+pct+"%\"></div></div><div class=\"progress-stats\">Выполнено: <b>"+done+"</b> из <b>"+q.c+"</b> ("+pct+"%) "+(need>0?"— осталось: <span style=\"color:var(--red)\">"+need+"</span>":"— ✅ график выполнен!")+"</div>"+(need>0?"<div style=\"margin-top:10px\"><button class=\"btn btn-primary btn-sm\" id=\"remindBtn2\">✉️ Напомнить себе</button></div>":"")+"</div><div class=\"progress-card\"><h3>👤 Мои данные</h3><div style=\"font-size:13px;display:grid;grid-template-columns:1fr 1fr;gap:6px\"><div><b>ФИО:</b> "+currentUser.name+"</div><div><b>Должность:</b> "+currentUser.role+"</div><div><b>Филиал:</b> "+(currentUser.branch||"—")+"</div><div><b>Подразделение:</b> "+(currentUser.dept||"—")+"</div><div><b>Регион:</b> "+(currentUser.region||"—")+"</div><div><b>Область:</b> "+(currentUser.oblast||"—")+"</div><div><b>Город:</b> "+(currentUser.city||"—")+"</div><div><b>Email:</b> "+(currentUser.email||"—")+"</div></div></div>"+rh;
}
// ===== NAVIGATION =====
function switchPanel(name){
currentPanel=name;document.querySelectorAll(".panel").forEach(function(p){p.classList.remove("active")});var panel=document.getElementById(name==="newAudit"?"panelNewAudit":name==="mySchedule"?"panelMySchedule":name==="dashboard"?"panelDashboard":name==="violations"?"panelViolations":"panelHistory");if(panel)panel.classList.add("active");document.querySelectorAll("nav a").forEach(function(a){a.classList.toggle("active",a.getAttribute("data-panel")===name)});
if(name==="dashboard")renderDashboard();if(name==="history")renderHistory();if(name==="mySchedule")renderMySchedule();if(name==="violations")renderViolations();
}
// ===== DASHBOARD =====
function getFilteredAudits(){
var all=allUsers();var audits=getAudits();var fr=document.getElementById("dfRegion").value,fo=document.getElementById("dfOblast").value,fb=document.getElementById("dfBranch").value,fd=document.getElementById("dfDept").value,fc=document.getElementById("dfCity").value;var df=document.getElementById("dfDateFrom").value,dt=document.getElementById("dfDateTo").value;
audits=audits.filter(function(a){var u=all[a.createdBy];if(!u)return true;if(fr!=="all"&&u.region!==fr)return false;if(fo!=="all"&&u.oblast!==fo)return false;if(fb!=="all"&&u.branch!==fb)return false;if(fd!=="all"&&u.dept!==fd)return false;if(fc!=="all"&&u.city!==fc)return false;return true});
if(df)audits=audits.filter(function(a){return a.date>=df});if(dt)audits=audits.filter(function(a){return a.date<=dt});return audits;
}
function renderDashboard(){
document.getElementById("adminBar").style.display=isAdmin()?"flex":"none";var all=allUsers();var userList=[];for(var k in all)userList.push({login:k,name:all[k].name,role:all[k].role,region:all[k].region||"",oblast:all[k].oblast||"",branch:all[k].branch||"",dept:all[k].dept||"",city:all[k].city||""});
function fillSelect(id,vals){var s=document.getElementById(id);if(!s)return;var v=s.value;s.innerHTML="<option value=\"all\">Все</option>"+vals.sort().map(function(x){return"<option>"+x+"</option>"}).join("");s.value=v||"all"}
var regions=[],oblasts=[],branches=[],depts=[],cities=[];userList.forEach(function(u){if(u.region)regions.push(u.region);if(u.oblast)oblasts.push(u.oblast);if(u.branch)branches.push(u.branch);if(u.dept)depts.push(u.dept);if(u.city)cities.push(u.city)});
regions=[...new Set(regions)];oblasts=[...new Set(oblasts)];branches=[...new Set(branches)];depts=[...new Set(depts)];cities=[...new Set(cities)];
fillSelect("dfRegion",regions);fillSelect("dfOblast",oblasts);fillSelect("dfBranch",branches);fillSelect("dfDept",depts);fillSelect("dfCity",cities);
var audits=getFilteredAudits();var total=audits.length;var allSafe=audits.filter(function(a){return a.overallSafe}).length;var withDanger=audits.filter(function(a){return !a.overallSafe}).length;var totalVio=audits.reduce(function(s,a){return s+(a.totalViolations||0)},0);
var onTrack=0,behind=0;userList.forEach(function(u){if(u.login==="admin")return;var q=getQuota(u.role);if(!q.p)return;var p=getPeriod(q.p);var ua=getAudits().filter(function(a){return a.createdBy===u.login&&new Date(a.date)>=p.s});if(ua.length>=q.c)onTrack++;else behind++});
document.getElementById("statTotal").textContent=total;document.getElementById("statAllSafe").textContent=allSafe;document.getElementById("statWithDanger").textContent=withDanger;document.getElementById("statViolations").textContent=totalVio;document.getElementById("statOnTrack").textContent=onTrack;document.getElementById("statBehind").textContent=behind;
var sp=total>0?(allSafe/total*100):50,dp=total>0?(withDanger/total*100):50;document.getElementById("riskSafeBar").style.width=sp+"%";document.getElementById("riskUnsafeBar").style.width=dp+"%";document.getElementById("riskSafeLabel").textContent=allSafe;document.getElementById("riskUnsafeLabel").textContent=withDanger;
for(var ck in charts){try{charts[ck].destroy()}catch(e){}}charts={};
var ctx1=document.getElementById("chartCategories").getContext("2d");charts.cat=new Chart(ctx1,{type:"bar",data:{labels:CATS.map(function(c){return c.title.split(". ")[1]}),datasets:[{label:"Нарушений",data:CATS.map(function(cat){return audits.reduce(function(s,a){var c=a.categories&&a.categories[cat.id];return s+(c?c.items.length:0)},0)}),backgroundColor:"#E63946",borderRadius:6}]},options:{responsive:true,plugins:{legend:{display:false}},scales:{y:{beginAtZero:true,ticks:{stepSize:1}}}}});
var dates={};audits.forEach(function(a){if(!dates[a.date])dates[a.date]=0;dates[a.date]+=(a.totalViolations||0)});var sd=Object.keys(dates).sort();
var ctx2=document.getElementById("chartTimeline").getContext("2d");charts.tl=new Chart(ctx2,{type:"line",data:{labels:sd,datasets:[{label:"Нарушений",data:sd.map(function(d){return dates[d]}),borderColor:"#E63946",backgroundColor:"rgba(230,57,70,0.08)",fill:true,tension:0.3,pointRadius:5}]},options:{responsive:true,plugins:{legend:{display:false}},scales:{y:{beginAtZero:true,ticks:{stepSize:1}}}}});
var rc={};audits.forEach(function(a){var u=all[a.createdBy];var r=u?u.region||"Не указан":"Не указан";rc[r]=(rc[r]||0)+1});var rs=Object.entries(rc).sort(function(a,b){return b[1]-a[1]});
var ctx3=document.getElementById("chartRegions").getContext("2d");charts.reg=new Chart(ctx3,{type:"bar",data:{labels:rs.map(function(r){return r[0]}),datasets:[{label:"Аудитов",data:rs.map(function(r){return r[1]}),backgroundColor:["#00B4D8","#48CAE4","#90E0EF","#0077B6","#023E8A","#E63946","#E76F51","#2D6A4F"],borderRadius:6}]},options:{responsive:true,plugins:{legend:{display:false}},scales:{y:{beginAtZero:true,ticks:{stepSize:1}}}}});
var bc={};audits.forEach(function(a){var u=all[a.createdBy];var b=u?u.branch||"Не указан":"Не указан";bc[b]=(bc[b]||0)+1});var bs=Object.entries(bc).sort(function(a,b){return b[1]-a[1]}).slice(0,10);
var ctx4=document.getElementById("chartBranches").getContext("2d");charts.br=new Chart(ctx4,{type:"bar",data:{labels:bs.map(function(r){return r[0].length>25?r[0].slice(0,25)+"...":r[0]}),datasets:[{label:"Аудитов",data:bs.map(function(r){return r[1]}),backgroundColor:"#00B4D8",borderRadius:6}]},options:{indexAxis:"y",responsive:true,plugins:{legend:{display:false}},scales:{x:{beginAtZero:true,ticks:{stepSize:1}}}}});
var roleS={};userList.forEach(function(u){if(u.login==="admin")return;var q=getQuota(u.role);if(!q.p)return;var p=getPeriod(q.p);var ua=getAudits().filter(function(a){return a.createdBy===u.login&&new Date(a.date)>=p.s});if(!roleS[u.role])roleS[u.role]={total:0,done:0};roleS[u.role].total++;if(ua.length>=q.c)roleS[u.role].done++});var rl=Object.keys(roleS);
var ctx5=document.getElementById("chartRoles").getContext("2d");charts.roles=new Chart(ctx5,{type:"bar",data:{labels:rl,datasets:[{label:"Выполняют",data:rl.map(function(r){return roleS[r].done}),backgroundColor:"#2D6A4F",borderRadius:6},{label:"Отстают",data:rl.map(function(r){return roleS[r].total-roleS[r].done}),backgroundColor:"#E63946",borderRadius:6}]},options:{responsive:true,plugins:{legend:{position:"bottom"}},scales:{x:{stacked:true},y:{stacked:true,beginAtZero:true,ticks:{stepSize:1}}}}});
var ic={};audits.forEach(function(a){if(a.categories){Object.values(a.categories).forEach(function(cat){if(cat.items){cat.items.forEach(function(it){ic[it.item]=(ic[it.item]||0)+1})})}});var ti=Object.entries(ic).sort(function(a,b){return b[1]-a[1]}).slice(0,10);
var ctx6=document.getElementById("chartTopItems").getContext("2d");charts.top=new Chart(ctx6,{type:"bar",data:{labels:ti.map(function(i){return i[0].length>30?i[0].slice(0,30)+"...":i[0]}),datasets:[{label:"Раз",data:ti.map(function(i){return i[1]}),backgroundColor:["#E63946","#E76F51","#F4A261","#E9C46A","#2A9D8F","#264653","#00B4D8","#0077B6","#023E8A","#6C757D"],borderRadius:4}]},options:{indexAxis:"y",responsive:true,plugins:{legend:{display:false}},scales:{x:{beginAtZero:true,ticks:{stepSize:1}}}}});
}
// ===== VIOLATIONS =====
function renderViolations(){
var all=allUsers();var audits=getFilteredAudits();var today=new Date().toISOString().split("T")[0];var fStatus=document.getElementById("vfStatus").value,fType=document.getElementById("vfType").value;
var allVios=[];audits.forEach(function(a){if(!a.violations)return;var u=all[a.createdBy]||{};a.violations.forEach(function(v,i){var dd=v.date||"",done=v.done&&v.done.trim();var st="pending";if(done)st="done";else if(dd&&dd<today)st="overdue";allVios.push({nc:v.nc,executor:v.executor,type:v.type,measure:v.measure,responsible:v.responsible,date:dd,done:v.done||"",status:st,auditDate:a.date,auditNumber:a.number||"—"})})});
document.getElementById("vTotal").textContent=allVios.length;document.getElementById("vDone").textContent=allVios.filter(function(v){return v.status==="done"}).length;document.getElementById("vOverdue").textContent=allVios.filter(function(v){return v.status==="overdue"}).length;document.getElementById("vPending").textContent=allVios.filter(function(v){return v.status==="pending"}).length;
if(fStatus!=="all")allVios=allVios.filter(function(v){return v.status===fStatus});if(fType!=="all")allVios=allVios.filter(function(v){return v.type===fType});
var tbody=document.getElementById("violationsBody"),nd=document.getElementById("vioNoData");if(allVios.length===0){tbody.innerHTML="";nd.style.display="block";return}nd.style.display="none";
tbody.innerHTML=allVios.map(function(v,i){var sc=v.status==="done"?"badge-safe":v.status==="overdue"?"badge-danger":"badge-warn";var sl=v.status==="done"?"Устранено":v.status==="overdue"?"Просрочено":"В работе";return"<tr><td>"+(i+1)+"</td><td style=\"max-width:180px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap\" title=\""+esc(v.nc)+"\">"+v.nc+"</td><td>"+v.auditDate+" (№"+v.auditNumber+")</td><td>"+v.executor+"</td><td>"+v.type+"</td><td style=\"max-width:140px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap\" title=\""+esc(v.measure||"")+"\">"+(v.measure||"—")+"</td><td>"+v.responsible+"</td><td>"+(v.date||"—")+"</td><td>"+(v.done||"—")+"</td><td><span class=\"badge "+sc+"\">"+sl+"</span></td></tr>"}).join("")
}
// ===== HISTORY =====
function renderHistory(){
var audits=getAudits();var fOverall=document.getElementById("filterOverall").value,fDate=document.getElementById("filterDate").value,fLoc=document.getElementById("filterLocation").value.toLowerCase();if(fOverall==="safe")audits=audits.filter(function(a){return a.overallSafe});if(fOverall==="danger")audits=audits.filter(function(a){return !a.overallSafe});if(fDate)audits=audits.filter(function(a){return a.date===fDate});if(fLoc)audits=audits.filter(function(a){return a.location.toLowerCase().indexOf(fLoc)>=0});
var tbody=document.getElementById("historyBody"),nd=document.getElementById("noDataRow");if(audits.length===0){tbody.innerHTML="";nd.style.display="block";return}nd.style.display="none";
tbody.innerHTML=audits.map(function(a){var ab=isAdmin()?"<a class=\"view-link\" id=\"edit-"+a.id+"\">✏️</a><button class=\"btn btn-danger btn-sm\" id=\"del-"+a.id+"\" style=\"margin-left:6px\">🗑️</button>":"<span style=\"color:var(--gray-500);font-size:11px\">только чтение</span>";return"<tr><td>"+(a.number||"—")+"</td><td>"+a.date+"</td><td>"+(a.timeStart||"—")+" — "+(a.timeEnd||"—")+"</td><td>"+a.location+"</td><td>"+a.observer+"</td><td><span class=\"badge "+(a.overallSafe?"badge-safe":"badge-danger")+"\">"+(a.overallSafe?"Безопасно":"Нарушения")+"</span></td><td>"+(a.totalViolations||0)+"</td><td>"+ab+"</td></tr>"}).join("")
}
function editAudit(id){if(!isAdmin()){alert("Только администратор");return}var a=getAudits().find(function(x){return x.id===id});if(!a)return;editId=id;document.getElementById("pabNumber").value=a.number||"";document.getElementById("pabDate").value=a.date||"";document.getElementById("pabTimeStart").value=a.timeStart||"";document.getElementById("pabTimeEnd").value=a.timeEnd||"";document.getElementById("pabLocation").value=a.location||"";document.getElementById("pabRegion").value=a.region||"";document.getElementById("pabWorkType").value=a.workType||"";document.getElementById("pabWorkerCount").value=a.workerCount||1;document.getElementById("pabObserver").value=a.observer||"";document.getElementById("pabObserverRole").value=a.observerRole||"";document.getElementById("pabSupervisor").value=a.supervisor||"";document.getElementById("pabSupervisorRole").value=a.supervisorRole||"";document.getElementById("pabEmail").value=a.email||"";setOverall(a.overallSafe?"safe":"danger");CATS.forEach(function(cat){var cd=a.categories&&a.categories[cat.id];var it=cd?cd.items:[];it.forEach(function(i){var idx=cat.items.indexOf(i.item);if(idx>=0){var cb=document.getElementById("cb-"+cat.id+"-"+idx);if(cb){cb.checked=true;cb.parentElement.classList.add("checked")}if(i.other){var o=document.getElementById("other-"+cat.id);if(o){o.value=i.other;o.style.display="block";o.classList.add("visible")}}}});var as=it.length===0;document.getElementById("ascb-"+cat.id).checked=as;document.getElementById("as-"+cat.id).classList.toggle("active",as);updateCatTotal(cat.id)});document.getElementById("vioRows").innerHTML="";if(a.violations&&a.violations.length>0){vioRowCount=a.violations.length;document.getElementById("vioRows").innerHTML=a.violations.map(function(v,i){return makeVioRow(i+1,v)}).join("")}else{vioRowCount=1;document.getElementById("vioRows").innerHTML=makeVioRow(1)}setDialogue(a.dialogue||[]);switchPanel("newAudit");window.scrollTo(0,0)}
function deleteAudit(id){if(!isAdmin()){alert("Только администратор");return}if(!confirm("Удалить?"))return;saveAudits(getAudits().filter(function(a){return a.id!==id}));renderHistory()}
// ===== ADMIN =====
function clearAllAudits(){if(!isAdmin())return;if(!confirm("Удалить ВСЕ аудиты?"))return;if(!confirm("Точно?"))return;localStorage.removeItem("pab_audits");alert("Удалено");renderDashboard();renderHistory();renderViolations()}
function clearAuditsByDate(){if(!isAdmin())return;var from=prompt("С даты (ГГГГ-ММ-ДД):","");if(!from)return;var to=prompt("По дату (ГГГГ-ММ-ДД):","");var audits=getAudits();var before=audits.length;audits=audits.filter(function(a){if(a.date<from)return true;if(to&&a.date>to)return true;return false});if(before===audits.length){alert("Нет аудитов за период");return}if(!confirm("Удалить "+(before-audits.length)+" аудитов?"))return;saveAudits(audits);alert("Удалено "+(before-audits.length));renderDashboard();renderHistory();renderViolations()}
function showAllUsers(){if(!isAdmin())return;var all=allUsers();var h="<h2>👥 Пользователи</h2><table style=\"width:100%;border-collapse:collapse;font-size:13px\"><tr style=\"background:#0F1218;color:#fff\"><th>Логин</th><th>ФИО</th><th>Должность</th><th>Филиал</th><th>Подразделение</th><th>Регион</th><th>Область</th><th>Город</th><th>Email</th><th>График</th></tr>";for(var k in all){var u=all[k],q=getQuota(u.role);h+="<tr style=\"border-bottom:1px solid #E2E6EB\"><td>"+k+(k==="admin"?" ⭐":"")+"</td><td>"+u.name+"</td><td>"+u.role+"</td><td>"+(u.branch||"—")+"</td><td>"+(u.dept||"—")+"</td><td>"+(u.region||"—")+"</td><td>"+(u.oblast||"—")+"</td><td>"+(u.city||"—")+"</td><td>"+(u.email||"—")+"</td><td>"+q.l+"</td></tr>"}h+="</table>";var w=window.open("","_blank","width=1000,height=600");w.document.write("<!DOCTYPE html><html><head><meta charset=\"utf-8\"><title>Пользователи</title><style>body{font:14px/1.5 Arial;max-width:1100px;margin:30px auto;padding:20px}table{width:100%;border-collapse:collapse}td{padding:8px 10px}@media print{body{margin:0;padding:10px}}</style></head><body><button onclick=\"window.print()\" style=\"padding:8px 20px;font-size:14px;margin-bottom:16px\">🖨️ Печать</button>"+h+"</body></html>");w.document.close()}
// ===== CSV EXPORTS =====
function exportCSV(){var audits=getAudits();if(audits.length===0){alert("Нет данных");return}var all=allUsers();var h="Бланк №;Дата;Время;Место;Наблюдатель;Должность;Филиал;Регион;Область;Город;Статус;Нарушений";var rows=audits.map(function(a){var u=all[a.createdBy]||{};return(a.number||"")+";"+a.date+";"+(a.timeStart||"")+"-"+(a.timeEnd||"")+";\""+a.location+"\";\""+a.observer+"\";\""+(a.observerRole||"")+"\";\""+(u.branch||"")+"\";\""+(u.region||"")+"\";\""+(u.oblast||"")+"\";\""+(u.city||"")+"\";"+(a.overallSafe?"Безопасно":"Нарушения")+";"+(a.totalViolations||0)});var csv="\uFEFF"+h+"\n"+rows.join("\n");var blob=new Blob([csv],{type:"text/csv;charset=utf-8"});var url=URL.createObjectURL(blob);var dl=document.createElement("a");dl.href=url;dl.download="pab-audit.csv";dl.click();URL.revokeObjectURL(url)}
function downloadFullCSV(){var audits=getFilteredAudits();if(audits.length===0){alert("Нет данных");return}var all=allUsers();var h="Бланк №;Дата;Время;Место;Тип работы;Наблюдатель;Должность;Руководитель;Филиал;Подразделение;Регион;Область;Город;Статус;Нарушений";var rows=audits.map(function(a){var u=all[a.createdBy]||{};var cv=CATS.filter(function(cat){var c=a.categories&&a.categories[cat.id];return c&&c.items.length>0}).map(function(c){return c.title.split(". ")[1]}).join(" | ");return(a.number||"")+";"+a.date+";"+(a.timeStart||"")+"-"+(a.timeEnd||"")+";\""+a.location+"\";\""+(a.workType||"")+"\";\""+a.observer+"\";\""+(a.observerRole||"")+"\";\""+(a.supervisor||"")+"\";\""+(u.branch||"")+"\";\""+(u.dept||"")+"\";\""+(u.region||"")+"\";\""+(u.oblast||"")+"\";\""+(u.city||"")+"\";"+(a.overallSafe?"Безопасно":"Нарушения")+";"+(a.totalViolations||0)});var csv="\uFEFF"+h+"\n"+rows.join("\n");var blob=new Blob([csv],{type:"text/csv;charset=utf-8"});var url=URL.createObjectURL(blob);var dl=document.createElement("a");dl.href=url;dl.download="pab-full-report.csv";dl.click();URL.revokeObjectURL(url)}
function downloadWorkerReport(){var audits=getFilteredAudits();var all=allUsers();var ul=[];for(var k in all)if(k!=="admin")ul.push({login:k,name:all[k].name,role:all[k].role,branch:all[k].branch||"",dept:all[k].dept||"",region:all[k].region||"",oblast:all[k].oblast||"",city:all[k].city||"",email:all[k].email||""});if(ul.length===0){alert("Нет работников");return}var h="Логин;ФИО;Должность;Филиал;Подразделение;Регион;Область;Город;Email;График;Период;Выполнено;Нужно;Статус";var rows=ul.map(function(u){var q=getQuota(u.role);if(!q.p)return u.login+";\""+u.name+"\";\""+u.role+"\";\""+u.branch+"\";\""+u.dept+"\";\""+u.region+"\";\""+u.oblast+"\";\""+u.city+"\";\""+u.email+"\";Без графика;;;0;—";var p=getPeriod(q.p);var done=audits.filter(function(a){return a.createdBy===u.login&&new Date(a.date)>=p.s}).length;var st=done>=q.c?"Выполнен":"Отстаёт ("+(q.c-done)+")";return u.login+";\""+u.name+"\";\""+u.role+"\";\""+u.branch+"\";\""+u.dept+"\";\""+u.region+"\";\""+u.oblast+"\";\""+u.city+"\";\""+u.email+"\";\""+q.l+"\";\""+p.l+"\";"+done+";"+q.c+";\""+st+"\""});var csv="\uFEFF"+h+"\n"+rows.join("\n");var blob=new Blob([csv],{type:"text/csv;charset=utf-8"});var url=URL.createObjectURL(blob);var dl=document.createElement("a");dl.href=url;dl.download="pab-worker-report.csv";dl.click();URL.revokeObjectURL(url)}
function downloadSummaryHTML(){var audits=getFilteredAudits();var all=allUsers();var ul=[];for(var k in all)if(k!=="admin")ul.push({login:k,name:all[k].name,role:all[k].role,branch:all[k].branch||"",dept:all[k].dept||"",region:all[k].region||"",city:all[k].city||""});var total=audits.length,allSafe=audits.filter(function(a){return a.overallSafe}).length,withDanger=audits.filter(function(a){return !a.overallSafe}).length,totalVio=audits.reduce(function(s,a){return s+(a.totalViolations||0)},0);var onTrack=0,behind=0;ul.forEach(function(u){var q=getQuota(u.role);if(!q.p)return;var p=getPeriod(q.p);var done=audits.filter(function(a){return a.createdBy===u.login&&new Date(a.date)>=p.s}).length;if(done>=q.c)onTrack++;else behind++});var wr=ul.map(function(u){var q=getQuota(u.role);if(!q.p)return"<tr><td>"+u.name+"</td><td>"+u.role+"</td><td>"+u.branch+"</td><td>"+u.dept+"</td><td>"+u.region+"</td><td>"+u.city+"</td><td>Без графика</td><td>—</td></tr>";var p=getPeriod(q.p);var done=audits.filter(function(a){return a.createdBy===u.login&&new Date(a.date)>=p.s}).length;var c=done>=q.c?"green":"red";return"<tr><td>"+u.name+"</td><td>"+u.role+"</td><td>"+u.branch+"</td><td>"+u.dept+"</td><td>"+u.region+"</td><td>"+u.city+"</td><td style=\"color:"+c+"\">"+done+"/"+q.c+" ("+q.l+")</td><td>"+p.l+"</td></tr>"}).join("");var h="<!DOCTYPE html><html><head><meta charset=\"utf-8\"><title>Сводный отчёт ПАБ</title><style>body{font:14px/1.5 Arial;max-width:1000px;margin:30px auto;padding:20px;color:#0F1218}h1{font-size:24px;border-bottom:3px solid #00B4D8;padding-bottom:10px}h2{font-size:18px;margin-top:30px;color:#00B4D8}.cards{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:12px;margin:16px 0}.card{background:#F2F4F7;padding:16px;border-radius:10px;text-align:center}.card .val{font-size:28px;font-weight:800}.green{color:#2D6A4F}.red{color:#E63946}table{width:100%;border-collapse:collapse;margin:12px 0;font-size:13px}th{background:#0F1218;color:#fff;padding:10px 12px;text-align:left}td{padding:8px 12px;border-bottom:1px solid #E2E6EB}@media print{body{margin:0;padding:10px}button{display:none}}</style></head><body><button onclick=\"window.print()\" style=\"padding:10px 24px;font-size:15px;margin-bottom:16px\">🖨️ Печать</button><h1>📊 Сводный отчёт ПАБ</h1><p>Сформирован: "+new Date().toLocaleString("ru")+"</p><div class=\"cards\"><div class=\"card\"><div>Всего аудитов</div><div class=\"val\">"+total+"</div></div><div class=\"card\"><div>Безопасно</div><div class=\"val green\">"+allSafe+"</div></div><div class=\"card\"><div>С нарушениями</div><div class=\"val red\">"+withDanger+"</div></div><div class=\"card\"><div>Нарушений</div><div class=\"val red\">"+totalVio+"</div></div><div class=\"card\"><div>Выполняют график</div><div class=\"val green\">"+onTrack+"</div></div><div class=\"card\"><div>Отстают</div><div class=\"val red\">"+behind+"</div></div></div><h2>👥 График по работникам</h2><table><tr><th>ФИО</th><th>Должность</th><th>Филиал</th><th>Подразделение</th><th>Регион</th><th>Город</th><th>Прогресс</th><th>Период</th></tr>"+wr+"</table></body></html>";var w=window.open("","_blank","width=1000,height=800");w.document.write(h);w.document.close()}
// ===== INIT =====
function init(){
// LOGIN - using onclick like the test page
var loginBtn = document.getElementById("loginBtn");
if (loginBtn) {
loginBtn.onclick = function(){
var u = document.getElementById("loginUser").value.trim().toLowerCase();
var p = document.getElementById("loginPass").value.trim();
var err = document.getElementById("loginError");
var all = allUsers();
if (!all[u] || all[u].pass !== p) { err.style.display = "block"; return; }
err.style.display = "none";
currentUser = {login: u, name: all[u].name, role: all[u].role, email: all[u].email || "", branch: all[u].branch || "", dept: all[u].dept || "", region: all[u].region || "", oblast: all[u].oblast || "", city: all[u].city || ""};
localStorage.setItem("pab_current", JSON.stringify(currentUser));
showApp();
};
}
document.getElementById("regBtn").addEventListener("click",function(){doRegister()});
document.getElementById("loginUser").addEventListener("keydown",function(e){if(e.key==="Enter")doLogin()});
document.getElementById("loginPass").addEventListener("keydown",function(e){if(e.key==="Enter")doLogin()});
document.getElementById("logoutBtn").addEventListener("click",function(){doLogout()});
document.getElementById("regRole").addEventListener("change",function(){var r=document.getElementById("regRole").value;var q=getQuota(r);document.getElementById("roleHint").textContent=q.c>0?"📅 График: "+q.l:"📅 Без графика"});
// Tab switching
document.getElementById("tabLogin").addEventListener("click",function(){switchLoginTab("login")});
document.getElementById("tabRegister").addEventListener("click",function(){switchLoginTab("register")});
// Register
document.getElementById("regBtn").addEventListener("click",function(){doRegister()});
// Audit form listeners
document.getElementById("submitAuditBtn").addEventListener("click",function(){submitAudit()});
document.getElementById("resetAuditBtn").addEventListener("click",function(){resetAuditForm()});
document.getElementById("emailConfirmBtn").addEventListener("click",function(){sendEmailConfirm()});
document.getElementById("printConfirmBtn").addEventListener("click",function(){printConfirm()});
document.getElementById("addVioBtn").addEventListener("click",function(){addVioRow()});
document.getElementById("overallSafe").addEventListener("click",function(){setOverall("safe")});
document.getElementById("overallDanger").addEventListener("click",function(){setOverall("danger")});
document.getElementById("scheduleRemindBtn").addEventListener("click",function(){sendScheduleReminder()});
// Dialogue checkboxes
for(var i=0;i<5;i++){(function(idx){var cb=document.getElementById("dcb-"+idx);if(cb)cb.addEventListener("change",function(){if(idx===4){var o=document.getElementById("dialogueOther");if(o){o.style.display=this.checked?"block":"none";o.classList.toggle("visible",this.checked)}}})})(i)}
// Dashboard filter listeners
["dfRegion","dfOblast","dfBranch","dfDept","dfCity","dfDateFrom","dfDateTo"].forEach(function(id){var el=document.getElementById(id);if(el)el.addEventListener("change",function(){renderDashboard()})});
// Violations filter listeners
["vfStatus","vfType"].forEach(function(id){var el=document.getElementById(id);if(el)el.addEventListener("change",function(){renderViolations()})});
// History filter listeners
["filterOverall","filterDate","filterLocation"].forEach(function(id){var el=document.getElementById(id);if(el)el.addEventListener("input",function(){renderHistory()});else{var el2=document.getElementById(id);if(el2)el2.addEventListener("change",function(){renderHistory()})}});
document.getElementById("exportCsvBtn").addEventListener("click",function(){exportCSV()});
// Admin buttons
document.getElementById("dlFullCsv").addEventListener("click",function(){downloadFullCSV()});
document.getElementById("dlWorkerCsv").addEventListener("click",function(){downloadWorkerReport()});
document.getElementById("dlSummaryHtml").addEventListener("click",function(){downloadSummaryHTML()});
document.getElementById("clearAllBtn").addEventListener("click",function(){clearAllAudits()});
document.getElementById("clearPeriodBtn").addEventListener("click",function(){clearAuditsByDate()});
document.getElementById("showUsersBtn").addEventListener("click",function(){showAllUsers()});
// Nav listeners
document.querySelectorAll("nav a").forEach(function(a){a.addEventListener("click",function(e){e.preventDefault();var panel=this.getAttribute("data-panel");if(panel)switchPanel(panel)})});
// Category header toggle listeners (delegated)
document.getElementById("categorySections").addEventListener("click",function(e){var h=e.target.closest(".cat-header");if(h){var id=h.getAttribute("data-cat");if(id)toggleCat(id);return}var as=e.target.closest(".all-safe-toggle");if(as){var id=as.id.replace("as-","");toggleAllSafe(id);return}});
// Checkbox listeners for categories (delegated)
document.getElementById("categorySections").addEventListener("change",function(e){if(e.target.type==="checkbox"&&e.target.id&&e.target.id.indexOf("cb-")===0){var parts=e.target.id.split("-");if(parts.length===3)onCheckItem(parts[1],parseInt(parts[2]))}});
// Vio row remove (delegated)
document.getElementById("vioRows").addEventListener("click",function(e){if(e.target.classList.contains("remove-vio-btn")){var row=e.target.getAttribute("data-row");if(row)removeVioRow(parseInt(row))}});
// History edit/delete (delegated)
document.getElementById("historyBody").addEventListener("click",function(e){var ed=e.target.closest("a.view-link");if(ed){var id=parseInt(ed.id.replace("edit-",""));if(id)editAudit(id);return}var dl=e.target.closest("button.btn-danger");if(dl){var id=parseInt(dl.id.replace("del-",""));if(id)deleteAudit(id)}});
// Init data
document.getElementById("pabDate").value=new Date().toISOString().split("T")[0];
buildCategorySections();
initVioRows();
// Restore session
var saved=localStorage.getItem("pab_current");if(saved){try{currentUser=JSON.parse(saved);showApp()}catch(e){localStorage.removeItem("pab_current")}}
}
init();
</script>
</body>
</html>