import calendarUtil from "../calendarUtil.js"; class WeekHeaderRow extends Shadow { constructor(groupedDays, calendars, onDayTap = null) { super() this.groupedDays = groupedDays; this.calendars = calendars; this.onDayTap = onDayTap; } render() { const allDayEvents = this.collectAllDayEvents(); const maxEventsPerDay = Math.max(0, ...this.groupedDays.map(g => g.allDay.length)) VStack(() => { HStack(() => { this.groupedDays.forEach((group, index) => { const day = group.day; const today = calendarUtil.isToday(day); const isLast = index === this.groupedDays.length - 1; VStack(() => { h3(day.getDate()) .margin(0) .fontSize(1.35, em) .fontWeight("700") .lineHeight("1") .textAlign("center") p(day.toLocaleDateString("en-US", { weekday: "short" }).toUpperCase()) .margin(0) .fontSize(0.72, em) .fontWeight("600") .letterSpacing(0.04, em) .opacity(today ? 1 : 0.5) .textAlign("center") }) .color(today ? "var(--quillred)" : "var(--headertext)") .flex(1) .width(0, px) .minWidth(0) .justifyContent("center") .alignItems("center") .gap(0.5, em) .paddingTop(0.85, em) .background(today ? "var(--desktop-item-background)" : "transparent") .borderRight(isLast ? "1px solid transparent" : "1px solid var(--divider)") .boxSizing("border-box") .paddingBottom(maxEventsPerDay > 0 ? (maxEventsPerDay * 2.0) + 0.7 : 0.35, em) .cursor("pointer") .onTap(() => { this.onDayTap(day) }) }) }) .width(100, pct) .alignItems("stretch") this.allDayRow(allDayEvents, maxEventsPerDay); }) .width(100, pct) .position("relative") .background("var(--sidebottombars)") .borderBottom("1px solid var(--divider)") } allDayRow(allDayEvents, maxEventsPerDay) { if (allDayEvents.length === 0) return; const rowHeight = 1.75; const gap = 0.25; const totalHeight = maxEventsPerDay * rowHeight + (maxEventsPerDay - 1) * gap; ZStack(() => { allDayEvents.forEach(({ event, startIndex, endIndex, clippedLeft, clippedRight }) => { this.spanningEvent(event, startIndex, endIndex, rowHeight, gap, clippedLeft, clippedRight); }) }) .position("absolute") .bottom(0.25, em) .left(0, px) .width(100, pct) .height(totalHeight, em) .boxSizing("border-box") .pointerEvents("none") } spanningEvent(event, startIndex, endIndex, rowHeight, gap, clippedLeft, clippedRight) { const totalDays = this.groupedDays.length; const spanCount = endIndex - startIndex + 1; const leftPct = (startIndex / totalDays) * 100; const widthPct = (spanCount / totalDays) * 100; const color = calendarUtil.getCalendarColor(this.calendars, event.calendars.find(id => this.calendars.some(c => c.id === id)) ?? event.calendars[0]); const id = event.id ?? event.title; const slot = this.groupedDays[startIndex].allDay.findIndex(e => (e.id ?? e.title) === id); const topEm = slot * (rowHeight + gap); const borderLeft = clippedLeft ? 0 : 0.25; const borderRight = clippedRight ? 0 : 0.25; const leftPad = clippedLeft ? 0 : 1.25; const rightPad = clippedRight ? 0 : 1.25; HStack(() => { p(event.title) .margin(0) .fontSize(0.72, em) .fontWeight("600") .color("white") .whiteSpace("nowrap") .overflow("hidden") }) .position("absolute") .top(topEm, em) .left(leftPct + leftPad, pct) .width(widthPct - leftPad - rightPad, pct) .height(rowHeight, em) .padding(0.35, em) .background(color) .borderTopLeftRadius(`${borderLeft}em`) .borderBottomLeftRadius(`${borderLeft}em`) .borderTopRightRadius(`${borderRight}em`) .borderBottomRightRadius(`${borderRight}em`) .alignItems("center") .boxSizing("border-box") .overflow("hidden") .pointerEvents("auto") .cursor("pointer") .onTap(() => $("bottomsheet-").showEvent(event)) } collectAllDayEvents() { const seen = new Map(); // Key by id + time_start date: events spanning multiple days share the same time_start // so they merge into one bar; different occurrences of the same recurring template // have different time_start dates and render as separate bars. const eventKey = (event) => { const d = event.time_start instanceof Date ? event.time_start : new Date(event.time_start); return `${event.id ?? event.title}_${calendarUtil.toDateInput(d)}`; }; this.groupedDays.forEach((group, index) => { group.allDay.forEach(event => { const key = eventKey(event); if (!seen.has(key)) { seen.set(key, { event, startIndex: index, endIndex: index }); } else { seen.get(key).endIndex = index; } }); }); const lastIndex = this.groupedDays.length - 1; return Array.from(seen.values()).map(entry => ({ ...entry, clippedLeft: entry.startIndex === 0 && calendarUtil.startOfDay(entry.event.time_start) < calendarUtil.startOfDay(this.groupedDays[0].day), clippedRight: entry.endIndex === lastIndex && calendarUtil.startOfDay(entry.event.time_end) > calendarUtil.startOfDay(this.groupedDays[lastIndex].day) })); } } register(WeekHeaderRow)