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 [];
}
}
const FILTER_EF_EMPTY_TEXT = 'Выберите форму обучения';
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 filterEfBox = document.getElementById('filter-ef-box');
const filterEfMenu = document.getElementById('filter-ef-menu');
const filterEfText = document.getElementById('filter-ef-text');
const filterEfCheckboxes = document.getElementById('filter-ef-checkboxes');
// Модалка группы
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 manageSubgroupCountSelect = document.getElementById('manage-subgroup-count');
const manageSubgroupCapacities = document.getElementById('manage-subgroup-capacities');
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 = [];
let activeSubgroupGroup = null;
bindEvents();
await loadInitialData();
function bindEvents() {
initFilterEfDropdown();
filterEfCheckboxes.addEventListener('change', handleFilterEfChange);
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);
manageSubgroupCountSelect.addEventListener('change', () => renderSubgroupCapacityFields(Number(manageSubgroupCountSelect.value)));
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');
});
}
function initFilterEfDropdown() {
if (!filterEfBox || !filterEfMenu) return;
const toggleDropdown = (event) => {
event.stopPropagation();
const isOpen = filterEfMenu.classList.contains('open');
document.querySelectorAll('.dropdown-menu').forEach(menu => menu.classList.remove('open'));
document.querySelectorAll('.select-box').forEach(box => box.classList.remove('active'));
if (!isOpen) {
filterEfMenu.classList.add('open');
filterEfBox.classList.add('active');
filterEfBox.setAttribute('aria-expanded', 'true');
} else {
filterEfBox.setAttribute('aria-expanded', 'false');
}
};
filterEfBox.addEventListener('click', toggleDropdown);
filterEfBox.addEventListener('keydown', (event) => {
if (event.key === 'Enter' || event.key === ' ') {
event.preventDefault();
toggleDropdown(event);
}
});
filterEfMenu.addEventListener('click', (event) => {
event.stopPropagation();
});
}
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 = `
| Ошибка загрузки данных: ${escapeHtml(error.message)} |
`;
}
}
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 = `| Ошибка загрузки: ${escapeHtml(error.message)} |
`;
}
}
async function loadSubgroups() {
try {
subgroups = await api.get('/api/subgroups');
} catch (error) {
console.error("Failed to load subgroups", error);
}
}
function applyGroupFilter() {
const selectedIds = selectedFilterEducationFormIds();
const filtered = selectedIds.length
? allGroups.filter(group => selectedIds.includes(String(group.educationFormId)))
: allGroups;
renderGroups(filtered);
}
function handleFilterEfChange(event) {
const changedCheckbox = event.target.closest('input[type="checkbox"]');
const allCheckbox = filterEfCheckboxes.querySelector('input[value="all"]');
if (changedCheckbox?.value === 'all' && changedCheckbox.checked) {
filterEfCheckboxes.querySelectorAll('input[type="checkbox"]:not([value="all"])').forEach(checkbox => {
checkbox.checked = false;
});
} else if (changedCheckbox?.value !== 'all' && changedCheckbox?.checked) {
allCheckbox.checked = false;
}
if (!selectedFilterEducationFormIds().length) {
allCheckbox.checked = true;
}
updateFilterEfText();
applyGroupFilter();
}
function selectedFilterEducationFormIds() {
const allCheckbox = filterEfCheckboxes.querySelector('input[value="all"]');
if (allCheckbox?.checked) return [];
return Array.from(filterEfCheckboxes.querySelectorAll('input[type="checkbox"]:checked:not([value="all"])'))
.map(checkbox => String(checkbox.value));
}
function updateFilterEfText() {
const allCheckbox = filterEfCheckboxes.querySelector('input[value="all"]');
const selectedIds = selectedFilterEducationFormIds();
if (allCheckbox?.checked) {
filterEfText.textContent = 'Все';
return;
}
if (!selectedIds.length) {
filterEfText.textContent = FILTER_EF_EMPTY_TEXT;
return;
}
if (selectedIds.length === 1) {
const form = educationForms.find(item => String(item.id) === selectedIds[0]);
filterEfText.textContent = form?.name || FILTER_EF_EMPTY_TEXT;
return;
}
filterEfText.textContent = `Выбрано: ${selectedIds.length}`;
}
function renderGroups(groups) {
if (!groups || !groups.length) {
groupsTbody.innerHTML = '| Нет групп |
';
return;
}
groupsTbody.innerHTML = groups.map(group => `
| ${group.id} |
${escapeHtml(group.name)} |
${escapeHtml(String(group.groupSize))} |
${escapeHtml(group.educationFormName)} |
${escapeHtml(departmentLabel(group.departmentId))} |
${group.course || '-'} |
${escapeHtml(specialityLabel(group.specialtyId || group.specialityCode))} |
${escapeHtml(group.specialtyProfileName || '-')} |
${escapeHtml(statusLabel(group))} |
${group.status === 'ARCHIVED'
? ``
: ``}
|
`).join('');
}
function populateEfSelects(forms) {
populateEfSelect(newGroupEfSelect, forms);
populateEfSelect(editGroupEfSelect, forms);
renderFilterEfCheckboxes(forms);
}
function renderFilterEfCheckboxes(forms) {
const selectedIds = new Set(selectedFilterEducationFormIds());
const allChecked = !selectedIds.size;
filterEfCheckboxes.innerHTML = `
${forms.map(form => {
const id = String(form.id);
return `
`;
}).join('')}
`;
updateFilterEfText();
}
function populateEfSelect(select, forms) {
const currentVal = select.value;
select.innerHTML = forms.map(form => ``).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 = '' +
items.map(department => {
const name = department.departmentName || department.name || 'Без названия';
const code = department.departmentCode || department.code || department.id;
return ``;
}).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 = '' +
items.map(speciality => ``).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
? '' + profiles.map(profile =>
``
).join('')
: '';
if (selectedProfileId && profiles.some(profile => String(profile.id) === String(selectedProfileId))) {
select.value = selectedProfileId;
}
syncSelects(select);
}
function populateYearSelect() {
manageCalendarYearSelect.innerHTML = '' +
academicYears.map(year => ``).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
? '' + matching.map(calendar =>
``
).join('')
: '';
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-archive-group');
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) {
activeSubgroupGroup = 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 = '| Подгруппы не созданы |
';
return;
}
const canDeleteSingleSubgroup = list.length === 1;
const deleteTitle = canDeleteSingleSubgroup
? 'Удалить подгруппу'
: 'Чтобы изменить количество подгрупп, выберите нужный режим выше и нажмите «Применить»';
manageSubgroupsTbody.innerHTML = list.map(sub => `
| ${escapeHtml(sub.name)} |
${escapeHtml(String(sub.studentCapacity ?? '-'))} чел. |
|
`).join('');
}
function resetSubgroupForm() {
manageSubgroupIdInput.value = '';
const current = currentGroupSubgroups(manageSubgroupGroupIdInput.value);
const count = [2, 3].includes(current.length) ? current.length : 0;
manageSubgroupCountSelect.value = String(count);
syncSelects(manageSubgroupCountSelect);
renderSubgroupCapacityFields(count, current.map(item => item.studentCapacity));
subgroupFormTitle.textContent = 'Настройка подгрупп';
btnSaveSubgroup.textContent = 'Применить';
btnCancelSubgroupEdit.style.display = 'none';
hideAlert('manage-subgroup-alert');
}
function currentGroupSubgroups(groupId) {
return subgroups
.filter(sub => String(sub.groupId) === String(groupId))
.sort((left, right) => naturalSubgroupOrder(left.name).localeCompare(naturalSubgroupOrder(right.name), 'ru'));
}
function naturalSubgroupOrder(name) {
const number = String(name || '').match(/\d+/)?.[0];
return number ? String(number).padStart(3, '0') : String(name || '');
}
function renderSubgroupCapacityFields(count, capacities = []) {
manageSubgroupCapacities.className = `subgroup-capacity-fields subgroup-capacity-fields-${count}`;
if (!count) {
manageSubgroupCapacities.innerHTML = '';
return;
}
const groupSize = Number(activeSubgroupGroup?.groupSize || 0);
const suggested = suggestedSubgroupCapacities(groupSize, count);
manageSubgroupCapacities.innerHTML = Array.from({ length: count }, (_, index) => {
const value = capacities[index] ?? suggested[index] ?? '';
return `
`;
}).join('');
}
function suggestedSubgroupCapacities(groupSize, count) {
if (!groupSize || !count) return [];
const base = Math.floor(groupSize / count);
const remainder = groupSize % count;
return Array.from({ length: count }, (_, index) => base + (index < remainder ? 1 : 0));
}
async function saveSubgroup(event) {
event.preventDefault();
hideAlert('manage-subgroup-alert');
const groupId = manageSubgroupGroupIdInput.value;
const group = activeSubgroupGroup || allGroups.find(item => String(item.id) === String(groupId));
const count = Number(manageSubgroupCountSelect.value);
const existingSubgroups = currentGroupSubgroups(groupId);
if (!group) {
showAlert('manage-subgroup-alert', 'Группа не найдена', 'error');
return;
}
if (count === 0) {
await applyNoSubgroups(groupId, existingSubgroups);
return;
}
const capacities = Array.from(manageSubgroupCapacities.querySelectorAll('.manage-subgroup-capacity'))
.map(input => Number(input.value));
const validationError = validateSubgroupCapacities(capacities, Number(group.groupSize), count);
if (validationError) {
showAlert('manage-subgroup-alert', validationError, 'error');
return;
}
try {
await syncSubgroups(groupId, existingSubgroups, capacities);
showAlert('manage-subgroup-alert', 'Подгруппы сохранены', 'success');
await loadSubgroups();
resetSubgroupForm();
renderSubgroupsList(groupId);
} catch (error) {
showAlert('manage-subgroup-alert', error.message || 'Ошибка сохранения подгруппы', 'error');
}
}
function validateSubgroupCapacities(capacities, groupSize, count) {
if (capacities.length !== count || capacities.some(value => !Number.isInteger(value) || value <= 0)) {
return 'Укажите положительную численность для каждой подгруппы';
}
const total = capacities.reduce((sum, value) => sum + value, 0);
if (total !== groupSize) {
return `Сумма численностей подгрупп должна быть равна численности группы (${groupSize} чел.)`;
}
return null;
}
async function applyNoSubgroups(groupId, existingSubgroups) {
try {
for (const subgroup of existingSubgroups) {
await api.delete(`/api/groups/${groupId}/subgroups/${subgroup.id}`);
}
showAlert('manage-subgroup-alert', 'Деление на подгруппы отключено', 'success');
await loadSubgroups();
resetSubgroupForm();
renderSubgroupsList(groupId);
} catch (error) {
showAlert('manage-subgroup-alert', error.message || 'Ошибка отключения подгрупп', 'error');
}
}
async function syncSubgroups(groupId, existingSubgroups, capacities) {
const targetCount = capacities.length;
const extraSubgroups = existingSubgroups.slice(targetCount);
for (const subgroup of extraSubgroups) {
await api.delete(`/api/groups/${groupId}/subgroups/${subgroup.id}`);
}
const updates = [];
const creates = [];
for (let index = 0; index < targetCount; index += 1) {
const payload = {
name: `Подгруппа ${index + 1}`,
studentCapacity: capacities[index]
};
const existing = existingSubgroups[index];
if (existing) {
updates.push({ existing, payload });
} else {
creates.push(payload);
}
}
updates.sort((left, right) => {
const leftIncreases = left.payload.studentCapacity > Number(left.existing.studentCapacity || 0);
const rightIncreases = right.payload.studentCapacity > Number(right.existing.studentCapacity || 0);
return Number(leftIncreases) - Number(rightIncreases);
});
for (const update of updates) {
await api.put(`/api/groups/${groupId}/subgroups/${update.existing.id}`, update.payload);
}
for (const payload of creates) {
await api.post(`/api/groups/${groupId}/subgroups`, payload);
}
}
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;
resetSubgroupForm();
const inputIndex = currentGroupSubgroups(groupId).findIndex(item => item.id === subgroup.id);
const input = manageSubgroupCapacities.querySelectorAll('.manage-subgroup-capacity')[inputIndex];
input?.focus();
hideAlert('manage-subgroup-alert');
return;
}
if (deleteButton) {
if (deleteButton.disabled) {
showAlert('manage-subgroup-alert', 'Чтобы изменить количество подгрупп, выберите нужный режим выше и нажмите «Применить»', 'error');
return;
}
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 = '| Загрузка... |
';
try {
currentAssignments = await api.get(`/api/groups/${groupId}/calendar-assignments`);
renderAssignmentsList();
} catch (error) {
manageCalendarTbody.innerHTML = `| Ошибка: ${escapeHtml(error.message)} |
`;
}
}
function renderAssignmentsList() {
if (!currentAssignments.length) {
manageCalendarTbody.innerHTML = '| Календарные графики не назначены |
';
return;
}
manageCalendarTbody.innerHTML = currentAssignments.map(assignment => `
| ${escapeHtml(assignment.academicYearTitle)} |
${escapeHtml(assignment.calendarTitle)} |
${renderAssignmentSubjects(assignment.subjects || [])} |
|
`).join('');
}
function renderAssignmentSubjects(subjects) {
if (!subjects.length) {
return 'Не привязаны';
}
const grouped = subjects.reduce((acc, subject) => {
const key = String(subject.semesterNumber || '-');
if (!acc.has(key)) acc.set(key, []);
acc.get(key).push(subject);
return acc;
}, new Map());
return `${Array.from(grouped.entries())
.sort(([a], [b]) => Number(a) - Number(b))
.map(([semesterNumber, items]) => `
${escapeHtml(semesterNumber)} семестр
${items.map(subject => escapeHtml(subjectLabel(subject))).join(', ')}
`).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 subjectLabel(subject) {
const code = subject.subjectCode ? `${subject.subjectCode} · ` : '';
return `${code}${subject.subjectName || subject.subjectId || 'Без названия'}`;
}
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 }));
});
}
}