(() => { 'use strict'; const token = localStorage.getItem('token'); const role = localStorage.getItem('role'); if (!token || role !== 'ADMIN') { window.location.href = '/'; return; } // ---- DOM refs ---- const pageTitle = document.getElementById('page-title'); const btnLogout = document.getElementById('btn-logout'); const menuToggle = document.getElementById('menu-toggle'); const sidebar = document.querySelector('.sidebar'); const sidebarOverlay = document.getElementById('sidebar-overlay'); // Global Ripple Effect document.addEventListener('click', function (e) { const btn = e.target.closest('.btn-create, .btn-delete, .btn-logout'); if (!btn) return; const rect = btn.getBoundingClientRect(); const x = e.clientX - rect.left; const y = e.clientY - rect.top; const ripple = document.createElement('span'); ripple.classList.add('ripple'); ripple.style.left = `${x}px`; ripple.style.top = `${y}px`; if (getComputedStyle(btn).position === 'static') { btn.style.position = 'relative'; } btn.style.overflow = 'hidden'; btn.appendChild(ripple); setTimeout(() => ripple.remove(), 600); }); // Users const usersTbody = document.getElementById('users-tbody'); const createForm = document.getElementById('create-form'); const createAlert = document.getElementById('create-alert'); // Groups const groupsTbody = document.getElementById('groups-tbody'); const createGroupForm = document.getElementById('create-group-form'); const createGroupAlert = document.getElementById('create-group-alert'); const newGroupEfSelect = document.getElementById('new-group-ef'); const filterEfSelect = document.getElementById('filter-ef'); // Education Forms const efTbody = document.getElementById('ef-tbody'); const createEfForm = document.getElementById('create-ef-form'); const createEfAlert = document.getElementById('create-ef-alert'); // Classrooms const classroomsTbody = document.getElementById('classrooms-tbody'); const createClassroomForm = document.getElementById('create-classroom-form'); const createClassroomAlert = document.getElementById('create-classroom-alert'); const modalEditClassroom = document.getElementById('modal-edit-classroom'); const modalEditClassroomClose = document.getElementById('modal-edit-classroom-close'); const editClassroomForm = document.getElementById('edit-classroom-form'); const editClassroomAlert = document.getElementById('edit-classroom-alert'); const editEquipmentCheckboxes = document.getElementById('edit-equipment-checkboxes'); // Equipments const equipmentsTbody = document.getElementById('equipments-tbody'); const createEquipmentForm = document.getElementById('create-equipment-form'); const createEquipmentAlert = document.getElementById('create-equipment-alert'); const equipmentCheckboxes = document.getElementById('equipment-checkboxes'); // Subjects const subjectsTbody = document.getElementById('subjects-tbody'); const createSubjectForm = document.getElementById('create-subject-form'); const createSubjectAlert = document.getElementById('create-subject-alert'); const assignTeacherForm = document.getElementById('assign-teacher-form'); const assignTeacherAlert = document.getElementById('assign-teacher-alert'); const assignTeacherSelect = document.getElementById('assign-teacher-select'); const assignSubjectSelect = document.getElementById('assign-subject-select'); const teacherSubjectsTbody = document.getElementById('teacher-subjects-tbody'); // --- Multi-select logic --- function updateSelectText(containerId, textId) { const container = document.getElementById(containerId); const textEl = document.getElementById(textId); if (!container || !textEl) return; const checked = Array.from(container.querySelectorAll('input:checked')); if (checked.length === 0) { textEl.textContent = 'Выберите оборудование...'; } else if (checked.length === 1) { textEl.textContent = checked[0].parentElement.textContent.trim(); } else { textEl.textContent = `Выбрано: ${checked.length}`; } } function initMultiSelect(boxId, menuId, textId, checkboxContainerId) { const box = document.getElementById(boxId); const menu = document.getElementById(menuId); const container = document.getElementById(checkboxContainerId); if (!box || !menu || !container) return; box.addEventListener('click', (e) => { e.stopPropagation(); const isOpen = menu.classList.contains('open'); document.querySelectorAll('.dropdown-menu').forEach(m => m.classList.remove('open')); document.querySelectorAll('.select-box').forEach(b => b.classList.remove('active')); if (!isOpen) { menu.classList.add('open'); box.classList.add('active'); } }); menu.addEventListener('click', (e) => { e.stopPropagation(); }); container.addEventListener('change', () => { updateSelectText(checkboxContainerId, textId); }); } initMultiSelect('equipment-select-box', 'equipment-dropdown-menu', 'equipment-select-text', 'equipment-checkboxes'); initMultiSelect('edit-equipment-select-box', 'edit-equipment-dropdown-menu', 'edit-equipment-select-text', 'edit-equipment-checkboxes'); document.addEventListener('click', () => { document.querySelectorAll('.dropdown-menu').forEach(m => m.classList.remove('open')); document.querySelectorAll('.select-box').forEach(b => b.classList.remove('active')); }); // -------------------------- const navItems = document.querySelectorAll('.nav-item[data-tab]'); const tabContents = document.querySelectorAll('.tab-content'); // ---- State ---- let allGroups = []; let allEducationForms = []; let allEquipments = []; let allSubjects = []; let allTeachers = []; // ---- Tab Switching ---- const TAB_TITLES = { users: 'Управление пользователями', groups: 'Управление группами', 'edu-forms': 'Формы обучения', equipments: 'Оборудование', classrooms: 'Аудитории', subjects: 'Дисциплины и преподаватели' }; navItems.forEach(item => { item.addEventListener('click', (e) => { e.preventDefault(); switchTab(item.dataset.tab); }); }); function switchTab(tab) { navItems.forEach(n => n.classList.remove('active')); document.querySelector(`.nav-item[data-tab="${tab}"]`)?.classList.add('active'); tabContents.forEach(tc => tc.style.display = 'none'); const target = document.getElementById('tab-' + tab); if (target) target.style.display = ''; pageTitle.textContent = TAB_TITLES[tab] || ''; if (tab === 'users') loadUsers(); if (tab === 'groups') { loadEducationForms().then(() => loadGroups()); } if (tab === 'edu-forms') loadEducationForms(); if (tab === 'equipments') loadEquipments(); if (tab === 'classrooms') { loadEquipments().then(() => loadClassrooms()); } if (tab === 'subjects') { Promise.all([loadSubjects(), loadTeachers()]).then(() => loadTeacherSubjects()); } sidebar.classList.remove('open'); sidebarOverlay.classList.remove('open'); } // ---- Mobile Menu ---- menuToggle.addEventListener('click', () => { sidebar.classList.toggle('open'); sidebarOverlay.classList.toggle('open'); }); sidebarOverlay.addEventListener('click', () => { sidebar.classList.remove('open'); sidebarOverlay.classList.remove('open'); }); // ---- Helpers ---- const ROLE_LABELS = { ADMIN: 'Администратор', TEACHER: 'Преподаватель', STUDENT: 'Студент' }; const ROLE_BADGE = { ADMIN: 'badge-admin', TEACHER: 'badge-teacher', STUDENT: 'badge-student' }; function escapeHtml(str) { const div = document.createElement('div'); div.textContent = str; return div.innerHTML; } function showAlert(el, msg, type) { el.className = 'form-alert ' + type; el.textContent = msg; } function hideAlert(el) { el.className = 'form-alert'; el.textContent = ''; } // ============================================================ // USERS // ============================================================ async function loadUsers() { try { const res = await fetch('/api/users', { headers: { 'Authorization': 'Bearer ' + token }, }); const users = await res.json(); renderUsers(users); } catch (e) { usersTbody.innerHTML = 'Ошибка загрузки'; } } function renderUsers(users) { if (!users.length) { usersTbody.innerHTML = 'Нет пользователей'; return; } usersTbody.innerHTML = users.map(u => ` ${u.id} ${escapeHtml(u.username)} ${ROLE_LABELS[u.role] || u.role} `).join(''); } createForm.addEventListener('submit', async (e) => { e.preventDefault(); hideAlert(createAlert); const username = document.getElementById('new-username').value.trim(); const password = document.getElementById('new-password').value; const role = document.getElementById('new-role').value; if (!username || !password) { showAlert(createAlert, 'Заполните все поля', 'error'); return; } try { const res = await fetch('/api/users', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + token }, body: JSON.stringify({ username, password, role }), }); const data = await res.json(); if (res.ok) { showAlert(createAlert, `Пользователь "${data.username}" создан`, 'success'); createForm.reset(); loadUsers(); } else { showAlert(createAlert, data.message || 'Ошибка создания', 'error'); } } catch (e) { showAlert(createAlert, 'Ошибка соединения', 'error'); } }); usersTbody.addEventListener('click', async (e) => { const btn = e.target.closest('.btn-delete'); if (!btn) return; if (!confirm('Удалить пользователя?')) return; try { const res = await fetch('/api/users/' + btn.dataset.id, { method: 'DELETE', headers: { 'Authorization': 'Bearer ' + token }, }); if (res.ok) loadUsers(); else alert('Ошибка удаления'); } catch (e) { alert('Ошибка соединения'); } }); // ============================================================ // EDUCATION FORMS // ============================================================ async function loadEducationForms() { try { const res = await fetch('/api/education-forms', { headers: { 'Authorization': 'Bearer ' + token }, }); allEducationForms = await res.json(); renderEfTable(allEducationForms); populateEfSelects(allEducationForms); } catch (e) { efTbody.innerHTML = 'Ошибка загрузки'; } } function renderEfTable(forms) { if (!forms.length) { efTbody.innerHTML = 'Нет форм обучения'; return; } efTbody.innerHTML = forms.map(ef => ` ${ef.id} ${escapeHtml(ef.name)} `).join(''); } function populateEfSelects(forms) { // Group creation select const currentVal = newGroupEfSelect.value; newGroupEfSelect.innerHTML = forms.map(ef => `` ).join(''); if (currentVal && forms.find(f => f.id == currentVal)) { newGroupEfSelect.value = currentVal; } // Filter select const currentFilter = filterEfSelect.value; filterEfSelect.innerHTML = '' + forms.map(ef => `` ).join(''); if (currentFilter) filterEfSelect.value = currentFilter; } createEfForm.addEventListener('submit', async (e) => { e.preventDefault(); hideAlert(createEfAlert); const name = document.getElementById('new-ef-name').value.trim(); if (!name) { showAlert(createEfAlert, 'Введите название', 'error'); return; } try { const res = await fetch('/api/education-forms', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + token }, body: JSON.stringify({ name }), }); const data = await res.json(); if (res.ok) { showAlert(createEfAlert, `Форма "${data.name}" создана`, 'success'); createEfForm.reset(); loadEducationForms(); } else { showAlert(createEfAlert, data.message || 'Ошибка создания', 'error'); } } catch (e) { showAlert(createEfAlert, 'Ошибка соединения', 'error'); } }); efTbody.addEventListener('click', async (e) => { const btn = e.target.closest('.btn-delete'); if (!btn) return; if (!confirm('Удалить форму обучения?')) return; try { const res = await fetch('/api/education-forms/' + btn.dataset.id, { method: 'DELETE', headers: { 'Authorization': 'Bearer ' + token }, }); if (res.ok) { loadEducationForms(); } else { const data = await res.json(); alert(data.message || 'Ошибка удаления'); } } catch (e) { alert('Ошибка соединения'); } }); // ============================================================ // GROUPS // ============================================================ async function loadGroups() { try { const res = await fetch('/api/groups', { headers: { 'Authorization': 'Bearer ' + token }, }); allGroups = await res.json(); applyGroupFilter(); } catch (e) { groupsTbody.innerHTML = 'Ошибка загрузки'; } } function applyGroupFilter() { const filterId = filterEfSelect.value; const filtered = filterId ? allGroups.filter(g => g.educationFormId == filterId) : allGroups; renderGroups(filtered); } filterEfSelect.addEventListener('change', applyGroupFilter); function renderGroups(groups) { if (!groups.length) { groupsTbody.innerHTML = 'Нет групп'; return; } groupsTbody.innerHTML = groups.map(g => ` ${g.id} ${escapeHtml(g.name)} ${escapeHtml(g.educationFormName)} `).join(''); } createGroupForm.addEventListener('submit', async (e) => { e.preventDefault(); hideAlert(createGroupAlert); const name = document.getElementById('new-group-name').value.trim(); const educationFormId = newGroupEfSelect.value; if (!name) { showAlert(createGroupAlert, 'Введите название группы', 'error'); return; } if (!educationFormId) { showAlert(createGroupAlert, 'Выберите форму обучения', 'error'); return; } try { const res = await fetch('/api/groups', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + token }, body: JSON.stringify({ name, educationFormId: Number(educationFormId) }), }); const data = await res.json(); if (res.ok) { showAlert(createGroupAlert, `Группа "${data.name}" создана`, 'success'); createGroupForm.reset(); loadGroups(); } else { showAlert(createGroupAlert, data.message || 'Ошибка создания', 'error'); } } catch (e) { showAlert(createGroupAlert, 'Ошибка соединения', 'error'); } }); groupsTbody.addEventListener('click', async (e) => { const btn = e.target.closest('.btn-delete'); if (!btn) return; if (!confirm('Удалить группу?')) return; try { const res = await fetch('/api/groups/' + btn.dataset.id, { method: 'DELETE', headers: { 'Authorization': 'Bearer ' + token }, }); if (res.ok) loadGroups(); else alert('Ошибка удаления'); } catch (e) { alert('Ошибка соединения'); } }); // ============================================================ // EQUIPMENTS // ============================================================ async function loadEquipments() { try { const res = await fetch('/api/equipments', { headers: { 'Authorization': 'Bearer ' + token }, }); allEquipments = await res.json(); renderEquipments(allEquipments); renderEquipmentCheckboxes(allEquipments); } catch (e) { if (equipmentsTbody) equipmentsTbody.innerHTML = 'Ошибка загрузки'; if (equipmentCheckboxes) equipmentCheckboxes.innerHTML = '

Ошибка загрузки

'; } } function renderEquipments(equipments) { if (!equipments.length) { equipmentsTbody.innerHTML = 'Нет оборудования'; return; } equipmentsTbody.innerHTML = equipments.map(eq => ` ${eq.id} ${escapeHtml(eq.name)} `).join(''); } function renderEquipmentCheckboxes(equipments) { if (!equipments.length) { equipmentCheckboxes.innerHTML = '

Нет доступного оборудования

'; return; } equipmentCheckboxes.innerHTML = equipments.map(eq => ` `).join(''); updateSelectText('equipment-checkboxes', 'equipment-select-text'); } createEquipmentForm.addEventListener('submit', async (e) => { e.preventDefault(); hideAlert(createEquipmentAlert); const name = document.getElementById('new-equipment-name').value.trim(); if (!name) { showAlert(createEquipmentAlert, 'Введите название', 'error'); return; } try { const res = await fetch('/api/equipments', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + token }, body: JSON.stringify({ name }), }); const data = await res.json(); if (res.ok) { showAlert(createEquipmentAlert, `Оборудование "${data.name}" добавлено`, 'success'); createEquipmentForm.reset(); loadEquipments(); } else { showAlert(createEquipmentAlert, data.message || 'Ошибка создания', 'error'); } } catch (e) { showAlert(createEquipmentAlert, 'Ошибка соединения', 'error'); } }); equipmentsTbody.addEventListener('click', async (e) => { const btn = e.target.closest('.btn-delete'); if (!btn) return; if (!confirm('Удалить оборудование?')) return; try { const res = await fetch('/api/equipments/' + btn.dataset.id, { method: 'DELETE', headers: { 'Authorization': 'Bearer ' + token }, }); if (res.ok) { loadEquipments(); } else { const data = await res.json(); alert(data.message || 'Ошибка удаления'); } } catch (e) { alert('Ошибка соединения'); } }); // ============================================================ // CLASSROOMS // ============================================================ async function loadClassrooms() { try { const res = await fetch('/api/classrooms', { headers: { 'Authorization': 'Bearer ' + token }, }); const classrooms = await res.json(); renderClassrooms(classrooms); } catch (e) { classroomsTbody.innerHTML = 'Ошибка загрузки'; } } function renderClassrooms(classrooms) { if (!classrooms.length) { classroomsTbody.innerHTML = 'Нет аудиторий'; return; } classroomsTbody.innerHTML = classrooms.map(c => { const equipHtml = c.equipments && c.equipments.length ? c.equipments.map(eq => escapeHtml(eq.name)).join(', ') : '—'; return ` ${c.id} ${escapeHtml(c.name)} ${c.capacity} чел. ${equipHtml}
${c.isAvailable ? 'Доступна' : 'Не доступна'}
`; }).join(''); } createClassroomForm.addEventListener('submit', async (e) => { e.preventDefault(); hideAlert(createClassroomAlert); const name = document.getElementById('new-classroom-name').value.trim(); const capacity = parseInt(document.getElementById('new-classroom-capacity').value, 10); const checkedBoxes = Array.from(equipmentCheckboxes.querySelectorAll('input:checked')); const equipmentIds = checkedBoxes.map(chk => parseInt(chk.value, 10)); if (!name || isNaN(capacity)) { showAlert(createClassroomAlert, 'Заполните обязательные поля', 'error'); return; } try { const res = await fetch('/api/classrooms', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + token }, body: JSON.stringify({ name, capacity, equipmentIds, isAvailable: true }), }); const data = await res.json(); if (res.ok) { showAlert(createClassroomAlert, `Аудитория "${data.name}" добавлена`, 'success'); createClassroomForm.reset(); updateSelectText('equipment-checkboxes', 'equipment-select-text'); loadClassrooms(); } else { showAlert(createClassroomAlert, data.message || 'Ошибка создания', 'error'); } } catch (e) { showAlert(createClassroomAlert, 'Ошибка соединения', 'error'); } }); classroomsTbody.addEventListener('click', async (e) => { const btnDelete = e.target.closest('.btn-delete'); const btnToggleStatus = e.target.closest('.btn-icon-toggle'); const btnEdit = e.target.closest('.btn-edit-classroom'); if (btnDelete) { if (!confirm('Удалить аудиторию?')) return; try { const res = await fetch('/api/classrooms/' + btnDelete.dataset.id, { method: 'DELETE', headers: { 'Authorization': 'Bearer ' + token }, }); if (res.ok) loadClassrooms(); else alert('Ошибка удаления'); } catch (err) { alert('Ошибка соединения'); } } if (btnToggleStatus) { const id = btnToggleStatus.dataset.id; const currentStatus = btnToggleStatus.dataset.currentStatus === 'true'; try { const res = await fetch('/api/classrooms/' + id, { method: 'PUT', headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + token }, body: JSON.stringify({ isAvailable: !currentStatus }), }); if (res.ok) loadClassrooms(); else alert('Ошибка изменения статуса'); } catch (err) { alert('Ошибка соединения'); } } if (btnEdit) { const id = btnEdit.dataset.id; openEditClassroomModal(id); } }); let editingClassroomData = null; async function openEditClassroomModal(id) { try { const res = await fetch('/api/classrooms', { headers: { 'Authorization': 'Bearer ' + token } }); const classrooms = await res.json(); editingClassroomData = classrooms.find(c => c.id == id); if (!editingClassroomData) return; document.getElementById('edit-classroom-id').value = editingClassroomData.id; document.getElementById('edit-classroom-name').value = editingClassroomData.name; document.getElementById('edit-classroom-capacity').value = editingClassroomData.capacity; if (allEquipments.length) { editEquipmentCheckboxes.innerHTML = allEquipments.map(eq => { const isChecked = editingClassroomData.equipments.some(e => e.id === eq.id) ? 'checked' : ''; return ` `; }).join(''); } else { editEquipmentCheckboxes.innerHTML = '

Нет доступного оборудования

'; } updateSelectText('edit-equipment-checkboxes', 'edit-equipment-select-text'); hideAlert(editClassroomAlert); modalEditClassroom.classList.add('open'); } catch (e) { alert('Ошибка загрузки данных аудитории'); } } modalEditClassroomClose.addEventListener('click', () => { modalEditClassroom.classList.remove('open'); }); modalEditClassroom.addEventListener('click', (e) => { if (e.target === modalEditClassroom) { modalEditClassroom.classList.remove('open'); } }); editClassroomForm.addEventListener('submit', async (e) => { e.preventDefault(); hideAlert(editClassroomAlert); const id = document.getElementById('edit-classroom-id').value; const name = document.getElementById('edit-classroom-name').value.trim(); const capacity = parseInt(document.getElementById('edit-classroom-capacity').value, 10); const checkedBoxes = Array.from(editEquipmentCheckboxes.querySelectorAll('input:checked')); const equipmentIds = checkedBoxes.map(chk => parseInt(chk.value, 10)); if (!name || isNaN(capacity)) { showAlert(editClassroomAlert, 'Заполните обязательные поля', 'error'); return; } try { const res = await fetch('/api/classrooms/' + id, { method: 'PUT', headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + token }, body: JSON.stringify({ name, capacity, equipmentIds, isAvailable: editingClassroomData.isAvailable }), }); const data = await res.json(); if (res.ok) { modalEditClassroom.classList.remove('open'); showAlert(createClassroomAlert, `Аудитория "${data.name}" обновлена`, 'success'); loadClassrooms(); } else { showAlert(editClassroomAlert, data.message || 'Ошибка обновления', 'error'); } } catch (e) { showAlert(editClassroomAlert, 'Ошибка соединения', 'error'); } }); // ============================================================ // SUBJECTS // ============================================================ async function loadSubjects() { try { const res = await fetch('/api/subjects', { headers: { 'Authorization': 'Bearer ' + token }, }); allSubjects = await res.json(); renderSubjects(allSubjects); populateSubjectSelect(allSubjects); } catch (e) { if (subjectsTbody) subjectsTbody.innerHTML = 'Ошибка загрузки'; } } function renderSubjects(subjects) { if (!subjects.length) { subjectsTbody.innerHTML = 'Нет дисциплин'; return; } subjectsTbody.innerHTML = subjects.map(s => ` ${s.id} ${escapeHtml(s.name)} `).join(''); } function populateSubjectSelect(subjects) { if (!assignSubjectSelect) return; const currentVal = assignSubjectSelect.value; assignSubjectSelect.innerHTML = '' + subjects.map(s => ``).join(''); if (currentVal && subjects.find(s => s.id == currentVal)) { assignSubjectSelect.value = currentVal; } } async function loadTeachers() { try { const res = await fetch('/api/users/teachers', { headers: { 'Authorization': 'Bearer ' + token }, }); allTeachers = await res.json(); populateTeacherSelect(allTeachers); } catch (e) { if (assignTeacherSelect) assignTeacherSelect.innerHTML = ''; } } function populateTeacherSelect(teachers) { if (!assignTeacherSelect) return; const currentVal = assignTeacherSelect.value; if (!teachers.length) { assignTeacherSelect.innerHTML = ''; return; } assignTeacherSelect.innerHTML = '' + teachers.map(t => ``).join(''); if (currentVal && teachers.find(t => t.id == currentVal)) { assignTeacherSelect.value = currentVal; } } async function loadTeacherSubjects() { try { const res = await fetch('/api/teacher-subjects', { headers: { 'Authorization': 'Bearer ' + token }, }); const tsData = await res.json(); renderTeacherSubjects(tsData); } catch (e) { if (teacherSubjectsTbody) teacherSubjectsTbody.innerHTML = 'Ошибка загрузки'; } } function renderTeacherSubjects(tsArray) { if (!tsArray.length) { teacherSubjectsTbody.innerHTML = 'Нет привязок'; return; } teacherSubjectsTbody.innerHTML = tsArray.map(ts => ` ${escapeHtml(ts.username)} ${escapeHtml(ts.subjectName)} `).join(''); } createSubjectForm.addEventListener('submit', async (e) => { e.preventDefault(); hideAlert(createSubjectAlert); const name = document.getElementById('new-subject-name').value.trim(); if (!name) { showAlert(createSubjectAlert, 'Введите название', 'error'); return; } try { const res = await fetch('/api/subjects', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + token }, body: JSON.stringify({ name }), }); const data = await res.json(); if (res.ok) { showAlert(createSubjectAlert, `Дисциплина "${data.name}" добавлена`, 'success'); createSubjectForm.reset(); loadSubjects(); } else { showAlert(createSubjectAlert, data.message || 'Ошибка создания', 'error'); } } catch (e) { showAlert(createSubjectAlert, 'Ошибка соединения', 'error'); } }); subjectsTbody.addEventListener('click', async (e) => { const btn = e.target.closest('.btn-delete'); if (!btn) return; if (!confirm('Удалить дисциплину?')) return; try { const res = await fetch('/api/subjects/' + btn.dataset.id, { method: 'DELETE', headers: { 'Authorization': 'Bearer ' + token }, }); if (res.ok) { loadSubjects(); loadTeacherSubjects(); } else { const data = await res.json(); alert(data.message || 'Ошибка удаления'); } } catch (e) { alert('Ошибка соединения'); } }); assignTeacherForm.addEventListener('submit', async (e) => { e.preventDefault(); hideAlert(assignTeacherAlert); const userId = assignTeacherSelect.value; const subjectId = assignSubjectSelect.value; if (!userId) { showAlert(assignTeacherAlert, 'Выберите преподавателя', 'error'); return; } if (!subjectId) { showAlert(assignTeacherAlert, 'Выберите дисциплину', 'error'); return; } try { const res = await fetch('/api/teacher-subjects', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + token }, body: JSON.stringify({ userId: Number(userId), subjectId: Number(subjectId) }), }); const data = await res.json(); if (res.ok) { showAlert(assignTeacherAlert, 'Привязка создана', 'success'); loadTeacherSubjects(); } else { showAlert(assignTeacherAlert, data.message || 'Ошибка привязки', 'error'); } } catch (e) { showAlert(assignTeacherAlert, 'Ошибка соединения', 'error'); } }); teacherSubjectsTbody.addEventListener('click', async (e) => { const btn = e.target.closest('.btn-delete'); if (!btn) return; if (!confirm('Удалить привязку?')) return; try { const res = await fetch('/api/teacher-subjects', { method: 'DELETE', headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + token }, body: JSON.stringify({ userId: Number(btn.dataset.userId), subjectId: Number(btn.dataset.subjectId) }), }); if (res.ok) loadTeacherSubjects(); else alert('Ошибка удаления'); } catch (e) { alert('Ошибка соединения'); } }); // ============================================================ // LOGOUT & INIT // ============================================================ btnLogout.addEventListener('click', () => { localStorage.removeItem('token'); localStorage.removeItem('role'); window.location.href = '/'; }); loadUsers(); })();