профили

This commit is contained in:
Zuev
2026-05-31 16:34:05 +03:00
parent 7926038bdc
commit f3a9905423
3 changed files with 223 additions and 8 deletions

View File

@@ -193,6 +193,16 @@ public class SpecialityController {
));
}
@GetMapping("/profiles")
public List<SpecialtyProfileDto> 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)) {

View File

@@ -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 = '<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() {
@@ -395,5 +433,117 @@ export async function initUniversityStructure() {
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-edit-classroom btn-edit-tab-profile" data-id="${p.id}">Изменить</button>
<button class="btn-delete 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();
}

View File

@@ -3,6 +3,7 @@
<div class="tabs-header-buttons" style="display: flex; gap: 1rem; margin-bottom: 1.5rem; border-bottom: 1px solid var(--border-color); padding-bottom: 0.5rem;">
<button class="btn-tab active" id="btn-tab-departments" style="background: none; border: none; border-bottom: 2px solid var(--primary); color: var(--text-main); font-weight: 600; padding: 0.5rem 1rem; cursor: pointer;">Кафедры</button>
<button class="btn-tab" id="btn-tab-specialties" style="background: none; border: none; border-bottom: 2px solid transparent; color: var(--text-secondary); font-weight: 500; padding: 0.5rem 1rem; cursor: pointer;">Специальности</button>
<button class="btn-tab" id="btn-tab-profiles" style="background: none; border: none; border-bottom: 2px solid transparent; color: var(--text-secondary); font-weight: 500; padding: 0.5rem 1rem; cursor: pointer;">Профили</button>
</div>
<!-- Раздел Кафедр -->
@@ -89,6 +90,60 @@
</tr>
</tbody>
</table>
</div>
</div>
<!-- Раздел Профилей -->
<div id="tab-content-profiles" class="tab-content-section" style="display: none;">
<div class="card create-card">
<h2 id="main-profile-form-title">Создание профиля</h2>
<form id="create-profile-tab-form">
<input type="hidden" id="tab-profile-id">
<div class="form-row">
<div class="form-group" style="flex: 1; min-width: 200px;">
<label for="tab-profile-spec">Специальность</label>
<select id="tab-profile-spec" required>
<option value="">Выберите специальность</option>
</select>
</div>
<div class="form-group" style="flex: 1.5; min-width: 250px;">
<label for="tab-profile-name">Название профиля</label>
<input type="text" id="tab-profile-name" placeholder="Например: Искусственный интеллект" required>
</div>
<div class="form-group" style="flex: 2; min-width: 250px;">
<label for="tab-profile-description">Описание</label>
<input type="text" id="tab-profile-description" placeholder="Необязательно">
</div>
<div style="display: flex; gap: 0.5rem; align-items: flex-end;">
<button type="submit" class="btn-primary" id="btn-tab-save-profile">Создать</button>
<button type="button" class="btn-secondary" id="btn-tab-cancel-profile-edit" style="display: none;">Отмена</button>
</div>
</div>
<div class="form-alert" id="tab-profile-alert" role="alert"></div>
</form>
</div>
<div class="card">
<div class="card-header-row">
<h2>Профили обучения</h2>
</div>
<div class="table-wrap">
<table id="profiles-table">
<thead>
<tr>
<th>ID</th>
<th>Специальность</th>
<th>Название профиля</th>
<th>Описание</th>
<th>Действия</th>
</tr>
</thead>
<tbody id="profiles-tbody">
<tr>
<td colspan="5" class="loading-row">Загрузка...</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>