diff --git a/src/ui.js b/src/ui.js
deleted file mode 100644
index f80d1e5..0000000
--- a/src/ui.js
+++ /dev/null
@@ -1,217 +0,0 @@
-// ui.js
-// Основные функции UI: загрузка файлов, отображение списка, запуск сверки, сортировка, фильтрация, экспорт CSV, переключатель темы.
-import {readExcel, validateFile, normalizePlate, normalizeName, similarity} from './utils.js';
-
-const state = {
- files: [], // загруженные файлы (File objects)
- vedomosti: [], // массив объектов {driver, plate, cash}
- transactions: [], // массив объектов {driver, date, sum}
- results: [] // массив {driver, plate, cash, collected, diff, status}
-};
-
-// ---------- Элементы UI ----------
-const dropzone = document.getElementById('dropzone');
-const fileInput = document.getElementById('fileInput');
-const fileList = document.getElementById('fileList');
-const btnReconcile = document.getElementById('btnReconcile');
-const btnClear = document.getElementById('btnClear');
-const progressBar = document.getElementById('progressBar');
-const progressFill = document.getElementById('progressFill');
-const emptyState = document.getElementById('emptyState');
-const resultActions = document.getElementById('resultActions');
-const tabsContainer = document.getElementById('tabsContainer');
-const themeToggle = document.getElementById('themeToggle');
-
-// ---------- Загрузка файлов ----------
-function initDropzone() {
- dropzone.addEventListener('click', () => fileInput.click());
- dropzone.addEventListener('dragover', e => { e.preventDefault(); dropzone.classList.add('dragover'); });
- dropzone.addEventListener('dragleave', () => dropzone.classList.remove('dragover'));
- dropzone.addEventListener('drop', e => {
- e.preventDefault(); dropzone.classList.remove('dragover');
- handleFiles(e.dataTransfer.files);
- });
- fileInput.addEventListener('change', e => handleFiles(e.target.files));
-}
-
-function handleFiles(files) {
- for (const f of files) {
- if (validateFile(f)) state.files.push(f);
- }
- renderFileList();
- updateButtons();
-}
-
-function renderFileList() {
- fileList.innerHTML = '';
- state.files.forEach((file, i) => {
- const isTrans = /transaction/i.test(file.name);
- const el = document.createElement('div');
- el.className = 'file-item';
- el.innerHTML = `
${isTrans ? 'TX' : 'VD'}
- ${file.name}
-
${isTrans ? 'Выгрузка транзакций' : 'Ведомость'} • ${(file.size / 1024).toFixed(0)} КБ
- `;
- fileList.appendChild(el);
- });
- fileList.querySelectorAll('.file-remove').forEach(b => {
- b.addEventListener('click', e => {
- const idx = +e.currentTarget.dataset.i;
- state.files.splice(idx, 1);
- renderFileList();
- updateButtons();
- });
- });
-}
-
-function updateButtons() {
- const enabled = state.files.length > 0;
- btnReconcile.disabled = !enabled;
- btnClear.disabled = !enabled;
-}
-
-btnClear.addEventListener('click', () => {
- state.files = [];
- fileInput.value = '';
- renderFileList();
- updateButtons();
- emptyState.style.display = '';
- resultActions.style.display = 'none';
- tabsContainer.style.display = 'none';
-});
-
-// ---------- Тема ----------
-themeToggle.addEventListener('click', () => {
- const html = document.documentElement;
- const dark = html.dataset.theme === 'dark';
- html.dataset.theme = dark ? '' : 'dark';
- themeToggle.textContent = dark ? 'Светлая тема' : 'Тёмная тема';
-});
-
-// ---------- Сверка ----------
-btnReconcile.addEventListener('click', async () => {
- progressBar.classList.add('active');
- progressFill.style.width = '0%';
- let progress = 0;
- const step = inc => {
- progress = Math.min(100, progress + inc);
- progressFill.style.width = `${progress}%`;
- };
-
- // 1. Чтение файлов
- const sheetsArr = await Promise.all(state.files.map(f => readExcel(f)));
- state.vedomosti = [];
- state.transactions = [];
- sheetsArr.forEach((sheets, idx) => {
- const file = state.files[idx];
- const isTrans = /transaction/i.test(file.name);
- const sheet = sheets[0]; // берем первый лист
- const header = sheet.data[0].map(h => String(h).toUpperCase().trim());
- if (isTrans) {
- const iDriver = header.findIndex(h => h.includes('CONDUCTOR'));
- const iDate = header.findIndex(h => h.includes('DATE'));
- const iSum = header.findIndex(h => h.includes('TARIF_SUM'));
- if (iDriver === -1 || iDate === -1 || iSum === -1) return; // пропускаем некорректные
- for (let r = 1; r < sheet.data.length; r++) {
- const row = sheet.data[r];
- const driver = String(row[iDriver] || '').trim();
- const date = String(row[iDate] || '').trim();
- const sum = Number(row[iSum] || 0) / 100; // копейки -> рубли
- if (driver) state.transactions.push({ driver, date, sum });
- }
- } else {
- const iDriver = header.findIndex(h => h.includes('ВОДИТЕЛЬ') || h.includes('DRIVER'));
- const iPlate = header.findIndex(h => h.includes('ГОС. НОМЕР') || h.includes('PLATE'));
- const iCash = header.findIndex(h => h.includes('СДАНО') || h.includes('CASH'));
- if (iDriver === -1) return;
- for (let r = 1; r < sheet.data.length; r++) {
- const row = sheet.data[r];
- const driver = String(row[iDriver] || '').trim();
- const plate = iPlate !== -1 ? String(row[iPlate] || '').trim() : '';
- const cash = iCash !== -1 ? Number(row[iCash] || 0) : 0;
- state.vedomosti.push({ driver, plate, cash });
- }
- }
- });
- step(20);
-
- // 2. Сопоставление и расчёт
- const transMap = {};
- state.transactions.forEach(t => {
- const key = `${normalizeName(t.driver)}|${normalizePlate(t.driver)}`;
- transMap[key] = (transMap[key] || 0) + t.sum;
- });
- state.results = state.vedomosti.map(v => {
- const key = `${normalizeName(v.driver)}|${normalizePlate(v.plate)}`;
- const collected = transMap[key] || 0;
- const diff = collected - v.cash;
- let status = 'ok';
- if (diff === 0) status = 'ok';
- else if (Math.abs(diff) <= 100) status = 'warn';
- else status = 'err';
- return { driver: v.driver, plate: v.plate, cash: v.cash, collected, diff, status };
- });
- step(40);
-
- // 3. Отображение результатов
- renderResults();
- step(40);
- progressBar.classList.remove('active');
-});
-
-function renderResults() {
- emptyState.style.display = 'none';
- resultActions.style.display = '';
- tabsContainer.style.display = '';
- // Таблица расхождений
- const tbody = document.getElementById('tableDiscrepancies');
- tbody.innerHTML = state.results.map(r => {
- const diffColor = r.diff === 0 ? '#10b981' : '#ef4444';
- const badgeClass = r.status === 'ok' ? 'badge-ok' : (r.status === 'warn' ? 'badge-warn' : 'badge-err');
- const badgeText = r.status === 'ok' ? '✓ Совпадает' : (r.status === 'warn' ? `⚠ ${r.diff} ₽` : `✕ ${r.diff} ₽`);
- const diffSign = r.diff > 0 ? '+' : '';
- return `
- | ${r.driver} |
- ${r.plate} |
- ${r.cash.toLocaleString('ru-RU')} |
- ${r.collected.toLocaleString('ru-RU')} |
- ${diffSign}${r.diff.toLocaleString('ru-RU')} |
- ${badgeText} |
-
`;
- }).join('');
- document.getElementById('countDisc').textContent = state.results.filter(r => r.status !== 'ok').length;
- document.getElementById('countAll').textContent = state.results.length;
- // Таблица "Все водители"
- const allBody = document.getElementById('tableAll');
- allBody.innerHTML = state.results.map(r => `| - | ${r.driver} | - | ${r.cash} | ${r.collected} | ${r.diff} |
`).join('');
- // CSV экспорт
- document.getElementById('btnDownload').onclick = exportCSV;
-}
-
-function exportCSV() {
- const header = ['Водитель', 'Гос. номер', 'Сдано', 'Собрано', 'Расхождение', 'Статус'];
- const rows = state.results.map(r => [r.driver, r.plate, r.cash, r.collected, r.diff, r.status]);
- const csvContent = [header, ...rows]
- .map(row => row.map(v => `"${String(v).replace(/"/g, '""')}"`).join(','))
- .join('\n');
- const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
- const url = URL.createObjectURL(blob);
- const a = document.createElement('a');
- a.href = url;
- a.download = 'reconciliation.csv';
- a.click();
- URL.revokeObjectURL(url);
-}
-
-// ---------- Таб переключения ----------
-document.querySelectorAll('.tab-btn').forEach(b => {
- b.addEventListener('click', () => {
- document.querySelectorAll('.tab-btn').forEach(x => x.classList.remove('active'));
- document.querySelectorAll('.tab-panel').forEach(x => x.classList.remove('active'));
- b.classList.add('active');
- document.getElementById('panel-' + b.dataset.tab).classList.add('active');
- });
-});
-
-// ---------- Инициализация ----------
-initDropzone();