574 lines
26 KiB
HTML
574 lines
26 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; }
|
||
.kt-ai-table tr.selected { background: var(--kt-ai-bg-soft); }
|
||
.kt-ai-table tr.excluded { opacity: 0.5; background: var(--kt-ai-bg-soft); }
|
||
.kt-ai-table tr.excluded td { text-decoration: line-through; }
|
||
.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: 900px; }
|
||
.filter-note { font-size: var(--kt-ai-text-xs); color: var(--kt-ai-fg-faint); margin-left: var(--kt-ai-space-2); }
|
||
.compare-panel { display: none; padding: var(--kt-ai-space-6) 0; }
|
||
.compare-panel.active { display: block; }
|
||
.compare-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: var(--kt-ai-space-4); }
|
||
.compare-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); }
|
||
.compare-card h4 { margin: 0 0 var(--kt-ai-space-3) 0; font-size: var(--kt-ai-text-lg); }
|
||
.compare-row { display: flex; justify-content: space-between; padding: var(--kt-ai-space-2) 0; border-bottom: 1px solid var(--kt-ai-border-strong); }
|
||
.compare-row:last-child { border-bottom: none; }
|
||
.compare-label { color: var(--kt-ai-fg-muted); }
|
||
.compare-value { font-weight: var(--kt-ai-weight-medium); }
|
||
.diff-highlight { background: var(--kt-ai-bg-soft); padding: var(--kt-ai-space-1) var(--kt-ai-space-2); border-radius: var(--kt-ai-radius-md); }
|
||
.decision-section { padding: var(--kt-ai-space-6) 0; border-top: 1px solid var(--kt-ai-border); margin-top: var(--kt-ai-space-6); }
|
||
.status-badge { display: inline-block; padding: var(--kt-ai-space-1) var(--kt-ai-space-3); border-radius: var(--kt-ai-radius-full); font-size: var(--kt-ai-text-sm); font-weight: var(--kt-ai-weight-medium); }
|
||
.status-new { background: var(--kt-ai-bg-soft); color: var(--kt-ai-fg); }
|
||
.status-review { background: var(--kt-ai-bg-soft); color: var(--kt-ai-fg-muted); }
|
||
.status-approved { background: var(--kt-ai-bg-soft); color: var(--kt-ai-fg); }
|
||
.status-rejected { background: var(--kt-ai-bg-soft); color: var(--kt-ai-fg-muted); }
|
||
.toolbar { display: flex; gap: var(--kt-ai-space-3); align-items: center; margin-bottom: var(--kt-ai-space-4); }
|
||
</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>
|
||
<span class="status-badge status-new" id="header-status">Новые позиции</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-groups">0</div>
|
||
<div class="label">Группы</div>
|
||
<div class="hint">похожих товаров</div>
|
||
</div>
|
||
<div class="kt-ai-kpi">
|
||
<div class="value" id="kpi-excluded">0</div>
|
||
<div class="label">Исключено</div>
|
||
<div class="hint">строк отклонено</div>
|
||
</div>
|
||
<div class="kt-ai-kpi">
|
||
<div class="value" id="kpi-sum">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-category">
|
||
<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="review">На согласовании</option>
|
||
<option value="approved">Утверждено</option>
|
||
<option value="rejected">Отклонено</option>
|
||
</select>
|
||
<span class="filter-note" data-kt-filter-note>в демо не меняет KPI</span>
|
||
</div>
|
||
<div class="kt-ai-field" style="min-width: 200px;">
|
||
<label>Период</label>
|
||
<select class="kt-ai-select" id="filter-period">
|
||
<option value="all">За всё время</option>
|
||
<option value="2026-09">Сентябрь 2026</option>
|
||
<option value="2026-10">Октябрь 2026</option>
|
||
<option value="2026-Q4">4 квартал 2026</option>
|
||
</select>
|
||
</div>
|
||
<span class="kt-ai-spacer"></span>
|
||
<button class="kt-ai-btn" data-variant="ghost" id="btn-reset">Сбросить</button>
|
||
<button class="kt-ai-btn" data-variant="primary" id="btn-compare" disabled>Сравнить выбранные</button>
|
||
<button class="kt-ai-btn" data-variant="ghost" id="btn-export">Выгрузить Excel</button>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="table-section">
|
||
<div class="kt-ai-wrap">
|
||
<div class="toolbar">
|
||
<span style="color: var(--kt-ai-fg-muted); font-size: var(--kt-ai-text-sm);">Выделите 2-4 похожих строки для сравнения (Ctrl+клик)</span>
|
||
</div>
|
||
<div class="kt-ai-table-wrap">
|
||
<table class="kt-ai-table">
|
||
<thead>
|
||
<tr>
|
||
<th style="width: 40px;">☐</th>
|
||
<th>Наименование</th>
|
||
<th>Категория</th>
|
||
<th class="num">Кол-во</th>
|
||
<th class="num">Ед. изм.</th>
|
||
<th class="num">Цена, ₸</th>
|
||
<th class="num">Сумма, ₸</th>
|
||
<th>Срок поставки</th>
|
||
<th>Поставщик</th>
|
||
<th>Статус</th>
|
||
<th class="actions">Действия</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="plan-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="compare-panel" id="compare-panel">
|
||
<div class="kt-ai-wrap">
|
||
<div class="kt-ai-section-head">
|
||
<h2>Сравнение позиций</h2>
|
||
<p>Выберите оптимальный вариант или исключите неподходящие</p>
|
||
</div>
|
||
<div class="compare-grid" id="compare-cards"></div>
|
||
|
||
<div class="decision-section">
|
||
<h3 style="margin-bottom: var(--kt-ai-space-4);">Решение по группе</h3>
|
||
<form id="decision-form" class="form-grid">
|
||
<div class="kt-ai-field" style="grid-column: 1 / -1;">
|
||
<label>Комментарий к решению *</label>
|
||
<textarea class="kt-ai-textarea" name="comment" required rows="3" placeholder="Обоснуйте выбор или причину исключения..."></textarea>
|
||
</div>
|
||
<div class="kt-ai-field">
|
||
<label>Решение</label>
|
||
<select class="kt-ai-select" name="decision" required>
|
||
<option value="">Выберите решение</option>
|
||
<option value="approve-best">Утвердить лучший вариант</option>
|
||
<option value="exclude-all">Исключить все позиции</option>
|
||
<option value="send-review">Отправить на доработку</option>
|
||
</select>
|
||
</div>
|
||
<div class="kt-ai-field">
|
||
<label>Ответственный</label>
|
||
<input class="kt-ai-input" name="owner" required placeholder="ФИО">
|
||
</div>
|
||
<div style="grid-column: 1 / -1; display: flex; gap: var(--kt-ai-space-3);">
|
||
<button class="kt-ai-btn" data-variant="primary" type="submit">Сохранить решение</button>
|
||
<button class="kt-ai-btn" data-variant="ghost" type="button" id="btn-close-compare">Закрыть</button>
|
||
</div>
|
||
</form>
|
||
</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-plan-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="category" 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="quantity" type="number" required placeholder="100">
|
||
</div>
|
||
<div class="kt-ai-field">
|
||
<label>Ед. изм.</label>
|
||
<select class="kt-ai-select" name="unit">
|
||
<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="price" type="number" required placeholder="500">
|
||
</div>
|
||
<div class="kt-ai-field">
|
||
<label>Срок поставки *</label>
|
||
<input class="kt-ai-input" name="delivery" type="date" required>
|
||
</div>
|
||
<div class="kt-ai-field">
|
||
<label>Поставщик *</label>
|
||
<input class="kt-ai-input" name="supplier" required placeholder="ООО «Пример»">
|
||
</div>
|
||
<div class="kt-ai-field">
|
||
<label>Статус</label>
|
||
<select class="kt-ai-select" name="status">
|
||
<option value="new">Новый</option>
|
||
<option value="review">На согласовании</option>
|
||
</select>
|
||
</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_procurement_data';
|
||
|
||
const DEFAULT_PLAN = [
|
||
{ id: 1, name: 'Кабель АВВГ 3х2.5', category: 'Кабель силовой', quantity: 150, unit: 'м', price: 450, sum: 67500, delivery: '2026-09-20', supplier: 'ТОО «КабельКЗ»', status: 'new', period: '2026-09' },
|
||
{ id: 2, name: 'Кабель АВВГ 3х2.5', category: 'Кабель силовой', quantity: 200, unit: 'м', price: 420, sum: 84000, delivery: '2026-09-25', supplier: 'ИП «Алмаз»', status: 'review', period: '2026-09' },
|
||
{ id: 3, name: 'Кабель АВВГ 3х2.5', category: 'Кабель силовой', quantity: 100, unit: 'м', price: 480, sum: 48000, delivery: '2026-09-15', supplier: 'ТОО «ЭлектроМир»', status: 'new', period: '2026-09' },
|
||
{ id: 4, name: 'Кабель АВВГ 3х4.0', category: 'Кабель силовой', quantity: 180, unit: 'м', price: 680, sum: 122400, delivery: '2026-09-22', supplier: 'ТОО «КабельКЗ»', status: 'new', period: '2026-09' },
|
||
{ id: 5, name: 'Кабель ОКЛ-Т 4х9', category: 'Кабель оптоволоконный', quantity: 500, unit: 'м', price: 1250, sum: 625000, delivery: '2026-10-10', supplier: 'ТОО «Оптима»', status: 'review', period: '2026-10' },
|
||
{ id: 6, name: 'Кабель ОКЛ-Т 4х9', category: 'Кабель оптоволоконный', quantity: 450, unit: 'м', price: 1180, sum: 531000, delivery: '2026-10-05', supplier: 'АО «ТелекомСнаб»', status: 'new', period: '2026-10' },
|
||
{ id: 7, name: 'Коммутатор Cisco 2960', category: 'Оборудование', quantity: 5, unit: 'шт', price: 185000, sum: 925000, delivery: '2026-10-15', supplier: 'ТОО «IT-Дистрибьюция»', status: 'approved', period: '2026-10' },
|
||
{ id: 8, name: 'Кронштейн универсальный', category: 'Материалы', quantity: 50, unit: 'шт', price: 2500, sum: 125000, delivery: '2026-09-18', supplier: 'ИП «СтройМетиз»', status: 'new', period: '2026-09' },
|
||
{ id: 9, name: 'Кронштейн универсальный', category: 'Материалы', quantity: 60, unit: 'шт', price: 2300, sum: 138000, delivery: '2026-09-28', supplier: 'ТОО «КрепежЦентр»', status: 'review', period: '2026-09' }
|
||
];
|
||
|
||
let selectedIds = [];
|
||
let excludedIds = [];
|
||
let decisions = [];
|
||
|
||
function getData() {
|
||
const stored = localStorage.getItem(STORAGE_KEY);
|
||
if (stored) {
|
||
const parsed = JSON.parse(stored);
|
||
return parsed.planItems || DEFAULT_PLAN;
|
||
}
|
||
return DEFAULT_PLAN;
|
||
}
|
||
|
||
function savePlanItem(item) {
|
||
const stored = localStorage.getItem(STORAGE_KEY);
|
||
let data = stored ? JSON.parse(stored) : { planItems: [], decisions: [] };
|
||
if (!data.planItems) data.planItems = [];
|
||
item.id = Date.now();
|
||
item.sum = (item.quantity * item.price);
|
||
data.planItems.push(item);
|
||
localStorage.setItem(STORAGE_KEY, JSON.stringify(data));
|
||
}
|
||
|
||
function saveDecision(decision) {
|
||
const stored = localStorage.getItem(STORAGE_KEY);
|
||
let data = stored ? JSON.parse(stored) : { planItems: [], decisions: [] };
|
||
if (!data.decisions) data.decisions = [];
|
||
decision.id = Date.now();
|
||
decision.date = new Date().toISOString();
|
||
data.decisions.push(decision);
|
||
localStorage.setItem(STORAGE_KEY, JSON.stringify(data));
|
||
decisions = data.decisions;
|
||
}
|
||
|
||
function getStatusChip(status) {
|
||
const map = {
|
||
'new': { label: 'Новый', tone: 'blue' },
|
||
'review': { label: 'На согласовании', tone: 'orange' },
|
||
'approved': { label: 'Утверждено', tone: 'green' },
|
||
'rejected': { label: 'Отклонено', tone: 'red' }
|
||
};
|
||
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 formatMoney(amount) {
|
||
return new Intl.NumberFormat('ru-RU').format(amount);
|
||
}
|
||
|
||
function updateKPI(items) {
|
||
document.getElementById('kpi-total').textContent = items.length;
|
||
const groups = new Set(items.map(i => i.name)).size;
|
||
document.getElementById('kpi-groups').textContent = groups;
|
||
document.getElementById('kpi-excluded').textContent = excludedIds.length;
|
||
document.getElementById('kpi-sum').textContent = formatMoney(items.reduce((sum, i) => sum + (i.sum || 0), 0));
|
||
}
|
||
|
||
function renderTable(items) {
|
||
const tbody = document.getElementById('plan-table-body');
|
||
const empty = document.getElementById('empty-state');
|
||
|
||
if (items.length === 0) {
|
||
tbody.innerHTML = '';
|
||
empty.style.display = 'block';
|
||
return;
|
||
}
|
||
|
||
empty.style.display = 'none';
|
||
tbody.innerHTML = items.map(item => {
|
||
const isSelected = selectedIds.includes(item.id);
|
||
const isExcluded = excludedIds.includes(item.id);
|
||
const rowClass = [
|
||
isSelected ? 'selected' : '',
|
||
isExcluded ? 'excluded' : ''
|
||
].filter(Boolean).join(' ') || '';
|
||
|
||
return `
|
||
<tr class="${rowClass}" data-id="${item.id}">
|
||
<td>
|
||
<input type="checkbox" ${isSelected ? 'checked' : ''} onchange="toggleSelect(${item.id})">
|
||
</td>
|
||
<td>${item.name}</td>
|
||
<td>${item.category}</td>
|
||
<td class="num">${item.quantity}</td>
|
||
<td class="num">${item.unit}</td>
|
||
<td class="num">${formatMoney(item.price)}</td>
|
||
<td class="num">${formatMoney(item.sum)}</td>
|
||
<td>${formatDate(item.delivery)}</td>
|
||
<td>${item.supplier}</td>
|
||
<td>${getStatusChip(item.status)}</td>
|
||
<td class="actions">
|
||
<button class="kt-ai-btn" data-variant="ghost" data-size="sm" onclick="excludeItem(${item.id})" ${isExcluded ? 'disabled' : ''}>
|
||
${isExcluded ? 'Исключено' : 'Исключить'}
|
||
</button>
|
||
</td>
|
||
</tr>
|
||
`}).join('');
|
||
|
||
const compareBtn = document.getElementById('btn-compare');
|
||
compareBtn.disabled = selectedIds.length < 2;
|
||
compareBtn.textContent = selectedIds.length > 0
|
||
? `Сравнить выбранные (${selectedIds.length})`
|
||
: 'Сравнить выбранные';
|
||
}
|
||
|
||
window.toggleSelect = function(id) {
|
||
const idx = selectedIds.indexOf(id);
|
||
if (idx > -1) {
|
||
selectedIds.splice(idx, 1);
|
||
} else {
|
||
if (selectedIds.length >= 4) {
|
||
alert('Можно выбрать не более 4 позиций для сравнения');
|
||
return;
|
||
}
|
||
selectedIds.push(id);
|
||
}
|
||
applyFilters();
|
||
};
|
||
|
||
window.excludeItem = function(id) {
|
||
const reason = prompt('Укажите причину исключения:');
|
||
if (reason) {
|
||
excludedIds.push(id);
|
||
applyFilters();
|
||
}
|
||
};
|
||
|
||
function applyFilters() {
|
||
const categoryFilter = document.getElementById('filter-category').value;
|
||
const statusFilter = document.getElementById('filter-status').value;
|
||
const periodFilter = document.getElementById('filter-period').value;
|
||
|
||
let items = getData();
|
||
|
||
if (categoryFilter) items = items.filter(i => i.category === categoryFilter);
|
||
if (statusFilter) items = items.filter(i => i.status === statusFilter);
|
||
if (periodFilter && periodFilter !== 'all') {
|
||
if (periodFilter.includes('Q')) {
|
||
const quarter = periodFilter.split('-Q')[1];
|
||
const year = periodFilter.split('-Q')[0];
|
||
const qMonths = { '4': ['10', '11', '12'] }[quarter];
|
||
items = items.filter(i => qMonths.some(m => i.period.startsWith(year + '-' + m)));
|
||
} else {
|
||
items = items.filter(i => i.period === periodFilter);
|
||
}
|
||
}
|
||
|
||
updateKPI(items);
|
||
renderTable(items);
|
||
}
|
||
|
||
function showComparePanel() {
|
||
if (selectedIds.length < 2) {
|
||
alert('Выберите минимум 2 позиции для сравнения');
|
||
return;
|
||
}
|
||
|
||
const items = getData().filter(i => selectedIds.includes(i.id));
|
||
const panel = document.getElementById('compare-panel');
|
||
const cardsContainer = document.getElementById('compare-cards');
|
||
|
||
cardsContainer.innerHTML = items.map((item, idx) => `
|
||
<div class="compare-card">
|
||
<h4>${item.name} #${idx + 1}</h4>
|
||
<div class="compare-row">
|
||
<span class="compare-label">Поставщик</span>
|
||
<span class="compare-value">${item.supplier}</span>
|
||
</div>
|
||
<div class="compare-row">
|
||
<span class="compare-label">Цена за ед.</span>
|
||
<span class="compare-value">${formatMoney(item.price)} ₸</span>
|
||
</div>
|
||
<div class="compare-row">
|
||
<span class="compare-label">Количество</span>
|
||
<span class="compare-value">${item.quantity} ${item.unit}</span>
|
||
</div>
|
||
<div class="compare-row">
|
||
<span class="compare-label">Сумма</span>
|
||
<span class="compare-value">${formatMoney(item.sum)} ₸</span>
|
||
</div>
|
||
<div class="compare-row">
|
||
<span class="compare-label">Срок поставки</span>
|
||
<span class="compare-value ${idx === 0 ? 'diff-highlight' : ''}">${formatDate(item.delivery)}</span>
|
||
</div>
|
||
<div class="compare-row">
|
||
<span class="compare-label">Статус</span>
|
||
<span class="compare-value">${getStatusChip(item.status)}</span>
|
||
</div>
|
||
<div style="margin-top: var(--kt-ai-space-3);">
|
||
<button class="kt-ai-btn" data-variant="ghost" data-size="sm" style="width: 100%;" onclick="excludeFromCompare(${item.id})">
|
||
Исключить эту позицию
|
||
</button>
|
||
</div>
|
||
</div>
|
||
`).join('');
|
||
|
||
panel.classList.add('active');
|
||
panel.scrollIntoView({ behavior: 'smooth' });
|
||
}
|
||
|
||
window.excludeFromCompare = function(id) {
|
||
const reason = prompt('Укажите причину исключения:');
|
||
if (reason) {
|
||
excludedIds.push(id);
|
||
selectedIds = selectedIds.filter(sid => sid !== id);
|
||
applyFilters();
|
||
document.getElementById('compare-panel').classList.remove('active');
|
||
}
|
||
};
|
||
|
||
document.getElementById('btn-compare').addEventListener('click', showComparePanel);
|
||
|
||
document.getElementById('btn-close-compare').addEventListener('click', () => {
|
||
document.getElementById('compare-panel').classList.remove('active');
|
||
});
|
||
|
||
document.getElementById('decision-form').addEventListener('submit', (e) => {
|
||
e.preventDefault();
|
||
const form = e.target;
|
||
const decision = {
|
||
selectedIds: [...selectedIds],
|
||
excludedIds: [...excludedIds],
|
||
comment: form.comment.value,
|
||
decision: form.decision.value,
|
||
owner: form.owner.value
|
||
};
|
||
saveDecision(decision);
|
||
alert('Решение сохранено!');
|
||
form.reset();
|
||
document.getElementById('compare-panel').classList.remove('active');
|
||
selectedIds = [];
|
||
applyFilters();
|
||
});
|
||
|
||
document.getElementById('btn-export').addEventListener('click', () => {
|
||
const items = getData();
|
||
const csvContent = [
|
||
['ID', 'Наименование', 'Категория', 'Количество', 'Ед. изм.', 'Цена', 'Сумма', 'Срок поставки', 'Поставщик', 'Статус', 'Исключено'],
|
||
...items.map(i => [
|
||
i.id, i.name, i.category, i.quantity, i.unit, i.price, i.sum, i.delivery, i.supplier, i.status,
|
||
excludedIds.includes(i.id) ? 'Да' : 'Нет'
|
||
])
|
||
].map(row => row.join(';')).join('\n');
|
||
|
||
const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
|
||
const link = document.createElement('a');
|
||
link.href = URL.createObjectURL(blob);
|
||
link.download = 'закупочный_план_' + new Date().toISOString().slice(0, 10) + '.csv';
|
||
link.click();
|
||
});
|
||
|
||
document.getElementById('filter-category').addEventListener('change', applyFilters);
|
||
document.getElementById('filter-status').addEventListener('change', applyFilters);
|
||
document.getElementById('filter-period').addEventListener('change', applyFilters);
|
||
|
||
document.getElementById('btn-reset').addEventListener('click', () => {
|
||
document.getElementById('filter-category').value = '';
|
||
document.getElementById('filter-status').value = '';
|
||
document.getElementById('filter-period').value = 'all';
|
||
selectedIds = [];
|
||
applyFilters();
|
||
});
|
||
|
||
document.getElementById('add-plan-form').addEventListener('submit', (e) => {
|
||
e.preventDefault();
|
||
const form = e.target;
|
||
const item = {
|
||
name: form.name.value,
|
||
category: form.category.value,
|
||
quantity: parseInt(form.quantity.value),
|
||
unit: form.unit.value,
|
||
price: parseInt(form.price.value),
|
||
delivery: form.delivery.value,
|
||
supplier: form.supplier.value,
|
||
status: form.status.value,
|
||
period: form.delivery.value.slice(0, 7)
|
||
};
|
||
savePlanItem(item);
|
||
form.reset();
|
||
applyFilters();
|
||
});
|
||
|
||
applyFilters();
|
||
</script>
|
||
|
||
</body>
|
||
</html>
|