import { CalendarEvent } from '../types/CalendarTypes'; import { calendarConfig } from '../core/CalendarConfig'; import { TimeFormatter } from '../utils/TimeFormatter'; import { PositionUtils } from '../utils/PositionUtils'; /** * 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 using TimeFormatter */ protected formatTime(date: Date): string { return TimeFormatter.formatTime(date); } /** * Calculate event position for timed events using PositionUtils */ protected calculateEventPosition(): { top: number; height: number } { return PositionUtils.calculateEventPosition(this.event.start, this.event.end); } } /** * 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 timeRange = TimeFormatter.formatTimeRange(this.event.start, this.event.end); const durationMinutes = (this.event.end.getTime() - this.event.start.getTime()) / (1000 * 60); this.element.innerHTML = ` ${timeRange} ${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); } }