let allOrders = []; let currentFilter = { branch: 'all', type: 'all', search: '' }; let selectedOrder = null; let charts = {}; async function loadOrders() { try { const res = await fetch('/api/items'); const data = await res.json(); allOrders = data.items || []; renderTable(); updateKPI(); } catch (e) { console.error('Failed to load orders:', e); } } function formatDate(iso) { if (!iso) return ''; const d = new Date(iso); return d.toLocaleDateString('ru-RU', { day: '2-digit', month: '2-digit', year: 'numeric' }); } function getTypeClass(type) { if (!type) return 'type-prikaz'; const t = type.toLowerCase(); if (t.includes('приказ')) return 'type-prikaz'; if (t.includes('распоряж')) return 'type-rasp'; if (t.includes('утвержд')) return 'type-utv'; if (t.includes('решение')) return 'type-resh'; return 'type-prikaz'; } function getStatusClass(status) { if (!status) return 'status-draft'; const s = status.toLowerCase(); if (s.includes('действ')) return 'status-active'; if (s.includes('истёк') || s.includes('истек')) return 'status-expired'; return 'status-draft'; } function getFilteredOrders() { return allOrders.filter(o => { if (currentFilter.branch !== 'all' && o.branch !== currentFilter.branch) return false; if (currentFilter.type !== 'all' && o.type !== currentFilter.type) return false; if (currentFilter.search) { const s = currentFilter.search.toLowerCase(); const match = (o.number || '').toLowerCase().includes(s) || (o.title || '').toLowerCase().includes(s) || (o.branch || '').toLowerCase().includes(s); if (!match) return false; } return true; }); } function renderTable() { const orders = getFilteredOrders(); const tbody = document.getElementById('ordersBody'); const empty = document.getElementById('emptyState'); if (orders.length === 0) { tbody.innerHTML = ''; empty.style.display = 'block'; return; } empty.style.display = 'none'; tbody.innerHTML = orders.map(o => ` ${o.type || 'Приказ'} ${o.number || ''} ${formatDate(o.date)} ${o.title || ''} ${o.branch || ''} ${o.status || 'Проект'} `).join(''); tbody.querySelectorAll('tr').forEach(tr => { tr.addEventListener('click', () => { const id = parseInt(tr.dataset.id); const order = allOrders.find(o => o.id === id); if (order) openDrawer(order); }); }); } function updateKPI() { const now = new Date(); const thisMonth = now.toISOString().slice(0, 7); const branches = new Set(allOrders.map(o => o.branch).filter(Boolean)); const active = allOrders.filter(o => (o.status || '').toLowerCase().includes('действ')).length; const monthCount = allOrders.filter(o => (o.date || '').startsWith(thisMonth)).length; document.getElementById('kpi-total').textContent = allOrders.length; document.getElementById('kpi-active').textContent = active; document.getElementById('kpi-month').textContent = monthCount; document.getElementById('kpi-branches').textContent = branches.size; } function openDrawer(order) { selectedOrder = order; document.getElementById('drawerTitle').textContent = order.title || 'Без названия'; document.getElementById('drawerMeta').textContent = `${order.number || ''} • ${formatDate(order.date)} • ${order.branch || ''}`; document.getElementById('drawerContent').innerHTML = `
Тип документа
${order.type || 'Приказ'}
Номер
${order.number || '—'}
Дата
${formatDate(order.date)}
Филиал
${order.branch || '—'}
Статус
${order.status || 'Проект'}
Содержание
${order.content || 'Нет содержания'}
`; document.getElementById('drawer').classList.add('open'); document.getElementById('drawerOverlay').classList.add('open'); } function closeDrawer() { document.getElementById('drawer').classList.remove('open'); document.getElementById('drawerOverlay').classList.remove('open'); selectedOrder = null; } function renderCharts() { const ctxTypes = document.getElementById('chartTypes').getContext('2d'); const ctxBranches = document.getElementById('chartBranches').getContext('2d'); const ctxMonths = document.getElementById('chartMonths').getContext('2d'); const ctxStatus = document.getElementById('chartStatus').getContext('2d'); // По типам const byType = {}; allOrders.forEach(o => { byType[o.type || 'Приказ'] = (byType[o.type || 'Приказ'] || 0) + 1; }); // По филиалам const byBranch = {}; allOrders.forEach(o => { byBranch[o.branch || 'Не указан'] = (byBranch[o.branch || 'Не указан'] || 0) + 1; }); // По месяцам const byMonth = {}; allOrders.forEach(o => { const m = (o.date || '').slice(0, 7) || 'Не указана'; byMonth[m] = (byMonth[m] || 0) + 1; }); // По статусам const byStatus = {}; allOrders.forEach(o => { byStatus[o.status || 'Проект'] = (byStatus[o.status || 'Проект'] || 0) + 1; }); const common = { responsive: true, maintainAspectRatio: false, plugins: { legend: { display: false } }, scales: { x: { grid: { display: false } }, y: { beginAtZero: true, ticks: { precision: 0 } } } }; if (charts.types) charts.types.destroy(); charts.types = new Chart(ctxTypes, { type: 'doughnut', data: { labels: Object.keys(byType), datasets: [{ data: Object.values(byType), backgroundColor: ['#3b82f6', '#ec4899', '#10b981', '#f59e0b'] }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { position: 'bottom' } } } }); if (charts.branches) charts.branches.destroy(); charts.branches = new Chart(ctxBranches, { type: 'bar', data: { labels: Object.keys(byBranch), datasets: [{ data: Object.values(byBranch), backgroundColor: '#3b82f6' }] }, options: { ...common, indexAxis: 'y' } }); if (charts.months) charts.months.destroy(); charts.months = new Chart(ctxMonths, { type: 'line', data: { labels: Object.keys(byMonth).sort(), datasets: [{ data: Object.values(byMonth), borderColor: '#10b981', fill: true, backgroundColor: 'rgba(16,185,129,0.1)' }] }, options: common }); if (charts.status) charts.status.destroy(); charts.status = new Chart(ctxStatus, { type: 'pie', data: { labels: Object.keys(byStatus), datasets: [{ data: Object.values(byStatus), backgroundColor: ['#10b981', '#ef4444', '#f59e0b'] }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { position: 'bottom' } } } }); } // Event listeners document.addEventListener('DOMContentLoaded', () => { loadOrders(); // Поиск document.getElementById('searchInput').addEventListener('input', (e) => { currentFilter.search = e.target.value; renderTable(); }); // Фильтры по типу document.querySelectorAll('[data-type]').forEach(btn => { btn.addEventListener('click', () => { document.querySelectorAll('[data-type]').forEach(b => b.classList.remove('is-active')); btn.classList.add('is-active'); currentFilter.type = btn.dataset.type; renderTable(); }); }); // Фильтры по филиалам document.querySelectorAll('[data-branch]').forEach(btn => { btn.addEventListener('click', (e) => { e.preventDefault(); currentFilter.branch = btn.dataset.branch; renderTable(); }); }); // Открытие drawer document.getElementById('drawerOverlay').addEventListener('click', closeDrawer); document.getElementById('closeDrawer').addEventListener('click', closeDrawer); // Статистика document.getElementById('openStats').addEventListener('click', (e) => { e.preventDefault(); renderCharts(); document.getElementById('statsModal').classList.add('open'); }); document.getElementById('closeStats').addEventListener('click', () => { document.getElementById('statsModal').classList.remove('open'); }); // Кнопки добавления document.getElementById('addBtn').addEventListener('click', () => { alert('Форма добавления документа будет в следующей версии'); }); document.getElementById('addBtnSidebar').addEventListener('click', () => { alert('Форма добавления документа будет в следующей версии'); }); // Удаление document.getElementById('deleteBtn').addEventListener('click', async () => { if (!selectedOrder || !confirm('Удалить документ ' + selectedOrder.number + '?')) return; try { await fetch('/api/items', { method: 'DELETE', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id: selectedOrder.id }) }); closeDrawer(); loadOrders(); } catch (e) { console.error('Delete failed:', e); } }); });