import { CalendarEvent } from '../types/CalendarTypes'; import { calendarConfig } from '../core/CalendarConfig'; /** * Abstract base class for event DOM elements */ export abstract class BaseEventElement { protected element: HTMLElement; protected event: CalendarEvent; protected constructor(event: CalendarEvent) { this.event = event; this.element = this.createElement(); this.setDataAttributes(); } /** * Create the underlying DOM element */ protected abstract createElement(): HTMLElement; /** * Set standard data attributes on the element */ protected setDataAttributes(): void { this.element.dataset.eventId = this.event.id; this.element.dataset.title = this.event.title; this.element.dataset.start = this.event.start.toISOString(); this.element.dataset.end = this.event.end.toISOString(); this.element.dataset.type = this.event.type; this.element.dataset.duration = this.event.metadata?.duration?.toString() || '60'; } /** * Get the DOM element */ public getElement(): HTMLElement { return this.element; } /** * Format time for display */ protected formatTime(date: Date): string { const hours = date.getHours(); const minutes = date.getMinutes(); const period = hours >= 12 ? 'PM' : 'AM'; const displayHours = hours > 12 ? hours - 12 : (hours === 0 ? 12 : hours); return `${displayHours}:${minutes.toString().padStart(2, '0')} ${period}`; } /** * Calculate event position for timed events */ protected calculateEventPosition(): { top: number; height: number } { const gridSettings = calendarConfig.getGridSettings(); const dayStartHour = gridSettings.dayStartHour; const hourHeight = gridSettings.hourHeight; const startMinutes = this.event.start.getHours() * 60 + this.event.start.getMinutes(); const endMinutes = this.event.end.getHours() * 60 + this.event.end.getMinutes(); const dayStartMinutes = dayStartHour * 60; const top = ((startMinutes - dayStartMinutes) / 60) * hourHeight; const durationMinutes = endMinutes - startMinutes; const height = (durationMinutes / 60) * hourHeight; return { top, height }; } } /** * Timed event element (swp-event) */ export class SwpEventElement extends BaseEventElement { private constructor(event: CalendarEvent) { super(event); this.createInnerStructure(); this.applyPositioning(); } protected createElement(): HTMLElement { return document.createElement('swp-event'); } /** * Create inner HTML structure */ private createInnerStructure(): void { const startTime = this.formatTime(this.event.start); const endTime = this.formatTime(this.event.end); const durationMinutes = (this.event.end.getTime() - this.event.start.getTime()) / (1000 * 60); this.element.innerHTML = ` ${startTime} - ${endTime} ${this.event.title} `; } /** * Apply positioning styles */ private applyPositioning(): void { const position = this.calculateEventPosition(); this.element.style.position = 'absolute'; this.element.style.top = `${position.top + 1}px`; this.element.style.height = `${position.height - 3}px`; this.element.style.left = '2px'; this.element.style.right = '2px'; } /** * Factory method to create a SwpEventElement from a CalendarEvent */ public static fromCalendarEvent(event: CalendarEvent): SwpEventElement { return new SwpEventElement(event); } /** * Factory method to convert an all-day HTML element to a timed SwpEventElement */ public static fromAllDayElement(allDayElement: HTMLElement): SwpEventElement { // Extract data from all-day element's dataset const eventId = allDayElement.dataset.eventId || ''; const title = allDayElement.dataset.title || allDayElement.textContent || 'Untitled'; const type = allDayElement.dataset.type || 'work'; const startStr = allDayElement.dataset.start; const endStr = allDayElement.dataset.end; const durationStr = allDayElement.dataset.duration; if (!startStr || !endStr) { throw new Error('All-day element missing start/end dates'); } // Parse dates and set reasonable 1-hour duration for timed event const originalStart = new Date(startStr); const duration = durationStr ? parseInt(durationStr) : 60; // Default 1 hour // For conversion, use current time or a reasonable default (9 AM) const now = new Date(); const startDate = new Date(originalStart); startDate.setHours(now.getHours() || 9, now.getMinutes() || 0, 0, 0); const endDate = new Date(startDate); endDate.setMinutes(endDate.getMinutes() + duration); // Create CalendarEvent object const calendarEvent: CalendarEvent = { id: eventId, title: title, start: startDate, end: endDate, type: type, allDay: false, syncStatus: 'synced', metadata: { duration: duration.toString() } }; return new SwpEventElement(calendarEvent); } } /** * All-day event element (swp-allday-event) */ export class SwpAllDayEventElement extends BaseEventElement { private columnIndex: number; private constructor(event: CalendarEvent, columnIndex: number) { super(event); this.columnIndex = columnIndex; this.setAllDayAttributes(); this.createInnerStructure(); this.applyGridPositioning(); } protected createElement(): HTMLElement { return document.createElement('swp-allday-event'); } /** * Set all-day specific attributes */ private setAllDayAttributes(): void { this.element.dataset.allDay = "true"; // Override start/end times to be full day const dateStr = this.event.start.toISOString().split('T')[0]; this.element.dataset.start = `${dateStr}T00:00:00`; this.element.dataset.end = `${dateStr}T23:59:59`; } /** * Create inner structure (just text content for all-day events) */ private createInnerStructure(): void { this.element.textContent = this.event.title; } /** * Apply CSS grid positioning */ private applyGridPositioning(): void { this.element.style.gridColumn = this.columnIndex.toString(); } /** * Factory method to create from CalendarEvent and target date */ public static fromCalendarEvent(event: CalendarEvent, targetDate: string): SwpAllDayEventElement { // Calculate column index const dayHeaders = document.querySelectorAll('swp-day-header'); let columnIndex = 1; dayHeaders.forEach((header, index) => { if ((header as HTMLElement).dataset.date === targetDate) { columnIndex = index + 1; } }); return new SwpAllDayEventElement(event, columnIndex); } }