Фильтр по датам, отслеживание нарушений, управление данными админа
This commit is contained in:
parent
c9ac12a3a4
commit
7f0c0c322e
153
index.html
153
index.html
@ -238,6 +238,7 @@ body{font:15px/1.5 -apple-system,BlinkMacSystemFont,"Segoe UI",Inter,system-ui,s
|
||||
<a href="#" data-panel="newAudit" class="active" onclick="switchPanel('newAudit',this)">Новый аудит</a>
|
||||
<a href="#" data-panel="mySchedule" onclick="switchPanel('mySchedule',this)">Мой график</a>
|
||||
<a href="#" data-panel="dashboard" onclick="switchPanel('dashboard',this)">Дашборд</a>
|
||||
<a href="#" data-panel="violations" onclick="switchPanel('violations',this)">Нарушения</a>
|
||||
<a href="#" data-panel="history" onclick="switchPanel('history',this)">История</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" onclick="doLogout()">Выход</button></div>
|
||||
@ -324,6 +325,10 @@ body{font:15px/1.5 -apple-system,BlinkMacSystemFont,"Segoe UI",Inter,system-ui,s
|
||||
<button class="btn btn-primary btn-sm" onclick="downloadFullCSV()">📥 Скачать все данные (CSV)</button>
|
||||
<button class="btn btn-outline btn-sm" onclick="downloadWorkerReport()">👥 Отчёт по работникам (CSV)</button>
|
||||
<button class="btn btn-outline btn-sm" onclick="downloadSummaryHTML()">📊 Сводный отчёт (HTML)</button>
|
||||
<span style="color:var(--gray-200);margin:0 4px">|</span>
|
||||
<button class="btn btn-danger btn-sm" onclick="clearAllAudits()">🗑️ Очистить все аудиты</button>
|
||||
<button class="btn btn-outline btn-sm" onclick="clearAuditsByDate()">📅 Очистить за период</button>
|
||||
<button class="btn btn-outline btn-sm" onclick="showAllUsers()">👥 Все пользователи</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="filter-bar" id="dashboardFilters">
|
||||
@ -332,6 +337,10 @@ body{font:15px/1.5 -apple-system,BlinkMacSystemFont,"Segoe UI",Inter,system-ui,s
|
||||
<select id="dfBranch" onchange="renderDashboard()"><option value="all">Все филиалы</option></select>
|
||||
<select id="dfDept" onchange="renderDashboard()"><option value="all">Все подразделения</option></select>
|
||||
<select id="dfCity" onchange="renderDashboard()"><option value="all">Все города</option></select>
|
||||
<span style="font-size:12px;color:var(--gray-500)">с</span>
|
||||
<input type="date" id="dfDateFrom" onchange="renderDashboard()" style="width:140px">
|
||||
<span style="font-size:12px;color:var(--gray-500)">по</span>
|
||||
<input type="date" id="dfDateTo" onchange="renderDashboard()" 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>
|
||||
@ -356,6 +365,28 @@ body{font:15px/1.5 -apple-system,BlinkMacSystemFont,"Segoe UI",Inter,system-ui,s
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ========== VIOLATIONS TRACKING ========== -->
|
||||
<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" onchange="renderViolations()"><option value="all">Все</option><option value="done">Устранено</option><option value="overdue">Просрочено</option><option value="pending">В работе</option></select>
|
||||
<select id="vfType" onchange="renderViolations()"><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>
|
||||
|
||||
<!-- ========== HISTORY ========== -->
|
||||
<div id="panelHistory" class="panel">
|
||||
<div class="page-header"><h2>📁 История аудитов</h2><p>Архив всех проведённых ПАБ</p></div>
|
||||
@ -701,6 +732,7 @@ function switchPanel(name,el){
|
||||
if(name==='dashboard')renderDashboard();
|
||||
if(name==='history')renderHistory();
|
||||
if(name==='mySchedule')renderMySchedule();
|
||||
if(name==='violations')renderViolations();
|
||||
}
|
||||
|
||||
// ========== DASHBOARD ==========
|
||||
@ -742,6 +774,12 @@ function renderDashboard(){
|
||||
return true;
|
||||
});
|
||||
|
||||
// Date range filter
|
||||
const df=document.getElementById('dfDateFrom')?.value;
|
||||
const dt=document.getElementById('dfDateTo')?.value;
|
||||
if(df)audits=audits.filter(a=>a.date>=df);
|
||||
if(dt)audits=audits.filter(a=>a.date<=dt);
|
||||
|
||||
const total=audits.length;
|
||||
const allSafe=audits.filter(a=>a.overallSafe).length;
|
||||
const withDanger=audits.filter(a=>!a.overallSafe).length;
|
||||
@ -857,7 +895,8 @@ function exportCSV(){
|
||||
|
||||
// ========== ADMIN DOWNLOADS ==========
|
||||
function downloadFullCSV(){
|
||||
const audits=getAudits();if(audits.length===0){alert('Нет данных');return}
|
||||
const {audits,all}=getDashboardFilters();
|
||||
if(audits.length===0){alert('Нет данных');return}
|
||||
const all=getAllUsers();
|
||||
const header='Бланк №;Дата;Время;Место;Тип работы;Наблюдатель;Должность;Руководитель;Филиал;Подразделение;Регион;Область;Город;Статус;Нарушений;Категории с нарушениями';
|
||||
const rows=audits.map(a=>{
|
||||
@ -869,7 +908,8 @@ function downloadFullCSV(){
|
||||
}
|
||||
|
||||
function downloadWorkerReport(){
|
||||
const all=getAllUsers();const audits=getAudits();const userList=Object.entries(all).filter(([l])=>l!=='admin');
|
||||
const {audits}=getDashboardFilters();
|
||||
const all=getAllUsers();const userList=Object.entries(all).filter(([l])=>l!=='admin');
|
||||
if(userList.length===0){alert('Нет зарегистрированных работников');return}
|
||||
const header='Логин;ФИО;Должность;Филиал;Подразделение;Регион;Область;Город;Email;График;Период;Выполнено;Нужно;Статус';
|
||||
const rows=userList.map(([login,u])=>{
|
||||
@ -883,7 +923,8 @@ function downloadWorkerReport(){
|
||||
}
|
||||
|
||||
function downloadSummaryHTML(){
|
||||
const audits=getAudits();const all=getAllUsers();const userList=Object.entries(all).filter(([l])=>l!=='admin');
|
||||
const {audits}=getDashboardFilters();
|
||||
const all=getAllUsers();const userList=Object.entries(all).filter(([l])=>l!=='admin');
|
||||
const total=audits.length;const allSafe=audits.filter(a=>a.overallSafe).length;
|
||||
const withDanger=audits.filter(a=>!a.overallSafe).length;const totalVio=audits.reduce((s,a)=>s+(a.totalViolations||0),0);
|
||||
|
||||
@ -944,6 +985,112 @@ tr:hover td{background:#F2F4F7}
|
||||
</body></html>`;
|
||||
const w=window.open('','_blank','width=1000,height=800');w.document.write(html);w.document.close();
|
||||
}
|
||||
|
||||
// ========== VIOLATIONS TRACKING ==========
|
||||
function renderViolations(){
|
||||
const all=getAllUsers();let audits=getAudits();
|
||||
const df=document.getElementById('dfDateFrom')?.value,dt=document.getElementById('dfDateTo')?.value;
|
||||
if(df)audits=audits.filter(a=>a.date>=df);
|
||||
if(dt)audits=audits.filter(a=>a.date<=dt);
|
||||
const fStatus=document.getElementById('vfStatus')?.value||'all';
|
||||
const fType=document.getElementById('vfType')?.value||'all';
|
||||
|
||||
// Collect all violations from all audits
|
||||
const today=new Date().toISOString().split('T')[0];
|
||||
let allVios=[];
|
||||
audits.forEach(a=>{
|
||||
if(!a.violations)return;
|
||||
const u=all[a.createdBy]||{};
|
||||
a.violations.forEach((v,i)=>{
|
||||
const dueDate=v.date||'';
|
||||
const done=v.done&&v.done.trim();
|
||||
let status='pending';
|
||||
if(done)status='done';
|
||||
else if(dueDate&&dueDate<today)status='overdue';
|
||||
allVios.push({...v,status,auditDate:a.date,auditNumber:a.number||'—',auditLocation:a.location,auditId:a.id,auditObserver:a.observer,observerBranch:u.branch||''});
|
||||
});
|
||||
});
|
||||
|
||||
// Stats
|
||||
const total=allVios.length;
|
||||
const doneCount=allVios.filter(v=>v.status==='done').length;
|
||||
const overdueCount=allVios.filter(v=>v.status==='overdue').length;
|
||||
const pendingCount=allVios.filter(v=>v.status==='pending').length;
|
||||
document.getElementById('vTotal').textContent=total;
|
||||
document.getElementById('vDone').textContent=doneCount;
|
||||
document.getElementById('vOverdue').textContent=overdueCount;
|
||||
document.getElementById('vPending').textContent=pendingCount;
|
||||
|
||||
// Filter
|
||||
if(fStatus!=='all')allVios=allVios.filter(v=>v.status===fStatus);
|
||||
if(fType!=='all')allVios=allVios.filter(v=>v.type===fType);
|
||||
|
||||
const 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((v,i)=>{
|
||||
const sc=v.status==='done'?'badge-safe':v.status==='overdue'?'badge-danger':'badge-warn';
|
||||
const 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="${escAttr(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="${escAttr(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('');
|
||||
}
|
||||
|
||||
// ========== ADMIN DATA MANAGEMENT ==========
|
||||
function clearAllAudits(){
|
||||
if(!isAdmin())return;
|
||||
if(!confirm('ВНИМАНИЕ! Это удалит ВСЕ аудиты безвозвратно. Продолжить?'))return;
|
||||
if(!confirm('Точно удалить все данные?'))return;
|
||||
localStorage.removeItem('safetyAudits');
|
||||
alert('Все аудиты удалены.');
|
||||
renderDashboard();renderHistory();renderViolations();
|
||||
}
|
||||
|
||||
function clearAuditsByDate(){
|
||||
if(!isAdmin())return;
|
||||
const from=prompt('Удалить аудиты С даты (ГГГГ-ММ-ДД):','');
|
||||
if(!from)return;
|
||||
const to=prompt('Удалить аудиты ПО дату (ГГГГ-ММ-ДД, или оставьте пустым):','');
|
||||
let audits=getAudits();
|
||||
const before=audits.length;
|
||||
audits=audits.filter(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) +' аудитов за период '+from+(to?' — '+to:'')+'?'))return;
|
||||
saveAudits(audits);
|
||||
alert('Удалено '+(before-audits.length)+' аудитов.');
|
||||
renderDashboard();renderHistory();renderViolations();
|
||||
}
|
||||
|
||||
function showAllUsers(){
|
||||
if(!isAdmin())return;
|
||||
const all=getAllUsers();
|
||||
const userList=Object.entries(all);
|
||||
let html=`<h2>👥 Зарегистрированные пользователи (${userList.length})</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>`;
|
||||
userList.forEach(([login,u])=>{
|
||||
const q=getQuota(u.role);
|
||||
html+=`<tr style="border-bottom:1px solid #E2E6EB"><td>${login}${login==='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.label}</td></tr>`;
|
||||
});
|
||||
html+='</table>';
|
||||
const 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>${html}<p style="margin-top:20px;font-size:11px;color:#5B6573">⭐ — предустановленный администратор</p></body></html>`);
|
||||
w.document.close();
|
||||
}
|
||||
|
||||
// ========== DOWNLOADS WITH DATE RANGE ==========
|
||||
function getDashboardFilters(){
|
||||
const all=getAllUsers();
|
||||
let audits=getAudits();
|
||||
const fr=document.getElementById('dfRegion')?.value||'all',fo=document.getElementById('dfOblast')?.value||'all',fb=document.getElementById('dfBranch')?.value||'all',fd=document.getElementById('dfDept')?.value||'all',fc=document.getElementById('dfCity')?.value||'all';
|
||||
const df=document.getElementById('dfDateFrom')?.value,dt=document.getElementById('dfDateTo')?.value;
|
||||
audits=audits.filter(a=>{const 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(a=>a.date>=df);if(dt)audits=audits.filter(a=>a.date<=dt);
|
||||
return {audits,all};
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user