1916 lines
61 KiB
HTML
1916 lines
61 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;
|
||
}
|
||
|
||
/* Form */
|
||
.form-grid {
|
||
display: grid;
|
||
grid-template-columns: 1fr 1fr;
|
||
gap: 16px;
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.form-grid .full {
|
||
grid-column: 1 / -1;
|
||
}
|
||
|
||
.form-group {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 4px;
|
||
}
|
||
|
||
.form-group label {
|
||
font-size: 13px;
|
||
font-weight: 700;
|
||
color: var(--gray-500);
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.3px;
|
||
}
|
||
|
||
.form-group input,
|
||
.form-group select,
|
||
.form-group textarea {
|
||
padding: 10px 14px;
|
||
border: 1px solid var(--gray-200);
|
||
border-radius: 8px;
|
||
font-size: 15px;
|
||
font-family: inherit;
|
||
color: var(--ink);
|
||
background: var(--white);
|
||
transition: border-color 0.15s;
|
||
}
|
||
|
||
.form-group input:focus,
|
||
.form-group select:focus,
|
||
.form-group textarea:focus {
|
||
outline: none;
|
||
border-color: var(--blue);
|
||
box-shadow: 0 0 0 3px var(--blue-100);
|
||
}
|
||
|
||
.form-group textarea {
|
||
min-height: 72px;
|
||
resize: vertical;
|
||
}
|
||
|
||
.form-actions {
|
||
display: flex;
|
||
gap: 12px;
|
||
align-items: center;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.form-msg {
|
||
font-size: 14px;
|
||
font-weight: 600;
|
||
padding: 4px 0;
|
||
}
|
||
|
||
.form-msg.ok { color: var(--green); }
|
||
.form-msg.err { color: var(--red); }
|
||
|
||
/* Period filter */
|
||
.period-filter {
|
||
display: flex;
|
||
gap: 10px;
|
||
flex-wrap: wrap;
|
||
align-items: center;
|
||
margin-bottom: 24px;
|
||
}
|
||
|
||
.period-filter select,
|
||
.period-filter input {
|
||
padding: 8px 14px;
|
||
border: 1px solid var(--gray-200);
|
||
border-radius: 8px;
|
||
font-size: 14px;
|
||
font-family: inherit;
|
||
}
|
||
|
||
.period-filter label {
|
||
font-size: 13px;
|
||
font-weight: 700;
|
||
color: var(--gray-500);
|
||
}
|
||
|
||
/* Data actions */
|
||
.data-actions {
|
||
display: flex;
|
||
gap: 10px;
|
||
flex-wrap: wrap;
|
||
margin-bottom: 24px;
|
||
}
|
||
|
||
.data-actions .btn-sm {
|
||
font-size: 13px;
|
||
padding: 8px 16px;
|
||
}
|
||
|
||
.btn-red {
|
||
background: var(--red);
|
||
color: var(--white);
|
||
}
|
||
|
||
.btn-red:hover { background: #B91C1C; }
|
||
|
||
.btn-amber {
|
||
background: var(--amber);
|
||
color: var(--white);
|
||
}
|
||
|
||
.btn-amber:hover { background: #B45309; }
|
||
|
||
/* Entry list */
|
||
.entry-card {
|
||
background: var(--white);
|
||
border: 1px solid var(--gray-200);
|
||
border-radius: 10px;
|
||
padding: 14px 18px;
|
||
margin-bottom: 8px;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
font-size: 14px;
|
||
transition: border-color 0.15s;
|
||
}
|
||
|
||
.entry-card:hover { border-color: var(--blue-100); }
|
||
|
||
.entry-card .entry-date {
|
||
font-weight: 700;
|
||
color: var(--blue);
|
||
white-space: nowrap;
|
||
min-width: 72px;
|
||
}
|
||
|
||
.entry-card .entry-cat {
|
||
background: var(--gray-100);
|
||
padding: 2px 10px;
|
||
border-radius: 100px;
|
||
font-size: 12px;
|
||
font-weight: 600;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.entry-card .entry-desc {
|
||
flex: 1;
|
||
color: var(--gray-600);
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.entry-card .entry-status {
|
||
font-weight: 700;
|
||
font-size: 12px;
|
||
padding: 3px 10px;
|
||
border-radius: 100px;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.entry-status.fixed { background: var(--green-50); color: var(--green); }
|
||
.entry-status.pending { background: var(--red-50); color: var(--red); }
|
||
|
||
.entry-card .entry-del {
|
||
background: none;
|
||
border: none;
|
||
color: var(--gray-500);
|
||
cursor: pointer;
|
||
font-size: 16px;
|
||
padding: 4px;
|
||
line-height: 1;
|
||
}
|
||
|
||
.entry-card .entry-del:hover { color: var(--red); }
|
||
|
||
.entries-wrap {
|
||
max-height: 400px;
|
||
overflow-y: auto;
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
.entries-info {
|
||
font-size: 13px;
|
||
color: var(--gray-500);
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
/* Toggle panels */
|
||
.toggle-panel {
|
||
display: none;
|
||
}
|
||
|
||
.toggle-panel.active {
|
||
display: block;
|
||
}
|
||
|
||
.panel-switch {
|
||
display: flex;
|
||
gap: 0;
|
||
margin-bottom: 32px;
|
||
border: 1px solid var(--gray-200);
|
||
border-radius: 10px;
|
||
overflow: hidden;
|
||
width: fit-content;
|
||
}
|
||
|
||
.panel-switch button {
|
||
padding: 12px 24px;
|
||
border: none;
|
||
background: var(--white);
|
||
font-size: 14px;
|
||
font-weight: 600;
|
||
cursor: pointer;
|
||
font-family: inherit;
|
||
color: var(--ink);
|
||
transition: all 0.15s;
|
||
}
|
||
|
||
.panel-switch button:not(:last-child) {
|
||
border-right: 1px solid var(--gray-200);
|
||
}
|
||
|
||
.panel-switch button.active {
|
||
background: var(--blue);
|
||
color: var(--white);
|
||
}
|
||
|
||
.panel-switch button:hover:not(.active) {
|
||
background: var(--gray-50);
|
||
}
|
||
|
||
/* 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; }
|
||
.form-grid { grid-template-columns: 1fr; }
|
||
.entry-card { flex-wrap: wrap; }
|
||
.entry-card .entry-desc { white-space: normal; }
|
||
}
|
||
</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>
|
||
|
||
<!-- ========================================= -->
|
||
<!-- INTERACTIVE TOOL -->
|
||
<!-- ========================================= -->
|
||
<section id="analyzer" class="section analyzer">
|
||
<div class="container">
|
||
<span class="section-label">Рабочий инструмент</span>
|
||
<h2>Ввод и анализ нарушений</h2>
|
||
<p class="section-subtitle">Вносите нарушения, выявленные в ходе проверок. Данные сохраняются в браузере. Всегда доступен дашборд с аналитикой по периодам.</p>
|
||
|
||
<!-- Panel switch -->
|
||
<div class="panel-switch" id="panelSwitch">
|
||
<button class="active" data-panel="panel-input">Ввод данных</button>
|
||
<button data-panel="panel-dashboard">Дашборд</button>
|
||
<button data-panel="panel-list">Все записи</button>
|
||
<button data-panel="panel-import">Импорт CSV</button>
|
||
</div>
|
||
|
||
<!-- PANEL 1: Input form -->
|
||
<div class="toggle-panel active" id="panel-input">
|
||
<div class="form-grid">
|
||
<div class="form-group">
|
||
<label>Дата проверки</label>
|
||
<input type="date" id="fDate" required>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>Филиал</label>
|
||
<select id="fFilial">
|
||
<option value="">Выберите...</option>
|
||
<option>ОДС</option>
|
||
<option>ДРБ</option>
|
||
<option>ДИТ</option>
|
||
<option>Сервисная фабрика</option>
|
||
<option>ДКБ</option>
|
||
<option>ДУП</option>
|
||
<option>ДТК</option>
|
||
<option>СФ</option>
|
||
<option>ЦТО СФ</option>
|
||
<option>ЦЭиК</option>
|
||
</select>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>Область</label>
|
||
<select id="fRegion">
|
||
<option value="">Выберите...</option>
|
||
<option>Акмолинская</option>
|
||
<option>Астана</option>
|
||
<option>Карагандинская</option>
|
||
<option>Улытауская</option>
|
||
<option>Костанайская</option>
|
||
<option>Павлодарская</option>
|
||
<option>СКО</option>
|
||
</select>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>Город / Район</label>
|
||
<input type="text" id="fCity" placeholder="Например: Шахтинск">
|
||
</div>
|
||
<div class="form-group">
|
||
<label>Объект с адресом</label>
|
||
<input type="text" id="fAddr" placeholder="Например: ул. Казахстанская 100а">
|
||
</div>
|
||
<div class="form-group">
|
||
<label>Кому выдано</label>
|
||
<input type="text" id="fIssuedTo" placeholder="Например: Начальник ЛТЦ">
|
||
</div>
|
||
<div class="form-group">
|
||
<label>Категория нарушения</label>
|
||
<select id="fCat">
|
||
<option value="">Выберите...</option>
|
||
<option>основные док-ты по БиОТ</option>
|
||
<option>Пожарная безопасность</option>
|
||
<option>Электробезопасность</option>
|
||
<option>СИЗ</option>
|
||
<option>Транспортная безопасность</option>
|
||
<option>Промышленная безопасность</option>
|
||
<option>Санитарно-бытовые условия</option>
|
||
<option>организационные требования БиОТ</option>
|
||
<option>Производственная санитария</option>
|
||
<option>Инструкция по проведению 4-х ступенчатого контроля</option>
|
||
<option>Наряды-допуски</option>
|
||
</select>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>Статус</label>
|
||
<select id="fStatus">
|
||
<option value="на контроле">На контроле</option>
|
||
<option value="исполнено">Исполнено</option>
|
||
</select>
|
||
</div>
|
||
<div class="form-group full">
|
||
<label>Описание нарушения</label>
|
||
<textarea id="fDesc" placeholder="Кратко опишите нарушение..."></textarea>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>Срок исполнения</label>
|
||
<input type="date" id="fDeadline">
|
||
</div>
|
||
<div class="form-group">
|
||
<label>Проверяющий (кто выдал)</label>
|
||
<select id="fInspector">
|
||
<option value="">Выберите...</option>
|
||
<option>Инженер по БиОТ Семидоцкий С.А.</option>
|
||
<option>Ведущий инженер ОБиОТ Туржанов А.Т.</option>
|
||
<option>Инженер ОБиОТ Бачинская Н.В.</option>
|
||
<option>Ведущий инженер ОБиОТ Ажакметов М.З.</option>
|
||
<option>Ведущий инженер ОБиОТ Баяхметова Ж.Т.</option>
|
||
<option>Инженер ОБиОТ Садыков М.Ф.</option>
|
||
<option>Ведущий инженер ОБиОТ Тумабаева С.А.</option>
|
||
<option>Инженер по БиОТ Джусупова А.Т.</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
<div class="form-actions">
|
||
<button class="btn btn-blue" id="btnAdd">Добавить запись</button>
|
||
<span class="form-msg" id="formMsg"></span>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- PANEL 2: Dashboard -->
|
||
<div class="toggle-panel" id="panel-dashboard">
|
||
<div class="period-filter">
|
||
<label>Период:</label>
|
||
<select id="perPreset">
|
||
<option value="all">Всё время</option>
|
||
<option value="this-month">Текущий месяц</option>
|
||
<option value="prev-month">Прошлый месяц</option>
|
||
<option value="q1">1 квартал 2026</option>
|
||
<option value="q2">2 квартал 2026</option>
|
||
<option value="custom">Произвольный...</option>
|
||
</select>
|
||
<span id="perCustom" style="display:none">
|
||
с <input type="date" id="perFrom"> по <input type="date" id="perTo">
|
||
<button class="btn btn-blue btn-sm" id="btnPerApply">Применить</button>
|
||
</span>
|
||
</div>
|
||
<div class="result-grid" id="dashMetrics"></div>
|
||
<div id="dashCharts"></div>
|
||
<div id="dashConclusions"></div>
|
||
</div>
|
||
|
||
<!-- PANEL 3: All entries -->
|
||
<div class="toggle-panel" id="panel-list">
|
||
<div class="data-actions">
|
||
<button class="btn btn-blue btn-sm" id="btnExportCSV">Скачать CSV</button>
|
||
<button class="btn btn-red btn-sm" id="btnClear">Очистить все данные</button>
|
||
</div>
|
||
<div class="entries-info" id="entriesInfo"></div>
|
||
<div class="entries-wrap" id="entriesList"></div>
|
||
</div>
|
||
|
||
<!-- PANEL 4: Import CSV -->
|
||
<div class="toggle-panel" id="panel-import">
|
||
<div class="upload-area" id="dropArea">
|
||
<div class="upload-icon">📂</div>
|
||
<p><strong>Перетащите CSV-файл сюда</strong> или нажмите для выбора</p>
|
||
<p class="upload-hint">Данные добавятся к уже имеющимся записям</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>
|
||
</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 STORAGE_KEY = 'prombez_violations';
|
||
var FILIALS = ['ОДС','ДРБ','ДИТ','Сервисная фабрика','ДКБ','ДУП','ДТК','СФ','ЦТО СФ','ЦЭиК'];
|
||
|
||
// ---- Data layer ----
|
||
function loadData() {
|
||
try { return JSON.parse(localStorage.getItem(STORAGE_KEY)) || []; }
|
||
catch(e) { return []; }
|
||
}
|
||
|
||
function saveData(arr) {
|
||
try { localStorage.setItem(STORAGE_KEY, JSON.stringify(arr)); }
|
||
catch(e) { showMsg('Нет места в хранилище браузера. Экспортируйте данные.', 'err'); }
|
||
}
|
||
|
||
function addEntry(entry) {
|
||
var data = loadData();
|
||
entry.id = Date.now() + Math.random();
|
||
entry.created = new Date().toISOString();
|
||
data.unshift(entry);
|
||
saveData(data);
|
||
}
|
||
|
||
function appendEntries(entries) {
|
||
var data = loadData();
|
||
entries.forEach(function(e) {
|
||
e.id = Date.now() + Math.random();
|
||
e.created = new Date().toISOString();
|
||
data.unshift(e);
|
||
});
|
||
saveData(data);
|
||
}
|
||
|
||
function deleteEntry(id) {
|
||
var data = loadData().filter(function(e) { return e.id !== id; });
|
||
saveData(data);
|
||
}
|
||
|
||
function clearAll() {
|
||
if (confirm('Удалить ВСЕ записи? Это действие нельзя отменить.')) {
|
||
saveData([]);
|
||
renderAll();
|
||
}
|
||
}
|
||
|
||
// ---- Category normalization ----
|
||
function normalizeCategory(raw) {
|
||
if (!raw) return 'Без категории';
|
||
var s = raw.toLowerCase().trim();
|
||
if (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 || 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 || s.indexOf('инструкци') !== -1) return 'Документация по БиОТ';
|
||
if (s.length > 3) return raw.trim();
|
||
return 'Без категории';
|
||
}
|
||
|
||
function isFixed(val) {
|
||
var s = (val || '').toLowerCase().trim();
|
||
return s === 'исполнено' || s === 'орындалды' || s === 'устранено' || s.indexOf('исполн') !== -1;
|
||
}
|
||
|
||
// ---- Period filter ----
|
||
function filterByPeriod(data) {
|
||
var preset = document.getElementById('perPreset').value;
|
||
var now = new Date();
|
||
var from, to;
|
||
|
||
if (preset === 'this-month') {
|
||
from = new Date(now.getFullYear(), now.getMonth(), 1);
|
||
to = new Date(now.getFullYear(), now.getMonth() + 1, 0);
|
||
} else if (preset === 'prev-month') {
|
||
from = new Date(now.getFullYear(), now.getMonth() - 1, 1);
|
||
to = new Date(now.getFullYear(), now.getMonth(), 0);
|
||
} else if (preset === 'q1') {
|
||
from = new Date(2026, 0, 1); to = new Date(2026, 2, 31);
|
||
} else if (preset === 'q2') {
|
||
from = new Date(2026, 3, 1); to = new Date(2026, 5, 30);
|
||
} else if (preset === 'custom') {
|
||
from = document.getElementById('perFrom').value ? new Date(document.getElementById('perFrom').value) : null;
|
||
to = document.getElementById('perTo').value ? new Date(document.getElementById('perTo').value) : null;
|
||
}
|
||
|
||
if (preset === 'all' || (!from && !to)) return data;
|
||
|
||
return data.filter(function(r) {
|
||
var d = parseDate(r.date);
|
||
if (!d) return true;
|
||
if (from && d < from) return false;
|
||
if (to) { to.setHours(23,59,59,999); if (d > to) return false; }
|
||
return true;
|
||
});
|
||
}
|
||
|
||
function parseDate(str) {
|
||
if (!str) return null;
|
||
str = String(str).trim();
|
||
var parts = str.split(/[.\-\/]/);
|
||
if (parts.length === 3) {
|
||
var d = parseInt(parts[0]), m = parseInt(parts[1]), y = parseInt(parts[2]);
|
||
if (y < 100) y += 2000;
|
||
if (parts[2].length === 4) { d = parseInt(parts[0]); m = parseInt(parts[1]); }
|
||
else if (parts[0].length === 4) { y = parseInt(parts[0]); m = parseInt(parts[1]); d = parseInt(parts[2]); }
|
||
return new Date(y, m - 1, d);
|
||
}
|
||
return null;
|
||
}
|
||
|
||
// ---- Dashboard ----
|
||
function renderDashboard() {
|
||
var data = loadData();
|
||
var filtered = filterByPeriod(data);
|
||
var total = filtered.length;
|
||
if (total === 0) {
|
||
document.getElementById('dashMetrics').innerHTML = '<div style="grid-column:1/-1;text-align:center;padding:40px;color:var(--gray-500)">Нет данных за выбранный период</div>';
|
||
document.getElementById('dashCharts').innerHTML = '';
|
||
document.getElementById('dashConclusions').innerHTML = '';
|
||
return;
|
||
}
|
||
|
||
var fixed = filtered.filter(function(r) { return isFixed(r.status); }).length;
|
||
var pending = total - fixed;
|
||
|
||
var catMap = {};
|
||
var deptMap = {};
|
||
var regionMap = {};
|
||
var inspMap = {};
|
||
var monthlyMap = {};
|
||
|
||
filtered.forEach(function(r) {
|
||
var cat = normalizeCategory(r.category || r.cat || '');
|
||
if (!catMap[cat]) catMap[cat] = 0; catMap[cat]++;
|
||
|
||
var dept = r.filial || r.dept || 'Не указано';
|
||
if (!deptMap[dept]) deptMap[dept] = 0; deptMap[dept]++;
|
||
|
||
var reg = r.region || r.oblast || 'Не указано';
|
||
if (!regionMap[reg]) regionMap[reg] = 0; regionMap[reg]++;
|
||
|
||
var insp = r.inspector || r.issuedBy || 'Не указано';
|
||
if (!inspMap[insp]) inspMap[insp] = 0; inspMap[insp]++;
|
||
|
||
var d = parseDate(r.date);
|
||
if (d) {
|
||
var key = d.getFullYear() + '-' + String(d.getMonth() + 1).padStart(2, '0');
|
||
if (!monthlyMap[key]) monthlyMap[key] = 0; monthlyMap[key]++;
|
||
}
|
||
});
|
||
|
||
var catSorted = Object.entries(catMap).sort(function(a,b) { return b[1] - a[1]; });
|
||
var deptSorted = Object.entries(deptMap).sort(function(a,b) { return b[1] - a[1]; });
|
||
var regionSorted = Object.entries(regionMap).sort(function(a,b) { return b[1] - a[1]; });
|
||
var inspSorted = Object.entries(inspMap).sort(function(a,b) { return b[1] - a[1]; });
|
||
var monthlySorted = Object.entries(monthlyMap).sort();
|
||
|
||
// Metrics
|
||
var topCat = catSorted[0];
|
||
var htmlM = '';
|
||
htmlM += buildMetric(total, 'Всего записей');
|
||
htmlM += buildMetric(fixed, 'Устранено', 'good');
|
||
htmlM += buildMetric(pending, 'На контроле', pending > fixed ? 'warn' : '');
|
||
htmlM += buildMetric(Object.keys(catMap).length, 'Категорий');
|
||
if (topCat) {
|
||
htmlM += buildMetric(Math.round(topCat[1]/total*100) + '%', 'Доля «'+topCat[0]+'»', topCat[1]/total>0.25?'warn':'');
|
||
}
|
||
document.getElementById('dashMetrics').innerHTML = htmlM;
|
||
|
||
// Charts
|
||
var htmlC = '<div class="result-charts">';
|
||
htmlC += renderBarChart('По категориям', catSorted, total, 'dash-cat');
|
||
htmlC += renderBarChart('По филиалам', deptSorted, total, 'dash-dept');
|
||
htmlC += renderBarChart('По областям', regionSorted, total, 'dash-reg');
|
||
htmlC += renderBarChart('По проверяющим', inspSorted, total, 'dash-insp');
|
||
if (monthlySorted.length > 0) {
|
||
htmlC += renderBarChart('Динамика по месяцам', monthlySorted, total, 'dash-mon');
|
||
}
|
||
htmlC += '</div>';
|
||
document.getElementById('dashCharts').innerHTML = htmlC;
|
||
|
||
// Conclusions
|
||
var htmlCon = '';
|
||
var worstDept = deptSorted[deptSorted.length - 1];
|
||
if (topCat && topCat[1] / total > 0.2) {
|
||
htmlCon += '<div class="conclusion-box warn-box"><h4>Проблемная зона</h4><p>Наиболее частой категорией является <strong>' +
|
||
escHtml(topCat[0]).toLowerCase() + '</strong> — доля <strong>' +
|
||
Math.round(topCat[1]/total*100) + '%</strong> (' + topCat[1] + ' из ' + total + ' записей).</p></div>';
|
||
}
|
||
htmlCon += '<div class="conclusion-box"><h4>Общие выводы</h4><p>Всего <strong>' + total +
|
||
'</strong> записей. Устранено: <strong>' + fixed + '</strong> (' + Math.round(fixed/total*100) + '%). ' +
|
||
'На контроле: <strong>' + pending + '</strong>. ' +
|
||
'ТОП-3 категории: ' + catSorted.slice(0,3).map(function(c,i) {
|
||
return (i+1)+') '+escHtml(c[0])+' — '+c[1]+' ('+Math.round(c[1]/total*100)+'%)';
|
||
}).join('; ') + '.</p></div>';
|
||
if (pending > fixed) {
|
||
htmlCon += '<div class="conclusion-box"><h4>Рекомендации</h4><p>На контроле <strong>' + pending +
|
||
'</strong> нарушений. Усилить контроль устранения. ';
|
||
if (topCat) {
|
||
var c = topCat[0];
|
||
if (c === 'Средства индивидуальной защиты') htmlCon += 'По СИЗ: внеплановые проверки и целевой инструктаж.';
|
||
else if (c === 'Наряды-допуски') htmlCon += 'По нарядам-допускам: доп. обучение и аудит документации.';
|
||
else if (c === 'Электробезопасность') htmlCon += 'По электробезопасности: усилить контроль.';
|
||
else if (c === 'Пожарная безопасность') htmlCon += 'По пожарной безопасности: проверить инструктажи.';
|
||
else htmlCon += 'По «'+escHtml(c)+'»: внеплановая проверка.';
|
||
}
|
||
htmlCon += '</p></div>';
|
||
}
|
||
document.getElementById('dashConclusions').innerHTML = htmlCon;
|
||
}
|
||
|
||
function renderBarChart(title, items, total, id) {
|
||
var html = '<div class="result-chart-card"><h3>' + title + '</h3>';
|
||
var shown = items.slice(0, 12);
|
||
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;
|
||
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" style="width:' + barPct + '%"></div></div></div>';
|
||
});
|
||
html += '</div>';
|
||
return html;
|
||
}
|
||
|
||
function buildMetric(value, label, cls) {
|
||
return '<div class="result-metric' + (cls ? ' ' + cls : '') + '"><div class="value">' +
|
||
value + '</div><div class="label">' + label + '</div></div>';
|
||
}
|
||
|
||
// ---- Entry list ----
|
||
function renderEntryList() {
|
||
var data = loadData();
|
||
document.getElementById('entriesInfo').textContent = 'Всего записей: ' + data.length;
|
||
var html = '';
|
||
data.slice(0, 200).forEach(function(r, i) {
|
||
var fixed = isFixed(r.status);
|
||
html += '<div class="entry-card">' +
|
||
'<span class="entry-date">' + escHtml(r.date || '—') + '</span>' +
|
||
'<span class="entry-cat">' + escHtml(normalizeCategory(r.category || r.cat || '')) + '</span>' +
|
||
'<span class="entry-desc">' + escHtml((r.desc || r.description || '').slice(0, 80)) + '</span>' +
|
||
'<span class="entry-status ' + (fixed ? 'fixed' : 'pending') + '">' + (fixed ? 'Исполнено' : 'На контроле') + '</span>' +
|
||
'<button class="entry-del" data-id="' + r.id + '" title="Удалить">×</button>' +
|
||
'</div>';
|
||
});
|
||
if (data.length === 0) html = '<p style="color:var(--gray-500);text-align:center;padding:40px">Нет записей. Добавьте через форму «Ввод данных».</p>';
|
||
if (data.length > 200) html += '<p style="font-size:13px;color:var(--gray-500);text-align:center">Показаны последние 200 из ' + data.length + '</p>';
|
||
document.getElementById('entriesList').innerHTML = html;
|
||
|
||
document.querySelectorAll('.entry-del').forEach(function(btn) {
|
||
btn.addEventListener('click', function() {
|
||
deleteEntry(parseFloat(btn.getAttribute('data-id')));
|
||
renderAll();
|
||
});
|
||
});
|
||
}
|
||
|
||
// ---- Form handling ----
|
||
function submitForm() {
|
||
var entry = {
|
||
date: document.getElementById('fDate').value,
|
||
filial: document.getElementById('fFilial').value,
|
||
region: document.getElementById('fRegion').value,
|
||
city: document.getElementById('fCity').value,
|
||
addr: document.getElementById('fAddr').value,
|
||
issuedTo: document.getElementById('fIssuedTo').value,
|
||
cat: document.getElementById('fCat').value,
|
||
status: document.getElementById('fStatus').value,
|
||
desc: document.getElementById('fDesc').value,
|
||
deadline: document.getElementById('fDeadline').value,
|
||
inspector: document.getElementById('fInspector').value
|
||
};
|
||
|
||
if (!entry.date || !entry.filial || !entry.cat || !entry.desc) {
|
||
showMsg('Заполните обязательные поля: дата, филиал, категория, описание', 'err');
|
||
return;
|
||
}
|
||
|
||
addEntry(entry);
|
||
showMsg('Запись добавлена!', 'ok');
|
||
document.getElementById('fDesc').value = '';
|
||
document.getElementById('fDate').value = '';
|
||
document.getElementById('fIssuedTo').value = '';
|
||
document.getElementById('fAddr').value = '';
|
||
document.getElementById('fCity').value = '';
|
||
document.getElementById('fDeadline').value = '';
|
||
renderAll();
|
||
setTimeout(function() { showMsg('', ''); }, 2500);
|
||
}
|
||
|
||
function showMsg(text, cls) {
|
||
var el = document.getElementById('formMsg');
|
||
el.textContent = text;
|
||
el.className = 'form-msg ' + cls;
|
||
}
|
||
|
||
// ---- CSV Export ----
|
||
function exportCSV() {
|
||
var data = loadData();
|
||
if (data.length === 0) { alert('Нет данных для экспорта.'); return; }
|
||
var header = 'Дата,Филиал,Область,Город/Район,Объект,Категория нарушения,Описание,Статус,Срок,Проверяющий,Кому выдано';
|
||
var lines = [header];
|
||
data.forEach(function(r) {
|
||
lines.push([
|
||
r.date || '', r.filial || '', r.region || '', r.city || '',
|
||
r.addr || '', r.cat || r.category || '', r.desc || r.description || '',
|
||
r.status || '', r.deadline || '', r.inspector || r.issuedBy || '', r.issuedTo || ''
|
||
].map(function(v) { return '"' + String(v).replace(/"/g, '""') + '"'; }).join(','));
|
||
});
|
||
var blob = new Blob(['\uFEFF' + lines.join('\n')], { type: 'text/csv;charset=utf-8' });
|
||
var a = document.createElement('a');
|
||
a.href = URL.createObjectURL(blob);
|
||
a.download = 'violations_export_' + new Date().toISOString().slice(0,10) + '.csv';
|
||
a.click();
|
||
}
|
||
|
||
// ---- CSV Import ----
|
||
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 importCSV(rows) {
|
||
if (rows.length < 2) return 0;
|
||
var headers = rows[0].map(function(h) { return h.toLowerCase().trim(); });
|
||
var data = rows.slice(1);
|
||
|
||
function idx(keys) {
|
||
for (var i = 0; i < headers.length; i++)
|
||
for (var k = 0; k < keys.length; k++)
|
||
if (headers[i].indexOf(keys[k]) !== -1) return i;
|
||
return -1;
|
||
}
|
||
|
||
var ciDate = idx(['дата']);
|
||
var ciFilial = idx(['филиал']);
|
||
var ciRegion = idx(['область','регион']);
|
||
var ciCity = idx(['город','район']);
|
||
var ciAddr = idx(['объект','адрес']);
|
||
var ciCat = idx(['категор','нарушен']);
|
||
var ciDesc = -1;
|
||
for (var hi = 0; hi < headers.length; hi++) {
|
||
if (headers[hi].indexOf('категор') !== -1 && hi !== ciCat) { ciDesc = hi; break; }
|
||
if (headers[hi].indexOf('описан') !== -1 || headers[hi].indexOf('суть') !== -1) { ciDesc = hi; break; }
|
||
}
|
||
if (ciDesc < 0) ciDesc = ciCat >= 0 ? ciCat + 1 : -1;
|
||
var ciStatus = idx(['исполнен','статус','устран']);
|
||
var ciDeadline = idx(['срок']);
|
||
var ciInspector = idx(['выдал','кто выдал','проверяющ']);
|
||
var ciIssued = idx(['кому','выдано']);
|
||
|
||
var entries = [];
|
||
data.forEach(function(r) {
|
||
if (r.length < 2) return;
|
||
entries.push({
|
||
date: ciDate >= 0 ? r[ciDate] || '' : '',
|
||
filial: ciFilial >= 0 ? r[ciFilial] || '' : '',
|
||
region: ciRegion >= 0 ? r[ciRegion] || '' : '',
|
||
city: ciCity >= 0 ? r[ciCity] || '' : '',
|
||
addr: ciAddr >= 0 ? r[ciAddr] || '' : '',
|
||
cat: ciCat >= 0 ? r[ciCat] || '' : '',
|
||
desc: ciDesc >= 0 && ciDesc < r.length ? r[ciDesc] || '' : '',
|
||
status: ciStatus >= 0 ? r[ciStatus] || '' : '',
|
||
deadline: ciDeadline >= 0 ? r[ciDeadline] || '' : '',
|
||
inspector: ciInspector >= 0 ? r[ciInspector] || '' : '',
|
||
issuedTo: ciIssued >= 0 ? r[ciIssued] || '' : ''
|
||
});
|
||
});
|
||
|
||
if (entries.length > 0) {
|
||
appendEntries(entries);
|
||
}
|
||
return entries.length;
|
||
}
|
||
|
||
function processCSVFile(file) {
|
||
document.getElementById('uploadStatus').textContent = 'Обработка...';
|
||
var reader = new FileReader();
|
||
reader.onload = function(e) {
|
||
try {
|
||
var text = e.target.result;
|
||
if (text.charCodeAt(0) === 0xFEFF) text = text.slice(1);
|
||
var rows = parseCSV(text);
|
||
var count = importCSV(rows);
|
||
if (count === 0) {
|
||
document.getElementById('uploadStatus').textContent = 'Не удалось распознать данные.';
|
||
} else {
|
||
document.getElementById('uploadStatus').textContent = 'Импортировано: ' + count + ' записей из «' + file.name + '»';
|
||
renderAll();
|
||
}
|
||
} catch(err) {
|
||
document.getElementById('uploadStatus').textContent = 'Ошибка: ' + err.message;
|
||
}
|
||
};
|
||
reader.readAsText(file, 'UTF-8');
|
||
}
|
||
|
||
function generateSampleCSV() {
|
||
var header = 'Дата,Филиал,Область,Город/Район,Объект с адресом,Категория нарушения,Описание нарушения,Срок исполнения,исполнено,кто выдал,кому выдано';
|
||
var filials = ['ОДС','ДРБ','ДИТ','Сервисная фабрика','ДКБ','ДУП','ЦТО СФ','ЦЭиК'];
|
||
var oblasts = ['Акмолинская','Карагандинская','Астана','Астана','Улытауская','Карагандинская','Акмолинская'];
|
||
var cities = ['Шахтинск','Астана','Караганда','Темиртау','Щучинск','Атбасар','Балхаш','Кокшетау'];
|
||
var inspectors = [
|
||
'Инженер по БиОТ Семидоцкий С.А.','Ведущий инженер ОБиОТ Туржанов А.Т.',
|
||
'Инженер ОБиОТ Бачинская Н.В.','Ведущий инженер ОБиОТ Ажакметов М.З.',
|
||
'Ведущий инженер ОБиОТ Баяхметова Ж.Т.','Инженер ОБиОТ Садыков М.Ф.'
|
||
];
|
||
var realCats = ['основные док-ты по БиОТ','Пожарная безопасность','Электробезопасность','СИЗ','Транспортная безопасность','Промышленная безопасность','Санитарно-бытовые условия','организационные требования БиОТ'];
|
||
var descs = [
|
||
'Отсутствует журнал инструктажа','Не проведён повторный инструктаж','Работники не ознакомлены с регламентом СИЗ',
|
||
'Просроченные диэлектрические перчатки','Неактуальные инструкции по ТБ','Отсутствует аптечка',
|
||
'Пожарный кран не проверялся','Работники без касок','Прошёл срок поверки инструмента',
|
||
'Аварийный выход заблокирован','На территории скопление снега'
|
||
];
|
||
var statuses = ['исполнено','исполнено','исполнено','на контроле','на контроле','исполнено','на контроле'];
|
||
var months = ['01','02','03','04','05','06'];
|
||
|
||
var lines = [header];
|
||
for (var i = 0; i < 200; i++) {
|
||
var d = String(Math.floor(Math.random()*28)+1).padStart(2,'0') + '.' + months[Math.floor(Math.random()*months.length)] + '.2026';
|
||
lines.push([d, filials[Math.floor(Math.random()*filials.length)], oblasts[Math.floor(Math.random()*oblasts.length)],
|
||
cities[Math.floor(Math.random()*cities.length)], 'ул. Примерная '+(Math.floor(Math.random()*50)+1),
|
||
realCats[Math.floor(Math.random()*realCats.length)], descs[Math.floor(Math.random()*descs.length)],
|
||
'', statuses[Math.floor(Math.random()*statuses.length)],
|
||
inspectors[Math.floor(Math.random()*inspectors.length)], ''].join(','));
|
||
}
|
||
return lines.join('\n');
|
||
}
|
||
|
||
// ---- Panel switching ----
|
||
function switchPanel(name) {
|
||
document.querySelectorAll('.toggle-panel').forEach(function(p) { p.classList.remove('active'); });
|
||
document.querySelectorAll('.panel-switch button').forEach(function(b) { b.classList.remove('active'); });
|
||
var panel = document.getElementById(name);
|
||
if (panel) panel.classList.add('active');
|
||
var btn = document.querySelector('[data-panel="' + name + '"]');
|
||
if (btn) btn.classList.add('active');
|
||
|
||
if (name === 'panel-dashboard') renderDashboard();
|
||
if (name === 'panel-list') renderEntryList();
|
||
}
|
||
|
||
function renderAll() {
|
||
renderDashboard();
|
||
renderEntryList();
|
||
}
|
||
|
||
// ---- ESC helper ----
|
||
function escHtml(s) {
|
||
return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');
|
||
}
|
||
|
||
// ---- Init ----
|
||
function init() {
|
||
// Panel switch clicks
|
||
document.querySelectorAll('.panel-switch button').forEach(function(btn) {
|
||
btn.addEventListener('click', function() {
|
||
switchPanel(btn.getAttribute('data-panel'));
|
||
});
|
||
});
|
||
|
||
// Form submit
|
||
document.getElementById('btnAdd').addEventListener('click', submitForm);
|
||
|
||
// Period filter
|
||
document.getElementById('perPreset').addEventListener('change', function() {
|
||
document.getElementById('perCustom').style.display = this.value === 'custom' ? 'inline' : 'none';
|
||
renderDashboard();
|
||
});
|
||
document.getElementById('btnPerApply').addEventListener('click', renderDashboard);
|
||
|
||
// Export / Clear
|
||
document.getElementById('btnExportCSV').addEventListener('click', exportCSV);
|
||
document.getElementById('btnClear').addEventListener('click', clearAll);
|
||
|
||
// File drop / import
|
||
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) processCSVFile(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) processCSVFile(e.dataTransfer.files[0]);
|
||
});
|
||
|
||
// Sample data
|
||
document.getElementById('sampleBtn').addEventListener('click', function() {
|
||
document.getElementById('uploadStatus').textContent = 'Загрузка демо-данных...';
|
||
var blob = new Blob(['\uFEFF' + generateSampleCSV()], { type: 'text/csv;charset=utf-8' });
|
||
processCSVFile(new File([blob], 'demo.csv', { type: 'text/csv' }));
|
||
});
|
||
|
||
// Initial render
|
||
renderDashboard();
|
||
renderEntryList();
|
||
}
|
||
|
||
init();
|
||
})();
|
||
</script>
|
||
|
||
</body>
|
||
</html>
|