safety-audit/app.html

232 lines
33 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="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="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="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>
<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></tr></thead><tbody id="hbd"><tr><td colspan="7" 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))}
// 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||"";
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();
}
function doLogout(){sessionStorage.removeItem("pab_user");location.href="index.html"}
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></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></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();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=\"importData()\">📥 Импорт</button><button class=\"btn bo\" onclick=\"exportData()\">📤 Экспорт</button></div>":"";
c.innerHTML=adb+"<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 bl\"><div class=\"n\">"+ov+"</div><div class=\"l\">Перевыполняют</div></div><div class=\"st gr\"><div class=\"n\">"+ot+"</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||"—"})})}
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+" ("+x.location+")",ex:x.observer,ms:"",rs:"",dt:"",dn:"",st:"info",ad:x.date,an:x.number||"—"})})})}
});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></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.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=\"7\" style=\"text-align:center;padding:20px;color:#5B6573\">Нет записей</td></tr>":a.map(function(x){var ab=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.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])}}
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,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(),overallSafe:document.getElementById("os").classList.contains("sf"),categories:cats,totalViolations:tv,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);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("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="";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.observer+";"+(x.observerRole||"")+";"+(u.branch||"")+";"+(u.region||"")+";"+(u.city||"")+";"+(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.csv";dl.click();URL.revokeObjectURL(ur)}
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 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 data={users:getU(),audits:getA(),exported:new Date().toISOString()};
var json=JSON.stringify(data,null,2);
var blob=new Blob([json],{type:"application/json"});
var url=URL.createObjectURL(blob);var dl=document.createElement("a");
dl.href=url;dl.download="pab-data-"+new Date().toISOString().split("T")[0]+".json";dl.click();URL.revokeObjectURL(url);
alert("Файл сохранён. Отправьте его админу для импорта.");
}
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();
</script>
</body>
</html>