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

308 lines
11 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 = {};
let currentUser = null;
async function checkAuth() {
try {
const res = await fetch('/api/me');
const data = await res.json();
if (data.ok) {
currentUser = data.user;
document.getElementById('userName').textContent = user.name;
document.getElementById('userRole').textContent = user.role;
document.getElementById('app').style.display = 'block';
loadOrders();
} else {
window.location.href = '/login.html';
}
} catch (e) {
window.location.href = '/login.html';
}
}
async function loadOrders() {
try {
const res = await fetch('/api/items');
const data = await res.json();
if (data.error) {
window.location.href = '/login.html';
return;
}
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' } } }
});
}
async function logout() {
try {
await fetch('/api/logout', { method: 'POST' });
} catch (e) {}
window.location.href = '/login.html';
}
document.addEventListener('DOMContentLoaded', () => {
checkAuth();
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();
});
});
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);
}
});
document.getElementById('logoutBtn').addEventListener('click', logout);
});