// public/admin.js – панель администратора let currentUser = null; let currentPreviewLessons = []; let allLessonsForFilters = []; // храним все уроки для построения зависимых фильтров document.addEventListener('DOMContentLoaded', async () => { await checkAuth(); await loadFilterOptions(); loadLessons(); setupEventListeners(); }); async function checkAuth() { try { const res = await fetch('/api/me'); const data = await res.json(); if (!data.authenticated || data.user.role !== 'admin') { window.location.href = '/login.html'; return; } currentUser = data.user; document.getElementById('userInfo').innerHTML = `👋 ${currentUser.full_name} (${currentUser.role})`; } catch (err) { window.location.href = '/login.html'; } } // Загрузка опций для выпадающих списков фильтров (начальные) async function loadFilterOptions() { try { const [classes, teachers, topics] = await Promise.all([ fetch('/api/filter-options/class-names').then(r => r.json()), fetch('/api/filter-options/teachers').then(r => r.json()), fetch('/api/filter-options/topics').then(r => r.json()) ]); window.allClassNames = classes; window.allTeachers = teachers; window.allTopics = topics; populateSelect('filterClass', window.allClassNames, 'Все классы'); populateSelect('filterTeacher', window.allTeachers, 'Все учителя'); populateSelect('filterTopic', window.allTopics, 'Все темы'); } catch (err) { console.error('Ошибка загрузки опций фильтров', err); } } function populateSelect(selectId, options, defaultLabel) { const select = document.getElementById(selectId); if (!select) return; const currentValue = select.value; select.innerHTML = ``; options.forEach(opt => { const option = document.createElement('option'); option.value = opt; option.textContent = opt; select.appendChild(option); }); if (currentValue && options.includes(currentValue)) { select.value = currentValue; } else { select.value = ''; } } // Обновление зависимых фильтров на основе текущих значений и allLessonsForFilters function updateDependentFilters() { const selectedClass = document.getElementById('filterClass').value; const selectedTeacher = document.getElementById('filterTeacher').value; const selectedTopic = document.getElementById('filterTopic').value; let filteredLessons = allLessonsForFilters; if (selectedClass) { filteredLessons = filteredLessons.filter(l => l.class_name === selectedClass); } if (selectedTeacher) { filteredLessons = filteredLessons.filter(l => l.teacher === selectedTeacher); } if (selectedTopic) { filteredLessons = filteredLessons.filter(l => l.topic === selectedTopic); } const availableClasses = [...new Set(filteredLessons.map(l => l.class_name))].sort(); const availableTeachers = [...new Set(filteredLessons.map(l => l.teacher))].sort(); const availableTopics = [...new Set(filteredLessons.map(l => l.topic).filter(t => t))].sort(); const oldClass = document.getElementById('filterClass').value; const oldTeacher = document.getElementById('filterTeacher').value; const oldTopic = document.getElementById('filterTopic').value; populateSelect('filterClass', availableClasses, 'Все классы'); populateSelect('filterTeacher', availableTeachers, 'Все учителя'); populateSelect('filterTopic', availableTopics, 'Все темы'); if (oldClass && availableClasses.includes(oldClass)) document.getElementById('filterClass').value = oldClass; else document.getElementById('filterClass').value = ''; if (oldTeacher && availableTeachers.includes(oldTeacher)) document.getElementById('filterTeacher').value = oldTeacher; else document.getElementById('filterTeacher').value = ''; if (oldTopic && availableTopics.includes(oldTopic)) document.getElementById('filterTopic').value = oldTopic; else document.getElementById('filterTopic').value = ''; } async function loadLessons(filters = {}) { const params = new URLSearchParams(filters); const res = await fetch(`/api/admin/lessons?${params}`); const lessons = await res.json(); // Сохраняем полный список для фильтрации (без учёта фильтров) allLessonsForFilters = lessons; updateDependentFilters(); // обновить списки в select на основе всех уроков const container = document.getElementById('lessonsList'); if (!container) return; const grouped = {}; lessons.forEach(lesson => { if (!grouped[lesson.class_name]) grouped[lesson.class_name] = []; grouped[lesson.class_name].push(lesson); }); container.innerHTML = ''; for (const [className, classLessons] of Object.entries(grouped)) { const section = document.createElement('div'); section.className = 'class-group'; section.innerHTML = `

${className}

