137 lines
4.7 KiB
JavaScript
137 lines
4.7 KiB
JavaScript
import calendarUtil from "../calendarUtil.js"
|
|
import "./MonthHeaderRow.js"
|
|
import "./MonthGrid.js"
|
|
|
|
css(`
|
|
monthview- {
|
|
scrollbar-width: none;
|
|
-ms-overflow-style: none;
|
|
}
|
|
`)
|
|
|
|
class MonthView extends Shadow {
|
|
constructor(calendars, events, currentDate, weekStartsOn = 0, onDayTap = null) {
|
|
super()
|
|
this.calendars = calendars;
|
|
this.events = events;
|
|
this.currentDate = currentDate;
|
|
this.weekStartsOn = weekStartsOn;
|
|
this.onDayTap = onDayTap;
|
|
}
|
|
|
|
render() {
|
|
const weeks = this.buildMonthWeeks();
|
|
|
|
VStack(() => {
|
|
MonthHeaderRow(this.weekStartsOn)
|
|
MonthGrid(weeks, this.calendars, this.onDayTap)
|
|
})
|
|
.width(100, pct)
|
|
.height(100, pct)
|
|
.boxSizing("border-box")
|
|
.fontSize(0.9, em)
|
|
}
|
|
|
|
buildMonthWeeks() {
|
|
const month = this.currentDate.getMonth();
|
|
|
|
const allDays = calendarUtil.buildMonthGrid(this.currentDate, this.weekStartsOn);
|
|
const gridStart = allDays[0];
|
|
|
|
// Split into weeks
|
|
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 expanded = calendarUtil.expandRecurringEvents(this.events, gridStart, gridEnd);
|
|
const relevantEvents = expanded.filter(event =>
|
|
calendarUtil.rangesOverlap(event.time_start, event.time_end, gridStart, gridEnd)
|
|
&& this.calendars.some(c => event.calendars?.some(id => id === c.id))
|
|
);
|
|
|
|
// Build week data with slot maps (for spanning event row alignment)
|
|
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]);
|
|
|
|
// Events that appear in this week
|
|
const weekEvents = events.filter(event =>
|
|
calendarUtil.rangesOverlap(event.time_start, event.time_end, weekStart, weekEnd)
|
|
);
|
|
|
|
// Sort: all-day / multi-day first, then timed; then by start
|
|
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;
|
|
});
|
|
|
|
// slotRows[row] = array of 7 entries (null or event)
|
|
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));
|
|
|
|
// Find first slot row where all cols [startCol..endCol] are free
|
|
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
|
|
};
|
|
}
|
|
});
|
|
|
|
// Transpose: slotMap[colIndex] = ordered list of slot entries (or null gaps)
|
|
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;
|
|
}
|
|
// Clamp to week boundaries for events that start/end outside the week
|
|
return date.getTime() < week[0].getTime() ? 0 : 6;
|
|
|
|
}
|
|
}
|
|
|
|
register(MonthView) |