@@ -448,26 +448,25 @@ export async function initAcademicCalendar() {
|
||||
}
|
||||
|
||||
async function loadCalendars() {
|
||||
calendarsTbody.innerHTML = '<tr><td colspan="7" class="loading-row">Загрузка...</td></tr>';
|
||||
calendarsTbody.innerHTML = '<tr><td colspan="6" class="loading-row">Загрузка...</td></tr>';
|
||||
try {
|
||||
calendars = await api.get('/api/admin/academic-calendars');
|
||||
renderCalendars();
|
||||
populateEditorCalendarSelect();
|
||||
populateSubjectCalendarSelect();
|
||||
} catch (error) {
|
||||
calendarsTbody.innerHTML = `<tr><td colspan="7" class="loading-row">Ошибка загрузки: ${escapeHtml(error.message)}</td></tr>`;
|
||||
calendarsTbody.innerHTML = `<tr><td colspan="6" class="loading-row">Ошибка загрузки: ${escapeHtml(error.message)}</td></tr>`;
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
function renderCalendars() {
|
||||
if (!calendars.length) {
|
||||
calendarsTbody.innerHTML = '<tr><td colspan="7" class="loading-row">Календарные графики не созданы</td></tr>';
|
||||
calendarsTbody.innerHTML = '<tr><td colspan="6" class="loading-row">Календарные графики не созданы</td></tr>';
|
||||
return;
|
||||
}
|
||||
calendarsTbody.innerHTML = calendars.map(calendar => `
|
||||
<tr>
|
||||
<td>${escapeHtml(buildAcademicCalendarTitle(calendar) || '-')}</td>
|
||||
<td>${escapeHtml(calendar.academicYearTitle || '-')}</td>
|
||||
<td>${escapeHtml(`${calendar.specialtyCode || ''} ${calendar.specialtyName || ''}`.trim() || '-')}</td>
|
||||
<td>${escapeHtml(calendar.specialtyProfileName || '-')}</td>
|
||||
@@ -488,7 +487,6 @@ export async function initAcademicCalendar() {
|
||||
hideAlert('academic-calendar-alert');
|
||||
const payload = {
|
||||
id: calendarIdInput.value ? Number(calendarIdInput.value) : null,
|
||||
title: buildCalendarTitleFromForm(),
|
||||
academicYearId: Number(calendarYearSelect.value),
|
||||
specialtyId: Number(calendarSpecialtySelect.value),
|
||||
specialtyProfileId: Number(calendarProfileSelect.value),
|
||||
@@ -496,7 +494,7 @@ export async function initAcademicCalendar() {
|
||||
courseCount: Number(calendarCourseCountInput.value)
|
||||
};
|
||||
try {
|
||||
if (!payload.title || !payload.academicYearId || !payload.specialtyId
|
||||
if (!payload.academicYearId || !payload.specialtyId
|
||||
|| !payload.specialtyProfileId || !payload.studyFormId || !payload.courseCount) {
|
||||
throw new Error('Заполните все поля календарного графика');
|
||||
}
|
||||
@@ -504,7 +502,7 @@ export async function initAcademicCalendar() {
|
||||
const saved = id
|
||||
? await api.put('/api/admin/academic-calendars/' + id, payload)
|
||||
: await api.post('/api/admin/academic-calendars', payload);
|
||||
const savedTitle = buildAcademicCalendarTitle(saved) || payload.title;
|
||||
const savedTitle = buildAcademicCalendarTitle(saved);
|
||||
showAlert('academic-calendar-alert', `График "${escapeHtml(savedTitle)}" сохранён`, 'success');
|
||||
resetCalendarForm();
|
||||
await loadCalendars();
|
||||
@@ -1126,9 +1124,13 @@ export async function initAcademicCalendar() {
|
||||
viewportHeight: window.innerHeight
|
||||
});
|
||||
calendarDayTooltipPositionAnimation?.cancel();
|
||||
const positionedTransform = `translate3d(${left}px, ${top}px, 0) scale(1)`;
|
||||
calendarDayTooltipPositionAnimation = tooltip.animate(
|
||||
[{ left: `${left}px`, top: `${top}px` }],
|
||||
{ duration: 0, fill: 'forwards' }
|
||||
[
|
||||
{ transform: positionedTransform },
|
||||
{ transform: positionedTransform }
|
||||
],
|
||||
{ duration: 1, fill: 'forwards' }
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1515,27 +1517,6 @@ export async function initAcademicCalendar() {
|
||||
|| '';
|
||||
}
|
||||
|
||||
function buildCalendarTitleFromForm() {
|
||||
const specialty = specialties.find(item =>
|
||||
String(item.id) === String(calendarSpecialtySelect.value)
|
||||
);
|
||||
const profile = (profilesBySpecialty.get(String(calendarSpecialtySelect.value)) || [])
|
||||
.find(item => String(item.id) === String(calendarProfileSelect.value));
|
||||
const studyForm = studyForms.find(item =>
|
||||
String(item.id) === String(calendarStudyFormSelect.value)
|
||||
);
|
||||
const academicYear = academicYears.find(item =>
|
||||
String(item.id) === String(calendarYearSelect.value)
|
||||
);
|
||||
|
||||
return buildAcademicCalendarTitle({
|
||||
specialtyCode: specialityCode(specialty),
|
||||
specialtyProfileName: profile?.name,
|
||||
studyFormName: studyForm?.name,
|
||||
academicYearTitle: academicYear?.title
|
||||
});
|
||||
}
|
||||
|
||||
function dayLabel(value) {
|
||||
return DAY_OPTIONS.find(day => String(day.value) === String(value))?.label || '-';
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ const CELL_PARITY_LAYOUT = [
|
||||
];
|
||||
const MOBILE_QUERY = '(max-width: 760px)';
|
||||
const DEFAULT_SEMESTER_WEEKS = 22;
|
||||
const SCHEDULE_REFRESH_PROMPT = 'Нажмите «Показать», чтобы обновить расписание';
|
||||
|
||||
export async function initScheduleView() {
|
||||
const session = getSession();
|
||||
@@ -89,7 +90,7 @@ export async function initScheduleView() {
|
||||
targetSelect?.addEventListener('change', () => {
|
||||
clearInactiveEntityValues();
|
||||
updateEntityFieldVisibility();
|
||||
resetResults('Нажмите «Показать», чтобы обновить расписание');
|
||||
resetResults(SCHEDULE_REFRESH_PROMPT);
|
||||
});
|
||||
loadButton?.addEventListener('click', loadData);
|
||||
previousPeriodButton?.addEventListener('click', () => movePeriod(-1));
|
||||
@@ -100,16 +101,21 @@ export async function initScheduleView() {
|
||||
nextPeriodButton?.addEventListener('click', () => movePeriod(1));
|
||||
dateInput?.addEventListener('change', () => {
|
||||
updatePeriodLabel();
|
||||
resetResults('Нажмите «Показать», чтобы обновить расписание');
|
||||
resetResults(SCHEDULE_REFRESH_PROMPT);
|
||||
});
|
||||
semesterSelect?.addEventListener('change', () => {
|
||||
synchronizeDateWithSemester();
|
||||
updatePeriodLabel();
|
||||
resetResults('Нажмите «Показать», чтобы обновить расписание');
|
||||
resetResults(SCHEDULE_REFRESH_PROMPT);
|
||||
});
|
||||
resultPreviousButton?.addEventListener('click', () => moveResult(-1));
|
||||
resultNextButton?.addEventListener('click', () => moveResult(1));
|
||||
tables?.addEventListener('click', event => {
|
||||
const reloadButton = event.target.closest('[data-schedule-view-reload]');
|
||||
if (reloadButton) {
|
||||
loadData();
|
||||
return;
|
||||
}
|
||||
const button = event.target.closest('[data-schedule-override-edit]');
|
||||
if (!button) return;
|
||||
const lesson = state.lessons.find(item =>
|
||||
@@ -743,9 +749,20 @@ export async function initScheduleView() {
|
||||
`;
|
||||
}
|
||||
|
||||
function setTableState(message) {
|
||||
function setTableState(message, showReloadButton = false) {
|
||||
if (!tables) return;
|
||||
tables.innerHTML = `<div class="loading-row schedule-view-state">${escapeHtml(message)}</div>`;
|
||||
tables.innerHTML = `
|
||||
<div class="loading-row schedule-view-state${showReloadButton ? ' schedule-view-state-actionable' : ''}">
|
||||
<span>${escapeHtml(message)}</span>
|
||||
${showReloadButton ? `
|
||||
<button type="button"
|
||||
class="btn btn-md btn-primary schedule-view-state-reload"
|
||||
data-schedule-view-reload>
|
||||
Показать расписание
|
||||
</button>
|
||||
` : ''}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
function resetResults(message) {
|
||||
@@ -757,7 +774,7 @@ export async function initScheduleView() {
|
||||
renderResultNavigation();
|
||||
tableTitle.textContent = 'Расписание';
|
||||
tableCaption.textContent = message;
|
||||
setTableState(message);
|
||||
setTableState(message, message === SCHEDULE_REFRESH_PROMPT);
|
||||
countLabel.textContent = '0 занятий';
|
||||
dayTabs.hidden = true;
|
||||
dayTabs.innerHTML = '';
|
||||
|
||||
@@ -30,6 +30,10 @@ const SEMESTER_LABELS = {
|
||||
const WHOLE_GROUP_SUBGROUP_VALUE = '__whole_group__';
|
||||
const ACADEMIC_HOURS_PER_SLOT = 2;
|
||||
|
||||
export function scheduleSlotActionLabel(slotCount) {
|
||||
return slotCount === 1 ? 'Очистить' : 'Удалить';
|
||||
}
|
||||
|
||||
const LESSON_TYPE_LIMITS = [
|
||||
{
|
||||
key: 'lecture',
|
||||
@@ -1364,7 +1368,11 @@ export async function initSchedule() {
|
||||
}
|
||||
|
||||
function renderRuleSlots(slots) {
|
||||
ruleSlotsContainer.innerHTML = (slots || [{}]).map((slot, index) => `
|
||||
const normalizedSlots = slots?.length ? slots : [{}];
|
||||
const isSingleSlot = normalizedSlots.length === 1;
|
||||
const actionLabel = scheduleSlotActionLabel(normalizedSlots.length);
|
||||
const actionClass = isSingleSlot ? 'btn-ghost' : 'btn-danger';
|
||||
ruleSlotsContainer.innerHTML = normalizedSlots.map((slot, index) => `
|
||||
<div class="schedule-slot-row ${isLaboratorySlot(slot) ? 'has-subgroup' : ''}" data-index="${index}">
|
||||
<div class="form-group"><label>День</label><select class="slot-day" required>${options(DAY_OPTIONS, slot.dayOfWeek)}</select></div>
|
||||
<div class="form-group"><label>Чётность</label><select class="slot-parity" required>${options(Object.entries(PARITY_LABELS).map(([value, label]) => ({ value, label })), slot.parity || 'BOTH')}</select></div>
|
||||
@@ -1374,7 +1382,7 @@ export async function initSchedule() {
|
||||
<div class="form-group"><label>Тип</label><select class="slot-lesson-type" required>${options(orderedLessonTypes().map(toLessonTypeOption), slot.lessonTypeId)}</select></div>
|
||||
${isLaboratorySlot(slot) ? renderSubgroupField(slot, index) : ''}
|
||||
<div class="form-group"><label>Формат</label><select class="slot-format" required>${options([{ value: 'Очно', label: 'Очно' }, { value: 'Онлайн', label: 'Онлайн' }], slot.lessonFormat || 'Очно')}</select></div>
|
||||
<button type="button" class="btn btn-sm btn-danger schedule-slot-remove" data-index="${index}">Удалить</button>
|
||||
<button type="button" class="btn btn-md ${actionClass} schedule-slot-remove" data-index="${index}">${actionLabel}</button>
|
||||
</div>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user