import { EventBus } from '../core/EventBus'; import { IEventBus, CalendarEvent, RenderContext } from '../types/CalendarTypes'; import { CoreEvents } from '../constants/CoreEvents'; import { calendarConfig } from '../core/CalendarConfig'; import { CalendarTypeFactory } from '../factories/CalendarTypeFactory'; import { EventManager } from '../managers/EventManager'; import { EventRendererStrategy } from './EventRenderer'; import { SwpEventElement } from '../elements/SwpEventElement'; /** * EventRenderingService - Render events i DOM med positionering using Strategy Pattern * Håndterer event positioning og overlap detection */ export class EventRenderingService { private eventBus: IEventBus; private eventManager: EventManager; private strategy: EventRendererStrategy; constructor(eventBus: IEventBus, eventManager: EventManager) { this.eventBus = eventBus; this.eventManager = eventManager; // Cache strategy at initialization const calendarType = calendarConfig.getCalendarMode(); this.strategy = CalendarTypeFactory.getEventRenderer(calendarType); this.setupEventListeners(); } /** * Render events in a specific container for a given period */ public renderEvents(context: RenderContext): void { // Clear existing events in the specific container first this.strategy.clearEvents(context.container); // Get events from EventManager for the period const events = this.eventManager.getEventsForPeriod( context.startDate, context.endDate ); if (events.length === 0) { return; } // Use cached strategy to render events in the specific container this.strategy.renderEvents(events, context.container); // Emit EVENTS_RENDERED event for filtering system this.eventBus.emit(CoreEvents.EVENTS_RENDERED, { events: events, container: context.container }); } private setupEventListeners(): void { // Event-driven rendering: React to grid and container events this.eventBus.on(CoreEvents.GRID_RENDERED, (event: Event) => { this.handleGridRendered(event as CustomEvent); }); // CONTAINER_READY_FOR_EVENTS removed - events are now pre-rendered synchronously // this.eventBus.on(EventTypes.CONTAINER_READY_FOR_EVENTS, (event: Event) => { // this.handleContainerReady(event as CustomEvent); // }); this.eventBus.on(CoreEvents.VIEW_CHANGED, (event: Event) => { this.handleViewChanged(event as CustomEvent); }); // Simple drag:end listener to clean up day event clones this.eventBus.on('drag:end', (event: Event) => { const { eventId } = (event as CustomEvent).detail; const dayEventClone = document.querySelector(`swp-event[data-event-id="clone-${eventId}"]`); if (dayEventClone) { dayEventClone.remove(); } }); // Listen for conversion from all-day event to time event this.eventBus.on('drag:convert-to-time_event', (event: Event) => { const { draggedEventId, mousePosition, column } = (event as CustomEvent).detail; console.log('🔄 EventRendererManager: Received drag:convert-to-time_event', { draggedEventId, mousePosition, column }); this.handleConvertToTimeEvent(draggedEventId, mousePosition, column); }); } /** * Handle GRID_RENDERED event - render events in the current grid */ private handleGridRendered(event: CustomEvent): void { const { container, startDate, endDate, currentDate } = event.detail; if (!container) { return; } let periodStart: Date; let periodEnd: Date; if (startDate && endDate) { // Direct date format - use as provided periodStart = startDate; periodEnd = endDate; } else if (currentDate) { return; } else { return; } this.renderEvents({ container: container, startDate: periodStart, endDate: periodEnd }); } /** * Handle CONTAINER_READY_FOR_EVENTS event - render events in pre-rendered container */ private handleContainerReady(event: CustomEvent): void { const { container, startDate, endDate } = event.detail; if (!container || !startDate || !endDate) { return; } this.renderEvents({ container: container, startDate: new Date(startDate), endDate: new Date(endDate) }); } /** * Handle VIEW_CHANGED event - clear and re-render for new view */ private handleViewChanged(event: CustomEvent): void { // Clear all existing events since view structure may have changed this.clearEvents(); // New rendering will be triggered by subsequent GRID_RENDERED event } /** * Handle conversion from all-day event to time event */ private handleConvertToTimeEvent(draggedEventId: string, mousePosition: any, column: string): void { // Find all-day event clone const allDayClone = document.querySelector(`swp-allday-container swp-allday-event[data-event-id="clone-${draggedEventId}"]`); if (!allDayClone) { console.warn('EventRendererManager: All-day clone not found - drag may not have started properly', { draggedEventId }); return; } // Use SwpEventElement factory to create day event from all-day event const dayEventElement = SwpEventElement.fromAllDayElement(allDayClone as HTMLElement); const dayElement = dayEventElement.getElement(); // Remove the all-day clone - it's no longer needed since we're converting to day event allDayClone.remove(); // Set clone ID dayElement.dataset.eventId = `clone-${draggedEventId}`; // Find target column const columnElement = document.querySelector(`swp-day-column[data-date="${column}"]`); if (!columnElement) { console.warn('EventRendererManager: Target column not found', { column }); return; } // Find events layer in the column const eventsLayer = columnElement.querySelector('swp-events-layer'); if (!eventsLayer) { console.warn('EventRendererManager: Events layer not found in column'); return; } // Add to events layer eventsLayer.appendChild(dayElement); // Position based on mouse Y coordinate const columnRect = columnElement.getBoundingClientRect(); const relativeY = Math.max(0, mousePosition.y - columnRect.top); dayElement.style.top = `${relativeY}px`; // Set drag styling dayElement.style.zIndex = '1000'; dayElement.style.cursor = 'grabbing'; dayElement.style.opacity = ''; dayElement.style.transform = ''; console.log('✅ EventRendererManager: Converted all-day event to time event', { draggedEventId, column, mousePosition, relativeY }); } private clearEvents(container?: HTMLElement): void { this.strategy.clearEvents(container); } public refresh(container?: HTMLElement): void { // Clear events in specific container or globally this.clearEvents(container); } public destroy(): void { this.clearEvents(); } }