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