let items = []; let filteredItems = []; let currentFilter = { status: '', search: '', requireCheck: false }; let sortField = 'name'; let sortAsc = true; let currentUser = null; let token = null; const $ = (id) => document.getElementById(id); // Токен и пользователь из localStorage token = localStorage.getItem('token'); currentUser = JSON.parse(localStorage.getItem('user') || 'null'); async function checkAuth() { if (!token || !currentUser) { window.location.href = 'login.html'; return; } $('userInfo').textContent = currentUser.login + (currentUser.role === 'admin' ? ' (Админ)' : ''); $('usersBtn').style.display = currentUser.role === 'admin' ? 'inline-block' : 'none'; } async function apiRequest(url, method = 'GET') { const res = await fetch(url); return res.json(); } async function loadItems() { const data = await apiRequest('/api/items'); if (data) { items = data; applyFilters(); } } async function loadStats() { const stats = await apiRequest('/api/stats'); if (!stats) return; $('statTotal').textContent = stats.total; $('statExploitation').textContent = stats.byStatus['в эксплуатации'] || 0; $('statWarehouse').textContent = stats.byStatus['на складе'] || 0; $('statRepair').textContent = stats.byStatus['в ремонте'] || 0; $('statWrittenOff').textContent = stats.byStatus['списано'] || 0; $('statRequireCheck').textContent = stats.requireCheck || 0; $('statOverdue').textContent = stats.overdue || 0; } function applyFilters() { filteredItems = items.filter(item => { if (currentFilter.status === 'require') { const in30Days = new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString().slice(0, 10); return item.nextCheckDate && item.nextCheckDate <= in30Days; } if (currentFilter.status && item.status !== currentFilter.status) return false; if (currentFilter.search) { const s = currentFilter.search.toLowerCase(); const text = (item.name + ' ' + (item.serialNumber||'') + ' ' + item.inventoryNumber + ' ' + item.responsible + ' ' + item.location).toLowerCase(); if (!text.includes(s)) return false; } return true; }); sortItems(); renderTable(); } function sortItems() { filteredItems.sort((a, b) => { const aVal = a[sortField] || ''; const bVal = b[sortField] || ''; const cmp = aVal.localeCompare(bVal, 'ru'); return sortAsc ? cmp : -cmp; }); } function renderTable() { const tbody = $('tableBody'); const today = new Date().toISOString().slice(0, 10); const in30Days = new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString().slice(0, 10); const canEdit = currentUser && currentUser.role === 'admin'; tbody.innerHTML = filteredItems.map(item => { let dateClass = 'check-date-ok'; if (item.nextCheckDate && item.nextCheckDate < today) dateClass = 'check-date-overdue'; else if (item.nextCheckDate && item.nextCheckDate <= in30Days) dateClass = 'check-date-warning'; return ` ${escapeHtml(item.name)} ${escapeHtml(item.type)} ${escapeHtml(item.inventoryNumber)} ${escapeHtml(item.responsible)} ${escapeHtml(item.location)} ${escapeHtml(item.status)} ${formatDate(item.nextCheckDate)} ${canEdit ? `` : ''} ${canEdit ? `` : ''} ${!canEdit ? 'Только просмотр' : ''} `; }).join(''); } function escapeHtml(text) { const div = document.createElement('div'); div.textContent = text || ''; return div.innerHTML; } function formatDate(dateStr) { if (!dateStr) return ''; const d = new Date(dateStr + 'T00:00:00'); return d.toLocaleDateString('ru-RU', { day: '2-digit', month: '2-digit', year: 'numeric' }); } function openModal(item = null) { $('itemModal').classList.add('on'); $('modalTitle').textContent = item ? 'Редактировать запись' : 'Добавить запись'; if (item) { $('itemId').value = item.id; $('name').value = item.name; $('type').value = item.type; $('serialNumber').value = item.serialNumber || ''; $('inventoryNumber').value = item.inventoryNumber; $('location').value = item.location; $('responsible').value = item.responsible; $('status').value = item.status; $('checkIntervalMonths').value = item.checkIntervalMonths || 12; $('lastCheckDate').value = item.lastCheckDate || ''; $('nextCheckDate').value = item.nextCheckDate; $('note').value = item.note || ''; } else { $('itemForm').reset(); $('itemId').value = ''; $('checkIntervalMonths').value = 12; const next = new Date(Date.now() + 365 * 24 * 60 * 60 * 1000).toISOString().slice(0, 10); $('nextCheckDate').value = next; } } function closeModal() { $('itemModal').classList.remove('on'); } function openLogModal() { $('logModal').classList.add('on'); loadLog(); } function closeLogModal() { $('logModal').classList.remove('on'); } function openUsersModal() { $('usersModal').classList.add('on'); loadUsers(); } function closeUsersModal() { $('usersModal').classList.remove('on'); } async function loadLog() { const log = await apiRequest('/api/log'); if (!log) return; $('logBody').innerHTML = log.map(entry => ` ${new Date(entry.timestamp).toLocaleString('ru-RU')} ${entry.action} ${escapeHtml(entry.itemName)} ${escapeHtml(entry.user)} ${Object.entries(entry.changes||{}).map(([k,v])=>`${k}: ${v}`).join('; ')||'—'} `).join(''); } async function loadUsers() { const users = await apiRequest('/api/users'); if (!users) return; $('usersBody').innerHTML = users.map(u => ` ${escapeHtml(u.login)} ${u.role==='admin'?'Админ':'Пользователь'} ${new Date(u.createdAt).toLocaleDateString('ru-RU')} ${u.login!==currentUser.login?``:''} `).join(''); } async function saveItem(e) { e.preventDefault(); const id = $('itemId').value; const item = { name: $('name').value, type: $('type').value, serialNumber: $('serialNumber').value, inventoryNumber: $('inventoryNumber').value, location: $('location').value, responsible: $('responsible').value, status: $('status').value, checkIntervalMonths: parseInt($('checkIntervalMonths').value) || 12, lastCheckDate: $('lastCheckDate').value, nextCheckDate: $('nextCheckDate').value, note: $('note').value }; const res = await fetch('/api/items', { method: id ? 'PUT' : 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id: id ? parseInt(id) : null, ...item }) }); const data = await res.json(); if (data.ok) { closeModal(); await loadItems(); await loadStats(); } else alert('Ошибка: ' + (data.error || 'сохранения')); } async function deleteItem(id) { if (!confirm('Удалить запись?')) return; const res = await fetch('/api/items/' + id, { method: 'DELETE' }); const data = await res.json(); if (data.ok) { await loadItems(); await loadStats(); } else alert('Ошибка: ' + (data.error || 'удаления')); } async function createUser(e) { e.preventDefault(); const login = $('newLogin').value, password = $('newPassword').value, role = $('newRole').value; const res = await fetch('/api/users', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ login, password, role }) }); const data = await res.json(); if (data.ok) { $('userForm').reset(); await loadUsers(); } else alert('Ошибка: ' + data.error); } async function deleteUser(login) { if (!confirm(`Удалить ${login}?`)) return; const res = await fetch('/api/users/' + login, { method: 'DELETE' }); const data = await res.json(); if (data.ok) await loadUsers(); else alert('Ошибка: ' + data.error); } function logout() { localStorage.removeItem('token'); localStorage.removeItem('user'); window.location.href = 'login.html'; } function exportCsv() { const head = ['Наименование','Тип','Серийный номер','Инвентарный номер','Местоположение','Ответственный','Статус','Дата последней проверки','Дата следующей проверки','Интервал (мес)','Комментарий']; const body = filteredItems.map(i => [i.name,i.type,i.serialNumber||'',i.inventoryNumber,i.location,i.responsible,i.status,i.lastCheckDate||'',i.nextCheckDate,i.checkIntervalMonths||'',i.note||''] .map(v=>'"'+String(v??'').replace(/"/g,'""')+'"').join(';')); const csv = '\uFEFF'+[head.join(';'),...body].join('\n'); const a = document.createElement('a'); a.href = URL.createObjectURL(new Blob([csv],{type:'text/csv'})); a.download = 'vygruzka.csv'; a.click(); } $('addBtn').addEventListener('click', ()=>openModal()); $('exportBtn').addEventListener('click', exportCsv); $('logBtn').addEventListener('click', openLogModal); $('usersBtn').addEventListener('click', openUsersModal); $('logoutBtn').addEventListener('click', logout); $('closeModal').addEventListener('click', closeModal); $('cancelBtn').addEventListener('click', closeModal); $('closeLogModal').addEventListener('click', closeLogModal); $('closeUsersModal').addEventListener('click', closeUsersModal); $('itemForm').addEventListener('submit', saveItem); $('userForm').addEventListener('submit', createUser); $('searchInput').addEventListener('input', e=>{currentFilter.search=e.target.value;applyFilters();}); document.querySelectorAll('.chip').forEach(chip=>{ chip.addEventListener('click', ()=>{ document.querySelectorAll('.chip').forEach(c=>c.classList.remove('active')); chip.classList.add('active'); currentFilter.status=chip.dataset.status; currentFilter.requireCheck=chip.dataset.status==='require'; applyFilters(); }); }); document.querySelectorAll('th[data-sort]').forEach(th=>{ th.addEventListener('click', ()=>{ const f=th.dataset.sort; sortAsc=sortField===f?!sortAsc:true; sortField=f; sortItems(); renderTable(); }); }); $('lastCheckDate').addEventListener('change', ()=>{ const d=$('lastCheckDate').value,m=parseInt($('checkIntervalMonths').value)||12; if(d){const dt=new Date(d+'T00:00:00');dt.setMonth(dt.getMonth()+m);$('nextCheckDate').value=dt.toISOString().slice(0,10);} }); $('checkIntervalMonths').addEventListener('change', ()=>{ const d=$('lastCheckDate').value,m=parseInt($('checkIntervalMonths').value)||12; if(d){const dt=new Date(d+'T00:00:00');dt.setMonth(dt.getMonth()+m);$('nextCheckDate').value=dt.toISOString().slice(0,10);} }); window.editItem=id=>{const item=items.find(i=>i.id===id);if(item)openModal(item);}; window.deleteItem=deleteItem; window.deleteUser=deleteUser; checkAuth(); loadItems(); loadStats();