import calendarUtil from "../calendarUtil.js" import "./DesktopMonthGrid.js" class DesktopMonthView extends Shadow { constructor(calendars, events, currentDate, weekStartsOn = 0, onEventClick = null, onDayDoubleClick = null) { super() this.calendars = calendars; this.events = events; this.currentDate = currentDate; this.weekStartsOn = weekStartsOn; this.onEventClick = onEventClick; this.onDayDoubleClick = onDayDoubleClick; } render() { const weeks = this.buildMonthWeeks(); DesktopMonthGrid(weeks, this.calendars, this.weekStartsOn, this.onEventClick, this.onDayDoubleClick) .width(100, pct) .height(100, pct) } buildMonthWeeks() { const month = this.currentDate.getMonth(); const allDays = calendarUtil.buildMonthGrid(this.currentDate, this.weekStartsOn); const gridStart = allDays[0]; const weeks = []; for (let w = 0; w < 6; w++) { weeks.push(allDays.slice(w * 7, w * 7 + 7)); } const gridEnd = calendarUtil.endOfDay(weeks[weeks.length - 1][6]); const monthStart = calendarUtil.startOfDay(new Date(this.currentDate.getFullYear(), this.currentDate.getMonth(), 1)); const monthEnd = calendarUtil.endOfDay(new Date(this.currentDate.getFullYear(), this.currentDate.getMonth() + 1, 0)); const expanded = calendarUtil.expandRecurringEvents(this.events, gridStart, gridEnd); const relevantEvents = expanded.filter(event => calendarUtil.rangesOverlap(event.time_start, event.time_end, gridStart, gridEnd) && calendarUtil.rangesOverlap(event.time_start, event.time_end, monthStart, monthEnd) && this.calendars.some(c => event.calendars?.some(id => id === c.id)) ); return weeks.map(week => this.buildWeekData(week, month, relevantEvents)); } buildWeekData(week, currentMonth, events) { const weekStart = calendarUtil.startOfDay(week[0]); const weekEnd = calendarUtil.endOfDay(week[6]); const weekEvents = events.filter(event => calendarUtil.rangesOverlap(event.time_start, event.time_end, weekStart, weekEnd) ); weekEvents.sort((a, b) => { const aSpan = a.all_day || this.isMultiDay(a); const bSpan = b.all_day || this.isMultiDay(b); if (aSpan !== bSpan) return aSpan ? -1 : 1; return a.time_start - b.time_start; }); const slotRows = []; weekEvents.forEach(event => { const startCol = Math.max(0, this.dayIndex(event.time_start, week)); const endCol = Math.min(6, this.dayIndex(event.time_end, week)); let row = 0; while (true) { if (!slotRows[row]) slotRows[row] = new Array(7).fill(null); if (slotRows[row].slice(startCol, endCol + 1).every(v => v === null)) break; row++; } for (let c = startCol; c <= endCol; c++) { slotRows[row][c] = { event, isStart: c === startCol, isEnd: c === endCol, isSingleDay: startCol === endCol }; } }); const slotMap = Array.from({ length: 7 }, (_, col) => slotRows.map(row => row[col] ?? null) ); return { days: week.map(day => ({ day, isCurrentMonth: day.getMonth() === currentMonth, events: weekEvents.filter(event => { const effectiveEnd = event.all_day ? calendarUtil.endOfDay(event.time_end) : event.time_end; return calendarUtil.rangesOverlap( event.time_start, effectiveEnd, calendarUtil.startOfDay(day), calendarUtil.endOfDay(day) ); }) })), slotMap }; } isMultiDay(event) { return calendarUtil.startOfDay(event.time_start).getTime() !== calendarUtil.startOfDay(event.time_end).getTime(); } dayIndex(date, week) { const dayStart = calendarUtil.startOfDay(date); for (let i = 0; i < 7; i++) { if (calendarUtil.startOfDay(week[i]).getTime() === dayStart.getTime()) return i; } return date.getTime() < week[0].getTime() ? 0 : 6; } } register(DesktopMonthView)