// Event rendering strategy interface and implementations import { CalendarEvent } from '../types/CalendarTypes'; import { CalendarConfig } from '../core/CalendarConfig'; import { DateUtils } from '../utils/DateUtils'; /** * Interface for event rendering strategies */ export interface EventRendererStrategy { findColumn(event: CalendarEvent): HTMLElement | null; renderEvents(events: CalendarEvent[], config: CalendarConfig): void; clearEvents(): void; } /** * Base class for event renderers with common functionality */ export abstract class BaseEventRenderer implements EventRendererStrategy { abstract findColumn(event: CalendarEvent): HTMLElement | null; renderEvents(events: CalendarEvent[], config: CalendarConfig): void { console.log('BaseEventRenderer: renderEvents called with', events.length, 'events'); // Clear existing events first this.clearEvents(); // Filter out all-day events (handled by GridManager) const nonAllDayEvents = events.filter(event => !event.allDay); console.log('BaseEventRenderer: Rendering', nonAllDayEvents.length, 'non-all-day events'); // Render each event in the correct column nonAllDayEvents.forEach(event => { const column = this.findColumn(event); if (column) { const eventsLayer = column.querySelector('swp-events-layer'); if (eventsLayer) { console.log(`BaseEventRenderer: Rendering event "${event.title}" in events layer`); this.renderEvent(event, eventsLayer, config); // Debug: Verify event was actually added const renderedEvents = eventsLayer.querySelectorAll('swp-event'); console.log(`BaseEventRenderer: Events layer now has ${renderedEvents.length} events`); } else { console.warn('BaseEventRenderer: No events layer found in column for event', event.title, 'Column:', column); } } else { console.warn('BaseEventRenderer: No column found for event', event.title); } }); } protected renderEvent(event: CalendarEvent, container: Element, config: CalendarConfig): void { const eventElement = document.createElement('swp-event'); eventElement.dataset.eventId = event.id; eventElement.dataset.type = event.type; // Calculate position based on time const position = this.calculateEventPosition(event, config); eventElement.style.position = 'absolute'; eventElement.style.top = `${position.top + 1}px`; eventElement.style.height = `${position.height - 1}px`; // Set color eventElement.style.backgroundColor = event.metadata?.color || '#3498db'; // Format time for display const startTime = this.formatTime(event.start); const endTime = this.formatTime(event.end); // Create event content eventElement.innerHTML = ` ${startTime} - ${endTime} ${event.title} `; container.appendChild(eventElement); console.log(`BaseEventRenderer: Created event element for "${event.title}":`, { top: eventElement.style.top, height: eventElement.style.height, backgroundColor: eventElement.style.backgroundColor, position: eventElement.style.position, innerHTML: eventElement.innerHTML }); } protected calculateEventPosition(event: CalendarEvent, config: CalendarConfig): { top: number; height: number } { const startDate = new Date(event.start); const endDate = new Date(event.end); const gridSettings = config.getGridSettings(); const dayStartHour = gridSettings.dayStartHour; const hourHeight = gridSettings.hourHeight; // Calculate minutes from visible day start const startMinutes = startDate.getHours() * 60 + startDate.getMinutes(); const endMinutes = endDate.getHours() * 60 + endDate.getMinutes(); const dayStartMinutes = dayStartHour * 60; // Calculate top position (subtract day start to align with time axis) const top = ((startMinutes - dayStartMinutes) / 60) * hourHeight; // Calculate height const durationMinutes = endMinutes - startMinutes; const height = (durationMinutes / 60) * hourHeight; console.log('Event positioning calculation:', { eventTime: `${startDate.getHours()}:${startDate.getMinutes()}`, startMinutes, endMinutes, dayStartMinutes, dayStartHour, hourHeight, top, height }); return { top, height }; } protected formatTime(isoString: string): string { const date = new Date(isoString); const hours = date.getHours(); const minutes = date.getMinutes(); const period = hours >= 12 ? 'PM' : 'AM'; const displayHour = hours > 12 ? hours - 12 : (hours === 0 ? 12 : hours); return `${displayHour}:${minutes.toString().padStart(2, '0')} ${period}`; } clearEvents(): void { const existingEvents = document.querySelectorAll('swp-event'); if (existingEvents.length > 0) { console.warn(`🗑️ BaseEventRenderer: REMOVING ${existingEvents.length} events from DOM! Stack trace:`, new Error().stack); } existingEvents.forEach(event => event.remove()); } } /** * Date-based event renderer */ export class DateEventRenderer extends BaseEventRenderer { findColumn(event: CalendarEvent): HTMLElement | null { const eventDate = new Date(event.start); const dateStr = DateUtils.formatDate(eventDate); const dayColumn = document.querySelector(`swp-day-column[data-date="${dateStr}"]`) as HTMLElement; // Debug: Check all available columns const allColumns = document.querySelectorAll('swp-day-column'); const availableDates = Array.from(allColumns).map(col => (col as HTMLElement).dataset.date); console.log('DateEventRenderer: Event', event.title, 'start:', event.start); console.log('DateEventRenderer: Looking for date:', dateStr); console.log('DateEventRenderer: Available columns with dates:', availableDates); console.log('DateEventRenderer: Found column:', !!dayColumn); return dayColumn; } } /** * Resource-based event renderer */ export class ResourceEventRenderer extends BaseEventRenderer { findColumn(event: CalendarEvent): HTMLElement | null { if (!event.resourceName) { console.warn('ResourceEventRenderer: Event has no resourceName', event); return null; } const resourceColumn = document.querySelector(`swp-resource-column[data-resource="${event.resourceName}"]`) as HTMLElement; console.log('ResourceEventRenderer: Looking for resource column with name', event.resourceName, 'found:', !!resourceColumn); return resourceColumn; } }