профили
This commit is contained in:
@@ -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")
|
@GetMapping("/{specialtyId}/profiles")
|
||||||
public ResponseEntity<?> getProfiles(@PathVariable Long specialtyId) {
|
public ResponseEntity<?> getProfiles(@PathVariable Long specialtyId) {
|
||||||
if (!specialtiesRepository.existsById(specialtyId)) {
|
if (!specialtiesRepository.existsById(specialtyId)) {
|
||||||
|
|||||||
@@ -17,8 +17,10 @@ export async function initUniversityStructure() {
|
|||||||
// Элементы вкладок
|
// Элементы вкладок
|
||||||
const tabDepartments = document.getElementById('btn-tab-departments');
|
const tabDepartments = document.getElementById('btn-tab-departments');
|
||||||
const tabSpecialties = document.getElementById('btn-tab-specialties');
|
const tabSpecialties = document.getElementById('btn-tab-specialties');
|
||||||
|
const tabProfiles = document.getElementById('btn-tab-profiles');
|
||||||
const contentDepartments = document.getElementById('tab-content-departments');
|
const contentDepartments = document.getElementById('tab-content-departments');
|
||||||
const contentSpecialties = document.getElementById('tab-content-specialties');
|
const contentSpecialties = document.getElementById('tab-content-specialties');
|
||||||
|
const contentProfiles = document.getElementById('tab-content-profiles');
|
||||||
|
|
||||||
// Элементы модалки профилей
|
// Элементы модалки профилей
|
||||||
const profilesModal = document.getElementById('modal-manage-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 btnCancelProfileEdit = document.getElementById('btn-cancel-profile-edit');
|
||||||
const profilesTbody = document.getElementById('manage-profiles-tbody');
|
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 departments = [];
|
||||||
let specialties = [];
|
let specialties = [];
|
||||||
let currentProfiles = [];
|
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', () => {
|
tabDepartments.addEventListener('click', () => {
|
||||||
|
deactivateTabs();
|
||||||
tabDepartments.classList.add('active');
|
tabDepartments.classList.add('active');
|
||||||
tabDepartments.style.borderBottomColor = 'var(--primary)';
|
tabDepartments.style.borderBottomColor = 'var(--primary)';
|
||||||
tabDepartments.style.color = 'var(--text-main)';
|
tabDepartments.style.color = 'var(--text-main)';
|
||||||
tabSpecialties.classList.remove('active');
|
|
||||||
tabSpecialties.style.borderBottomColor = 'transparent';
|
|
||||||
tabSpecialties.style.color = 'var(--text-secondary)';
|
|
||||||
contentDepartments.style.display = 'block';
|
contentDepartments.style.display = 'block';
|
||||||
contentSpecialties.style.display = 'none';
|
|
||||||
});
|
});
|
||||||
|
|
||||||
tabSpecialties.addEventListener('click', () => {
|
tabSpecialties.addEventListener('click', () => {
|
||||||
|
deactivateTabs();
|
||||||
tabSpecialties.classList.add('active');
|
tabSpecialties.classList.add('active');
|
||||||
tabSpecialties.style.borderBottomColor = 'var(--primary)';
|
tabSpecialties.style.borderBottomColor = 'var(--primary)';
|
||||||
tabSpecialties.style.color = 'var(--text-main)';
|
tabSpecialties.style.color = 'var(--text-main)';
|
||||||
tabDepartments.classList.remove('active');
|
|
||||||
tabDepartments.style.borderBottomColor = 'transparent';
|
|
||||||
tabDepartments.style.color = 'var(--text-secondary)';
|
|
||||||
contentSpecialties.style.display = 'block';
|
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 {
|
try {
|
||||||
specialties = await api.get('/api/specialties');
|
specialties = await api.get('/api/specialties');
|
||||||
renderSpecialties();
|
renderSpecialties();
|
||||||
|
populateSpecialtiesDropdown();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
specTbody.innerHTML = '<tr><td colspan="4" class="loading-row">Ошибка загрузки</td></tr>';
|
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() {
|
function renderDepartments() {
|
||||||
@@ -395,5 +433,117 @@ export async function initUniversityStructure() {
|
|||||||
if (e.target === profilesModal) profilesModal.classList.remove('open');
|
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();
|
await loadData();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;">
|
<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 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-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>
|
</div>
|
||||||
|
|
||||||
<!-- Раздел Кафедр -->
|
<!-- Раздел Кафедр -->
|
||||||
@@ -89,6 +90,60 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user