diff --git a/src/index.ts b/src/index.ts index d7ef47c..5d587eb 100644 --- a/src/index.ts +++ b/src/index.ts @@ -11,10 +11,10 @@ import { calendarConfig } from './core/CalendarConfig.js'; import { CalendarTypeFactory } from './factories/CalendarTypeFactory.js'; /** - * Initialize the calendar application with new state-driven approach + * Initialize the calendar application with simple direct calls */ async function initializeCalendar(): Promise { - console.log('🗓️ Initializing Calendar Plantempus with state management...'); + console.log('🗓️ Initializing Calendar Plantempus with simple coordination...'); // Declare managers outside try block for global access let calendarManager: CalendarManager; @@ -30,30 +30,28 @@ async function initializeCalendar(): Promise { const config = calendarConfig; // Initialize the CalendarTypeFactory before creating managers - console.log('🏭 Phase 0: Initializing CalendarTypeFactory...'); + console.log('🏭 Initializing CalendarTypeFactory...'); CalendarTypeFactory.initialize(); - // Initialize managers in proper order - console.log('📋 Phase 1: Creating core managers...'); + // Create all managers + console.log('📋 Creating managers...'); calendarManager = new CalendarManager(eventBus, config); navigationManager = new NavigationManager(eventBus); viewManager = new ViewManager(eventBus); - - console.log('🎯 Phase 2: Creating data and rendering managers...'); - // These managers will now respond to state-driven events eventManager = new EventManager(eventBus); eventRenderer = new EventRenderer(eventBus); + gridManager = new GridManager(); + scrollManager = new ScrollManager(); - console.log('🏗️ Phase 3: Creating layout managers...'); - scrollManager = new ScrollManager(); // Will respond to GRID_RENDERED - gridManager = new GridManager(); // Will respond to RENDERING_STARTED + // Set manager references in CalendarManager + calendarManager.setManagers(eventManager, gridManager, eventRenderer, scrollManager); // Enable debug mode for development eventBus.setDebug(true); - // Initialize all managers using state-driven coordination - console.log('🚀 Phase 4: Starting state-driven initialization...'); - await calendarManager.initialize(); // Now async and fully coordinated + // Initialize using simple direct calls + console.log('🚀 Starting simple initialization...'); + await calendarManager.initialize(); console.log('🎊 Calendar Plantempus initialized successfully!'); console.log('📊 Initialization Report:', calendarManager.getInitializationReport()); diff --git a/src/managers/CalendarManager.ts b/src/managers/CalendarManager.ts index 67a0855..c314d9e 100644 --- a/src/managers/CalendarManager.ts +++ b/src/managers/CalendarManager.ts @@ -2,17 +2,22 @@ import { EventBus } from '../core/EventBus.js'; import { EventTypes } from '../constants/EventTypes.js'; import { CalendarConfig } from '../core/CalendarConfig.js'; import { CalendarEvent, CalendarView, IEventBus } from '../types/CalendarTypes.js'; -import { CalendarStateManager } from './CalendarStateManager.js'; -import { StateEvents } from '../types/CalendarState.js'; +import { EventManager } from './EventManager.js'; +import { GridManager } from './GridManager.js'; +import { EventRenderer } from './EventRenderer.js'; +import { ScrollManager } from './ScrollManager.js'; /** * CalendarManager - Main coordinator for all calendar managers - * Now delegates initialization to CalendarStateManager for better coordination + * Uses simple direct method calls instead of complex state management */ export class CalendarManager { private eventBus: IEventBus; private config: CalendarConfig; - private stateManager: CalendarStateManager; + private eventManager: EventManager; + private gridManager: GridManager; + private eventRenderer: EventRenderer; + private scrollManager: ScrollManager; private currentView: CalendarView = 'week'; private currentDate: Date = new Date(); private isInitialized: boolean = false; @@ -20,13 +25,22 @@ export class CalendarManager { constructor(eventBus: IEventBus, config: CalendarConfig) { this.eventBus = eventBus; this.config = config; - this.stateManager = new CalendarStateManager(); this.setupEventListeners(); - console.log('📋 CalendarManager: Created with state management'); + console.log('📋 CalendarManager: Created with direct coordination'); } /** - * Initialize calendar system using state-driven approach + * Set manager references (called from index.ts) + */ + public setManagers(eventManager: EventManager, gridManager: GridManager, eventRenderer: EventRenderer, scrollManager: ScrollManager): void { + this.eventManager = eventManager; + this.gridManager = gridManager; + this.eventRenderer = eventRenderer; + this.scrollManager = scrollManager; + } + + /** + * Initialize calendar system using simple direct calls */ public async initialize(): Promise { if (this.isInitialized) { @@ -34,22 +48,37 @@ export class CalendarManager { return; } - console.log('🚀 CalendarManager: Starting state-driven initialization'); + console.log('🚀 CalendarManager: Starting simple initialization'); try { - // Delegate to StateManager for coordinated initialization - await this.stateManager.initialize(); + // Step 1: Load data + console.log('📊 Loading event data...'); + await this.eventManager.loadData(); - // Set initial view and date after successful initialization + // Step 2: Render grid structure + console.log('🏗️ Rendering grid...'); + 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: Render events (after view is set) + console.log('🎨 Rendering events...'); + const events = this.eventManager.getEvents(); + await this.eventRenderer.renderEvents(events); + this.isInitialized = true; - console.log('✅ CalendarManager: Initialization complete'); + console.log('✅ CalendarManager: Simple initialization complete'); } catch (error) { console.error('❌ CalendarManager initialization failed:', error); - throw error; // Let the caller handle the error + throw error; } } @@ -139,28 +168,19 @@ export class CalendarManager { * Check om calendar er initialiseret */ public isCalendarInitialized(): boolean { - return this.isInitialized && this.stateManager.isReady(); - } - - /** - * Get current calendar state - */ - public getCurrentState(): string { - return this.stateManager.getCurrentState(); - } - - /** - * Get state manager for advanced operations - */ - public getStateManager(): CalendarStateManager { - return this.stateManager; + return this.isInitialized; } /** * Get initialization report for debugging */ public getInitializationReport(): any { - return this.stateManager.getInitializationReport(); + return { + isInitialized: this.isInitialized, + currentView: this.currentView, + currentDate: this.currentDate, + initializationTime: 'N/A - simple initialization' + }; } /** diff --git a/src/managers/CalendarStateManager.ts b/src/managers/CalendarStateManager.ts deleted file mode 100644 index 1393abb..0000000 --- a/src/managers/CalendarStateManager.ts +++ /dev/null @@ -1,471 +0,0 @@ -// Calendar state management and coordination - -import { eventBus } from '../core/EventBus'; -import { calendarConfig } from '../core/CalendarConfig'; -import { - CalendarState, - StateEvents, - CalendarEvent, - StateChangeEvent, - ErrorEvent, - VALID_STATE_TRANSITIONS, - InitializationPhase, - STATE_TO_PHASE -} from '../types/CalendarState'; - -/** - * Central coordinator for calendar initialization and state management - * Ensures proper sequencing and eliminates race conditions - */ -export class CalendarStateManager { - private currentState: CalendarState = CalendarState.UNINITIALIZED; - private stateHistory: Array<{ state: CalendarState; timestamp: number }> = []; - private initializationStartTime: number = 0; - private phaseTimings: Map = new Map(); - - constructor() { - console.log('📋 CalendarStateManager: Created'); - this.recordStateChange(CalendarState.UNINITIALIZED); - } - - /** - * Get current calendar state - */ - getCurrentState(): CalendarState { - return this.currentState; - } - - /** - * Check if calendar is in ready state - */ - isReady(): boolean { - return this.currentState === CalendarState.READY; - } - - /** - * Get current initialization phase - */ - getCurrentPhase(): InitializationPhase { - return STATE_TO_PHASE[this.currentState]; - } - - /** - * Main initialization method - coordinates all calendar setup - */ - async initialize(): Promise { - console.log('🚀 CalendarStateManager: Starting calendar initialization'); - this.initializationStartTime = Date.now(); - - try { - // Phase 1: Configuration loading (blocks everything else) - await this.executeConfigurationPhase(); - - // Phase 2: Parallel data loading and DOM structure setup - await this.executeDataAndDOMPhase(); - - // Phase 3: Event rendering (requires both data and DOM) - await this.executeEventRenderingPhase(); - - // Phase 4: Finalization - await this.executeFinalizationPhase(); - - const totalTime = Date.now() - this.initializationStartTime; - console.log(`🎊 Calendar initialization complete in ${totalTime}ms`); - - } catch (error) { - console.error('❌ Calendar initialization failed:', error); - await this.handleInitializationError(error as Error); - } - } - - /** - * Phase 1: Configuration Loading - * Must complete before any other operations - */ - private async executeConfigurationPhase(): Promise { - console.log('📖 Phase 1: Configuration Loading'); - await this.transitionTo(CalendarState.INITIALIZING); - - this.startPhase(InitializationPhase.CONFIGURATION); - - // Emit config loading started - this.emitEvent(StateEvents.CONFIG_LOADING_STARTED, 'CalendarStateManager', { - configSource: 'URL and DOM attributes' - }); - - // Configuration is already loaded in CalendarConfig constructor - // but we validate and emit the completion event - const configValid = this.validateConfiguration(); - - if (!configValid) { - throw new Error('Invalid calendar configuration'); - } - - this.emitEvent(StateEvents.CONFIG_LOADED, 'CalendarStateManager', { - calendarMode: calendarConfig.getCalendarMode(), - dateViewSettings: calendarConfig.getDateViewSettings(), - gridSettings: calendarConfig.getGridSettings() - }); - - await this.transitionTo(CalendarState.CONFIG_LOADED); - this.endPhase(InitializationPhase.CONFIGURATION); - } - - /** - * Phase 2: Parallel Data Loading and DOM Setup - * These can run concurrently to improve performance - */ - private async executeDataAndDOMPhase(): Promise { - console.log('📊 Phase 2: Data Loading and DOM Setup (Parallel)'); - this.startPhase(InitializationPhase.DATA_AND_DOM); - - // Start both data loading and rendering setup in parallel - const dataPromise = this.coordinateDataLoading(); - const domPromise = this.coordinateDOMSetup(); - - // Wait for both to complete - await Promise.all([dataPromise, domPromise]); - - this.endPhase(InitializationPhase.DATA_AND_DOM); - } - - /** - * Coordinate data loading process - */ - private async coordinateDataLoading(): Promise { - await this.transitionTo(CalendarState.DATA_LOADING); - - this.emitEvent(StateEvents.DATA_LOADING_STARTED, 'CalendarStateManager', { - mode: calendarConfig.getCalendarMode(), - period: this.getCurrentPeriod() - }); - - // EventManager will respond to DATA_LOADING_STARTED and load data - // We wait for its DATA_LOADED response - await this.waitForEvent(StateEvents.DATA_LOADED, 10000); - - await this.transitionTo(CalendarState.DATA_LOADED); - console.log('✅ Data loading phase complete'); - } - - /** - * Coordinate DOM structure setup - */ - private async coordinateDOMSetup(): Promise { - await this.transitionTo(CalendarState.RENDERING); - - this.emitEvent(StateEvents.RENDERING_STARTED, 'CalendarStateManager', { - phase: 'DOM structure setup' - }); - - // GridManager will respond to RENDERING_STARTED and create DOM structure - // We wait for its GRID_RENDERED response - await this.waitForEvent(StateEvents.GRID_RENDERED, 5000); - - await this.transitionTo(CalendarState.RENDERED); - console.log('✅ DOM setup phase complete'); - } - - /** - * Phase 3: Event Rendering - * Requires both data and DOM to be ready - */ - private async executeEventRenderingPhase(): Promise { - console.log('🎨 Phase 3: Event Rendering'); - this.startPhase(InitializationPhase.EVENT_RENDERING); - - // Both data and DOM are ready, trigger event rendering - // EventRenderer will wait for both GRID_RENDERED and DATA_LOADED - - // Wait for events to be rendered - await this.waitForEvent(StateEvents.EVENTS_RENDERED, 3000); - - this.emitEvent(StateEvents.RENDERING_COMPLETE, 'CalendarStateManager', { - phase: 'Event rendering complete' - }); - - this.endPhase(InitializationPhase.EVENT_RENDERING); - console.log('✅ Event rendering phase complete'); - } - - /** - * Phase 4: Finalization - * System is ready for user interaction - */ - private async executeFinalizationPhase(): Promise { - console.log('🏁 Phase 4: Finalization'); - this.startPhase(InitializationPhase.FINALIZATION); - - await this.transitionTo(CalendarState.READY); - - const totalTime = Date.now() - this.initializationStartTime; - - this.emitEvent(StateEvents.CALENDAR_READY, 'CalendarStateManager', { - initializationTime: totalTime, - finalState: this.currentState, - phaseTimings: this.getPhaseTimings() - }); - - this.endPhase(InitializationPhase.FINALIZATION); - console.log(`🎉 Calendar is ready! Total initialization time: ${totalTime}ms`); - } - - /** - * Transition to a new state with validation - */ - private async transitionTo(newState: CalendarState): Promise { - if (!this.isValidTransition(this.currentState, newState)) { - const error = new Error(`Invalid state transition: ${this.currentState} → ${newState}`); - await this.handleInitializationError(error); - return; - } - - const oldState = this.currentState; - this.currentState = newState; - this.recordStateChange(newState); - - // Emit state change event - const stateChangeEvent: StateChangeEvent = { - type: StateEvents.CALENDAR_STATE_CHANGED, - component: 'CalendarStateManager', - timestamp: Date.now(), - data: { - from: oldState, - to: newState, - transitionValid: true - }, - metadata: { - phase: STATE_TO_PHASE[newState] - } - }; - - eventBus.emit(StateEvents.CALENDAR_STATE_CHANGED, stateChangeEvent); - console.log(`📍 State: ${oldState} → ${newState} [${STATE_TO_PHASE[newState]}]`); - } - - /** - * Validate state transition - */ - private isValidTransition(from: CalendarState, to: CalendarState): boolean { - const allowedTransitions = VALID_STATE_TRANSITIONS[from] || []; - return allowedTransitions.includes(to); - } - - /** - * Handle initialization errors with recovery attempts - */ - private async handleInitializationError(error: Error): Promise { - console.error('💥 Initialization error:', error); - - const errorEvent: ErrorEvent = { - type: StateEvents.CALENDAR_ERROR, - component: 'CalendarStateManager', - error, - timestamp: Date.now(), - data: { - failedComponent: 'CalendarStateManager', - currentState: this.currentState, - canRecover: this.canRecoverFromError(error) - } - }; - - eventBus.emit(StateEvents.CALENDAR_ERROR, errorEvent); - - // Attempt recovery if possible - if (this.canRecoverFromError(error)) { - await this.attemptRecovery(error); - } else { - await this.transitionTo(CalendarState.ERROR); - } - } - - /** - * Attempt to recover from errors - */ - private async attemptRecovery(error: Error): Promise { - console.log('🔧 Attempting error recovery...'); - - this.emitEvent(StateEvents.RECOVERY_ATTEMPTED, 'CalendarStateManager', { - error: error.message, - currentState: this.currentState - }); - - try { - // Simple recovery strategy: try to continue from a stable state - if (this.currentState === CalendarState.DATA_LOADING) { - // Retry data loading - await this.coordinateDataLoading(); - } else if (this.currentState === CalendarState.RENDERING) { - // Retry DOM setup - await this.coordinateDOMSetup(); - } - - this.emitEvent(StateEvents.RECOVERY_SUCCESS, 'CalendarStateManager', { - recoveredFrom: error.message - }); - - } catch (recoveryError) { - console.error('❌ Recovery failed:', recoveryError); - - this.emitEvent(StateEvents.RECOVERY_FAILED, 'CalendarStateManager', { - originalError: error.message, - recoveryError: (recoveryError as Error).message - }); - - await this.transitionTo(CalendarState.ERROR); - } - } - - /** - * Determine if error is recoverable - */ - private canRecoverFromError(error: Error): boolean { - // Simple recovery logic - can be extended - const recoverableErrors = [ - 'timeout', - 'network', - 'dom not ready', - 'data loading failed' - ]; - - return recoverableErrors.some(pattern => - error.message.toLowerCase().includes(pattern) - ); - } - - /** - * Validate calendar configuration - */ - private validateConfiguration(): boolean { - try { - const mode = calendarConfig.getCalendarMode(); - const gridSettings = calendarConfig.getGridSettings(); - - // Basic validation - if (!mode || !['date', 'resource'].includes(mode)) { - console.error('Invalid calendar mode:', mode); - return false; - } - - if (!gridSettings.hourHeight || gridSettings.hourHeight < 20) { - console.error('Invalid hour height:', gridSettings.hourHeight); - return false; - } - - return true; - } catch (error) { - console.error('Configuration validation failed:', error); - return false; - } - } - - /** - * Get current period for data loading - */ - private getCurrentPeriod(): { start: string; end: string } { - const currentDate = calendarConfig.getSelectedDate() || new Date(); - const mode = calendarConfig.getCalendarMode(); - - if (mode === 'date') { - const dateSettings = calendarConfig.getDateViewSettings(); - - if (dateSettings.period === 'week') { - const weekStart = new Date(currentDate); - weekStart.setDate(currentDate.getDate() - currentDate.getDay()); - const weekEnd = new Date(weekStart); - weekEnd.setDate(weekStart.getDate() + 6); - - return { - start: weekStart.toISOString().split('T')[0], - end: weekEnd.toISOString().split('T')[0] - }; - } - } - - // Default to current day - return { - start: currentDate.toISOString().split('T')[0], - end: currentDate.toISOString().split('T')[0] - }; - } - - /** - * Utility methods - */ - private recordStateChange(state: CalendarState): void { - this.stateHistory.push({ - state, - timestamp: Date.now() - }); - } - - private startPhase(phase: InitializationPhase): void { - this.phaseTimings.set(phase, { start: Date.now() }); - } - - private endPhase(phase: InitializationPhase): void { - const timing = this.phaseTimings.get(phase); - if (timing) { - timing.end = Date.now(); - console.log(`⏱️ ${phase} completed in ${timing.end - timing.start}ms`); - } - } - - private getPhaseTimings(): Record { - const timings: Record = {}; - - this.phaseTimings.forEach((timing, phase) => { - if (timing.start && timing.end) { - timings[phase] = timing.end - timing.start; - } - }); - - return timings; - } - - private emitEvent(type: string, component: string, data?: any): void { - const event: CalendarEvent = { - type, - component, - timestamp: Date.now(), - data, - metadata: { - phase: this.getCurrentPhase() - } - }; - - eventBus.emit(type, event); - } - - private async waitForEvent(eventType: string, timeout: number = 5000): Promise { - return new Promise((resolve, reject) => { - const timer = setTimeout(() => { - reject(new Error(`Timeout waiting for event: ${eventType}`)); - }, timeout); - - const handler = (event: Event) => { - clearTimeout(timer); - resolve((event as CustomEvent).detail); - eventBus.off(eventType, handler); - }; - - eventBus.on(eventType, handler); - }); - } - - /** - * Debug methods - */ - getStateHistory(): Array<{ state: CalendarState; timestamp: number }> { - return [...this.stateHistory]; - } - - getInitializationReport(): any { - return { - currentState: this.currentState, - totalTime: Date.now() - this.initializationStartTime, - phaseTimings: this.getPhaseTimings(), - stateHistory: this.stateHistory - }; - } -} \ No newline at end of file diff --git a/src/managers/EventManager.ts b/src/managers/EventManager.ts index 8d75494..47b6907 100644 --- a/src/managers/EventManager.ts +++ b/src/managers/EventManager.ts @@ -20,45 +20,26 @@ export class EventManager { } private setupEventListeners(): void { - // Listen for state-driven data loading request - this.eventBus.on(StateEvents.DATA_LOADING_STARTED, (e: Event) => { - const detail = (e as CustomEvent).detail; - console.log('EventManager: Received DATA_LOADING_STARTED, starting data load'); - - this.loadMockData().then(() => { - console.log('EventManager: loadMockData() completed, emitting DATA_LOADED'); - // Emit state-driven data loaded event - this.eventBus.emit(StateEvents.DATA_LOADED, { - type: StateEvents.DATA_LOADED, - component: 'EventManager', - timestamp: Date.now(), - data: { - eventCount: this.events.length, - calendarMode: calendarConfig.getCalendarMode(), - period: detail.data?.period || { start: '', end: '' }, - events: this.events // Include actual events for EventRenderer - }, - metadata: { - phase: 'data-loading' - } - }); - }).catch(error => { - console.error('EventManager: loadMockData() failed:', error); - this.eventBus.emit(StateEvents.DATA_FAILED, { - type: StateEvents.DATA_FAILED, - component: 'EventManager', - timestamp: Date.now(), - error, - metadata: { - phase: 'data-loading' - } - }); + // Keep only UI-related event listeners here if needed + // Data loading is now handled via direct method calls + } + + /** + * Public method to load data - called directly by CalendarManager + */ + public async loadData(): Promise { + console.log('EventManager: Loading data via direct call'); + await this.loadMockData(); + console.log(`EventManager: Data loaded successfully - ${this.events.length} events`); + + // Debug: Log first few events + if (this.events.length > 0) { + console.log('EventManager: First event:', { + title: this.events[0].title, + start: this.events[0].start, + end: this.events[0].end }); - }); - - - // Legacy event listeners removed - data is now managed via state-driven events only - + } } private async loadMockData(): Promise { diff --git a/src/managers/EventRenderer.ts b/src/managers/EventRenderer.ts index b739a5b..0cf477a 100644 --- a/src/managers/EventRenderer.ts +++ b/src/managers/EventRenderer.ts @@ -11,99 +11,31 @@ import { CalendarTypeFactory } from '../factories/CalendarTypeFactory'; */ export class EventRenderer { private eventBus: IEventBus; - private pendingEvents: CalendarEvent[] = []; - private dataReady: boolean = false; - private gridReady: boolean = false; constructor(eventBus: IEventBus) { this.eventBus = eventBus; this.setupEventListeners(); } - private setupEventListeners(): void { - // Listen for state-driven data loaded event - this.eventBus.on(StateEvents.DATA_LOADED, (event: Event) => { - const customEvent = event as CustomEvent; - // Events are in customEvent.detail (direct from StateEvent payload) - const eventCount = customEvent.detail.data?.eventCount || 0; - const events = customEvent.detail.data?.events || []; - console.log('EventRenderer: Received DATA_LOADED with', eventCount, 'events'); - this.pendingEvents = events; // Store the actual events - this.dataReady = true; - this.tryRenderEvents(); - }); - - // Listen for state-driven grid rendered event - this.eventBus.on(StateEvents.GRID_RENDERED, (event: Event) => { - const customEvent = event as CustomEvent; - console.log('EventRenderer: Received GRID_RENDERED'); - this.gridReady = true; - this.tryRenderEvents(); - }); - - - this.eventBus.on(EventTypes.VIEW_RENDERED, () => { - // Clear existing events when view changes - this.clearEvents(); - }); - - // Handle calendar type changes - this.eventBus.on(EventTypes.CALENDAR_TYPE_CHANGED, () => { - // Re-render events with new strategy - this.tryRenderEvents(); - }); - } - - private tryRenderEvents(): void { - // Only render if we have both data and grid ready - console.log('EventRenderer: tryRenderEvents called', { - dataReady: this.dataReady, - gridReady: this.gridReady, - pendingEvents: this.pendingEvents.length - }); + /** + * Public method to render events - called directly by CalendarManager + */ + public async renderEvents(events: CalendarEvent[]): Promise { + console.log('EventRenderer: Direct renderEvents called with', events.length, 'events'); - if (!this.dataReady || !this.gridReady) { - console.log('EventRenderer: Waiting - data ready:', this.dataReady, 'grid ready:', this.gridReady); + // Debug: Check if we have any events + if (events.length === 0) { + console.warn('EventRenderer: No events to render'); return; } - if (this.pendingEvents.length > 0) { - const calendarType = calendarConfig.getCalendarMode(); - let columnsSelector = calendarType === 'resource' ? 'swp-resource-column' : 'swp-day-column'; - const columns = document.querySelectorAll(columnsSelector); - - console.log(`EventRenderer: Found ${columns.length} ${columnsSelector} elements for ${calendarType} calendar`); - - if (columns.length > 0) { - console.log('🎨 EventRenderer: Both data and grid ready, rendering events!'); - const eventCount = this.pendingEvents.length; - this.renderEvents(this.pendingEvents); - this.pendingEvents = []; // Clear pending events after rendering - - // Emit events rendered event - this.eventBus.emit(StateEvents.EVENTS_RENDERED, { - type: StateEvents.EVENTS_RENDERED, - component: 'EventRenderer', - timestamp: Date.now(), - data: { - eventCount, - calendarMode: calendarType, - renderMethod: 'state-driven' - }, - metadata: { - phase: 'event-rendering' - } - }); - } else { - console.log('EventRenderer: Grid not ready yet, columns not found'); - } - } else { - console.log('EventRenderer: No pending events to render'); - } - } - - private renderEvents(events: CalendarEvent[]): void { - console.log('EventRenderer: renderEvents called with', events.length, 'events'); + // Debug: Log first event details + console.log('EventRenderer: First event details:', { + title: events[0].title, + start: events[0].start, + end: events[0].end, + allDay: events[0].allDay + }); // Get the appropriate event renderer strategy const calendarType = calendarConfig.getCalendarMode(); @@ -111,27 +43,62 @@ export class EventRenderer { console.log(`EventRenderer: Using ${calendarType} event renderer strategy`); + // Debug: Check if columns exist + const columns = document.querySelectorAll('swp-day-column'); + console.log(`EventRenderer: Found ${columns.length} day columns in DOM`); + // Use strategy to render events eventRenderer.renderEvents(events, calendarConfig); + + // Debug: Check if events are actually in DOM after rendering + setTimeout(() => { + const allRenderedEvents = document.querySelectorAll('swp-event'); + console.log(`EventRenderer: DOM check - ${allRenderedEvents.length} swp-event elements found in DOM`); + + if (allRenderedEvents.length > 0) { + const firstEvent = allRenderedEvents[0] as HTMLElement; + console.log('EventRenderer: First event DOM details:', { + visible: firstEvent.offsetWidth > 0 && firstEvent.offsetHeight > 0, + offsetParent: !!firstEvent.offsetParent, + computedDisplay: window.getComputedStyle(firstEvent).display, + computedVisibility: window.getComputedStyle(firstEvent).visibility, + computedOpacity: window.getComputedStyle(firstEvent).opacity, + parentElement: firstEvent.parentElement?.tagName + }); + } + }, 100); + + console.log(`EventRenderer: Successfully rendered ${events.length} events`); + } - // Emit event rendered - this.eventBus.emit(EventTypes.EVENT_RENDERED, { - count: events.filter(e => !e.allDay).length + private setupEventListeners(): void { + // Keep only UI-related event listeners + this.eventBus.on(EventTypes.VIEW_RENDERED, () => { + // Clear existing events when view changes + this.clearEvents(); + }); + + // Handle calendar type changes + this.eventBus.on(EventTypes.CALENDAR_TYPE_CHANGED, () => { + // Re-render would need to be triggered by CalendarManager now + this.clearEvents(); }); } + private clearEvents(): void { + console.warn(`🗑️ EventRenderer: clearEvents() called from EventRenderer manager`); const calendarType = calendarConfig.getCalendarMode(); const eventRenderer = CalendarTypeFactory.getEventRenderer(calendarType); eventRenderer.clearEvents(); } public refresh(): void { - this.tryRenderEvents(); + // Refresh would need to be coordinated by CalendarManager now + this.clearEvents(); } public destroy(): void { - this.pendingEvents = []; this.clearEvents(); } } \ No newline at end of file diff --git a/src/managers/GridManager.ts b/src/managers/GridManager.ts index 8b5f0af..6f27def 100644 --- a/src/managers/GridManager.ts +++ b/src/managers/GridManager.ts @@ -54,15 +54,11 @@ export class GridManager { private findElements(): void { this.grid = document.querySelector('swp-calendar-container'); + console.log('GridManager: findElements called, found swp-calendar-container:', !!this.grid); } private subscribeToEvents(): void { - // Listen for state-driven rendering start event - eventBus.on(StateEvents.RENDERING_STARTED, (e: Event) => { - const detail = (e as CustomEvent).detail; - console.log('GridManager: Received RENDERING_STARTED, starting DOM structure setup'); - this.render(); - }); + // State-driven events removed - render() is now called directly by CalendarManager // Re-render grid on config changes eventBus.on(EventTypes.CONFIG_UPDATE, (e: Event) => { @@ -119,39 +115,23 @@ export class GridManager { } /** - * Render the complete grid structure + * Render the complete grid structure - now returns Promise for direct calls */ - render(): void { - if (!this.grid) return; + async render(): Promise { + if (!this.grid) { + console.warn('GridManager: render() called but this.grid is null, re-finding elements'); + this.findElements(); + if (!this.grid) { + throw new Error('GridManager: swp-calendar-container not found, cannot render'); + } + } + console.log('GridManager: Starting render with grid element:', this.grid); this.updateGridStyles(); this.renderGrid(); - // Emit state-driven grid rendered event const columnCount = this.getColumnCount(); - console.log('GridManager: Emitting GRID_RENDERED event'); - - eventBus.emit(StateEvents.GRID_RENDERED, { - type: StateEvents.GRID_RENDERED, - component: 'GridManager', - timestamp: Date.now(), - data: { - columnCount, - gridMode: calendarConfig.getCalendarMode(), - domElementsCreated: [ - 'swp-header-spacer', - 'swp-time-axis', - 'swp-grid-container', - 'swp-calendar-header', - 'swp-scrollable-content' - ] - }, - metadata: { - phase: 'rendering' - } - }); - - console.log('GridManager: GRID_RENDERED event emitted'); + console.log(`GridManager: Render complete - created ${columnCount} columns`); } /** diff --git a/src/managers/ScrollManager.ts b/src/managers/ScrollManager.ts index d77256a..f960a6a 100644 --- a/src/managers/ScrollManager.ts +++ b/src/managers/ScrollManager.ts @@ -24,22 +24,16 @@ export class ScrollManager { this.subscribeToEvents(); } + /** + * Public method to initialize scroll after grid is rendered + */ + public initialize(): void { + console.log('ScrollManager: Initialize called, setting up scrolling'); + this.setupScrolling(); + } + private subscribeToEvents(): void { - // Initialize scroll when grid is rendered - eventBus.on(StateEvents.GRID_RENDERED, () => { - console.log('ScrollManager: Received GRID_RENDERED event'); - this.setupScrolling(); - }); - - // Add safety check - if grid is already rendered when ScrollManager initializes - // This prevents race condition where GridManager renders before ScrollManager subscribes - //setTimeout(() => { - // const existingGrid = document.querySelector('swp-calendar-container'); - // if (existingGrid && existingGrid.children.length > 0) { - // console.log('ScrollManager: Grid already exists, setting up scrolling'); - // this.setupScrolling(); - // } - //}, 0); + // State events removed - initialize() is now called directly after grid render // Handle window resize window.addEventListener('resize', () => { diff --git a/src/renderers/ColumnRenderer.ts b/src/renderers/ColumnRenderer.ts index 14f128a..4fdf151 100644 --- a/src/renderers/ColumnRenderer.ts +++ b/src/renderers/ColumnRenderer.ts @@ -27,8 +27,8 @@ export class DateColumnRenderer implements ColumnRenderer { const { currentWeek, config } = context; const dates = this.getWeekDates(currentWeek); - const weekDays = config.get('weekDays'); - const daysToShow = dates.slice(0, weekDays); + const dateSettings = config.getDateViewSettings(); + const daysToShow = dates.slice(0, dateSettings.weekDays); console.log('DateColumnRenderer: About to render', daysToShow.length, 'date columns'); diff --git a/src/renderers/EventRenderer.ts b/src/renderers/EventRenderer.ts index e0e2e52..474c7fb 100644 --- a/src/renderers/EventRenderer.ts +++ b/src/renderers/EventRenderer.ts @@ -36,9 +36,14 @@ export abstract class BaseEventRenderer implements EventRendererStrategy { 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); + 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); @@ -71,14 +76,23 @@ export abstract class BaseEventRenderer implements EventRendererStrategy { `; 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 dayStartHour = config.get('dayStartHour'); - const hourHeight = config.get('hourHeight'); + 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(); @@ -92,6 +106,17 @@ export abstract class BaseEventRenderer implements EventRendererStrategy { 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 }; } @@ -107,6 +132,9 @@ export abstract class BaseEventRenderer implements EventRendererStrategy { 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()); } } @@ -119,7 +147,16 @@ export class DateEventRenderer extends BaseEventRenderer { const eventDate = new Date(event.start); const dateStr = DateUtils.formatDate(eventDate); const dayColumn = document.querySelector(`swp-day-column[data-date="${dateStr}"]`) as HTMLElement; - console.log('DateEventRenderer: Looking for day column with date', dateStr, 'found:', !!dayColumn); + + // 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; } }