Files
magistr/frontend/admin/admin.js

726 lines
30 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.
(() => {
'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');
// --- 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 = [];
// ---- Tab Switching ----
const TAB_TITLES = {
users: 'Управление пользователями',
groups: 'Управление группами',
'edu-forms': 'Формы обучения',
equipments: 'Оборудование',
classrooms: 'Аудитории'
};
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()); }
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 = '<tr><td colspan="4" class="loading-row">Ошибка загрузки</td></tr>';
}
}
function renderUsers(users) {
if (!users.length) {
usersTbody.innerHTML = '<tr><td colspan="4" class="loading-row">Нет пользователей</td></tr>';
return;
}
usersTbody.innerHTML = users.map(u => `
<tr>
<td>${u.id}</td>
<td>${escapeHtml(u.username)}</td>
<td><span class="badge ${ROLE_BADGE[u.role] || ''}">${ROLE_LABELS[u.role] || u.role}</span></td>
<td><button class="btn-delete" data-id="${u.id}">Удалить</button></td>
<td><button class="btn-delete" data-role="${u.role}">Добавить занятие</button></td>
</tr>`).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 = '<tr><td colspan="3" class="loading-row">Ошибка загрузки</td></tr>';
}
}
function renderEfTable(forms) {
if (!forms.length) {
efTbody.innerHTML = '<tr><td colspan="3" class="loading-row">Нет форм обучения</td></tr>';
return;
}
efTbody.innerHTML = forms.map(ef => `
<tr>
<td>${ef.id}</td>
<td>${escapeHtml(ef.name)}</td>
<td><button class="btn-delete" data-id="${ef.id}">Удалить</button></td>
</tr>`).join('');
}
function populateEfSelects(forms) {
// Group creation select
const currentVal = newGroupEfSelect.value;
newGroupEfSelect.innerHTML = forms.map(ef =>
`<option value="${ef.id}">${escapeHtml(ef.name)}</option>`
).join('');
if (currentVal && forms.find(f => f.id == currentVal)) {
newGroupEfSelect.value = currentVal;
}
// Filter select
const currentFilter = filterEfSelect.value;
filterEfSelect.innerHTML = '<option value="">Все формы</option>' +
forms.map(ef =>
`<option value="${ef.id}">${escapeHtml(ef.name)}</option>`
).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 = '<tr><td colspan="4" class="loading-row">Ошибка загрузки</td></tr>';
}
}
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 = '<tr><td colspan="4" class="loading-row">Нет групп</td></tr>';
return;
}
groupsTbody.innerHTML = groups.map(g => `
<tr>
<td>${g.id}</td>
<td>${escapeHtml(g.name)}</td>
<td><span class="badge badge-ef">${escapeHtml(g.educationFormName)}</span></td>
<td><button class="btn-delete" data-id="${g.id}">Удалить</button></td>
</tr>`).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 = '<tr><td colspan="3" class="loading-row">Ошибка загрузки</td></tr>';
if (equipmentCheckboxes) equipmentCheckboxes.innerHTML = '<p class="text-error">Ошибка загрузки</p>';
}
}
function renderEquipments(equipments) {
if (!equipments.length) {
equipmentsTbody.innerHTML = '<tr><td colspan="3" class="loading-row">Нет оборудования</td></tr>';
return;
}
equipmentsTbody.innerHTML = equipments.map(eq => `
<tr>
<td>${eq.id}</td>
<td>${escapeHtml(eq.name)}</td>
<td><button class="btn-delete" data-id="${eq.id}">Удалить</button></td>
</tr>`).join('');
}
function renderEquipmentCheckboxes(equipments) {
if (!equipments.length) {
equipmentCheckboxes.innerHTML = '<p class="text-muted"><small>Нет доступного оборудования</small></p>';
return;
}
equipmentCheckboxes.innerHTML = equipments.map(eq => `
<label class="checkbox-item">
<input type="checkbox" value="${eq.id}"> ${escapeHtml(eq.name)}
</label>
`).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 = '<tr><td colspan="6" class="loading-row">Ошибка загрузки</td></tr>';
}
}
function renderClassrooms(classrooms) {
if (!classrooms.length) {
classroomsTbody.innerHTML = '<tr><td colspan="6" class="loading-row">Нет аудиторий</td></tr>';
return;
}
classroomsTbody.innerHTML = classrooms.map(c => {
const equipHtml = c.equipments && c.equipments.length
? c.equipments.map(eq => escapeHtml(eq.name)).join(', ')
: '—';
return `
<tr>
<td>${c.id}</td>
<td><strong>${escapeHtml(c.name)}</strong></td>
<td>${c.capacity} чел.</td>
<td><small>${equipHtml}</small></td>
<td>
<div class="status-cell">
<span class="badge ${c.isAvailable ? 'badge-available' : 'badge-unavailable'}">
${c.isAvailable ? 'Доступна' : 'Не доступна'}
</span>
<button class="btn-icon-toggle" data-id="${c.id}" data-current-status="${c.isAvailable}" title="Сменить статус">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12a9 9 0 01-9 9m9-9a9 9 0 00-9-9m9 9H3m9 9a9 9 0 01-9-9m9 9c1.657 0 3-4.03 3-9s-1.343-9-3-9m0 18c-1.657 0-3-4.03-3-9s1.343-9 3-9m-9 9a9 9 0 019-9"></path></svg>
</button>
</div>
</td>
<td style="text-align: right;">
<button class="btn-edit-classroom" data-id="${c.id}" style="padding: 0.35rem 0.7rem; background: rgba(99, 102, 241, 0.1); border: 1px solid rgba(99, 102, 241, 0.2); border-radius: var(--radius-sm); color: var(--accent-hover); cursor: pointer; margin-right: 0.5rem;">Изменить</button>
<button class="btn-delete" data-id="${c.id}">Удалить</button>
</td>
</tr>`;
}).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 `
<label class="checkbox-item">
<input type="checkbox" value="${eq.id}" ${isChecked}> ${escapeHtml(eq.name)}
</label>
`;
}).join('');
} else {
editEquipmentCheckboxes.innerHTML = '<p class="text-muted"><small>Нет доступного оборудования</small></p>';
}
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'); }
});
// ============================================================
// LOGOUT & INIT
// ============================================================
btnLogout.addEventListener('click', () => {
localStorage.removeItem('token');
localStorage.removeItem('role');
window.location.href = '/';
});
loadUsers();
})();