// Демо-данные: 8 обращений по требованиям брифа const DEMO_DATA = [ { id: 1, date: '2026-08-20', name: 'Айгүл Сәтбаева', contact: '+7 (777) 123-45-67', problem: 'Проблема с интернетом', responsible: 'Ерлан Нурбаев', deadline: '2026-08-28', status: 'В работе' }, { id: 2, date: '2026-08-22', name: 'Данияр Төлегенов', contact: '+7 (701) 234-56-78', problem: 'Нет доступа к личному кабинету', responsible: 'Гүлнұр Иманова', deadline: '2026-08-27', status: 'Новые' }, { id: 3, date: '2026-08-18', name: 'Нұрсұлтан Қасымов', contact: '+7 (778) 345-67-89', problem: 'Не проходит оплата', responsible: 'Марат Серік', deadline: '2026-08-25', status: 'Выполнены' }, { id: 4, date: '2026-08-19', name: 'Асель Жумабаева', contact: '+7 (702) 456-78-90', problem: 'Низкая скорость соединения', responsible: 'Айдана Оразалы', deadline: '2026-08-26', status: 'В работе' }, { id: 5, date: '2026-08-21', name: 'Бауыржан Омаров', contact: '+7 (775) 567-89-01', problem: 'Частые обрывы связи', responsible: 'Ерлан Нурбаев', deadline: '2026-08-29', status: 'Новые' }, { id: 6, date: '2026-08-17', name: 'Гүлмира Ибраева', contact: '+7 (707) 678-90-12', problem: 'Некорректные показания счётчика', responsible: 'Гүлнұр Иманова', deadline: '2026-08-24', status: 'В работе' }, { id: 7, date: '2026-08-15', name: 'Ермек Байтасов', contact: '+7 (776) 789-01-23', problem: 'Подключение нового абонента', responsible: 'Марат Серік', deadline: '2026-08-22', status: 'Выполнены' }, { id: 8, date: '2026-08-16', name: 'Зауре Алимова', contact: '+7 (708) 890-12-34', problem: 'Ошибка при авторизации в приложении', responsible: 'Айдана Оразалы', deadline: '2026-08-23', status: 'В работе' } ]; // Хранилище let appeals = []; let changeLog = []; let currentFilter = 'all'; let searchQuery = ''; // Инициализация function init() { loadData(); setupEventListeners(); render(); } // Загрузка данных из localStorage function loadData() { const stored = localStorage.getItem('appealsData'); if (stored) { appeals = JSON.parse(stored); } else { appeals = [...DEMO_DATA]; saveData(); } const logStored = localStorage.getItem('appealsLog'); if (logStored) { changeLog = JSON.parse(logStored); } else { changeLog = []; saveLog(); } } // Сохранение данных function saveData() { localStorage.setItem('appealsData', JSON.stringify(appeals)); } function saveLog() { localStorage.setItem('appealsLog', JSON.stringify(changeLog)); } // Добавление записи в журнал function addToLog(action, who) { const now = new Date(); const timestamp = now.toLocaleString('ru-RU', { day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit' }); changeLog.unshift({ when: timestamp, what: action, who: who }); // Храним только последние 50 записей if (changeLog.length > 50) { changeLog = changeLog.slice(0, 50); } saveLog(); } // Проверка на просрочку function isOverdue(item) { if (item.status === 'Выполнены') return false; const today = new Date(); today.setHours(0, 0, 0, 0); const deadline = new Date(item.deadline); return deadline < today; } // Форматирование даты function formatDate(dateStr) { const [year, month, day] = dateStr.split('-'); return `${day}.${month}.${year}`; } // Фильтрация данных function getFilteredData() { let filtered = [...appeals]; // Фильтр по статусу if (currentFilter !== 'all') { filtered = filtered.filter(item => item.status === currentFilter); } // Поиск if (searchQuery.trim()) { const query = searchQuery.toLowerCase(); filtered = filtered.filter(item => item.name.toLowerCase().includes(query) || item.contact.toLowerCase().includes(query) || item.problem.toLowerCase().includes(query) || item.responsible.toLowerCase().includes(query) || item.status.toLowerCase().includes(query) ); } return filtered; } // Подсчёт статистики function updateStats() { const total = appeals.length; const newCount = appeals.filter(i => i.status === 'Новые').length; const inProgress = appeals.filter(i => i.status === 'В работе').length; const completed = appeals.filter(i => i.status === 'Выполнены').length; const overdue = appeals.filter(i => isOverdue(i)).length; document.getElementById('stat-total').textContent = total; document.getElementById('stat-new').textContent = newCount; document.getElementById('stat-in-progress').textContent = inProgress; document.getElementById('stat-completed').textContent = completed; document.getElementById('stat-overdue').textContent = overdue; } // Рендер таблицы function renderTable() { const tbody = document.getElementById('table-body'); const filtered = getFilteredData(); if (filtered.length === 0) { tbody.innerHTML = 'Нет данных'; return; } tbody.innerHTML = filtered.map(item => { const overdue = isOverdue(item); let statusClass = 'status-new'; if (item.status === 'В работе') statusClass = 'status-in-progress'; if (item.status === 'Выполнены') statusClass = 'status-completed'; return ` ${formatDate(item.date)} ${escapeHtml(item.name)} ${escapeHtml(item.contact)} ${escapeHtml(item.problem)} ${escapeHtml(item.responsible)} ${formatDate(item.deadline)} ${item.status} ${overdue ? 'Просрочено' : '—'}
`; }).join(''); } // Рендер журнала function renderLog() { const tbody = document.getElementById('log-body'); if (changeLog.length === 0) { tbody.innerHTML = 'Журнал пуст'; return; } tbody.innerHTML = changeLog.slice(0, 20).map(log => ` ${escapeHtml(log.when)} ${escapeHtml(log.what)} ${escapeHtml(log.who)} `).join(''); } // Полный рендер function render() { updateStats(); renderTable(); renderLog(); } // Настройка обработчиков function setupEventListeners() { // Чипсы статусов document.querySelectorAll('.chip').forEach(chip => { chip.addEventListener('click', () => { document.querySelectorAll('.chip').forEach(c => c.classList.remove('active')); chip.classList.add('active'); currentFilter = chip.dataset.status; renderTable(); }); }); // Поиск document.getElementById('search-input').addEventListener('input', (e) => { searchQuery = e.target.value; renderTable(); }); // Добавление document.getElementById('btn-add').addEventListener('click', () => { openAddModal(); }); // Выгрузка document.getElementById('btn-export').addEventListener('click', exportToCsv); // Модальное окно document.getElementById('modal-close').addEventListener('click', closeModal); document.getElementById('btn-cancel').addEventListener('click', closeModal); document.getElementById('modal-overlay').addEventListener('click', (e) => { if (e.target === document.getElementById('modal-overlay')) { closeModal(); } }); // Форма document.getElementById('appeal-form').addEventListener('submit', handleFormSubmit); } // Открытие модального окна для добавления function openAddModal() { document.getElementById('modal-title').textContent = 'Добавить обращение'; document.getElementById('form-id').value = ''; document.getElementById('form-date').value = new Date().toISOString().split('T')[0]; document.getElementById('form-deadline').value = ''; document.getElementById('form-name').value = ''; document.getElementById('form-contact').value = ''; document.getElementById('form-problem').value = ''; document.getElementById('form-responsible').value = ''; document.getElementById('form-status').value = 'Новые'; document.getElementById('modal-overlay').classList.add('active'); } // Открытие модального окна для редактирования function openEditModal(id) { const item = appeals.find(a => a.id === id); if (!item) return; document.getElementById('modal-title').textContent = 'Редактировать обращение'; document.getElementById('form-id').value = item.id; document.getElementById('form-date').value = item.date; document.getElementById('form-deadline').value = item.deadline; document.getElementById('form-name').value = item.name; document.getElementById('form-contact').value = item.contact; document.getElementById('form-problem').value = item.problem; document.getElementById('form-responsible').value = item.responsible; document.getElementById('form-status').value = item.status; document.getElementById('modal-overlay').classList.add('active'); } // Закрытие модального окна function closeModal() { document.getElementById('modal-overlay').classList.remove('active'); } // Обработка формы function handleFormSubmit(e) { e.preventDefault(); const id = document.getElementById('form-id').value; const formData = { date: document.getElementById('form-date').value, name: document.getElementById('form-name').value, contact: document.getElementById('form-contact').value, problem: document.getElementById('form-problem').value, responsible: document.getElementById('form-responsible').value, deadline: document.getElementById('form-deadline').value, status: document.getElementById('form-status').value }; if (id) { // Редактирование const index = appeals.findIndex(a => a.id === parseInt(id)); if (index !== -1) { const old = appeals[index]; appeals[index] = { ...formData, id: parseInt(id) }; addToLog(`Изменено обращение #${id}: "${old.name}"`, 'Пользователь'); } } else { // Добавление const newId = Date.now(); appeals.unshift({ ...formData, id: newId }); addToLog(`Добавлено обращение #${newId}: "${formData.name}"`, 'Пользователь'); } saveData(); closeModal(); render(); } // Удаление function deleteAppeal(id) { const item = appeals.find(a => a.id === id); if (!item) return; if (confirm(`Вы уверены, что хотите удалить обращение "${item.name}"?`)) { appeals = appeals.filter(a => a.id !== id); addToLog(`Удалено обращение #${id}: "${item.name}"`, 'Пользователь'); saveData(); render(); } } // Выгрузка в CSV function exportToCsv() { const filtered = getFilteredData(); if (filtered.length === 0) { alert('Нет данных для выгрузки'); return; } const headers = ['Дата обращения', 'Кто обратился', 'Контакт', 'Суть проблемы', 'Ответственный', 'Срок', 'Статус', 'Просрочка']; const rows = filtered.map(item => [ formatDate(item.date), item.name, item.contact, item.problem, item.responsible, formatDate(item.deadline), item.status, isOverdue(item) ? 'Да' : 'Нет' ]); const csvContent = '\uFEFF' + [ headers.join(';'), ...rows.map(row => row.map(cell => '"' + String(cell).replace(/"/g, '""') + '"').join(';')) ].join('\n'); const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' }); const link = document.createElement('a'); link.href = URL.createObjectURL(blob); link.download = `reestr_${new Date().toISOString().split('T')[0]}.csv`; link.click(); addToLog('Выгружен реестр в Excel (CSV)', 'Пользователь'); } // Экранирование HTML function escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } // Запуск init();