import { api } from '../api.js';
import { escapeHtml, showAlert, hideAlert } from '../utils.js';
const ROLE_DEPARTMENT = 'DEPARTMENT';
const TARGET_GROUPS = 'groups';
const TARGET_TEACHERS = 'teachers';
const TARGET_CLASSROOMS = 'classrooms';
const TARGET_DEPARTMENTS = 'departments';
const PARITY_LABELS = {
BOTH: 'каждая',
ODD: 'нечётная',
EVEN: 'чётная'
};
const DAY_SHORT_LABELS = {
1: 'Пн',
2: 'Вт',
3: 'Ср',
4: 'Чт',
5: 'Пт',
6: 'Сб',
7: 'Вс'
};
const DAY_FULL_LABELS = {
1: 'Понедельник',
2: 'Вторник',
3: 'Среда',
4: 'Четверг',
5: 'Пятница',
6: 'Суббота',
7: 'Воскресенье'
};
const DAY_ORDER = [1, 2, 3, 4, 5, 6, 7];
const CELL_PARITY_LAYOUT = [
{ parity: 'ODD', label: 'Нечётная' },
{ parity: 'EVEN', label: 'Чётная' }
];
const MOBILE_QUERY = '(max-width: 760px)';
const DEFAULT_SEMESTER_WEEKS = 22;
export async function initScheduleView() {
const role = localStorage.getItem('role');
const userDepartmentId = normalizeId(localStorage.getItem('departmentId'));
const dateInput = document.getElementById('schedule-view-date');
const targetSelect = document.getElementById('schedule-view-target');
const loadButton = document.getElementById('schedule-view-load');
const previousPeriodButton = document.getElementById('schedule-view-prev-period');
const todayButton = document.getElementById('schedule-view-today');
const nextPeriodButton = document.getElementById('schedule-view-next-period');
const resultPreviousButton = document.getElementById('schedule-view-result-prev');
const resultNextButton = document.getElementById('schedule-view-result-next');
const resultTabs = document.getElementById('schedule-view-result-tabs');
const activeLabel = document.getElementById('schedule-view-active-label');
const countLabel = document.getElementById('schedule-view-count');
const periodLabel = document.getElementById('schedule-view-period-label');
const tableTitle = document.getElementById('schedule-view-table-title');
const tableCaption = document.getElementById('schedule-view-table-caption');
const dayTabs = document.getElementById('schedule-view-day-tabs');
const tables = document.getElementById('schedule-view-tables');
const entityFields = Array.from(document.querySelectorAll('[data-schedule-view-target-field]'));
const departmentSelect = document.getElementById('schedule-view-department');
const state = {
dictionaries: {
groups: [],
teachers: [],
classrooms: [],
departments: []
},
sections: [],
activeSectionIndex: 0,
mobileDay: 1,
range: null,
totalLessons: 0
};
const mobileMedia = window.matchMedia(MOBILE_QUERY);
setDefaultDate(dateInput);
configureDepartmentTarget();
updatePeriodLabel();
updateEntityFieldVisibility();
renderResultNavigation();
targetSelect?.addEventListener('change', () => {
clearInactiveEntityValues();
updateEntityFieldVisibility();
resetResults('Нажмите «Показать», чтобы обновить расписание');
});
loadButton?.addEventListener('click', loadData);
previousPeriodButton?.addEventListener('click', () => movePeriod(-1));
todayButton?.addEventListener('click', () => {
dateInput.value = toIsoDate(new Date());
loadData();
});
nextPeriodButton?.addEventListener('click', () => movePeriod(1));
dateInput?.addEventListener('change', () => {
updatePeriodLabel();
resetResults('Нажмите «Показать», чтобы обновить расписание');
});
resultPreviousButton?.addEventListener('click', () => moveResult(-1));
resultNextButton?.addEventListener('click', () => moveResult(1));
if (typeof mobileMedia.addEventListener === 'function') {
mobileMedia.addEventListener('change', renderActiveSection);
} else if (typeof mobileMedia.addListener === 'function') {
mobileMedia.addListener(renderActiveSection);
}
try {
state.dictionaries = await loadDictionaries(role, userDepartmentId);
configureDepartmentAccess();
updateEntityFieldVisibility();
await loadData();
} catch (error) {
const message = error.message || 'Ошибка загрузки справочников';
showAlert('schedule-view-alert', message, 'error');
resetResults(message);
}
function configureDepartmentTarget() {
if (role !== ROLE_DEPARTMENT) return;
targetSelect.value = TARGET_DEPARTMENTS;
targetSelect.disabled = true;
}
function configureDepartmentAccess() {
if (role !== ROLE_DEPARTMENT) return;
targetSelect.value = TARGET_DEPARTMENTS;
targetSelect.disabled = true;
if (departmentSelect) {
departmentSelect.value = userDepartmentId || '';
departmentSelect.disabled = true;
if (userDepartmentId && departmentSelect.value !== userDepartmentId) {
departmentSelect.insertAdjacentHTML('afterbegin',
``);
departmentSelect.value = userDepartmentId;
}
}
}
function updateEntityFieldVisibility() {
const target = activeTarget();
entityFields.forEach(field => {
field.hidden = field.dataset.scheduleViewTargetField !== target;
});
}
function clearInactiveEntityValues() {
const target = activeTarget();
[
[TARGET_GROUPS, 'schedule-view-group'],
[TARGET_TEACHERS, 'schedule-view-teacher'],
[TARGET_CLASSROOMS, 'schedule-view-classroom'],
[TARGET_DEPARTMENTS, 'schedule-view-department']
].forEach(([fieldTarget, id]) => {
if (fieldTarget === target) return;
if (role === ROLE_DEPARTMENT && id === 'schedule-view-department') return;
const select = document.getElementById(id);
if (select) select.value = '';
});
}
function activeTarget() {
if (role === ROLE_DEPARTMENT) return TARGET_DEPARTMENTS;
return targetSelect?.value || TARGET_GROUPS;
}
async function movePeriod(direction) {
if (!dateInput.value) {
dateInput.value = toIsoDate(new Date());
}
const range = twoWeekRange(dateInput.value);
const nextDate = addDays(parseIsoDate(range.startDate), direction * 14);
dateInput.value = toIsoDate(nextDate);
await loadData();
}
async function loadData() {
hideAlert('schedule-view-alert');
if (!dateInput.value) {
const message = 'Выберите дату в периоде';
showAlert('schedule-view-alert', message, 'error');
resetResults(message);
return;
}
if (role === ROLE_DEPARTMENT && !userDepartmentId) {
const message = 'Не удалось определить кафедру пользователя';
showAlert('schedule-view-alert', message, 'error');
resetResults(message);
return;
}
const target = activeTarget();
const range = twoWeekRange(dateInput.value);
const previousKey = state.sections[state.activeSectionIndex]?.key;
state.range = range;
updatePeriodLabel(range);
setTableState('Загрузка...');
setActiveSummary('Загрузка расписаний...');
countLabel.textContent = 'Загрузка...';
resultTabs.innerHTML = '';
dayTabs.hidden = true;
dayTabs.innerHTML = '';
const params = new URLSearchParams({
startDate: range.startDate,
endDate: range.endDate
});
applyTargetFilter(params, target);
addOptional(params, 'subjectId', valueOf('schedule-view-subject'));
addOptional(params, 'lessonTypeId', valueOf('schedule-view-lesson-type'));
addOptional(params, 'parity', valueOf('schedule-view-parity'));
try {
const lessons = await api.get(`/api/schedule/search?${params}`);
renderLessons(lessons, range, target, previousKey);
} catch (error) {
const message = error.message || 'Ошибка загрузки расписания';
showAlert('schedule-view-alert', message, 'error');
resetResults(message);
}
}
function applyTargetFilter(params, target) {
if (role === ROLE_DEPARTMENT) {
params.set('departmentId', userDepartmentId);
return;
}
if (target === TARGET_GROUPS) {
addOptional(params, 'groupId', valueOf('schedule-view-group'));
return;
}
if (target === TARGET_TEACHERS) {
addOptional(params, 'teacherId', valueOf('schedule-view-teacher'));
return;
}
if (target === TARGET_CLASSROOMS) {
addOptional(params, 'classroomId', valueOf('schedule-view-classroom'));
return;
}
if (target === TARGET_DEPARTMENTS) {
addOptional(params, 'departmentId', valueOf('schedule-view-department'));
}
}
function renderLessons(lessons, range, target, previousKey) {
state.totalLessons = lessons.length;
if (!lessons.length) {
state.sections = [];
state.activeSectionIndex = 0;
renderResultNavigation();
tableTitle.textContent = 'Расписание';
tableCaption.textContent = periodCaption(range);
setTableState('Занятия не найдены');
countLabel.textContent = '0 занятий';
return;
}
const sections = buildScheduleSections(lessons, splitTypeForTarget(target), buildSectionContext(target));
if (!sections.length) {
state.sections = [];
state.activeSectionIndex = 0;
renderResultNavigation();
tableTitle.textContent = 'Расписание';
tableCaption.textContent = periodCaption(range);
setTableState('Нет данных для выбранного режима просмотра');
countLabel.textContent = lessonCountLabel(lessons.length);
return;
}
state.sections = sections;
state.activeSectionIndex = Math.max(0, sections.findIndex(section => section.key === previousKey));
if (state.activeSectionIndex < 0) {
state.activeSectionIndex = 0;
}
state.mobileDay = preferredMobileDay(range);
renderResultNavigation();
renderActiveSection();
}
function buildSectionContext(target) {
const selectedGroupId = target === TARGET_GROUPS ? normalizeId(valueOf('schedule-view-group')) : '';
const selectedDepartmentId = role === ROLE_DEPARTMENT
? userDepartmentId
: (target === TARGET_DEPARTMENTS ? normalizeId(valueOf('schedule-view-department')) : '');
const allowedGroupIds = new Set();
if (selectedGroupId) {
allowedGroupIds.add(selectedGroupId);
} else if (selectedDepartmentId) {
state.dictionaries.groups
.filter(group => String(group.departmentId) === String(selectedDepartmentId))
.forEach(group => allowedGroupIds.add(String(group.id)));
}
return {
selectedGroupId,
selectedGroupName: selectedGroupId ? groupNameById(selectedGroupId) : '',
allowedGroupIds,
groupNamesById: new Map(state.dictionaries.groups.map(group => [String(group.id), group.name]))
};
}
function splitTypeForTarget(target) {
if (target === TARGET_TEACHERS) return TARGET_TEACHERS;
if (target === TARGET_CLASSROOMS) return TARGET_CLASSROOMS;
return TARGET_GROUPS;
}
function renderResultNavigation() {
const section = activeSection();
const totalSections = state.sections.length;
countLabel.textContent = state.totalLessons
? `${lessonCountLabel(state.totalLessons)}, ${sectionCountLabel(totalSections)}`
: '0 занятий';
resultPreviousButton.disabled = totalSections < 2;
resultNextButton.disabled = totalSections < 2;
if (!section) {
setActiveSummary('Нет выбранного расписания');
resultTabs.innerHTML = 'Ничего не найдено';
return;
}
setActiveSummary(`${section.title} · ${lessonCountLabel(section.lessons.length)} · ${state.activeSectionIndex + 1} из ${totalSections}`);
resultTabs.innerHTML = state.sections.map((item, index) => `
`).join('');
resultTabs.querySelectorAll('[data-schedule-view-section]').forEach(button => {
button.addEventListener('click', () => {
state.activeSectionIndex = Number(button.dataset.scheduleViewSection);
state.mobileDay = preferredMobileDay(state.range);
renderResultNavigation();
renderActiveSection();
});
});
}
function setActiveSummary(text) {
activeLabel.textContent = text;
}
function moveResult(direction) {
if (state.sections.length < 2) return;
state.activeSectionIndex = (state.activeSectionIndex + direction + state.sections.length) % state.sections.length;
state.mobileDay = preferredMobileDay(state.range);
renderResultNavigation();
renderActiveSection();
}
function activeSection() {
return state.sections[state.activeSectionIndex] || null;
}
function renderActiveSection() {
const section = activeSection();
if (!section || !state.range) {
dayTabs.hidden = true;
dayTabs.innerHTML = '';
return;
}
tableTitle.textContent = section.title;
tableCaption.textContent = `${lessonCountLabel(section.lessons.length)} · ${periodCaption(state.range)}`;
if (mobileMedia.matches) {
renderMobileDayTabs();
renderMobileSchedule(section, state.range);
return;
}
dayTabs.hidden = true;
dayTabs.innerHTML = '';
renderDesktopSchedule(section, state.range);
}
function renderDesktopSchedule(section, range) {
const slots = collectSlots(section.lessons);
const lessonsByDateSlot = groupLessonsByDateSlot(section.lessons);
const blocks = buildCombinedWeekBlocks(range.startDate, range.endDate, section.lessons);
if (!blocks.length || !slots.length) {
setTableState('Занятия не найдены');
return;
}
tables.innerHTML = blocks
.map(block => renderCombinedWeekTable(block, slots, lessonsByDateSlot, section))
.join('');
}
function renderMobileDayTabs() {
dayTabs.hidden = false;
dayTabs.innerHTML = DAY_ORDER.map(day => `
`).join('');
dayTabs.querySelectorAll('[data-schedule-view-day]').forEach(button => {
button.addEventListener('click', () => {
state.mobileDay = Number(button.dataset.scheduleViewDay);
renderActiveSection();
});
});
}
function renderMobileSchedule(section, range) {
const slots = collectSlots(section.lessons);
const lessonsByDateSlot = groupLessonsByDateSlot(section.lessons);
const blocks = buildCombinedWeekBlocks(range.startDate, range.endDate, section.lessons);
if (!blocks.length || !slots.length) {
setTableState('Занятия не найдены');
return;
}
tables.innerHTML = blocks.map(block => renderMobileDayBlock(block, slots, lessonsByDateSlot)).join('');
}
function renderMobileDayBlock(block, slots, lessonsByDateSlot) {
const dayDates = block.datesByDay[state.mobileDay] || {};
const rows = slots.map(slot => {
const states = slotStatesForDates(dayDates, slot, lessonsByDateSlot);
return `
| Время | ${dayHeaders}
|---|