624 lines
29 KiB
JavaScript
624 lines
29 KiB
JavaScript
import { api } from '../api.js';
|
||
import { escapeHtml, showAlert, hideAlert } from '../utils.js';
|
||
|
||
async function fetchEducationForms() {
|
||
try {
|
||
return await api.get('/api/education-forms');
|
||
} catch (e) {
|
||
console.error("Failed to fetch education forms", e);
|
||
return [];
|
||
}
|
||
}
|
||
|
||
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 newGroupDepartmentSelect = document.getElementById('new-group-department');
|
||
const newGroupSpecialitySelect = document.getElementById('new-group-speciality-code');
|
||
const newGroupProfileSelect = document.getElementById('new-group-profile');
|
||
const filterEfSelect = document.getElementById('filter-ef');
|
||
|
||
// Модалка группы
|
||
const modalEditGroup = document.getElementById('modal-edit-group');
|
||
const modalEditGroupClose = document.getElementById('modal-edit-group-close');
|
||
const editGroupForm = document.getElementById('edit-group-form');
|
||
const editGroupEfSelect = document.getElementById('edit-group-ef');
|
||
const editGroupDepartmentSelect = document.getElementById('edit-group-department');
|
||
const editGroupSpecialitySelect = document.getElementById('edit-group-speciality-code');
|
||
const editGroupProfileSelect = document.getElementById('edit-group-profile');
|
||
|
||
// Модалка подгрупп
|
||
const modalManageSubgroups = document.getElementById('modal-manage-subgroups');
|
||
const modalManageSubgroupsClose = document.getElementById('modal-manage-subgroups-close');
|
||
const subgroupsGroupContext = document.getElementById('subgroups-group-context');
|
||
const subgroupFormTitle = document.getElementById('subgroup-form-title');
|
||
const manageSubgroupForm = document.getElementById('manage-subgroup-form');
|
||
const manageSubgroupIdInput = document.getElementById('manage-subgroup-id');
|
||
const manageSubgroupGroupIdInput = document.getElementById('manage-subgroup-group-id');
|
||
const manageSubgroupNameInput = document.getElementById('manage-subgroup-name');
|
||
const manageSubgroupCapacityInput = document.getElementById('manage-subgroup-capacity');
|
||
const btnSaveSubgroup = document.getElementById('btn-save-subgroup');
|
||
const btnCancelSubgroupEdit = document.getElementById('btn-cancel-subgroup-edit');
|
||
const manageSubgroupsTbody = document.getElementById('manage-subgroups-tbody');
|
||
|
||
// Модалка календарей
|
||
const modalManageCalendar = document.getElementById('modal-manage-calendar');
|
||
const modalManageCalendarClose = document.getElementById('modal-manage-calendar-close');
|
||
const calendarGroupContext = document.getElementById('calendar-group-context');
|
||
const manageCalendarForm = document.getElementById('manage-calendar-form');
|
||
const manageCalendarGroupIdInput = document.getElementById('manage-calendar-group-id');
|
||
const manageCalendarYearSelect = document.getElementById('manage-calendar-year');
|
||
const manageCalendarSelect = document.getElementById('manage-calendar-id');
|
||
const manageCalendarTbody = document.getElementById('manage-calendar-tbody');
|
||
|
||
let allGroups = [];
|
||
let subgroups = [];
|
||
let educationForms = [];
|
||
let departments = [];
|
||
let specialties = [];
|
||
let profilesBySpecialty = new Map();
|
||
let academicYears = [];
|
||
let calendars = [];
|
||
let currentAssignments = [];
|
||
|
||
bindEvents();
|
||
await loadInitialData();
|
||
|
||
function bindEvents() {
|
||
filterEfSelect.addEventListener('change', applyGroupFilter);
|
||
newGroupSpecialitySelect.addEventListener('change', () => populateProfileSelect(newGroupProfileSelect, newGroupSpecialitySelect.value));
|
||
editGroupSpecialitySelect.addEventListener('change', () => populateProfileSelect(editGroupProfileSelect, editGroupSpecialitySelect.value));
|
||
|
||
createGroupForm.addEventListener('submit', createGroup);
|
||
editGroupForm.addEventListener('submit', updateGroup);
|
||
|
||
// Модалка подгрупп
|
||
manageSubgroupForm.addEventListener('submit', saveSubgroup);
|
||
btnCancelSubgroupEdit.addEventListener('click', resetSubgroupForm);
|
||
manageSubgroupsTbody.addEventListener('click', handleSubgroupTableClick);
|
||
|
||
// Модалка календарей
|
||
manageCalendarForm.addEventListener('submit', saveAssignment);
|
||
manageCalendarYearSelect.addEventListener('change', populateCalendarSelect);
|
||
|
||
groupsTbody.addEventListener('click', handleGroupTableClick);
|
||
|
||
// Закрытие модалок
|
||
modalEditGroupClose.addEventListener('click', () => modalEditGroup.classList.remove('open'));
|
||
modalEditGroup.addEventListener('click', (event) => {
|
||
if (event.target === modalEditGroup) modalEditGroup.classList.remove('open');
|
||
});
|
||
|
||
modalManageSubgroupsClose.addEventListener('click', () => modalManageSubgroups.classList.remove('open'));
|
||
modalManageSubgroups.addEventListener('click', (event) => {
|
||
if (event.target === modalManageSubgroups) modalManageSubgroups.classList.remove('open');
|
||
});
|
||
|
||
modalManageCalendarClose.addEventListener('click', () => modalManageCalendar.classList.remove('open'));
|
||
modalManageCalendar.addEventListener('click', (event) => {
|
||
if (event.target === modalManageCalendar) modalManageCalendar.classList.remove('open');
|
||
});
|
||
}
|
||
|
||
async function loadInitialData() {
|
||
try {
|
||
[educationForms, departments, specialties, academicYears, calendars] = await Promise.all([
|
||
fetchEducationForms(),
|
||
api.get('/api/departments'),
|
||
api.get('/api/specialties'),
|
||
api.get('/api/admin/calendar/years'),
|
||
api.get('/api/admin/academic-calendars')
|
||
]);
|
||
await loadProfiles();
|
||
populateEfSelects(educationForms);
|
||
populateDepartmentSelects(departments);
|
||
populateSpecialitySelects(specialties);
|
||
populateYearSelect();
|
||
await loadGroups();
|
||
await loadSubgroups();
|
||
} catch (error) {
|
||
groupsTbody.innerHTML = `<tr><td colspan="10" class="loading-row">Ошибка загрузки данных: ${escapeHtml(error.message)}</td></tr>`;
|
||
}
|
||
}
|
||
|
||
async function loadProfiles() {
|
||
const pairs = await Promise.all(specialties.map(async (speciality) => {
|
||
const profiles = await api.get(`/api/specialties/${speciality.id}/profiles`);
|
||
return [String(speciality.id), profiles];
|
||
}));
|
||
profilesBySpecialty = new Map(pairs);
|
||
}
|
||
|
||
async function loadGroups() {
|
||
try {
|
||
allGroups = await api.get('/api/groups?includeArchived=true');
|
||
applyGroupFilter();
|
||
} catch (error) {
|
||
groupsTbody.innerHTML = `<tr><td colspan="10" class="loading-row">Ошибка загрузки: ${escapeHtml(error.message)}</td></tr>`;
|
||
}
|
||
}
|
||
|
||
async function loadSubgroups() {
|
||
try {
|
||
subgroups = await api.get('/api/subgroups');
|
||
} catch (error) {
|
||
console.error("Failed to load subgroups", error);
|
||
}
|
||
}
|
||
|
||
function applyGroupFilter() {
|
||
const filterId = filterEfSelect.value;
|
||
const filtered = filterId
|
||
? allGroups.filter(group => group.educationFormId == filterId)
|
||
: allGroups;
|
||
renderGroups(filtered);
|
||
}
|
||
|
||
function renderGroups(groups) {
|
||
if (!groups || !groups.length) {
|
||
groupsTbody.innerHTML = '<tr><td colspan="10" class="loading-row">Нет групп</td></tr>';
|
||
return;
|
||
}
|
||
groupsTbody.innerHTML = groups.map(group => `
|
||
<tr>
|
||
<td>${group.id}</td>
|
||
<td><strong>${escapeHtml(group.name)}</strong></td>
|
||
<td>${escapeHtml(String(group.groupSize))}</td>
|
||
<td><span class="badge badge-ef">${escapeHtml(group.educationFormName)}</span></td>
|
||
<td>${escapeHtml(departmentLabel(group.departmentId))}</td>
|
||
<td>${group.course || '-'}</td>
|
||
<td>${escapeHtml(specialityLabel(group.specialtyId || group.specialityCode))}</td>
|
||
<td>${escapeHtml(group.specialtyProfileName || '-')}</td>
|
||
<td><span class="badge ${statusBadgeClass(group)}">${escapeHtml(statusLabel(group))}</span></td>
|
||
<td style="text-align: right; white-space: nowrap;">
|
||
<button class="btn-edit-classroom btn-edit-group" data-id="${group.id}">Изменить</button>
|
||
<button class="btn-primary btn-manage-subgroups" data-id="${group.id}" style="padding: 0.25rem 0.5rem; font-size: 0.85rem;">Подгруппы</button>
|
||
<button class="btn-primary btn-manage-calendar" data-id="${group.id}" style="padding: 0.25rem 0.5rem; font-size: 0.85rem;">Календарь</button>
|
||
${group.status === 'ARCHIVED'
|
||
? `<button class="btn-restore-group" data-id="${group.id}" style="margin-left: 0.5rem;">Восстановить</button>`
|
||
: `<button class="btn-delete" data-id="${group.id}" style="margin-left: 0.5rem;">Архив</button>`}
|
||
</td>
|
||
</tr>
|
||
`).join('');
|
||
}
|
||
|
||
function populateEfSelects(forms) {
|
||
populateEfSelect(newGroupEfSelect, forms);
|
||
populateEfSelect(editGroupEfSelect, forms);
|
||
const currentFilter = filterEfSelect.value;
|
||
filterEfSelect.innerHTML = '<option value="">Все формы</option>' +
|
||
forms.map(form => `<option value="${form.id}">${escapeHtml(form.name)}</option>`).join('');
|
||
if (currentFilter) filterEfSelect.value = currentFilter;
|
||
syncSelects(filterEfSelect);
|
||
}
|
||
|
||
function populateEfSelect(select, forms) {
|
||
const currentVal = select.value;
|
||
select.innerHTML = forms.map(form => `<option value="${form.id}">${escapeHtml(form.name)}</option>`).join('');
|
||
if (currentVal && forms.find(form => form.id == currentVal)) {
|
||
select.value = currentVal;
|
||
}
|
||
syncSelects(select);
|
||
}
|
||
|
||
function populateDepartmentSelects(items) {
|
||
populateDepartmentSelect(newGroupDepartmentSelect, items);
|
||
populateDepartmentSelect(editGroupDepartmentSelect, items);
|
||
}
|
||
|
||
function populateDepartmentSelect(select, items) {
|
||
const currentVal = select.value;
|
||
select.innerHTML = '<option value="">Выберите кафедру</option>' +
|
||
items.map(department => {
|
||
const name = department.departmentName || department.name || 'Без названия';
|
||
const code = department.departmentCode || department.code || department.id;
|
||
return `<option value="${department.id}">${escapeHtml(name)} (${escapeHtml(String(code))})</option>`;
|
||
}).join('');
|
||
if (currentVal && items.find(department => department.id == currentVal)) {
|
||
select.value = currentVal;
|
||
}
|
||
syncSelects(select);
|
||
}
|
||
|
||
function populateSpecialitySelects(items) {
|
||
populateSpecialitySelect(newGroupSpecialitySelect, items);
|
||
populateSpecialitySelect(editGroupSpecialitySelect, items);
|
||
populateProfileSelect(newGroupProfileSelect, newGroupSpecialitySelect.value);
|
||
populateProfileSelect(editGroupProfileSelect, editGroupSpecialitySelect.value);
|
||
}
|
||
|
||
function populateSpecialitySelect(select, items) {
|
||
const currentVal = select.value;
|
||
select.innerHTML = '<option value="">Выберите специальность</option>' +
|
||
items.map(speciality => `<option value="${speciality.id}">${escapeHtml(specialityLabel(speciality.id))}</option>`).join('');
|
||
if (currentVal && items.find(speciality => speciality.id == currentVal)) {
|
||
select.value = currentVal;
|
||
}
|
||
syncSelects(select);
|
||
}
|
||
|
||
function populateProfileSelect(select, specialtyId, selectedProfileId = null) {
|
||
const profiles = profilesBySpecialty.get(String(specialtyId)) || [];
|
||
select.innerHTML = profiles.length
|
||
? '<option value="">Выберите профиль</option>' + profiles.map(profile =>
|
||
`<option value="${profile.id}">${escapeHtml(profile.name)}</option>`
|
||
).join('')
|
||
: '<option value="">Нет профилей</option>';
|
||
if (selectedProfileId && profiles.some(profile => String(profile.id) === String(selectedProfileId))) {
|
||
select.value = selectedProfileId;
|
||
}
|
||
syncSelects(select);
|
||
}
|
||
|
||
function populateYearSelect() {
|
||
manageCalendarYearSelect.innerHTML = '<option value="">Выберите учебный год</option>' +
|
||
academicYears.map(year => `<option value="${year.id}">${escapeHtml(year.title)}</option>`).join('');
|
||
if (academicYears.length) {
|
||
manageCalendarYearSelect.value = String(academicYears[0].id);
|
||
}
|
||
syncSelects(manageCalendarYearSelect);
|
||
}
|
||
|
||
function populateCalendarSelect() {
|
||
const groupId = manageCalendarGroupIdInput.value;
|
||
const group = allGroups.find(g => g.id == groupId);
|
||
const yearId = manageCalendarYearSelect.value;
|
||
const matching = group && yearId
|
||
? calendars.filter(calendar =>
|
||
String(calendar.academicYearId) === String(yearId)
|
||
&& String(calendar.specialtyId) === String(group.specialtyId || group.specialityCode)
|
||
&& String(calendar.specialtyProfileId) === String(group.specialtyProfileId)
|
||
&& String(calendar.studyFormId) === String(group.educationFormId)
|
||
)
|
||
: [];
|
||
manageCalendarSelect.innerHTML = matching.length
|
||
? '<option value="">Выберите график</option>' + matching.map(calendar =>
|
||
`<option value="${calendar.id}">${escapeHtml(calendar.title)}</option>`
|
||
).join('')
|
||
: '<option value="">Нет подходящих графиков</option>';
|
||
syncSelects(manageCalendarSelect);
|
||
}
|
||
|
||
async function createGroup(event) {
|
||
event.preventDefault();
|
||
hideAlert('create-group-alert');
|
||
const payload = readGroupPayload('new');
|
||
if (!payload) return;
|
||
|
||
try {
|
||
const data = await api.post('/api/groups', payload);
|
||
showAlert('create-group-alert', `Группа "${escapeHtml(data.name || payload.name)}" создана`, 'success');
|
||
createGroupForm.reset();
|
||
syncSelects(newGroupEfSelect, newGroupDepartmentSelect, newGroupSpecialitySelect, newGroupProfileSelect);
|
||
await loadGroups();
|
||
} catch (error) {
|
||
showAlert('create-group-alert', error.message || 'Ошибка создания', 'error');
|
||
}
|
||
}
|
||
|
||
async function updateGroup(event) {
|
||
event.preventDefault();
|
||
hideAlert('edit-group-alert');
|
||
const id = document.getElementById('edit-group-id').value;
|
||
const payload = readGroupPayload('edit');
|
||
if (!payload) return;
|
||
|
||
try {
|
||
const data = await api.put('/api/groups/' + id, payload);
|
||
modalEditGroup.classList.remove('open');
|
||
showAlert('create-group-alert', `Группа "${escapeHtml(data.name || payload.name)}" обновлена`, 'success');
|
||
await loadGroups();
|
||
} catch (error) {
|
||
showAlert('edit-group-alert', error.message || 'Ошибка обновления', 'error');
|
||
}
|
||
}
|
||
|
||
function readGroupPayload(prefix) {
|
||
const isEdit = prefix === 'edit';
|
||
const alertId = isEdit ? 'edit-group-alert' : 'create-group-alert';
|
||
const name = document.getElementById(`${prefix}-group-name`).value.trim();
|
||
const groupSize = document.getElementById(`${prefix}-group-size`).value;
|
||
const educationFormId = (isEdit ? editGroupEfSelect : newGroupEfSelect).value;
|
||
const departmentId = (isEdit ? editGroupDepartmentSelect : newGroupDepartmentSelect).value;
|
||
const yearStartStudy = document.getElementById(`${prefix}-group-yearStartStudy`).value;
|
||
const specialtyId = (isEdit ? editGroupSpecialitySelect : newGroupSpecialitySelect).value;
|
||
const specialtyProfileId = (isEdit ? editGroupProfileSelect : newGroupProfileSelect).value;
|
||
|
||
if (!name) { showAlert(alertId, 'Введите название группы', 'error'); return null; }
|
||
if (!groupSize) { showAlert(alertId, 'Введите размер группы', 'error'); return null; }
|
||
if (!educationFormId) { showAlert(alertId, 'Выберите форму обучения', 'error'); return null; }
|
||
if (!departmentId) { showAlert(alertId, 'Выберите кафедру', 'error'); return null; }
|
||
if (!yearStartStudy) { showAlert(alertId, 'Введите год начала обучения', 'error'); return null; }
|
||
if (!specialtyId) { showAlert(alertId, 'Выберите специальность', 'error'); return null; }
|
||
if (!specialtyProfileId) { showAlert(alertId, 'Выберите профиль обучения', 'error'); return null; }
|
||
|
||
return {
|
||
name,
|
||
groupSize: Number(groupSize),
|
||
educationFormId: Number(educationFormId),
|
||
departmentId: Number(departmentId),
|
||
yearStartStudy: Number(yearStartStudy),
|
||
specialtyId: Number(specialtyId),
|
||
specialtyProfileId: Number(specialtyProfileId)
|
||
};
|
||
}
|
||
|
||
async function handleGroupTableClick(event) {
|
||
const btnDelete = event.target.closest('.btn-delete');
|
||
const btnEdit = event.target.closest('.btn-edit-group');
|
||
const btnRestore = event.target.closest('.btn-restore-group');
|
||
const btnSubgroups = event.target.closest('.btn-manage-subgroups');
|
||
const btnCalendar = event.target.closest('.btn-manage-calendar');
|
||
|
||
if (btnDelete) {
|
||
if (!confirm('Архивировать группу? История расписания сохранится.')) return;
|
||
try {
|
||
await api.delete('/api/groups/' + btnDelete.dataset.id);
|
||
await loadGroups();
|
||
} catch (error) {
|
||
alert(error.message || 'Ошибка архивирования');
|
||
}
|
||
}
|
||
|
||
if (btnRestore) {
|
||
try {
|
||
await api.post('/api/groups/' + btnRestore.dataset.id + '/restore', {});
|
||
await loadGroups();
|
||
} catch (error) {
|
||
alert(error.message || 'Ошибка восстановления');
|
||
}
|
||
}
|
||
|
||
if (btnEdit) {
|
||
openEditGroupModal(btnEdit.dataset.id);
|
||
}
|
||
|
||
if (btnSubgroups) {
|
||
const groupId = btnSubgroups.dataset.id;
|
||
const group = allGroups.find(g => g.id == groupId);
|
||
if (group) openSubgroupsModal(group);
|
||
}
|
||
|
||
if (btnCalendar) {
|
||
const groupId = btnCalendar.dataset.id;
|
||
const group = allGroups.find(g => g.id == groupId);
|
||
if (group) openCalendarModal(group);
|
||
}
|
||
}
|
||
|
||
function openEditGroupModal(id) {
|
||
const group = allGroups.find(item => item.id == id);
|
||
if (!group) {
|
||
alert('Группа не найдена');
|
||
return;
|
||
}
|
||
|
||
document.getElementById('edit-group-id').value = group.id;
|
||
document.getElementById('edit-group-name').value = group.name || '';
|
||
document.getElementById('edit-group-size').value = group.groupSize || '';
|
||
editGroupEfSelect.value = group.educationFormId || '';
|
||
editGroupDepartmentSelect.value = group.departmentId || '';
|
||
document.getElementById('edit-group-yearStartStudy').value = group.yearStartStudy || '';
|
||
editGroupSpecialitySelect.value = group.specialtyId || group.specialityCode || '';
|
||
populateProfileSelect(editGroupProfileSelect, editGroupSpecialitySelect.value, group.specialtyProfileId);
|
||
syncSelects(editGroupEfSelect, editGroupDepartmentSelect, editGroupSpecialitySelect, editGroupProfileSelect);
|
||
hideAlert('edit-group-alert');
|
||
modalEditGroup.classList.add('open');
|
||
}
|
||
|
||
// --- Управление подгруппами ---
|
||
function openSubgroupsModal(group) {
|
||
subgroupsGroupContext.textContent = `Группа: ${group.name} (${group.groupSize} чел.)`;
|
||
manageSubgroupGroupIdInput.value = group.id;
|
||
resetSubgroupForm();
|
||
modalManageSubgroups.classList.add('open');
|
||
renderSubgroupsList(group.id);
|
||
}
|
||
|
||
function renderSubgroupsList(groupId) {
|
||
const list = subgroups.filter(sub => String(sub.groupId) === String(groupId));
|
||
if (!list.length) {
|
||
manageSubgroupsTbody.innerHTML = '<tr><td colspan="3" class="loading-row">Подгруппы не созданы</td></tr>';
|
||
return;
|
||
}
|
||
manageSubgroupsTbody.innerHTML = list.map(sub => `
|
||
<tr>
|
||
<td>${escapeHtml(sub.name)}</td>
|
||
<td>${escapeHtml(String(sub.studentCapacity ?? '-'))} чел.</td>
|
||
<td>
|
||
<button class="btn-edit-classroom btn-edit-subgroup" data-id="${sub.id}">Изменить</button>
|
||
<button class="btn-delete btn-delete-subgroup" data-id="${sub.id}">Удалить</button>
|
||
</td>
|
||
</tr>
|
||
`).join('');
|
||
}
|
||
|
||
function resetSubgroupForm() {
|
||
manageSubgroupIdInput.value = '';
|
||
manageSubgroupNameInput.value = '';
|
||
manageSubgroupCapacityInput.value = '';
|
||
subgroupFormTitle.textContent = 'Создание подгруппы';
|
||
btnSaveSubgroup.textContent = 'Создать';
|
||
btnCancelSubgroupEdit.style.display = 'none';
|
||
hideAlert('manage-subgroup-alert');
|
||
}
|
||
|
||
async function saveSubgroup(event) {
|
||
event.preventDefault();
|
||
hideAlert('manage-subgroup-alert');
|
||
const groupId = manageSubgroupGroupIdInput.value;
|
||
const subgroupId = manageSubgroupIdInput.value;
|
||
const name = manageSubgroupNameInput.value.trim();
|
||
const capacity = manageSubgroupCapacityInput.value;
|
||
|
||
if (!name || !capacity) {
|
||
showAlert('manage-subgroup-alert', 'Заполните все поля', 'error');
|
||
return;
|
||
}
|
||
|
||
const payload = {
|
||
name,
|
||
studentCapacity: Number(capacity)
|
||
};
|
||
|
||
try {
|
||
const endpoint = `/api/groups/${groupId}/subgroups${subgroupId ? `/${subgroupId}` : ''}`;
|
||
const saved = subgroupId
|
||
? await api.put(endpoint, payload)
|
||
: await api.post(endpoint, payload);
|
||
showAlert('manage-subgroup-alert', `Подгруппа "${escapeHtml(saved.name)}" сохранена`, 'success');
|
||
resetSubgroupForm();
|
||
await loadSubgroups();
|
||
renderSubgroupsList(groupId);
|
||
} catch (error) {
|
||
showAlert('manage-subgroup-alert', error.message || 'Ошибка сохранения подгруппы', 'error');
|
||
}
|
||
}
|
||
|
||
async function handleSubgroupTableClick(event) {
|
||
const editButton = event.target.closest('.btn-edit-subgroup');
|
||
const deleteButton = event.target.closest('.btn-delete-subgroup');
|
||
const groupId = manageSubgroupGroupIdInput.value;
|
||
|
||
if (editButton) {
|
||
const subgroup = subgroups.find(item => item.id == editButton.dataset.id);
|
||
if (!subgroup) return;
|
||
|
||
manageSubgroupIdInput.value = subgroup.id;
|
||
manageSubgroupNameInput.value = subgroup.name || '';
|
||
manageSubgroupCapacityInput.value = subgroup.studentCapacity ?? '';
|
||
subgroupFormTitle.textContent = 'Редактирование подгруппы';
|
||
btnSaveSubgroup.textContent = 'Сохранить';
|
||
btnCancelSubgroupEdit.style.display = 'inline-block';
|
||
hideAlert('manage-subgroup-alert');
|
||
return;
|
||
}
|
||
|
||
if (deleteButton) {
|
||
if (!confirm('Удалить подгруппу?')) return;
|
||
try {
|
||
await api.delete(`/api/groups/${groupId}/subgroups/${deleteButton.dataset.id}`);
|
||
showAlert('manage-subgroup-alert', 'Подгруппа удалена', 'success');
|
||
resetSubgroupForm();
|
||
await loadSubgroups();
|
||
renderSubgroupsList(groupId);
|
||
} catch (error) {
|
||
showAlert('manage-subgroup-alert', error.message || 'Ошибка удаления подгруппы', 'error');
|
||
}
|
||
}
|
||
}
|
||
|
||
// --- Управление календарными графиками ---
|
||
async function openCalendarModal(group) {
|
||
calendarGroupContext.textContent = `Группа: ${group.name} (${specialityLabel(group.specialtyId || group.specialityCode)})`;
|
||
manageCalendarGroupIdInput.value = group.id;
|
||
hideAlert('manage-calendar-alert');
|
||
populateCalendarSelect();
|
||
modalManageCalendar.classList.add('open');
|
||
await loadAssignmentsList(group.id);
|
||
}
|
||
|
||
async function loadAssignmentsList(groupId) {
|
||
manageCalendarTbody.innerHTML = '<tr><td colspan="3" class="loading-row">Загрузка...</td></tr>';
|
||
try {
|
||
currentAssignments = await api.get(`/api/groups/${groupId}/calendar-assignments`);
|
||
renderAssignmentsList();
|
||
} catch (error) {
|
||
manageCalendarTbody.innerHTML = `<tr><td colspan="3" class="loading-row">Ошибка: ${escapeHtml(error.message)}</td></tr>`;
|
||
}
|
||
}
|
||
|
||
function renderAssignmentsList() {
|
||
if (!currentAssignments.length) {
|
||
manageCalendarTbody.innerHTML = '<tr><td colspan="3" class="loading-row">Календарные графики не назначены</td></tr>';
|
||
return;
|
||
}
|
||
manageCalendarTbody.innerHTML = currentAssignments.map(assignment => `
|
||
<tr>
|
||
<td>${escapeHtml(assignment.academicYearTitle)}</td>
|
||
<td>${escapeHtml(assignment.calendarTitle)}</td>
|
||
<td>
|
||
<button class="btn-delete btn-delete-assignment" data-id="${assignment.id}">Удалить</button>
|
||
</td>
|
||
</tr>
|
||
`).join('');
|
||
}
|
||
|
||
async function saveAssignment(event) {
|
||
event.preventDefault();
|
||
hideAlert('manage-calendar-alert');
|
||
const groupId = manageCalendarGroupIdInput.value;
|
||
const academicYearId = manageCalendarYearSelect.value;
|
||
const calendarId = manageCalendarSelect.value;
|
||
|
||
if (!academicYearId || !calendarId) {
|
||
showAlert('manage-calendar-alert', 'Выберите учебный год и график', 'error');
|
||
return;
|
||
}
|
||
|
||
try {
|
||
await api.put(`/api/groups/${groupId}/calendar-assignments`, {
|
||
academicYearId: Number(academicYearId),
|
||
calendarId: Number(calendarId)
|
||
});
|
||
showAlert('manage-calendar-alert', 'Календарный график назначен', 'success');
|
||
await loadAssignmentsList(groupId);
|
||
} catch (error) {
|
||
showAlert('manage-calendar-alert', error.message || 'Ошибка назначения графика', 'error');
|
||
}
|
||
}
|
||
|
||
manageCalendarTbody.addEventListener('click', async (event) => {
|
||
const deleteButton = event.target.closest('.btn-delete-assignment');
|
||
if (!deleteButton) return;
|
||
if (!confirm('Удалить назначение графика?')) return;
|
||
const groupId = manageCalendarGroupIdInput.value;
|
||
|
||
try {
|
||
await api.delete(`/api/groups/${groupId}/calendar-assignments/${deleteButton.dataset.id}`);
|
||
showAlert('manage-calendar-alert', 'Назначение удалено', 'success');
|
||
await loadAssignmentsList(groupId);
|
||
} catch (error) {
|
||
showAlert('manage-calendar-alert', error.message || 'Ошибка удаления назначения', 'error');
|
||
}
|
||
});
|
||
|
||
|
||
// --- Вспомогательные функции ---
|
||
function departmentLabel(departmentId) {
|
||
const department = departments.find(item => item.id == departmentId);
|
||
if (!department) return departmentId || '-';
|
||
return department.departmentName || department.name || departmentId;
|
||
}
|
||
|
||
function specialityLabel(specialityId) {
|
||
const speciality = specialties.find(item => item.id == specialityId);
|
||
if (!speciality) return specialityId || '-';
|
||
const code = speciality.specialityCode || speciality.specialtyCode || speciality.specialty_code || speciality.id;
|
||
const name = speciality.specialityName || speciality.name || '';
|
||
return name ? `${code} — ${name}` : code;
|
||
}
|
||
|
||
function statusLabel(group) {
|
||
if (group.status === 'ARCHIVED') return 'Архив';
|
||
return group.studyStateName || (group.active ? 'Активна' : 'Неактивна');
|
||
}
|
||
|
||
function statusBadgeClass(group) {
|
||
if (group.status === 'ARCHIVED' || group.studyState === 'GRADUATED' || group.studyState === 'INACTIVE') {
|
||
return 'badge-unavailable';
|
||
}
|
||
if (group.studyState === 'NOT_STARTED') {
|
||
return 'badge-ef';
|
||
}
|
||
return 'badge-available';
|
||
}
|
||
|
||
function syncSelects(...selects) {
|
||
selects.filter(Boolean).forEach(select => {
|
||
select.dispatchEvent(new Event('change', { bubbles: true }));
|
||
});
|
||
}
|
||
}
|