// Equipment data let equipment = []; // Load from API async function loadEquipment() { try { const response = await fetch('api/items'); if (response.ok) { equipment = await response.json(); if (equipment.length === 0) { // Initialize with demo data if empty equipment = getDemoData(); saveEquipment(); } } else { equipment = getDemoData(); } } catch (e) { equipment = getDemoData(); } updateAll(); } function getDemoData() { return [ { id: 1, name: 'Огнетушитель ОП-5', category: 'Огнетушитель', inventoryNumber: 'ИН-001', location: 'Кабинет 101', responsible: 'Иванов И.И.', lastCheck: '2025-09-15', period: 12, nextCheck: '2026-09-15', note: 'Порошковый огнетушитель', status: 'warning' }, { id: 2, name: 'Аптечка медицинская', category: 'Аптечка', inventoryNumber: 'ИН-002', location: 'Кабинет 205', responsible: 'Петрова А.С.', lastCheck: '2025-03-10', period: 12, nextCheck: '2026-03-10', note: '', status: 'expired' }, { id: 3, name: 'Дрель аккумуляторная', category: 'Электроинструмент', inventoryNumber: 'ИН-003', location: 'Склад №3', responsible: 'Сидоров П.К.', lastCheck: '2026-08-01', period: 6, nextCheck: '2027-02-01', note: 'Bosch GSR 12V', status: 'ok' }, { id: 4, name: 'Каска защитная', category: 'СИЗ', inventoryNumber: 'ИН-004', location: 'Участок №2', responsible: 'Козлов М.М.', lastCheck: '2026-06-15', period: 24, nextCheck: '2028-06-15', note: '', status: 'ok' }, { id: 5, name: 'Мультиметр цифровой', category: 'Измерительный прибор', inventoryNumber: 'ИН-005', location: 'Лаборатория', responsible: 'Николаев В.В.', lastCheck: '2026-07-20', period: 12, nextCheck: '2027-07-20', note: 'Fluke 117', status: 'ok' }, { id: 6, name: 'Лестница алюминиевая', category: 'Лестница', inventoryNumber: 'ИН-006', location: 'Хозблок', responsible: 'Морозов А.А.', lastCheck: '2025-12-01', period: 12, nextCheck: '2026-12-01', note: '3 метра', status: 'ok' }, { id: 7, name: 'Огнетушитель ОУ-3', category: 'Огнетушитель', inventoryNumber: 'ИН-007', location: 'Серверная', responsible: 'Иванов И.И.', lastCheck: '2026-08-25', period: 60, nextCheck: '2031-08-25', note: 'Углекислотный', status: 'ok' }, { id: 8, name: 'Перчатки диэлектрические', category: 'СИЗ', inventoryNumber: 'ИН-008', location: 'Электрощитовая', responsible: 'Соколов Д.Д.', lastCheck: '2026-03-01', period: 6, nextCheck: '2026-09-01', note: '', status: 'expired' } ]; } function updateAll() { calculateStatuses(); updateKPIs(); renderDashboard(); renderTable(); renderExpiring(); renderExpired(); updateNotifications(); updateCounts(); } function calculateStatuses() { const today = new Date(); today.setHours(0, 0, 0, 0); equipment.forEach(item => { const nextCheck = new Date(item.nextCheck); const diffTime = nextCheck - today; const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)); if (diffDays < 0) { item.status = 'expired'; } else if (diffDays <= 30) { item.status = 'warning'; } else { item.status = 'ok'; } item.daysLeft = diffDays; }); } function updateKPIs() { const today = new Date(); const thisMonth = today.getMonth(); const thisYear = today.getFullYear(); const total = equipment.length; const expiring = equipment.filter(e => e.status === 'warning').length; const expired = equipment.filter(e => e.status === 'expired').length; const active = equipment.filter(e => e.status === 'ok').length; const thisMonthCount = equipment.filter(e => { const next = new Date(e.nextCheck); return next.getMonth() === thisMonth && next.getFullYear() === thisYear; }).length; document.getElementById('kpi-total').textContent = total; document.getElementById('kpi-expiring').textContent = expiring; document.getElementById('kpi-expired').textContent = expired; document.getElementById('kpi-active').textContent = active; document.getElementById('kpi-this-month').textContent = thisMonthCount; } function renderDashboard() { const tbody = document.getElementById('dashboard-table-body'); const expiring = equipment .filter(e => e.status === 'warning' || e.status === 'expired') .sort((a, b) => a.daysLeft - b.daysLeft) .slice(0, 10); if (expiring.length === 0) { tbody.innerHTML = 'Нет предстоящих проверок'; return; } tbody.innerHTML = expiring.map(item => { const statusClass = item.status === 'expired' ? 'status-danger' : 'status-warning'; const statusText = item.status === 'expired' ? 'ПРОСРОЧЕНО' : 'Скоро проверка'; const rowClass = item.status === 'expired' ? 'row-expired' : 'row-expiring'; return ` ${escapeHtml(item.name)} ${escapeHtml(item.category)} ${escapeHtml(item.inventoryNumber)} ${formatDate(item.nextCheck)} ${item.daysLeft < 0 ? Math.abs(item.daysLeft) + ' дней назад' : item.daysLeft + ' дн.'} ${statusText} `; }).join(''); } function renderTable() { const tbody = document.getElementById('all-table-body'); const search = document.getElementById('global-search').value.toLowerCase(); const categoryFilter = document.getElementById('filter-category').value; const statusFilter = document.getElementById('filter-status').value; let filtered = equipment.filter(item => { const matchesSearch = !search || item.name.toLowerCase().includes(search) || item.inventoryNumber.toLowerCase().includes(search) || item.responsible.toLowerCase().includes(search) || item.location.toLowerCase().includes(search); const matchesCategory = !categoryFilter || item.category === categoryFilter; const matchesStatus = !statusFilter || item.status === statusFilter; return matchesSearch && matchesCategory && matchesStatus; }); if (filtered.length === 0) { tbody.innerHTML = 'Оборудование не найдено'; return; } tbody.innerHTML = filtered.map((item, index) => { const statusClass = item.status === 'expired' ? 'status-danger' : item.status === 'warning' ? 'status-warning' : ''; const statusText = item.status === 'expired' ? 'ПРОСРОЧЕНО' : item.status === 'warning' ? 'Скоро проверка' : 'Действует'; const rowClass = item.status === 'expired' ? 'row-expired' : item.status === 'warning' ? 'row-expiring' : ''; return ` ${index + 1} ${escapeHtml(item.name)} ${escapeHtml(item.category)} ${escapeHtml(item.inventoryNumber)} ${escapeHtml(item.location)} ${escapeHtml(item.responsible)} ${formatDate(item.lastCheck)} ${item.period} мес. ${formatDate(item.nextCheck)} ${statusText} `; }).join(''); } function renderExpiring() { const tbody = document.getElementById('expiring-table-body'); const expiring = equipment.filter(e => e.status === 'warning').sort((a, b) => a.daysLeft - b.daysLeft); if (expiring.length === 0) { tbody.innerHTML = 'Нет оборудования на проверке'; return; } tbody.innerHTML = expiring.map((item, index) => ` ${index + 1} ${escapeHtml(item.name)} ${escapeHtml(item.category)} ${escapeHtml(item.inventoryNumber)} ${escapeHtml(item.location)} ${escapeHtml(item.responsible)} ${formatDate(item.nextCheck)} ${item.daysLeft} дн. `).join(''); } function renderExpired() { const tbody = document.getElementById('expired-table-body'); const expired = equipment.filter(e => e.status === 'expired').sort((a, b) => a.daysLeft - b.daysLeft); if (expired.length === 0) { tbody.innerHTML = 'Нет просроченного оборудования'; return; } tbody.innerHTML = expired.map((item, index) => ` ${index + 1} ${escapeHtml(item.name)} ${escapeHtml(item.category)} ${escapeHtml(item.inventoryNumber)} ${escapeHtml(item.location)} ${escapeHtml(item.responsible)} ${formatDate(item.nextCheck)} ${Math.abs(item.daysLeft)} дн. `).join(''); } function updateNotifications() { const container = document.getElementById('notifications-container'); const expired = equipment.filter(e => e.status === 'expired'); const expiring = equipment.filter(e => e.status === 'warning' && e.daysLeft <= 7); let html = ''; if (expired.length > 0) { html += `
Проверка просрочена! ${expired.length} ед. оборудования с истёкшим сроком проверки.
`; } if (expiring.length > 0) { html += `
Внимание! До проверки осталось 7 дней или меньше! ${expiring.length} ед. оборудования.
`; } container.innerHTML = html; } function updateCounts() { const expiringCount = equipment.filter(e => e.status === 'warning').length; const expiredCount = equipment.filter(e => e.status === 'expired').length; const expiringBadge = document.getElementById('expiring-count'); const expiredBadge = document.getElementById('expired-count'); if (expiringCount > 0) { expiringBadge.textContent = expiringCount; expiringBadge.style.display = 'inline-block'; } else { expiringBadge.style.display = 'none'; } if (expiredCount > 0) { expiredBadge.textContent = expiredCount; expiredBadge.classList.add('status-danger'); expiredBadge.style.display = 'inline-block'; } else { expiredBadge.style.display = 'none'; } } async function addEquipment(event) { event.preventDefault(); const form = event.target; const formData = new FormData(form); const lastCheck = formData.get('lastCheck'); const period = parseInt(formData.get('period')); const nextCheck = calculateNextDateValue(lastCheck, period); if (editingId) { // Edit existing item const index = equipment.findIndex(e => e.id === editingId); if (index !== -1) { equipment[index] = { ...equipment[index], name: formData.get('name'), category: formData.get('category'), inventoryNumber: formData.get('inventoryNumber'), location: formData.get('location'), responsible: formData.get('responsible'), lastCheck: lastCheck, period: period, nextCheck: nextCheck, note: formData.get('note') || '' }; await saveEquipment(); updateAll(); resetForm(); switchTab('all'); } editingId = null; } else { // Add new item const newItem = { id: Date.now(), name: formData.get('name'), category: formData.get('category'), inventoryNumber: formData.get('inventoryNumber'), location: formData.get('location'), responsible: formData.get('responsible'), lastCheck: lastCheck, period: period, nextCheck: nextCheck, note: formData.get('note') || '', status: 'ok' }; equipment.push(newItem); await saveEquipment(); resetForm(); updateAll(); switchTab('all'); } } function calculateNextDateValue(lastCheck, periodMonths) { const date = new Date(lastCheck); date.setMonth(date.getMonth() + periodMonths); return date.toISOString().split('T')[0]; } function calculateNextDate() { const form = document.getElementById('add-form'); const lastCheck = form.lastCheck.value; const period = parseInt(form.period.value); if (lastCheck && period) { const nextCheck = calculateNextDateValue(lastCheck, period); document.getElementById('next-check').value = nextCheck; } } function resetForm() { document.getElementById('add-form').reset(); document.getElementById('next-check').value = ''; editingId = null; // Reset button text const submitBtn = document.getElementById('add-form').querySelector('button[type="submit"]'); submitBtn.innerHTML = 'Сохранить'; } function openDrawer(id) { const item = equipment.find(e => e.id === id); if (!item) return; const drawer = document.getElementById('equipment-drawer'); const title = document.getElementById('drawer-title'); const content = document.getElementById('drawer-content'); const actions = document.getElementById('drawer-actions'); title.textContent = item.name; const statusClass = item.status === 'expired' ? 'status-danger' : item.status === 'warning' ? 'status-warning' : ''; const statusText = item.status === 'expired' ? 'ПРОСРОЧЕНО' : item.status === 'warning' ? 'Скоро проверка' : 'Действует'; content.innerHTML = `
${statusText} ${item.daysLeft !== undefined ? `До проверки: ${item.daysLeft < 0 ? Math.abs(item.daysLeft) + ' дней назад' : item.daysLeft + ' дн.'}` : ''}
Категория
${escapeHtml(item.category)}
Инвентарный номер
${escapeHtml(item.inventoryNumber)}
Место нахождения
${escapeHtml(item.location)}
Ответственный
${escapeHtml(item.responsible)}
Дата последней проверки
${formatDate(item.lastCheck)}
Периодичность
${item.period} мес.
Следующая проверка
${formatDate(item.nextCheck)}
${item.note ? `
Примечание
${escapeHtml(item.note)}
` : ''}

