From f3a990542327942a84ee4d430f518c55c28e3ea4 Mon Sep 17 00:00:00 2001 From: Zuev Date: Sun, 31 May 2026 16:34:05 +0300 Subject: [PATCH] =?UTF-8?q?=D0=BF=D1=80=D0=BE=D1=84=D0=B8=D0=BB=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/controller/SpecialityController.java | 10 ++ .../admin/js/views/university-structure.js | 166 +++++++++++++++++- .../admin/views/university-structure.html | 55 ++++++ 3 files changed, 223 insertions(+), 8 deletions(-) diff --git a/backend/src/main/java/com/magistr/app/controller/SpecialityController.java b/backend/src/main/java/com/magistr/app/controller/SpecialityController.java index ed2d9d4..d683998 100644 --- a/backend/src/main/java/com/magistr/app/controller/SpecialityController.java +++ b/backend/src/main/java/com/magistr/app/controller/SpecialityController.java @@ -193,6 +193,16 @@ public class SpecialityController { )); } + @GetMapping("/profiles") + public List getAllProfiles() { + logger.info("Получен запрос на получение всех профилей обучения"); + return specialtyProfileRepository.findAll().stream() + .filter(profile -> profile.getSpeciality().isActiveRecord()) + .sorted((a, b) -> a.getName().compareToIgnoreCase(b.getName())) + .map(this::toProfileDto) + .toList(); + } + @GetMapping("/{specialtyId}/profiles") public ResponseEntity getProfiles(@PathVariable Long specialtyId) { if (!specialtiesRepository.existsById(specialtyId)) { diff --git a/frontend/admin/js/views/university-structure.js b/frontend/admin/js/views/university-structure.js index 3281d60..bcce5c9 100644 --- a/frontend/admin/js/views/university-structure.js +++ b/frontend/admin/js/views/university-structure.js @@ -17,8 +17,10 @@ export async function initUniversityStructure() { // Элементы вкладок 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'); @@ -34,31 +36,58 @@ export async function initUniversityStructure() { 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 = []; // --- Логика переключения табов --- + function deactivateTabs() { + [tabDepartments, tabSpecialties, tabProfiles].forEach(tab => { + if (tab) { + tab.classList.remove('active'); + tab.style.borderBottomColor = 'transparent'; + tab.style.color = 'var(--text-secondary)'; + } + }); + [contentDepartments, contentSpecialties, contentProfiles].forEach(content => { + if (content) content.style.display = 'none'; + }); + } + tabDepartments.addEventListener('click', () => { + deactivateTabs(); tabDepartments.classList.add('active'); tabDepartments.style.borderBottomColor = 'var(--primary)'; tabDepartments.style.color = 'var(--text-main)'; - tabSpecialties.classList.remove('active'); - tabSpecialties.style.borderBottomColor = 'transparent'; - tabSpecialties.style.color = 'var(--text-secondary)'; contentDepartments.style.display = 'block'; - contentSpecialties.style.display = 'none'; }); tabSpecialties.addEventListener('click', () => { + deactivateTabs(); tabSpecialties.classList.add('active'); tabSpecialties.style.borderBottomColor = 'var(--primary)'; tabSpecialties.style.color = 'var(--text-main)'; - tabDepartments.classList.remove('active'); - tabDepartments.style.borderBottomColor = 'transparent'; - tabDepartments.style.color = 'var(--text-secondary)'; contentSpecialties.style.display = 'block'; - contentDepartments.style.display = 'none'; + }); + + tabProfiles.addEventListener('click', () => { + deactivateTabs(); + tabProfiles.classList.add('active'); + tabProfiles.style.borderBottomColor = 'var(--primary)'; + tabProfiles.style.color = 'var(--text-main)'; + contentProfiles.style.display = 'block'; }); // --- Загрузка данных --- @@ -75,9 +104,18 @@ export async function initUniversityStructure() { try { specialties = await api.get('/api/specialties'); renderSpecialties(); + populateSpecialtiesDropdown(); } catch (e) { specTbody.innerHTML = 'Ошибка загрузки'; } + + // Загрузка всех профилей + try { + allProfiles = await api.get('/api/specialties/profiles'); + renderAllProfiles(); + } catch (e) { + tabProfileTbody.innerHTML = 'Ошибка загрузки'; + } } function renderDepartments() { @@ -395,5 +433,117 @@ export async function initUniversityStructure() { if (e.target === profilesModal) profilesModal.classList.remove('open'); }); + // --- Дополнительные функции для вкладки Профилей --- + function populateSpecialtiesDropdown() { + const currentValue = tabProfileSpecSelect.value; + tabProfileSpecSelect.innerHTML = '' + + specialties.map(s => ``).join(''); + tabProfileSpecSelect.value = currentValue; + } + + function renderAllProfiles() { + if (!allProfiles || !allProfiles.length) { + tabProfileTbody.innerHTML = 'Профили не созданы'; + return; + } + tabProfileTbody.innerHTML = allProfiles.map(p => ` + + ${p.id} + ${escapeHtml(p.specialityCode)} · ${escapeHtml(p.specialityName)} + ${escapeHtml(p.name)} + ${escapeHtml(p.description || '-')} + + + + + + `).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(); } diff --git a/frontend/admin/views/university-structure.html b/frontend/admin/views/university-structure.html index 2a44ebc..53e4ea4 100644 --- a/frontend/admin/views/university-structure.html +++ b/frontend/admin/views/university-structure.html @@ -3,6 +3,7 @@
+
@@ -89,6 +90,60 @@ + + + + +