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 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 calendarForm = document.getElementById('group-calendar-form');
const calendarGroupSelect = document.getElementById('calendar-group');
const calendarYearSelect = document.getElementById('calendar-year');
const calendarSelect = document.getElementById('calendar-id');
const calendarAssignmentsTbody = document.getElementById('group-calendar-tbody');
const subgroupForm = document.getElementById('subgroup-form');
const subgroupIdInput = document.getElementById('subgroup-id');
const subgroupGroupSelect = document.getElementById('subgroup-group');
const subgroupNameInput = document.getElementById('subgroup-name');
const subgroupCapacityInput = document.getElementById('subgroup-capacity');
const subgroupSubmitButton = document.getElementById('subgroup-submit');
const subgroupResetButton = document.getElementById('subgroup-reset');
const subgroupsTbody = document.getElementById('subgroups-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));
calendarGroupSelect.addEventListener('change', () => {
populateCalendarSelect();
loadAssignments();
});
calendarYearSelect.addEventListener('change', populateCalendarSelect);
createGroupForm.addEventListener('submit', createGroup);
editGroupForm.addEventListener('submit', updateGroup);
calendarForm.addEventListener('submit', saveAssignment);
subgroupForm.addEventListener('submit', saveSubgroup);
subgroupGroupSelect.addEventListener('change', renderSubgroups);
subgroupResetButton.addEventListener('click', resetSubgroupForm);
groupsTbody.addEventListener('click', handleGroupTableClick);
subgroupsTbody.addEventListener('click', handleSubgroupTableClick);
calendarAssignmentsTbody.addEventListener('click', handleAssignmentTableClick);
modalEditGroupClose.addEventListener('click', () => modalEditGroup.classList.remove('open'));
modalEditGroup.addEventListener('click', (event) => {
if (event.target === modalEditGroup) {
modalEditGroup.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();
populateCalendarSelect();
await loadGroups();
await loadSubgroups();
} catch (error) {
groupsTbody.innerHTML = `
| Ошибка загрузки данных: ${escapeHtml(error.message)} |
`;
subgroupsTbody.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');
applyGroupFilter();
populateGroupSelect();
populateSubgroupGroupSelect();
await loadAssignments();
} catch (error) {
groupsTbody.innerHTML = `| Ошибка загрузки: ${escapeHtml(error.message)} |
`;
}
}
async function loadSubgroups() {
try {
subgroups = await api.get('/api/subgroups');
renderSubgroups();
} catch (error) {
subgroupsTbody.innerHTML = `| Ошибка загрузки: ${escapeHtml(error.message)} |
`;
}
}
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 = '| Нет групп |
';
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 || '-')} |
|
`).join('');
}
function populateEfSelects(forms) {
populateEfSelect(newGroupEfSelect, forms);
populateEfSelect(editGroupEfSelect, forms);
const currentFilter = filterEfSelect.value;
filterEfSelect.innerHTML = '' +
forms.map(form => ``).join('');
if (currentFilter) filterEfSelect.value = currentFilter;
syncSelects(filterEfSelect);
}
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() {
calendarYearSelect.innerHTML = '' +
academicYears.map(year => ``).join('');
if (!calendarYearSelect.value && academicYears.length) {
calendarYearSelect.value = String(academicYears[0].id);
}
syncSelects(calendarYearSelect);
}
function populateGroupSelect() {
const current = calendarGroupSelect.value;
calendarGroupSelect.innerHTML = '' +
allGroups.map(group => ``).join('');
if (current && allGroups.some(group => String(group.id) === String(current))) {
calendarGroupSelect.value = current;
} else if (allGroups.length) {
calendarGroupSelect.value = String(allGroups[0].id);
}
syncSelects(calendarGroupSelect);
}
function populateSubgroupGroupSelect() {
const current = subgroupGroupSelect.value;
subgroupGroupSelect.innerHTML = '' +
allGroups.map(group => ``).join('');
if (current && allGroups.some(group => String(group.id) === String(current))) {
subgroupGroupSelect.value = current;
} else if (allGroups.length) {
subgroupGroupSelect.value = String(allGroups[0].id);
}
syncSelects(subgroupGroupSelect);
}
function populateCalendarSelect() {
const group = selectedCalendarGroup();
const yearId = calendarYearSelect.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)
)
: [];
calendarSelect.innerHTML = matching.length
? '' + matching.map(calendar =>
``
).join('')
: '';
syncSelects(calendarSelect);
}
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();
await loadSubgroups();
} 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();
await loadSubgroups();
} 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');
if (btnDelete) {
if (!confirm('Удалить группу?')) return;
try {
await api.delete('/api/groups/' + btnDelete.dataset.id);
await loadGroups();
await loadSubgroups();
} catch (error) {
alert(error.message || 'Ошибка удаления');
}
}
if (btnEdit) {
openEditGroupModal(btnEdit.dataset.id);
}
}
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');
}
async function saveSubgroup(event) {
event.preventDefault();
hideAlert('subgroup-alert');
const groupId = subgroupGroupSelect.value;
const name = subgroupNameInput.value.trim();
const capacity = subgroupCapacityInput.value;
const subgroupId = subgroupIdInput.value;
if (!groupId) { showAlert('subgroup-alert', 'Выберите группу', 'error'); return; }
if (!name) { showAlert('subgroup-alert', 'Введите название подгруппы', 'error'); return; }
if (capacity && Number(capacity) < 0) { showAlert('subgroup-alert', 'Численность не может быть отрицательной', 'error'); return; }
const payload = {
name,
studentCapacity: capacity ? Number(capacity) : null
};
try {
const endpoint = `/api/groups/${groupId}/subgroups${subgroupId ? `/${subgroupId}` : ''}`;
const saved = subgroupId
? await api.put(endpoint, payload)
: await api.post(endpoint, payload);
showAlert('subgroup-alert', `Подгруппа "${escapeHtml(saved.name)}" сохранена`, 'success');
resetSubgroupForm();
await loadSubgroups();
} catch (error) {
showAlert('subgroup-alert', error.message || 'Ошибка сохранения подгруппы', 'error');
}
}
function renderSubgroups() {
const groupId = subgroupGroupSelect.value;
const visibleSubgroups = groupId
? subgroups.filter(subgroup => String(subgroup.groupId) === String(groupId))
: subgroups;
if (!visibleSubgroups.length) {
subgroupsTbody.innerHTML = '| Подгруппы не созданы |
';
return;
}
subgroupsTbody.innerHTML = visibleSubgroups.map(subgroup => `
| ${escapeHtml(subgroup.groupName || groupName(subgroup.groupId))} |
${escapeHtml(subgroup.name)} |
${escapeHtml(String(subgroup.studentCapacity ?? '-'))} |
|
`).join('');
}
async function handleSubgroupTableClick(event) {
const editButton = event.target.closest('.btn-edit-subgroup');
const deleteButton = event.target.closest('.btn-delete-subgroup');
if (editButton) {
const subgroup = subgroups.find(item => String(item.id) === String(editButton.dataset.id));
if (!subgroup) return;
subgroupIdInput.value = subgroup.id;
subgroupGroupSelect.value = subgroup.groupId;
subgroupGroupSelect.disabled = true;
subgroupNameInput.value = subgroup.name || '';
subgroupCapacityInput.value = subgroup.studentCapacity ?? '';
subgroupSubmitButton.textContent = 'Сохранить подгруппу';
syncSelects(subgroupGroupSelect);
hideAlert('subgroup-alert');
return;
}
if (deleteButton) {
const subgroup = subgroups.find(item => String(item.id) === String(deleteButton.dataset.id));
if (!subgroup || !confirm('Удалить подгруппу?')) return;
try {
await api.delete(`/api/groups/${subgroup.groupId}/subgroups/${subgroup.id}`);
showAlert('subgroup-alert', 'Подгруппа удалена', 'success');
resetSubgroupForm();
await loadSubgroups();
} catch (error) {
showAlert('subgroup-alert', error.message || 'Ошибка удаления подгруппы', 'error');
}
}
}
function resetSubgroupForm() {
subgroupIdInput.value = '';
subgroupNameInput.value = '';
subgroupCapacityInput.value = '';
subgroupGroupSelect.disabled = false;
subgroupSubmitButton.textContent = 'Создать подгруппу';
syncSelects(subgroupGroupSelect);
hideAlert('subgroup-alert');
}
async function saveAssignment(event) {
event.preventDefault();
hideAlert('group-calendar-alert');
const groupId = calendarGroupSelect.value;
const academicYearId = calendarYearSelect.value;
const calendarId = calendarSelect.value;
if (!groupId || !academicYearId || !calendarId) {
showAlert('group-calendar-alert', 'Выберите группу, учебный год и график', 'error');
return;
}
try {
await api.put(`/api/groups/${groupId}/calendar-assignments`, {
academicYearId: Number(academicYearId),
calendarId: Number(calendarId)
});
showAlert('group-calendar-alert', 'Календарный график назначен', 'success');
await loadAssignments();
} catch (error) {
showAlert('group-calendar-alert', error.message || 'Ошибка назначения графика', 'error');
}
}
async function loadAssignments() {
const groupId = calendarGroupSelect.value;
if (!groupId) {
currentAssignments = [];
calendarAssignmentsTbody.innerHTML = '| Выберите группу |
';
return;
}
try {
currentAssignments = await api.get(`/api/groups/${groupId}/calendar-assignments`);
renderAssignments();
} catch (error) {
calendarAssignmentsTbody.innerHTML = `| Ошибка загрузки: ${escapeHtml(error.message)} |
`;
}
}
function renderAssignments() {
if (!currentAssignments.length) {
calendarAssignmentsTbody.innerHTML = '| Графики не назначены |
';
return;
}
calendarAssignmentsTbody.innerHTML = currentAssignments.map(assignment => `
| ${escapeHtml(assignment.academicYearTitle)} |
${escapeHtml(assignment.calendarTitle)} |
|
`).join('');
}
async function handleAssignmentTableClick(event) {
const deleteButton = event.target.closest('.btn-delete-assignment');
if (!deleteButton) return;
if (!confirm('Удалить назначение графика?')) return;
try {
await api.delete(`/api/groups/${calendarGroupSelect.value}/calendar-assignments/${deleteButton.dataset.id}`);
showAlert('group-calendar-alert', 'Назначение удалено', 'success');
await loadAssignments();
} catch (error) {
showAlert('group-calendar-alert', error.message || 'Ошибка удаления назначения', 'error');
}
}
function selectedCalendarGroup() {
return allGroups.find(group => String(group.id) === String(calendarGroupSelect.value));
}
function departmentLabel(departmentId) {
const department = departments.find(item => item.id == departmentId);
if (!department) return departmentId || '-';
return department.departmentName || department.name || departmentId;
}
function groupName(groupId) {
const group = allGroups.find(item => String(item.id) === String(groupId));
return group ? group.name : groupId || '-';
}
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 syncSelects(...selects) {
selects.filter(Boolean).forEach(select => {
select.dispatchEvent(new Event('change', { bubbles: true }));
});
}
}