Files
magistr/frontend/admin/admin.js

356 lines
14 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');
// 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');
const navItems = document.querySelectorAll('.nav-item[data-tab]');
const tabContents = document.querySelectorAll('.tab-content');
// ---- State ----
let allGroups = [];
let allEducationForms = [];
// ---- Tab Switching ----
const TAB_TITLES = {
users: 'Управление пользователями',
groups: 'Управление группами',
'edu-forms': 'Формы обучения',
};
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();
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('Ошибка соединения'); }
});
// ============================================================
// LOGOUT & INIT
// ============================================================
btnLogout.addEventListener('click', () => {
localStorage.removeItem('token');
localStorage.removeItem('role');
window.location.href = '/';
});
loadUsers();
})();