Изменил страницу "Кафедра",добавлена модалка с полями для создания записи в Кафедральный файлик
This commit is contained in:
@@ -11,7 +11,7 @@ export async function initDepartment() {
|
||||
// Загрузка кафедр
|
||||
try {
|
||||
departments = await api.get('/api/departments');
|
||||
departmentSelect.innerHTML = '<option value="">Выберите кафедру...</option>' +
|
||||
departmentSelect.innerHTML = '<option value="">Выберите кафедру...</option>' +
|
||||
departments.map(d => `<option value="${d.id}">${escapeHtml(d.departmentName || d.name)}</option>`).join('');
|
||||
} catch (e) {
|
||||
departmentSelect.innerHTML = '<option value="">Ошибка загрузки</option>';
|
||||
@@ -33,22 +33,14 @@ export async function initDepartment() {
|
||||
const deptName = departmentSelect.options[departmentSelect.selectedIndex].text;
|
||||
|
||||
try {
|
||||
const params = new URLSearchParams({
|
||||
departmentId,
|
||||
semesterType,
|
||||
period
|
||||
});
|
||||
|
||||
// Запрос на бэк
|
||||
const params = new URLSearchParams({ departmentId, semesterType, period });
|
||||
const data = await api.get(`/api/department/schedule?${params.toString()}`);
|
||||
|
||||
const semesterName = semesterType === 'spring' ? 'весенний' : (semesterType === 'autumn' ? 'осенний' : semesterType);
|
||||
const periodName = period.replace('-', '/'); // Display 2024-2025 as 2024/2025
|
||||
|
||||
renderScheduleBlock(deptName, semesterName, periodName, data);
|
||||
|
||||
form.reset();
|
||||
|
||||
const semesterName = semesterType === 'spring' ? 'весенний' : (semesterType === 'autumn' ? 'осенний' : semesterType);
|
||||
const periodName = period.replace('-', '/');
|
||||
|
||||
renderScheduleBlock(deptName, semesterName, periodName, data);
|
||||
form.reset();
|
||||
} catch (err) {
|
||||
showAlert('schedule-form-alert', err.message || 'Ошибка загрузки данных', 'error');
|
||||
}
|
||||
@@ -57,8 +49,7 @@ export async function initDepartment() {
|
||||
function renderScheduleBlock(deptName, semester, period, schedule) {
|
||||
const details = document.createElement('details');
|
||||
details.className = 'table-item';
|
||||
details.open = true; // Сразу открываем новый блок
|
||||
|
||||
details.open = true;
|
||||
details.innerHTML = `
|
||||
<summary>
|
||||
<div class="chev" aria-hidden="true">
|
||||
@@ -75,7 +66,6 @@ export async function initDepartment() {
|
||||
</div>
|
||||
<div class="meta">${schedule ? schedule.length : 0} записей</div>
|
||||
</summary>
|
||||
|
||||
<div class="content">
|
||||
<table>
|
||||
<thead>
|
||||
@@ -96,7 +86,6 @@ export async function initDepartment() {
|
||||
</table>
|
||||
</div>
|
||||
`;
|
||||
|
||||
container.prepend(details);
|
||||
}
|
||||
|
||||
@@ -104,34 +93,306 @@ export async function initDepartment() {
|
||||
if (!schedule || schedule.length === 0) {
|
||||
return '<tr><td colspan="8" class="loading-row">Нет данных</td></tr>';
|
||||
}
|
||||
|
||||
return schedule.map(r => `
|
||||
<tr>
|
||||
<td>${escapeHtml(r.specialityCode || '-')}</td>
|
||||
<td>
|
||||
${(() => {
|
||||
const course = r.groupCourse || '-';
|
||||
const semester = r.semester || '-';
|
||||
if (course === '-' && semester === '-') return '-';
|
||||
return `${course} | ${semester}`;
|
||||
})()}
|
||||
</td>
|
||||
<td>${(() => {
|
||||
const course = r.groupCourse || '-';
|
||||
const semester = r.semester || '-';
|
||||
if (course === '-' && semester === '-') return '-';
|
||||
return `${course} | ${semester}`;
|
||||
})()}</td>
|
||||
<td>${escapeHtml(r.groupName || '-')}</td>
|
||||
<td>${escapeHtml(r.subjectName || '-')}</td>
|
||||
<td>${escapeHtml(r.lessonType || '-')}</td>
|
||||
<td>${escapeHtml(r.numberOfHours || '-')}</td>
|
||||
<td>
|
||||
${r.division === true ? '✓' : (r.division === false ? '' : escapeHtml(''))}
|
||||
</td>
|
||||
<td>
|
||||
${(() => {
|
||||
const jobTitle = r.teacherJobTitle || '-';
|
||||
const teacherName = r.teacherName || '-';
|
||||
if (jobTitle === '-' && teacherName === '-') return '-';
|
||||
return `${jobTitle}, ${teacherName}`;
|
||||
})()}
|
||||
</td>
|
||||
<td>${r.division === true ? '✓' : ''}</td>
|
||||
<td>${(() => {
|
||||
const jobTitle = r.teacherJobTitle || '-';
|
||||
const teacherName = r.teacherName || '-';
|
||||
if (jobTitle === '-' && teacherName === '-') return '-';
|
||||
return `${jobTitle}, ${teacherName}`;
|
||||
})()}</td>
|
||||
</tr>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
// =========================================================
|
||||
// ЛОГИКА ДЛЯ ФУНКЦИОНАЛА "СОЗДАТЬ ЗАПИСЬ (К/Ф)"
|
||||
// Два модальных окна поверх всего контента в одном оверлее
|
||||
// =========================================================
|
||||
const btnCreateSchedule = document.getElementById('btn-create-schedule');
|
||||
const csOverlay = document.getElementById('cs-overlay');
|
||||
|
||||
const modalCreateSchedule = document.getElementById('modal-create-schedule');
|
||||
const modalCreateScheduleClose = document.getElementById('modal-create-schedule-close');
|
||||
const formCreateSchedule = document.getElementById('create-schedule-form');
|
||||
|
||||
const modalViewSchedules = document.getElementById('modal-view-schedules');
|
||||
const btnSaveSchedules = document.getElementById('btn-save-schedules');
|
||||
const preparedSchedulesTbody = document.getElementById('prepared-schedules-tbody');
|
||||
|
||||
const csGroupSelect = document.getElementById('cs-group');
|
||||
const csSubjectSelect = document.getElementById('cs-subject');
|
||||
const csTeacherSelect = document.getElementById('cs-teacher');
|
||||
const csDepartmentIdInput = document.getElementById('cs-department-id');
|
||||
|
||||
let preparedSchedules = [];
|
||||
let csGroups = [];
|
||||
let csSubjects = [];
|
||||
let csTeachers = [];
|
||||
|
||||
const SEMESTER_LABELS = { autumn: 'Осенний', spring: 'Весенний' };
|
||||
const LESSON_TYPE_LABELS = { 1: 'Лекция', 2: 'Практическая работа', 3: 'Лабораторная работа' };
|
||||
|
||||
const localDepartmentId = localStorage.getItem('departmentId');
|
||||
|
||||
// ===== Загрузка справочников =====
|
||||
async function loadDictionariesForSchedule() {
|
||||
try {
|
||||
csGroups = await api.get('/api/groups');
|
||||
csGroupSelect.innerHTML = '<option value="">Выберите группу</option>' +
|
||||
csGroups.map(g => `<option value="${g.id}">${escapeHtml(g.name)}</option>`).join('');
|
||||
|
||||
csSubjects = await api.get('/api/subjects');
|
||||
csSubjectSelect.innerHTML = '<option value="">Выберите дисциплину</option>' +
|
||||
csSubjects.map(s => `<option value="${s.id}">${escapeHtml(s.name)}</option>`).join('');
|
||||
|
||||
if (localDepartmentId) {
|
||||
csTeachers = await api.get(`/api/users/teachers/${localDepartmentId}`);
|
||||
csTeacherSelect.innerHTML = '<option value="">Выберите преподавателя</option>' +
|
||||
csTeachers.map(t => `<option value="${t.id}">${escapeHtml(t.fullName || t.username)}</option>`).join('');
|
||||
} else {
|
||||
csTeacherSelect.innerHTML = '<option value="">Ошибка: Не найден ID кафедры</option>';
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Ошибка загрузки справочников:', e);
|
||||
}
|
||||
}
|
||||
|
||||
loadDictionariesForSchedule();
|
||||
|
||||
// ===== Открытие / Закрытие оверлея =====
|
||||
function openOverlay() {
|
||||
csOverlay.classList.add('open');
|
||||
document.body.style.overflow = 'hidden'; // Предотвращаем скролл страницы
|
||||
}
|
||||
|
||||
function closeOverlay() {
|
||||
csOverlay.classList.remove('open');
|
||||
document.body.style.overflow = '';
|
||||
hideAlert('create-schedule-alert');
|
||||
hideAlert('save-schedules-alert');
|
||||
}
|
||||
|
||||
function updateTableVisibility() {
|
||||
modalViewSchedules.style.display = preparedSchedules.length > 0 ? '' : 'none';
|
||||
}
|
||||
|
||||
// ===== Кнопка «Создать запись» =====
|
||||
btnCreateSchedule.addEventListener('click', () => {
|
||||
if (localDepartmentId) {
|
||||
csDepartmentIdInput.value = localDepartmentId;
|
||||
} else {
|
||||
showAlert('schedule-form-alert', 'Требуется перезайти (отсутствует ID кафедры)', 'error');
|
||||
return;
|
||||
}
|
||||
openOverlay();
|
||||
});
|
||||
|
||||
// ===== Закрытие =====
|
||||
modalCreateScheduleClose.addEventListener('click', closeOverlay);
|
||||
|
||||
csOverlay.addEventListener('click', (e) => {
|
||||
// Закрыть по клику на затемнённый фон (но не по клику на содержимое модалок)
|
||||
if (e.target === csOverlay || e.target.classList.contains('cs-overlay-scroll')) {
|
||||
closeOverlay();
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Escape' && csOverlay.classList.contains('open')) {
|
||||
closeOverlay();
|
||||
}
|
||||
});
|
||||
|
||||
// ===== Рендер таблицы =====
|
||||
function renderPreparedSchedules() {
|
||||
if (preparedSchedules.length === 0) {
|
||||
preparedSchedulesTbody.innerHTML = '<tr><td colspan="10" class="loading-row">Нет записей</td></tr>';
|
||||
return;
|
||||
}
|
||||
preparedSchedulesTbody.innerHTML = preparedSchedules.map((s, index) => {
|
||||
const groupName = csGroups.find(g => g.id == s.groupId)?.name || s.groupId;
|
||||
const subjectName = csSubjects.find(sub => sub.id == s.subjectsId)?.name || s.subjectsId;
|
||||
const teacherName = csTeachers.find(t => t.id == s.teacherId)?.fullName
|
||||
|| csTeachers.find(t => t.id == s.teacherId)?.username || s.teacherId;
|
||||
const lessonTypeName = LESSON_TYPE_LABELS[s.lessonTypeId] || 'Неизвестно';
|
||||
const semLabel = SEMESTER_LABELS[s.semesterType] || s.semesterType;
|
||||
const periodDisplay = s.period.replace('-', '/');
|
||||
const divText = s.isDivision ? '✓' : '';
|
||||
const hasError = !!s._errorMsg;
|
||||
const rowStyle = hasError ? ' style="background: rgba(239, 68, 68, 0.08);"' : '';
|
||||
let row = `
|
||||
<tr${rowStyle}>
|
||||
<td>${escapeHtml(periodDisplay)}</td>
|
||||
<td>${escapeHtml(semLabel)}</td>
|
||||
<td>${s.semester}</td>
|
||||
<td>${escapeHtml(String(groupName))}</td>
|
||||
<td>${escapeHtml(String(subjectName))}</td>
|
||||
<td>${escapeHtml(lessonTypeName)}</td>
|
||||
<td>${s.numberOfHours}</td>
|
||||
<td>${divText}</td>
|
||||
<td>${escapeHtml(String(teacherName))}</td>
|
||||
<td><button type="button" class="btn-delete" data-index="${index}">Удалить</button></td>
|
||||
</tr>`;
|
||||
if (hasError) {
|
||||
row += `<tr style="background: rgba(239, 68, 68, 0.05);">
|
||||
<td colspan="10" style="color: var(--error); font-size: 0.85rem; padding: 0.4rem 0.85rem;">
|
||||
⚠ ${escapeHtml(s._errorMsg)}
|
||||
</td>
|
||||
</tr>`;
|
||||
}
|
||||
return row;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
// ===== Удаление строки из таблицы =====
|
||||
preparedSchedulesTbody.addEventListener('click', (e) => {
|
||||
if (e.target.classList.contains('btn-delete')) {
|
||||
const idx = parseInt(e.target.getAttribute('data-index'), 10);
|
||||
preparedSchedules.splice(idx, 1);
|
||||
renderPreparedSchedules();
|
||||
updateTableVisibility();
|
||||
}
|
||||
});
|
||||
|
||||
// ===== Очистка полей формы (частичная) =====
|
||||
// НЕ очищаем select'ы — они остаются заполненными для удобства.
|
||||
// Пользователь сам изменит нужные поля для следующей записи.
|
||||
function clearFormFields() {
|
||||
document.getElementById('cs-hours').value = '';
|
||||
document.getElementById('cs-division').checked = false;
|
||||
}
|
||||
|
||||
// ===== Добавление записи в список =====
|
||||
formCreateSchedule.addEventListener('submit', (e) => {
|
||||
e.preventDefault();
|
||||
hideAlert('create-schedule-alert');
|
||||
|
||||
const depId = csDepartmentIdInput.value;
|
||||
const period = document.getElementById('cs-period').value;
|
||||
const semesterType = document.querySelector('input[name="csSemesterType"]:checked')?.value;
|
||||
const semester = document.getElementById('cs-semester').value;
|
||||
const groupId = csGroupSelect.value;
|
||||
const subjectId = csSubjectSelect.value;
|
||||
const lessonTypeId = document.getElementById('cs-lesson-type').value;
|
||||
const hours = document.getElementById('cs-hours').value;
|
||||
const isDivision = document.getElementById('cs-division').checked;
|
||||
const teacherId = csTeacherSelect.value;
|
||||
|
||||
if (!period || !semesterType || !semester || !groupId || !subjectId || !lessonTypeId || !hours || !teacherId) {
|
||||
showAlert('create-schedule-alert', 'Заполните все обязательные поля', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
const newRecord = {
|
||||
departmentId: Number(depId),
|
||||
semester: Number(semester),
|
||||
groupId: Number(groupId),
|
||||
subjectsId: Number(subjectId),
|
||||
lessonTypeId: Number(lessonTypeId),
|
||||
numberOfHours: Number(hours),
|
||||
isDivision: isDivision,
|
||||
teacherId: Number(teacherId),
|
||||
semesterType: semesterType,
|
||||
period: period
|
||||
};
|
||||
|
||||
// Проверка на дубликат в уже добавленных записях
|
||||
const isDuplicate = preparedSchedules.some(s =>
|
||||
s.period === newRecord.period &&
|
||||
s.semesterType === newRecord.semesterType &&
|
||||
s.semester === newRecord.semester &&
|
||||
s.groupId === newRecord.groupId &&
|
||||
s.subjectsId === newRecord.subjectsId &&
|
||||
s.lessonTypeId === newRecord.lessonTypeId &&
|
||||
s.numberOfHours === newRecord.numberOfHours &&
|
||||
s.isDivision === newRecord.isDivision &&
|
||||
s.teacherId === newRecord.teacherId
|
||||
);
|
||||
|
||||
if (isDuplicate) {
|
||||
showAlert('create-schedule-alert', 'Такая запись уже есть в списке', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
preparedSchedules.push(newRecord);
|
||||
|
||||
clearFormFields();
|
||||
|
||||
showAlert('create-schedule-alert', 'Запись добавлена ✓', 'success');
|
||||
setTimeout(() => hideAlert('create-schedule-alert'), 2000);
|
||||
|
||||
renderPreparedSchedules();
|
||||
updateTableVisibility();
|
||||
});
|
||||
|
||||
// ===== Сохранение в БД =====
|
||||
btnSaveSchedules.addEventListener('click', async () => {
|
||||
if (preparedSchedules.length === 0) {
|
||||
showAlert('save-schedules-alert', 'Нет записей для сохранения', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
btnSaveSchedules.disabled = true;
|
||||
btnSaveSchedules.textContent = 'Сохранение...';
|
||||
hideAlert('save-schedules-alert');
|
||||
|
||||
let errors = 0;
|
||||
let saved = 0;
|
||||
const failedRecords = [];
|
||||
|
||||
for (const record of preparedSchedules) {
|
||||
try {
|
||||
await api.post('/api/department/schedule/create', record);
|
||||
saved++;
|
||||
} catch (err) {
|
||||
console.error('Ошибка сохранения записи:', err);
|
||||
errors++;
|
||||
// Помечаем запись как дубликат, если бэк вернул соответствующую ошибку
|
||||
const isDuplicate = err.status === 409 ||
|
||||
(err.message && err.message.toLowerCase().includes('уже существует'));
|
||||
failedRecords.push({
|
||||
...record,
|
||||
_errorMsg: isDuplicate
|
||||
? 'Такая запись уже есть в базе данных'
|
||||
: (err.message || 'Ошибка сохранения')
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
btnSaveSchedules.disabled = false;
|
||||
btnSaveSchedules.textContent = 'Сохранить в БД';
|
||||
|
||||
if (errors === 0) {
|
||||
showAlert('save-schedules-alert', `Все записи (${saved}) успешно сохранены!`, 'success');
|
||||
preparedSchedules = [];
|
||||
renderPreparedSchedules();
|
||||
updateTableVisibility();
|
||||
setTimeout(closeOverlay, 2000);
|
||||
} else {
|
||||
// Оставляем неудачные записи для повторной попытки / удаления
|
||||
preparedSchedules = failedRecords;
|
||||
renderPreparedSchedules();
|
||||
if (saved > 0) {
|
||||
showAlert('save-schedules-alert',
|
||||
`Сохранено: ${saved}. Ошибок: ${errors}. Проблемные записи отмечены в таблице.`, 'error');
|
||||
} else {
|
||||
showAlert('save-schedules-alert',
|
||||
`Не удалось сохранить. Ошибок: ${errors}. Проблемные записи отмечены в таблице.`, 'error');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user