История проверок

История проверок будет доступна после обновления системы
`; actions.innerHTML = `
`; drawer.hidden = false; } function closeDrawer() { document.getElementById('equipment-drawer').hidden = true; } let editingId = null; function editEquipment(id) { const item = equipment.find(e => e.id === id); if (!item) return; editingId = id; closeDrawer(); switchTab('add'); const form = document.getElementById('add-form'); form.name.value = item.name; form.category.value = item.category; form.inventoryNumber.value = item.inventoryNumber; form.location.value = item.location; form.responsible.value = item.responsible; form.lastCheck.value = item.lastCheck; form.period.value = item.period; document.getElementById('next-check').value = item.nextCheck; form.note.value = item.note || ''; // Change button text const submitBtn = form.querySelector('button[type="submit"]'); submitBtn.innerHTML = ' Сохранить изменения'; } function exportToExcel() { const headers = ['№', 'Наименование', 'Категория', 'Инвентарный номер', 'Место нахождения', 'Ответственный', 'Дата последней проверки', 'Периодичность (мес.)', 'Следующая проверка', 'Статус']; const rows = equipment.map((item, index) => [ index + 1, item.name, item.category, item.inventoryNumber, item.location, item.responsible, item.lastCheck, item.period, item.nextCheck, item.status === 'expired' ? 'Просрочено' : item.status === 'warning' ? 'Скоро проверка' : 'Действует' ]); const csv = '\uFEFF' + [headers, ...rows].map(row => row.map(cell => '"' + String(cell).replace(/"/g, '""') + '"').join(';') ).join('\n'); const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' }); const link = document.createElement('a'); link.href = URL.createObjectURL(blob); link.download = 'oborudovanie_' + new Date().toISOString().split('T')[0] + '.csv'; link.click(); } function generateReport(type) { let filtered = []; let filename = ''; switch(type) { case 'all': filtered = equipment; filename = 'otchet_vse_oborudovanie'; break; case 'expiring': filtered = equipment.filter(e => e.status === 'warning'); filename = 'otchet_na_proverku'; break; case 'expired': filtered = equipment.filter(e => e.status === 'expired'); filename = 'otchet_prosrocheno'; break; case 'active': filtered = equipment.filter(e => e.status === 'ok'); filename = 'otchet_proverno'; break; } const headers = ['№', 'Наименование', 'Категория', 'Инвентарный номер', 'Место нахождения', 'Ответственный', 'Дата последней проверки', 'Периодичность (мес.)', 'Следующая проверка', 'Статус', 'Примечание']; const rows = filtered.map((item, index) => [ index + 1, item.name, item.category, item.inventoryNumber, item.location, item.responsible, item.lastCheck, item.period, item.nextCheck, item.status === 'expired' ? 'Просрочено' : item.status === 'warning' ? 'Скоро проверка' : 'Действует', item.note || '' ]); const csv = '\uFEFF' + [headers, ...rows].map(row => row.map(cell => '"' + String(cell).replace(/"/g, '""') + '"').join(';') ).join('\n'); const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' }); const link = document.createElement('a'); link.href = URL.createObjectURL(blob); link.download = filename + '_' + new Date().toISOString().split('T')[0] + '.csv'; link.click(); } function switchTab(tabId) { document.querySelectorAll('.tab-content').forEach(tab => tab.classList.remove('active')); document.querySelectorAll('.kt-ai-nav-item').forEach(nav => nav.classList.remove('is-active')); document.getElementById('tab-' + tabId).classList.add('active'); document.querySelector('[data-tab="' + tabId + '"]').classList.add('is-active'); const titles = { dashboard: 'Dashboard', all: 'Всё оборудование', expiring: 'Проверка в ближайшие 30 дней', expired: 'Просрочено', add: 'Добавить оборудование', reports: 'Отчёты' }; document.getElementById('page-title').textContent = titles[tabId] || ''; if (tabId === 'all') renderTable(); if (tabId === 'expiring') renderExpiring(); if (tabId === 'expired') renderExpired(); } function toggleTheme() { const html = document.documentElement; const current = html.getAttribute('data-theme'); const next = current === 'dark' ? 'light' : 'dark'; html.setAttribute('data-theme', next); // Update logo based on theme const logos = document.querySelectorAll('img[src*="kazakhtelecom-logo"]'); logos.forEach(img => { if (next === 'dark') { img.src = 'design-system/brand/kazakhtelecom-logo-white.png'; } else { img.src = 'design-system/brand/kazakhtelecom-logo.png'; } }); } function formatDate(dateStr) { if (!dateStr) return ''; const date = new Date(dateStr); return date.toLocaleDateString('ru-RU', { day: '2-digit', month: '2-digit', year: 'numeric' }); } function escapeHtml(text) { if (!text) return ''; return String(text) .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"'); } async async function saveEquipment() { try { await fetch('api/items', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(equipment) }); } catch (e) { console.error('Failed to save equipment:', e); } } function deleteEquipment(id) { if (!confirm('Вы уверены, что хотите удалить это оборудование?')) return; equipment = equipment.filter(e => e.id !== id); saveEquipment(); closeDrawer(); updateAll(); } // Navigation document.querySelectorAll('[data-tab]').forEach(link => { link.addEventListener('click', (e) => { e.preventDefault(); const tabId = link.getAttribute('data-tab'); switchTab(tabId); }); }); // Global search document.getElementById('global-search').addEventListener('input', () => { const activeTab = document.querySelector('.tab-content.active').id; if (activeTab === 'tab-all') renderTable(); }); // Initialize loadEquipment();