diff --git a/ui.js b/ui.js
deleted file mode 100644
index e4faf80..0000000
--- a/ui.js
+++ /dev/null
@@ -1,248 +0,0 @@
-/**
- * ui.js — Компоненты интерфейса
- * Рендер файлов, таблиц, сортировка, вкладки, управление темой
- */
-
-import { formatMoney } from './utils.js';
-
-/* ========== Управление файлами ========== */
-
-/**
- * Создать DOM-элемент для отображения загруженного файла
- * @param {File} file
- * @param {number} index
- * @param {string} type - 'vedomost' | 'transaction'
- * @returns {HTMLElement}
- */
-export function renderFileItem(file, index, type) {
- const isTrans = type === 'transaction';
- const el = document.createElement('div');
- el.className = 'file-item';
- el.dataset.index = index;
- el.innerHTML = `
-
- ${isTrans ? '📊' : '📋'}
-
-
-
${escapeHtml(file.name)}
-
${isTrans ? 'Выгрузка транзакций' : 'Ведомость'} · ${(file.size / 1024).toFixed(0)} КБ
-
- `;
- return el;
-}
-
-/**
- * Создать DOM-элемент для сообщения об ошибке в файле
- * @param {string} fileName
- * @param {string} message
- * @returns {HTMLElement}
- */
-export function renderFileError(fileName, message) {
- const el = document.createElement('div');
- el.className = 'file-item file-error';
- el.innerHTML = `
- ⚠️
-
-
${escapeHtml(fileName)}
-
${escapeHtml(message)}
-
`;
- return el;
-}
-
-/* ========== Сортировка таблиц ========== */
-
-/**
- * Отсортировать массив данных по ключу
- * @param {Object[]} data
- * @param {string} key
- * @param {boolean} asc
- * @returns {Object[]}
- */
-export function sortData(data, key, asc = true) {
- return [...data].sort((a, b) => {
- let av = a[key], bv = b[key];
- if (av === null || av === undefined) av = '';
- if (bv === null || bv === undefined) bv = '';
- if (typeof av === 'number' && typeof bv === 'number') {
- return asc ? av - bv : bv - av;
- }
- // Числовые строки сравниваем как числа
- const na = parseFloat(String(av).replace(/\s/g, '').replace(',', '.'));
- const nb = parseFloat(String(bv).replace(/\s/g, '').replace(',', '.'));
- if (!isNaN(na) && !isNaN(nb)) {
- return asc ? na - nb : nb - na;
- }
- return asc
- ? String(av).localeCompare(String(bv), 'ru')
- : String(bv).localeCompare(String(av), 'ru');
- });
-}
-
-/**
- * Включить сортировку по клику на заголовки таблицы
- * @param {string} tbodyId - ID tbody
- * @param {Object[]} sourceData - ссылка на исходный массив
- * @param {Function} renderFn - функция перерисовки
- */
-export function enableSorting(tbodyId, sourceData, renderFn) {
- const tbody = document.getElementById(tbodyId);
- if (!tbody) return;
- const table = tbody.closest('table');
- if (!table) return;
- const ths = table.querySelectorAll('th[data-key]');
- let sortState = { key: null, asc: true };
-
- ths.forEach(th => {
- th.style.cursor = 'pointer';
- th.addEventListener('click', () => {
- const key = th.dataset.key;
- if (sortState.key === key) {
- sortState.asc = !sortState.asc;
- } else {
- sortState.key = key;
- sortState.asc = true;
- }
- // Визуальный индикатор
- ths.forEach(t => t.classList.remove('sort-asc', 'sort-desc'));
- th.classList.add(sortState.asc ? 'sort-asc' : 'sort-desc');
- const sorted = sortData(sourceData, key, sortState.asc);
- renderFn(sorted);
- });
- });
-}
-
-/* ========== Рендер таблиц ========== */
-
-/**
- * Рендер тела таблицы из массива объектов
- * @param {HTMLElement} tbody
- * @param {Object[]} rows
- * @param {string[]} keys - порядок/набор ключей для отображения
- * @param {Function} formatter - функция форматирования ячейки (value, key, row) => string
- */
-export function renderTableBody(tbody, rows, keys, formatter) {
- if (!rows || rows.length === 0) {
- tbody.innerHTML = `| Нет данных |
`;
- return;
- }
- tbody.innerHTML = rows.map(row => {
- const cells = (keys || Object.keys(row)).map(key => {
- const val = row[key];
- const formatted = formatter ? formatter(val, key, row) : formatCell(val);
- return `${formatted} | `;
- }).join('');
- return `${cells}
`;
- }).join('');
-}
-
-/**
- * Форматирование значения ячейки по умолчанию
- */
-function formatCell(val) {
- if (val === null || val === undefined || val === '') return '—';
- if (typeof val === 'number') return formatMoney(val);
- return escapeHtml(String(val));
-}
-
-/**
- * Экранирование HTML
- */
-function escapeHtml(str) {
- const div = document.createElement('div');
- div.textContent = str;
- return div.innerHTML;
-}
-
-/* ========== Управление вкладками ========== */
-
-/**
- * Инициализировать переключатели вкладок
- */
-export function initTabs() {
- document.querySelectorAll('.tab-btn').forEach(btn => {
- btn.addEventListener('click', () => {
- document.querySelectorAll('.tab-btn').forEach(x => x.classList.remove('active'));
- document.querySelectorAll('.tab-panel').forEach(x => x.classList.remove('active'));
- btn.classList.add('active');
- const panel = document.getElementById('panel-' + btn.dataset.tab);
- if (panel) panel.classList.add('active');
- });
- });
-}
-
-/* ========== Тема ========== */
-
-let currentTheme = localStorage.getItem('theme') || 'light';
-
-/**
- * Применить тему
- */
-export function applyTheme() {
- document.documentElement.setAttribute('data-theme', currentTheme);
- const btn = document.getElementById('btnToggleTheme');
- if (btn) {
- btn.textContent = currentTheme === 'light'
- ? '🌙 Тёмная тема'
- : '☀️ Светлая тема';
- }
-}
-
-/**
- * Переключить тему
- */
-export function toggleTheme() {
- currentTheme = currentTheme === 'light' ? 'dark' : 'light';
- localStorage.setItem('theme', currentTheme);
- applyTheme();
-}
-
-/**
- * Инициализировать тему
- */
-export function initTheme(toggleBtnId) {
- applyTheme();
- const btn = document.getElementById(toggleBtnId);
- if (btn) btn.addEventListener('click', toggleTheme);
-}
-
-/* ========== Прогресс-бар ========== */
-
-/**
- * Показать прогресс-бар с анимацией
- * @param {number} duration - длительность анимации в мс
- * @returns {Promise} - разрешается по завершении
- */
-export function showProgress(duration = 800) {
- const bar = document.getElementById('progressBar');
- const fill = document.getElementById('progressFill');
- if (!bar || !fill) return Promise.resolve();
- bar.classList.add('active');
- fill.style.width = '0%';
- return new Promise(resolve => {
- // Плавная анимация до 95%
- requestAnimationFrame(() => {
- fill.style.transition = `width ${duration}ms cubic-bezier(0.4, 0, 0.2, 1)`;
- fill.style.width = '95%';
- });
- // Завершение
- setTimeout(() => {
- fill.style.width = '100%';
- setTimeout(() => {
- bar.classList.remove('active');
- fill.style.width = '0%';
- fill.style.transition = 'none';
- resolve();
- }, 300);
- }, duration + 200);
- });
-}
-
-/**
- * Сбросить прогресс-бар
- */
-export function resetProgress() {
- const bar = document.getElementById('progressBar');
- const fill = document.getElementById('progressFill');
- if (bar) bar.classList.remove('active');
- if (fill) { fill.style.width = '0%'; fill.style.transition = 'none'; }
-}
\ No newline at end of file