327 lines
13 KiB
HTML
327 lines
13 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="ru" data-theme="light" data-kt-data-mode="demo">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||
<title>Учёт кабельной продукции</title>
|
||
<link rel="stylesheet" href="design-system/kt-ai-fonts.css">
|
||
<link rel="stylesheet" href="design-system/kt-ai-tokens.css">
|
||
<link rel="stylesheet" href="design-system/kt-ai-components.css">
|
||
<link rel="stylesheet" href="design-system/kt-ai-page.css">
|
||
<style>
|
||
a.kt-ai-btn { text-decoration: none; }
|
||
.kt-ai-app { min-height: 100vh; background: var(--kt-ai-bg); color: var(--kt-ai-fg); font-family: var(--kt-ai-font-sans); }
|
||
.app-header { display: flex; align-items: center; gap: var(--kt-ai-space-4); padding: var(--kt-ai-space-4) 0; }
|
||
.app-title { font-size: var(--kt-ai-text-xl); font-weight: var(--kt-ai-weight-semibold); }
|
||
.kpi-section { padding: var(--kt-ai-space-6) 0; }
|
||
.filters-section { padding: var(--kt-ai-space-4) 0; display: flex; gap: var(--kt-ai-space-4); flex-wrap: wrap; align-items: center; }
|
||
.table-section { padding: var(--kt-ai-space-4) 0; }
|
||
.kt-ai-table-wrap { overflow-x: auto; }
|
||
.kt-ai-table { width: 100%; border-collapse: collapse; }
|
||
.kt-ai-table th { text-align: left; padding: var(--kt-ai-space-3) var(--kt-ai-space-4); font-weight: var(--kt-ai-weight-medium); color: var(--kt-ai-fg-muted); border-bottom: 1px solid var(--kt-ai-border); }
|
||
.kt-ai-table td { padding: var(--kt-ai-space-3) var(--kt-ai-space-4); border-bottom: 1px solid var(--kt-ai-border-strong); vertical-align: middle; }
|
||
.kt-ai-table tr:hover td { background: var(--kt-ai-bg-soft); }
|
||
.kt-ai-table .num { text-align: right; font-variant-numeric: tabular-nums; }
|
||
.kt-ai-table .actions { white-space: nowrap; }
|
||
.empty-state { text-align: center; padding: var(--kt-ai-space-12) var(--kt-ai-space-4); color: var(--kt-ai-fg-muted); }
|
||
.form-section { padding: var(--kt-ai-space-6) 0; }
|
||
.form-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: var(--kt-ai-space-4); max-width: 800px; }
|
||
.filter-note { font-size: var(--kt-ai-text-xs); color: var(--kt-ai-fg-faint); margin-left: var(--kt-ai-space-2); }
|
||
</style>
|
||
</head>
|
||
<body class="kt-ai-app">
|
||
|
||
<div class="kt-ai-wrap">
|
||
<header class="app-header">
|
||
<img src="https://code.vibe42.kz/tore/aitu/kt-logo.svg" alt="Казахтелеком" height="28">
|
||
<span class="app-title">Учёт кабельной продукции</span>
|
||
<span class="kt-ai-spacer"></span>
|
||
<span class="kt-ai-chip" data-tone="orange">Демо-данные</span>
|
||
</header>
|
||
</div>
|
||
|
||
<section class="kpi-section">
|
||
<div class="kt-ai-wrap">
|
||
<div class="kt-ai-kpi-strip">
|
||
<div class="kt-ai-kpi">
|
||
<div class="value" id="kpi-total">0</div>
|
||
<div class="label">Всего</div>
|
||
<div class="hint">кабельных позиций</div>
|
||
</div>
|
||
<div class="kt-ai-kpi">
|
||
<div class="value" id="kpi-in-work">0</div>
|
||
<div class="label">В работе</div>
|
||
<div class="hint">активных задач</div>
|
||
</div>
|
||
<div class="kt-ai-kpi">
|
||
<div class="value" id="kpi-ready">0</div>
|
||
<div class="label">Готово</div>
|
||
<div class="hint">завершено</div>
|
||
</div>
|
||
<div class="kt-ai-kpi">
|
||
<div class="value" id="kpi-meters">0</div>
|
||
<div class="label">Метров</div>
|
||
<div class="hint">общая длина</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="filters-section">
|
||
<div class="kt-ai-wrap">
|
||
<div class="kt-ai-field" style="min-width: 180px;">
|
||
<label>Тип кабеля</label>
|
||
<select class="kt-ai-select" id="filter-type">
|
||
<option value="">Все типы</option>
|
||
<option value="Силовой">Силовой</option>
|
||
<option value="Оптоволоконный">Оптоволоконный</option>
|
||
<option value="Коаксиальный">Коаксиальный</option>
|
||
<option value="Контрольный">Контрольный</option>
|
||
</select>
|
||
</div>
|
||
<div class="kt-ai-field" style="min-width: 160px;">
|
||
<label>Статус</label>
|
||
<select class="kt-ai-select" id="filter-status">
|
||
<option value="">Все статусы</option>
|
||
<option value="new">Новый</option>
|
||
<option value="in-work">В работе</option>
|
||
<option value="ready">Готово</option>
|
||
<option value="archived">Архив</option>
|
||
</select>
|
||
<span class="filter-note" data-kt-filter-note>в демо не меняет KPI</span>
|
||
</div>
|
||
<div class="kt-ai-field" style="min-width: 200px;">
|
||
<label>Поиск</label>
|
||
<input class="kt-ai-input" id="filter-search" placeholder="Название или сечение...">
|
||
</div>
|
||
<span class="kt-ai-spacer"></span>
|
||
<button class="kt-ai-btn" data-variant="ghost" id="btn-reset">Сбросить</button>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="table-section">
|
||
<div class="kt-ai-wrap">
|
||
<div class="kt-ai-table-wrap">
|
||
<table class="kt-ai-table">
|
||
<thead>
|
||
<tr>
|
||
<th>Название</th>
|
||
<th>Тип</th>
|
||
<th class="num">Сечение, мм²</th>
|
||
<th class="num">Длина, м</th>
|
||
<th>Статус</th>
|
||
<th>Дата</th>
|
||
<th class="actions">Действия</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="cable-table-body"></tbody>
|
||
</table>
|
||
</div>
|
||
<div class="kt-ai-empty" id="empty-state" style="display: none;">
|
||
<div class="empty-state">
|
||
<p style="font-size: var(--kt-ai-text-lg); margin-bottom: var(--kt-ai-space-2);">Нет данных</p>
|
||
<p>По выбранным фильтрам ничего не найдено</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="form-section">
|
||
<div class="kt-ai-wrap">
|
||
<div class="kt-ai-section-head">
|
||
<h2>Добавить кабель</h2>
|
||
<p>Заполните форму для создания новой записи</p>
|
||
</div>
|
||
<form id="add-cable-form" class="form-grid">
|
||
<div class="kt-ai-field">
|
||
<label>Название *</label>
|
||
<input class="kt-ai-input" name="name" required placeholder="Например: АВВГ 3х2.5">
|
||
</div>
|
||
<div class="kt-ai-field">
|
||
<label>Тип *</label>
|
||
<select class="kt-ai-select" name="type" required>
|
||
<option value="">Выберите тип</option>
|
||
<option value="Силовой">Силовой</option>
|
||
<option value="Оптоволоконный">Оптоволоконный</option>
|
||
<option value="Коаксиальный">Коаксиальный</option>
|
||
<option value="Контрольный">Контрольный</option>
|
||
</select>
|
||
</div>
|
||
<div class="kt-ai-field">
|
||
<label>Сечение, мм² *</label>
|
||
<input class="kt-ai-input" name="section" type="number" step="0.1" required placeholder="2.5">
|
||
</div>
|
||
<div class="kt-ai-field">
|
||
<label>Длина, м *</label>
|
||
<input class="kt-ai-input" name="length" type="number" required placeholder="100">
|
||
</div>
|
||
<div class="kt-ai-field">
|
||
<label>Статус *</label>
|
||
<select class="kt-ai-select" name="status" required>
|
||
<option value="new">Новый</option>
|
||
<option value="in-work">В работе</option>
|
||
<option value="ready">Готово</option>
|
||
</select>
|
||
</div>
|
||
<div class="kt-ai-field">
|
||
<label>Дата *</label>
|
||
<input class="kt-ai-input" name="date" type="date" required>
|
||
</div>
|
||
<div style="grid-column: 1 / -1;">
|
||
<button class="kt-ai-btn" data-variant="primary" type="submit">Добавить в учёт</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</section>
|
||
|
||
<div class="kt-ai-wrap kt-ai-pagefoot">
|
||
<span>© 2026 Казахтелеком — Учёт кабельной продукции</span>
|
||
<span class="kt-ai-spacer"></span>
|
||
<span>Демо-прототип</span>
|
||
</div>
|
||
|
||
<script>
|
||
const STORAGE_KEY = 'kt_cables_data';
|
||
|
||
const DEFAULT_CABLES = [
|
||
{ id: 1, name: 'АВВГ 3х2.5', type: 'Силовой', section: 2.5, length: 150, status: 'in-work', date: '2026-09-10' },
|
||
{ id: 2, name: 'АВВГ 3х4.0', type: 'Силовой', section: 4.0, length: 200, status: 'new', date: '2026-09-12' },
|
||
{ id: 3, name: 'АВВГ 3х2.5', type: 'Силовой', section: 2.5, length: 80, status: 'ready', date: '2026-09-08' },
|
||
{ id: 4, name: 'ОКЛ-Т 4х9', type: 'Оптоволоконный', section: 9, length: 500, status: 'in-work', date: '2026-09-11' },
|
||
{ id: 5, name: 'КВВГ 7х1.5', type: 'Контрольный', section: 1.5, length: 120, status: 'archived', date: '2026-09-05' }
|
||
];
|
||
|
||
function getCables() {
|
||
const stored = localStorage.getItem(STORAGE_KEY);
|
||
if (stored) {
|
||
const parsed = JSON.parse(stored);
|
||
return parsed.userCables || [];
|
||
}
|
||
return DEFAULT_CABLES;
|
||
}
|
||
|
||
function saveCable(cable) {
|
||
const stored = localStorage.getItem(STORAGE_KEY);
|
||
let data = stored ? JSON.parse(stored) : { userCables: [] };
|
||
if (!data.userCables) data.userCables = [];
|
||
cable.id = Date.now();
|
||
data.userCables.push(cable);
|
||
localStorage.setItem(STORAGE_KEY, JSON.stringify(data));
|
||
}
|
||
|
||
function deleteCable(id) {
|
||
const stored = localStorage.getItem(STORAGE_KEY);
|
||
if (stored) {
|
||
let data = JSON.parse(stored);
|
||
if (data.userCables) {
|
||
data.userCables = data.userCables.filter(c => c.id !== id);
|
||
localStorage.setItem(STORAGE_KEY, JSON.stringify(data));
|
||
}
|
||
}
|
||
}
|
||
|
||
function getStatusChip(status) {
|
||
const map = {
|
||
'new': { label: 'Новый', tone: 'blue' },
|
||
'in-work': { label: 'В работе', tone: 'orange' },
|
||
'ready': { label: 'Готово', tone: 'green' },
|
||
'archived': { label: 'Архив', tone: 'gray' }
|
||
};
|
||
const s = map[status] || { label: status, tone: 'gray' };
|
||
return `<span class="kt-ai-chip" data-tone="${s.tone}">${s.label}</span>`;
|
||
}
|
||
|
||
function formatDate(dateStr) {
|
||
const d = new Date(dateStr);
|
||
return d.toLocaleDateString('ru-RU', { day: '2-digit', month: '2-digit', year: '2-digit' });
|
||
}
|
||
|
||
function updateKPI(cables) {
|
||
document.getElementById('kpi-total').textContent = cables.length;
|
||
document.getElementById('kpi-in-work').textContent = cables.filter(c => c.status === 'in-work').length;
|
||
document.getElementById('kpi-ready').textContent = cables.filter(c => c.status === 'ready').length;
|
||
document.getElementById('kpi-meters').textContent = cables.reduce((sum, c) => sum + (parseInt(c.length) || 0), 0);
|
||
}
|
||
|
||
function renderTable(cables) {
|
||
const tbody = document.getElementById('cable-table-body');
|
||
const empty = document.getElementById('empty-state');
|
||
|
||
if (cables.length === 0) {
|
||
tbody.innerHTML = '';
|
||
empty.style.display = 'block';
|
||
return;
|
||
}
|
||
|
||
empty.style.display = 'none';
|
||
tbody.innerHTML = cables.map(c => `
|
||
<tr>
|
||
<td>${c.name}</td>
|
||
<td>${c.type}</td>
|
||
<td class="num">${c.section}</td>
|
||
<td class="num">${c.length}</td>
|
||
<td>${getStatusChip(c.status)}</td>
|
||
<td>${formatDate(c.date)}</td>
|
||
<td class="actions">
|
||
<button class="kt-ai-btn" data-variant="ghost" data-size="sm" onclick="deleteRow(${c.id})">Удалить</button>
|
||
</td>
|
||
</tr>
|
||
`).join('');
|
||
}
|
||
|
||
function applyFilters() {
|
||
const typeFilter = document.getElementById('filter-type').value;
|
||
const statusFilter = document.getElementById('filter-status').value;
|
||
const searchFilter = document.getElementById('filter-search').value.toLowerCase();
|
||
|
||
let cables = getCables();
|
||
|
||
if (typeFilter) cables = cables.filter(c => c.type === typeFilter);
|
||
if (statusFilter) cables = cables.filter(c => c.status === statusFilter);
|
||
if (searchFilter) cables = cables.filter(c =>
|
||
c.name.toLowerCase().includes(searchFilter) ||
|
||
String(c.section).includes(searchFilter)
|
||
);
|
||
|
||
updateKPI(cables);
|
||
renderTable(cables);
|
||
}
|
||
|
||
function deleteRow(id) {
|
||
deleteCable(id);
|
||
applyFilters();
|
||
}
|
||
|
||
document.getElementById('filter-type').addEventListener('change', applyFilters);
|
||
document.getElementById('filter-status').addEventListener('change', applyFilters);
|
||
document.getElementById('filter-search').addEventListener('input', applyFilters);
|
||
|
||
document.getElementById('btn-reset').addEventListener('click', () => {
|
||
document.getElementById('filter-type').value = '';
|
||
document.getElementById('filter-status').value = '';
|
||
document.getElementById('filter-search').value = '';
|
||
applyFilters();
|
||
});
|
||
|
||
document.getElementById('add-cable-form').addEventListener('submit', (e) => {
|
||
e.preventDefault();
|
||
const form = e.target;
|
||
const cable = {
|
||
name: form.name.value,
|
||
type: form.type.value,
|
||
section: parseFloat(form.section.value),
|
||
length: parseInt(form.length.value),
|
||
status: form.status.value,
|
||
date: form.date.value
|
||
};
|
||
saveCable(cable);
|
||
form.reset();
|
||
applyFilters();
|
||
});
|
||
|
||
applyFilters();
|
||
</script>
|
||
|
||
</body>
|
||
</html>
|