diff --git a/src/datasources/DateColumnDataSource.ts b/src/datasources/DateColumnDataSource.ts new file mode 100644 index 0000000..5edf94e --- /dev/null +++ b/src/datasources/DateColumnDataSource.ts @@ -0,0 +1,120 @@ +import { IColumnDataSource } from '../types/ColumnDataSource'; +import { DateService } from '../utils/DateService'; +import { Configuration } from '../configurations/CalendarConfig'; +import { CalendarView } from '../types/CalendarTypes'; + +/** + * DateColumnDataSource - Provides date-based columns + * + * Calculates which dates to display based on: + * - Current date + * - Current view (day/week/month) + * - Workweek settings + */ +export class DateColumnDataSource implements IColumnDataSource { + private dateService: DateService; + private config: Configuration; + private currentDate: Date; + private currentView: CalendarView; + + constructor( + dateService: DateService, + config: Configuration, + currentDate: Date, + currentView: CalendarView + ) { + this.dateService = dateService; + this.config = config; + this.currentDate = currentDate; + this.currentView = currentView; + } + + /** + * Get columns (dates) to display + */ + public getColumns(): Date[] { + switch (this.currentView) { + case 'week': + return this.getWeekDates(); + case 'month': + return this.getMonthDates(); + case 'day': + return [this.currentDate]; + default: + return this.getWeekDates(); + } + } + + /** + * Get type of datasource + */ + public getType(): 'date' | 'resource' { + return 'date'; + } + + /** + * Update current date + */ + public setCurrentDate(date: Date): void { + this.currentDate = date; + } + + /** + * Update current view + */ + public setCurrentView(view: CalendarView): void { + this.currentView = view; + } + + /** + * Get dates for week view based on workweek settings + */ + private getWeekDates(): Date[] { + const weekStart = this.getISOWeekStart(this.currentDate); + const workWeekSettings = this.config.getWorkWeekSettings(); + return this.dateService.getWorkWeekDates(weekStart, workWeekSettings.workDays); + } + + /** + * Get all dates in current month + */ + private getMonthDates(): Date[] { + const dates: Date[] = []; + const monthStart = this.getMonthStart(this.currentDate); + const monthEnd = this.getMonthEnd(this.currentDate); + + const totalDays = Math.ceil((monthEnd.getTime() - monthStart.getTime()) / (1000 * 60 * 60 * 24)) + 1; + + for (let i = 0; i < totalDays; i++) { + dates.push(this.dateService.addDays(monthStart, i)); + } + + return dates; + } + + /** + * Get ISO week start (Monday) + */ + private getISOWeekStart(date: Date): Date { + const weekBounds = this.dateService.getWeekBounds(date); + return this.dateService.startOfDay(weekBounds.start); + } + + /** + * Get month start + */ + private getMonthStart(date: Date): Date { + const year = date.getFullYear(); + const month = date.getMonth(); + return this.dateService.startOfDay(new Date(year, month, 1)); + } + + /** + * Get month end + */ + private getMonthEnd(date: Date): Date { + const nextMonth = this.dateService.addMonths(date, 1); + const firstOfNextMonth = this.getMonthStart(nextMonth); + return this.dateService.endOfDay(this.dateService.addDays(firstOfNextMonth, -1)); + } +} diff --git a/src/managers/GridManager.ts b/src/managers/GridManager.ts index 693584e..6ad0708 100644 --- a/src/managers/GridManager.ts +++ b/src/managers/GridManager.ts @@ -8,6 +8,9 @@ import { CoreEvents } from '../constants/CoreEvents'; import { CalendarView } from '../types/CalendarTypes'; import { GridRenderer } from '../renderers/GridRenderer'; import { DateService } from '../utils/DateService'; +import { DateColumnDataSource } from '../datasources/DateColumnDataSource'; +import { Configuration } from '../configurations/CalendarConfig'; +import { EventManager } from './EventManager'; /** * Simplified GridManager focused on coordination, delegates rendering to GridRenderer @@ -18,13 +21,21 @@ export class GridManager { private currentView: CalendarView = 'week'; private gridRenderer: GridRenderer; private dateService: DateService; + private config: Configuration; + private dataSource: DateColumnDataSource; + private eventManager: EventManager; constructor( gridRenderer: GridRenderer, - dateService: DateService + dateService: DateService, + config: Configuration, + eventManager: EventManager ) { this.gridRenderer = gridRenderer; this.dateService = dateService; + this.config = config; + this.eventManager = eventManager; + this.dataSource = new DateColumnDataSource(dateService, config, this.currentDate, this.currentView); this.init(); } @@ -33,22 +44,6 @@ export class GridManager { this.subscribeToEvents(); } - /** - * Get the start of the ISO week (Monday) for a given date - */ - private getISOWeekStart(date: Date): Date { - const weekBounds = this.dateService.getWeekBounds(date); - return this.dateService.startOfDay(weekBounds.start); - } - - /** - * Get the end of the ISO week (Sunday) for a given date - */ - private getWeekEnd(date: Date): Date { - const weekBounds = this.dateService.getWeekBounds(date); - return this.dateService.endOfDay(weekBounds.end); - } - private findElements(): void { this.container = document.querySelector('swp-calendar-container'); } @@ -58,14 +53,18 @@ export class GridManager { eventBus.on(CoreEvents.VIEW_CHANGED, (e: Event) => { const detail = (e as CustomEvent).detail; this.currentView = detail.currentView; + this.dataSource.setCurrentView(this.currentView); this.render(); }); - // Listen for navigation events from NavigationButtons + // Listen for navigation events from NavigationManager + // NavigationManager has already created new grid with animation + // GridManager only needs to update state, NOT re-render eventBus.on(CoreEvents.NAVIGATION_COMPLETED, (e: Event) => { const detail = (e as CustomEvent).detail; this.currentDate = detail.newDate; - this.render(); + this.dataSource.setCurrentDate(this.currentDate); + // Do NOT call render() - NavigationManager already created new grid }); // Listen for config changes that affect rendering @@ -88,109 +87,28 @@ export class GridManager { return; } - // Delegate to GridRenderer with current view context + // Get dates from datasource - single source of truth + const dates = this.dataSource.getColumns(); + + // Get events for the period from EventManager + const startDate = dates[0]; + const endDate = dates[dates.length - 1]; + const events = await this.eventManager.getEventsForPeriod(startDate, endDate); + + // Delegate to GridRenderer with dates and events this.gridRenderer.renderGrid( this.container, - this.currentDate + this.currentDate, + this.currentView, + dates, + events ); - // Get display dates for current view - const dates = this.getDisplayDates(); - - // 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, - dates: dates, - layoutConfig: layoutConfig, - columnCount: layoutConfig.columnCount + dates: dates }); } - - - - /** - * Get current view's display dates - */ - public getDisplayDates(): Date[] { - switch (this.currentView) { - case 'week': - const weekStart = this.getISOWeekStart(this.currentDate); - return this.dateService.getFullWeekDates(weekStart); - case 'month': - return this.getMonthDates(this.currentDate); - case 'day': - return [this.currentDate]; - default: - const defaultWeekStart = this.getISOWeekStart(this.currentDate); - return this.dateService.getFullWeekDates(defaultWeekStart); - } - } - - - /** - * Get layout config for current view - */ - private getLayoutConfig(): { columnCount: number; type: string } { - 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' - }; - } - } - - /** - * Helper method to get month start - */ - private getMonthStart(date: Date): Date { - const year = date.getFullYear(); - const month = date.getMonth(); - return this.dateService.startOfDay(new Date(year, month, 1)); - } - - /** - * Helper method to get month end - */ - private getMonthEnd(date: Date): Date { - const nextMonth = this.dateService.addMonths(date, 1); - const firstOfNextMonth = this.getMonthStart(nextMonth); - return this.dateService.endOfDay(this.dateService.addDays(firstOfNextMonth, -1)); - } - - /** - * 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); - - const totalDays = Math.ceil((monthEnd.getTime() - monthStart.getTime()) / (1000 * 60 * 60 * 24)) + 1; - - for (let i = 0; i < totalDays; i++) { - dates.push(this.dateService.addDays(monthStart, i)); - } - - return dates; - } } \ No newline at end of file diff --git a/src/managers/NavigationManager.ts b/src/managers/NavigationManager.ts index 5f9693f..a3d0405 100644 --- a/src/managers/NavigationManager.ts +++ b/src/managers/NavigationManager.ts @@ -1,16 +1,20 @@ -import { IEventBus } from '../types/CalendarTypes'; +import { IEventBus, CalendarView } from '../types/CalendarTypes'; import { EventRenderingService } from '../renderers/EventRendererManager'; import { DateService } from '../utils/DateService'; import { CoreEvents } from '../constants/CoreEvents'; import { WeekInfoRenderer } from '../renderers/WeekInfoRenderer'; import { GridRenderer } from '../renderers/GridRenderer'; import { INavButtonClickedEventPayload } from '../types/EventTypes'; +import { DateColumnDataSource } from '../datasources/DateColumnDataSource'; +import { Configuration } from '../configurations/CalendarConfig'; export class NavigationManager { private eventBus: IEventBus; private weekInfoRenderer: WeekInfoRenderer; private gridRenderer: GridRenderer; private dateService: DateService; + private config: Configuration; + private dataSource: DateColumnDataSource; private currentWeek: Date; private targetWeek: Date; private animationQueue: number = 0; @@ -20,14 +24,17 @@ export class NavigationManager { eventRenderer: EventRenderingService, gridRenderer: GridRenderer, dateService: DateService, - weekInfoRenderer: WeekInfoRenderer + weekInfoRenderer: WeekInfoRenderer, + config: Configuration ) { this.eventBus = eventBus; this.dateService = dateService; this.weekInfoRenderer = weekInfoRenderer; this.gridRenderer = gridRenderer; + this.config = config; this.currentWeek = this.getISOWeekStart(new Date()); this.targetWeek = new Date(this.currentWeek); + this.dataSource = new DateColumnDataSource(dateService, config, this.currentWeek, 'week' as CalendarView); this.init(); } @@ -184,8 +191,12 @@ export class NavigationManager { console.log('Calling GridRenderer instead of NavigationRenderer'); console.log('Target week:', targetWeek); + // Update DataSource with target week and get dates + this.dataSource.setCurrentDate(targetWeek); + const dates = this.dataSource.getColumns(); + // Always create a fresh container for consistent behavior - newGrid = this.gridRenderer.createNavigationGrid(container, targetWeek); + newGrid = this.gridRenderer.createNavigationGrid(container, dates); console.groupEnd(); diff --git a/src/renderers/ColumnRenderer.ts b/src/renderers/ColumnRenderer.ts index 62be950..e64506b 100644 --- a/src/renderers/ColumnRenderer.ts +++ b/src/renderers/ColumnRenderer.ts @@ -15,7 +15,7 @@ export interface IColumnRenderer { * Context for column rendering */ export interface IColumnRenderContext { - currentWeek: Date; + dates: Date[]; config: Configuration; } @@ -35,24 +35,18 @@ export class DateColumnRenderer implements IColumnRenderer { } render(columnContainer: HTMLElement, context: IColumnRenderContext): void { - const { currentWeek, config } = context; + const { dates } = context; - const workWeekSettings = config.getWorkWeekSettings(); - const dates = this.dateService.getWorkWeekDates(currentWeek, workWeekSettings.workDays); - const dateSettings = config.dateViewSettings; - const daysToShow = dates.slice(0, dateSettings.weekDays); - - - daysToShow.forEach((date) => { + dates.forEach((date) => { const column = document.createElement('swp-day-column'); (column as any).dataset.date = this.dateService.formatISODate(date); - + // Apply work hours styling this.applyWorkHoursToColumn(column, date); - + const eventsLayer = document.createElement('swp-events-layer'); column.appendChild(eventsLayer); - + columnContainer.appendChild(column); }); } diff --git a/src/renderers/GridRenderer.ts b/src/renderers/GridRenderer.ts index 72eb8ae..0574a31 100644 --- a/src/renderers/GridRenderer.ts +++ b/src/renderers/GridRenderer.ts @@ -1,5 +1,5 @@ import { Configuration } from '../configurations/CalendarConfig'; -import { CalendarView } from '../types/CalendarTypes'; +import { CalendarView, ICalendarEvent } from '../types/CalendarTypes'; import { IColumnRenderer, IColumnRenderContext } from './ColumnRenderer'; import { eventBus } from '../core/EventBus'; import { DateService } from '../utils/DateService'; @@ -104,11 +104,15 @@ export class GridRenderer { * @param grid - Container element where grid will be rendered * @param currentDate - Base date for the current view (e.g., any date in the week) * @param view - Calendar view type (day/week/month) + * @param dates - Array of dates to render as columns + * @param events - All events for the period */ public renderGrid( grid: HTMLElement, currentDate: Date, - view: CalendarView = 'week' + view: CalendarView = 'week', + dates: Date[] = [], + events: ICalendarEvent[] = [] ): void { if (!grid || !currentDate) { @@ -120,10 +124,10 @@ export class GridRenderer { // Only clear and rebuild if grid is empty (first render) if (grid.children.length === 0) { - this.createCompleteGridStructure(grid, currentDate, view); + this.createCompleteGridStructure(grid, currentDate, view, dates, events); } else { // Optimized update - only refresh dynamic content - this.updateGridContent(grid, currentDate, view); + this.updateGridContent(grid, currentDate, view, dates, events); } } @@ -141,11 +145,14 @@ export class GridRenderer { * @param grid - Parent container * @param currentDate - Current view date * @param view - View type + * @param dates - Array of dates to render */ private createCompleteGridStructure( grid: HTMLElement, currentDate: Date, - view: CalendarView + view: CalendarView, + dates: Date[], + events: ICalendarEvent[] ): void { // Create all elements in memory first for better performance const fragment = document.createDocumentFragment(); @@ -160,7 +167,7 @@ export class GridRenderer { fragment.appendChild(timeAxis); // Create grid container with caching - const gridContainer = this.createOptimizedGridContainer(currentDate, view); + const gridContainer = this.createOptimizedGridContainer(dates, events); this.cachedGridContainer = gridContainer; fragment.appendChild(gridContainer); @@ -207,11 +214,12 @@ export class GridRenderer { * * @param currentDate - Current view date * @param view - View type + * @param dates - Array of dates to render * @returns Complete grid container element */ private createOptimizedGridContainer( - currentDate: Date, - view: CalendarView + dates: Date[], + events: ICalendarEvent[] ): HTMLElement { const gridContainer = document.createElement('swp-grid-container'); @@ -229,7 +237,7 @@ export class GridRenderer { // Create column container const columnContainer = document.createElement('swp-day-columns'); - this.renderColumnContainer(columnContainer, currentDate, view); + this.renderColumnContainer(columnContainer, dates, events); timeGrid.appendChild(columnContainer); scrollableContent.appendChild(timeGrid); @@ -240,27 +248,25 @@ export class GridRenderer { /** - * Delegates column rendering to the injected ColumnRenderer strategy + * Renders columns by delegating to ColumnRenderer * - * This is where the Strategy Pattern is applied: - * - DateColumnRenderer iterates dates and creates day columns - * - Could be swapped with other implementations (e.g., ResourceColumnRenderer) + * GridRenderer delegates column creation to ColumnRenderer. + * Event rendering is handled by EventRenderingService listening to GRID_RENDERED. * * @param columnContainer - Empty container to populate - * @param currentDate - Current view date - * @param view - View type + * @param dates - Array of dates to render + * @param events - All events for the period (passed through, not used here) */ private renderColumnContainer( columnContainer: HTMLElement, - currentDate: Date, - view: CalendarView + dates: Date[], + events: ICalendarEvent[] ): void { - const context: IColumnRenderContext = { - currentWeek: currentDate, // ColumnRenderer expects currentWeek property + // Delegate to ColumnRenderer + this.columnRenderer.render(columnContainer, { + dates: dates, config: this.config - }; - - this.columnRenderer.render(columnContainer, context); + }); } /** @@ -272,17 +278,21 @@ export class GridRenderer { * @param grid - Existing grid element * @param currentDate - New view date * @param view - View type + * @param dates - Array of dates to render + * @param events - All events for the period */ private updateGridContent( grid: HTMLElement, currentDate: Date, - view: CalendarView + view: CalendarView, + dates: Date[], + events: ICalendarEvent[] ): void { // Update column container if needed const columnContainer = grid.querySelector('swp-day-columns'); if (columnContainer) { columnContainer.innerHTML = ''; - this.renderColumnContainer(columnContainer as HTMLElement, currentDate, view); + this.renderColumnContainer(columnContainer as HTMLElement, dates, events); } } /** @@ -292,14 +302,15 @@ export class GridRenderer { * Creates a complete grid positioned absolutely for animation. * * Note: Positioning is handled by Animation API, not here. + * Events will be rendered by EventRenderingService when GRID_RENDERED emits. * * @param parentContainer - Container for the new grid - * @param weekStart - Start date of the new week + * @param dates - Array of dates to render * @returns New grid element ready for animation */ - public createNavigationGrid(parentContainer: HTMLElement, weekStart: Date): HTMLElement { - // Use SAME method as initial load - respects workweek settings - const newGrid = this.createOptimizedGridContainer(weekStart, 'week'); + public createNavigationGrid(parentContainer: HTMLElement, dates: Date[]): HTMLElement { + // Create grid structure without events (events rendered by EventRenderingService) + const newGrid = this.createOptimizedGridContainer(dates, []); // Position new grid for animation - NO transform here, let Animation API handle it newGrid.style.position = 'absolute'; diff --git a/src/types/ColumnDataSource.ts b/src/types/ColumnDataSource.ts new file mode 100644 index 0000000..442cc59 --- /dev/null +++ b/src/types/ColumnDataSource.ts @@ -0,0 +1,18 @@ +/** + * IColumnDataSource - Defines the contract for providing column data + * + * This interface abstracts away whether columns represent dates or resources, + * allowing the calendar to switch between date-based and resource-based views. + */ +export interface IColumnDataSource { + /** + * Get the list of column identifiers to render + * @returns Array of identifiers (dates or resource IDs) + */ + getColumns(): Date[]; + + /** + * Get the type of columns this datasource provides + */ + getType(): 'date' | 'resource'; +}