otslezhivat-povtornye-obrasc/script.js

254 lines
8.3 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 => `
<tr>
<td>${formatDate(appeal.createdAt)}</td>
<td>${escapeHtml(appeal.author)}</td>
<td class="appeal-text">${escapeHtml(appeal.text)}</td>
<td><span class="status-pill status-${appeal.status}">${getStatusText(appeal.status)}</span></td>
<td>
<button class="icon-btn" onclick="editAppeal(${appeal.id})" title="Редактировать">
<svg class="kt-icon" aria-hidden="true"><use href="#pencil"></use></svg>
</button>
</td>
</tr>
`).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;