Files
magistr/frontend/admin/js/views/university-structure.js

535 lines
24 KiB
JavaScript

import { api } from '../api.js';
import { escapeHtml, showAlert, hideAlert } from '../utils.js';
export async function initUniversityStructure() {
const deptTbody = document.getElementById('departments-tbody');
const specTbody = document.getElementById('specialties-tbody');
const createDeptForm = document.getElementById('create-department-form');
const createSpecForm = document.getElementById('create-specialty-form');
const editDeptForm = document.getElementById('edit-department-form');
const editSpecForm = document.getElementById('edit-specialty-form');
const editDeptModal = document.getElementById('modal-edit-department');
const editSpecModal = document.getElementById('modal-edit-specialty');
const editDeptClose = document.getElementById('modal-edit-department-close');
const editSpecClose = document.getElementById('modal-edit-specialty-close');
// Элементы вкладок
const tabDepartments = document.getElementById('btn-tab-departments');
const tabSpecialties = document.getElementById('btn-tab-specialties');
const tabProfiles = document.getElementById('btn-tab-profiles');
const contentDepartments = document.getElementById('tab-content-departments');
const contentSpecialties = document.getElementById('tab-content-specialties');
const contentProfiles = document.getElementById('tab-content-profiles');
// Элементы модалки профилей
const profilesModal = document.getElementById('modal-manage-profiles');
const profilesModalClose = document.getElementById('modal-manage-profiles-close');
const profilesSpecContext = document.getElementById('profiles-spec-context');
const profileFormTitle = document.getElementById('profile-form-title');
const manageProfileForm = document.getElementById('manage-profile-form');
const profileIdInput = document.getElementById('manage-profile-id');
const profileSpecIdInput = document.getElementById('manage-profile-spec-id');
const profileNameInput = document.getElementById('manage-profile-name');
const profileDescInput = document.getElementById('manage-profile-description');
const btnSaveProfile = document.getElementById('btn-save-profile');
const btnCancelProfileEdit = document.getElementById('btn-cancel-profile-edit');
const profilesTbody = document.getElementById('manage-profiles-tbody');
// Элементы вкладки Профилей
const tabProfileTbody = document.getElementById('profiles-tbody');
const createProfileTabForm = document.getElementById('create-profile-tab-form');
const tabProfileIdInput = document.getElementById('tab-profile-id');
const tabProfileSpecSelect = document.getElementById('tab-profile-spec');
const tabProfileNameInput = document.getElementById('tab-profile-name');
const tabProfileDescInput = document.getElementById('tab-profile-description');
const mainProfileFormTitle = document.getElementById('main-profile-form-title');
const btnTabSaveProfile = document.getElementById('btn-tab-save-profile');
const btnTabCancelProfileEdit = document.getElementById('btn-tab-cancel-profile-edit');
let departments = [];
let specialties = [];
let currentProfiles = [];
let allProfiles = [];
// --- Логика переключения табов ---
const structureTabs = [
{ button: tabDepartments, panel: contentDepartments },
{ button: tabSpecialties, panel: contentSpecialties },
{ button: tabProfiles, panel: contentProfiles },
];
function activateStructureTab(activeButton) {
structureTabs.forEach(({ button, panel }) => {
const active = button === activeButton;
button?.classList.toggle('active', active);
button?.setAttribute('aria-selected', active ? 'true' : 'false');
if (panel) {
panel.classList.toggle('active', active);
panel.hidden = !active;
}
});
}
structureTabs.forEach(({ button }) => {
button?.addEventListener('click', () => activateStructureTab(button));
});
// --- Загрузка данных ---
async function loadData() {
// Загрузка кафедр
try {
departments = await api.get('/api/departments');
renderDepartments();
} catch (e) {
deptTbody.innerHTML = '<tr><td colspan="4" class="loading-row">Ошибка загрузки</td></tr>';
}
// Загрузка специальностей
try {
specialties = await api.get('/api/specialties');
renderSpecialties();
populateSpecialtiesDropdown();
} catch (e) {
specTbody.innerHTML = '<tr><td colspan="4" class="loading-row">Ошибка загрузки</td></tr>';
}
// Загрузка всех профилей
try {
allProfiles = await api.get('/api/specialties/profiles');
renderAllProfiles();
} catch (e) {
tabProfileTbody.innerHTML = '<tr><td colspan="5" class="loading-row">Ошибка загрузки</td></tr>';
}
}
function renderDepartments() {
if (!departments || !departments.length) {
deptTbody.innerHTML = '<tr><td colspan="4" class="loading-row">Кафедры не созданы</td></tr>';
return;
}
deptTbody.innerHTML = departments.map(d => `
<tr>
<td>${d.id}</td>
<td>${escapeHtml(d.departmentName || d.name)}</td>
<td>${escapeHtml(String(d.departmentCode || d.code))}</td>
<td>
<button class="btn btn-sm btn-secondary btn-edit-department" data-id="${d.id}">Изменить</button>
<button class="btn btn-sm btn-danger btn-delete-department" data-id="${d.id}">Удалить</button>
</td>
</tr>
`).join('');
}
function renderSpecialties() {
if (!specialties || !specialties.length) {
specTbody.innerHTML = '<tr><td colspan="4" class="loading-row">Специальности не созданы</td></tr>';
return;
}
specTbody.innerHTML = specialties.map(s => `
<tr>
<td>${s.id}</td>
<td>${escapeHtml(s.specialityName || s.name)}</td>
<td>${escapeHtml(s.specialityCode || s.specialtyCode || s.specialty_code)}</td>
<td>
<button class="btn btn-sm btn-secondary btn-edit-specialty" data-id="${s.id}">Изменить</button>
<button class="btn btn-sm btn-secondary btn-manage-profiles" data-id="${s.id}">Профили</button>
<button class="btn btn-sm btn-danger btn-delete-specialty" data-id="${s.id}">Удалить</button>
</td>
</tr>
`).join('');
}
// --- Кафедры: CRUD ---
createDeptForm.addEventListener('submit', async (e) => {
e.preventDefault();
hideAlert('create-dept-alert');
const name = document.getElementById('dept-name').value.trim();
const code = document.getElementById('dept-code').value.trim();
if (!name || !code) {
showAlert('create-dept-alert', 'Заполните все поля', 'error');
return;
}
try {
await api.post('/api/departments', { departmentName: name, departmentCode: Number(code) });
showAlert('create-dept-alert', `Кафедра "${name}" создана`, 'success');
createDeptForm.reset();
loadData();
} catch (error) {
showAlert('create-dept-alert', error.message || 'Ошибка создания кафедры', 'error');
}
});
deptTbody.addEventListener('click', async (e) => {
const editBtn = e.target.closest('.btn-edit-department');
const deleteBtn = e.target.closest('.btn-delete-department');
if (editBtn) {
const department = departments.find(item => item.id == editBtn.dataset.id);
if (!department) return;
document.getElementById('edit-dept-id').value = department.id;
document.getElementById('edit-dept-name').value = department.departmentName || department.name || '';
document.getElementById('edit-dept-code').value = department.departmentCode || department.code || '';
hideAlert('edit-dept-alert');
editDeptModal.classList.add('open');
return;
}
if (deleteBtn) {
if (!confirm('Удалить кафедру?')) return;
try {
await api.delete('/api/departments/' + deleteBtn.dataset.id);
showAlert('create-dept-alert', 'Кафедра удалена', 'success');
await loadData();
} catch (error) {
showAlert('create-dept-alert', error.message || 'Ошибка удаления кафедры', 'error');
}
}
});
editDeptForm.addEventListener('submit', async (e) => {
e.preventDefault();
hideAlert('edit-dept-alert');
const id = document.getElementById('edit-dept-id').value;
const name = document.getElementById('edit-dept-name').value.trim();
const code = document.getElementById('edit-dept-code').value.trim();
if (!name || !code) {
showAlert('edit-dept-alert', 'Заполните все поля', 'error');
return;
}
try {
await api.put('/api/departments/' + id, { departmentName: name, departmentCode: Number(code) });
editDeptModal.classList.remove('open');
showAlert('create-dept-alert', `Кафедра "${name}" обновлена`, 'success');
await loadData();
} catch (error) {
showAlert('edit-dept-alert', error.message || 'Ошибка обновления кафедры', 'error');
}
});
// --- Специальности: CRUD ---
createSpecForm.addEventListener('submit', async (e) => {
e.preventDefault();
hideAlert('create-spec-alert');
const name = document.getElementById('spec-name').value.trim();
const code = document.getElementById('spec-code').value.trim();
if (!name || !code) {
showAlert('create-spec-alert', 'Заполните все поля', 'error');
return;
}
try {
await api.post('/api/specialties', { specialityName: name, specialityCode: code });
showAlert('create-spec-alert', `Специальность "${name}" создана`, 'success');
createSpecForm.reset();
loadData();
} catch (error) {
showAlert('create-spec-alert', error.message || 'Ошибка создания специальности', 'error');
}
});
specTbody.addEventListener('click', async (e) => {
const editBtn = e.target.closest('.btn-edit-specialty');
const deleteBtn = e.target.closest('.btn-delete-specialty');
const profilesBtn = e.target.closest('.btn-manage-profiles');
if (editBtn) {
const speciality = specialties.find(item => item.id == editBtn.dataset.id);
if (!speciality) return;
document.getElementById('edit-spec-id').value = speciality.id;
document.getElementById('edit-spec-name').value = speciality.specialityName || speciality.name || '';
document.getElementById('edit-spec-code').value = speciality.specialityCode || speciality.specialtyCode || speciality.specialty_code || '';
hideAlert('edit-spec-alert');
editSpecModal.classList.add('open');
return;
}
if (deleteBtn) {
if (!confirm('Удалить специальность?')) return;
try {
await api.delete('/api/specialties/' + deleteBtn.dataset.id);
showAlert('create-spec-alert', 'Специальность удалена', 'success');
await loadData();
} catch (error) {
showAlert('create-spec-alert', error.message || 'Ошибка удаления специальности', 'error');
}
}
if (profilesBtn) {
const specId = profilesBtn.dataset.id;
const speciality = specialties.find(item => item.id == specId);
if (!speciality) return;
openProfilesModal(speciality);
}
});
editSpecForm.addEventListener('submit', async (e) => {
e.preventDefault();
hideAlert('edit-spec-alert');
const id = document.getElementById('edit-spec-id').value;
const name = document.getElementById('edit-spec-name').value.trim();
const code = document.getElementById('edit-spec-code').value.trim();
if (!name || !code) {
showAlert('edit-spec-alert', 'Заполните все поля', 'error');
return;
}
try {
await api.put('/api/specialties/' + id, { specialityName: name, specialityCode: code });
editSpecModal.classList.remove('open');
showAlert('create-spec-alert', `Специальность "${name}" обновлена`, 'success');
await loadData();
} catch (error) {
showAlert('edit-spec-alert', error.message || 'Ошибка обновления специальности', 'error');
}
});
// --- Профили обучения: Логика ---
async function openProfilesModal(speciality) {
profilesSpecContext.textContent = `${speciality.specialityCode || speciality.specialtyCode || speciality.specialty_code} · ${speciality.specialityName || speciality.name}`;
profileSpecIdInput.value = speciality.id;
resetProfileForm();
profilesModal.classList.add('open');
await loadProfiles(speciality.id);
}
async function loadProfiles(specId) {
profilesTbody.innerHTML = '<tr><td colspan="4" class="loading-row">Загрузка...</td></tr>';
try {
currentProfiles = await api.get(`/api/specialties/${specId}/profiles`);
renderProfilesTable();
} catch (e) {
profilesTbody.innerHTML = `<tr><td colspan="4" class="loading-row">Ошибка загрузки: ${escapeHtml(e.message)}</td></tr>`;
}
}
function renderProfilesTable() {
if (!currentProfiles || !currentProfiles.length) {
profilesTbody.innerHTML = '<tr><td colspan="4" class="loading-row">Профили не созданы</td></tr>';
return;
}
profilesTbody.innerHTML = currentProfiles.map(p => `
<tr>
<td>${p.id}</td>
<td>${escapeHtml(p.name)}</td>
<td>${escapeHtml(p.description || '-')}</td>
<td>
<button class="btn btn-sm btn-secondary btn-edit-profile" data-id="${p.id}">Изменить</button>
<button class="btn btn-sm btn-danger btn-delete-profile" data-id="${p.id}">Удалить</button>
</td>
</tr>
`).join('');
}
function resetProfileForm() {
manageProfileForm.reset();
profileIdInput.value = '';
profileFormTitle.textContent = 'Создание профиля';
btnSaveProfile.textContent = 'Создать';
btnCancelProfileEdit.style.display = 'none';
hideAlert('manage-profile-alert');
}
manageProfileForm.addEventListener('submit', async (e) => {
e.preventDefault();
hideAlert('manage-profile-alert');
const specId = profileSpecIdInput.value;
const profileId = profileIdInput.value;
const name = profileNameInput.value.trim();
const description = profileDescInput.value.trim();
if (!name) {
showAlert('manage-profile-alert', 'Введите название профиля', 'error');
return;
}
try {
if (profileId) {
// Обновление
await api.put(`/api/specialties/${specId}/profiles/${profileId}`, { id: Number(profileId), name, description });
showAlert('manage-profile-alert', `Профиль "${name}" обновлен`, 'success');
} else {
// Создание
await api.post(`/api/specialties/${specId}/profiles`, { name, description });
showAlert('manage-profile-alert', `Профиль "${name}" создан`, 'success');
}
resetProfileForm();
await loadProfiles(specId);
} catch (error) {
showAlert('manage-profile-alert', error.message || 'Ошибка сохранения профиля', 'error');
}
});
profilesTbody.addEventListener('click', async (e) => {
const editBtn = e.target.closest('.btn-edit-profile');
const deleteBtn = e.target.closest('.btn-delete-profile');
const specId = profileSpecIdInput.value;
if (editBtn) {
const profile = currentProfiles.find(item => item.id == editBtn.dataset.id);
if (!profile) return;
profileIdInput.value = profile.id;
profileNameInput.value = profile.name || '';
profileDescInput.value = profile.description || '';
profileFormTitle.textContent = 'Редактирование профиля';
btnSaveProfile.textContent = 'Сохранить';
btnCancelProfileEdit.style.display = 'inline-block';
hideAlert('manage-profile-alert');
return;
}
if (deleteBtn) {
if (!confirm('Удалить профиль обучения?')) return;
try {
await api.delete(`/api/specialties/${specId}/profiles/${deleteBtn.dataset.id}`);
showAlert('manage-profile-alert', 'Профиль удален', 'success');
await loadProfiles(specId);
} catch (error) {
showAlert('manage-profile-alert', error.message || 'Ошибка удаления профиля', 'error');
}
}
});
btnCancelProfileEdit.addEventListener('click', resetProfileForm);
// Закрытие модалок
editDeptClose.addEventListener('click', () => editDeptModal.classList.remove('open'));
editSpecClose.addEventListener('click', () => editSpecModal.classList.remove('open'));
profilesModalClose.addEventListener('click', () => profilesModal.classList.remove('open'));
editDeptModal.addEventListener('click', (e) => {
if (e.target === editDeptModal) editDeptModal.classList.remove('open');
});
editSpecModal.addEventListener('click', (e) => {
if (e.target === editSpecModal) editSpecModal.classList.remove('open');
});
profilesModal.addEventListener('click', (e) => {
if (e.target === profilesModal) profilesModal.classList.remove('open');
});
// --- Дополнительные функции для вкладки Профилей ---
function populateSpecialtiesDropdown() {
const currentValue = tabProfileSpecSelect.value;
tabProfileSpecSelect.innerHTML = '<option value="">Выберите специальность</option>' +
specialties.map(s => `<option value="${s.id}">${escapeHtml(s.specialityCode || s.specialtyCode)} · ${escapeHtml(s.specialityName || s.name)}</option>`).join('');
tabProfileSpecSelect.value = currentValue;
}
function renderAllProfiles() {
if (!allProfiles || !allProfiles.length) {
tabProfileTbody.innerHTML = '<tr><td colspan="5" class="loading-row">Профили не созданы</td></tr>';
return;
}
tabProfileTbody.innerHTML = allProfiles.map(p => `
<tr>
<td>${p.id}</td>
<td>${escapeHtml(p.specialityCode)} · ${escapeHtml(p.specialityName)}</td>
<td>${escapeHtml(p.name)}</td>
<td>${escapeHtml(p.description || '-')}</td>
<td>
<button class="btn btn-sm btn-secondary btn-edit-tab-profile" data-id="${p.id}">Изменить</button>
<button class="btn btn-sm btn-danger btn-delete-tab-profile" data-id="${p.id}">Удалить</button>
</td>
</tr>
`).join('');
}
function resetTabProfileForm() {
createProfileTabForm.reset();
tabProfileIdInput.value = '';
tabProfileSpecSelect.disabled = false;
mainProfileFormTitle.textContent = 'Создание профиля';
btnTabSaveProfile.textContent = 'Создать';
btnTabCancelProfileEdit.style.display = 'none';
hideAlert('tab-profile-alert');
tabProfileSpecSelect.dispatchEvent(new Event('change'));
}
createProfileTabForm.addEventListener('submit', async (e) => {
e.preventDefault();
hideAlert('tab-profile-alert');
const specId = tabProfileSpecSelect.value;
const profileId = tabProfileIdInput.value;
const name = tabProfileNameInput.value.trim();
const description = tabProfileDescInput.value.trim();
if (!specId) {
showAlert('tab-profile-alert', 'Выберите специальность', 'error');
return;
}
if (!name) {
showAlert('tab-profile-alert', 'Введите название профиля', 'error');
return;
}
try {
if (profileId) {
await api.put(`/api/specialties/${specId}/profiles/${profileId}`, { id: Number(profileId), name, description });
showAlert('tab-profile-alert', `Профиль "${name}" обновлен`, 'success');
} else {
await api.post(`/api/specialties/${specId}/profiles`, { name, description });
showAlert('tab-profile-alert', `Профиль "${name}" создан`, 'success');
}
resetTabProfileForm();
await loadData();
} catch (error) {
showAlert('tab-profile-alert', error.message || 'Ошибка сохранения профиля', 'error');
}
});
tabProfileTbody.addEventListener('click', async (e) => {
const editBtn = e.target.closest('.btn-edit-tab-profile');
const deleteBtn = e.target.closest('.btn-delete-tab-profile');
if (editBtn) {
const profile = allProfiles.find(item => item.id == editBtn.dataset.id);
if (!profile) return;
tabProfileIdInput.value = profile.id;
tabProfileSpecSelect.value = profile.specialityId;
tabProfileSpecSelect.disabled = true;
tabProfileSpecSelect.dispatchEvent(new Event('change'));
tabProfileNameInput.value = profile.name || '';
tabProfileDescInput.value = profile.description || '';
mainProfileFormTitle.textContent = 'Редактирование профиля';
btnTabSaveProfile.textContent = 'Сохранить';
btnTabCancelProfileEdit.style.display = 'inline-block';
hideAlert('tab-profile-alert');
createProfileTabForm.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
return;
}
if (deleteBtn) {
const profile = allProfiles.find(item => item.id == deleteBtn.dataset.id);
if (!profile) return;
if (!confirm('Удалить профиль обучения?')) return;
try {
await api.delete(`/api/specialties/${profile.specialityId}/profiles/${profile.id}`);
showAlert('tab-profile-alert', 'Профиль удален', 'success');
await loadData();
} catch (error) {
showAlert('tab-profile-alert', error.message || 'Ошибка удаления профиля', 'error');
}
}
});
btnTabCancelProfileEdit.addEventListener('click', resetTabProfileForm);
await loadData();
}