const MOSCOW_TIMEZONE = 'Europe/Moscow'; const WEEKDAYS = ['Понедельник', 'Вторник', 'Среда', 'Четверг', 'Пятница', 'Суббота', 'Воскресенье']; const MONTHS = ['января', 'февраля', 'марта', 'апреля', 'мая', 'июня', 'июля', 'августа', 'сентября', 'октября', 'ноября', 'декабря']; function getMoscowDateString() { const formatter = new Intl.DateTimeFormat('en-CA', { timeZone: MOSCOW_TIMEZONE, year: 'numeric', month: '2-digit', day: '2-digit' }); return formatter.format(new Date()); } function parseISODate(value) { const [year, month, day] = value.split('-').map(Number); return new Date(year, month - 1, day); } function formatISODate(date) { return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`; } function addDays(dateStr, days) { const date = parseISODate(dateStr); date.setDate(date.getDate() + days); return formatISODate(date); } function formatDate(dateStr) { const date = parseISODate(dateStr); return `${WEEKDAYS[(date.getDay() + 6) % 7]}, ${date.getDate()} ${MONTHS[date.getMonth()]}`; } function calculateEndTime(startTime, durationMin) { const [hour, minute] = startTime.split(':').map(Number); const totalMinutes = hour * 60 + minute + durationMin; const endHour = Math.floor(totalMinutes / 60); const endMinute = totalMinutes % 60; return `${String(endHour).padStart(2, '0')}:${String(endMinute).padStart(2, '0')}`; } function renderDay(container, titleEl, dateStr, items, options = {}) { let titleText = formatDate(dateStr); if (options.today) { titleText = `Сегодня, ${titleText.toLowerCase()}`; } else if (options.tomorrow) { titleText = `Завтра, ${titleText.toLowerCase()}`; } titleEl.textContent = titleText; const tasks = items.filter((item) => item.kind === 'task'); const events = items .filter((item) => item.kind === 'event') .sort((a, b) => a.start_time.localeCompare(b.start_time)); if (!tasks.length && !events.length) { container.innerHTML = '
На этот день расписания нет.
'; return; } const tasksHtml = tasks.length ? `
${tasks.map((task) => `
${task.repeat_weekly ? 'Каждую неделю: ' : ''}${escapeHtml(task.title)}
`).join('')}
` : ''; const eventsHtml = events.length ? `
${events.map((event) => `
${event.start_time}-${calculateEndTime(event.start_time, event.duration_min)} ${escapeHtml(event.title)}
`).join('')}
` : ''; container.innerHTML = `${tasksHtml}${eventsHtml}`; } function escapeHtml(value) { return String(value) .replaceAll('&', '&') .replaceAll('<', '<') .replaceAll('>', '>') .replaceAll('"', '"') .replaceAll("'", '''); } async function loadSchedule() { const today = getMoscowDateString(); const tomorrow = addDays(today, 1); const response = await fetch(`/api/schedule?from_date=${today}&to_date=${tomorrow}`); if (!response.ok) { throw new Error('Не удалось загрузить расписание'); } const data = await response.json(); renderDay( document.getElementById('today-content'), document.getElementById('today-title'), today, data.items.filter((item) => item.date === today), { today: true } ); renderDay( document.getElementById('tomorrow-content'), document.getElementById('tomorrow-title'), tomorrow, data.items.filter((item) => item.date === tomorrow), { tomorrow: true } ); } loadSchedule().catch((error) => console.error(error)); setInterval(() => loadSchedule().catch((error) => console.error(error)), 5 * 60 * 1000);