From 8bde59e9535fb52fa4fab31545ad1f7c0d1606c1 Mon Sep 17 00:00:00 2001 From: vrubel Date: Tue, 30 Dec 2025 13:07:17 +0300 Subject: [PATCH] Refactor date handling in schedule script to ensure correct week calculations and improve error handling. Added validation for week start and end days, enhanced date formatting, and restricted date selection in task/event modals to current and next week. Updated navigation buttons to reflect current week status. --- frontend/admin/static/script.js | 302 +++++++++++++++++++++++++++----- 1 file changed, 255 insertions(+), 47 deletions(-) diff --git a/frontend/admin/static/script.js b/frontend/admin/static/script.js index 165c985..d25e91b 100644 --- a/frontend/admin/static/script.js +++ b/frontend/admin/static/script.js @@ -19,56 +19,153 @@ function getWeekStart(dateStr = null) { if (!dateStr) { dateStr = getTodayInLondon(); } - const date = new Date(dateStr + 'T00:00:00'); - const day = date.getDay(); - const diff = date.getDate() - day + (day === 0 ? -6 : 1); // Понедельник = 1 - const monday = new Date(date.setDate(diff)); - return monday.toISOString().split('T')[0]; + // Создаем дату из строки YYYY-MM-DD в локальном времени + const [year, month, day] = dateStr.split('-').map(Number); + const date = new Date(year, month - 1, day); + // getDay() возвращает 0=воскресенье, 1=понедельник, ..., 6=суббота + // Находим понедельник текущей недели + const dayOfWeek = date.getDay(); + // Если воскресенье (0), идем на 6 дней назад, иначе на (dayOfWeek-1) дней назад + const diff = dayOfWeek === 0 ? -6 : -(dayOfWeek - 1); + const monday = new Date(date); + monday.setDate(date.getDate() + diff); + // Проверяем, что получился понедельник + if (monday.getDay() !== 1) { + console.error('Error: getWeekStart did not return Monday!', monday.getDay()); + } + // Возвращаем в формате YYYY-MM-DD + const yearStr = monday.getFullYear(); + const monthStr = String(monday.getMonth() + 1).padStart(2, '0'); + const dayStr = String(monday.getDate()).padStart(2, '0'); + return `${yearStr}-${monthStr}-${dayStr}`; } function formatDate(dateStr) { - const date = new Date(dateStr + 'T00:00:00'); - return `${weekdaysShort[date.getDay()]}, ${date.getDate()}`; + // Создаем дату из строки YYYY-MM-DD в локальном времени + const [year, month, day] = dateStr.split('-').map(Number); + const date = new Date(year, month - 1, day); + // getDay() возвращает 0=воскресенье, 1=понедельник, ..., 6=суббота + // Конвертируем в формат где 0=понедельник, 6=воскресенье + const jsDay = date.getDay(); + const weekdayIndex = jsDay === 0 ? 6 : jsDay - 1; // 0=пн, 6=вс + return `${weekdaysShort[weekdayIndex]}, ${date.getDate()}`; } function formatDateFull(dateStr) { - const date = new Date(dateStr + 'T00:00:00'); + // Создаем дату из строки YYYY-MM-DD в локальном времени + const [year, month, day] = dateStr.split('-').map(Number); + const date = new Date(year, month - 1, day); const months = ['января', 'февраля', 'марта', 'апреля', 'мая', 'июня', 'июля', 'августа', 'сентября', 'октября', 'ноября', 'декабря']; - return `${weekdays[date.getDay()]}, ${date.getDate()} ${months[date.getMonth()]}`; + // getDay() возвращает 0=воскресенье, 1=понедельник, ..., 6=суббота + // Конвертируем в формат где 0=понедельник, 6=воскресенье + const jsDay = date.getDay(); + const weekdayIndex = jsDay === 0 ? 6 : jsDay - 1; // 0=пн, 6=вс + return `${weekdays[weekdayIndex]}, ${date.getDate()} ${months[date.getMonth()]}`; } function getWeekDates(startDate) { + // startDate должен быть понедельником (в формате YYYY-MM-DD) const dates = []; - const start = new Date(startDate + 'T00:00:00'); + // Создаем дату из строки в локальном времени + const [year, month, day] = startDate.split('-').map(Number); + const start = new Date(year, month - 1, day); + // Убеждаемся, что это понедельник + const dayOfWeek = start.getDay(); + if (dayOfWeek !== 1) { + // Если не понедельник, находим понедельник + const diff = dayOfWeek === 0 ? -6 : -(dayOfWeek - 1); + start.setDate(start.getDate() + diff); + } + // Возвращаем 7 дней: понедельник - воскресенье + const baseDate = new Date(start); for (let i = 0; i < 7; i++) { - const date = new Date(start); - date.setDate(start.getDate() + i); - dates.push(date.toISOString().split('T')[0]); + const date = new Date(baseDate); + date.setDate(baseDate.getDate() + i); + // Форматируем в YYYY-MM-DD + const yearStr = date.getFullYear(); + const monthStr = String(date.getMonth() + 1).padStart(2, '0'); + const dayStr = String(date.getDate()).padStart(2, '0'); + dates.push(`${yearStr}-${monthStr}-${dayStr}`); + } + // Проверяем, что первый день - понедельник, последний - воскресенье + const [firstYear, firstMonth, firstDayNum] = dates[0].split('-').map(Number); + const firstDate = new Date(firstYear, firstMonth - 1, firstDayNum); + const [lastYear, lastMonth, lastDayNum] = dates[6].split('-').map(Number); + const lastDate = new Date(lastYear, lastMonth - 1, lastDayNum); + const firstDay = firstDate.getDay(); + const lastDay = lastDate.getDay(); + if (firstDay !== 1 || lastDay !== 0) { + console.error('Error: Week should start on Monday and end on Sunday!', { + first: dates[0], firstDay, + last: dates[6], lastDay + }); } return dates; } async function loadSchedule() { + // Убеждаемся, что currentWeekStart - это понедельник + if (!currentWeekStart) { + currentWeekStart = getWeekStart(); + } else { + currentWeekStart = getWeekStart(currentWeekStart); + } + const weekDates = getWeekDates(currentWeekStart); const fromDate = weekDates[0]; const toDate = weekDates[6]; - console.log('Loading schedule from', fromDate, 'to', toDate); + // Проверяем, что неделя начинается с понедельника и заканчивается воскресеньем + const [mondayYear, mondayMonth, mondayDay] = weekDates[0].split('-').map(Number); + const monday = new Date(mondayYear, mondayMonth - 1, mondayDay); + const [sundayYear, sundayMonth, sundayDay] = weekDates[6].split('-').map(Number); + const sunday = new Date(sundayYear, sundayMonth - 1, sundayDay); + if (monday.getDay() !== 1 || sunday.getDay() !== 0) { + console.error('Week should start on Monday and end on Sunday! Fixing...', { + monday: weekDates[0], mondayDay: monday.getDay(), + sunday: weekDates[6], sundayDay: sunday.getDay() + }); + // Исправляем автоматически без рекурсии + currentWeekStart = getWeekStart(); + const correctedWeekDates = getWeekDates(currentWeekStart); + // Используем исправленные даты напрямую + const correctedFromDate = correctedWeekDates[0]; + const correctedToDate = correctedWeekDates[6]; + + try { + const response = await fetch(`/api/schedule?from_date=${correctedFromDate}&to_date=${correctedToDate}`); + const data = await response.json(); + renderSchedule(correctedWeekDates, data.items); + document.getElementById('week-range').textContent = + `${formatDateFull(correctedWeekDates[0])} - ${formatDateFull(correctedWeekDates[6])}`; + updateWeekNavigationButtons(); + } catch (error) { + console.error('Error loading schedule:', error); + } + return; + } try { const response = await fetch(`/api/schedule?from_date=${fromDate}&to_date=${toDate}`); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } const data = await response.json(); - console.log('Received items:', data.items); + if (!data.items) { + console.error('Invalid response format:', data); + return; + } renderSchedule(weekDates, data.items); // Обновляем заголовок недели - const startDate = new Date(weekDates[0] + 'T00:00:00'); - const endDate = new Date(weekDates[6] + 'T00:00:00'); document.getElementById('week-range').textContent = `${formatDateFull(weekDates[0])} - ${formatDateFull(weekDates[6])}`; + + // Обновляем кнопки навигации + updateWeekNavigationButtons(); } catch (error) { console.error('Error loading schedule:', error); } @@ -76,21 +173,14 @@ async function loadSchedule() { function renderSchedule(weekDates, items) { const grid = document.getElementById('schedule-grid'); + if (!grid) { + console.error('Schedule grid not found!'); + return; + } grid.innerHTML = ''; - console.log('Rendering schedule for dates:', weekDates); - console.log('All items:', items); - weekDates.forEach(dateStr => { - const dayItems = items.filter(item => { - const match = item.date === dateStr; - if (!match && items.length > 0) { - console.log(`Item date ${item.date} !== ${dateStr}`); - } - return match; - }); - console.log(`Date ${dateStr}: ${dayItems.length} items`); - const date = new Date(dateStr + 'T00:00:00'); + const dayItems = items.filter(item => item.date === dateStr); const column = document.createElement('div'); column.className = 'day-column'; @@ -107,14 +197,12 @@ function renderSchedule(weekDates, items) { } function renderDayItems(items) { - console.log('Rendering items for day:', items); if (items.length === 0) { return '
Нет записей
'; } const tasks = items.filter(item => item.kind === 'task'); const events = items.filter(item => item.kind === 'event'); - console.log('Tasks:', tasks, 'Events:', events); let html = ''; @@ -168,10 +256,19 @@ function closeModal() { } function showAddTaskModal(selectedDate = null) { - const weekDates = getWeekDates(currentWeekStart); const today = getTodayInLondon(); - const todayDate = new Date(today + 'T00:00:00'); - const selectedDateObj = selectedDate ? new Date(selectedDate + 'T00:00:00') : todayDate; + const todayWeekStart = getWeekStart(today); + const nextWeekStart = new Date(todayWeekStart + 'T00:00:00'); + nextWeekStart.setDate(nextWeekStart.getDate() + 7); + const nextWeekStartStr = nextWeekStart.toISOString().split('T')[0]; + + // Ограничиваем выбор дат: только текущая и следующая неделя + const minDate = todayWeekStart; + const maxDate = new Date(nextWeekStartStr + 'T00:00:00'); + maxDate.setDate(maxDate.getDate() + 6); // Воскресенье следующей недели + const maxDateStr = maxDate.toISOString().split('T')[0]; + + const selectedDateObj = selectedDate ? new Date(selectedDate + 'T00:00:00') : new Date(today + 'T00:00:00'); // getDay() возвращает 0=воскресенье, 1=понедельник, ..., 6=суббота // Конвертируем в формат 0=понедельник, 6=воскресенье const jsWeekday = selectedDateObj.getDay(); @@ -188,7 +285,7 @@ function showAddTaskModal(selectedDate = null) {
- +
@@ -253,7 +350,10 @@ function showAddTaskModal(selectedDate = null) { const result = await response.json(); console.log('Task created:', result); closeModal(); - await loadSchedule(); + // Небольшая задержка перед обновлением, чтобы БД успела сохранить + setTimeout(() => { + loadSchedule().catch(err => console.error('Error reloading schedule:', err)); + }, 100); } else { const error = await response.json(); console.error('Error creating task:', error); @@ -268,12 +368,23 @@ function showAddTaskModal(selectedDate = null) { function showAddEventModal(selectedDate = null) { const today = getTodayInLondon(); + const todayWeekStart = getWeekStart(today); + const nextWeekStart = new Date(todayWeekStart + 'T00:00:00'); + nextWeekStart.setDate(nextWeekStart.getDate() + 7); + const nextWeekStartStr = nextWeekStart.toISOString().split('T')[0]; + + // Ограничиваем выбор дат: только текущая и следующая неделя + const minDate = todayWeekStart; + const maxDate = new Date(nextWeekStartStr + 'T00:00:00'); + maxDate.setDate(maxDate.getDate() + 6); // Воскресенье следующей недели + const maxDateStr = maxDate.toISOString().split('T')[0]; + const content = `

Добавить занятие

- +
@@ -352,7 +463,10 @@ function showAddEventModal(selectedDate = null) { const result = await response.json(); console.log('Event created:', result); closeModal(); - await loadSchedule(); + // Небольшая задержка перед обновлением, чтобы БД успела сохранить + setTimeout(() => { + loadSchedule().catch(err => console.error('Error reloading schedule:', err)); + }, 100); } else { const error = await response.json(); console.error('Error creating event:', error); @@ -379,32 +493,126 @@ async function deleteItem(id, kind) { }); if (response.ok) { - loadSchedule(); + // Небольшая задержка перед обновлением + setTimeout(() => { + loadSchedule().catch(err => console.error('Error reloading schedule:', err)); + }, 100); } else { - alert('Ошибка при удалении'); + const error = await response.json(); + alert('Ошибка при удалении: ' + (error.detail || 'Неизвестная ошибка')); } } catch (error) { - alert('Ошибка при удалении'); + alert('Ошибка при удалении: ' + error.message); console.error(error); } } +function updateWeekNavigationButtons() { + const today = getTodayInLondon(); + const todayWeekStart = getWeekStart(today); + + // Вычисляем следующую неделю + const [year, month, day] = todayWeekStart.split('-').map(Number); + const nextWeekStartDate = new Date(year, month - 1, day); + nextWeekStartDate.setDate(nextWeekStartDate.getDate() + 7); + const nextWeekStartStr = `${nextWeekStartDate.getFullYear()}-${String(nextWeekStartDate.getMonth() + 1).padStart(2, '0')}-${String(nextWeekStartDate.getDate()).padStart(2, '0')}`; + + // Создаем даты для сравнения + const [currYear, currMonth, currDay] = currentWeekStart.split('-').map(Number); + const currentWeekStartDate = new Date(currYear, currMonth - 1, currDay); + + const [todayYear, todayMonth, todayDay] = todayWeekStart.split('-').map(Number); + const todayWeekStartDate = new Date(todayYear, todayMonth - 1, todayDay); + + const [nextYear, nextMonth, nextDay] = nextWeekStartStr.split('-').map(Number); + const nextWeekStartDateObj = new Date(nextYear, nextMonth - 1, nextDay); + + // Отключаем кнопку "предыдущая неделя", если уже на текущей неделе + const prevButton = document.getElementById('prev-week'); + if (currentWeekStartDate.getTime() <= todayWeekStartDate.getTime()) { + prevButton.disabled = true; + prevButton.style.opacity = '0.5'; + prevButton.style.cursor = 'not-allowed'; + } else { + prevButton.disabled = false; + prevButton.style.opacity = '1'; + prevButton.style.cursor = 'pointer'; + } + + // Отключаем кнопку "следующая неделя", если уже на следующей неделе + const nextButton = document.getElementById('next-week'); + if (currentWeekStartDate.getTime() >= nextWeekStartDateObj.getTime()) { + nextButton.disabled = true; + nextButton.style.opacity = '0.5'; + nextButton.style.cursor = 'not-allowed'; + } else { + nextButton.disabled = false; + nextButton.style.opacity = '1'; + nextButton.style.cursor = 'pointer'; + } +} + // Инициализация document.addEventListener('DOMContentLoaded', () => { currentWeekStart = getWeekStart(); loadSchedule(); + updateWeekNavigationButtons(); document.getElementById('prev-week').addEventListener('click', () => { - const date = new Date(currentWeekStart + 'T00:00:00'); - date.setDate(date.getDate() - 7); - currentWeekStart = date.toISOString().split('T')[0]; + const today = getTodayInLondon(); + const todayWeekStart = getWeekStart(today); + + // Создаем даты для сравнения + const [currYear, currMonth, currDay] = currentWeekStart.split('-').map(Number); + const currentWeekStartDate = new Date(currYear, currMonth - 1, currDay); + + const [todayYear, todayMonth, todayDay] = todayWeekStart.split('-').map(Number); + const todayWeekStartDate = new Date(todayYear, todayMonth - 1, todayDay); + + // Разрешаем только текущую и следующую неделю + // Если текущая неделя - это сегодняшняя неделя, не разрешаем идти назад + if (currentWeekStartDate.getTime() <= todayWeekStartDate.getTime()) { + console.log('Already on current week, cannot go back'); + return; // Уже на текущей неделе, нельзя идти назад + } + + // Переходим на предыдущую неделю + const prevWeekDate = new Date(currYear, currMonth - 1, currDay); + prevWeekDate.setDate(prevWeekDate.getDate() - 7); + currentWeekStart = `${prevWeekDate.getFullYear()}-${String(prevWeekDate.getMonth() + 1).padStart(2, '0')}-${String(prevWeekDate.getDate()).padStart(2, '0')}`; + // Убеждаемся, что это понедельник + currentWeekStart = getWeekStart(currentWeekStart); + console.log('Moving to previous week:', currentWeekStart); loadSchedule(); }); document.getElementById('next-week').addEventListener('click', () => { - const date = new Date(currentWeekStart + 'T00:00:00'); - date.setDate(date.getDate() + 7); - currentWeekStart = date.toISOString().split('T')[0]; + const today = getTodayInLondon(); + const todayWeekStart = getWeekStart(today); + + // Вычисляем следующую неделю от текущей + const [year, month, day] = currentWeekStart.split('-').map(Number); + const nextWeekDate = new Date(year, month - 1, day); + nextWeekDate.setDate(nextWeekDate.getDate() + 7); + const nextWeekStartStr = `${nextWeekDate.getFullYear()}-${String(nextWeekDate.getMonth() + 1).padStart(2, '0')}-${String(nextWeekDate.getDate()).padStart(2, '0')}`; + + // Вычисляем максимально допустимую следующую неделю (от сегодня) + const [todayYear, todayMonth, todayDay] = todayWeekStart.split('-').map(Number); + const maxNextWeekDate = new Date(todayYear, todayMonth - 1, todayDay); + maxNextWeekDate.setDate(maxNextWeekDate.getDate() + 7); + const maxNextWeekStr = `${maxNextWeekDate.getFullYear()}-${String(maxNextWeekDate.getMonth() + 1).padStart(2, '0')}-${String(maxNextWeekDate.getDate()).padStart(2, '0')}`; + + // Разрешаем только текущую и следующую неделю + if (nextWeekStartStr > maxNextWeekStr) { + console.log('Already on next week, cannot go further'); + return; // Уже на следующей неделе, нельзя идти дальше + } + + // Переходим на следующую неделю + currentWeekStart = nextWeekStartStr; + // Убеждаемся, что это понедельник + currentWeekStart = getWeekStart(currentWeekStart); + console.log('Moving to next week:', currentWeekStart); loadSchedule(); });