514 lines
24 KiB
HTML
514 lines
24 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="ru" data-theme="light">
|
||
<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; }
|
||
body.kt-ai-app { background: var(--kt-ai-bg); color: var(--kt-ai-fg); font-family: var(--kt-ai-font-sans); margin: 0; }
|
||
.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-lg); font-weight: var(--kt-ai-weight-semibold); }
|
||
.filters-bar { display: flex; flex-wrap: wrap; gap: var(--kt-ai-space-3); align-items: center; margin-bottom: var(--kt-ai-space-5); }
|
||
.filter-group { display: flex; align-items: center; gap: var(--kt-ai-space-2); }
|
||
.filter-label { font-size: var(--kt-ai-text-sm); color: var(--kt-ai-fg-muted); }
|
||
.kt-ai-table-wrap { overflow-x: auto; border: 1px solid var(--kt-ai-border); border-radius: var(--kt-ai-radius-xl); }
|
||
.kt-ai-table { width: 100%; border-collapse: collapse; font-size: var(--kt-ai-text-sm); }
|
||
.kt-ai-table th { text-align: left; padding: var(--kt-ai-space-3) var(--kt-ai-space-4); border-bottom: 1px solid var(--kt-ai-border); background: var(--kt-ai-bg-soft); font-weight: var(--kt-ai-weight-semibold); color: var(--kt-ai-fg-muted); }
|
||
.kt-ai-table td { padding: var(--kt-ai-space-3) var(--kt-ai-space-4); border-bottom: 1px solid var(--kt-ai-border); vertical-align: middle; }
|
||
.kt-ai-table tr:hover { background: var(--kt-ai-bg-soft); }
|
||
.kt-ai-table tr.selected { background: rgba(0, 122, 255, 0.08); }
|
||
.kt-ai-table tr.disabled { opacity: 0.5; pointer-events: none; }
|
||
.num { text-align: right; font-variant-numeric: tabular-nums; }
|
||
.actions-cell { display: flex; gap: var(--kt-ai-space-2); }
|
||
.comment-modal { position: fixed; inset: 0; background: rgba(0,0,0,0.5); display: none; align-items: center; justify-content: center; z-index: 1000; }
|
||
.comment-modal.open { display: flex; }
|
||
.comment-box { background: var(--kt-ai-bg); border-radius: var(--kt-ai-radius-2xl); padding: var(--kt-ai-space-6); width: 100%; max-width: 480px; box-shadow: 0 10px 40px rgba(0,0,0,0.2); }
|
||
.comment-box h3 { margin: 0 0 var(--kt-ai-space-4); font-size: var(--kt-ai-text-lg); }
|
||
.comment-box textarea { width: 100%; min-height: 100px; margin-bottom: var(--kt-ai-space-4); }
|
||
.comment-actions { display: flex; gap: var(--kt-ai-space-3); justify-content: flex-end; }
|
||
.stats-strip { display: flex; gap: var(--kt-ai-space-4); margin-bottom: var(--kt-ai-space-5); flex-wrap: wrap; }
|
||
.stat-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-4); min-width: 160px; }
|
||
.stat-value { font-size: var(--kt-ai-text-2xl); font-weight: var(--kt-ai-weight-bold); color: var(--kt-ai-fg); }
|
||
.stat-label { font-size: var(--kt-ai-text-sm); color: var(--kt-ai-fg-muted); margin-top: var(--kt-ai-space-1); }
|
||
.empty-state { text-align: center; padding: var(--kt-ai-space-10); color: var(--kt-ai-fg-muted); }
|
||
.bulk-actions { display: flex; gap: var(--kt-ai-space-3); align-items: center; margin-bottom: var(--kt-ai-space-4); padding: var(--kt-ai-space-3); background: var(--kt-ai-bg-soft); border-radius: var(--kt-ai-radius-xl); }
|
||
.selected-count { font-size: var(--kt-ai-text-sm); color: var(--kt-ai-fg-muted); }
|
||
.compare-panel { position: fixed; bottom: 0; left: 0; right: 0; background: var(--kt-ai-bg); border-top: 1px solid var(--kt-ai-border); padding: var(--kt-ai-space-4); display: none; z-index: 100; }
|
||
.compare-panel.open { display: block; }
|
||
.compare-list { display: flex; gap: var(--kt-ai-space-3); overflow-x: auto; padding-bottom: var(--kt-ai-space-3); }
|
||
.compare-item { background: var(--kt-ai-bg-soft); border-radius: var(--kt-ai-radius-lg); padding: var(--kt-ai-space-3); min-width: 200px; position: relative; }
|
||
.compare-item .remove { position: absolute; top: var(--kt-ai-space-1); right: var(--kt-ai-space-2); cursor: pointer; color: var(--kt-ai-fg-muted); }
|
||
.compare-item .name { font-weight: var(--kt-ai-weight-medium); font-size: var(--kt-ai-text-sm); margin-bottom: var(--kt-ai-space-2); }
|
||
.compare-item .detail { font-size: var(--kt-ai-text-xs); color: var(--kt-ai-fg-muted); }
|
||
.compare-actions { display: flex; gap: var(--kt-ai-space-3); margin-top: var(--kt-ai-space-3); }
|
||
.highlight-group { background: rgba(255, 191, 0, 0.1); }
|
||
</style>
|
||
</head>
|
||
<body class="kt-ai-app">
|
||
|
||
<div class="kt-ai-wrap" style="padding-top:var(--kt-ai-space-4);padding-bottom:var(--kt-ai-space-4)">
|
||
|
||
<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-chip" data-tone="orange">Демо-данные</span>
|
||
<span class="kt-ai-spacer"></span>
|
||
<span style="font-size:var(--kt-ai-text-sm);color:var(--kt-ai-fg-muted)">Данияр</span>
|
||
</header>
|
||
|
||
<div class="stats-strip" style="margin-top:var(--kt-ai-space-5)">
|
||
<div class="stat-card">
|
||
<div class="stat-value" id="stat-total">0</div>
|
||
<div class="stat-label">Всего позиций</div>
|
||
</div>
|
||
<div class="stat-card">
|
||
<div class="stat-value" id="stat-new">0</div>
|
||
<div class="stat-label">Новые</div>
|
||
</div>
|
||
<div class="stat-card">
|
||
<div class="stat-value" id="stat-confirmed">0</div>
|
||
<div class="stat-label">Подтверждено</div>
|
||
</div>
|
||
<div class="stat-card">
|
||
<div class="stat-value" id="stat-rejected">0</div>
|
||
<div class="stat-label">Отклонено</div>
|
||
</div>
|
||
<div class="stat-card">
|
||
<div class="stat-value" id="stat-amount">0 ₸</div>
|
||
<div class="stat-label">Сумма</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="filters-bar">
|
||
<div class="filter-group">
|
||
<label class="filter-label">Поиск:</label>
|
||
<input class="kt-ai-input" id="filter-search" placeholder="Название, код..." style="width:220px">
|
||
</div>
|
||
<div class="filter-group">
|
||
<label class="filter-label">Категория:</label>
|
||
<select class="kt-ai-select" id="filter-category">
|
||
<option value="">Все</option>
|
||
<option>IT-оборудование</option>
|
||
<option>Канцтовары</option>
|
||
<option>Мебель</option>
|
||
<option>Расходные материалы</option>
|
||
</select>
|
||
</div>
|
||
<div class="filter-group">
|
||
<label class="filter-label">Статус:</label>
|
||
<select class="kt-ai-select" id="filter-status">
|
||
<option value="">Все</option>
|
||
<option value="new">Новые</option>
|
||
<option value="confirmed">Подтверждено</option>
|
||
<option value="rejected">Отклонено</option>
|
||
</select>
|
||
</div>
|
||
<div class="filter-group">
|
||
<label class="filter-label">Группа:</label>
|
||
<select class="kt-ai-select" id="filter-group">
|
||
<option value="">Все</option>
|
||
<option value="A">Группа A</option>
|
||
<option value="B">Группа B</option>
|
||
<option value="C">Группа C</option>
|
||
</select>
|
||
</div>
|
||
<button class="kt-ai-btn" data-variant="ghost" id="btn-reset-filters">Сбросить</button>
|
||
</div>
|
||
|
||
<div class="bulk-actions">
|
||
<span class="selected-count" id="selected-count">Выбрано: 0</span>
|
||
<button class="kt-ai-btn" data-variant="primary" id="btn-bulk-confirm" disabled>Подтвердить выбранные</button>
|
||
<button class="kt-ai-btn" data-variant="danger" id="btn-bulk-reject" disabled>Отклонить выбранные</button>
|
||
<button class="kt-ai-btn" data-variant="ghost" id="btn-compare">Сравнить выбранные</button>
|
||
</div>
|
||
|
||
<div class="kt-ai-table-wrap">
|
||
<table class="kt-ai-table">
|
||
<thead>
|
||
<tr>
|
||
<th style="width:40px"><input type="checkbox" id="check-all"></th>
|
||
<th>Код</th>
|
||
<th>Наименование</th>
|
||
<th>Категория</th>
|
||
<th>Группа</th>
|
||
<th class="num">Количество</th>
|
||
<th class="num">Цена (₸)</th>
|
||
<th class="num">Сумма (₸)</th>
|
||
<th>Статус</th>
|
||
<th>Действия</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="table-body"></tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<div class="empty-state" id="empty-state" style="display:none">
|
||
<p style="font-size:var(--kt-ai-text-lg);margin-bottom:var(--kt-ai-space-2)">Нет позиций для отображения</p>
|
||
<p style="font-size:var(--kt-ai-text-sm)">Измените параметры фильтров или добавьте новые позиции</p>
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<div class="compare-panel" id="compare-panel">
|
||
<div class="kt-ai-wrap">
|
||
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:var(--kt-ai-space-3)">
|
||
<strong>Сравнение позиций</strong>
|
||
<button class="kt-ai-btn" data-variant="ghost" id="btn-close-compare">Закрыть</button>
|
||
</div>
|
||
<div class="compare-list" id="compare-list"></div>
|
||
<div class="compare-actions">
|
||
<button class="kt-ai-btn" data-variant="primary" id="btn-confirm-compare">Подтвердить группу</button>
|
||
<button class="kt-ai-btn" data-variant="danger" id="btn-reject-compare">Отклонить группу</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="comment-modal" id="comment-modal">
|
||
<div class="comment-box">
|
||
<h3 id="comment-title">Комментарий</h3>
|
||
<textarea class="kt-ai-textarea" id="comment-text" placeholder="Введите комментарий..."></textarea>
|
||
<div class="comment-actions">
|
||
<button class="kt-ai-btn" data-variant="ghost" id="btn-cancel-comment">Отмена</button>
|
||
<button class="kt-ai-btn" data-variant="primary" id="btn-save-comment">Сохранить</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
// Демо-данные для годового плана
|
||
const DEMO_DATA = [
|
||
{ id: 1, code: "ZK-2026-001", name: "Ноутбук Dell Latitude 3520", category: "IT-оборудование", group: "A", qty: 15, price: 285000, status: "new", comment: "" },
|
||
{ id: 2, code: "ZK-2026-002", name: "Ноутбук HP ProBook 450", category: "IT-оборудование", group: "A", qty: 10, price: 295000, status: "new", comment: "" },
|
||
{ id: 3, code: "ZK-2026-003", name: "Ноутбук Lenovo ThinkPad E15", category: "IT-оборудование", group: "A", qty: 12, price: 278000, status: "confirmed", comment: "Выбрали Dell, эта позиция лишняя" },
|
||
{ id: 4, code: "ZK-2026-004", name: "Бумага A4 SvetoCopy", category: "Канцтовары", group: "B", qty: 500, price: 1850, status: "new", comment: "" },
|
||
{ id: 5, code: "ZK-2026-005", name: "Бумага A4 Xerox Business", category: "Канцтовары", group: "B", qty: 300, price: 2100, status: "rejected", comment: "Дорого, есть аналог дешевле" },
|
||
{ id: 6, code: "ZK-2026-006", name: "Кресло офисное Chairman", category: "Мебель", group: "C", qty: 25, price: 45000, status: "new", comment: "" },
|
||
{ id: 7, code: "ZK-2026-007", name: "Кресло офисное Nowy Styl", category: "Мебель", group: "C", qty: 20, price: 52000, status: "new", comment: "" },
|
||
{ id: 8, code: "ZK-2026-008", name: "Стол офисный 1200мм", category: "Мебель", group: "C", qty: 30, price: 68000, status: "confirmed", comment: "" },
|
||
{ id: 9, code: "ZK-2026-009", name: "Картридж HP 12A", category: "Расходные материалы", group: "B", qty: 100, price: 8500, status: "new", comment: "" },
|
||
{ id: 10, code: "ZK-2026-010", name: "Картридж Canon 725", category: "Расходные материалы", group: "B", qty: 80, price: 7200, status: "new", comment: "" },
|
||
{ id: 11, code: "ZK-2026-011", name: "Монитор Dell 24 P2422H", category: "IT-оборудование", group: "A", qty: 20, price: 95000, status: "new", comment: "" },
|
||
{ id: 12, code: "ZK-2026-012", name: "Монитор LG 24MK600M", category: "IT-оборудование", group: "A", qty: 18, price: 88000, status: "rejected", comment: "Мало портов" },
|
||
{ id: 13, code: "ZK-2026-013", name: "Ручка шариковая синяя", category: "Канцтовары", group: "B", qty: 1000, price: 150, status: "confirmed", comment: "" },
|
||
{ id: 14, code: "ZK-2026-014", name: "Папка-регистратор А4", category: "Канцтовары", group: "B", qty: 200, price: 850, status: "new", comment: "" },
|
||
{ id: 15, code: "ZK-2026-015", name: "Степлер офисный", category: "Канцтовары", group: "B", qty: 50, price: 1200, status: "new", comment: "" }
|
||
];
|
||
|
||
let positions = [];
|
||
let selectedIds = new Set();
|
||
let compareIds = new Set();
|
||
let pendingAction = null;
|
||
let pendingIds = [];
|
||
|
||
// Загрузка данных из localStorage или демо
|
||
function loadData() {
|
||
const saved = localStorage.getItem('zakupki_positions');
|
||
if (saved) {
|
||
positions = JSON.parse(saved);
|
||
} else {
|
||
positions = [...DEMO_DATA];
|
||
saveData();
|
||
}
|
||
}
|
||
|
||
function saveData() {
|
||
localStorage.setItem('zakupki_positions', JSON.stringify(positions));
|
||
}
|
||
|
||
// Форматирование цены
|
||
function formatPrice(num) {
|
||
return num.toString().replace(/\s/g, '').replace(/(\d)(?=(\d{3})+$)/g, '$1 ');
|
||
}
|
||
|
||
// Отрисовка статистики
|
||
function renderStats() {
|
||
const total = positions.length;
|
||
const newCount = positions.filter(p => p.status === 'new').length;
|
||
const confirmed = positions.filter(p => p.status === 'confirmed').length;
|
||
const rejected = positions.filter(p => p.status === 'rejected').length;
|
||
const amount = positions.filter(p => p.status === 'confirmed').reduce((sum, p) => sum + p.qty * p.price, 0);
|
||
|
||
document.getElementById('stat-total').textContent = total;
|
||
document.getElementById('stat-new').textContent = newCount;
|
||
document.getElementById('stat-confirmed').textContent = confirmed;
|
||
document.getElementById('stat-rejected').textContent = rejected;
|
||
document.getElementById('stat-amount').textContent = formatPrice(amount) + ' ₸';
|
||
}
|
||
|
||
// Получение статус-чипа
|
||
function getStatusChip(status) {
|
||
const config = {
|
||
new: { text: 'Новая', tone: 'blue' },
|
||
confirmed: { text: 'Подтверждено', tone: 'green' },
|
||
rejected: { text: 'Отклонено', tone: 'red' }
|
||
};
|
||
const c = config[status];
|
||
return `<span class="kt-ai-chip" data-tone="${c.tone}">${c.text}</span>`;
|
||
}
|
||
|
||
// Фильтрация данных
|
||
function getFilteredData() {
|
||
const search = document.getElementById('filter-search').value.toLowerCase();
|
||
const category = document.getElementById('filter-category').value;
|
||
const status = document.getElementById('filter-status').value;
|
||
const group = document.getElementById('filter-group').value;
|
||
|
||
return positions.filter(p => {
|
||
if (search && !p.name.toLowerCase().includes(search) && !p.code.toLowerCase().includes(search)) return false;
|
||
if (category && p.category !== category) return false;
|
||
if (status && p.status !== status) return false;
|
||
if (group && p.group !== group) return false;
|
||
return true;
|
||
});
|
||
}
|
||
|
||
// Отрисовка таблицы
|
||
function renderTable() {
|
||
const data = getFilteredData();
|
||
const tbody = document.getElementById('table-body');
|
||
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(p => {
|
||
const sum = p.qty * p.price;
|
||
const isSelected = selectedIds.has(p.id);
|
||
const rowClass = isSelected ? 'selected' : '';
|
||
const isDisabled = p.status === 'confirmed' || p.status === 'rejected';
|
||
const disabledClass = isDisabled ? 'disabled' : '';
|
||
|
||
return `
|
||
<tr class="${rowClass} ${disabledClass}" data-id="${p.id}">
|
||
<td><input type="checkbox" class="row-checkbox" data-id="${p.id}" ${isSelected ? 'checked' : ''} ${isDisabled ? 'disabled' : ''}></td>
|
||
<td style="font-family:var(--kt-ai-font-mono);font-size:var(--kt-ai-text-xs)">${p.code}</td>
|
||
<td>${p.name}</td>
|
||
<td>${p.category}</td>
|
||
<td><span class="kt-ai-chip" data-tone="gray">Группа ${p.group}</span></td>
|
||
<td class="num">${formatPrice(p.qty)}</td>
|
||
<td class="num">${formatPrice(p.price)}</td>
|
||
<td class="num">${formatPrice(sum)}</td>
|
||
<td>${getStatusChip(p.status)}</td>
|
||
<td class="actions-cell">
|
||
${p.status === 'new' ? `
|
||
<button class="kt-ai-btn" data-variant="ghost" data-action="confirm" data-id="${p.id}">✓</button>
|
||
<button class="kt-ai-btn" data-variant="ghost" data-action="reject" data-id="${p.id}">✗</button>
|
||
` : `
|
||
<button class="kt-ai-btn" data-variant="ghost" data-action="view" data-id="${p.id}" title="Просмотр">👁</button>
|
||
`}
|
||
</td>
|
||
</tr>
|
||
`;
|
||
}).join('');
|
||
|
||
updateBulkActions();
|
||
}
|
||
|
||
// Обновление кнопок массовых действий
|
||
function updateBulkActions() {
|
||
const count = selectedIds.size;
|
||
document.getElementById('selected-count').textContent = `Выбрано: ${count}`;
|
||
document.getElementById('btn-bulk-confirm').disabled = count === 0;
|
||
document.getElementById('btn-bulk-reject').disabled = count === 0;
|
||
document.getElementById('btn-compare').disabled = count < 2;
|
||
}
|
||
|
||
// Открытие модального окна комментария
|
||
function openCommentModal(action, ids) {
|
||
pendingAction = action;
|
||
pendingIds = ids;
|
||
const modal = document.getElementById('comment-modal');
|
||
const title = document.getElementById('comment-title');
|
||
const text = document.getElementById('comment-text');
|
||
|
||
title.textContent = ids.length > 1
|
||
? `Комментарий для ${ids.length} позиций`
|
||
: (action === 'confirm' ? 'Подтверждение позиции' : 'Отклонение позиции');
|
||
text.value = '';
|
||
modal.classList.add('open');
|
||
text.focus();
|
||
}
|
||
|
||
// Закрытие модального окна
|
||
function closeCommentModal() {
|
||
document.getElementById('comment-modal').classList.remove('open');
|
||
pendingAction = null;
|
||
pendingIds = [];
|
||
}
|
||
|
||
// Применение действия
|
||
function applyAction(action, ids, comment) {
|
||
positions = positions.map(p => {
|
||
if (ids.includes(p.id)) {
|
||
return { ...p, status: action === 'confirm' ? 'confirmed' : 'rejected', comment };
|
||
}
|
||
return p;
|
||
});
|
||
saveData();
|
||
selectedIds.clear();
|
||
compareIds.clear();
|
||
document.getElementById('compare-panel').classList.remove('open');
|
||
renderStats();
|
||
renderTable();
|
||
}
|
||
|
||
// Обработчик чекбоксов
|
||
function handleCheckbox(id, checked) {
|
||
if (checked) {
|
||
selectedIds.add(id);
|
||
} else {
|
||
selectedIds.delete(id);
|
||
compareIds.delete(id);
|
||
}
|
||
updateBulkActions();
|
||
renderComparePanel();
|
||
}
|
||
|
||
// Открытие панели сравнения
|
||
function openComparePanel() {
|
||
const panel = document.getElementById('compare-panel');
|
||
panel.classList.add('open');
|
||
renderComparePanel();
|
||
}
|
||
|
||
// Отрисовка панели сравнения
|
||
function renderComparePanel() {
|
||
const list = document.getElementById('compare-list');
|
||
const items = positions.filter(p => compareIds.has(p.id));
|
||
|
||
if (items.length === 0) {
|
||
list.innerHTML = '<p style="color:var(--kt-ai-fg-muted);padding:var(--kt-ai-space-3)">Выберите минимум 2 позиции для сравнения</p>';
|
||
return;
|
||
}
|
||
|
||
list.innerHTML = items.map(p => `
|
||
<div class="compare-item">
|
||
<span class="remove" data-action="remove-compare" data-id="${p.id}">✕</span>
|
||
<div class="name">${p.name}</div>
|
||
<div class="detail">Код: ${p.code}</div>
|
||
<div class="detail">Кол-во: ${formatPrice(p.qty)}</div>
|
||
<div class="detail">Цена: ${formatPrice(p.price)} ₸</div>
|
||
<div class="detail">Сумма: ${formatPrice(p.qty * p.price)} ₸</div>
|
||
</div>
|
||
`).join('');
|
||
}
|
||
|
||
// Инициализация
|
||
function init() {
|
||
loadData();
|
||
renderStats();
|
||
renderTable();
|
||
|
||
// Фильтры
|
||
document.getElementById('filter-search').addEventListener('input', renderTable);
|
||
document.getElementById('filter-category').addEventListener('change', renderTable);
|
||
document.getElementById('filter-status').addEventListener('change', renderTable);
|
||
document.getElementById('filter-group').addEventListener('change', renderTable);
|
||
document.getElementById('btn-reset-filters').addEventListener('click', () => {
|
||
document.getElementById('filter-search').value = '';
|
||
document.getElementById('filter-category').value = '';
|
||
document.getElementById('filter-status').value = '';
|
||
document.getElementById('filter-group').value = '';
|
||
renderTable();
|
||
});
|
||
|
||
// Чекбокс "выбрать всё"
|
||
document.getElementById('check-all').addEventListener('change', (e) => {
|
||
const filtered = getFilteredData();
|
||
const available = filtered.filter(p => p.status === 'new').map(p => p.id);
|
||
if (e.target.checked) {
|
||
available.forEach(id => selectedIds.add(id));
|
||
} else {
|
||
available.forEach(id => selectedIds.delete(id));
|
||
}
|
||
renderTable();
|
||
});
|
||
|
||
// Клик по таблице
|
||
document.getElementById('table-body').addEventListener('click', (e) => {
|
||
const checkbox = e.target.closest('.row-checkbox');
|
||
const btn = e.target.closest('button[data-action]');
|
||
|
||
if (checkbox) {
|
||
handleCheckbox(parseInt(checkbox.dataset.id), checkbox.checked);
|
||
return;
|
||
}
|
||
|
||
if (btn) {
|
||
const action = btn.dataset.action;
|
||
const id = parseInt(btn.dataset.id);
|
||
|
||
if (action === 'confirm') {
|
||
openCommentModal('confirm', [id]);
|
||
} else if (action === 'reject') {
|
||
openCommentModal('reject', [id]);
|
||
} else if (action === 'view') {
|
||
const pos = positions.find(p => p.id === id);
|
||
alert(`Позиция: ${pos.name}\nСтатус: ${pos.status}\nКомментарий: ${pos.comment || '—'}`);
|
||
}
|
||
}
|
||
});
|
||
|
||
// Массовые действия
|
||
document.getElementById('btn-bulk-confirm').addEventListener('click', () => {
|
||
openCommentModal('confirm', [...selectedIds]);
|
||
});
|
||
document.getElementById('btn-bulk-reject').addEventListener('click', () => {
|
||
openCommentModal('reject', [...selectedIds]);
|
||
});
|
||
document.getElementById('btn-compare').addEventListener('click', openComparePanel);
|
||
|
||
// Сравнение
|
||
document.getElementById('btn-close-compare').addEventListener('click', () => {
|
||
document.getElementById('compare-panel').classList.remove('open');
|
||
});
|
||
document.getElementById('compare-list').addEventListener('click', (e) => {
|
||
if (e.target.dataset.action === 'remove-compare') {
|
||
compareIds.delete(parseInt(e.target.dataset.id));
|
||
selectedIds.delete(parseInt(e.target.dataset.id));
|
||
renderComparePanel();
|
||
renderTable();
|
||
}
|
||
});
|
||
document.getElementById('btn-confirm-compare').addEventListener('click', () => {
|
||
if (compareIds.size === 0) return;
|
||
openCommentModal('confirm', [...compareIds]);
|
||
});
|
||
document.getElementById('btn-reject-compare').addEventListener('click', () => {
|
||
if (compareIds.size === 0) return;
|
||
openCommentModal('reject', [...compareIds]);
|
||
});
|
||
|
||
// Модальное окно
|
||
document.getElementById('btn-cancel-comment').addEventListener('click', closeCommentModal);
|
||
document.getElementById('btn-save-comment').addEventListener('click', () => {
|
||
const comment = document.getElementById('comment-text').value.trim();
|
||
if (pendingAction && pendingIds.length > 0) {
|
||
applyAction(pendingAction, pendingIds, comment);
|
||
closeCommentModal();
|
||
}
|
||
});
|
||
|
||
// Закрытие по клику вне
|
||
document.getElementById('comment-modal').addEventListener('click', (e) => {
|
||
if (e.target.id === 'comment-modal') {
|
||
closeCommentModal();
|
||
}
|
||
});
|
||
}
|
||
|
||
init();
|
||
</script>
|
||
|
||
</body>
|
||
</html>
|