1591 lines
56 KiB
HTML
1591 lines
56 KiB
HTML
<!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;
|
||
--blue: #2563EB;
|
||
--blue-50: #EFF6FF;
|
||
--blue-100: #DBEAFE;
|
||
--green: #059669;
|
||
--green-50: #ECFDF5;
|
||
--red: #DC2626;
|
||
--red-50: #FEF2F2;
|
||
--amber: #D97706;
|
||
--amber-50: #FFFBEB;
|
||
--white: #FFFFFF;
|
||
--gray-50: #F9FAFB;
|
||
--gray-100: #F2F4F7;
|
||
--gray-200: #E5E7EB;
|
||
--gray-500: #5B6573;
|
||
--gray-600: #4B5563;
|
||
}
|
||
|
||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||
|
||
body {
|
||
font: 17px/1.6 -apple-system, BlinkMacSystemFont, "Segoe UI", Inter, system-ui, sans-serif;
|
||
color: var(--ink);
|
||
background: var(--white);
|
||
}
|
||
|
||
.container {
|
||
max-width: 1140px;
|
||
margin: 0 auto;
|
||
padding: 0 24px;
|
||
}
|
||
|
||
/* Hero */
|
||
.hero {
|
||
background: linear-gradient(135deg, #2563EB 0%, #1D4ED8 100%);
|
||
color: var(--white);
|
||
padding: 100px 0 80px;
|
||
}
|
||
|
||
.hero-badge {
|
||
display: inline-block;
|
||
background: rgba(255,255,255,0.15);
|
||
color: var(--white);
|
||
padding: 6px 16px;
|
||
border-radius: 100px;
|
||
font-size: 14px;
|
||
font-weight: 600;
|
||
margin-bottom: 24px;
|
||
backdrop-filter: blur(10px);
|
||
}
|
||
|
||
.hero h1 {
|
||
font-size: 48px;
|
||
font-weight: 800;
|
||
line-height: 1.1;
|
||
max-width: 700px;
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.hero p {
|
||
font-size: 19px;
|
||
color: rgba(255,255,255,0.8);
|
||
max-width: 560px;
|
||
margin-bottom: 36px;
|
||
line-height: 1.6;
|
||
}
|
||
|
||
.hero-actions {
|
||
display: flex;
|
||
gap: 12px;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.btn {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
padding: 14px 28px;
|
||
border-radius: 8px;
|
||
font-weight: 700;
|
||
font-size: 15px;
|
||
text-decoration: none;
|
||
transition: transform 0.15s, box-shadow 0.15s;
|
||
cursor: pointer;
|
||
border: none;
|
||
font-family: inherit;
|
||
}
|
||
|
||
.btn:hover {
|
||
transform: translateY(-1px);
|
||
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
|
||
}
|
||
|
||
.btn-primary {
|
||
background: var(--white);
|
||
color: #1D4ED8;
|
||
}
|
||
|
||
.btn-outline {
|
||
background: transparent;
|
||
color: var(--white);
|
||
border: 2px solid rgba(255,255,255,0.4);
|
||
}
|
||
|
||
.btn-outline:hover {
|
||
border-color: var(--white);
|
||
}
|
||
|
||
.btn-blue {
|
||
background: var(--blue);
|
||
color: var(--white);
|
||
}
|
||
|
||
.btn-blue:hover {
|
||
background: #1D4ED8;
|
||
}
|
||
|
||
.btn-sm {
|
||
padding: 8px 18px;
|
||
font-size: 13px;
|
||
font-weight: 600;
|
||
}
|
||
|
||
/* Section */
|
||
.section {
|
||
padding: 80px 0;
|
||
}
|
||
|
||
.section-label {
|
||
display: inline-block;
|
||
background: var(--blue-50);
|
||
color: var(--blue);
|
||
padding: 4px 14px;
|
||
border-radius: 100px;
|
||
font-size: 13px;
|
||
font-weight: 700;
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.5px;
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
.section h2 {
|
||
font-size: 36px;
|
||
font-weight: 700;
|
||
margin-bottom: 12px;
|
||
line-height: 1.2;
|
||
}
|
||
|
||
.section-subtitle {
|
||
font-size: 17px;
|
||
color: var(--gray-500);
|
||
max-width: 600px;
|
||
margin-bottom: 40px;
|
||
}
|
||
|
||
/* Quick Links */
|
||
.quick-links {
|
||
background: var(--gray-50);
|
||
}
|
||
|
||
.quick-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||
gap: 20px;
|
||
}
|
||
|
||
.quick-card {
|
||
background: var(--white);
|
||
border: 1px solid var(--gray-200);
|
||
border-radius: 12px;
|
||
padding: 28px;
|
||
text-decoration: none;
|
||
color: var(--ink);
|
||
transition: border-color 0.2s, box-shadow 0.2s;
|
||
display: flex;
|
||
align-items: flex-start;
|
||
gap: 16px;
|
||
}
|
||
|
||
.quick-card:hover {
|
||
border-color: var(--blue);
|
||
box-shadow: 0 4px 16px rgba(37,99,235,0.08);
|
||
}
|
||
|
||
.quick-card-icon {
|
||
font-size: 28px;
|
||
flex-shrink: 0;
|
||
width: 48px;
|
||
height: 48px;
|
||
border-radius: 10px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.quick-card-icon.blue { background: var(--blue-50); }
|
||
.quick-card-icon.green { background: var(--green-50); }
|
||
.quick-card-icon.red { background: var(--red-50); }
|
||
|
||
.quick-card h3 {
|
||
font-size: 17px;
|
||
font-weight: 700;
|
||
margin-bottom: 4px;
|
||
}
|
||
|
||
.quick-card p {
|
||
font-size: 14px;
|
||
color: var(--gray-500);
|
||
}
|
||
|
||
/* Blocks */
|
||
.blocks-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
|
||
gap: 20px;
|
||
}
|
||
|
||
.block-card {
|
||
background: var(--white);
|
||
border: 1px solid var(--gray-200);
|
||
border-radius: 12px;
|
||
padding: 32px;
|
||
transition: border-color 0.2s;
|
||
}
|
||
|
||
.block-card:hover {
|
||
border-color: var(--blue-100);
|
||
}
|
||
|
||
.block-number {
|
||
font-size: 13px;
|
||
font-weight: 700;
|
||
color: var(--blue);
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.5px;
|
||
margin-bottom: 12px;
|
||
}
|
||
|
||
.block-card h3 {
|
||
font-size: 20px;
|
||
font-weight: 700;
|
||
margin-bottom: 12px;
|
||
}
|
||
|
||
.block-card ul {
|
||
list-style: none;
|
||
}
|
||
|
||
.block-card li {
|
||
font-size: 15px;
|
||
color: var(--gray-600);
|
||
padding: 6px 0;
|
||
padding-left: 20px;
|
||
position: relative;
|
||
}
|
||
|
||
.block-card li::before {
|
||
content: '';
|
||
position: absolute;
|
||
left: 0;
|
||
top: 13px;
|
||
width: 6px;
|
||
height: 6px;
|
||
border-radius: 50%;
|
||
background: var(--blue);
|
||
}
|
||
|
||
/* Steps */
|
||
.steps {
|
||
display: grid;
|
||
gap: 16px;
|
||
counter-reset: step;
|
||
}
|
||
|
||
.step {
|
||
display: flex;
|
||
gap: 20px;
|
||
align-items: flex-start;
|
||
}
|
||
|
||
.step-num {
|
||
width: 44px;
|
||
height: 44px;
|
||
background: var(--blue);
|
||
color: var(--white);
|
||
border-radius: 12px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-weight: 800;
|
||
font-size: 18px;
|
||
flex-shrink: 0;
|
||
counter-increment: step;
|
||
}
|
||
|
||
.step-num::before {
|
||
content: counter(step);
|
||
}
|
||
|
||
.step-text h3 {
|
||
font-size: 17px;
|
||
font-weight: 700;
|
||
margin-bottom: 4px;
|
||
}
|
||
|
||
.step-text p {
|
||
font-size: 15px;
|
||
color: var(--gray-500);
|
||
}
|
||
|
||
/* Categories */
|
||
.cat-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
|
||
gap: 12px;
|
||
}
|
||
|
||
.cat-tag {
|
||
background: var(--gray-50);
|
||
border: 1px solid var(--gray-200);
|
||
border-radius: 8px;
|
||
padding: 12px 16px;
|
||
font-size: 14px;
|
||
font-weight: 600;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
|
||
.cat-dot {
|
||
width: 10px;
|
||
height: 10px;
|
||
border-radius: 50%;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.cat-dot.d1 { background: #2563EB; }
|
||
.cat-dot.d2 { background: #7C3AED; }
|
||
.cat-dot.d3 { background: #DC2626; }
|
||
.cat-dot.d4 { background: #EA580C; }
|
||
.cat-dot.d5 { background: #D97706; }
|
||
.cat-dot.d6 { background: #059669; }
|
||
.cat-dot.d7 { background: #0891B2; }
|
||
.cat-dot.d8 { background: #4F46E5; }
|
||
.cat-dot.d9 { background: #DB2777; }
|
||
|
||
/* Dashboard Preview */
|
||
.dash-preview {
|
||
background: var(--gray-50);
|
||
border: 1px solid var(--gray-200);
|
||
border-radius: 16px;
|
||
padding: 40px;
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||
gap: 24px;
|
||
}
|
||
|
||
.dash-metric {
|
||
text-align: center;
|
||
padding: 20px;
|
||
background: var(--white);
|
||
border-radius: 10px;
|
||
}
|
||
|
||
.dash-metric .value {
|
||
font-size: 36px;
|
||
font-weight: 800;
|
||
color: var(--blue);
|
||
line-height: 1;
|
||
margin-bottom: 6px;
|
||
}
|
||
|
||
.dash-metric .label {
|
||
font-size: 14px;
|
||
color: var(--gray-500);
|
||
}
|
||
|
||
.dash-chart {
|
||
grid-column: 1 / -1;
|
||
background: var(--white);
|
||
border-radius: 10px;
|
||
padding: 24px;
|
||
min-height: 200px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.chart-bars {
|
||
display: flex;
|
||
align-items: flex-end;
|
||
gap: 16px;
|
||
height: 160px;
|
||
}
|
||
|
||
.chart-bar-wrapper {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
|
||
.chart-bar {
|
||
width: 40px;
|
||
border-radius: 6px 6px 0 0;
|
||
transition: height 0.3s;
|
||
}
|
||
|
||
.chart-bar-label {
|
||
font-size: 11px;
|
||
color: var(--gray-500);
|
||
font-weight: 600;
|
||
max-width: 60px;
|
||
text-align: center;
|
||
line-height: 1.2;
|
||
}
|
||
|
||
/* CTA */
|
||
.cta {
|
||
background: linear-gradient(135deg, #1D4ED8 0%, #1E40AF 100%);
|
||
color: var(--white);
|
||
text-align: center;
|
||
padding: 80px 0;
|
||
}
|
||
|
||
.cta h2 {
|
||
font-size: 32px;
|
||
font-weight: 700;
|
||
margin-bottom: 12px;
|
||
}
|
||
|
||
.cta p {
|
||
color: rgba(255,255,255,0.7);
|
||
margin-bottom: 28px;
|
||
max-width: 480px;
|
||
margin-left: auto;
|
||
margin-right: auto;
|
||
}
|
||
|
||
.cta .btn {
|
||
background: var(--white);
|
||
color: #1D4ED8;
|
||
}
|
||
|
||
/* Footer */
|
||
.footer {
|
||
background: var(--gray-50);
|
||
border-top: 1px solid var(--gray-200);
|
||
padding: 32px 0;
|
||
text-align: center;
|
||
}
|
||
|
||
.footer p {
|
||
font-size: 14px;
|
||
color: var(--gray-500);
|
||
}
|
||
|
||
/* ===================================== */
|
||
/* ANALYZER */
|
||
/* ===================================== */
|
||
|
||
.analyzer {
|
||
background: var(--gray-50);
|
||
}
|
||
|
||
.upload-area {
|
||
background: var(--white);
|
||
border: 2px dashed var(--gray-200);
|
||
border-radius: 16px;
|
||
padding: 48px 24px;
|
||
text-align: center;
|
||
transition: border-color 0.2s, background 0.2s;
|
||
cursor: pointer;
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
.upload-area:hover,
|
||
.upload-area.drag-over {
|
||
border-color: var(--blue);
|
||
background: var(--blue-50);
|
||
}
|
||
|
||
.upload-area .upload-icon {
|
||
font-size: 40px;
|
||
margin-bottom: 12px;
|
||
}
|
||
|
||
.upload-area p {
|
||
color: var(--gray-500);
|
||
font-size: 15px;
|
||
}
|
||
|
||
.upload-area .upload-hint {
|
||
font-size: 13px;
|
||
color: var(--gray-500);
|
||
margin-top: 4px;
|
||
}
|
||
|
||
.upload-actions {
|
||
display: flex;
|
||
gap: 12px;
|
||
flex-wrap: wrap;
|
||
align-items: center;
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.upload-status {
|
||
font-size: 14px;
|
||
color: var(--gray-500);
|
||
margin-left: 12px;
|
||
}
|
||
|
||
/* Results */
|
||
#results {
|
||
display: none;
|
||
}
|
||
|
||
#results.active {
|
||
display: block;
|
||
}
|
||
|
||
.result-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
||
gap: 16px;
|
||
margin-bottom: 32px;
|
||
}
|
||
|
||
.result-metric {
|
||
background: var(--white);
|
||
border: 1px solid var(--gray-200);
|
||
border-radius: 12px;
|
||
padding: 24px;
|
||
text-align: center;
|
||
}
|
||
|
||
.result-metric .value {
|
||
font-size: 32px;
|
||
font-weight: 800;
|
||
color: var(--blue);
|
||
line-height: 1;
|
||
margin-bottom: 6px;
|
||
}
|
||
|
||
.result-metric .label {
|
||
font-size: 13px;
|
||
color: var(--gray-500);
|
||
line-height: 1.3;
|
||
}
|
||
|
||
.result-metric.warn .value { color: var(--red); }
|
||
.result-metric.good .value { color: var(--green); }
|
||
|
||
/* Charts */
|
||
.result-charts {
|
||
display: grid;
|
||
grid-template-columns: 1fr 1fr;
|
||
gap: 24px;
|
||
margin-bottom: 32px;
|
||
}
|
||
|
||
.result-chart-card {
|
||
background: var(--white);
|
||
border: 1px solid var(--gray-200);
|
||
border-radius: 12px;
|
||
padding: 28px;
|
||
}
|
||
|
||
.result-chart-card h3 {
|
||
font-size: 17px;
|
||
font-weight: 700;
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.result-chart-card.full {
|
||
grid-column: 1 / -1;
|
||
}
|
||
|
||
/* Bar chart list */
|
||
.bar-list-item {
|
||
margin-bottom: 14px;
|
||
}
|
||
|
||
.bar-list-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
font-size: 14px;
|
||
margin-bottom: 4px;
|
||
}
|
||
|
||
.bar-list-header .name {
|
||
font-weight: 600;
|
||
}
|
||
|
||
.bar-list-header .count {
|
||
color: var(--gray-500);
|
||
}
|
||
|
||
.bar-list-track {
|
||
height: 8px;
|
||
background: var(--gray-100);
|
||
border-radius: 4px;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.bar-list-fill {
|
||
height: 100%;
|
||
border-radius: 4px;
|
||
background: var(--blue);
|
||
transition: width 0.4s ease;
|
||
}
|
||
|
||
.bar-list-fill.warn { background: var(--red); }
|
||
.bar-list-fill.good { background: var(--green); }
|
||
|
||
/* Table */
|
||
.result-table-wrap {
|
||
overflow-x: auto;
|
||
margin-bottom: 32px;
|
||
}
|
||
|
||
.result-table {
|
||
width: 100%;
|
||
border-collapse: collapse;
|
||
font-size: 14px;
|
||
background: var(--white);
|
||
border-radius: 12px;
|
||
overflow: hidden;
|
||
border: 1px solid var(--gray-200);
|
||
}
|
||
|
||
.result-table th {
|
||
background: var(--gray-50);
|
||
padding: 12px 16px;
|
||
text-align: left;
|
||
font-weight: 700;
|
||
font-size: 13px;
|
||
color: var(--gray-500);
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.3px;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.result-table td {
|
||
padding: 10px 16px;
|
||
border-top: 1px solid var(--gray-100);
|
||
}
|
||
|
||
.result-table tbody tr:hover {
|
||
background: var(--gray-50);
|
||
}
|
||
|
||
.section-tabs {
|
||
display: flex;
|
||
gap: 8px;
|
||
flex-wrap: wrap;
|
||
margin-bottom: 32px;
|
||
margin-top: 40px;
|
||
}
|
||
|
||
.tab-btn {
|
||
padding: 10px 20px;
|
||
border: 1px solid var(--gray-200);
|
||
border-radius: 8px;
|
||
background: var(--white);
|
||
font-size: 14px;
|
||
font-weight: 600;
|
||
cursor: pointer;
|
||
transition: all 0.15s;
|
||
color: var(--ink);
|
||
font-family: inherit;
|
||
}
|
||
|
||
.tab-btn:hover {
|
||
border-color: var(--blue);
|
||
color: var(--blue);
|
||
}
|
||
|
||
.tab-btn.active {
|
||
background: var(--blue);
|
||
color: var(--white);
|
||
border-color: var(--blue);
|
||
}
|
||
|
||
.tab-content {
|
||
display: none;
|
||
}
|
||
|
||
.tab-content.active {
|
||
display: block;
|
||
}
|
||
|
||
/* Conclusions */
|
||
.conclusion-box {
|
||
background: var(--blue-50);
|
||
border: 1px solid var(--blue-100);
|
||
border-radius: 12px;
|
||
padding: 24px;
|
||
margin-top: 32px;
|
||
}
|
||
|
||
.conclusion-box.warn-box {
|
||
background: var(--red-50);
|
||
border-color: #FECACA;
|
||
}
|
||
|
||
.conclusion-box h4 {
|
||
font-size: 16px;
|
||
font-weight: 700;
|
||
margin-bottom: 12px;
|
||
}
|
||
|
||
.conclusion-box p {
|
||
font-size: 15px;
|
||
color: var(--gray-600);
|
||
line-height: 1.7;
|
||
}
|
||
|
||
/* Mobile */
|
||
@media (max-width: 640px) {
|
||
.hero { padding: 64px 0 48px; }
|
||
.hero h1 { font-size: 32px; }
|
||
.hero p { font-size: 16px; }
|
||
.section { padding: 48px 0; }
|
||
.section h2 { font-size: 26px; }
|
||
.blocks-grid { grid-template-columns: 1fr; }
|
||
.dash-preview { padding: 24px; }
|
||
.dash-metric .value { font-size: 28px; }
|
||
.result-charts { grid-template-columns: 1fr; }
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
|
||
<!-- Hero -->
|
||
<header class="hero">
|
||
<div class="container">
|
||
<span class="hero-badge">AI Analytics</span>
|
||
<h1>ИИ-агент аналитики производственной безопасности</h1>
|
||
<p>Автоматический сбор, анализ и визуализация результатов внутренних проверок. Контроль выполнения плана и формирование управленческих отчётов.</p>
|
||
<div class="hero-actions">
|
||
<a href="#analyzer" class="btn btn-primary">Загрузить данные</a>
|
||
<a href="#quick" class="btn btn-outline">Инструменты</a>
|
||
<a href="#blocks" class="btn btn-outline">Возможности</a>
|
||
</div>
|
||
</div>
|
||
</header>
|
||
|
||
<!-- Quick Links -->
|
||
<section id="quick" class="section quick-links">
|
||
<div class="container">
|
||
<span class="section-label">Инструменты</span>
|
||
<h2>Быстрый доступ</h2>
|
||
<p class="section-subtitle">Основные рабочие инструменты команды</p>
|
||
<div class="quick-grid">
|
||
<a href="#" class="quick-card">
|
||
<span class="quick-card-icon blue">📋</span>
|
||
<div>
|
||
<h3>Google Forms — проверки</h3>
|
||
<p>Заполнить форму по результатам проверки</p>
|
||
</div>
|
||
</a>
|
||
<a href="#" class="quick-card">
|
||
<span class="quick-card-icon green">📊</span>
|
||
<div>
|
||
<h3>Google Sheets — данные</h3>
|
||
<p>Просмотр и выгрузка всех записей проверок</p>
|
||
</div>
|
||
</a>
|
||
<a href="#" class="quick-card">
|
||
<span class="quick-card-icon green">📅</span>
|
||
<div>
|
||
<h3>План проверок</h3>
|
||
<p>Таблица плановых показателей по подразделениям</p>
|
||
</div>
|
||
</a>
|
||
<a href="#analyzer" class="quick-card">
|
||
<span class="quick-card-icon blue">🤖</span>
|
||
<div>
|
||
<h3>Анализатор CSV</h3>
|
||
<p>Загрузите выгрузку из Google Sheets — получите аналитику</p>
|
||
</div>
|
||
</a>
|
||
<a href="#" class="quick-card">
|
||
<span class="quick-card-icon green">📄</span>
|
||
<div>
|
||
<h3>Месячный отчёт</h3>
|
||
<p>Автоматическая аналитическая справка (PDF)</p>
|
||
</div>
|
||
</a>
|
||
<a href="#" class="quick-card">
|
||
<span class="quick-card-icon red">📖</span>
|
||
<div>
|
||
<h3>Инструкция</h3>
|
||
<p>Руководство по работе с системой</p>
|
||
</div>
|
||
</a>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- Functional Blocks -->
|
||
<section id="blocks" class="section">
|
||
<div class="container">
|
||
<span class="section-label">Функционал</span>
|
||
<h2>6 аналитических блоков</h2>
|
||
<p class="section-subtitle">Что умеет ИИ-агент и какую информацию выдаёт автоматически</p>
|
||
<div class="blocks-grid">
|
||
<div class="block-card">
|
||
<span class="block-number">Блок №1</span>
|
||
<h3>Анализ нарушений</h3>
|
||
<ul>
|
||
<li>Общее количество выявленных нарушений</li>
|
||
<li>Количество по каждому виду нарушений</li>
|
||
<li>Доля каждого вида</li>
|
||
<li>Распределение по подразделениям и регионам</li>
|
||
<li>Рейтинг подразделений (лучшие / худшие)</li>
|
||
<li>Рейтинг видов нарушений</li>
|
||
</ul>
|
||
</div>
|
||
<div class="block-card">
|
||
<span class="block-number">Блок №2</span>
|
||
<h3>Анализ динамики</h3>
|
||
<ul>
|
||
<li>Сравнение: текущий месяц vs предыдущий</li>
|
||
<li>Сравнение с аналогичным периодом прошлого года</li>
|
||
<li>Процент роста / снижения</li>
|
||
<li>Тренды по каждому виду нарушений</li>
|
||
<li>Динамика по подразделениям</li>
|
||
</ul>
|
||
</div>
|
||
<div class="block-card">
|
||
<span class="block-number">Блок №3</span>
|
||
<h3>Проблемные зоны</h3>
|
||
<ul>
|
||
<li>Повторяющиеся нарушения</li>
|
||
<li>Подразделения с устойчиво высоким уровнем</li>
|
||
<li>Ухудшение показателей</li>
|
||
<li>Наиболее частые нарушения</li>
|
||
<li>Автоматические выводы — «проблемное направление X, оно даёт 27% нарушений»</li>
|
||
</ul>
|
||
</div>
|
||
<div class="block-card">
|
||
<span class="block-number">Блок №4</span>
|
||
<h3>Контроль плана проверок</h3>
|
||
<ul>
|
||
<li>Выполнение (%) = Факт / План × 100</li>
|
||
<li>Рейтинг подразделений по выполнению</li>
|
||
<li>Перечень с низким исполнением</li>
|
||
<li>Анализ по регионам</li>
|
||
<li>Перевыполнение / невыполнение</li>
|
||
</ul>
|
||
</div>
|
||
<div class="block-card">
|
||
<span class="block-number">Блок №5</span>
|
||
<h3>Аналитическая справка</h3>
|
||
<ul>
|
||
<li>Общая информация за месяц</li>
|
||
<li>Количество проверок и выполнение плана</li>
|
||
<li>ТОП-3 распространённых нарушений</li>
|
||
<li>Подразделения-лидеры и аутсайдеры</li>
|
||
<li>Тенденции месяца</li>
|
||
</ul>
|
||
</div>
|
||
<div class="block-card">
|
||
<span class="block-number">Блок №6</span>
|
||
<h3>Рекомендации</h3>
|
||
<ul>
|
||
<li>Внеплановые проверки по проблемным зонам</li>
|
||
<li>Целевые инструктажи</li>
|
||
<li>Дополнительное обучение</li>
|
||
<li>Выборочный аудит документации</li>
|
||
<li>Усиление контроля</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ========================================= -->
|
||
<!-- ANALYZER -->
|
||
<!-- ========================================= -->
|
||
<section id="analyzer" class="section analyzer">
|
||
<div class="container">
|
||
<span class="section-label">Анализатор</span>
|
||
<h2>Загрузите данные из Google Sheets</h2>
|
||
<p class="section-subtitle">Экспортируйте таблицу в CSV (Файл → Скачать → CSV) и загрузите сюда. Анализатор сам определит категории, построит рейтинги и покажет выводы.</p>
|
||
|
||
<div class="upload-area" id="dropArea">
|
||
<div class="upload-icon">📂</div>
|
||
<p><strong>Перетащите CSV-файл сюда</strong> или нажмите для выбора</p>
|
||
<p class="upload-hint">Поддерживаются файлы .csv из Google Sheets (кодировка UTF-8)</p>
|
||
<input type="file" id="fileInput" accept=".csv" style="display:none">
|
||
</div>
|
||
|
||
<div class="upload-actions">
|
||
<button class="btn btn-blue btn-sm" id="sampleBtn">Загрузить демо-данные</button>
|
||
<span class="upload-status" id="uploadStatus"></span>
|
||
</div>
|
||
|
||
<!-- Results -->
|
||
<div id="results">
|
||
<div class="sort-info" style="font-size:14px;color:var(--gray-500);margin-bottom:24px" id="dataInfo"></div>
|
||
|
||
<!-- Key Metrics -->
|
||
<div class="result-grid" id="metricsGrid"></div>
|
||
|
||
<!-- Tabs -->
|
||
<div class="section-tabs">
|
||
<button class="tab-btn active" data-tab="tab-categories">По категориям</button>
|
||
<button class="tab-btn" data-tab="tab-divisions">По подразделениям</button>
|
||
<button class="tab-btn" data-tab="tab-regions">По регионам</button>
|
||
<button class="tab-btn" data-tab="tab-inspectors">По проверяющим</button>
|
||
<button class="tab-btn" data-tab="tab-repeat">Повторяющиеся</button>
|
||
<button class="tab-btn" data-tab="tab-table">Все записи</button>
|
||
</div>
|
||
|
||
<div class="tab-content active" id="tab-categories"></div>
|
||
<div class="tab-content" id="tab-divisions"></div>
|
||
<div class="tab-content" id="tab-regions"></div>
|
||
<div class="tab-content" id="tab-inspectors"></div>
|
||
<div class="tab-content" id="tab-repeat"></div>
|
||
<div class="tab-content" id="tab-table"></div>
|
||
|
||
<!-- Auto conclusion -->
|
||
<div id="conclusions"></div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- Dashboard Preview -->
|
||
<section id="dashboard-preview" class="section" style="background: var(--gray-50);">
|
||
<div class="container">
|
||
<span class="section-label">Дашборд</span>
|
||
<h2>Визуализация данных</h2>
|
||
<p class="section-subtitle">Пример того, как выглядит аналитическая панель ИИ-агента</p>
|
||
<div class="dash-preview">
|
||
<div class="dash-metric">
|
||
<div class="value">247</div>
|
||
<div class="label">Нарушений за месяц</div>
|
||
</div>
|
||
<div class="dash-metric">
|
||
<div class="value">87%</div>
|
||
<div class="label">Выполнение плана</div>
|
||
</div>
|
||
<div class="dash-metric">
|
||
<div class="value">142</div>
|
||
<div class="label">Проведено проверок</div>
|
||
</div>
|
||
<div class="dash-metric">
|
||
<div class="value">32</div>
|
||
<div class="label">Устранено за месяц</div>
|
||
</div>
|
||
<div class="dash-chart">
|
||
<div class="chart-bars">
|
||
<div class="chart-bar-wrapper">
|
||
<div class="chart-bar" style="height: 120px; background: #2563EB;"></div>
|
||
<span class="chart-bar-label">Янв</span>
|
||
</div>
|
||
<div class="chart-bar-wrapper">
|
||
<div class="chart-bar" style="height: 90px; background: #2563EB;"></div>
|
||
<span class="chart-bar-label">Фев</span>
|
||
</div>
|
||
<div class="chart-bar-wrapper">
|
||
<div class="chart-bar" style="height: 140px; background: #2563EB;"></div>
|
||
<span class="chart-bar-label">Мар</span>
|
||
</div>
|
||
<div class="chart-bar-wrapper">
|
||
<div class="chart-bar" style="height: 100px; background: #2563EB;"></div>
|
||
<span class="chart-bar-label">Апр</span>
|
||
</div>
|
||
<div class="chart-bar-wrapper">
|
||
<div class="chart-bar" style="height: 160px; background: #DC2626;"></div>
|
||
<span class="chart-bar-label">Май</span>
|
||
</div>
|
||
<div class="chart-bar-wrapper">
|
||
<div class="chart-bar" style="height: 130px; background: #2563EB;"></div>
|
||
<span class="chart-bar-label">Июн</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- How to work -->
|
||
<section id="steps" class="section">
|
||
<div class="container">
|
||
<span class="section-label">Процесс</span>
|
||
<h2>Как работать с системой</h2>
|
||
<p class="section-subtitle">4 шага от проверки до управленческого отчёта</p>
|
||
<div class="steps">
|
||
<div class="step">
|
||
<div class="step-num"></div>
|
||
<div class="step-text">
|
||
<h3>Заполните Google Forms после проверки</h3>
|
||
<p>Проверяющий вносит данные: дата, подразделение, регион, вид и количество нарушений, описание. Данные автоматически попадают в Google Sheets.</p>
|
||
</div>
|
||
</div>
|
||
<div class="step">
|
||
<div class="step-num"></div>
|
||
<div class="step-text">
|
||
<h3>Выгрузите CSV и загрузите в анализатор</h3>
|
||
<p>В Google Sheets: Файл → Скачать → CSV. Перетащите файл на эту страницу — анализатор сам разберёт данные.</p>
|
||
</div>
|
||
</div>
|
||
<div class="step">
|
||
<div class="step-num"></div>
|
||
<div class="step-text">
|
||
<h3>Смотрите аналитику</h3>
|
||
<p>Рейтинги категорий, подразделений и регионов. Графики, проблемные зоны, повторяющиеся нарушения — всё считается автоматически.</p>
|
||
</div>
|
||
</div>
|
||
<div class="step">
|
||
<div class="step-num"></div>
|
||
<div class="step-text">
|
||
<h3>Получайте выводы и рекомендации</h3>
|
||
<p>Анализатор формирует текстовые выводы с цифрами и конкретными предложениями по улучшению ситуации.</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- Categories -->
|
||
<section class="section" style="background: var(--gray-50);">
|
||
<div class="container">
|
||
<span class="section-label">Классификация</span>
|
||
<h2>9 категорий нарушений</h2>
|
||
<p class="section-subtitle">Анализатор автоматически распределяет нарушения по категориям</p>
|
||
<div class="cat-grid">
|
||
<span class="cat-tag"><span class="cat-dot d1"></span> Документация по БиОТ</span>
|
||
<span class="cat-tag"><span class="cat-dot d2"></span> Наряды-допуски</span>
|
||
<span class="cat-tag"><span class="cat-dot d3"></span> Пожарная безопасность</span>
|
||
<span class="cat-tag"><span class="cat-dot d4"></span> Транспортная безопасность</span>
|
||
<span class="cat-tag"><span class="cat-dot d5"></span> Промышленная безопасность</span>
|
||
<span class="cat-tag"><span class="cat-dot d6"></span> Санитарно-бытовые требования</span>
|
||
<span class="cat-tag"><span class="cat-dot d7"></span> Средства индивидуальной защиты</span>
|
||
<span class="cat-tag"><span class="cat-dot d8"></span> Обучение и проверка знаний</span>
|
||
<span class="cat-tag"><span class="cat-dot d9"></span> Электробезопасность</span>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- CTA -->
|
||
<section class="cta">
|
||
<div class="container">
|
||
<h2>Начните использовать анализатор</h2>
|
||
<p>Выгрузите данные из Google Sheets в CSV — и получите полную аналитику за минуту. Все вопросы — в рабочий чат команды.</p>
|
||
<a href="#analyzer" class="btn">Загрузить данные</a>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- Footer -->
|
||
<footer class="footer">
|
||
<div class="container">
|
||
<p>ИИ-агент аналитики производственной безопасности · Внутренний портал команды · 2026</p>
|
||
</div>
|
||
</footer>
|
||
|
||
<!-- ========================================= -->
|
||
<!-- JAVASCRIPT: CSV Analyzer -->
|
||
<!-- ========================================= -->
|
||
<script>
|
||
(function() {
|
||
'use strict';
|
||
|
||
var CATEGORIES = [
|
||
'Документация по БиОТ',
|
||
'Наряды-допуски',
|
||
'Пожарная безопасность',
|
||
'Транспортная безопасность',
|
||
'Промышленная безопасность',
|
||
'Санитарно-бытовые требования',
|
||
'Средства индивидуальной защиты',
|
||
'Обучение и проверка знаний',
|
||
'Электробезопасность',
|
||
'Организационные требования БиОТ',
|
||
'Производственная санитария'
|
||
];
|
||
|
||
var CAT_SHORT = {
|
||
'Документация по БиОТ': 'Документация БиОТ',
|
||
'Наряды-допуски': 'Наряды-допуски',
|
||
'Пожарная безопасность': 'Пожарная безопасность',
|
||
'Транспортная безопасность': 'Транспортная безопасность',
|
||
'Промышленная безопасность': 'Промышленная безопасность',
|
||
'Санитарно-бытовые требования': 'Санитарно-бытовые',
|
||
'Средства индивидуальной защиты': 'СИЗ',
|
||
'Обучение и проверка знаний': 'Обучение',
|
||
'Электробезопасность': 'Электробезопасность',
|
||
'Организационные требования БиОТ': 'Орг. требования',
|
||
'Производственная санитария': 'Произв. санитария',
|
||
'Без категории': 'Без категории'
|
||
};
|
||
|
||
function findColumn(headers, keywords) {
|
||
var h = headers.map(function(hdr) { return hdr.toLowerCase().trim(); });
|
||
for (var i = 0; i < h.length; i++) {
|
||
for (var k = 0; k < keywords.length; k++) {
|
||
if (h[i].indexOf(keywords[k].toLowerCase()) !== -1) return i;
|
||
}
|
||
}
|
||
return -1;
|
||
}
|
||
|
||
function parseCSV(text) {
|
||
var rows = [];
|
||
var row = [];
|
||
var cell = '';
|
||
var quoted = false;
|
||
for (var i = 0; i < text.length; i++) {
|
||
var ch = text[i];
|
||
if (quoted) {
|
||
if (ch === '"') {
|
||
if (i + 1 < text.length && text[i + 1] === '"') { cell += '"'; i++; }
|
||
else quoted = false;
|
||
} else { cell += ch; }
|
||
} else {
|
||
if (ch === '"') { quoted = true; }
|
||
else if (ch === ',' || ch === ';' || ch === '\t') {
|
||
row.push(cell.trim()); cell = '';
|
||
} else if (ch === '\n' || ch === '\r') {
|
||
if (ch === '\r' && i + 1 < text.length && text[i + 1] === '\n') i++;
|
||
row.push(cell.trim());
|
||
if (row.length > 0 && (row.length > 1 || row[0])) rows.push(row);
|
||
row = []; cell = '';
|
||
} else { cell += ch; }
|
||
}
|
||
}
|
||
row.push(cell.trim());
|
||
if (row.length > 0 && (row.length > 1 || row[0])) rows.push(row);
|
||
return rows;
|
||
}
|
||
|
||
function normalizeCategory(raw) {
|
||
if (!raw) return '';
|
||
var s = raw.toLowerCase().trim();
|
||
if (s === 'сиз' || s === 'не обеспечение сиз' || s.indexOf('сиз') !== -1) return 'Средства индивидуальной защиты';
|
||
if (s.indexOf('пожарн') !== -1 || s.indexOf('пожар') !== -1 || s.indexOf('өрт') !== -1) return 'Пожарная безопасность';
|
||
if (s.indexOf('электро') !== -1) return 'Электробезопасность';
|
||
if (s.indexOf('транспорт') !== -1 || s.indexOf('тсс') !== -1) return 'Транспортная безопасность';
|
||
if (s.indexOf('промышленн') !== -1 || s.indexOf('помышленн') !== -1 || s.indexOf('өнеркәсіп') !== -1) return 'Промышленная безопасность';
|
||
if (s.indexOf('наряд') !== -1 || s.indexOf('допуск') !== -1) return 'Наряды-допуски';
|
||
if (s.indexOf('санитар') !== -1 || s.indexOf('бытов') !== -1 || s.indexOf('гигиенич') !== -1 || s.indexOf('санитари') !== -1) return 'Санитарно-бытовые требования';
|
||
if (s.indexOf('производственн') !== -1 && s.indexOf('санитар') !== -1) return 'Производственная санитария';
|
||
if (s.indexOf('обучен') !== -1 || s.indexOf('знаний') !== -1 || s.indexOf('оқыту') !== -1 || s.indexOf('нұсқау') !== -1) return 'Обучение и проверка знаний';
|
||
if (s.indexOf('4-х ступенчат') !== -1 || s.indexOf('четырехступенчат') !== -1 || s.indexOf('организационн') !== -1) return 'Организационные требования БиОТ';
|
||
if (s.indexOf('биот') !== -1 || s.indexOf('документаци') !== -1 || s.indexOf('док-ты') !== -1 || s.indexOf('документ') !== -1) return 'Документация по БиОТ';
|
||
if (s.indexOf('инструктаж') !== -1 || s.indexOf('инструкци') !== -1) return 'Документация по БиОТ';
|
||
if (s.length > 3) return raw.trim();
|
||
return '';
|
||
}
|
||
|
||
function isFixedStatus(val) {
|
||
var s = (val || '').toLowerCase().trim();
|
||
if (s === 'исполнено' || s === 'орындалды' || s === 'устранено' || s === 'выполнено') return true;
|
||
if (s.indexOf('исполн') !== -1) return true;
|
||
return false;
|
||
}
|
||
|
||
function analyzeData(rows) {
|
||
if (rows.length < 2) return null;
|
||
var headers = rows[0];
|
||
var data = rows.slice(1);
|
||
|
||
var colDate = findColumn(headers, ['дата']);
|
||
var colDept = findColumn(headers, ['филиал','подразделение','структур']);
|
||
var colRegion = findColumn(headers, ['область','регион']);
|
||
var colCity = findColumn(headers, ['город','район','населен']);
|
||
var colAddr = findColumn(headers, ['объект','адрес']);
|
||
var colCategory = findColumn(headers, ['категория','нарушен']);
|
||
var colInspector = findColumn(headers, ['выдал','кто выдал','проверяющий']);
|
||
var colStatus = findColumn(headers, ['исполнено','статус','устран']);
|
||
var colDeadline = findColumn(headers, ['срок']);
|
||
var colDescCat2 = -1;
|
||
|
||
// Second category column detection: find second match
|
||
for (var hi = 0; hi < headers.length; hi++) {
|
||
var hdr = headers[hi].toLowerCase().trim();
|
||
if (hdr.indexOf('категори') !== -1 && hi !== colCategory) { colDescCat2 = hi; break; }
|
||
if (hdr.indexOf('описан') !== -1 || hdr.indexOf('суть') !== -1) { colDescCat2 = hi; break; }
|
||
}
|
||
var colDesc = colDescCat2 >= 0 ? colDescCat2 : (colCategory >= 0 ? colCategory + 1 : -1);
|
||
|
||
var records = [];
|
||
for (var i = 0; i < data.length; i++) {
|
||
var r = data[i];
|
||
if (r.length < 2) continue;
|
||
|
||
var catRaw = colCategory >= 0 ? (r[colCategory] || '') : '';
|
||
var catNorm = normalizeCategory(catRaw);
|
||
|
||
var descRaw = colDesc >= 0 && colDesc < r.length ? (r[colDesc] || '') : '';
|
||
if (!catNorm && descRaw) catNorm = normalizeCategory(descRaw);
|
||
if (!catNorm && catRaw) catNorm = catRaw;
|
||
|
||
var statusRaw = colStatus >= 0 ? (r[colStatus] || '') : '';
|
||
var fixed = isFixedStatus(statusRaw);
|
||
|
||
records.push({
|
||
date: colDate >= 0 ? (r[colDate] || '') : '',
|
||
dept: colDept >= 0 ? (r[colDept] || 'Не указано') : 'Не указано',
|
||
region: colRegion >= 0 ? (r[colRegion] || 'Не указано') : 'Не указано',
|
||
city: colCity >= 0 ? (r[colCity] || '') : '',
|
||
addr: colAddr >= 0 ? (r[colAddr] || '') : '',
|
||
inspector: colInspector >= 0 ? (r[colInspector] || 'Не указано') : 'Не указано',
|
||
count: 1,
|
||
category: catNorm || 'Без категории',
|
||
desc: descRaw,
|
||
status: statusRaw,
|
||
fixed: fixed,
|
||
deadline: colDeadline >= 0 ? (r[colDeadline] || '') : '',
|
||
raw: r
|
||
});
|
||
}
|
||
|
||
if (records.length === 0) return null;
|
||
|
||
var totalRecords = records.length;
|
||
var totalFixed = records.filter(function(r) { return r.fixed; }).length;
|
||
var totalPending = totalRecords - totalFixed;
|
||
|
||
var catStats = {};
|
||
records.forEach(function(r) {
|
||
if (!catStats[r.category]) catStats[r.category] = 0;
|
||
catStats[r.category] += r.count;
|
||
});
|
||
var catSorted = Object.entries(catStats).sort(function(a, b) { return b[1] - a[1]; });
|
||
|
||
var deptStats = {};
|
||
records.forEach(function(r) {
|
||
if (!deptStats[r.dept]) deptStats[r.dept] = 0;
|
||
deptStats[r.dept] += r.count;
|
||
});
|
||
var deptSorted = Object.entries(deptStats).sort(function(a, b) { return b[1] - a[1]; });
|
||
|
||
var regionStats = {};
|
||
records.forEach(function(r) {
|
||
if (!regionStats[r.region]) regionStats[r.region] = 0;
|
||
regionStats[r.region] += r.count;
|
||
});
|
||
var regionSorted = Object.entries(regionStats).sort(function(a, b) { return b[1] - a[1]; });
|
||
|
||
var inspStats = {};
|
||
records.forEach(function(r) {
|
||
if (!inspStats[r.inspector]) inspStats[r.inspector] = 0;
|
||
inspStats[r.inspector] += r.count;
|
||
});
|
||
var inspSorted = Object.entries(inspStats).sort(function(a, b) { return b[1] - a[1]; });
|
||
|
||
var descStats = {};
|
||
records.forEach(function(r) {
|
||
var key = (r.desc || r.category || '').trim().toLowerCase();
|
||
if (!key || key.length < 3) key = r.category.toLowerCase() + '_' + (Math.random() + '').slice(2, 6);
|
||
if (!descStats[key]) descStats[key] = { count: 0, desc: r.desc || r.category, cat: r.category };
|
||
descStats[key].count += r.count;
|
||
});
|
||
var repeatSorted = Object.entries(descStats)
|
||
.filter(function(e) { return e[1].count > 1; })
|
||
.sort(function(a, b) { return b[1].count - a[1].count; });
|
||
|
||
var topCat = catSorted.length > 0 ? catSorted[0] : null;
|
||
|
||
return {
|
||
records: records, totalRecords: totalRecords,
|
||
totalFixed: totalFixed, totalPending: totalPending,
|
||
catSorted: catSorted, deptSorted: deptSorted,
|
||
regionSorted: regionSorted, inspSorted: inspSorted,
|
||
repeatSorted: repeatSorted, topCat: topCat,
|
||
headers: headers
|
||
};
|
||
}
|
||
|
||
function escHtml(s) {
|
||
return String(s).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
||
}
|
||
|
||
function buildMetric(value, label, cls) {
|
||
return '<div class="result-metric' + (cls ? ' ' + cls : '') + '">' +
|
||
'<div class="value">' + value + '</div><div class="label">' + label + '</div></div>';
|
||
}
|
||
|
||
function renderResults(data) {
|
||
var results = document.getElementById('results');
|
||
results.classList.add('active');
|
||
|
||
document.getElementById('dataInfo').textContent =
|
||
'Загружено: ' + data.totalRecords + ' записей. Устранено: ' + data.totalFixed +
|
||
' (' + Math.round(data.totalFixed / data.totalRecords * 100) + '%). На контроле: ' + data.totalPending + '.';
|
||
|
||
var metricsHTML = '';
|
||
metricsHTML += buildMetric(data.totalRecords, 'Всего записей');
|
||
metricsHTML += buildMetric(data.totalFixed, 'Устранено', 'good');
|
||
metricsHTML += buildMetric(data.totalPending, 'На контроле', data.totalPending > data.totalFixed * 2 ? 'warn' : '');
|
||
metricsHTML += buildMetric(data.catSorted.length, 'Категорий нарушений');
|
||
if (data.topCat) {
|
||
metricsHTML += buildMetric(
|
||
Math.round(data.topCat[1] / data.totalRecords * 100) + '%',
|
||
'Доля «' + (CAT_SHORT[data.topCat[0]] || data.topCat[0]) + '»',
|
||
data.topCat[1] / data.totalRecords > 0.25 ? 'warn' : ''
|
||
);
|
||
}
|
||
document.getElementById('metricsGrid').innerHTML = metricsHTML;
|
||
|
||
renderCategoriesTab(data);
|
||
renderDivisionsTab(data);
|
||
renderRegionsTab(data);
|
||
renderInspectorsTab(data);
|
||
renderRepeatTab(data);
|
||
renderTableTab(data);
|
||
renderConclusions(data);
|
||
|
||
document.querySelectorAll('.tab-btn').forEach(function(b) { b.classList.remove('active'); });
|
||
document.querySelector('[data-tab="tab-categories"]').classList.add('active');
|
||
document.querySelectorAll('.tab-content').forEach(function(c) { c.classList.remove('active'); });
|
||
document.getElementById('tab-categories').classList.add('active');
|
||
|
||
results.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||
}
|
||
|
||
function renderBarList(containerId, items, total, maxBars) {
|
||
maxBars = maxBars || 15;
|
||
var container = document.getElementById(containerId);
|
||
var titles = {
|
||
'tab-categories': 'Распределение по категориям нарушений',
|
||
'tab-divisions': 'Рейтинг филиалов',
|
||
'tab-regions': 'Рейтинг областей',
|
||
'tab-inspectors': 'По проверяющим',
|
||
'tab-repeat': 'Повторяющиеся нарушения'
|
||
};
|
||
var html = '<div class="result-chart-card full"><h3>' + (titles[containerId] || '') + '</h3>';
|
||
var shown = items.slice(0, maxBars);
|
||
var maxVal = shown.length > 0 ? shown[0][1] : 1;
|
||
shown.forEach(function(item, idx) {
|
||
var pct = total > 0 ? Math.round(item[1] / total * 100) : 0;
|
||
var barPct = maxVal > 0 ? Math.round(item[1] / maxVal * 100) : 0;
|
||
var isWarn = containerId === 'tab-categories' && idx === 0 && pct > 20;
|
||
html += '<div class="bar-list-item"><div class="bar-list-header"><span class="name">' +
|
||
escHtml(item[0]) + '</span><span class="count">' + item[1] + ' (' + pct + '%)</span></div>' +
|
||
'<div class="bar-list-track"><div class="bar-list-fill' + (isWarn ? ' warn' : '') +
|
||
'" style="width:' + barPct + '%"></div></div></div>';
|
||
});
|
||
if (items.length > maxBars) {
|
||
html += '<p style="font-size:13px;color:var(--gray-500);margin-top:12px">Показаны первые ' + maxBars + ' из ' + items.length + '</p>';
|
||
}
|
||
html += '</div>';
|
||
container.innerHTML = html;
|
||
}
|
||
|
||
function renderCategoriesTab(data) { renderBarList('tab-categories', data.catSorted, data.totalRecords, 15); }
|
||
function renderDivisionsTab(data) { renderBarList('tab-divisions', data.deptSorted, data.totalRecords); }
|
||
function renderRegionsTab(data) { renderBarList('tab-regions', data.regionSorted, data.totalRecords); }
|
||
function renderInspectorsTab(data) { renderBarList('tab-inspectors', data.inspSorted, data.totalRecords); }
|
||
|
||
function renderRepeatTab(data) {
|
||
var container = document.getElementById('tab-repeat');
|
||
var items = data.repeatSorted;
|
||
var html = '';
|
||
if (items.length === 0) {
|
||
html = '<div class="result-chart-card full"><h3>Повторяющиеся нарушения</h3><p style="color:var(--gray-500)">Повторяющихся нарушений не найдено.</p></div>';
|
||
} else {
|
||
html = '<div class="result-chart-card full"><h3>Повторяющиеся нарушения (' + items.length + ')</h3>';
|
||
items.slice(0, 30).forEach(function(item) {
|
||
html += '<div class="bar-list-item"><div class="bar-list-header"><span class="name">' +
|
||
escHtml(item[1].desc || item[0]) + '</span><span class="count">' + item[1].count + ' раз</span></div>' +
|
||
'<div class="bar-list-track"><div class="bar-list-fill warn" style="width:' + Math.min(100, item[1].count * 15) + '%"></div></div></div>';
|
||
});
|
||
html += '</div>';
|
||
}
|
||
container.innerHTML = html;
|
||
}
|
||
|
||
function renderTableTab(data) {
|
||
var container = document.getElementById('tab-table');
|
||
var r = data.records;
|
||
var cols = ['date','dept','region','inspector','category','desc','status'];
|
||
var labels = { date: 'Дата', dept: 'Филиал', region: 'Область', inspector: 'Проверяющий', category: 'Категория', desc: 'Описание', status: 'Статус' };
|
||
var html = '<div class="result-table-wrap"><table class="result-table"><thead><tr>';
|
||
cols.forEach(function(c) { html += '<th>' + labels[c] + '</th>'; });
|
||
html += '</tr></thead><tbody>';
|
||
r.slice(0, 500).forEach(function(rec) {
|
||
html += '<tr>';
|
||
cols.forEach(function(c) {
|
||
var val = rec[c] || '';
|
||
if (c === 'status') val = rec.fixed ? 'Устранено' : 'На контроле';
|
||
if (c === 'desc' && val.length > 80) val = val.slice(0, 80) + '…';
|
||
html += '<td>' + escHtml(String(val)) + '</td>';
|
||
});
|
||
html += '</tr>';
|
||
});
|
||
if (r.length > 500) {
|
||
html += '<tr><td colspan="7" style="text-align:center;color:var(--gray-500)">Показаны первые 500 из ' + r.length + ' записей</td></tr>';
|
||
}
|
||
html += '</tbody></table></div>';
|
||
container.innerHTML = html;
|
||
}
|
||
|
||
function renderConclusions(data) {
|
||
var html = '';
|
||
var topCat = data.catSorted[0];
|
||
var topDept = data.deptSorted[0];
|
||
var worstDept = data.deptSorted[data.deptSorted.length - 1];
|
||
|
||
if (topCat && topCat[1] / data.totalRecords > 0.2) {
|
||
html += '<div class="conclusion-box warn-box"><h4>Проблемная зона</h4><p>Наиболее частой категорией является <strong>' +
|
||
escHtml(topCat[0]).toLowerCase() + '</strong> — доля <strong>' +
|
||
Math.round(topCat[1] / data.totalRecords * 100) + '%</strong> (' + topCat[1] + ' из ' + data.totalRecords + ' записей). ' +
|
||
'Основная концентрация — филиал <strong>' + escHtml(topDept[0]) + '</strong> (' + topDept[1] + ' нарушений).</p></div>';
|
||
}
|
||
|
||
html += '<div class="conclusion-box"><h4>Общие выводы</h4><p>Всего <strong>' + data.totalRecords +
|
||
'</strong> записей о нарушениях. Устранено: <strong>' + data.totalFixed +
|
||
'</strong> (' + Math.round(data.totalFixed / data.totalRecords * 100) + '%). На контроле: <strong>' + data.totalPending + '</strong>. ' +
|
||
'ТОП-3 категории: ' + data.catSorted.slice(0, 3).map(function(c, i) {
|
||
return (i + 1) + ') ' + escHtml(c[0]) + ' — ' + c[1] + ' (' + Math.round(c[1] / data.totalRecords * 100) + '%)';
|
||
}).join('; ') + '.</p></div>';
|
||
|
||
if (data.totalPending > data.totalFixed) {
|
||
html += '<div class="conclusion-box"><h4>Рекомендации</h4><p>На контроле <strong>' + data.totalPending +
|
||
'</strong> нарушений — требуется усилить контроль устранения. ';
|
||
if (topCat) {
|
||
var cat = topCat[0];
|
||
if (cat === 'Средства индивидуальной защиты') html += 'По СИЗ: провести внеплановые проверки применения средств защиты и целевой инструктаж. ';
|
||
else if (cat === 'Наряды-допуски') html += 'По нарядам-допускам: организовать дополнительное обучение и выборочный аудит. ';
|
||
else if (cat === 'Электробезопасность') html += 'По электробезопасности: усилить контроль со стороны ответственных лиц. ';
|
||
else if (cat === 'Пожарная безопасность') html += 'По пожарной безопасности: проверить сроки и качество инструктажей. ';
|
||
else html += 'По «' + escHtml(cat) + '»: провести внеплановую проверку и целевой инструктаж. ';
|
||
}
|
||
html += 'Рекомендуется еженедельный мониторинг.</p></div>';
|
||
}
|
||
|
||
document.getElementById('conclusions').innerHTML = html;
|
||
}
|
||
|
||
function processFile(file) {
|
||
document.getElementById('uploadStatus').textContent = 'Обработка...';
|
||
var reader = new FileReader();
|
||
reader.onload = function(e) {
|
||
try {
|
||
var text = e.target.result;
|
||
// Remove BOM
|
||
if (text.charCodeAt(0) === 0xFEFF) text = text.slice(1);
|
||
|
||
// Auto-detect delimiter
|
||
var commaCount = (text.split('\n')[0] || '').split(',').length;
|
||
var semiCount = (text.split('\n')[0] || '').split(';').length;
|
||
if (semiCount > commaCount + 2) {
|
||
text = text.replace(/;/g, ',');
|
||
}
|
||
|
||
var rows = parseCSV(text);
|
||
if (rows.length < 2) {
|
||
document.getElementById('uploadStatus').textContent = 'Ошибка: файл пуст.';
|
||
return;
|
||
}
|
||
var data = analyzeData(rows);
|
||
if (!data || data.totalRecords === 0) {
|
||
document.getElementById('uploadStatus').textContent = 'Ошибка: не удалось распознать данные.';
|
||
return;
|
||
}
|
||
document.getElementById('uploadStatus').textContent = 'Загружен «' + file.name + '» (' + data.totalRecords + ' записей)';
|
||
renderResults(data);
|
||
} catch (err) {
|
||
document.getElementById('uploadStatus').textContent = 'Ошибка: ' + err.message;
|
||
}
|
||
};
|
||
reader.readAsText(file, 'UTF-8');
|
||
}
|
||
|
||
var dropArea = document.getElementById('dropArea');
|
||
var fileInput = document.getElementById('fileInput');
|
||
|
||
dropArea.addEventListener('click', function() { fileInput.click(); });
|
||
fileInput.addEventListener('change', function() {
|
||
if (fileInput.files.length > 0) processFile(fileInput.files[0]);
|
||
});
|
||
|
||
dropArea.addEventListener('dragover', function(e) {
|
||
e.preventDefault();
|
||
dropArea.classList.add('drag-over');
|
||
});
|
||
dropArea.addEventListener('dragleave', function() {
|
||
dropArea.classList.remove('drag-over');
|
||
});
|
||
dropArea.addEventListener('drop', function(e) {
|
||
e.preventDefault();
|
||
dropArea.classList.remove('drag-over');
|
||
if (e.dataTransfer.files.length > 0) processFile(e.dataTransfer.files[0]);
|
||
});
|
||
|
||
document.querySelectorAll('.tab-btn').forEach(function(btn) {
|
||
btn.addEventListener('click', function() {
|
||
document.querySelectorAll('.tab-btn').forEach(function(b) { b.classList.remove('active'); });
|
||
document.querySelectorAll('.tab-content').forEach(function(c) { c.classList.remove('active'); });
|
||
btn.classList.add('active');
|
||
var target = document.getElementById(btn.getAttribute('data-tab'));
|
||
if (target) target.classList.add('active');
|
||
});
|
||
});
|
||
|
||
document.getElementById('sampleBtn').addEventListener('click', function() {
|
||
document.getElementById('uploadStatus').textContent = 'Загрузка демо-данных...';
|
||
var sampleCSV = generateSampleCSV();
|
||
var blob = new Blob(['\uFEFF' + sampleCSV], { type: 'text/csv;charset=utf-8' });
|
||
processFile(new File([blob], 'demo_data.csv', { type: 'text/csv' }));
|
||
});
|
||
|
||
function generateSampleCSV() {
|
||
var header = 'Дата,Филиал,Область,Город/Район,Объект с адресом,Категория нарушения,Описание нарушения,Срок исполнения,исполнено,кто выдал,кому выдано';
|
||
var filials = ['ОДС','ОДС','ОДС','ДРБ','ДИТ','Сервисная фабрика','Сервисная фабрика','ДКБ','ДУП','СФ','ЦТО СФ','ЦЭиК'];
|
||
var oblasts = ['Акмолинская','Карагандинская','Астана','Астана','Улытауская','Карагандинская','Акмолинская','Астана','Карагандинская'];
|
||
var cities = ['Шахтинск','Астана','Караганда','Темиртау','Щучинск','Атбасар','Балхаш','Кокшетау'];
|
||
var inspectors = [
|
||
'Инженер по БиОТ Семидоцкий С.А.',
|
||
'Ведущий инженер ОБиОТ Туржанов А.Т.',
|
||
'Инженер ОБиОТ Бачинская Н.В.',
|
||
'Ведущий инженер ОБиОТ Ажакметов М.З.',
|
||
'Ведущий инженер ОБиОТ Баяхметова Ж.Т.',
|
||
'Инженер ОБиОТ Садыков М.Ф.',
|
||
'Ведущий инженер ОБиОТ Тумабаева С.А.',
|
||
'Инженер по БиОТ Джусупова А.Т.',
|
||
'Инженер по БиОТ Кришталь В.П.'
|
||
];
|
||
var realCats = [
|
||
'основные док-ты по БиОТ','основные док-ты по БиОТ','основные документы по БиОТ',
|
||
'Пожарная безопасность','Пожарная безопасность','пожарная безопасность',
|
||
'Электробезопасность',
|
||
'СИЗ','СИЗ','Не обеспечение СИЗ',
|
||
'Транспортная безопасность',
|
||
'Промышленная безопасность','промышленная безопасность',
|
||
'Санитарно-бытовые условия',
|
||
'организационные требования БиОТ','Инструкция по проведению 4-х ступенчатого контроля',
|
||
'Производственная санитария'
|
||
];
|
||
var descs = [
|
||
'Отсутствует журнал инструктажа на рабочем месте',
|
||
'Не проведён повторный инструктаж за 1 квартал',
|
||
'Работники не ознакомлены с регламентом обеспечения СИЗ',
|
||
'Форма журнала наряд-допусков не соответствует правилам',
|
||
'Просроченные диэлектрические перчатки',
|
||
'Лестница не имеет даты проверки лабораторных испытаний',
|
||
'Неактуальные инструкции по ТБ по видам работ',
|
||
'Отсутствует аптечка для оказания первой помощи',
|
||
'Пожарный кран не проверялся на работоспособность',
|
||
'Работники не применяли СИЗ (без касок, пояса)',
|
||
'Прошёл срок поверки инструмента с изолированными ручками',
|
||
'Не корректно ведётся журнал 4-х ступенчатого контроля',
|
||
'На территории скопление снега, требуется вывоз',
|
||
'Отсутствует комната для сушки спецодежды',
|
||
'Аварийный выход заблокирован',
|
||
'В журнале учёта такелажных средств нет подписи председателя',
|
||
'Нет таблички об ответственности по пожарной безопасности',
|
||
'Работники не обеспечены спецодеждой и обувью'
|
||
];
|
||
var statuses = ['исполнено','исполнено','исполнено','на контроле','на контроле','исполнено','на контроле'];
|
||
var months = ['01','01','02','02','03','03','04','04','05','05','06'];
|
||
|
||
var lines = [header];
|
||
for (var i = 0; i < 200; i++) {
|
||
var m = months[Math.floor(Math.random() * months.length)];
|
||
var day = String(Math.floor(Math.random() * 28) + 1).padStart(2, '0');
|
||
var date = day + '.' + m + '.2026';
|
||
var fil = filials[Math.floor(Math.random() * filials.length)];
|
||
var obl = oblasts[Math.floor(Math.random() * oblasts.length)];
|
||
var city = cities[Math.floor(Math.random() * cities.length)];
|
||
var addr = 'ул. Примерная ' + (Math.floor(Math.random() * 50) + 1);
|
||
var cat = realCats[Math.floor(Math.random() * realCats.length)];
|
||
var desc = descs[Math.floor(Math.random() * descs.length)];
|
||
var insp = inspectors[Math.floor(Math.random() * inspectors.length)];
|
||
var status = statuses[Math.floor(Math.random() * statuses.length)];
|
||
|
||
lines.push([date, fil, obl, city, addr, cat, desc, '', status, insp, ''].join(','));
|
||
}
|
||
return lines.join('\n');
|
||
}
|
||
|
||
})();
|
||
</script>
|
||
|
||
</body>
|
||
</html>
|