/** * GridManager - Simplified grid manager using centralized GridRenderer * Delegates DOM rendering to GridRenderer, focuses on coordination */ import { eventBus } from '../core/EventBus'; import { calendarConfig } from '../core/CalendarConfig'; import { CoreEvents } from '../constants/CoreEvents'; import { ResourceCalendarData, CalendarView } from '../types/CalendarTypes'; import { GridRenderer } from '../renderers/GridRenderer'; import { DateCalculator } from '../utils/DateCalculator'; /** * Simplified GridManager focused on coordination, delegates rendering to GridRenderer */ export class GridManager { private container: HTMLElement | null = null; private currentDate: Date = new Date(); private resourceData: ResourceCalendarData | null = null; private currentView: CalendarView = 'week'; private gridRenderer: GridRenderer; private eventCleanup: (() => void)[] = []; constructor() { // Initialize GridRenderer with config this.gridRenderer = new GridRenderer(); this.init(); } private init(): void { this.findElements(); this.subscribeToEvents(); } private findElements(): void { this.container = document.querySelector('swp-calendar-container'); } private subscribeToEvents(): void { // Listen for view changes this.eventCleanup.push( eventBus.on(CoreEvents.VIEW_CHANGED, (e: Event) => { const detail = (e as CustomEvent).detail; this.currentView = detail.currentView; this.render(); }) ); // Listen for config changes that affect rendering this.eventCleanup.push( eventBus.on(CoreEvents.REFRESH_REQUESTED, (e: Event) => { this.render(); }) ); this.eventCleanup.push( eventBus.on(CoreEvents.WORKWEEK_CHANGED, () => { this.render(); }) ); } /** * Switch to a different view */ public switchView(view: CalendarView): void { this.currentView = view; this.render(); } /** * Set resource data for resource calendar mode */ public setResourceData(resourceData: ResourceCalendarData | null): void { this.resourceData = resourceData; this.render(); } /** * Main render method - delegates to GridRenderer */ public async render(): Promise { if (!this.container) { return; } // Delegate to GridRenderer with current view context this.gridRenderer.renderGrid( this.container, this.currentDate, this.resourceData ); // Calculate period range using DateCalculator const periodRange = this.getPeriodRange(); // Get layout config based on current view const layoutConfig = this.getLayoutConfig(); // Emit grid rendered event eventBus.emit(CoreEvents.GRID_RENDERED, { container: this.container, currentDate: this.currentDate, startDate: periodRange.startDate, endDate: periodRange.endDate, layoutConfig: layoutConfig, columnCount: layoutConfig.columnCount }); } /** * Get current period label using DateCalculator */ public getCurrentPeriodLabel(): string { switch (this.currentView) { case 'week': case 'day': const weekStart = DateCalculator.getISOWeekStart(this.currentDate); const weekEnd = DateCalculator.getWeekEnd(this.currentDate); return DateCalculator.formatDateRange(weekStart, weekEnd); case 'month': return this.currentDate.toLocaleDateString('en-US', { month: 'long', year: 'numeric' }); default: const defaultWeekStart = DateCalculator.getISOWeekStart(this.currentDate); const defaultWeekEnd = DateCalculator.getWeekEnd(this.currentDate); return DateCalculator.formatDateRange(defaultWeekStart, defaultWeekEnd); } } /** * Navigate to next period using DateCalculator */ public navigateNext(): void { let nextDate: Date; switch (this.currentView) { case 'week': nextDate = DateCalculator.addWeeks(this.currentDate, 1); break; case 'month': nextDate = this.addMonths(this.currentDate, 1); break; case 'day': nextDate = DateCalculator.addDays(this.currentDate, 1); break; default: nextDate = DateCalculator.addWeeks(this.currentDate, 1); } this.currentDate = nextDate; eventBus.emit(CoreEvents.NAVIGATION_COMPLETED, { direction: 'next', newDate: nextDate, periodLabel: this.getCurrentPeriodLabel() }); this.render(); } /** * Navigate to previous period using DateCalculator */ public navigatePrevious(): void { let prevDate: Date; switch (this.currentView) { case 'week': prevDate = DateCalculator.addWeeks(this.currentDate, -1); break; case 'month': prevDate = this.addMonths(this.currentDate, -1); break; case 'day': prevDate = DateCalculator.addDays(this.currentDate, -1); break; default: prevDate = DateCalculator.addWeeks(this.currentDate, -1); } this.currentDate = prevDate; eventBus.emit(CoreEvents.NAVIGATION_COMPLETED, { direction: 'previous', newDate: prevDate, periodLabel: this.getCurrentPeriodLabel() }); this.render(); } /** * Navigate to today */ public navigateToToday(): void { this.currentDate = new Date(); eventBus.emit(CoreEvents.DATE_CHANGED, { newDate: this.currentDate, periodLabel: this.getCurrentPeriodLabel() }); this.render(); } /** * Get current view's display dates using DateCalculator */ public getDisplayDates(): Date[] { switch (this.currentView) { case 'week': const weekStart = DateCalculator.getISOWeekStart(this.currentDate); return DateCalculator.getFullWeekDates(weekStart); case 'month': return this.getMonthDates(this.currentDate); case 'day': return [this.currentDate]; default: const defaultWeekStart = DateCalculator.getISOWeekStart(this.currentDate); return DateCalculator.getFullWeekDates(defaultWeekStart); } } /** * Get period range for current view */ private getPeriodRange(): { startDate: Date; endDate: Date } { switch (this.currentView) { case 'week': const weekStart = DateCalculator.getISOWeekStart(this.currentDate); const weekEnd = DateCalculator.getWeekEnd(this.currentDate); return { startDate: weekStart, endDate: weekEnd }; case 'month': return { startDate: this.getMonthStart(this.currentDate), endDate: this.getMonthEnd(this.currentDate) }; case 'day': return { startDate: this.currentDate, endDate: this.currentDate }; default: const defaultWeekStart = DateCalculator.getISOWeekStart(this.currentDate); const defaultWeekEnd = DateCalculator.getWeekEnd(this.currentDate); return { startDate: defaultWeekStart, endDate: defaultWeekEnd }; } } /** * Get layout config for current view */ private getLayoutConfig(): any { switch (this.currentView) { case 'week': return { columnCount: 7, type: 'week' }; case 'month': return { columnCount: 7, type: 'month' }; case 'day': return { columnCount: 1, type: 'day' }; default: return { columnCount: 7, type: 'week' }; } } /** * Clean up all resources */ public destroy(): void { // Clean up event listeners this.eventCleanup.forEach(cleanup => cleanup()); this.eventCleanup = []; // Clear references this.container = null; this.resourceData = null; } /** * Helper method to add months to a date */ private addMonths(date: Date, months: number): Date { const result = new Date(date); result.setMonth(result.getMonth() + months); return result; } /** * Helper method to get month start */ private getMonthStart(date: Date): Date { const result = new Date(date); result.setDate(1); result.setHours(0, 0, 0, 0); return result; } /** * Helper method to get month end */ private getMonthEnd(date: Date): Date { const result = new Date(date); result.setMonth(result.getMonth() + 1, 0); result.setHours(23, 59, 59, 999); return result; } /** * Helper method to get all dates in a month */ private getMonthDates(date: Date): Date[] { const dates: Date[] = []; const monthStart = this.getMonthStart(date); const monthEnd = this.getMonthEnd(date); for (let d = new Date(monthStart); d <= monthEnd; d.setDate(d.getDate() + 1)) { dates.push(new Date(d)); } return dates; } }