import { EventBus } from '../core/EventBus.js'; import { CoreEvents } from '../constants/CoreEvents.js'; import { CalendarConfig } from '../core/CalendarConfig.js'; import { CalendarEvent, CalendarView, IEventBus } from '../types/CalendarTypes.js'; import { EventManager } from './EventManager.js'; import { GridManager } from './GridManager.js'; import { EventRenderingService } from '../renderers/EventRendererManager.js'; import { ScrollManager } from './ScrollManager.js'; import { DateCalculator } from '../utils/DateCalculator.js'; /** * CalendarManager - Main coordinator for all calendar managers * Uses simple direct method calls instead of complex state management */ export class CalendarManager { private eventBus: IEventBus; private config: CalendarConfig; private eventManager: EventManager; private gridManager: GridManager; private eventRenderer: EventRenderingService; private scrollManager: ScrollManager; private dateCalculator: DateCalculator; private currentView: CalendarView = 'week'; private currentDate: Date = new Date(); private isInitialized: boolean = false; constructor( eventBus: IEventBus, config: CalendarConfig, eventManager: EventManager, gridManager: GridManager, eventRenderer: EventRenderingService, scrollManager: ScrollManager ) { this.eventBus = eventBus; this.config = config; this.eventManager = eventManager; this.gridManager = gridManager; this.eventRenderer = eventRenderer; this.scrollManager = scrollManager; this.dateCalculator = new DateCalculator(config); this.setupEventListeners(); console.log('📋 CalendarManager: Created with proper dependency injection'); } /** * Initialize calendar system using simple direct calls */ public async initialize(): Promise { if (this.isInitialized) { console.warn('CalendarManager is already initialized'); return; } console.log('🚀 CalendarManager: Starting simple initialization'); try { // Debug: Check calendar type const calendarType = this.config.getCalendarMode(); console.log(`🔍 CalendarManager: Initializing ${calendarType} calendar`); // Step 1: Load data console.log('📊 Loading event data...'); await this.eventManager.loadData(); // Step 2: Pass data to GridManager and render grid structure console.log('🏗️ Rendering grid...'); if (calendarType === 'resource') { const resourceData = this.eventManager.getResourceData(); this.gridManager.setResourceData(resourceData); } await this.gridManager.render(); // Step 3: Initialize scroll synchronization console.log('📜 Setting up scroll synchronization...'); this.scrollManager.initialize(); // Step 4: Set initial view and date BEFORE event rendering console.log('⚙️ Setting initial view and date...'); this.setView(this.currentView); this.setCurrentDate(this.currentDate); // Step 5: Event rendering will be triggered by GRID_RENDERED event console.log('🎨 Event rendering will be triggered automatically by grid events...'); this.isInitialized = true; console.log('✅ CalendarManager: Simple initialization complete'); // Emit initialization complete event this.eventBus.emit(CoreEvents.INITIALIZED, { currentDate: this.currentDate, currentView: this.currentView }); } catch (error) { console.error('❌ CalendarManager initialization failed:', error); throw error; } } /** * Skift calendar view (dag/uge/måned) */ public setView(view: CalendarView): void { if (this.currentView === view) { return; } const previousView = this.currentView; this.currentView = view; console.log(`Changing view from ${previousView} to ${view}`); // Emit view change event this.eventBus.emit(CoreEvents.VIEW_CHANGED, { previousView, currentView: view, date: this.currentDate }); // Grid re-rendering will trigger event rendering automatically via GRID_RENDERED event } /** * Sæt aktuel dato */ public setCurrentDate(date: Date): void { // Validate input date if (!date || !(date instanceof Date) || isNaN(date.getTime())) { console.error('CalendarManager.setCurrentDate: Invalid date provided', date); return; } const previousDate = this.currentDate; this.currentDate = new Date(date); // Validate that both dates are valid before logging const prevDateStr = previousDate && !isNaN(previousDate.getTime()) ? previousDate.toISOString() : 'Invalid Date'; const newDateStr = this.currentDate.toISOString(); console.log(`Changing date from ${prevDateStr} to ${newDateStr}`); // Emit date change event this.eventBus.emit(CoreEvents.DATE_CHANGED, { previousDate, currentDate: this.currentDate, view: this.currentView }); // Grid update for new date will trigger event rendering automatically via GRID_RENDERED event } /** * Naviger til i dag */ public goToToday(): void { this.setCurrentDate(new Date()); } /** * Naviger til næste periode (dag/uge/måned afhængig af view) */ public goToNext(): void { const nextDate = this.calculateNextDate(); this.setCurrentDate(nextDate); } /** * Naviger til forrige periode (dag/uge/måned afhængig af view) */ public goToPrevious(): void { const previousDate = this.calculatePreviousDate(); this.setCurrentDate(previousDate); } /** * Hent aktuel view */ public getCurrentView(): CalendarView { return this.currentView; } /** * Hent aktuel dato */ public getCurrentDate(): Date { return new Date(this.currentDate); } /** * Hent calendar konfiguration */ public getConfig(): CalendarConfig { return this.config; } /** * Check om calendar er initialiseret */ public isCalendarInitialized(): boolean { return this.isInitialized; } /** * Get initialization report for debugging */ public getInitializationReport(): any { return { isInitialized: this.isInitialized, currentView: this.currentView, currentDate: this.currentDate, initializationTime: 'N/A - simple initialization' }; } /** * Genindlæs calendar data */ public refresh(): void { console.log('Refreshing calendar...'); this.eventBus.emit(CoreEvents.REFRESH_REQUESTED, { view: this.currentView, date: this.currentDate }); } /** * Ryd calendar og nulstil til standard tilstand */ public reset(): void { console.log('Resetting calendar...'); this.currentView = 'week'; this.currentDate = new Date(); this.eventBus.emit(CoreEvents.REFRESH_REQUESTED, { view: this.currentView, date: this.currentDate }); } /** * Setup event listeners for at håndtere events fra andre managers */ private setupEventListeners(): void { // Listen for workweek changes only this.eventBus.on(CoreEvents.WORKWEEK_CHANGED, (event: Event) => { const customEvent = event as CustomEvent; console.log('CalendarManager: Workweek changed to', customEvent.detail.workWeekId); this.handleWorkweekChange(); // Also update week info display since workweek affects date range display this.updateWeekInfoForWorkweekChange(); }); } /** * Beregn næste dato baseret på aktuel view */ private calculateNextDate(): Date { const nextDate = new Date(this.currentDate); switch (this.currentView) { case 'day': nextDate.setDate(nextDate.getDate() + 1); break; case 'week': const workWeekSettings = this.config.getWorkWeekSettings(); nextDate.setDate(nextDate.getDate() + 7); // Move to next calendar week regardless of work days break; case 'month': nextDate.setMonth(nextDate.getMonth() + 1); break; } return nextDate; } /** * Beregn forrige dato baseret på aktuel view */ private calculatePreviousDate(): Date { const previousDate = new Date(this.currentDate); switch (this.currentView) { case 'day': previousDate.setDate(previousDate.getDate() - 1); break; case 'week': const workWeekSettings = this.config.getWorkWeekSettings(); previousDate.setDate(previousDate.getDate() - 7); // Move to previous calendar week regardless of work days break; case 'month': previousDate.setMonth(previousDate.getMonth() - 1); break; } return previousDate; } /** * Calculate the current period based on view and date */ private calculateCurrentPeriod(): { start: string; end: string } { const current = new Date(this.currentDate); switch (this.currentView) { case 'day': const dayStart = new Date(current); dayStart.setHours(0, 0, 0, 0); const dayEnd = new Date(current); dayEnd.setHours(23, 59, 59, 999); return { start: dayStart.toISOString(), end: dayEnd.toISOString() }; case 'week': // Find start of week (Monday) const weekStart = new Date(current); const dayOfWeek = weekStart.getDay(); const daysToMonday = dayOfWeek === 0 ? 6 : dayOfWeek - 1; // Sunday = 0, so 6 days back to Monday weekStart.setDate(weekStart.getDate() - daysToMonday); weekStart.setHours(0, 0, 0, 0); // Find end of week (Sunday) const weekEnd = new Date(weekStart); weekEnd.setDate(weekEnd.getDate() + 6); weekEnd.setHours(23, 59, 59, 999); return { start: weekStart.toISOString(), end: weekEnd.toISOString() }; case 'month': const monthStart = new Date(current.getFullYear(), current.getMonth(), 1); const monthEnd = new Date(current.getFullYear(), current.getMonth() + 1, 0, 23, 59, 59, 999); return { start: monthStart.toISOString(), end: monthEnd.toISOString() }; default: // Fallback to week view const fallbackStart = new Date(current); fallbackStart.setDate(fallbackStart.getDate() - 3); fallbackStart.setHours(0, 0, 0, 0); const fallbackEnd = new Date(current); fallbackEnd.setDate(fallbackEnd.getDate() + 3); fallbackEnd.setHours(23, 59, 59, 999); return { start: fallbackStart.toISOString(), end: fallbackEnd.toISOString() }; } } /** * Handle workweek configuration changes */ private handleWorkweekChange(): void { console.log('CalendarManager: Handling workweek change - forcing full grid rebuild'); // Force a complete grid rebuild by clearing existing structure const container = document.querySelector('swp-calendar-container'); if (container) { container.innerHTML = ''; // Clear everything to force full rebuild } // Re-render the grid with new workweek settings (will now rebuild everything) this.gridManager.render(); // Re-initialize scroll manager after grid rebuild this.scrollManager.initialize(); // Re-render events in the new grid structure this.rerenderEvents(); } /** * Re-render events after grid structure changes */ private rerenderEvents(): void { console.log('CalendarManager: Re-rendering events for new workweek'); // Get current period data to determine date range const periodData = this.calculateCurrentPeriod(); // Find the grid container to render events in const container = document.querySelector('swp-calendar-container'); if (!container) { console.warn('CalendarManager: No container found for event re-rendering'); return; } // Trigger event rendering for the current date range using correct method this.eventRenderer.renderEvents({ container: container as HTMLElement, startDate: new Date(periodData.start), endDate: new Date(periodData.end) }); } /** * Update week info display after workweek changes */ private updateWeekInfoForWorkweekChange(): void { // Don't do anything here - let GRID_RENDERED event handle it console.log('CalendarManager: Workweek changed - week info will update after grid renders'); } /** * Update week info based on actual rendered columns */ private updateWeekInfoFromRenderedColumns(): void { console.log('CalendarManager: Updating week info from rendered columns'); // Get actual dates from rendered columns const columns = document.querySelectorAll('swp-day-column'); if (columns.length === 0) { console.warn('CalendarManager: No columns found for week info update'); return; } // Get first and last column dates const firstColumn = columns[0] as HTMLElement; const lastColumn = columns[columns.length - 1] as HTMLElement; const firstDateStr = firstColumn.dataset.date; const lastDateStr = lastColumn.dataset.date; if (!firstDateStr || !lastDateStr) { console.warn('CalendarManager: Column dates not found'); return; } // Parse dates const firstDate = new Date(firstDateStr); const lastDate = new Date(lastDateStr); // Calculate week number from first date const weekNumber = this.dateCalculator.getWeekNumber(firstDate); // Format date range const dateRange = this.dateCalculator.formatDateRange(firstDate, lastDate); console.log('CalendarManager: Week info from columns:', { firstDate: firstDateStr, lastDate: lastDateStr, weekNumber, dateRange }); // Emit week info update this.eventBus.emit(CoreEvents.WEEK_CHANGED, { weekNumber, dateRange, weekStart: firstDate, weekEnd: lastDate }); } }