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);
}
});
});