diff --git a/frontend/js/calendar.js b/frontend/js/calendar.js index a51930a..6658980 100644 --- a/frontend/js/calendar.js +++ b/frontend/js/calendar.js @@ -93,10 +93,11 @@ function getViewRange() { let start, end; if (state.currentView === 'month') { - start = new Date(d.getFullYear(), d.getMonth(), 1); - start.setDate(start.getDate() - dayOfWeek(start, weekStartDay) - 1); - end = new Date(d.getFullYear(), d.getMonth() + 1, 0); - end.setDate(end.getDate() + (6 - dayOfWeek(end, weekStartDay)) + 1); + // Rolling view: 5 weeks from the start of currentDate's week + start = weekStart(d, weekStartDay); + start.setDate(start.getDate() - 1); // 1-day buffer + end = new Date(start); + end.setDate(start.getDate() + 37); // 5 weeks + buffer } else if (state.currentView === 'week') { start = weekStart(d, weekStartDay); end = new Date(start); @@ -165,7 +166,17 @@ function updateTitle() { let title = ''; const M = t('months'); if (state.currentView === 'month') { - title = `${M[d.getMonth()]} ${d.getFullYear()}`; + // Show date range of the rolling 5-week window + const ws = weekStart(d, weekStartDay); + const we = new Date(ws); we.setDate(we.getDate() + 34); // last day of 5th week + const Ms = t('months_short'); + if (ws.getFullYear() !== we.getFullYear()) { + title = `${Ms[ws.getMonth()]} ${ws.getFullYear()} – ${Ms[we.getMonth()]} ${we.getFullYear()}`; + } else if (ws.getMonth() !== we.getMonth()) { + title = `${Ms[ws.getMonth()]} – ${Ms[we.getMonth()]} ${we.getFullYear()}`; + } else { + title = `${M[ws.getMonth()]} ${ws.getFullYear()}`; + } } else if (state.currentView === 'week') { const mon = weekStart(d, weekStartDay); const sun = new Date(mon); @@ -507,7 +518,9 @@ function renderCalendarList() { function navigate(dir) { const d = state.currentDate; if (state.currentView === 'month') { - state.currentDate = new Date(d.getFullYear(), d.getMonth() + dir, 1); + // Buttons jump 4 weeks (one screenful) + state.currentDate = new Date(d); + state.currentDate.setDate(d.getDate() + dir * 28); } else if (state.currentView === 'week') { state.currentDate = new Date(d); state.currentDate.setDate(d.getDate() + dir * 7); @@ -551,8 +564,8 @@ function bindTopbar() { const dir = e.deltaY > 0 ? 1 : -1; if (state.currentView === 'agenda') return; if (state.currentView === 'month') { - const d = state.currentDate; - state.currentDate = new Date(d.getFullYear(), d.getMonth() + dir, 1); + state.currentDate = new Date(state.currentDate); + state.currentDate.setDate(state.currentDate.getDate() + dir * 7); fetchAndRender(); } else { navigate(dir); diff --git a/frontend/js/views/month.js b/frontend/js/views/month.js index 1d9de77..0d4db29 100644 --- a/frontend/js/views/month.js +++ b/frontend/js/views/month.js @@ -1,23 +1,23 @@ -import { isToday, isPast, dayOfWeek, getISOWeekNumber } from '../utils.js'; +import { isToday, isPast, dayOfWeek, weekStart, getISOWeekNumber } from '../utils.js'; import { t } from '../i18n.js'; const LANE_H = 20; // px per lane (event height 18px + 2px gap) const MAX_LANES = 3; // max visible lanes per row +const NUM_ROWS = 5; // rolling view: always 5 weeks + export function renderMonth(container, currentDate, events, onDayClick, onEventClick, weekStartDay = 'monday') { - const year = currentDate.getFullYear(); - const month = currentDate.getMonth(); - const DOW = weekStartDay === 'sunday' ? t('dow_sunday') : t('dow_monday'); + // "Primary month" = currentDate's month (used for muting other-month days) + const primaryMonth = currentDate.getMonth(); + const DOW = weekStartDay === 'sunday' ? t('dow_sunday') : t('dow_monday'); - const firstDay = new Date(year, month, 1); + // Rolling grid: start at the week that contains currentDate + const gridStart = weekStart(currentDate, weekStartDay); - // Build 42-cell grid + // Build NUM_ROWS × 7 cells const cells = []; - const gridStart = new Date(firstDay); - const offset = dayOfWeek(firstDay, weekStartDay); - gridStart.setDate(gridStart.getDate() - offset); const d = new Date(gridStart); - for (let i = 0; i < 42; i++) { + for (let i = 0; i < NUM_ROWS * 7; i++) { cells.push(new Date(d)); d.setDate(d.getDate() + 1); } @@ -37,7 +37,7 @@ export function renderMonth(container, currentDate, events, onDayClick, onEventC // Build rows let bodyHtml = ''; - for (let row = 0; row < 6; row++) { + for (let row = 0; row < NUM_ROWS; row++) { const rowCells = cells.slice(row * 7, row * 7 + 7); const rowStart = new Date(rowCells[0]); rowStart.setHours(0, 0, 0, 0); const rowEnd = new Date(rowCells[6]); rowEnd.setHours(0, 0, 0, 0); @@ -119,7 +119,7 @@ export function renderMonth(container, currentDate, events, onDayClick, onEventC let colsHtml = ''; rowCells.forEach(cell => { const key = dateKey(cell); - const isOther = cell.getMonth() !== month; + const isOther = cell.getMonth() !== primaryMonth; const todayCls = isToday(cell) ? 'today' : ''; const otherCls = isOther ? 'other-month' : ''; const numCls = isToday(cell) ? 'today' : '';