import calendarUtil from "../calendarUtil.js" import "./SpacerCell.js" import "./TimedLabelsColumn.js" import "./TimedWeekGrid.js" import "./WeekHeaderRow.js" css(` weekview- { scrollbar-width: none; -ms-overflow-style: none; } weekview- .VStack::-webkit-scrollbar { display: none; width: 0px; height: 0px; } weekview- .VStack::-webkit-scrollbar-thumb { background: transparent; } weekview- .VStack::-webkit-scrollbar-track { background: transparent; } `) let _saved = null; class WeekView extends Shadow { constructor(calendars, events, currentDate, weekStartsOn = 0, onSlotTap = null, onDayTap = null, isCenter = false) { super() this.calendars = calendars; this.events = events; this.currentDate = currentDate; this.weekStartsOn = weekStartsOn; this.onSlotTap = onSlotTap; this.onDayTap = onDayTap; this.isCenter = isCenter; this.slots = calendarUtil.generateTimeSlots({ stepMinutes: 30 }); this.slotHeight = 2; this.sidebarWidth = 3; } render() { ZStack(() => { const visibleWeekStart = calendarUtil.startOfWeek(this.currentDate, this.weekStartsOn); const weekDays = this.getWeekDays(visibleWeekStart); const groupedDays = this.groupEventsByWeekDay( this.filterEventsForWeek(this.events, weekDays), weekDays ); const weekNumber = calendarUtil.getWeekNumber(visibleWeekStart); VStack(() => { HStack(() => { SpacerCell(weekNumber, this.sidebarWidth) WeekHeaderRow(groupedDays, this.calendars, this.onDayTap) }) .boxSizing("border-box") .position("sticky") .top(0) .width(100, pct) .zIndex(2) HStack(() => { TimedLabelsColumn(this.slots, this.slotHeight, this.sidebarWidth) TimedWeekGrid(weekDays, this.slots, groupedDays, this.calendars, this.slotHeight, "week", this.onSlotTap) }) .boxSizing("border-box") .onAppear(() => { // console.log("groupedDays:", groupedDays) this.scrollToEight(); }) }) }) .position("relative") .gap(1, em) .boxSizing("border-box") .width(100, pct) .height(100, pct) .fontSize(0.9, em) .overscrollBehavior("none") .overflowY("scroll") .display("block") } getWeekDays(weekStart) { return Array.from({ length: 7 }, (_, i) => calendarUtil.addDays(weekStart, i)); } filterEventsForWeek(events, weekDays) { const rangeStart = calendarUtil.startOfDay(weekDays[0]); const rangeEnd = calendarUtil.endOfDay(weekDays[6]); const expanded = calendarUtil.expandRecurringEvents(events, rangeStart, rangeEnd); return expanded.filter(event => { const end = event.all_day ? calendarUtil.endOfDay(event.time_end) : calendarUtil.timedEnd(event); return weekDays.some(day => calendarUtil.rangesOverlap( event.time_start, end, calendarUtil.startOfDay(day), calendarUtil.endOfDay(day) ) && this.calendars.some(c => event.calendars?.some(id => id === c.id))); }); } scrollToEight() { requestAnimationFrame(() => { const fs = parseFloat(getComputedStyle(this).fontSize); const slotsBeforeEight = this.slots.findIndex(s => s.hour24 === 8 && s.minute === 0); const defaultTarget = slotsBeforeEight * this.slotHeight * fs; if (this.isCenter) { const dateKey = calendarUtil.toDateInput(calendarUtil.startOfWeek(this.currentDate, this.weekStartsOn)); this.scrollTop = (_saved?.dateKey === dateKey) ? _saved.scrollTop : defaultTarget; this.addEventListener('scroll', () => { _saved = { dateKey, scrollTop: this.scrollTop }; }, { passive: true }); } else { this.scrollTop = defaultTarget; } }) } groupEventsByWeekDay(events, weekDays) { return weekDays.map(day => { const dayStart = calendarUtil.startOfDay(day); const dayEnd = calendarUtil.endOfDay(day) return { day, allDay: events.filter(event => { if (!event.all_day) return false; const end = calendarUtil.endOfDay(event.time_end); return calendarUtil.rangesOverlap(event.time_start, end, dayStart, dayEnd); }), timed: events.filter(event => { return !event.all_day && calendarUtil.rangesOverlap(event.time_start, calendarUtil.timedEnd(event), dayStart, dayEnd); }) }; }); } } register(WeekView)