(() => {
'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();
})();