Анализатор обновлён под реальные данные Google Sheets
This commit is contained in:
parent
a0c5504471
commit
d7a34a46ed
883
index.html
883
index.html
@ -89,6 +89,9 @@ body {
|
|||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
transition: transform 0.15s, box-shadow 0.15s;
|
transition: transform 0.15s, box-shadow 0.15s;
|
||||||
|
cursor: pointer;
|
||||||
|
border: none;
|
||||||
|
font-family: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn:hover {
|
.btn:hover {
|
||||||
@ -111,6 +114,21 @@ body {
|
|||||||
border-color: var(--white);
|
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 */
|
||||||
.section {
|
.section {
|
||||||
padding: 80px 0;
|
padding: 80px 0;
|
||||||
@ -445,6 +463,267 @@ body {
|
|||||||
color: var(--gray-500);
|
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 */
|
/* Mobile */
|
||||||
@media (max-width: 640px) {
|
@media (max-width: 640px) {
|
||||||
.hero { padding: 64px 0 48px; }
|
.hero { padding: 64px 0 48px; }
|
||||||
@ -455,6 +734,7 @@ body {
|
|||||||
.blocks-grid { grid-template-columns: 1fr; }
|
.blocks-grid { grid-template-columns: 1fr; }
|
||||||
.dash-preview { padding: 24px; }
|
.dash-preview { padding: 24px; }
|
||||||
.dash-metric .value { font-size: 28px; }
|
.dash-metric .value { font-size: 28px; }
|
||||||
|
.result-charts { grid-template-columns: 1fr; }
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
@ -467,9 +747,9 @@ body {
|
|||||||
<h1>ИИ-агент аналитики производственной безопасности</h1>
|
<h1>ИИ-агент аналитики производственной безопасности</h1>
|
||||||
<p>Автоматический сбор, анализ и визуализация результатов внутренних проверок. Контроль выполнения плана и формирование управленческих отчётов.</p>
|
<p>Автоматический сбор, анализ и визуализация результатов внутренних проверок. Контроль выполнения плана и формирование управленческих отчётов.</p>
|
||||||
<div class="hero-actions">
|
<div class="hero-actions">
|
||||||
<a href="#quick" class="btn btn-primary">Инструменты</a>
|
<a href="#analyzer" class="btn btn-primary">Загрузить данные</a>
|
||||||
|
<a href="#quick" class="btn btn-outline">Инструменты</a>
|
||||||
<a href="#blocks" class="btn btn-outline">Возможности</a>
|
<a href="#blocks" class="btn btn-outline">Возможности</a>
|
||||||
<a href="#steps" class="btn btn-outline">Как работать</a>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
@ -502,11 +782,11 @@ body {
|
|||||||
<p>Таблица плановых показателей по подразделениям</p>
|
<p>Таблица плановых показателей по подразделениям</p>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
<a href="#" class="quick-card">
|
<a href="#analyzer" class="quick-card">
|
||||||
<span class="quick-card-icon blue">🤖</span>
|
<span class="quick-card-icon blue">🤖</span>
|
||||||
<div>
|
<div>
|
||||||
<h3>Дашборд ИИ-агента</h3>
|
<h3>Анализатор CSV</h3>
|
||||||
<p>Интерактивная аналитика и визуализация</p>
|
<p>Загрузите выгрузку из Google Sheets — получите аналитику</p>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
<a href="#" class="quick-card">
|
<a href="#" class="quick-card">
|
||||||
@ -605,8 +885,59 @@ body {
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</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 -->
|
<!-- Dashboard Preview -->
|
||||||
<section class="section" style="background: var(--gray-50);">
|
<section id="dashboard-preview" class="section" style="background: var(--gray-50);">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<span class="section-label">Дашборд</span>
|
<span class="section-label">Дашборд</span>
|
||||||
<h2>Визуализация данных</h2>
|
<h2>Визуализация данных</h2>
|
||||||
@ -677,22 +1008,22 @@ body {
|
|||||||
<div class="step">
|
<div class="step">
|
||||||
<div class="step-num"></div>
|
<div class="step-num"></div>
|
||||||
<div class="step-text">
|
<div class="step-text">
|
||||||
<h3>ИИ-агент обрабатывает данные</h3>
|
<h3>Выгрузите CSV и загрузите в анализатор</h3>
|
||||||
<p>Автоматический сбор новых записей, классификация нарушений по 9 категориям, расчёт выполнения плана проверок.</p>
|
<p>В Google Sheets: Файл → Скачать → CSV. Перетащите файл на эту страницу — анализатор сам разберёт данные.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="step">
|
<div class="step">
|
||||||
<div class="step-num"></div>
|
<div class="step-num"></div>
|
||||||
<div class="step-text">
|
<div class="step-text">
|
||||||
<h3>Смотрите дашборд в реальном времени</h3>
|
<h3>Смотрите аналитику</h3>
|
||||||
<p>Интерактивные графики динамики, структура нарушений, рейтинги подразделений и регионов. Обновляется ежедневно и по запросу.</p>
|
<p>Рейтинги категорий, подразделений и регионов. Графики, проблемные зоны, повторяющиеся нарушения — всё считается автоматически.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="step">
|
<div class="step">
|
||||||
<div class="step-num"></div>
|
<div class="step-num"></div>
|
||||||
<div class="step-text">
|
<div class="step-text">
|
||||||
<h3>Получайте готовый отчёт и рекомендации</h3>
|
<h3>Получайте выводы и рекомендации</h3>
|
||||||
<p>1-го числа каждого месяца — автоматическая аналитическая справка с выводами и рекомендациями. Отчёт в PDF, Excel и PowerPoint.</p>
|
<p>Анализатор формирует текстовые выводы с цифрами и конкретными предложениями по улучшению ситуации.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -704,7 +1035,7 @@ body {
|
|||||||
<div class="container">
|
<div class="container">
|
||||||
<span class="section-label">Классификация</span>
|
<span class="section-label">Классификация</span>
|
||||||
<h2>9 категорий нарушений</h2>
|
<h2>9 категорий нарушений</h2>
|
||||||
<p class="section-subtitle">ИИ-агент автоматически распределяет нарушения по категориям</p>
|
<p class="section-subtitle">Анализатор автоматически распределяет нарушения по категориям</p>
|
||||||
<div class="cat-grid">
|
<div class="cat-grid">
|
||||||
<span class="cat-tag"><span class="cat-dot d1"></span> Документация по БиОТ</span>
|
<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 d2"></span> Наряды-допуски</span>
|
||||||
@ -722,9 +1053,9 @@ body {
|
|||||||
<!-- CTA -->
|
<!-- CTA -->
|
||||||
<section class="cta">
|
<section class="cta">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<h2>Начните использовать ИИ-агента</h2>
|
<h2>Начните использовать анализатор</h2>
|
||||||
<p>Подключите Google Forms к системе и получайте аналитику автоматически. Все вопросы — в рабочий чат команды.</p>
|
<p>Выгрузите данные из Google Sheets в CSV — и получите полную аналитику за минуту. Все вопросы — в рабочий чат команды.</p>
|
||||||
<a href="#quick" class="btn">Перейти к инструментам</a>
|
<a href="#analyzer" class="btn">Загрузить данные</a>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@ -735,5 +1066,525 @@ body {
|
|||||||
</div>
|
</div>
|
||||||
</footer>
|
</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>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user