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;