pervogo-maketa-opisanie-podh/index.html

330 lines
16 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="ru" data-theme="light">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Закупки — Töre</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">
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>📦</text></svg>">
<style>
a.kt-ai-btn { text-decoration: none; }
.kt-ai-app { 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); }
.kpi-strip { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: var(--kt-ai-space-4); margin-bottom: var(--kt-ai-space-6); }
.kpi-card { background: var(--kt-ai-card-bg); border: 1px solid var(--kt-ai-card-border); border-radius: var(--kt-ai-radius-xl); padding: var(--kt-ai-space-5); }
.kpi-value { font-size: var(--kt-ai-text-3xl); font-weight: var(--kt-ai-weight-bold); color: var(--kt-ai-fg); }
.kpi-label { font-size: var(--kt-ai-text-sm); color: var(--kt-ai-fg-muted); margin-top: var(--kt-ai-space-1); }
.kpi-hint { font-size: var(--kt-ai-text-xs); color: var(--kt-ai-fg-faint); margin-top: var(--kt-ai-space-2); }
.filters-bar { display: flex; flex-wrap: wrap; gap: var(--kt-ai-space-3); margin-bottom: var(--kt-ai-space-5); align-items: center; }
.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-size: var(--kt-ai-text-xs); color: var(--kt-ai-fg-muted); border-bottom: 1px solid var(--kt-ai-border); }
.kt-ai-table td { padding: var(--kt-ai-space-4); border-bottom: 1px solid var(--kt-ai-border); font-size: var(--kt-ai-text-sm); }
.kt-ai-table tr:hover { background: var(--kt-ai-bg-soft); }
.num { text-align: right; font-variant-numeric: tabular-nums; }
.status-chip { display: inline-flex; padding: 2px 8px; border-radius: var(--kt-ai-radius-full); font-size: var(--kt-ai-text-xs); font-weight: var(--kt-ai-weight-medium); }
.status-new { background: var(--kt-ai-bg-soft); color: var(--kt-ai-fg-muted); }
.status-prep { background: var(--kt-ai-bg-soft); color: var(--kt-ai-chart-orange); }
.status-active { background: var(--kt-ai-bg-soft); color: var(--kt-ai-chart-green); }
.status-done { background: var(--kt-ai-bg-soft); color: var(--kt-ai-fg-muted); }
.block-reason { font-size: var(--kt-ai-text-xs); color: var(--kt-ai-fg-muted); margin-left: var(--kt-ai-space-2); }
.empty-state { text-align: center; padding: var(--kt-ai-space-10); color: var(--kt-ai-fg-muted); }
.chart-wrap { height: 200px; margin-top: var(--kt-ai-space-6); }
.chart-bars { display: flex; align-items: flex-end; gap: 8px; height: 160px; padding-top: var(--kt-ai-space-4); }
.chart-bar { flex: 1; background: var(--kt-ai-chart-blue); border-radius: 2px 2px 0 0; min-width: 24px; position: relative; }
.chart-bar[data-kt-tip]::after { content: attr(data-kt-tip); position: absolute; top: -24px; left: 50%; transform: translateX(-50%); font-size: var(--kt-ai-text-xs); background: var(--kt-ai-fg); color: var(--kt-ai-bg); padding: 2px 6px; border-radius: 4px; opacity: 0; pointer-events: none; transition: opacity 0.2s; }
.chart-bar:hover[data-kt-tip]::after { opacity: 1; }
.chart-labels { display: flex; justify-content: space-between; margin-top: var(--kt-ai-space-2); font-size: var(--kt-ai-text-xs); color: var(--kt-ai-fg-muted); }
</style>
</head>
<body class="kt-ai-app" data-kt-data-mode="demo">
<header class="kt-ai-pagehead">
<div class="kt-ai-wrap kt-ai-pagehead-in">
<div class="app-header">
<img src="https://code.vibe42.kz/tore/aitu/kt-logo.svg" alt="Казахтелеком" height="28">
<strong>Управление закупками</strong>
</div>
<span class="kt-ai-spacer"></span>
<span class="kt-ai-chip" data-tone="orange">Демо-данные</span>
</div>
</header>
<main class="kt-ai-wrap" style="padding-top: var(--kt-ai-space-6); padding-bottom: var(--kt-ai-space-8);">
<!-- KPI панель -->
<div class="kpi-strip">
<div class="kpi-card">
<div class="kpi-value" id="kpi-total">0</div>
<div class="kpi-label">Всего закупок</div>
<div class="kpi-hint">Все активные процедуры</div>
</div>
<div class="kpi-card">
<div class="kpi-value" id="kpi-not-started">0</div>
<div class="kpi-label">Ещё не начаты</div>
<div class="kpi-hint">Требуют подготовки</div>
</div>
<div class="kpi-card">
<div class="kpi-value" id="kpi-active">0</div>
<div class="kpi-label">В работе</div>
<div class="kpi-hint">Приём заявок идёт</div>
</div>
<div class="kpi-card">
<div class="kpi-value" id="kpi-sum">0 ₸</div>
<div class="kpi-label">Общая сумма</div>
<div class="kpi-hint">Плановый бюджет</div>
</div>
</div>
<!-- Фильтры -->
<div class="filters-bar">
<div class="kt-ai-field">
<select class="kt-ai-select" id="filter-status" onchange="applyFilters()">
<option value="all">Все статусы</option>
<option value="new">Ещё не начаты</option>
<option value="prep">Подготовка</option>
<option value="active">В работе</option>
<option value="done">Завершены</option>
</select>
</div>
<div class="kt-ai-field">
<select class="kt-ai-select" id="filter-category" onchange="applyFilters()">
<option value="all">Все категории</option>
<option value="it">ИТ и оборудование</option>
<option value="services">Услуги</option>
<option value="materials">Материалы</option>
<option value="construction">Строительство</option>
</select>
</div>
<div class="kt-ai-field">
<select class="kt-ai-select" id="filter-period" onchange="applyFilters()">
<option value="all">Любой период</option>
<option value="month">Этот месяц</option>
<option value="quarter">Квартал</option>
</select>
</div>
<span class="kt-ai-spacer"></span>
<button class="kt-ai-btn" data-variant="ghost" onclick="resetFilters()">Сбросить</button>
</div>
<!-- Таблица закупок -->
<div class="kt-ai-card">
<div class="kt-ai-table-wrap">
<table class="kt-ai-table">
<thead>
<tr>
<th>№</th>
<th>Наименование</th>
<th>Категория</th>
<th>Сумма</th>
<th>Статус</th>
<th>Дата начала</th>
<th>Действие</th>
</tr>
</thead>
<tbody id="procurements-table"></tbody>
</table>
</div>
<div id="empty-state" class="empty-state" style="display: none;">
<p>Нет данных по выбранным фильтрам</p>
<p style="font-size: var(--kt-ai-text-xs); margin-top: var(--kt-ai-space-2);">Попробуйте изменить параметры фильтрации</p>
</div>
</div>
<!-- График по месяцам -->
<div class="kt-ai-card" style="margin-top: var(--kt-ai-space-5);">
<h3 style="font-size: var(--kt-ai-text-lg); margin-bottom: var(--kt-ai-space-4);">Динамика закупок</h3>
<div class="chart-wrap">
<div class="chart-bars" id="chart-bars"></div>
<div class="chart-labels" id="chart-labels"></div>
</div>
</div>
</main>
<footer class="kt-ai-wrap kt-ai-pagefoot">
<span>© 2026 Казахтелеком</span>
<span class="kt-ai-spacer"></span>
<span style="font-size: var(--kt-ai-text-sm); color: var(--kt-ai-fg-muted);">Система управления закупками</span>
</footer>
<script>
// Демо-данные с разными значениями под каждый фильтр
const DEMO_DATA = [
{ id: 'ZK-2026-001', name: 'Поставка серверного оборудования', category: 'it', sum: 45000000, status: 'new', date: '2026-01-15', startDate: '' },
{ id: 'ZK-2026-002', name: 'Услуги техподдержки ПО', category: 'services', sum: 12000000, status: 'prep', date: '2026-01-10', startDate: '2026-02-01' },
{ id: 'ZK-2026-003', name: 'Кабельная продукция', category: 'materials', sum: 8500000, status: 'active', date: '2025-12-20', startDate: '2026-01-05' },
{ id: 'ZK-2026-004', name: 'Строительство ВОЛС', category: 'construction', sum: 78000000, status: 'new', date: '2026-01-08', startDate: '' },
{ id: 'ZK-2026-005', name: 'Лицензии на ПО', category: 'it', sum: 5600000, status: 'active', date: '2025-12-15', startDate: '2025-12-28' },
{ id: 'ZK-2026-006', name: 'Офисная мебель', category: 'materials', sum: 3200000, status: 'done', date: '2025-11-20', startDate: '2025-12-01' },
{ id: 'ZK-2026-007', name: 'Монтаж систем безопасности', category: 'construction', sum: 15400000, status: 'prep', date: '2026-01-12', startDate: '2026-02-10' },
{ id: 'ZK-2026-008', name: 'Обучение персонала', category: 'services', sum: 2800000, status: 'new', date: '2026-01-18', startDate: '' },
{ id: 'ZK-2026-009', name: 'Сетевые коммутаторы', category: 'it', sum: 18900000, status: 'active', date: '2025-12-28', startDate: '2026-01-10' },
{ id: 'ZK-2026-010', name: 'Уборка помещений', category: 'services', sum: 4100000, status: 'done', date: '2025-10-15', startDate: '2025-11-01' },
{ id: 'ZK-2026-011', name: 'Оптические патч-корды', category: 'materials', sum: 1850000, status: 'prep', date: '2026-01-05', startDate: '2026-01-25' },
{ id: 'ZK-2026-012', name: 'Проектирование ЦОД', category: 'construction', sum: 32000000, status: 'new', date: '2026-01-20', startDate: '' }
];
const CATEGORY_NAMES = {
'it': 'ИТ и оборудование',
'services': 'Услуги',
'materials': 'Материалы',
'construction': 'Строительство'
};
const STATUS_CONFIG = {
'new': { label: 'Ещё не начата', class: 'status-new', canStart: false, reason: 'Срок не заполнен — укажите дату начала' },
'prep': { label: 'Подготовка', class: 'status-prep', canStart: false, reason: 'Документы на согласовании' },
'active': { label: 'В работе', class: 'status-active', canStart: true, reason: '' },
'done': { label: 'Завершена', class: 'status-done', canStart: false, reason: 'Процедура завершена' }
};
const FILTERS = [
{ id: 'status', label: 'Статус', affects: ['table', 'kpi', 'chart'] },
{ id: 'category', label: 'Категория', affects: ['table', 'kpi'] },
{ id: 'period', label: 'Период', affects: ['table', 'kpi', 'chart'] }
];
let currentData = [...DEMO_DATA];
function formatSum(sum) {
return sum.toLocaleString('ru-KZ') + ' ₸';
}
function getStatusChip(status) {
const cfg = STATUS_CONFIG[status];
return `<span class="status-chip ${cfg.class}">${cfg.label}</span>`;
}
function renderTable(data) {
const tbody = document.getElementById('procurements-table');
const emptyState = document.getElementById('empty-state');
if (data.length === 0) {
tbody.innerHTML = '';
emptyState.style.display = 'block';
return;
}
emptyState.style.display = 'none';
tbody.innerHTML = data.map(item => {
const cfg = STATUS_CONFIG[item.status];
const btnDisabled = !cfg.canStart ? 'disabled' : '';
const btnText = item.status === 'done' ? 'Архив' : (item.status === 'active' ? 'Управление' : 'Начать');
const reasonHtml = !cfg.canStart ? `<span class="block-reason">${cfg.reason}</span>` : '';
return `<tr>
<td style="font-family: var(--kt-ai-font-mono); font-size: var(--kt-ai-text-xs);">${item.id}</td>
<td>${item.name}</td>
<td>${CATEGORY_NAMES[item.category]}</td>
<td class="num">${formatSum(item.sum)}</td>
<td>${getStatusChip(item.status)}</td>
<td>${item.startDate || '—'}</td>
<td>
<button class="kt-ai-btn" data-size="sm" ${btnDisabled} onclick="handleAction('${item.id}')">${btnText}</button>
${reasonHtml}
</td>
</tr>`;
}).join('');
}
function renderKPI(data) {
const total = data.length;
const notStarted = data.filter(i => i.status === 'new').length;
const active = data.filter(i => i.status === 'active').length;
const sum = data.reduce((acc, i) => acc + i.sum, 0);
document.getElementById('kpi-total').textContent = total;
document.getElementById('kpi-not-started').textContent = notStarted;
document.getElementById('kpi-active').textContent = active;
document.getElementById('kpi-sum').textContent = formatSum(sum);
}
function renderChart(data) {
const months = ['Янв', 'Фев', 'Мар', 'Апр', 'Май', 'Июн'];
const monthData = [3, 5, 2, 4, 1, 2];
const max = Math.max(...monthData);
const barsContainer = document.getElementById('chart-bars');
const labelsContainer = document.getElementById('chart-labels');
barsContainer.innerHTML = monthData.map((val, idx) => {
const height = (val / max) * 140;
return `<div class="chart-bar" style="height: ${height}px;" data-kt-tip="${val} закуп.${idx === 0 ? ' (демо)' : ''}"></div>`;
}).join('');
labelsContainer.innerHTML = months.map(m => `<span>${m}</span>`).join('');
}
function applyFilters() {
const statusFilter = document.getElementById('filter-status').value;
const categoryFilter = document.getElementById('filter-category').value;
const periodFilter = document.getElementById('filter-period').value;
let filtered = [...DEMO_DATA];
if (statusFilter !== 'all') {
filtered = filtered.filter(i => i.status === statusFilter);
}
if (categoryFilter !== 'all') {
filtered = filtered.filter(i => i.category === categoryFilter);
}
if (periodFilter !== 'all') {
const now = new Date('2026-01-20');
const monthAgo = new Date('2026-01-01');
const quarterAgo = new Date('2025-11-01');
filtered = filtered.filter(i => {
const d = new Date(i.date);
if (periodFilter === 'month') return d >= monthAgo;
if (periodFilter === 'quarter') return d >= quarterAgo;
return true;
});
}
currentData = filtered;
renderTable(filtered);
renderKPI(filtered);
}
function resetFilters() {
document.getElementById('filter-status').value = 'all';
document.getElementById('filter-category').value = 'all';
document.getElementById('filter-period').value = 'all';
applyFilters();
}
function handleAction(id) {
const item = DEMO_DATA.find(i => i.id === id);
if (!item) return;
if (item.status === 'new') {
alert('Нельзя начать: ' + STATUS_CONFIG['new'].reason);
return;
}
if (item.status === 'prep') {
alert('Нельзя начать: ' + STATUS_CONFIG['prep'].reason);
return;
}
if (item.status === 'done') {
alert('Процедура уже завершена');
return;
}
alert('Открыто управление закупкой ' + item.id + ': ' + item.name);
}
// Инициализация
renderTable(DEMO_DATA);
renderKPI(DEMO_DATA);
renderChart(DEMO_DATA);
</script>
</body>
</html>