let appeals = []; let currentFilter = 'all'; let currentSort = 'date'; let editingId = null; const searchInput = document.getElementById('searchInput'); const appealsTable = document.getElementById('appealsTable'); const emptyState = document.getElementById('emptyState'); const drawer = document.getElementById('appealDrawer'); const drawerOverlay = document.getElementById('drawerOverlay'); const drawerTitle = document.getElementById('drawerTitle'); const appealForm = document.getElementById('appealForm'); const themeToggle = document.getElementById('themeToggle'); const toastContainer = document.getElementById('toastContainer'); const kpiTotal = document.getElementById('kpiTotal'); const kpiNew = document.getElementById('kpiNew'); const kpiDuplicate = document.getElementById('kpiDuplicate'); const kpiResolved = document.getElementById('kpiResolved'); document.addEventListener('DOMContentLoaded', () => { loadAppeals(); setupEventListeners(); }); function setupEventListeners() { document.getElementById('newAppealBtn').addEventListener('click', openNewDrawer); document.getElementById('emptyNewBtn').addEventListener('click', openNewDrawer); document.getElementById('closeDrawer').addEventListener('click', closeDrawer); document.getElementById('cancelBtn').addEventListener('click', closeDrawer); document.getElementById('saveBtn').addEventListener('click', saveAppeal); document.getElementById('drawerOverlay').addEventListener('click', closeDrawer); themeToggle.addEventListener('click', toggleTheme); searchInput.addEventListener('input', renderAppeals); document.getElementById('statusFilter').addEventListener('change', renderAppeals); document.querySelectorAll('[data-filter]').forEach(link => { link.addEventListener('click', (e) => { e.preventDefault(); document.querySelectorAll('[data-filter]').forEach(l => l.classList.remove('is-active')); link.classList.add('is-active'); currentFilter = link.dataset.filter; renderAppeals(); }); }); document.querySelectorAll('[data-sort]').forEach(btn => { btn.addEventListener('click', () => { document.querySelectorAll('[data-sort]').forEach(b => b.classList.remove('is-active')); btn.classList.add('is-active'); currentSort = btn.dataset.sort; renderAppeals(); }); }); } async function loadAppeals() { try { const res = await fetch('api/appeals'); appeals = await res.json(); renderAppeals(); } catch (err) { console.error('Ошибка загрузки:', err); showToast('Не удалось загрузить обращения'); } } function renderAppeals() { const searchTerm = searchInput.value.toLowerCase(); const statusFilter = document.getElementById('statusFilter').value; let filtered = appeals.filter(appeal => { const matchesSearch = appeal.author.toLowerCase().includes(searchTerm) || appeal.text.toLowerCase().includes(searchTerm) || appeal.contact.toLowerCase().includes(searchTerm) || appeal.createdAt.includes(searchTerm); const matchesFilter = currentFilter === 'all' || (currentFilter === 'new' && appeal.status === 'new') || (currentFilter === 'duplicate' && appeal.status === 'duplicate') || (currentFilter === 'resolved' && appeal.status === 'resolved'); const matchesStatus = !statusFilter || appeal.status === statusFilter; return matchesSearch && matchesFilter && matchesStatus; }); if (currentSort === 'date') { filtered.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt)); } else if (currentSort === 'author') { filtered.sort((a, b) => a.author.localeCompare(b.author)); } if (filtered.length === 0) { appealsTable.innerHTML = ''; emptyState.hidden = false; } else { emptyState.hidden = true; appealsTable.innerHTML = filtered.map(appeal => ` ${formatDate(appeal.createdAt)} ${escapeHtml(appeal.author)} ${escapeHtml(appeal.text)} ${getStatusText(appeal.status)} `).join(''); } updateKPI(); } function updateKPI() { kpiTotal.textContent = appeals.length; kpiNew.textContent = appeals.filter(a => a.status === 'new').length; kpiDuplicate.textContent = appeals.filter(a => a.status === 'duplicate').length; kpiResolved.textContent = appeals.filter(a => a.status === 'resolved').length; } function openNewDrawer() { editingId = null; drawerTitle.textContent = 'Новое обращение'; appealForm.reset(); document.getElementById('appealId').value = ''; openDrawer(); } function openDrawer() { drawer.classList.add('open'); drawerOverlay.classList.add('open'); document.body.style.overflow = 'hidden'; } function closeDrawer() { drawer.classList.remove('open'); drawerOverlay.classList.remove('open'); document.body.style.overflow = ''; } async function saveAppeal() { const author = document.getElementById('authorInput').value.trim(); const contact = document.getElementById('contactInput').value.trim(); const text = document.getElementById('textInput').value.trim(); const category = document.getElementById('categoryInput').value; const comment = document.getElementById('commentInput').value.trim(); if (!author || !text) { showToast('Заполните обязательные поля'); return; } try { if (editingId) { const res = await fetch(`api/appeals/${editingId}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ author, contact, text, category, comment }) }); if (res.ok) { showToast('Обращение обновлено'); await loadAppeals(); } } else { const res = await fetch('api/appeals', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ author, contact, text, category, comment, status: 'new' }) }); if (res.ok) { showToast('Обращение добавлено'); await loadAppeals(); } } closeDrawer(); } catch (err) { console.error('Ошибка сохранения:', err); showToast('Не удалось сохранить обращение'); } } function editAppeal(id) { const appeal = appeals.find(a => a.id === id); if (!appeal) return; editingId = id; drawerTitle.textContent = 'Редактирование обращения'; document.getElementById('appealId').value = appeal.id; document.getElementById('authorInput').value = appeal.author; document.getElementById('contactInput').value = appeal.contact; document.getElementById('textInput').value = appeal.text; document.getElementById('categoryInput').value = appeal.category || ''; document.getElementById('commentInput').value = appeal.comment || ''; openDrawer(); } function toggleTheme() { const html = document.documentElement; const current = html.getAttribute('data-theme'); const next = current === 'dark' ? 'light' : current === 'light' ? 'auto' : 'dark'; html.setAttribute('data-theme', next); const icon = themeToggle.querySelector('use'); icon.setAttribute('href', next === 'dark' ? '#moon' : '#sun'); } function showToast(message) { const toast = document.createElement('div'); toast.className = 'toast'; toast.textContent = message; toastContainer.appendChild(toast); setTimeout(() => { toast.remove(); }, 3000); } function formatDate(isoString) { const date = new Date(isoString); return date.toLocaleDateString('ru-RU', { day: '2-digit', month: '2-digit', year: '2-digit', hour: '2-digit', minute: '2-digit' }); } function getStatusText(status) { const texts = { new: 'Новое', duplicate: 'Повторное', resolved: 'Решено' }; return texts[status] || status; } function escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } window.editAppeal = editAppeal;