исправил настройку временных слотов

This commit is contained in:
Zuev
2026-05-02 01:34:21 +03:00
parent 3cb311f469
commit 44cfe8ab6e
10 changed files with 390 additions and 154 deletions

View File

@@ -67,6 +67,9 @@ export async function initAcademicCalendar() {
const calendarDayDialogDay = document.getElementById('calendar-day-dialog-day');
const calendarDayDialogActivitySummary = document.getElementById('calendar-day-dialog-activity-summary');
const calendarDayDialogActivitySelect = document.getElementById('calendar-day-dialog-activity');
const calendarDayDialogTimeScopeSelect = document.getElementById('calendar-day-dialog-time-scope');
const calendarDayDialogTimeSaveButton = document.getElementById('calendar-day-dialog-time-save');
const calendarDayDialogTimeClearButton = document.getElementById('calendar-day-dialog-time-clear');
let academicYears = [];
let semesters = [];
@@ -75,12 +78,21 @@ export async function initAcademicCalendar() {
let studyForms = [];
let activityTypes = [];
let calendars = [];
let timeSlotScopes = [];
let timeSlotDateAssignments = [];
let activeCalendarDay = null;
bindEvents();
try {
await Promise.all([loadBaseLists(), loadYears(), loadStudyForms(), loadActivityTypes()]);
await Promise.all([
loadBaseLists(),
loadYears(),
loadStudyForms(),
loadActivityTypes(),
loadTimeSlotScopes(),
loadTimeSlotDateAssignments()
]);
await loadCalendars();
} catch (error) {
showAlert('academic-calendar-alert', error.message || 'Ошибка загрузки календарных графиков', 'error');
@@ -126,6 +138,8 @@ export async function initAcademicCalendar() {
updateCalendarDayDialogSummary(activeCalendarDay);
renderCalendarTotals();
});
calendarDayDialogTimeSaveButton?.addEventListener('click', saveTimeSlotDateAssignment);
calendarDayDialogTimeClearButton?.addEventListener('click', clearTimeSlotDateAssignment);
academicYearForm?.addEventListener('submit', saveAcademicYear);
semesterForm?.addEventListener('submit', saveSemester);
@@ -309,6 +323,14 @@ export async function initAcademicCalendar() {
populateActivitySelects();
}
async function loadTimeSlotScopes() {
timeSlotScopes = await api.get('/api/admin/time-slots/scopes');
}
async function loadTimeSlotDateAssignments() {
timeSlotDateAssignments = await api.get('/api/admin/time-slots/date-assignments');
}
async function loadCalendars() {
calendarsTbody.innerHTML = '<tr><td colspan="7" class="loading-row">Загрузка...</td></tr>';
try {
@@ -432,6 +454,7 @@ export async function initAcademicCalendar() {
return;
}
try {
await loadTimeSlotDateAssignments();
const rows = await api.get(`/api/admin/academic-calendars/${calendar.id}/grid`);
const gridRows = rows.length ? rows : buildDefaultGrid(calendar);
renderCalendarGrid(gridRows);
@@ -573,12 +596,15 @@ export async function initAcademicCalendar() {
data-date="${row.date}"
data-week="${row.weekNumber}"
data-day="${row.dayOfWeek}"
data-time-assignment-id="${timeAssignmentForDate(row.date)?.id || ''}"
data-time-scope-id="${timeAssignmentForDate(row.date)?.scopeId || ''}"
role="button"
tabindex="0"
aria-label="${escapeHtml(`${formatShortDate(row.date)} ${dayName}, ${row.weekNumber} неделя, код ${code}`)}"
title="${escapeHtml(`${formatLongDate(row.date)}, ${dayName}, ${row.weekNumber} неделя, ${code} - ${activity.name || ''}`)}"
style="--activity-color:${escapeHtml(color)}">
<strong class="calendar-day-code">${escapeHtml(code)}</strong>
${timeAssignmentForDate(row.date) ? '<span class="calendar-day-time-marker">вр.</span>' : ''}
<select class="calendar-day-activity custom-select-initialized" aria-hidden="true" tabindex="-1">${options(activityTypes.map(toActivityOption), selected)}</select>
</td>
`;
@@ -594,7 +620,9 @@ export async function initAcademicCalendar() {
activityTypes.map(toActivityOption),
day.querySelector('.calendar-day-activity').value
);
populateTimeScopeDialogSelect(day.dataset.date);
syncSelects(calendarDayDialogActivitySelect);
syncSelects(calendarDayDialogTimeScopeSelect);
updateCalendarDayDialogSummary(day);
calendarDayDialog.classList.add('open');
}
@@ -619,6 +647,77 @@ export async function initAcademicCalendar() {
`;
}
async function saveTimeSlotDateAssignment() {
if (!activeCalendarDay) return;
hideAlert('calendar-day-dialog-time-alert');
const scopeId = calendarDayDialogTimeScopeSelect.value;
if (!scopeId) {
await clearTimeSlotDateAssignment();
return;
}
try {
await api.post('/api/admin/time-slots/date-assignments', {
date: activeCalendarDay.dataset.date,
scopeId: Number(scopeId)
});
await loadTimeSlotDateAssignments();
updateTimeAssignmentVisuals(activeCalendarDay.dataset.date);
populateTimeScopeDialogSelect(activeCalendarDay.dataset.date);
syncSelects(calendarDayDialogTimeScopeSelect);
showAlert('calendar-day-dialog-time-alert', 'Ручная сетка применена к дате', 'success');
} catch (error) {
showAlert('calendar-day-dialog-time-alert', error.message || 'Ошибка применения временной сетки', 'error');
}
}
async function clearTimeSlotDateAssignment() {
if (!activeCalendarDay) return;
hideAlert('calendar-day-dialog-time-alert');
const assignment = timeAssignmentForDate(activeCalendarDay.dataset.date);
if (!assignment) {
calendarDayDialogTimeScopeSelect.value = '';
syncSelects(calendarDayDialogTimeScopeSelect);
showAlert('calendar-day-dialog-time-alert', 'Для даты нет ручной сетки', 'success');
return;
}
try {
await api.delete('/api/admin/time-slots/date-assignments/' + assignment.id);
await loadTimeSlotDateAssignments();
updateTimeAssignmentVisuals(activeCalendarDay.dataset.date);
populateTimeScopeDialogSelect(activeCalendarDay.dataset.date);
syncSelects(calendarDayDialogTimeScopeSelect);
showAlert('calendar-day-dialog-time-alert', 'Ручная сетка убрана', 'success');
} catch (error) {
showAlert('calendar-day-dialog-time-alert', error.message || 'Ошибка удаления ручной сетки', 'error');
}
}
function populateTimeScopeDialogSelect(date) {
const assignment = timeAssignmentForDate(date);
const manualScopes = timeSlotScopes.filter(scope => scope.applyMode === 'MANUAL');
const automaticLabel = automaticTimeScopeLabel(date);
calendarDayDialogTimeScopeSelect.innerHTML = `<option value="">${escapeHtml(automaticLabel)}</option>` +
manualScopes.map(scope => `<option value="${scope.id}">${escapeHtml(scope.name)}</option>`).join('');
calendarDayDialogTimeScopeSelect.value = assignment?.scopeId || '';
calendarDayDialogTimeSaveButton.disabled = manualScopes.length === 0 && !assignment;
calendarDayDialogTimeClearButton.disabled = !assignment;
}
function updateTimeAssignmentVisuals(date) {
const assignment = timeAssignmentForDate(date);
calendarGrid.querySelectorAll(`.calendar-day[data-date="${date}"]`).forEach(day => {
day.dataset.timeAssignmentId = assignment?.id || '';
day.dataset.timeScopeId = assignment?.scopeId || '';
const marker = day.querySelector('.calendar-day-time-marker');
if (assignment && !marker) {
day.insertAdjacentHTML('beforeend', '<span class="calendar-day-time-marker">вр.</span>');
}
if (!assignment && marker) {
marker.remove();
}
});
}
function updateCalendarDayVisual(day) {
if (!day) return;
const select = day.querySelector('.calendar-day-activity');
@@ -784,6 +883,20 @@ export async function initAcademicCalendar() {
return activityTypes.find(type => String(type.id) === String(id));
}
function timeAssignmentForDate(date) {
return timeSlotDateAssignments.find(item => item.date === date);
}
function automaticTimeScopeLabel(date) {
const jsDate = new Date(`${date}T00:00:00`);
const jsDay = jsDate.getDay();
const isoDay = jsDay === 0 ? 7 : jsDay;
const weekdayScope = timeSlotScopes.find(scope => scope.applyMode === 'WEEKDAY' && Number(scope.dayOfWeek) === isoDay);
const defaultScope = timeSlotScopes.find(scope => scope.applyMode === 'DEFAULT');
const scopeName = weekdayScope?.name || defaultScope?.name || 'базовая сетка';
return `По правилу даты (${scopeName})`;
}
function semesterLabel(semester) {
const type = normalizeSemesterType(semester.semesterType);
return `${semester.academicYearTitle || '-'}, ${SEMESTER_LABELS[type] || type} (${semester.startDate} - ${semester.endDate})`;