`; classLessons.forEach(lesson => { const div = document.createElement('div'); div.className = 'lesson-item'; div.innerHTML = `
${lesson.subject} — ${lesson.teacher}
Тема: ${lesson.topic || '—'}
${lesson.date} ${lesson.time} | Места: ${lesson.current_slots}/${lesson.max_slots}
`; section.appendChild(div); }); container.appendChild(section); } document.querySelectorAll('.viewRegBtn').forEach(btn => btn.addEventListener('click', () => showRegistrations(btn.dataset.id))); document.querySelectorAll('.editBtn').forEach(btn => btn.addEventListener('click', () => openLessonModal(btn.dataset.id))); document.querySelectorAll('.deleteBtn').forEach(btn => btn.addEventListener('click', () => deleteLesson(btn.dataset.id))); } async function showRegistrations(lessonId) { const res = await fetch(`/api/admin/registrations?lesson_id=${lessonId}`); const registrations = await res.json(); const modal = document.getElementById('registrationsModal'); const listDiv = document.getElementById('registrationsList'); listDiv.innerHTML = registrations.length ? '' : '

Нет записей

'; modal.style.display = 'flex'; modal.querySelector('.close').onclick = () => modal.style.display = 'none'; } async function deleteLesson(id) { if (!confirm('Удалить урок? Все записи будут удалены.')) return; await fetch(`/api/admin/lessons/${id}`, { method: 'DELETE' }); loadLessons(getCurrentFilters()); } function openLessonModal(id = null) { const modal = document.getElementById('lessonModal'); const form = document.getElementById('lessonForm'); form.reset(); document.getElementById('lessonId').value = ''; document.getElementById('modalTitle').innerText = id ? 'Редактирование урока' : 'Новый урок'; if (id) { fetch(`/api/admin/lessons`).then(res => res.json()).then(lessons => { const lesson = lessons.find(l => l.id == id); if (lesson) { document.getElementById('lessonId').value = lesson.id; document.getElementById('className').value = lesson.class_name; document.getElementById('parallel').value = lesson.parallel; document.getElementById('subject').value = lesson.subject; document.getElementById('teacher').value = lesson.teacher; document.getElementById('topic').value = lesson.topic || ''; document.getElementById('maxSlots').value = lesson.max_slots; document.getElementById('date').value = lesson.date; document.getElementById('time').value = lesson.time; } }); } modal.style.display = 'flex'; modal.querySelector('.close').onclick = () => modal.style.display = 'none'; } document.getElementById('lessonForm')?.addEventListener('submit', async (e) => { e.preventDefault(); const id = document.getElementById('lessonId').value; const payload = { id: id || undefined, class_name: document.getElementById('className').value, parallel: parseInt(document.getElementById('parallel').value), subject: document.getElementById('subject').value, teacher: document.getElementById('teacher').value, topic: document.getElementById('topic').value, max_slots: parseInt(document.getElementById('maxSlots').value), date: document.getElementById('date').value, time: document.getElementById('time').value }; await fetch('/api/admin/lessons', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }); document.getElementById('lessonModal').style.display = 'none'; loadLessons(getCurrentFilters()); }); function getCurrentFilters() { return { class_name: document.getElementById('filterClass').value, parallel: document.getElementById('filterParallel').value, teacher: document.getElementById('filterTeacher').value, topic: document.getElementById('filterTopic').value }; } function setupEventListeners() { const classSelect = document.getElementById('filterClass'); const teacherSelect = document.getElementById('filterTeacher'); const topicSelect = document.getElementById('filterTopic'); function handleFilterChange() { // Обновить зависимые списки, затем применить фильтрацию списка уроков updateDependentFilters(); loadLessons(getCurrentFilters()); } classSelect?.addEventListener('change', handleFilterChange); teacherSelect?.addEventListener('change', handleFilterChange); topicSelect?.addEventListener('change', handleFilterChange); document.getElementById('applyFilters')?.addEventListener('click', () => { loadLessons(getCurrentFilters()); }); document.getElementById('resetFilters')?.addEventListener('click', () => { document.getElementById('filterClass').value = ''; document.getElementById('filterParallel').value = ''; document.getElementById('filterTeacher').value = ''; document.getElementById('filterTopic').value = ''; updateDependentFilters(); loadLessons({}); }); document.getElementById('addLessonBtn')?.addEventListener('click', () => openLessonModal()); document.getElementById('logoutBtn')?.addEventListener('click', async () => { await fetch('/api/logout', { method: 'POST' }); window.location.href = '/'; }); // Импорт JSON (без изменений) document.getElementById('previewImportBtn')?.addEventListener('click', async () => { const file = document.getElementById('jsonFileInput').files[0]; if (!file) return alert('Выберите JSON-файл'); const importMaxSlots = document.getElementById('importMaxSlots').value; if (!importMaxSlots) return alert('Укажите максимальное количество мест'); const text = await file.text(); try { const response = await fetch('/api/admin/import/preview', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ jsonData: JSON.parse(text) }) }); const data = await response.json(); if (data.success) { currentPreviewLessons = data.preview; const tbody = document.querySelector('#previewTable tbody'); tbody.innerHTML = ''; data.preview.slice(0,20).forEach(lesson => { const row = tbody.insertRow(); row.insertCell(0).innerText = lesson.className; row.insertCell(1).innerText = lesson.subject; row.insertCell(2).innerText = lesson.teacher; row.insertCell(3).innerText = lesson.topic || ''; }); if (data.preview.length > 20) { const row = tbody.insertRow(); const cell = row.insertCell(0); cell.colSpan = 4; cell.innerText = `... и ещё ${data.preview.length - 20} записей`; } document.getElementById('importPreview').style.display = 'block'; } else { alert('Ошибка предпросмотра: ' + (data.error || '')); } } catch (err) { alert('Ошибка при разборе JSON: ' + err.message); } }); document.getElementById('confirmImportBtn')?.addEventListener('click', async () => { if (!confirm(`Импортировать ${currentPreviewLessons.length} уроков?`)) return; const importMaxSlots = document.getElementById('importMaxSlots').value; const response = await fetch('/api/admin/import', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ lessons: currentPreviewLessons, defaultMaxSlots: importMaxSlots }) }); const result = await response.json(); if (result.success) { const created = result.results.filter(r => r.status === 'created').length; const skipped = result.results.filter(r => r.status === 'skipped').length; document.getElementById('importResult').innerHTML = `Импорт завершён: создано ${created}, пропущено (дубликаты) ${skipped}`; loadLessons(getCurrentFilters()); document.getElementById('importPreview').style.display = 'none'; } else { alert('Ошибка импорта: ' + (result.error || '')); } }); }