v3: interactive checklist page - 6 directions, 42 check items, AI auto-fill, violation form, document preview
This commit is contained in:
parent
c53ff4fbb8
commit
8366052d0c
860
checklist.html
Normal file
860
checklist.html
Normal file
@ -0,0 +1,860 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="ru">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||||
|
<title>Чек-лист проверки | Цифровой инспектор БиОТ</title>
|
||||||
|
<style>
|
||||||
|
:root {
|
||||||
|
--ink: #0F1218;
|
||||||
|
--cyan: #00E5FF;
|
||||||
|
--white: #fff;
|
||||||
|
--gray-500: #5B6573;
|
||||||
|
--gray-100: #F2F4F7;
|
||||||
|
--gray-800: #1A1D25;
|
||||||
|
--gray-700: #252833;
|
||||||
|
--gray-600: #3A3E4A;
|
||||||
|
--red: #FF4D4D;
|
||||||
|
--green: #00C853;
|
||||||
|
--amber: #FFB300;
|
||||||
|
--red-bg: rgba(255,77,77,0.08);
|
||||||
|
--green-bg: rgba(0,200,83,0.08);
|
||||||
|
--amber-bg: rgba(255,179,0,0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
*{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: #f0f2f5;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== TOP BAR ===== */
|
||||||
|
.topbar {
|
||||||
|
background: var(--ink);
|
||||||
|
color: var(--white);
|
||||||
|
padding: 12px 20px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
.topbar .back {
|
||||||
|
color: var(--cyan);
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 20px;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
.topbar .title { font-weight: 700; font-size: 16px; flex: 1 }
|
||||||
|
.topbar .save {
|
||||||
|
color: var(--cyan);
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== HEADER CARD ===== */
|
||||||
|
.header-card {
|
||||||
|
background: var(--white);
|
||||||
|
margin: 16px 16px 0;
|
||||||
|
border-radius: 14px;
|
||||||
|
padding: 20px;
|
||||||
|
box-shadow: 0 1px 3px rgba(0,0,0,0.06);
|
||||||
|
}
|
||||||
|
.header-card .field {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 10px 0;
|
||||||
|
border-bottom: 1px solid #f0f2f5;
|
||||||
|
}
|
||||||
|
.header-card .field:last-child { border-bottom: none }
|
||||||
|
.header-card .field .label {
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--gray-500);
|
||||||
|
width: 110px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
.header-card .field .value {
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 600;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
.header-card .field select, .header-card .field input {
|
||||||
|
flex: 1;
|
||||||
|
border: 1px solid #e0e3e8;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 8px 12px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-family: inherit;
|
||||||
|
background: #fafbfc;
|
||||||
|
color: var(--ink);
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-bar {
|
||||||
|
margin: 16px 16px 0;
|
||||||
|
background: var(--white);
|
||||||
|
border-radius: 14px;
|
||||||
|
padding: 14px 20px;
|
||||||
|
box-shadow: 0 1px 3px rgba(0,0,0,0.06);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
.progress-bar .track {
|
||||||
|
flex: 1;
|
||||||
|
height: 6px;
|
||||||
|
background: #e5e7eb;
|
||||||
|
border-radius: 3px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.progress-bar .fill {
|
||||||
|
height: 100%;
|
||||||
|
background: var(--cyan);
|
||||||
|
border-radius: 3px;
|
||||||
|
transition: width 0.3s;
|
||||||
|
}
|
||||||
|
.progress-bar .pct {
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--cyan);
|
||||||
|
min-width: 45px;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== TABS ===== */
|
||||||
|
.tabs {
|
||||||
|
display: flex;
|
||||||
|
margin: 16px 16px 0;
|
||||||
|
background: var(--white);
|
||||||
|
border-radius: 14px;
|
||||||
|
padding: 4px;
|
||||||
|
box-shadow: 0 1px 3px rgba(0,0,0,0.06);
|
||||||
|
overflow-x: auto;
|
||||||
|
gap: 2px;
|
||||||
|
}
|
||||||
|
.tab {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 90px;
|
||||||
|
padding: 10px 8px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 11px;
|
||||||
|
background: transparent;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--gray-500);
|
||||||
|
cursor: pointer;
|
||||||
|
white-space: nowrap;
|
||||||
|
transition: all 0.15s;
|
||||||
|
font-family: inherit;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.tab.active {
|
||||||
|
background: var(--ink);
|
||||||
|
color: var(--white);
|
||||||
|
}
|
||||||
|
.tab .tab-emoji { display: block; font-size: 18px; margin-bottom: 2px }
|
||||||
|
|
||||||
|
/* ===== CHECKLIST ===== */
|
||||||
|
.checklist {
|
||||||
|
margin: 12px 16px 80px;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.checklist.active { display: block }
|
||||||
|
|
||||||
|
.check-item {
|
||||||
|
background: var(--white);
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 16px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
box-shadow: 0 1px 3px rgba(0,0,0,0.04);
|
||||||
|
transition: box-shadow 0.15s;
|
||||||
|
}
|
||||||
|
.check-item.selected { box-shadow: 0 0 0 2px var(--cyan); }
|
||||||
|
.check-item.has-violation { box-shadow: 0 0 0 1px rgba(255,77,77,0.3); }
|
||||||
|
|
||||||
|
.check-item .item-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
.check-item .item-num {
|
||||||
|
width: 26px;
|
||||||
|
height: 26px;
|
||||||
|
background: #f0f2f5;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--gray-500);
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-top: 1px;
|
||||||
|
}
|
||||||
|
.check-item .item-text { flex: 1; font-size: 14px; font-weight: 500 }
|
||||||
|
.check-item .item-note {
|
||||||
|
margin-top: 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--gray-500);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Status buttons */
|
||||||
|
.status-row {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
.status-btn {
|
||||||
|
flex: 1;
|
||||||
|
padding: 10px 8px;
|
||||||
|
border: 2px solid #e5e7eb;
|
||||||
|
border-radius: 10px;
|
||||||
|
background: var(--white);
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.15s;
|
||||||
|
font-family: inherit;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.status-btn:hover { border-color: #d1d5db }
|
||||||
|
.status-btn.pass.active {
|
||||||
|
border-color: var(--green);
|
||||||
|
background: var(--green-bg);
|
||||||
|
color: var(--green);
|
||||||
|
}
|
||||||
|
.status-btn.fail.active {
|
||||||
|
border-color: var(--red);
|
||||||
|
background: var(--red-bg);
|
||||||
|
color: var(--red);
|
||||||
|
}
|
||||||
|
.status-btn.na.active {
|
||||||
|
border-color: var(--gray-500);
|
||||||
|
background: #f0f2f5;
|
||||||
|
color: var(--gray-500);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Violation form */
|
||||||
|
.violation-form {
|
||||||
|
display: none;
|
||||||
|
margin-top: 12px;
|
||||||
|
padding-top: 12px;
|
||||||
|
border-top: 1px solid #f0f2f5;
|
||||||
|
}
|
||||||
|
.violation-form.show { display: block }
|
||||||
|
|
||||||
|
.violation-form .form-row {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
.violation-form label {
|
||||||
|
display: block;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--gray-500);
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
.violation-form textarea, .violation-form input, .violation-form select {
|
||||||
|
width: 100%;
|
||||||
|
border: 1px solid #e0e3e8;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 8px 12px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-family: inherit;
|
||||||
|
background: #fafbfc;
|
||||||
|
color: var(--ink);
|
||||||
|
resize: vertical;
|
||||||
|
}
|
||||||
|
.violation-form textarea:focus, .violation-form input:focus, .violation-form select:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--cyan);
|
||||||
|
}
|
||||||
|
.violation-form .photo-btn {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
padding: 8px 14px;
|
||||||
|
border: 1px dashed #d1d5db;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: #fafbfc;
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--gray-500);
|
||||||
|
cursor: pointer;
|
||||||
|
font-family: inherit;
|
||||||
|
}
|
||||||
|
.violation-form .auto-fill-btn {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
padding: 8px 14px;
|
||||||
|
border: 1px solid rgba(0,229,255,0.3);
|
||||||
|
border-radius: 8px;
|
||||||
|
background: rgba(0,229,255,0.06);
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--cyan);
|
||||||
|
cursor: pointer;
|
||||||
|
font-family: inherit;
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
.risk-options { display: flex; gap: 8px }
|
||||||
|
.risk-opt {
|
||||||
|
flex: 1;
|
||||||
|
padding: 8px;
|
||||||
|
border: 2px solid #e5e7eb;
|
||||||
|
border-radius: 8px;
|
||||||
|
text-align: center;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
font-family: inherit;
|
||||||
|
background: var(--white);
|
||||||
|
transition: all 0.15s;
|
||||||
|
}
|
||||||
|
.risk-opt.low.active { border-color: var(--green); background: var(--green-bg); color: var(--green) }
|
||||||
|
.risk-opt.mid.active { border-color: var(--amber); background: var(--amber-bg); color: var(--amber) }
|
||||||
|
.risk-opt.high.active { border-color: var(--red); background: var(--red-bg); color: var(--red) }
|
||||||
|
|
||||||
|
/* ===== BOTTOM BAR ===== */
|
||||||
|
.bottom-bar {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
background: var(--white);
|
||||||
|
border-top: 1px solid #e5e7eb;
|
||||||
|
padding: 12px 20px;
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
z-index: 100;
|
||||||
|
box-shadow: 0 -2px 10px rgba(0,0,0,0.05);
|
||||||
|
}
|
||||||
|
.bottom-bar .btn {
|
||||||
|
flex: 1;
|
||||||
|
padding: 14px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 10px;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 15px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-family: inherit;
|
||||||
|
transition: all 0.15s;
|
||||||
|
}
|
||||||
|
.bottom-bar .btn-preview {
|
||||||
|
background: var(--white);
|
||||||
|
border: 2px solid var(--ink);
|
||||||
|
color: var(--ink);
|
||||||
|
}
|
||||||
|
.bottom-bar .btn-generate {
|
||||||
|
background: var(--ink);
|
||||||
|
color: var(--white);
|
||||||
|
}
|
||||||
|
.bottom-bar .btn-generate:hover { background: #2a2d35 }
|
||||||
|
.bottom-bar .btn-preview:hover { background: #f0f2f5 }
|
||||||
|
|
||||||
|
/* ===== MODAL ===== */
|
||||||
|
.modal-overlay {
|
||||||
|
display: none;
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
background: rgba(0,0,0,0.6);
|
||||||
|
z-index: 200;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
.modal-overlay.show { display: flex }
|
||||||
|
.modal {
|
||||||
|
background: var(--white);
|
||||||
|
border-radius: 16px;
|
||||||
|
padding: 28px;
|
||||||
|
max-width: 700px;
|
||||||
|
width: 100%;
|
||||||
|
max-height: 85vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
.modal h3 { font-size: 20px; font-weight: 700; margin-bottom: 6px }
|
||||||
|
.modal .modal-meta { font-size: 13px; color: var(--gray-500); margin-bottom: 20px }
|
||||||
|
.modal table { width: 100%; border-collapse: collapse; font-size: 13px }
|
||||||
|
.modal th {
|
||||||
|
background: #f3f4f6;
|
||||||
|
padding: 8px 10px;
|
||||||
|
text-align: left;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 11px;
|
||||||
|
color: var(--gray-500);
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
.modal td {
|
||||||
|
padding: 8px 10px;
|
||||||
|
border-bottom: 1px solid #f3f4f6;
|
||||||
|
}
|
||||||
|
.modal .close-btn {
|
||||||
|
margin-top: 20px;
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px;
|
||||||
|
background: #f0f2f5;
|
||||||
|
border: none;
|
||||||
|
border-radius: 10px;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 15px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-family: inherit;
|
||||||
|
}
|
||||||
|
.risk-dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; margin-right: 4px; vertical-align: middle }
|
||||||
|
.risk-dot.red { background: var(--red) }
|
||||||
|
.risk-dot.amber { background: var(--amber) }
|
||||||
|
.modal .empty-msg { text-align: center; color: var(--gray-500); padding: 40px 0; font-size: 15px }
|
||||||
|
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
.checklist-container { max-width: 768px; margin: 0 auto }
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast {
|
||||||
|
position: fixed;
|
||||||
|
top: 80px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
background: var(--ink);
|
||||||
|
color: var(--white);
|
||||||
|
padding: 12px 24px;
|
||||||
|
border-radius: 10px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
z-index: 300;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.3s;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.toast.show { opacity: 1 }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div class="topbar">
|
||||||
|
<a href="index.html" class="back">+</a>
|
||||||
|
<span class="title">Новая проверка</span>
|
||||||
|
<a href="#" class="save" onclick="saveChecklist()">Сохранить</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="max-width:768px; margin:0 auto">
|
||||||
|
|
||||||
|
<!-- Header -->
|
||||||
|
<div class="header-card">
|
||||||
|
<div class="field">
|
||||||
|
<span class="label">Объект</span>
|
||||||
|
<select><option>Цех №3</option><option>Складской комплекс</option><option>Административный корпус</option><option>Строительная площадка</option></select>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<span class="label">Подразделение</span>
|
||||||
|
<select><option>Производственный участок</option><option>Ремонтная служба</option><option>Энергоцех</option><option>Транспортный цех</option></select>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<span class="label">Ответственный</span>
|
||||||
|
<input type="text" placeholder="ФИО ответственного лица">
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<span class="label">Направление</span>
|
||||||
|
<select id="mainDirection"><option>Комплексная проверка</option><option>Охрана труда и ТБ</option><option>Пожарная безопасность</option><option>Электробезопасность</option><option>Транспортная безопасность</option><option>Охрана здоровья</option><option>Выездная проверка</option></select>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<span class="label">Дата проверки</span>
|
||||||
|
<input type="date" value="2026-06-03">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Progress -->
|
||||||
|
<div class="progress-bar">
|
||||||
|
<span style="font-size:12px;color:var(--gray-500);white-space:nowrap">Пройдено:</span>
|
||||||
|
<div class="track"><div class="fill" id="progressFill" style="width:0%"></div></div>
|
||||||
|
<span class="pct" id="progressPct">0%</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Tabs -->
|
||||||
|
<div class="tabs" id="tabBar">
|
||||||
|
<button class="tab active" data-tab="1"><span class="tab-emoji">⚠</span>ОТ и ТБ</button>
|
||||||
|
<button class="tab" data-tab="2"><span class="tab-emoji">🔥</span>Пожарная</button>
|
||||||
|
<button class="tab" data-tab="3"><span class="tab-emoji">⚡</span>Электро</button>
|
||||||
|
<button class="tab" data-tab="4"><span class="tab-emoji">🛣</span>Транспорт</button>
|
||||||
|
<button class="tab" data-tab="5"><span class="tab-emoji">❤</span>Здоровье</button>
|
||||||
|
<button class="tab" data-tab="6"><span class="tab-emoji">🗺</span>Выездные</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Checklist sections -->
|
||||||
|
<div id="checklistContainer"></div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Bottom bar -->
|
||||||
|
<div class="bottom-bar">
|
||||||
|
<button class="btn btn-preview" onclick="showPreview()">Предпросмотр</button>
|
||||||
|
<button class="btn btn-generate" onclick="generateOrder()">Сформировать указание</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Modal -->
|
||||||
|
<div class="modal-overlay" id="modalOverlay" onclick="if(event.target===this)closeModal()">
|
||||||
|
<div class="modal" id="modalContent"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Toast -->
|
||||||
|
<div class="toast" id="toast"></div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// ===== CHECKLIST DATA =====
|
||||||
|
const checklistSections = {
|
||||||
|
1: {
|
||||||
|
title: 'Охрана труда и ТБ',
|
||||||
|
items: [
|
||||||
|
{id:'1.1', text:'Наличие защитных ограждений на вращающихся и движущихся механизмах', note:'Пункт 1.2 Правил ОТ'},
|
||||||
|
{id:'1.2', text:'Состояние проходов, проездов и эвакуационных путей', note:'Свободны, не загромождены'},
|
||||||
|
{id:'1.3', text:'Наличие знаков безопасности и сигнальной разметки', note:'ГОСТ 12.4.026'},
|
||||||
|
{id:'1.4', text:'Применение средств индивидуальной защиты работниками', note:'Каски, очки, перчатки, спецобувь'},
|
||||||
|
{id:'1.5', text:'Наличие ограждений при работе на высоте (выше 1.3м)', note:'Страховочные системы, перила'},
|
||||||
|
{id:'1.6', text:'Состояние лестниц, подмостей и средств подмащивания', note:'Исправность, бирки, даты испытаний'},
|
||||||
|
{id:'1.7', text:'Исправность и своевременное освидетельствование грузоподъемных механизмов', note:'Краны, тельферы, стропы'},
|
||||||
|
{id:'1.8', text:'Наличие нарядов-допусков на огневые, газоопасные и высотные работы', note:'Оформлены, подписаны, сроки'},
|
||||||
|
{id:'1.9', text:'Проведение инструктажей (вводный, первичный, повторный, внеплановый)', note:'Записи в журналах, подписи'},
|
||||||
|
{id:'1.10', text:'Наличие и ведение журналов по охране труда', note:'Журнал инструктажа, журнал выдачи СИЗ, журнал НС'}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
2: {
|
||||||
|
title: 'Пожарная безопасность',
|
||||||
|
items: [
|
||||||
|
{id:'2.1', text:'Наличие и состояние первичных средств пожаротушения (огнетушители)', note:'Не просрочены, опломбированы, доступны'},
|
||||||
|
{id:'2.2', text:'Доступность пожарных гидрантов и рукавов', note:'Не загромождены, укомплектованы'},
|
||||||
|
{id:'2.3', text:'Наличие планов эвакуации и указателей выходов', note:'Актуальные, освещённые, на видных местах'},
|
||||||
|
{id:'2.4', text:'Состояние эвакуационных выходов и путей', note:'Не заперты, свободны, освещены'},
|
||||||
|
{id:'2.5', text:'Исправность автоматической пожарной сигнализации и оповещения', note:'Датчики дыма, сирены, проверки'},
|
||||||
|
{id:'2.6', text:'Проведение противопожарных инструктажей', note:'Записи в журнале, подписи, периодичность'},
|
||||||
|
{id:'2.7', text:'Состояние электропроводки и электрооборудования', note:'Отсутствие скруток, повреждений изоляции'},
|
||||||
|
{id:'2.8', text:'Наличие мест для курения, оборудованных по нормам', note:'Урны, знаки, удалённость от строений'}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
3: {
|
||||||
|
title: 'Электробезопасность',
|
||||||
|
items: [
|
||||||
|
{id:'3.1', text:'Наличие защитного заземления и зануления оборудования', note:'Визуальная целостность, протоколы замеров'},
|
||||||
|
{id:'3.2', text:'Состояние распределительных щитов и шкафов', note:'Закрыты, промаркированы, чистота'},
|
||||||
|
{id:'3.3', text:'Наличие предупреждающих знаков и плакатов на электроустановках', note:'«Осторожно! Электрическое напряжение»'},
|
||||||
|
{id:'3.4', text:'Наличие и сроки испытания диэлектрических средств защиты', note:'Перчатки, боты, коврики, штампы'},
|
||||||
|
{id:'3.5', text:'Наличие у персонала группы допуска по электробезопасности', note:'Удостоверения, сроки, соответствие работам'},
|
||||||
|
{id:'3.6', text:'Состояние изоляции кабелей и проводов', note:'Отсутствие повреждений, провисаний, скруток'},
|
||||||
|
{id:'3.7', text:'Наличие однолинейных схем электроснабжения', note:'Актуальные, на рабочих местах'}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
4: {
|
||||||
|
title: 'Транспортная безопасность',
|
||||||
|
items: [
|
||||||
|
{id:'4.1', text:'Проведение предрейсовых медицинских осмотров водителей', note:'Журнал, подписи, штампы'},
|
||||||
|
{id:'4.2', text:'Техническое состояние транспортных средств перед выездом', note:'Тормоза, рулевое, фары, шины'},
|
||||||
|
{id:'4.3', text:'Наличие и оформление путевых листов', note:'Заполнены, отметки механика и медика'},
|
||||||
|
{id:'4.4', text:'Наличие допусков на управление спецтехникой', note:'Удостоверения, категории, сроки'},
|
||||||
|
{id:'4.5', text:'Состояние грузозахватных приспособлений на транспорте', note:'Стропы, цепи, крюки — бирки, испытания'},
|
||||||
|
{id:'4.6', text:'Проведение инструктажей водителей и механизаторов', note:'Записи, подписи, периодичность'}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
5: {
|
||||||
|
title: 'Охрана здоровья',
|
||||||
|
items: [
|
||||||
|
{id:'5.1', text:'Проведение периодических медицинских осмотров работников', note:'График, заключения, допуски'},
|
||||||
|
{id:'5.2', text:'Наличие и укомплектованность аптечек первой помощи', note:'Сроки годности, опись, доступность'},
|
||||||
|
{id:'5.3', text:'Санитарное состояние производственных и бытовых помещений', note:'Чистота, уборка, дезинфекция'},
|
||||||
|
{id:'5.4', text:'Наличие питьевой воды и условия для приёма пищи', note:'Куллеры, столовая/комната приёма пищи'},
|
||||||
|
{id:'5.5', text:'Состояние систем вентиляции и кондиционирования', note:'Работают, чистые, проверки'},
|
||||||
|
{id:'5.6', text:'Освещённость рабочих мест', note:'Нормы, исправность светильников, замеры'},
|
||||||
|
{id:'5.7', text:'Параметры микроклимата на рабочих местах', note:'Температура, влажность, сквозняки'}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
6: {
|
||||||
|
title: 'Выездные проверки',
|
||||||
|
items: [
|
||||||
|
{id:'6.1', text:'Состояние мест производства работ на выездном объекте', note:'Ограждения, порядок, безопасность'},
|
||||||
|
{id:'6.2', text:'Наличие ограждений опасных зон на выездных объектах', note:'Котлованы, проёмы, высотные участки'},
|
||||||
|
{id:'6.3', text:'Безопасность складирования материалов и конструкций', note:'Устойчивость, проходы, высота штабелей'},
|
||||||
|
{id:'6.4', text:'Наличие первичных средств пожаротушения на выездном объекте', note:'Огнетушители, ящики с песком'},
|
||||||
|
{id:'6.5', text:'Соблюдение технологии и проекта производства работ', note:'ППР на месте, соответствие выполняемых работ'},
|
||||||
|
{id:'6.6', text:'Фиксация GPS-координат места проверки', note:'Широта/долгота, фото объекта'}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// ===== STATE =====
|
||||||
|
const state = {};
|
||||||
|
Object.keys(checklistSections).forEach(sec => {
|
||||||
|
checklistSections[sec].items.forEach(item => {
|
||||||
|
state[item.id] = { status: null, description:'', photo:'', risk:'', deadline:'', requirement:'', measure:'' };
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
let currentTab = 1;
|
||||||
|
|
||||||
|
// ===== RENDER =====
|
||||||
|
function renderChecklist(sectionId) {
|
||||||
|
const sec = checklistSections[sectionId];
|
||||||
|
let html = '';
|
||||||
|
sec.items.forEach((item, idx) => {
|
||||||
|
const st = state[item.id];
|
||||||
|
const hasViolation = st.status === 'fail';
|
||||||
|
const selected = st.status !== null;
|
||||||
|
html += `
|
||||||
|
<div class="check-item ${selected ? 'selected' : ''} ${hasViolation ? 'has-violation' : ''}" id="item-${item.id}">
|
||||||
|
<div class="item-header">
|
||||||
|
<div class="item-num">${idx + 1}</div>
|
||||||
|
<div>
|
||||||
|
<div class="item-text">${item.text}</div>
|
||||||
|
${item.note ? `<div class="item-note">${item.note}</div>` : ''}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="status-row">
|
||||||
|
<button class="status-btn pass ${st.status==='pass'?'active':''}" onclick="setStatus('${item.id}','pass')">Соответствует</button>
|
||||||
|
<button class="status-btn fail ${st.status==='fail'?'active':''}" onclick="setStatus('${item.id}','fail')">Не соответствует</button>
|
||||||
|
<button class="status-btn na ${st.status==='na'?'active':''}" onclick="setStatus('${item.id}','na')">Не применяется</button>
|
||||||
|
</div>
|
||||||
|
<div class="violation-form ${hasViolation ? 'show' : ''}" id="form-${item.id}">
|
||||||
|
<div class="form-row">
|
||||||
|
<label>Описание нарушения</label>
|
||||||
|
<textarea rows="2" placeholder="Опишите нарушение..." onchange="updateField('${item.id}','description',this.value)">${st.description}</textarea>
|
||||||
|
</div>
|
||||||
|
<div class="form-row">
|
||||||
|
<label>Фото нарушения</label>
|
||||||
|
<button class="photo-btn" onclick="alert('В боевой версии: камера / галерея')">+ Прикрепить фото</button>
|
||||||
|
</div>
|
||||||
|
<div class="form-row">
|
||||||
|
<label>Категория риска</label>
|
||||||
|
<div class="risk-options">
|
||||||
|
<div class="risk-opt low ${st.risk==='low'?'active':''}" onclick="setRisk('${item.id}','low',this)">Низкий</div>
|
||||||
|
<div class="risk-opt mid ${st.risk==='mid'?'active':''}" onclick="setRisk('${item.id}','mid',this)">Средний</div>
|
||||||
|
<div class="risk-opt high ${st.risk==='high'?'active':''}" onclick="setRisk('${item.id}','high',this)">Высокий</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-row">
|
||||||
|
<label>Срок устранения</label>
|
||||||
|
<input type="date" value="${st.deadline}" onchange="updateField('${item.id}','deadline',this.value)">
|
||||||
|
</div>
|
||||||
|
<div class="form-row">
|
||||||
|
<label>Ответственное лицо</label>
|
||||||
|
<input type="text" placeholder="Должность и ФИО" value="${st.measure}" onchange="updateField('${item.id}','measure',this.value)">
|
||||||
|
</div>
|
||||||
|
<button class="auto-fill-btn" onclick="autoFillViolation('${item.id}','${item.text}')">+ ИИ: автозаполнение</button>
|
||||||
|
</div>
|
||||||
|
</div>`;
|
||||||
|
});
|
||||||
|
document.getElementById('checklistContainer').innerHTML = html;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setStatus(id, status) {
|
||||||
|
if (state[id].status === status) {
|
||||||
|
state[id].status = null;
|
||||||
|
} else {
|
||||||
|
state[id].status = status;
|
||||||
|
}
|
||||||
|
if (status !== 'fail') {
|
||||||
|
state[id].description = '';
|
||||||
|
state[id].risk = '';
|
||||||
|
state[id].deadline = '';
|
||||||
|
state[id].measure = '';
|
||||||
|
}
|
||||||
|
renderChecklist(currentTab);
|
||||||
|
updateProgress();
|
||||||
|
}
|
||||||
|
|
||||||
|
function setRisk(id, risk, el) {
|
||||||
|
state[id].risk = risk;
|
||||||
|
renderChecklist(currentTab);
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateField(id, field, val) {
|
||||||
|
state[id][field] = val;
|
||||||
|
}
|
||||||
|
|
||||||
|
function autoFillViolation(id, itemText) {
|
||||||
|
const autoData = getAutoViolation(itemText);
|
||||||
|
state[id].description = autoData.description;
|
||||||
|
state[id].requirement = autoData.requirement;
|
||||||
|
state[id].measure = autoData.measure;
|
||||||
|
state[id].risk = autoData.risk;
|
||||||
|
state[id].deadline = autoData.deadline;
|
||||||
|
renderChecklist(currentTab);
|
||||||
|
showToast('+ ИИ: запись сформирована');
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAutoViolation(text) {
|
||||||
|
// Simulated AI auto-fill based on checklist item
|
||||||
|
const map = {
|
||||||
|
'ограждений': { description:'Отсутствует защитное ограждение вращающихся/движущихся механизмов', requirement:'Установить защитное ограждение согласно требованиям правил ОТ', measure:'Начальник участка', risk:'high', deadline:'2026-06-15' },
|
||||||
|
'СИЗ': { description:'Работники не применяют средства индивидуальной защиты', requirement:'Обеспечить применение СИЗ согласно нормам выдачи', measure:'Мастер участка', risk:'mid', deadline:'2026-06-10' },
|
||||||
|
'высоте': { description:'Работы на высоте выполняются без страховочной системы', requirement:'Выполнять работы на высоте только с применением страховочной привязи', measure:'Начальник участка', risk:'high', deadline:'2026-06-12' },
|
||||||
|
'огнетушител': { description:'Огнетушитель не прошёл своевременную перезарядку', requirement:'Выполнить перезарядку огнетушителя', measure:'Руководитель объекта', risk:'mid', deadline:'2026-06-10' },
|
||||||
|
'эвакуац': { description:'Эвакуационные выходы загромождены / заперты', requirement:'Обеспечить свободный доступ к эвакуационным выходам', measure:'Руководитель объекта', risk:'high', deadline:'2026-06-08' },
|
||||||
|
'заземл': { description:'Отсутствует или нарушено защитное заземление оборудования', requirement:'Восстановить защитное заземление согласно ПУЭ', measure:'Главный энергетик', risk:'high', deadline:'2026-06-14' },
|
||||||
|
'проводк': { description:'Выявлены повреждения изоляции электропроводки', requirement:'Заменить повреждённые участки электропроводки', measure:'Электрик участка', risk:'high', deadline:'2026-06-11' },
|
||||||
|
'медосмотр': { description:'У работников отсутствуют отметки о прохождении медосмотра', requirement:'Организовать прохождение медицинского осмотра', measure:'Специалист по ОТ', risk:'mid', deadline:'2026-06-20' },
|
||||||
|
'путев': { description:'Путевые листы оформлены с нарушениями / отсутствуют', requirement:'Обеспечить правильное оформление путевых листов', measure:'Механик гаража', risk:'low', deadline:'2026-06-09' },
|
||||||
|
'инструктаж': { description:'Пропущен срок проведения инструктажа', requirement:'Провести внеплановый инструктаж с записью в журнале', measure:'Мастер участка', risk:'mid', deadline:'2026-06-07' },
|
||||||
|
'знаков': { description:'Отсутствуют знаки безопасности в установленных местах', requirement:'Установить знаки безопасности согласно ГОСТ', measure:'Начальник участка', risk:'mid', deadline:'2026-06-17' },
|
||||||
|
'аптеч': { description:'Аптечка не укомплектована / просрочены медикаменты', requirement:'Укомплектовать аптечку согласно нормам', measure:'Специалист по ОТ', risk:'low', deadline:'2026-06-10' },
|
||||||
|
'вентиляц': { description:'Система вентиляции не обеспечивает нормативный воздухообмен', requirement:'Провести ремонт/очистку системы вентиляции', measure:'Главный механик', risk:'mid', deadline:'2026-06-21' },
|
||||||
|
'GPS': { description:'Не зафиксированы GPS-координаты места проверки', requirement:'Включить геолокацию и зафиксировать координаты', measure:'Инспектор', risk:'low', deadline:'2026-06-04' }
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const [key, val] of Object.entries(map)) {
|
||||||
|
if (text.toLowerCase().includes(key.toLowerCase())) return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { description:'Нарушение требований безопасности', requirement:'Устранить нарушение в соответствии с нормативными требованиями', measure:'Ответственный руководитель', risk:'mid', deadline:'2026-06-17' };
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== TAB SWITCHING =====
|
||||||
|
document.getElementById('tabBar').addEventListener('click', function(e) {
|
||||||
|
const tab = e.target.closest('.tab');
|
||||||
|
if (!tab) return;
|
||||||
|
currentTab = parseInt(tab.dataset.tab);
|
||||||
|
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
|
||||||
|
tab.classList.add('active');
|
||||||
|
renderChecklist(currentTab);
|
||||||
|
});
|
||||||
|
|
||||||
|
// ===== PROGRESS =====
|
||||||
|
function updateProgress() {
|
||||||
|
let total = 0;
|
||||||
|
let checked = 0;
|
||||||
|
Object.keys(checklistSections).forEach(sec => {
|
||||||
|
checklistSections[sec].items.forEach(item => {
|
||||||
|
total++;
|
||||||
|
if (state[item.id].status !== null) checked++;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
const pct = total > 0 ? Math.round(checked / total * 100) : 0;
|
||||||
|
document.getElementById('progressFill').style.width = pct + '%';
|
||||||
|
document.getElementById('progressPct').textContent = pct + '%';
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== GET VIOLATIONS =====
|
||||||
|
function getViolations() {
|
||||||
|
const violations = [];
|
||||||
|
Object.keys(checklistSections).forEach(sec => {
|
||||||
|
checklistSections[sec].items.forEach(item => {
|
||||||
|
const st = state[item.id];
|
||||||
|
if (st.status === 'fail') {
|
||||||
|
const riskLabel = {low:'Низкий', mid:'Средний', high:'Высокий'};
|
||||||
|
violations.push({
|
||||||
|
id: item.id,
|
||||||
|
num: violations.length + 1,
|
||||||
|
section: checklistSections[sec].title,
|
||||||
|
text: item.text,
|
||||||
|
description: st.description || item.text,
|
||||||
|
risk: st.risk || 'mid',
|
||||||
|
riskLabel: riskLabel[st.risk] || 'Средний',
|
||||||
|
deadline: st.deadline || '—',
|
||||||
|
responsible: st.measure || '—',
|
||||||
|
measure: (st.description || item.text).length > 80 ? (st.description || item.text) : 'Устранить нарушение согласно нормативным требованиям'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return violations;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== PREVIEW =====
|
||||||
|
function showPreview() {
|
||||||
|
const violations = getViolations();
|
||||||
|
const objName = document.querySelector('.header-card select').value;
|
||||||
|
const dept = document.querySelectorAll('.header-card select')[1].value;
|
||||||
|
const date = document.querySelector('.header-card input[type="date"]').value;
|
||||||
|
|
||||||
|
let html = `<h3>Предпросмотр нарушений</h3>`;
|
||||||
|
html += `<div class="modal-meta">Объект: ${objName} · Подразделение: ${dept} · Дата: ${date}</div>`;
|
||||||
|
|
||||||
|
if (violations.length === 0) {
|
||||||
|
html += `<div class="empty-msg">Нарушений не выявлено</div>`;
|
||||||
|
} else {
|
||||||
|
html += `<table>
|
||||||
|
<tr><th>№</th><th>Нарушение</th><th>Риск</th><th>Срок</th><th>Ответственный</th></tr>`;
|
||||||
|
violations.forEach(v => {
|
||||||
|
html += `<tr>
|
||||||
|
<td>${v.num}</td>
|
||||||
|
<td>${v.description.length > 80 ? v.description.substring(0,80)+'...' : v.description}</td>
|
||||||
|
<td><span class="risk-dot ${v.risk==='high'?'red':v.risk==='mid'?'amber':''}"></span>${v.riskLabel}</td>
|
||||||
|
<td>${v.deadline}</td>
|
||||||
|
<td>${v.responsible}</td>
|
||||||
|
</tr>`;
|
||||||
|
});
|
||||||
|
html += `</table>`;
|
||||||
|
}
|
||||||
|
html += `<button class="close-btn" onclick="closeModal()">Закрыть</button>`;
|
||||||
|
document.getElementById('modalContent').innerHTML = html;
|
||||||
|
document.getElementById('modalOverlay').classList.add('show');
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateOrder() {
|
||||||
|
const violations = getViolations();
|
||||||
|
if (violations.length === 0) {
|
||||||
|
showToast('+ Нет нарушений для формирования указания');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const objName = document.querySelector('.header-card select').value;
|
||||||
|
const dept = document.querySelectorAll('.header-card select')[1].value;
|
||||||
|
const date = document.querySelector('.header-card input[type="date"]').value;
|
||||||
|
const responsible = document.querySelector('.header-card input[type="text"]').value || '—';
|
||||||
|
|
||||||
|
let html = `<h3>Указание по безопасности и охране труда</h3>`;
|
||||||
|
html += `<div class="modal-meta">
|
||||||
|
Номер: ${new Date().getTime().toString().slice(-6)} ·
|
||||||
|
Объект: ${objName} ·
|
||||||
|
Подразделение: ${dept} ·
|
||||||
|
Дата: ${date} ·
|
||||||
|
Проверяющий: ${responsible}
|
||||||
|
</div>`;
|
||||||
|
html += `<table>
|
||||||
|
<tr><th>№</th><th>Выявленное нарушение</th><th>Корректирующее мероприятие</th><th>Ответственный</th><th>Срок</th></tr>`;
|
||||||
|
violations.forEach(v => {
|
||||||
|
html += `<tr>
|
||||||
|
<td>${v.num}</td>
|
||||||
|
<td>${v.description}</td>
|
||||||
|
<td>${v.measure}</td>
|
||||||
|
<td>${v.responsible}</td>
|
||||||
|
<td>${v.deadline}</td>
|
||||||
|
</tr>`;
|
||||||
|
});
|
||||||
|
html += `</table>
|
||||||
|
<div style="display:flex;justify-content:space-between;margin-top:16px;padding-top:12px;border-top:1px solid #e5e7eb;font-size:13px;color:var(--gray-500)">
|
||||||
|
<span>Подпись проверяющего: ____________</span>
|
||||||
|
<span>Подпись руководителя: ____________</span>
|
||||||
|
</div>
|
||||||
|
<div style="text-align:right;margin-top:12px;font-size:11px;color:var(--gray-500)">
|
||||||
|
<span style="background:var(--ink);color:var(--white);padding:4px 8px;border-radius:4px;font-family:monospace">QR-код</span>
|
||||||
|
Проверка подлинности
|
||||||
|
</div>`;
|
||||||
|
html += `<button class="close-btn" onclick="closeModal()">Закрыть</button>`;
|
||||||
|
html += `<button class="btn" style="width:100%;margin-top:8px;background:var(--cyan);color:var(--ink);border:none;padding:12px;border-radius:10px;font-weight:700;cursor:pointer;font-family:inherit" onclick="alert('В боевой версии: экспорт в PDF / Word')">Скачать PDF</button>`;
|
||||||
|
|
||||||
|
document.getElementById('modalContent').innerHTML = html;
|
||||||
|
document.getElementById('modalOverlay').classList.add('show');
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeModal() {
|
||||||
|
document.getElementById('modalOverlay').classList.remove('show');
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveChecklist() {
|
||||||
|
showToast('+ Чек-лист сохранён');
|
||||||
|
}
|
||||||
|
|
||||||
|
function showToast(msg) {
|
||||||
|
const t = document.getElementById('toast');
|
||||||
|
t.textContent = msg;
|
||||||
|
t.classList.add('show');
|
||||||
|
setTimeout(() => t.classList.remove('show'), 2000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== INIT =====
|
||||||
|
renderChecklist(1);
|
||||||
|
updateProgress();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@ -509,7 +509,7 @@ body {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="cta-row">
|
<div class="cta-row">
|
||||||
<a href="#waitlist" class="btn">Оставить заявку</a>
|
<a href="checklist.html" class="btn">Открыть чек-лист</a>
|
||||||
<a href="#process" class="btn-outline">Как работает</a>
|
<a href="#process" class="btn-outline">Как работает</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user