import { EventBus } from '../core/EventBus'; import { IEventBus, CalendarEvent } from '../types/CalendarTypes'; import { EventTypes } from '../constants/EventTypes'; import { calendarConfig } from '../core/CalendarConfig'; import { DateUtils } from '../utils/DateUtils'; /** * EventRenderer - Render events i DOM med positionering * Håndterer event positioning og overlap detection */ export class EventRenderer { private eventBus: IEventBus; constructor(eventBus: IEventBus) { this.eventBus = eventBus; this.setupEventListeners(); } private setupEventListeners(): void { this.eventBus.on(EventTypes.EVENTS_LOADED, (event: Event) => { const customEvent = event as CustomEvent; const { events } = customEvent.detail; console.log('EventRenderer: Received EVENTS_LOADED with', events.length, 'events'); // Store events but don't render yet - wait for grid to be ready this.pendingEvents = events; this.tryRenderEvents(); }); this.eventBus.on(EventTypes.GRID_RENDERED, () => { // Grid is ready, now we can render events this.tryRenderEvents(); }); this.eventBus.on(EventTypes.VIEW_RENDERED, () => { // Clear existing events when view changes this.clearEvents(); }); } private pendingEvents: CalendarEvent[] = []; private tryRenderEvents(): void { // Only render if we have both events and grid is ready console.log('EventRenderer: tryRenderEvents called, pending events:', this.pendingEvents.length); if (this.pendingEvents.length > 0) { const dayColumns = document.querySelectorAll('swp-day-column'); console.log('EventRenderer: Found', dayColumns.length, 'day columns'); if (dayColumns.length > 0) { this.renderEvents(this.pendingEvents); this.pendingEvents = []; // Clear pending events after rendering } } } private renderEvents(events: CalendarEvent[]): void { console.log('EventRenderer: renderEvents called with', events.length, 'events'); console.log('EventRenderer: All events:', events.map(e => ({ title: e.title, allDay: e.allDay, start: e.start }))); // Clear existing events first this.clearEvents(); // Get current week dates for filtering const currentWeekDates = this.getCurrentWeekDates(); // Filter events for current week and exclude all-day events (handled by GridManager) const currentWeekEvents = events.filter(event => { // Skip all-day events - they are handled by GridManager if (event.allDay) { return false; } const eventDate = new Date(event.start); const eventDateStr = DateUtils.formatDate(eventDate); const isInCurrentWeek = currentWeekDates.some(weekDate => DateUtils.formatDate(weekDate) === eventDateStr ); return isInCurrentWeek; }); // Render each event in the correct day column currentWeekEvents.forEach(event => { const eventDate = new Date(event.start); const dayColumn = this.findDayColumn(eventDate); if (dayColumn) { const eventsLayer = dayColumn.querySelector('swp-events-layer'); if (eventsLayer) { this.renderEvent(event, eventsLayer); } else { console.warn('EventRenderer: No events layer found in day column for', DateUtils.formatDate(eventDate)); } } else { console.warn('EventRenderer: No day column found for event date', DateUtils.formatDate(eventDate)); } }); // Emit event rendered this.eventBus.emit(EventTypes.EVENT_RENDERED, { count: currentWeekEvents.length }); } /** * Get current week dates (Sunday to Saturday) */ private getCurrentWeekDates(): Date[] { const today = new Date(); const weekStart = DateUtils.getWeekStart(today, 0); // Sunday start const dates: Date[] = []; for (let i = 0; i < 7; i++) { const date = DateUtils.addDays(weekStart, i); dates.push(date); } return dates; } /** * Find day column for specific date */ private findDayColumn(date: Date): HTMLElement | null { const dateStr = DateUtils.formatDate(date); const dayColumn = document.querySelector(`swp-day-column[data-date="${dateStr}"]`) as HTMLElement; console.log('EventRenderer: Looking for day column with date', dateStr, 'found:', !!dayColumn); return dayColumn; } private renderEvent(event: CalendarEvent, container: Element): 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); eventElement.style.position = 'absolute'; eventElement.style.top = `${position.top + 1}px`; eventElement.style.height = `${position.height - 1}px`; // Only set positioning and color - rest is in CSS 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} `; // Add event listeners this.addEventListeners(eventElement, event); container.appendChild(eventElement); // Event successfully rendered } private calculateEventPosition(event: CalendarEvent): { top: number; height: number } { const startDate = new Date(event.start); const endDate = new Date(event.end); // Use dayStartHour to match time-axis positioning - this is the visible start hour (6 AM) const dayStartHour = calendarConfig.get('dayStartHour'); // 6 (6 AM) const hourHeight = calendarConfig.get('hourHeight'); // Calculate minutes from visible day start (6 AM, not midnight) const eventHour = startDate.getHours(); const eventMinutes = startDate.getMinutes(); const startMinutes = (eventHour - dayStartHour) * 60 + eventMinutes; // Calculate duration in minutes const duration = (endDate.getTime() - startDate.getTime()) / (1000 * 60); // Convert to pixels - position relative to visible time-grid (starts at dayStartHour) const top = startMinutes * (hourHeight / 60); const height = duration * (hourHeight / 60); return { top, height }; } private formatTime(isoString: string): string { const date = new Date(isoString); const hours = date.getHours(); const minutes = date.getMinutes(); const period = hours >= 12 ? 'PM' : 'AM'; const displayHours = hours % 12 || 12; const displayMinutes = minutes.toString().padStart(2, '0'); return `${displayHours}:${displayMinutes} ${period}`; } private addEventListeners(eventElement: HTMLElement, event: CalendarEvent): void { // Click handler eventElement.addEventListener('click', (e) => { e.stopPropagation(); this.eventBus.emit(EventTypes.EVENT_SELECTED, { event, element: eventElement }); }); // Hover effects are handled by CSS // Hover effects are now handled by CSS } private clearEvents(): void { const eventsLayers = document.querySelectorAll('swp-events-layer'); eventsLayers.forEach(layer => { layer.innerHTML = ''; }); } public refresh(): void { // Request fresh events from EventManager this.eventBus.emit(EventTypes.REFRESH_REQUESTED); } public destroy(): void { this.clearEvents(); } }