diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 2305466..5dde62c 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -5,7 +5,8 @@ "Bash(npm run build:*)", "Bash(powershell:*)", "Bash(rg:*)", - "Bash(find:*)" + "Bash(find:*)", + "Bash(mv:*)" ], "deny": [] } diff --git a/docs/calendar-initialization-sequence.md b/docs/calendar-initialization-sequence.md new file mode 100644 index 0000000..22c29df --- /dev/null +++ b/docs/calendar-initialization-sequence.md @@ -0,0 +1,133 @@ +# Calendar Initialization Sequence Diagram + +Dette diagram viser den aktuelle initialization sekvens baseret på koden. + +```mermaid +sequenceDiagram + participant Browser + participant Index as index.ts + participant MF as ManagerFactory + participant CTF as CalendarTypeFactory + participant EB as EventBus + participant CM as CalendarManager + participant EM as EventManager + participant ERS as EventRenderingService + participant GM as GridManager + participant GSM as GridStyleManager + participant GR as GridRenderer + participant SM as ScrollManager + participant NM as NavigationManager + participant NR as NavigationRenderer + participant VM as ViewManager + + Browser->>Index: DOMContentLoaded + Index->>Index: initializeCalendar() + + Index->>CTF: initialize() + CTF-->>Index: Factory ready + + Index->>MF: getInstance() + MF-->>Index: Factory instance + + Index->>MF: createManagers(eventBus, config) + + MF->>EM: new EventManager(eventBus) + EM->>EB: setupEventListeners() + EM-->>MF: EventManager ready + + MF->>ERS: new EventRenderingService(eventBus, eventManager) + ERS->>EB: setupEventListeners() + ERS-->>MF: EventRenderingService ready + + MF->>GM: new GridManager() + GM->>GSM: new GridStyleManager(config) + GM->>GR: new GridRenderer(config) + GM->>EB: subscribeToEvents() + GM-->>MF: GridManager ready + + MF->>SM: new ScrollManager() + SM->>EB: subscribeToEvents() + SM-->>MF: ScrollManager ready + + MF->>NM: new NavigationManager(eventBus) + NM->>NR: new NavigationRenderer(eventBus) + NR->>EB: setupEventListeners() + NM->>EB: setupEventListeners() + NM-->>MF: NavigationManager ready + + MF->>VM: new ViewManager(eventBus) + VM->>EB: setupEventListeners() + VM-->>MF: ViewManager ready + + MF->>CM: new CalendarManager(eventBus, config, deps...) + CM->>EB: setupEventListeners() + CM-->>MF: CalendarManager ready + + MF-->>Index: All managers created + + Index->>EB: setDebug(true) + Index->>MF: initializeManagers(managers) + MF->>CM: initialize() + + CM->>EM: loadData() + EM->>EM: loadMockData() + EM->>EM: processCalendarData() + EM-->>CM: Data loaded + + CM->>GM: setResourceData(resourceData) + GM-->>CM: Resource data set + + CM->>GM: render() + GM->>GSM: updateGridStyles(resourceData) + GM->>GR: renderGrid(grid, currentWeek, resourceData, allDayEvents) + GR-->>GM: Grid rendered + + GM->>EB: emit(GRID_RENDERED, context) + EB-->>ERS: GRID_RENDERED event + + ERS->>EM: getEventsForPeriod(startDate, endDate) + EM-->>ERS: Filtered events + ERS->>ERS: strategy.renderEvents() + + CM->>SM: initialize() + SM->>SM: setupScrolling() + + CM->>CM: setView(currentView) + CM->>EB: emit(VIEW_CHANGED, viewData) + + CM->>CM: setCurrentDate(currentDate) + CM->>EB: emit(DATE_CHANGED, dateData) + + CM->>EB: emit(CALENDAR_INITIALIZED, initData) + + EB-->>NM: CALENDAR_INITIALIZED + NM->>NM: updateWeekInfo() + NM->>EB: emit(WEEK_INFO_UPDATED, weekInfo) + EB-->>NR: WEEK_INFO_UPDATED + NR->>NR: updateWeekInfoInDOM() + + EB-->>VM: CALENDAR_INITIALIZED + VM->>VM: initializeView() + VM->>EB: emit(VIEW_RENDERED, viewData) + + CM-->>MF: Initialization complete + MF-->>Index: All managers initialized + + Index->>Browser: Calendar ready +``` + +## Aktuel Arkitektur Status + +### Factory Pattern +- ManagerFactory håndterer manager instantiering +- Proper dependency injection via constructor + +### Event-Driven Communication +- EventBus koordinerer kommunikation mellem managers +- NavigationRenderer lytter til WEEK_INFO_UPDATED events +- EventRenderingService reagerer på GRID_RENDERED events + +### Separation of Concerns +- Managers håndterer business logic +- Renderers håndterer DOM manipulation +- EventBus håndterer kommunikation \ No newline at end of file diff --git a/src/factories/ManagerFactory.ts b/src/factories/ManagerFactory.ts new file mode 100644 index 0000000..055c2d4 --- /dev/null +++ b/src/factories/ManagerFactory.ts @@ -0,0 +1,88 @@ +import { IEventBus } from '../types/CalendarTypes'; +import { CalendarConfig } from '../core/CalendarConfig'; +import { EventManager } from '../managers/EventManager'; +import { EventRenderingService } from '../renderers/EventRendererManager'; +import { GridManager } from '../managers/GridManager'; +import { ScrollManager } from '../managers/ScrollManager'; +import { NavigationManager } from '../managers/NavigationManager'; +import { ViewManager } from '../managers/ViewManager'; +import { CalendarManager } from '../managers/CalendarManager'; + +/** + * Factory for creating and managing calendar managers with proper dependency injection + */ +export class ManagerFactory { + private static instance: ManagerFactory; + + private constructor() {} + + public static getInstance(): ManagerFactory { + if (!ManagerFactory.instance) { + ManagerFactory.instance = new ManagerFactory(); + } + return ManagerFactory.instance; + } + + /** + * Create all managers with proper dependency injection + */ + public createManagers(eventBus: IEventBus, config: CalendarConfig): { + eventManager: EventManager; + eventRenderer: EventRenderingService; + gridManager: GridManager; + scrollManager: ScrollManager; + navigationManager: NavigationManager; + viewManager: ViewManager; + calendarManager: CalendarManager; + } { + console.log('🏭 ManagerFactory: Creating managers with proper DI...'); + + // Create managers in dependency order + const eventManager = new EventManager(eventBus); + const eventRenderer = new EventRenderingService(eventBus, eventManager); + const gridManager = new GridManager(); + const scrollManager = new ScrollManager(); + const navigationManager = new NavigationManager(eventBus); + const viewManager = new ViewManager(eventBus); + + // CalendarManager depends on all other managers + const calendarManager = new CalendarManager( + eventBus, + config, + eventManager, + gridManager, + eventRenderer, + scrollManager + ); + + console.log('✅ ManagerFactory: All managers created successfully'); + + return { + eventManager, + eventRenderer, + gridManager, + scrollManager, + navigationManager, + viewManager, + calendarManager + }; + } + + /** + * Initialize all managers in the correct order + */ + public async initializeManagers(managers: { + calendarManager: CalendarManager; + [key: string]: any; + }): Promise { + console.log('🚀 ManagerFactory: Initializing managers...'); + + try { + await managers.calendarManager.initialize(); + console.log('✅ ManagerFactory: All managers initialized successfully'); + } catch (error) { + console.error('❌ ManagerFactory: Manager initialization failed:', error); + throw error; + } + } +} \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index a63cea6..b628e64 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,29 +1,14 @@ // Main entry point for Calendar Plantempus import { eventBus } from './core/EventBus.js'; -import { CalendarManager } from './managers/CalendarManager.js'; -import { NavigationManager } from './managers/NavigationManager.js'; -import { ViewManager } from './managers/ViewManager.js'; -import { EventManager } from './managers/EventManager.js'; -import { EventRenderer } from './renderers/EventRendererManager.js'; -import { GridManager } from './managers/GridManager.js'; -import { ScrollManager } from './managers/ScrollManager.js'; import { calendarConfig } from './core/CalendarConfig.js'; import { CalendarTypeFactory } from './factories/CalendarTypeFactory.js'; +import { ManagerFactory } from './factories/ManagerFactory.js'; /** * Initialize the calendar application with simple direct calls */ async function initializeCalendar(): Promise { - console.log('🗓️ Initializing Calendar Plantempus with simple coordination...'); - - // Declare managers outside try block for global access - let calendarManager: CalendarManager; - let navigationManager: NavigationManager; - let viewManager: ViewManager; - let eventManager: EventManager; - let eventRenderer: EventRenderer; - let gridManager: GridManager; - let scrollManager: ScrollManager; + console.log('🗓️ Initializing Calendar Plantempus with factory pattern...'); try { // Use the singleton calendar configuration @@ -33,46 +18,29 @@ async function initializeCalendar(): Promise { console.log('🏭 Initializing CalendarTypeFactory...'); CalendarTypeFactory.initialize(); - // Create all managers - console.log('📋 Creating managers...'); - calendarManager = new CalendarManager(eventBus, config); - navigationManager = new NavigationManager(eventBus); // No EventRenderer dependency - viewManager = new ViewManager(eventBus); - eventManager = new EventManager(eventBus); - eventRenderer = new EventRenderer(eventBus, eventManager); // Pass EventManager - gridManager = new GridManager(); - scrollManager = new ScrollManager(); - - // Set manager references in CalendarManager - calendarManager.setManagers(eventManager, gridManager, eventRenderer, scrollManager); + // Create managers using factory pattern + const managerFactory = ManagerFactory.getInstance(); + const managers = managerFactory.createManagers(eventBus, config); // Enable debug mode for development eventBus.setDebug(true); - // Initialize using simple direct calls - console.log('🚀 Starting simple initialization...'); - await calendarManager.initialize(); + // Initialize all managers + await managerFactory.initializeManagers(managers); console.log('🎊 Calendar Plantempus initialized successfully!'); - console.log('📊 Initialization Report:', calendarManager.getInitializationReport()); + console.log('📊 Initialization Report:', managers.calendarManager.getInitializationReport()); + + // Expose to window for debugging + (window as any).calendarDebug = { + eventBus, + ...managers + }; } catch (error) { console.error('💥 Calendar initialization failed:', error); - // Could implement fallback or retry logic here throw error; } - - // Expose to window for debugging - (window as any).calendarDebug = { - eventBus, - calendarManager, - navigationManager, - viewManager, - eventManager, - eventRenderer, - gridManager, - scrollManager - }; } // Initialize when DOM is ready - now handles async properly diff --git a/src/interfaces/IManager.ts b/src/interfaces/IManager.ts new file mode 100644 index 0000000..e8ee254 --- /dev/null +++ b/src/interfaces/IManager.ts @@ -0,0 +1,53 @@ +/** + * Base interface for all managers + */ +export interface IManager { + /** + * Initialize the manager + */ + initialize?(): Promise | void; + + /** + * Refresh the manager's state + */ + refresh?(): void; + + /** + * Destroy the manager and clean up resources + */ + destroy?(): void; +} + +/** + * Interface for managers that handle events + */ +export interface IEventManager extends IManager { + loadData(): Promise; + getEvents(): any[]; + getEventsForPeriod(startDate: Date, endDate: Date): any[]; +} + +/** + * Interface for managers that handle rendering + */ +export interface IRenderingManager extends IManager { + render(): Promise | void; +} + +/** + * Interface for managers that handle navigation + */ +export interface INavigationManager extends IManager { + getCurrentWeek(): Date; + navigateToToday(): void; + navigateToNextWeek(): void; + navigateToPreviousWeek(): void; +} + +/** + * Interface for managers that handle scrolling + */ +export interface IScrollManager extends IManager { + scrollTo(scrollTop: number): void; + scrollToHour(hour: number): void; +} \ No newline at end of file diff --git a/src/managers/CalendarManager.ts b/src/managers/CalendarManager.ts index 9c6f482..a19481c 100644 --- a/src/managers/CalendarManager.ts +++ b/src/managers/CalendarManager.ts @@ -4,7 +4,7 @@ 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 { EventRenderer } from '../renderers/EventRendererManager.js'; +import { EventRenderingService } from '../renderers/EventRendererManager.js'; import { ScrollManager } from './ScrollManager.js'; /** @@ -16,27 +16,28 @@ export class CalendarManager { private config: CalendarConfig; private eventManager: EventManager; private gridManager: GridManager; - private eventRenderer: EventRenderer; + private eventRenderer: EventRenderingService; private scrollManager: ScrollManager; private currentView: CalendarView = 'week'; private currentDate: Date = new Date(); private isInitialized: boolean = false; - constructor(eventBus: IEventBus, config: CalendarConfig) { + constructor( + eventBus: IEventBus, + config: CalendarConfig, + eventManager: EventManager, + gridManager: GridManager, + eventRenderer: EventRenderingService, + scrollManager: ScrollManager + ) { this.eventBus = eventBus; this.config = config; - this.setupEventListeners(); - console.log('📋 CalendarManager: Created with direct coordination'); - } - - /** - * 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; + this.setupEventListeners(); + console.log('📋 CalendarManager: Created with proper dependency injection'); } /** diff --git a/src/managers/EventManager.ts b/src/managers/EventManager.ts index 95d9abc..a778b6b 100644 --- a/src/managers/EventManager.ts +++ b/src/managers/EventManager.ts @@ -184,81 +184,6 @@ export class EventManager { this.syncEvents(); } - /** - * Load events for a specific week into a container (POC-style) - */ - private loadEventsForWeek(weekStart: Date, weekEnd: Date, container: HTMLElement): void { - console.log(`EventManager: Loading events for week ${weekStart.toDateString()} - ${weekEnd.toDateString()}`); - - // Filter events for this week - const weekEvents = this.events.filter(event => { - const eventDate = new Date(event.start); - return eventDate >= weekStart && eventDate <= weekEnd; - }); - - console.log(`EventManager: Found ${weekEvents.length} events for this week`); - - // Render events in the container (POC approach) - this.renderEventsInContainer(weekEvents, container); - } - - /** - * Render events in a specific container (POC-style) - */ - private renderEventsInContainer(events: CalendarEvent[], container: HTMLElement): void { - const dayColumns = container.querySelectorAll('swp-day-column'); - - events.forEach(event => { - const eventDate = new Date(event.start); - const dayOfWeek = eventDate.getDay(); // 0 = Sunday - const column = dayColumns[dayOfWeek]; - - if (column) { - const eventsLayer = column.querySelector('swp-events-layer'); - if (eventsLayer) { - this.renderEventInColumn(event, eventsLayer as HTMLElement); - } - } - }); - } - - /** - * Render a single event in a column (POC-style) - */ - private renderEventInColumn(event: CalendarEvent, eventsLayer: HTMLElement): void { - const eventElement = document.createElement('swp-event'); - eventElement.dataset.type = event.type || 'meeting'; - - // Calculate position (simplified - assumes 7 AM start like POC) - const startTime = new Date(event.start); - const hours = startTime.getHours(); - const minutes = startTime.getMinutes(); - const startMinutes = (hours - 7) * 60 + minutes; // 7 is start hour like POC - - // Calculate duration - const endTime = new Date(event.end); - const durationMs = endTime.getTime() - startTime.getTime(); - const durationMinutes = Math.floor(durationMs / (1000 * 60)); - - eventElement.style.top = `${startMinutes}px`; - eventElement.style.height = `${durationMinutes}px`; - - eventElement.innerHTML = ` - ${this.formatTime(hours, minutes)} - ${event.title} - `; - - eventsLayer.appendChild(eventElement); - } - - /** - * Format time for display (POC-style) - */ - private formatTime(hours: number, minutes: number): string { - const period = hours >= 12 ? 'PM' : 'AM'; - const displayHours = hours % 12 || 12; - return `${displayHours}:${String(minutes).padStart(2, '0')} ${period}`; - } public destroy(): void { this.events = []; diff --git a/src/managers/NavigationManager.ts b/src/managers/NavigationManager.ts index 8536e26..9ec7b24 100644 --- a/src/managers/NavigationManager.ts +++ b/src/managers/NavigationManager.ts @@ -197,25 +197,12 @@ export class NavigationManager { }); } - // Utility functions (from POC) - moved formatting to NavigationRenderer - private updateWeekInfo(): void { const weekNumber = DateUtils.getWeekNumber(this.currentWeek); const weekEnd = DateUtils.addDays(this.currentWeek, 6); const dateRange = DateUtils.formatDateRange(this.currentWeek, weekEnd); - // Update week info in DOM - const weekNumberElement = document.querySelector('swp-week-number'); - const dateRangeElement = document.querySelector('swp-date-range'); - - if (weekNumberElement) - weekNumberElement.textContent = `Week ${weekNumber}`; - - - if (dateRangeElement) - dateRangeElement.textContent = dateRange; - - // Notify other managers about week info update + // Notify other managers about week info update - DOM manipulation should happen via events this.eventBus.emit(EventTypes.WEEK_INFO_UPDATED, { weekNumber, dateRange, diff --git a/src/renderers/EventRendererManager.ts b/src/renderers/EventRendererManager.ts index c1d5798..6590a08 100644 --- a/src/renderers/EventRendererManager.ts +++ b/src/renderers/EventRendererManager.ts @@ -8,10 +8,10 @@ import { EventManager } from '../managers/EventManager'; import { EventRendererStrategy } from './EventRenderer'; /** - * EventRenderer - Render events i DOM med positionering using Strategy Pattern + * EventRenderingService - Render events i DOM med positionering using Strategy Pattern * Håndterer event positioning og overlap detection */ -export class EventRenderer { +export class EventRenderingService { private eventBus: IEventBus; private eventManager: EventManager; private strategy: EventRendererStrategy; diff --git a/src/renderers/GridRenderer.ts b/src/renderers/GridRenderer.ts index 2f60dd1..86e0b9b 100644 --- a/src/renderers/GridRenderer.ts +++ b/src/renderers/GridRenderer.ts @@ -3,7 +3,6 @@ import { ResourceCalendarData } from '../types/CalendarTypes'; import { CalendarTypeFactory } from '../factories/CalendarTypeFactory'; import { HeaderRenderContext } from './HeaderRenderer'; import { ColumnRenderContext } from './ColumnRenderer'; - /** * GridRenderer - Handles DOM rendering for the calendar grid * Separated from GridManager to follow Single Responsibility Principle diff --git a/src/renderers/NavigationRenderer.ts b/src/renderers/NavigationRenderer.ts index 365abf0..82ef28a 100644 --- a/src/renderers/NavigationRenderer.ts +++ b/src/renderers/NavigationRenderer.ts @@ -11,6 +11,34 @@ export class NavigationRenderer { constructor(eventBus: IEventBus) { this.eventBus = eventBus; + this.setupEventListeners(); + } + + /** + * Setup event listeners for DOM updates + */ + private setupEventListeners(): void { + this.eventBus.on(EventTypes.WEEK_INFO_UPDATED, (event: Event) => { + const customEvent = event as CustomEvent; + const { weekNumber, dateRange } = customEvent.detail; + this.updateWeekInfoInDOM(weekNumber, dateRange); + }); + } + + /** + * Update week info in DOM elements + */ + private updateWeekInfoInDOM(weekNumber: number, dateRange: string): void { + const weekNumberElement = document.querySelector('swp-week-number'); + const dateRangeElement = document.querySelector('swp-date-range'); + + if (weekNumberElement) { + weekNumberElement.textContent = `Week ${weekNumber}`; + } + + if (dateRangeElement) { + dateRangeElement.textContent = dateRange; + } } /**