mne-nuzhno-sozdat-sayt-dlya/script.js

288 lines
10 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 => `
<tr data-id="${o.id}">
<td><span class="type-badge ${getTypeClass(o.type)}">${o.type || 'Приказ'}</span></td>
<td style="font-family:var(--kt-ai-font-mono)">${o.number || ''}</td>
<td>${formatDate(o.date)}</td>
<td style="max-width:400px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">${o.title || ''}</td>
<td>${o.branch || ''}</td>
<td><span class="status-pill ${getStatusClass(o.status)}">${o.status || 'Проект'}</span></td>
</tr>
`).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 = `
<div class="detail-row">
<div class="detail-label">Тип документа</div>
<div class="detail-value"><span class="type-badge ${getTypeClass(order.type)}">${order.type || 'Приказ'}</span></div>
</div>
<div class="detail-row">
<div class="detail-label">Номер</div>
<div class="detail-value">${order.number || '—'}</div>
</div>
<div class="detail-row">
<div class="detail-label">Дата</div>
<div class="detail-value">${formatDate(order.date)}</div>
</div>
<div class="detail-row">
<div class="detail-label">Филиал</div>
<div class="detail-value">${order.branch || '—'}</div>
</div>
<div class="detail-row">
<div class="detail-label">Статус</div>
<div class="detail-value"><span class="status-pill ${getStatusClass(order.status)}">${order.status || 'Проект'}</span></div>
</div>
<div class="detail-row">
<div class="detail-label">Содержание</div>
<div class="detail-value" style="white-space:pre-wrap">${order.content || 'Нет содержания'}</div>
</div>
`;
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);
}
});
});