// Логика страницы отчетов (function() { // Проверка авторизации if (!auth.isUserAuthenticated()) { window.location.href = '../login.html'; return; } const state = { filters: { dateFrom: '', dateTo: '', opType: '', categoryId: '', itemQuery: '', employeeQuery: '' }, data: { summary: null, operations: [], byCategory: [], topItems: [] } }; // Инициализация $(document).ready(() => { bindEvents(); loadCategoriesForFilter(); applyFilters(); loadCategoriesNav(); }); function bindEvents() { $('#filtersForm').on('submit', function(e) { e.preventDefault(); state.filters = { dateFrom: $('#dateFrom').val(), dateTo: $('#dateTo').val(), opType: $('#opType').val(), categoryId: $('#categoryId').val(), itemQuery: $('#itemQuery').val(), employeeQuery: $('#employeeQuery').val() }; applyFilters(); }); $('#exportCsvBtn').on('click', exportCsv); $('#exportJsonBtn').on('click', exportJson); $('#resetFiltersBtn').on('click', resetFilters); } async function applyFilters() { try { showLoading(true); const token = localStorage.getItem('token'); const params = new URLSearchParams(state.filters); const headers = token ? { 'Authorization': `Bearer ${token}` } : {}; // Загружаем сводные данные и таблицы const [summaryResp, opsResp, byCatResp, topItemsResp] = await Promise.all([ fetch(`/api/reports/summary?${params.toString()}`, { headers }), fetch(`/api/reports/operations?${params.toString()}`, { headers }), fetch(`/api/reports/by-category?${params.toString()}`, { headers }), fetch(`/api/reports/top-items?${params.toString()}`, { headers }) ]); if (!summaryResp.ok || !opsResp.ok || !byCatResp.ok || !topItemsResp.ok) { throw new Error('Ошибка загрузки данных отчетов'); } state.data.summary = await summaryResp.json(); state.data.operations = await opsResp.json(); state.data.byCategory = await byCatResp.json(); state.data.topItems = await topItemsResp.json(); renderSummary(); renderTables(); } catch (e) { auth.showNotification('Ошибка при загрузке отчетов: ' + e.message, 'danger'); // Очищаем таблицы при ошибке API state.data = { summary: null, operations: [], byCategory: [], topItems: [] }; renderSummary(); renderTables(); } finally { showLoading(false); } } function resetFilters() { // Сбрасываем значения полей $('#dateFrom').val(''); $('#dateTo').val(''); $('#opType').val(''); $('#categoryId').val(''); $('#itemQuery').val(''); $('#employeeQuery').val(''); // Обнуляем состояние фильтров и перезагружаем state.filters = { dateFrom: '', dateTo: '', opType: '', categoryId: '', itemQuery: '', employeeQuery: '' }; applyFilters(); } function renderSummary() { const s = state.data.summary || { totalIncoming: 0, totalOutgoing: 0, totalOperations: 0, uniqueItems: 0 }; $('#totalIncoming').text(s.totalIncoming); $('#totalOutgoing').text(s.totalOutgoing); $('#totalOps').text(s.totalOperations); $('#uniqueItems').text(s.uniqueItems); } function renderTables() { const opsBody = $('#operationsTable tbody'); opsBody.empty(); state.data.operations.forEach(op => { opsBody.append(` ${formatDate(op.date)} ${op.type === 'incoming' ? 'Приход' : 'Расход'} ${escapeHtml(op.itemName)} ${op.quantity} ${escapeHtml(op.employeeName || op.username || '')} ${escapeHtml(op.categoryName || '')} `); }); const byCatBody = $('#byCategoryTable tbody'); byCatBody.empty(); state.data.byCategory.forEach(row => { byCatBody.append(` ${escapeHtml(row.categoryName)} ${row.itemCount} ${row.totalQuantity} `); }); const topItemsBody = $('#topItemsTable tbody'); topItemsBody.empty(); state.data.topItems.forEach(row => { topItemsBody.append(` ${escapeHtml(row.itemName)} ${row.totalIncoming} ${row.totalOutgoing} ${row.totalIncoming - row.totalOutgoing} `); }); } function exportCsv() { const rows = [ ['Дата','Тип','Товар','Количество','Сотрудник','Категория'], ...state.data.operations.map(o => [formatDate(o.date), o.type, o.itemName, o.quantity, o.employeeName || '', o.categoryName || '']) ]; const csv = rows.map(r => r.map(v => `"${String(v).replaceAll('"','""')}"`).join(',')).join('\n'); downloadFile('operations.csv', 'text/csv;charset=utf-8;', csv); } function exportJson() { const blob = new Blob([JSON.stringify(state.data, null, 2)], { type: 'application/json;charset=utf-8;' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'reports.json'; a.click(); URL.revokeObjectURL(url); } async function loadCategoriesForFilter() { try { const resp = await fetch('/api/categories'); const cats = await resp.json(); const select = $('#categoryId'); cats.forEach(c => select.append(``)); } catch {} } function loadCategoriesNav() { // На странице отчётов категории в меню не критичны; пропустим загрузку, чтобы не блокировать UI } function showLoading(show) { $('#loadingState').toggle(!!show); } function formatDate(iso) { const d = new Date(iso); return d.toLocaleString('ru-RU'); } function escapeHtml(s) { return String(s || '').replace(/[&<>"]+/g, c => ({'&':'&','<':'<','>':'>','"':'"'}[c])); } })();