safety-audit/app.html

257 lines
42 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 src="https://cdn.jsdelivr.net/npm/chart.js@4.4.7/dist/chart.umd.min.js"></script>
<style>
*{box-sizing:border-box;margin:0;padding:0}
body{font:15px/1.5 -apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;color:#0F1218;background:#F2F4F7;min-height:100vh}
.ah{background:#0F1218;color:#fff;padding:0 24px;display:flex;align-items:center;justify-content:space-between;height:56px;position:sticky;top:0;z-index:100}
.ah nav a{color:#9aa3b2;text-decoration:none;padding:7px 14px;border-radius:8px;font-size:13px;font-weight:600;cursor:pointer}.ah nav a.ac,.ah nav a:hover{color:#fff;background:rgba(255,255,255,.08)}
.acont{max-width:1140px;margin:0 auto;padding:24px}
.pn{display:none}.pn.ac{display:block}
.ph{margin-bottom:20px}.ph h2{font-size:26px;font-weight:800}
.card{background:#fff;border-radius:14px;padding:20px;box-shadow:0 2px 12px rgba(0,0,0,.06);margin-bottom:14px}
.card h3{font-size:16px;font-weight:700;margin-bottom:10px}
.stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(170px,1fr));gap:12px;margin-bottom:14px}
.st{background:#fff;border-radius:14px;padding:16px;box-shadow:0 2px 12px rgba(0,0,0,.06);text-align:center}
.st .n{font-size:30px;font-weight:800}.st .l{font-size:11px;color:#5B6573;text-transform:uppercase;margin-top:4px}
.gr .n{color:#2D6A4F}.rd .n{color:#E63946}.bl .n{color:#00B4D8}
.btn{display:inline-flex;align-items:center;gap:6px;padding:9px 18px;border-radius:8px;font-size:13px;font-weight:700;border:none;cursor:pointer;font-family:inherit}
.bp{background:#00B4D8;color:#fff}.bo{background:transparent;border:2px solid #E2E6EB;color:#0F1218}.bd{background:#E63946;color:#fff}
table{width:100%;border-collapse:collapse;font-size:13px;background:#fff;border-radius:14px;overflow:hidden}
th{background:#0F1218;color:#fff;padding:10px 12px;text-align:left;font-size:11px;text-transform:uppercase}
td{padding:8px 12px;border-bottom:1px solid #F2F4F7}tr:hover td{background:#F2F4F7}
.badge{display:inline-block;padding:2px 8px;border-radius:20px;font-size:10px;font-weight:700}
.bs{background:#EDF7F0;color:#2D6A4F}.bd2{background:#FFEBED;color:#E63946}.bw{background:#FFF3EF;color:#E76F51}
.fg{margin-bottom:12px}.fg label{display:block;font-size:11px;font-weight:700;color:#5B6573;margin-bottom:3px;text-transform:uppercase}
.fg input,.fg select,.fg textarea{width:100%;padding:9px 10px;border:2px solid #E2E6EB;border-radius:8px;font-size:13px;font-family:inherit;outline:none}
.fg input:focus,.fg select:focus,.fg textarea:focus{border-color:#00B4D8}
.fg textarea{resize:vertical;min-height:60px}
.hg{display:grid;grid-template-columns:1fr 1fr 1fr;gap:12px}.hg.c2{grid-template-columns:1fr 1fr}
.ci{display:flex;align-items:flex-start;gap:8px;padding:4px 0;font-size:13px}
.ci input[type=checkbox]{margin-top:2px;width:15px;height:15px;accent-color:#E63946;cursor:pointer;flex-shrink:0}
.ci.ck label{color:#E63946;font-weight:600}
.ot{display:flex;gap:10px;margin-top:10px}
.tb{flex:1;padding:10px;border:2px solid #E2E6EB;border-radius:8px;text-align:center;cursor:pointer;font-size:13px;font-weight:700}
.tb.sf{background:#EDF7F0;border-color:#2D6A4F;color:#2D6A4F}
.tb.df{background:#FFEBED;border-color:#E63946;color:#E63946}
.fs{background:#EDF7F0;border:2px solid #2D6A4F;border-radius:8px;padding:20px;color:#2D6A4F;font-weight:600;margin-top:14px;display:none;text-align:center}
@media(max-width:768px){.ah{padding:0 10px;flex-wrap:wrap;height:auto;padding-top:8px}.ah nav{width:100%;overflow-x:auto}.acont{padding:12px}.hg{grid-template-columns:1fr 1fr}.stats{grid-template-columns:1fr 1fr}}
</style>
</head>
<body>
<header class="ah"><div style="font-weight:700">🛡️ ПАБ Система</div><nav><a onclick="showPanel('NA')" class="ac">Новый аудит</a><a onclick="showPanel('MS')">Мой график</a><a onclick="showPanel('DB')">Дашборд</a><a onclick="showPanel('VL')">Нарушения</a><a onclick="showPanel('HS')">История</a></nav><span style="font-size:13px"><span id="dn" style="color:#48CAE4;font-weight:600"></span> <button class="btn bo" style="color:#9aa3b2;border-color:#3a4452;font-size:12px;padding:4px 10px" onclick="doLogout()">Выход</button></span></header>
<div class="acont">
<div id="sa" style="background:#FFF3EF;border:1px solid #E76F51;border-radius:14px;padding:16px 20px;margin-bottom:20px;display:none" class=""><span id="sat" style="font-size:14px;font-weight:600;color:#E76F51"></span> <a onclick="sendScheduleReminder()" style="color:#00B4D8;cursor:pointer;font-weight:600;text-decoration:underline;margin-left:8px;white-space:nowrap">✉️ Напомнить</a></div>
<div id="pnNA" class="pn ac">
<div class="ph"><h2>📋 Бланк ПАБ</h2></div>
<div class="card"><h3>📝 Данные аудита</h3>
<div class="hg"><div class="fg"><label>Бланк №</label><input type="number" id="pn"></div><div class="fg"><label>Дата</label><input type="date" id="pd"></div><div class="fg"><label>Регион</label><select id="pr"><option value="">--</option><option>Центральный</option><option>Алматинский</option><option>Восточный</option><option>Западный</option><option>Северный</option><option>Южный</option></select></div></div>
<div class="hg"><div class="fg"><label>Область</label><input id="pob"></div><div class="fg"><label>Город / село</label><input id="pct"></div><div class="fg"></div></div>
<div class="hg"><div class="fg"><label>Время начала</label><input type="time" id="ps"></div><div class="fg"><label>Время конца</label><input type="time" id="pe"></div><div class="fg"></div></div>
<div class="hg"><div class="fg"><label>Место</label><input id="pl"></div><div class="fg"><label>Тип работы</label><input id="pw"></div><div class="fg"><label>Кол-во наблюдаемых</label><input type="number" id="pc" value="1"></div></div>
<div class="hg c2"><div class="fg"><label>ФИО наблюдателя</label><input id="po"></div><div class="fg"><label>Должность наблюдателя</label><input id="por"></div></div>
<div class="hg c2"><div class="fg"><label>ФИО руководителя работ</label><input id="psv"></div><div class="fg"><label>Должность руководителя</label><input id="psr"></div></div>
<div class="fg"><label>Отметка</label><div class="ot"><div class="tb sf" id="os" onclick="setO('safe')">ВСЕ БЕЗОПАСНО</div><div class="tb" id="od" onclick="setO('danger')">⚠️ ЕСТЬ ОПАСНО</div></div></div>
<div class="fg" style="margin-top:10px"><label>📎 Прикрепить фото</label><input type="file" id="pfiles" multiple accept="image/*" onchange="var n=[];for(var i=0;i<this.files.length;i++)n.push(this.files[i].name);document.getElementById('fn').textContent=n.length?'📷 '+n.join(', '):''"><div id="fn" style="font-size:12px;color:#5B6573;margin-top:4px"></div></div></div>
<div class="card"><h3>📄 Категории наблюдения</h3><div id="cats"></div></div>
<div class="card"><h3>💬 Итог диалога</h3>
<div class="ci"><input type="checkbox" id="d0"><label for="d0">Работник привёл примеры безопасных действий</label></div>
<div class="ci"><input type="checkbox" id="d1"><label for="d1">Были обсуждены риски / проблемы</label></div>
<div class="ci"><input type="checkbox" id="d2"><label for="d2">Определены корректирующие меры</label></div>
<div class="ci"><input type="checkbox" id="d3"><label for="d3">Предложения работника зафиксированы</label></div>
</div>
<div class="card"><h3>📄 Несоответствия и корректирующие меры</h3>
<div style="overflow-x:auto"><table style="width:100%;border-collapse:collapse;font-size:12px"><thead><tr style="background:#0F1218;color:#fff"><th></th><th>Несоответствие</th><th>Исполнитель</th><th>Вид нарушения</th><th>Меры</th><th>Ответственный</th><th>Срок</th><th>Завершение</th><th></th></tr></thead><tbody id="vioBody"><tr id="vioRow1"><td>1</td><td><input class="v-nc" placeholder="Описание" style="width:100%;padding:3px;border:1px solid #E2E6EB;border-radius:4px;font-size:12px"></td><td><input class="v-ex" placeholder="Исполнитель" style="width:100%;padding:3px;border:1px solid #E2E6EB;border-radius:4px;font-size:12px"></td><td><select class="v-tp" style="width:100%;padding:3px;border:1px solid #E2E6EB;border-radius:4px;font-size:12px"><option>Нарушение</option><option>Замечание</option><option>Риск</option></select></td><td><input class="v-ms" placeholder="Меры" style="width:100%;padding:3px;border:1px solid #E2E6EB;border-radius:4px;font-size:12px"></td><td><input class="v-rs" placeholder="Ответственный" style="width:100%;padding:3px;border:1px solid #E2E6EB;border-radius:4px;font-size:12px"></td><td><input type="date" class="v-dt" style="width:100%;padding:3px;border:1px solid #E2E6EB;border-radius:4px;font-size:12px"></td><td><input class="v-fn" placeholder="Завершение" style="width:100%;padding:3px;border:1px solid #E2E6EB;border-radius:4px;font-size:12px"></td><td><button onclick="removeVioRow(this)" style="background:none;border:none;color:#E63946;cursor:pointer;font-size:16px">×</button></td></tr></tbody></table></div>
<button class="btn bo bs" onclick="addVioRowFn()" style="margin-top:6px">+ Добавить строку</button></div>
<button class="btn bp" onclick="submitAudit()" style="margin-right:10px">💾 Сохранить аудит</button><button class="btn bo" onclick="resetF()">🗑️ Очистить</button>
<div class="fs" id="fs"><div style="font-size:24px"></div><div style="font-size:16px;font-weight:800">Аудит успешно отправлен!</div><div id="sd" style="font-size:13px;color:#0F1218;margin-top:8px"></div></div>
</div>
<div id="pnMS" class="pn">
<div class="ph"><h2>📅 Мой график</h2></div><div id="msc">Загрузка...</div>
</div>
<div id="pnDB" class="pn">
<div class="ph"><h2>📊 Дашборд</h2></div><div id="dbc">Загрузка...</div>
</div>
<div id="pnVL" class="pn">
<div class="ph"><h2>⚠️ Несоответствия</h2></div><div id="vlc">Загрузка...</div>
</div>
<div id="pnHS" class="pn">
<div class="ph"><h2>📁 История</h2></div>
<button class="btn bo" onclick="exportCSV()" style="margin-bottom:10px">📥 CSV</button><button class="btn bo" onclick="exportData()" style="margin-bottom:10px;margin-left:6px">📤 Экспорт</button>
<table><thead><tr><th>Бланк</th><th>Дата</th><th>Место</th><th>Область</th><th>Город</th><th>Наблюдатель</th><th>Статус</th><th>Нарушений</th><th></th></tr></thead><tbody id="hbd"><tr><td colspan="9" style="text-align:center;padding:20px;color:#5B6573">Нет записей</td></tr></tbody></table>
</div>
</div>
<script>
var U,editId,lastSubmitted,vrc=6;
try{U=JSON.parse(sessionStorage.getItem("pab_user"));if(!U)location.href="index.html"}catch(e){location.href="index.html"}
document.getElementById("dn").textContent=U.login;
function isA(){return U&&U.login==="admin"}
function getU(){try{return JSON.parse(localStorage.getItem("pab_users")||"{}")}catch(e){return{}}}
function allU(){var r=getU();r.admin={pass:"admin",name:"Администратор",role:"Руководитель",email:"admin@telecom.kz",branch:"АО «Казахтелеком»",dept:"ЦА",region:"Центральный",oblast:"—",city:"г. Астана"};return r}
function getA(){try{return JSON.parse(localStorage.getItem("pab_audits")||"[]")}catch(e){return[]}}
function saveA(d){localStorage.setItem("pab_audits",JSON.stringify(d))}
function saveU(d){localStorage.setItem("pab_users",JSON.stringify(d))}
// Supabase — фоновая синхронизация, не трогает вкладки
function sbSync(){fetch(SBU+"/rest/v1/audits?select=*&order=created_at.desc",{headers:{"apikey":SBK,"Authorization":"Bearer "+SBK}}).then(function(r){return r.json()}).then(function(d){var am=[],ca=getA();d.forEach(function(x){am.push({id:x.id,number:x.number,date:x.date,location:x.location,region:x.region,workType:x.work_type,workerCount:x.worker_count,observer:x.observer,observerRole:x.observer_role,overallSafe:x.overall_safe,categories:x.categories,totalViolations:x.total_violations,dialogue:x.dialogue,photos:x.photos,docs:x.docs,createdBy:x.created_by,createdAt:x.created_at})});ca.forEach(function(a){var found=false;am.forEach(function(b){if(b.id===a.id)found=true});if(!found)am.push(a)});saveA(am)}).catch(function(){})}
function sbPushAudit(e){fetch(SBU+"/rest/v1/audits",{method:"POST",headers:{"apikey":SBK,"Authorization":"Bearer "+SBK,"Content-Type":"application/json","Prefer":"resolution=merge-duplicates"},body:JSON.stringify({id:e.id,number:e.number,date:e.date,location:e.location,region:e.region,work_type:e.workType,worker_count:e.workerCount,observer:e.observer,observer_role:e.observerRole,overall_safe:e.overallSafe,categories:e.categories,total_violations:e.totalViolations,dialogue:e.dialogue,photos:e.photos,docs:e.docs,created_by:e.createdBy,created_at:e.createdAt})}).catch(function(){})}
// Supabase для фото
var SBU="https://znexbjafkvyjffffbhlf.supabase.co";
var SBK="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InpuZXhiamFma3Z5amZmZmZiaGxmIiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImlhdCI6MTc4MDY0NDE4MiwiZXhwIjoyMDk2MjIwMTgyfQ.5pOYTkL5eCmpSHBY3EwKof6NVKt7tL4Fn8xUAKM8itE";
function gq(role){var q={c:1,p:"month",l:"1 раз в месяц"};return q}
function getUserQuota(u){if(u.freq){var p=u.freq.split(",");if(p.length===2)return{c:parseInt(p[0]),p:p[1],l:parseInt(p[0])+" раз(а) в "+(p[1]==="month"?"месяц":p[1]==="quarter"?"квартал":"полгода")}}return gq(u.role)}
function gp(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()}}return{s:new Date(n.getFullYear(),n.getMonth()<6?0:6,1),l:(n.getMonth()<6?1:2)+"-е полугодие "+n.getFullYear()}}
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:["Ремни безопасности","Опасное вождение","Состояние водителя","Телефон за рулём","Нарушение ПДД","Состояние ТС","Другое"]}];
// Build categories
var ch="";CATS.forEach(function(c){ch+="<div style=\"border:1px solid #E2E6EB;border-radius:8px;margin-bottom:8px;padding:12px;background:#F2F4F7\"><b>"+c.title+"</b><br>"+c.items.map(function(it,i){return"<div class=\"ci\"><input type=\"checkbox\" id=\"cb-"+c.id+"-"+i+"\" onchange=\"updateCT('"+c.id+"')\"><label for=\"cb-"+c.id+"-"+i+"\">"+it+"</label></div>"}).join("")+"<div style=\"margin-top:6px;font-size:12px;color:#5B6573\">Отмечено: <b id=\"cnt-"+c.id+"\">0</b></div></div>"});document.getElementById("cats").innerHTML=ch;
function updateCT(id){var cnt=0;CATS.find(function(c){return c.id===id}).items.forEach(function(_,i){if(document.getElementById("cb-"+id+"-"+i)&&document.getElementById("cb-"+id+"-"+i).checked)cnt++});document.getElementById("cnt-"+id).textContent=cnt}
function setO(t){var os=document.getElementById("os"),od=document.getElementById("od");os.className="tb"+(t==="safe"?" sf":"");od.className="tb"+(t==="danger"?" df":"")}
// Init form
document.getElementById("pd").value=new Date().toISOString().split("T")[0];
document.getElementById("po").value=U.name;document.getElementById("por").value=U.role||"";
document.getElementById("pr").value=U.region||"";document.getElementById("pob").value=U.oblast||"";document.getElementById("pct").value=U.city||"";
function showPanel(n){
["NA","MS","DB","VL","HS"].forEach(function(id){document.getElementById("pn"+id).classList.remove("ac")});
document.getElementById("pn"+n).classList.add("ac");
document.querySelectorAll("nav a").forEach(function(a){a.classList.toggle("ac",a.getAttribute("onclick").indexOf("'"+n+"'")>=0)});
if(n==="MS")rMS();if(n==="DB")rDB();if(n==="VL")rVL();if(n==="HS")rHS();sbSync();
}
function doLogout(){sessionStorage.removeItem("pab_user");location.href="index.html"}
function checkSA(){if(!U||isA()){document.getElementById("sa").style.display="none";return}var q=getUserQuota(U);if(!q.p)return;var p=gp(q.p);var done=getA().filter(function(a){return a.createdBy===U.login&&new Date(a.date)>=p.s}).length;var need=Math.max(0,q.c-done);var sa=document.getElementById("sa"),at=document.getElementById("sat");if(need>0){at.innerHTML="⚠️ Отставание: "+p.l+" — "+done+" из "+q.c+". Осталось: <b>"+need+"</b>.";sa.style.display="block";sa.style.background=need>=q.c?"#FFEBED":"#FFF3EF";sa.style.borderColor=need>=q.c?"#E63946":"#E76F51"}else{sa.style.display="none"}}
function sendScheduleReminder(){if(!U)return;var q=getUserQuota(U);var p=gp(q.p);var done=getA().filter(function(a){return a.createdBy===U.login&&new Date(a.date)>=p.s}).length;var need=Math.max(0,q.c-done);var to=U.email||"";if(!to||to.indexOf("@")<0){alert("Укажите email в профиле");return}location.href="mailto:"+encodeURIComponent(to)+"?subject="+encodeURIComponent("График ПАБ — "+p.l)+"&body="+encodeURIComponent("Уважаемый(ая) "+U.name+"!\n\nГрафик ПАБ: "+q.l+".\nПериод: "+p.l+".\nВыполнено: "+done+" из "+q.c+".\n"+(need>0?"Отставание: "+need+" ПАБ.":"График выполнен!")+"\n\nС уважением, Система ПАБ")}
function rMS(){
var c=document.getElementById("msc");if(!c)return;
if(isA()){
var all=allU();var rows="",wc=0;
for(var k in all){if(k==="admin")continue;wc++;var u=all[k];var q=getUserQuota(u);var p=gp(q.p);var d=getA().filter(function(a){return a.createdBy===k&&new Date(a.date)>=p.s}).length;var pct=Math.round(d/q.c*100);var st=d>q.c?"🔵 +"+(d-q.c):d>=q.c?"🟢 OK":"🔴 -"+(q.c-d);rows+="<tr><td>"+u.name+"</td><td>"+u.role+"</td><td>"+(u.branch||"—")+"</td><td>"+(u.region||"—")+"</td><td>"+q.l+"</td><td>"+d+"/"+q.c+"</td><td>"+st+"</td><td>"+p.l+"</td><td><button class=\"btn bd\" style=\"padding:2px 6px;font-size:10px\" onclick=\"delUser('"+k+"')\">🗑️</button></td></tr>"}
c.innerHTML=wc===0?"<div class=\"card\"><h3>👥 График работников</h3><p style=\"color:#5B6573\">Нет зарегистрированных работников. Зарегистрируйте их на странице входа.</p></div>":"<div class=\"card\"><h3>👥 График всех работников</h3></div><table><thead><tr><th>ФИО</th><th>Должность</th><th>Филиал</th><th>Регион</th><th>Норма</th><th>Прогресс</th><th>Статус</th><th>Период</th><th></th></tr></thead><tbody>"+rows+"</tbody></table>";
return;
}
var q=getUserQuota(U);var p=gp(q.p);var d=getA().filter(function(a){return a.createdBy===U.login&&new Date(a.date)>=p.s}).length;var pct=Math.round(d/q.c*100);var over=d>q.c;var cl=over?"#00B4D8":d>=q.c?"#2D6A4F":d>=q.c/2?"#E76F51":"#E63946";
c.innerHTML="<div class=\"card\"><h3>📅 "+p.l+"</h3><div style=\"font-size:12px;color:#5B6573;margin-bottom:8px\">Минимум: <b>"+q.l+"</b> | "+U.role+"</div><div style=\"height:14px;border-radius:7px;background:#E2E6EB;overflow:hidden;margin-bottom:6px\"><div style=\"height:100%;border-radius:7px;width:"+Math.min(pct,200)+"%;background:"+cl+";transition:width .5s\"></div></div><div style=\"font-size:13px;font-weight:600\">Проведено: <b>"+d+"</b> из <b>"+q.c+"</b>"+(over?" — <span style=\"color:#00B4D8\">✅ +"+(d-q.c)+" сверх плана!</span>":d>=q.c?" — ✅ план выполнен":" — <span style=\"color:#E63946\">осталось "+(q.c-d)+"</span>")+"</div></div><div class=\"card\" style=\"background:#E0F7FA;border:1px solid #00B4D8;margin-top:14px\"><h3>👤 Профиль</h3><div style=\"display:grid;grid-template-columns:1fr 1fr;gap:6px;font-size:13px\"><div><b>ФИО:</b> "+U.name+"</div><div><b>Должность:</b> "+U.role+"</div><div><b>Филиал:</b> "+(U.branch||"—")+"</div><div><b>Подразделение:</b> "+(U.dept||"—")+"</div><div><b>Регион:</b> "+(U.region||"—")+"</div><div><b>Область:</b> "+(U.oblast||"—")+"</div><div><b>Город:</b> "+(U.city||"—")+"</div><div><b>Email:</b> "+(U.email||"—")+"</div><div><b>График:</b> "+q.l+"</div><div><b>Проведено всего:</b> "+getA().filter(function(a){return a.createdBy===U.login}).length+"</div></div></div>"+(d>0?"<div class=\"card\"><h3>📋 Последние аудиты</h3>"+getA().filter(function(a){return a.createdBy===U.login}).slice(0,5).map(function(a){return"<div style=\"padding:6px 0;border-bottom:1px solid #F2F4F7;font-size:13px\">"+a.date+" — "+a.location+" — <span class=\"badge "+(a.overallSafe?"bs":"bd2")+"\">"+(a.overallSafe?"Безопасно":"Нарушений: "+a.totalViolations)+"</span></div>"}).join("")+"</div>":"");
}
function rDB(){
var c=document.getElementById("dbc");if(!c)return;
var a=getA(),all=allU();
// Date filter
var df=document.getElementById("df"),dt=document.getElementById("dt");
if(df&&df.value)a=a.filter(function(x){return x.date>=df.value});
if(dt&&dt.value)a=a.filter(function(x){return x.date<=dt.value});
var t=a.length,sf=a.filter(function(x){return x.overallSafe}).length,wd=a.filter(function(x){return !x.overallSafe}).length,tv=a.reduce(function(s,x){return s+(x.totalViolations||0)},0);
var ot=0,bh=0,ov=0;for(var k in all){if(k==="admin")continue;var u=all[k];var q=getUserQuota(u);if(!q.p)continue;var p=gp(q.p);var d=a.filter(function(x){return x.createdBy===k&&new Date(x.date)>=p.s}).length;if(d>q.c)ov++;else if(d>=q.c)ot++;else bh++}
var adb=isA()?"<div style=\"margin-bottom:12px;display:flex;gap:8px;flex-wrap:wrap\"><button class=\"btn bp\" onclick=\"downloadFullCSV()\">📥 CSV данные</button><button class=\"btn bo\" onclick=\"downloadSummaryHTML()\">📊 HTML отчёт</button><button class=\"btn bo\" onclick=\"showAllUsers()\">👥 Пользователи</button><span style=\"color:#E2E6EB;margin:0 4px\">|</span><button class=\"btn bp\" onclick=\"sbSync();alert('Синхронизировано!')\" style=\"background:#2D6A4F\">🔄 Синхронизировать</button><button class=\"btn bo\" onclick=\"importData()\">📥 Импорт</button><button class=\"btn bo\" onclick=\"exportData()\">📤 Экспорт</button></div>":"";
c.innerHTML=adb+"<div class=\"fb\"><span style=\"font-size:12px;color:#5B6573\">с</span><input type=\"date\" id=\"df\" style=\"width:140px\" onchange=\"rDB()\"><span style=\"font-size:12px;color:#5B6573\">по</span><input type=\"date\" id=\"dt\" style=\"width:140px\" onchange=\"rDB()\"><button class=\"btn bo bs\" onclick=\"document.getElementById('df').value='';document.getElementById('dt').value='';rDB()\" style=\"margin-left:6px\">✕ Сбросить</button></div>"+
"<div class=\"stats\"><div class=\"st\"><div class=\"n\">"+t+"</div><div class=\"l\">Всего аудитов</div></div><div class=\"st gr\"><div class=\"n\">"+sf+"</div><div class=\"l\">Безопасно</div></div><div class=\"st rd\"><div class=\"n\">"+wd+"</div><div class=\"l\">С нарушениями</div></div><div class=\"st rd\"><div class=\"n\">"+tv+"</div><div class=\"l\">Всего пунктов нарушений</div></div><div class=\"st gr\"><div class=\"n\">"+(ot+ov)+"</div><div class=\"l\">Выполняют график</div></div><div class=\"st rd\"><div class=\"n\">"+bh+"</div><div class=\"l\">Отстают от графика</div></div></div>"+
"<div style=\"display:grid;grid-template-columns:1fr 1fr;gap:14px;margin-bottom:14px\"><div class=\"card\"><h3>📂 Нарушения по категориям</h3><canvas id=\"ch1\"></canvas></div><div class=\"card\"><h3>📅 Динамика по датам</h3><canvas id=\"ch2\"></canvas></div></div>"+
"<div class=\"card\"><h3>🔝 Топ-10 нарушений</h3><canvas id=\"ch3\"></canvas></div>";
setTimeout(function(){
if(window._charts){for(var ck in window._charts)try{window._charts[ck].destroy()}catch(e){}}window._charts={};
var ctx1=document.getElementById("ch1");if(ctx1)window._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 a.reduce(function(s,x){var c2=x.categories&&x.categories[cat.id];return s+(c2?c2.items.length:0)},0)}),backgroundColor:"#E63946",borderRadius:4}]},options:{responsive:true,plugins:{legend:{display:false}}}});
var dates={};a.forEach(function(x){if(!dates[x.date])dates[x.date]=0;dates[x.date]+=(x.totalViolations||0)});var sd=Object.keys(dates).sort();
var ctx2=document.getElementById("ch2");if(ctx2)window._charts.tl=new Chart(ctx2,{type:"line",data:{labels:sd,datasets:[{label:"Нарушений",data:sd.map(function(d){return dates[d]}),borderColor:"#E63946",tension:0.3,pointRadius:4}]},options:{responsive:true,plugins:{legend:{display:false}}}});
var ic={};a.forEach(function(x){if(x.categories){Object.values(x.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 ctx3=document.getElementById("ch3");if(ctx3)window._charts.top=new Chart(ctx3,{type:"bar",data:{labels:ti.map(function(i){return 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}}}});
},300);
}
function rVL(){var c=document.getElementById("vlc");if(!c)return;var a=getA(),td=new Date().toISOString().split("T")[0],av=[];
a.forEach(function(x){
if(x.violations&&x.violations.length>0){x.violations.forEach(function(v){var dd=v.date||"",dn=v.done&&v.done.trim();var st="pending";if(dn)st="done";else if(dd&&dd<td)st="overdue";av.push({nc:v.nc,ex:v.executor,ms:v.measure,rs:v.responsible,dt:dd,dn:v.done||"",st:st,ad:x.date,an:x.number||"—",ob:x.oblast||"",ct:x.city||""})})}
else if(x.totalViolations>0&&x.categories){Object.values(x.categories).forEach(function(cat){if(cat.items)cat.items.forEach(function(it){av.push({nc:it.item,ex:x.observer,ms:"",rs:"",dt:"",dn:"",st:"pending",ad:x.date,an:x.number||"—",ob:x.oblast||"",ct:x.city||""})})})}
});c.innerHTML="<div class=\"stats\"><div class=\"st\"><div class=\"n\">"+av.length+"</div><div class=\"l\">Всего</div></div><div class=\"st gr\"><div class=\"n\">"+av.filter(function(v){return v.st==="done"}).length+"</div><div class=\"l\">Устранено</div></div><div class=\"st rd\"><div class=\"n\">"+av.filter(function(v){return v.st==="overdue"}).length+"</div><div class=\"l\">Просрочено</div></div><div class=\"st\"><div class=\"n\" style=\"color:#E76F51\">"+av.filter(function(v){return v.st==="pending"}).length+"</div><div class=\"l\">В работе</div></div></div>"+(av.length>0?"<table><thead><tr><th>№</th><th>Несоответствие</th><th>Аудит</th><th>Область</th><th>Город</th><th>Исполнитель</th><th>Меры</th><th>Срок</th><th>Статус</th></tr></thead><tbody>"+av.map(function(v,i){var sc=v.st==="done"?"bs":v.st==="overdue"?"bd2":"bw";var sl=v.st==="done"?"Устранено":v.st==="overdue"?"Просрочено":"В работе";return"<tr><td>"+(i+1)+"</td><td>"+v.nc+"</td><td>"+v.ad+"</td><td>"+(v.ob||"—")+"</td><td>"+(v.ct||"—")+"</td><td>"+v.ex+"</td><td>"+(v.ms||"—")+"</td><td>"+(v.dt||"—")+"</td><td><span class=\"badge "+sc+"\">"+sl+"</span></td></tr>"}).join("")+"</tbody></table>":"<p style=\"color:#5B6573;padding:20px\">Несоответствий не найдено</p>")}
function rHS(){var a=getA(),tb=document.getElementById("hbd");if(!tb)return;tb.innerHTML=a.length===0?"<tr><td colspan=\"9\" style=\"text-align:center;padding:20px;color:#5B6573\">Нет записей</td></tr>":a.map(function(x){var ab="<a style=\"color:#00B4D8;cursor:pointer;font-weight:600\" onclick=\"viewA("+x.id+")\">👁️</a>"+(isA()?" <a style=\"color:#00B4D8;cursor:pointer;font-weight:600\" onclick=\"editA("+x.id+")\">✏️</a> <button class=\"btn bd\" style=\"padding:4px 8px;font-size:11px\" onclick=\"delA("+x.id+")\">🗑️</button>":"");return"<tr><td>"+(x.number||"—")+"</td><td>"+x.date+"</td><td>"+x.location+"</td><td>"+(x.oblast||"—")+"</td><td>"+(x.city||"—")+"</td><td>"+x.observer+"</td><td><span class=\"badge "+(x.overallSafe?"bs":"bd2")+"\">"+(x.overallSafe?"Безопасно":"Нарушения")+"</span></td><td>"+(x.totalViolations||0)+"</td><td>"+ab+"</td></tr>"}).join("")}
function uploadPhotos(files,callback){var urls=[];var done=0;function check(){done++;if(done>=files.length)callback(urls)}if(files.length===0){callback([]);return}for(var i=0;i<files.length;i++){(function(f){var fn=Date.now()+"_"+Math.random().toString(36).slice(2)+"_"+f.name.replace(/[^a-zA-Z0-9._-]/g,"_");fetch(SBU+"/storage/v1/object/photos/"+fn,{method:"POST",headers:{"apikey":SBK,"Authorization":"Bearer "+SBK,"Content-Type":f.type},body:f}).then(function(r){if(r.ok){urls.push(SBU+"/storage/v1/object/public/photos/"+fn)}check()}).catch(function(){check()})})(files[i])}}
// Violations table
var vioRC=1;
function addVioRowFn(){vioRC++;var t=document.getElementById("vioRow1").cloneNode(true);t.id="vioRow"+vioRC;t.querySelector("td").textContent=vioRC;t.querySelectorAll("input").forEach(function(i){i.value=""});document.getElementById("vioBody").appendChild(t)}
function removeVioRow(btn){var rows=document.querySelectorAll("#vioBody tr");if(rows.length<=1)return;btn.closest("tr").remove();document.querySelectorAll("#vioBody tr").forEach(function(r,i){r.querySelector("td").textContent=i+1});vioRC=document.querySelectorAll("#vioBody tr").length}
function getVioData(){var r=[];document.querySelectorAll("#vioBody tr").forEach(function(row){var nc=row.querySelector(".v-nc");if(!nc||!nc.value.trim())return;r.push({nc:nc.value.trim(),executor:row.querySelector(".v-ex").value.trim(),type:row.querySelector(".v-tp").value,measure:row.querySelector(".v-ms").value.trim(),responsible:row.querySelector(".v-rs").value.trim(),date:row.querySelector(".v-dt").value,done:row.querySelector(".v-fn").value.trim()})});return r}
function submitAudit(){
if(editId&&!isA()){alert("Только администратор может редактировать");return}
var loc=document.getElementById("pl").value.trim();if(!loc){alert("Укажите место проведения");return}
var pf=document.getElementById("pfiles");var files=pf&&pf.files?Array.from(pf.files):[];
uploadPhotos(files,function(photoUrls){
var cats={},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)ch.push({item:item})});cats[cat.id]={items:ch,allSafe:ch.length===0};tv+=ch.length});
var dl=[];if(document.getElementById("d0").checked)dl.push("Работник привёл примеры безопасных действий");if(document.getElementById("d1").checked)dl.push("Были обсуждены риски / проблемы");if(document.getElementById("d2").checked)dl.push("Определены корректирующие меры");if(document.getElementById("d3").checked)dl.push("Предложения работника зафиксированы");
var e={id:editId||Date.now(),number:document.getElementById("pn").value.trim(),date:document.getElementById("pd").value,timeStart:document.getElementById("ps").value,timeEnd:document.getElementById("pe").value,location:loc,region:document.getElementById("pr").value,workType:document.getElementById("pw").value.trim(),workerCount:parseInt(document.getElementById("pc").value)||1,observer:document.getElementById("po").value.trim()||U.name,observerRole:document.getElementById("por").value.trim(),supervisor:document.getElementById("psv").value.trim(),supervisorRole:document.getElementById("psr").value.trim(),oblast:document.getElementById("pob").value.trim(),city:document.getElementById("pct").value.trim(),overallSafe:document.getElementById("os").classList.contains("sf"),categories:cats,totalViolations:tv,violations:getVioData(),dialogue:dl,photos:photoUrls,createdBy:U.login,createdAt:new Date().toISOString()};
var audits=getA();if(editId){audits=audits.map(function(a){return a.id===editId?e:a});editId=null}else{audits.unshift(e)}saveA(audits);sbPushAudit(e);lastSubmitted=e;
document.getElementById("sd").innerHTML="<b>Бланк №"+(e.number||"—")+"</b> | "+e.date+" | "+(e.overallSafe?"БЕЗОПАСНО":"НАРУШЕНИЙ: "+e.totalViolations);document.getElementById("fs").style.display="block";setTimeout(function(){document.getElementById("fs").style.display="none"},20000);
resetF();rVL();
});
}
function resetF(){
document.getElementById("pn").value="";document.getElementById("pd").value=new Date().toISOString().split("T")[0];document.getElementById("ps").value="";document.getElementById("pe").value="";document.getElementById("pr").value=U.region||"";document.getElementById("pl").value="";document.getElementById("pw").value="";document.getElementById("pc").value="1";document.getElementById("po").value=U.name;document.getElementById("por").value="";document.getElementById("psv").value="";document.getElementById("psr").value="";document.getElementById("pob").value=U.oblast||"";document.getElementById("pct").value=U.city||"";setO("safe");editId=null;
CATS.forEach(function(cat){cat.items.forEach(function(_,i){var cb=document.getElementById("cb-"+cat.id+"-"+i);if(cb)cb.checked=false});updateCT(cat.id)});
document.getElementById("d0").checked=false;document.getElementById("d1").checked=false;document.getElementById("d2").checked=false;document.getElementById("d3").checked=false;
document.getElementById("pfiles").value="";document.getElementById("fn").textContent="";
}
function exportCSV(){var a=getA();if(a.length===0){alert("Нет данных");return}var all=allU(),h="Бланк №;Дата;Место;Область;Город;Наблюдатель;Должность;Филиал;Регион;Статус;Нарушений",rs=a.map(function(x){var u=all[x.createdBy]||{};return(x.number||"")+";"+x.date+";"+x.location+";"+(x.oblast||"")+";"+(x.city||"")+";"+x.observer+";"+(x.observerRole||"")+";"+(u.branch||"")+";"+(u.region||"")+";"+(x.overallSafe?"Безопасно":"Нарушения")+";"+(x.totalViolations||0)});var bom="\uFEFF",csv=bom+h+"\n"+rs.join("\n"),bl=new Blob([csv],{type:"text/csv;charset=utf-8"}),ur=URL.createObjectURL(bl),dl=document.createElement("a");dl.href=ur;dl.download="pab.csv";dl.click();URL.revokeObjectURL(ur);alert("CSV в кодировке UTF-8. Откройте в Excel: Данные → Из текста → выберите файл → UTF-8")}
function editA(id){if(!isA()){alert("Только админ");return}alert("Редактирование: аудит #"+id)}
function delA(id){if(!isA()){alert("Только админ");return}if(!confirm("Удалить?"))return;saveA(getA().filter(function(a){return a.id!==id}));rHS()}
function delUser(login){if(!isA())return;if(!confirm("Удалить пользователя "+login+" и все его аудиты?"))return;var u=getU();delete u[login];saveU(u);var a=getA().filter(function(x){return x.createdBy!==login});saveA(a);fetch(SBU+"/rest/v1/users?login=eq."+encodeURIComponent(login),{method:"DELETE",headers:{"apikey":SBK,"Authorization":"Bearer "+SBK}}).catch(function(){});rMS();rHS();rDB()}
function downloadFullCSV(){var a=getA();if(a.length===0){alert("Нет данных");return}var all=allU(),h="Бланк №;Дата;Место;Наблюдатель;Филиал;Регион;Статус;Нарушений",rs=a.map(function(x){var u=all[x.createdBy]||{};return(x.number||"")+";"+x.date+";"+x.location+";"+x.observer+";"+(u.branch||"")+";"+(u.region||"")+";"+(x.overallSafe?"Безопасно":"Нарушения")+";"+(x.totalViolations||0)}),csv="\uFEFF"+h+"\n"+rs.join("\n"),bl=new Blob([csv],{type:"text/csv"}),ur=URL.createObjectURL(bl),dl=document.createElement("a");dl.href=ur;dl.download="pab-full.csv";dl.click();URL.revokeObjectURL(ur)}
function showAllUsers(){if(!isA())return;var all=allU(),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></tr>";for(var k in all){var u=all[k];h+="<tr><td>"+k+(k==="admin"?" ⭐":"")+"</td><td>"+u.name+"</td><td>"+u.role+"</td><td>"+(u.branch||"—")+"</td><td>"+(u.region||"—")+"</td><td>"+(u.city||"—")+"</td></tr>"}h+="</table>";var w=window.open("","_blank","width=800,height=500");w.document.write("<!DOCTYPE html><html><head><meta charset=\"utf-8\"><title>Пользователи</title><style>body{font:14px/1.5 Arial;max-width:800px;margin:20px auto;padding:20px}</style></head><body>"+h+"</body></html>");w.document.close()}
function downloadSummaryHTML(){var a=getA(),all=allU(),t=a.length;var w=window.open("","_blank","width=800,height=600");w.document.write("<!DOCTYPE html><html><head><meta charset=\"utf-8\"><title>Отчёт ПАБ</title><style>body{font:14px/1.5 Arial;max-width:900px;margin:20px auto;padding:20px}h1{font-size:22px}h2{font-size:16px;margin-top:20px}table{width:100%;border-collapse:collapse;font-size:12px}th{background:#0F1218;color:#fff;padding:8px}td{padding:6px 8px;border-bottom:1px solid #E2E6EB}@media print{button{display:none}}</style></head><body><button onclick=\"window.print()\" style=\"padding:8px 16px;margin-bottom:16px\">🖨️ Печать</button><h1>📊 Сводный отчёт ПАБ</h1><p>Сформирован: "+new Date().toLocaleString("ru")+" | Всего аудитов: "+t+"</p><h2>📋 Аудиты</h2><table><tr><th>№</th><th>Дата</th><th>Место</th><th>Наблюдатель</th><th>Статус</th><th>Нарушений</th></tr>"+a.map(function(x){return"<tr><td>"+(x.number||"—")+"</td><td>"+x.date+"</td><td>"+x.location+"</td><td>"+x.observer+"</td><td>"+(x.overallSafe?"Безопасно":"Нарушения")+"</td><td>"+(x.totalViolations||0)+"</td></tr>"}).join("")+"</table></body></html>");w.document.close()}
function exportData(){
var a=getA();if(a.length===0){alert("Нет данных");return}
var all=allU(),h="Бланк №;Дата;Время;Место;Область;Город;Наблюдатель;Должность;Руководитель;Тип работы;Регион;Филиал;Статус;Нарушений";
var rs=a.map(function(x){var u=all[x.createdBy]||{};return(x.number||"")+";"+x.date+";"+(x.timeStart||"")+"-"+(x.timeEnd||"")+";"+x.location+";"+(x.oblast||"")+";"+(x.city||"")+";"+x.observer+";"+(x.observerRole||"")+";"+(x.supervisor||"")+";"+(x.workType||"")+";"+(x.region||"")+";"+(u.branch||"")+";"+(x.overallSafe?"Безопасно":"Нарушения")+";"+(x.totalViolations||0)});
var csv="\uFEFF"+h+"\n"+rs.join("\n"),bl=new Blob([csv],{type:"text/csv;charset=utf-8"}),ur=URL.createObjectURL(bl),dl=document.createElement("a");
dl.href=ur;dl.download="pab-export-"+new Date().toISOString().split("T")[0]+".csv";dl.click();URL.revokeObjectURL(ur);
}
function importData(){
var input=document.createElement("input");input.type="file";input.accept=".json";
input.onchange=function(){
var file=this.files[0];if(!file)return;
var reader=new FileReader();
reader.onload=function(e){
try{
var data=JSON.parse(e.target.result);
if(data.audits&&confirm("Импортировать "+data.audits.length+" аудитов и пользователей? Текущие данные будут объединены.")){
var curA=getA(),curU=getU();
// Merge audits (avoid duplicates by id)
var ids={};curA.forEach(function(a){ids[a.id]=true});
data.audits.forEach(function(a){if(!ids[a.id])curA.push(a)});
saveA(curA);
// Merge users
for(var k in data.users){if(!curU[k])curU[k]=data.users[k]}
localStorage.setItem("pab_users",JSON.stringify(curU));
alert("Импортировано! Аудитов: "+curA.length+", пользователей: "+Object.keys(curU).length);
rDB();rHS();rVL();rMS();
}
}catch(ex){alert("Ошибка чтения файла")}
};
reader.readAsText(file);
};
input.click();
}
rHS();sbSync();checkSA();
</script>
</body>
</html>