export function renderScheduleOverviewState(container, { kind = 'loading', message = '', onRetry = null } = {}) {
if (!container) return;
const labels = {
loading: message || 'Загружаем ближайшие занятия…',
empty: message || 'В ближайшие дни занятий нет',
error: message || 'Не удалось загрузить ближайшие занятия'
};
container.innerHTML = `
${kind === 'loading' ? '' : ''}
${escapeHtml(labels[kind] || message)}
${kind === 'error' && onRetry ? '' : ''}
`;
container.querySelector('[data-overview-retry]')?.addEventListener('click', onRetry);
}
export function renderScheduleOverview(container, lessons, { role = 'student', now = new Date(), onGoToday = null } = {}) {
if (!container) return { todayCount: 0, hasNext: false };
const { todayIso, todayLessons, nextLesson } = buildScheduleOverviewModel(lessons, now);
container.innerHTML = `
Сегодня
${todayLessons.length ? pluralLessons(todayLessons.length) : 'Свободный день'}
${todayLessons.length}
${todayLessons.length
? todayLessons.map(lesson => renderCompactLesson(lesson, role)).join('')
: '
На сегодня занятий нет.
'}
Следующая пара
${nextLesson ? escapeHtml(relativeDate(nextLesson.date, todayIso)) : 'Пока ничего не запланировано'}
${nextLesson ? `
${lessonStart(nextLesson) <= now ? 'Идёт сейчас' : escapeHtml(trimTime(nextLesson.startTime))}` : ''}
${nextLesson ? renderNextLesson(nextLesson, role) : 'В ближайшие 14 дней занятий нет.
'}
`;
container.querySelector('[data-overview-today]')?.addEventListener('click', onGoToday || (() => {}));
return { todayCount: todayLessons.length, hasNext: Boolean(nextLesson) };
}
export function buildScheduleOverviewModel(lessons, now = new Date()) {
const ordered = [...(lessons || [])].sort((left, right) => lessonStart(left) - lessonStart(right));
const todayIso = toIsoDate(now);
return {
todayIso,
todayLessons: ordered.filter(lesson => lesson.date === todayIso),
nextLesson: ordered.find(lesson => lessonEnd(lesson) >= now) || null
};
}
function renderCompactLesson(lesson, role) {
return `
${escapeHtml(lesson.subjectName || 'Занятие')}
${escapeHtml(lessonMeta(lesson, role).join(' · ') || 'Подробности уточняются')}
`;
}
function renderNextLesson(lesson, role) {
return `
${escapeHtml(trimTime(lesson.startTime))}
до ${escapeHtml(trimTime(lesson.endTime))}
${escapeHtml(lesson.subjectName || 'Занятие')}
${escapeHtml(lessonMeta(lesson, role).join(' · ') || 'Подробности уточняются')}
${lesson.lessonTypeName ? `
${escapeHtml(lesson.lessonTypeName)}` : ''}
`;
}
function lessonMeta(lesson, role) {
const groups = Array.isArray(lesson.groupNames) ? lesson.groupNames.join(', ') : '';
const subgroup = Array.isArray(lesson.subgroupNames) ? lesson.subgroupNames.join(', ') : lesson.subgroupName;
return role === 'teacher'
? [groups || subgroup, lesson.classroomName].filter(Boolean)
: [lesson.teacherName, lesson.classroomName, subgroup].filter(Boolean);
}
function lessonStart(lesson) {
return new Date(`${lesson.date}T${normalizeTime(lesson.startTime)}`);
}
function lessonEnd(lesson) {
return new Date(`${lesson.date}T${normalizeTime(lesson.endTime || lesson.startTime)}`);
}
function normalizeTime(value) {
const text = String(value || '00:00:00');
return text.length === 5 ? `${text}:00` : text;
}
function relativeDate(value, todayIso) {
if (value === todayIso) return 'Сегодня';
const today = parseIsoDate(todayIso);
const target = parseIsoDate(value);
const dayDifference = Math.round((target - today) / 86400000);
if (dayDifference === 1) return 'Завтра';
return target.toLocaleDateString('ru-RU', { weekday: 'long', day: 'numeric', month: 'long' });
}
function parseIsoDate(value) {
const [year, month, day] = String(value).split('-').map(Number);
return new Date(year, month - 1, day);
}
function toIsoDate(date) {
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`;
}
function formatLongDate(date) {
const value = date.toLocaleDateString('ru-RU', { weekday: 'long', day: 'numeric', month: 'long' });
return value.charAt(0).toUpperCase() + value.slice(1);
}
function pluralLessons(count) {
const mod10 = count % 10;
const mod100 = count % 100;
const noun = mod10 === 1 && mod100 !== 11 ? 'занятие' : mod10 >= 2 && mod10 <= 4 && (mod100 < 12 || mod100 > 14) ? 'занятия' : 'занятий';
return `${count} ${noun}`;
}
function trimTime(value) {
return String(value || '').slice(0, 5);
}
function escapeHtml(value) {
return String(value ?? '')
.replaceAll('&', '&')
.replaceAll('<', '<')
.replaceAll('>', '>')
.replaceAll('"', '"')
.replaceAll("'", ''');
}