Refactor admin frontend into modular SPA

This commit is contained in:
Zuev
2026-02-26 00:27:10 +03:00
parent 18f47c6d3d
commit 772b110762
21 changed files with 2204 additions and 2351 deletions

View File

@@ -0,0 +1,108 @@
import { api } from '../api.js';
import { escapeHtml, showAlert, hideAlert } from '../utils.js';
import { fetchEducationForms } from './edu-forms.js';
export async function initGroups() {
const groupsTbody = document.getElementById('groups-tbody');
const createGroupForm = document.getElementById('create-group-form');
const newGroupEfSelect = document.getElementById('new-group-ef');
const filterEfSelect = document.getElementById('filter-ef');
let allGroups = [];
let educationForms = [];
async function loadInitialData() {
try {
educationForms = await fetchEducationForms();
populateEfSelects(educationForms);
await loadGroups();
} catch (e) {
groupsTbody.innerHTML = '<tr><td colspan="4" class="loading-row">Ошибка загрузки данных</td></tr>';
}
}
async function loadGroups() {
try {
allGroups = await api.get('/api/groups');
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 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;
}
function renderGroups(groups) {
if (!groups || !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('create-group-alert');
const name = document.getElementById('new-group-name').value.trim();
const educationFormId = newGroupEfSelect.value;
if (!name) { showAlert('create-group-alert', 'Введите название группы', 'error'); return; }
if (!educationFormId) { showAlert('create-group-alert', 'Выберите форму обучения', 'error'); return; }
try {
const data = await api.post('/api/groups', { name, educationFormId: Number(educationFormId) });
showAlert('create-group-alert', `Группа "${escapeHtml(data.name)}" создана`, 'success');
createGroupForm.reset();
loadGroups();
} catch (e) {
showAlert('create-group-alert', e.message || 'Ошибка создания', 'error');
}
});
groupsTbody.addEventListener('click', async (e) => {
const btn = e.target.closest('.btn-delete');
if (!btn) return;
if (!confirm('Удалить группу?')) return;
try {
await api.delete('/api/groups/' + btn.dataset.id);
loadGroups();
} catch (e) {
alert(e.message || 'Ошибка удаления');
}
});
loadInitialData();
}