From 29ba0bfa37b4ee26addcfbbfb29f65bb408c074a Mon Sep 17 00:00:00 2001 From: "Janus C. H. Knudsen" Date: Fri, 7 Nov 2025 23:07:00 +0100 Subject: [PATCH] Refactors view management in calendar component Introduces ViewSelectorManager to handle view state and UI interactions Separates view logic from configuration management Adds explicit tracking of current calendar view Enhances view selection and state management Improves modularity and separation of concerns --- src/configurations/CalendarConfig.ts | 4 + src/configurations/ConfigManager.ts | 3 +- src/index.ts | 6 +- src/managers/ViewManager.ts | 119 --------------------- src/managers/ViewSelectorManager.ts | 152 +++++++++++++++++++++++++++ wwwroot/data/calendar-config.json | 1 + 6 files changed, 162 insertions(+), 123 deletions(-) delete mode 100644 src/managers/ViewManager.ts create mode 100644 src/managers/ViewSelectorManager.ts diff --git a/src/configurations/CalendarConfig.ts b/src/configurations/CalendarConfig.ts index 6be9421..4340128 100644 --- a/src/configurations/CalendarConfig.ts +++ b/src/configurations/CalendarConfig.ts @@ -3,6 +3,7 @@ import { IGridSettings } from './GridSettings'; import { IDateViewSettings } from './DateViewSettings'; import { ITimeFormatConfig } from './TimeFormatConfig'; import { IWorkWeekSettings } from './WorkWeekSettings'; +import { CalendarView } from '../types/CalendarTypes'; /** * All-day event layout constants @@ -65,6 +66,7 @@ export class Configuration { public dateViewSettings: IDateViewSettings; public timeFormatConfig: ITimeFormatConfig; public currentWorkWeek: string; + public currentView: CalendarView; public selectedDate: Date; public apiEndpoint: string = '/api'; @@ -74,6 +76,7 @@ export class Configuration { dateViewSettings: IDateViewSettings, timeFormatConfig: ITimeFormatConfig, currentWorkWeek: string, + currentView: CalendarView, selectedDate: Date = new Date() ) { this.config = config; @@ -81,6 +84,7 @@ export class Configuration { this.dateViewSettings = dateViewSettings; this.timeFormatConfig = timeFormatConfig; this.currentWorkWeek = currentWorkWeek; + this.currentView = currentView; this.selectedDate = selectedDate; // Store as singleton instance for web components diff --git a/src/configurations/ConfigManager.ts b/src/configurations/ConfigManager.ts index f568e7a..c4532af 100644 --- a/src/configurations/ConfigManager.ts +++ b/src/configurations/ConfigManager.ts @@ -92,7 +92,8 @@ export class ConfigManager { data.gridSettings, data.dateViewSettings, data.timeFormatConfig, - data.currentWorkWeek + data.currentWorkWeek, + data.currentView || 'week' ); // Configure TimeFormatter diff --git a/src/index.ts b/src/index.ts index a8ad50a..064ec33 100644 --- a/src/index.ts +++ b/src/index.ts @@ -12,7 +12,7 @@ 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 { ViewSelectorManager } from './managers/ViewSelectorManager'; import { CalendarManager } from './managers/CalendarManager'; import { DragDropManager } from './managers/DragDropManager'; import { AllDayManager } from './managers/AllDayManager'; @@ -124,7 +124,7 @@ async function initializeCalendar(): Promise { builder.registerType(GridManager).as(); builder.registerType(ScrollManager).as(); builder.registerType(NavigationManager).as(); - builder.registerType(ViewManager).as(); + builder.registerType(ViewSelectorManager).as(); builder.registerType(DragDropManager).as(); builder.registerType(AllDayManager).as(); builder.registerType(ResizeHandleManager).as(); @@ -146,7 +146,7 @@ async function initializeCalendar(): Promise { const resizeHandleManager = app.resolveType(); const headerManager = app.resolveType(); const dragDropManager = app.resolveType(); - const viewManager = app.resolveType(); + const viewSelectorManager = app.resolveType(); const navigationManager = app.resolveType(); const edgeScrollManager = app.resolveType(); const allDayManager = app.resolveType(); diff --git a/src/managers/ViewManager.ts b/src/managers/ViewManager.ts deleted file mode 100644 index ffead14..0000000 --- a/src/managers/ViewManager.ts +++ /dev/null @@ -1,119 +0,0 @@ -import { CalendarView, IEventBus } from '../types/CalendarTypes'; -import { Configuration } from '../configurations/CalendarConfig'; -import { CoreEvents } from '../constants/CoreEvents'; - - -export class ViewManager { - private eventBus: IEventBus; - private config: Configuration; - private currentView: CalendarView = 'week'; - private buttonListeners: Map = new Map(); - - constructor(eventBus: IEventBus, config: Configuration) { - this.eventBus = eventBus; - this.config = config; - this.setupEventListeners(); - } - - private setupEventListeners(): void { - this.setupEventBusListeners(); - this.setupButtonHandlers(); - } - - - private setupEventBusListeners(): void { - this.eventBus.on(CoreEvents.INITIALIZED, () => { - this.initializeView(); - }); - - this.eventBus.on(CoreEvents.DATE_CHANGED, () => { - this.refreshCurrentView(); - }); - } - - private setupButtonHandlers(): void { - this.setupButtonGroup('swp-view-button[data-view]', 'data-view', (value) => { - if (this.isValidView(value)) { - this.changeView(value as CalendarView); - } - }); - - // NOTE: Workweek preset buttons are now handled by WorkweekPresetsManager - } - - - private setupButtonGroup(selector: string, attribute: string, handler: (value: string) => void): void { - const buttons = document.querySelectorAll(selector); - buttons.forEach(button => { - const clickHandler = (event: Event) => { - event.preventDefault(); - const value = button.getAttribute(attribute); - if (value) { - handler(value); - } - }; - button.addEventListener('click', clickHandler); - this.buttonListeners.set(button, clickHandler); - }); - } - - private getViewButtons(): NodeListOf { - return document.querySelectorAll('swp-view-button[data-view]'); - } - - - private initializeView(): void { - this.updateAllButtons(); - this.emitViewRendered(); - } - - private changeView(newView: CalendarView): void { - if (newView === this.currentView) return; - - const previousView = this.currentView; - this.currentView = newView; - - this.updateAllButtons(); - - this.eventBus.emit(CoreEvents.VIEW_CHANGED, { - previousView, - currentView: newView - }); - } - private updateAllButtons(): void { - this.updateButtonGroup( - this.getViewButtons(), - 'data-view', - this.currentView - ); - - // NOTE: Workweek button states are now managed by WorkweekPresetsManager - } - - private updateButtonGroup(buttons: NodeListOf, attribute: string, activeValue: string): void { - buttons.forEach(button => { - const buttonValue = button.getAttribute(attribute); - if (buttonValue === activeValue) { - button.setAttribute('data-active', 'true'); - } else { - button.removeAttribute('data-active'); - } - }); - } - - private emitViewRendered(): void { - this.eventBus.emit(CoreEvents.VIEW_RENDERED, { - view: this.currentView - }); - } - - private refreshCurrentView(): void { - this.emitViewRendered(); - } - - private isValidView(view: string): view is CalendarView { - return ['day', 'week', 'month'].includes(view); - } - - -} diff --git a/src/managers/ViewSelectorManager.ts b/src/managers/ViewSelectorManager.ts new file mode 100644 index 0000000..77b2340 --- /dev/null +++ b/src/managers/ViewSelectorManager.ts @@ -0,0 +1,152 @@ +import { CalendarView, IEventBus } from '../types/CalendarTypes'; +import { CoreEvents } from '../constants/CoreEvents'; +import { Configuration } from '../configurations/CalendarConfig'; + +/** + * ViewSelectorManager - Manages view selector UI and state + * + * RESPONSIBILITY: + * =============== + * This manager owns all logic related to the UI element. + * It follows the principle that each functional UI element has its own manager. + * + * RESPONSIBILITIES: + * - Handles button clicks on swp-view-button elements + * - Manages current view state (day/week/month) + * - Validates view values + * - Emits VIEW_CHANGED and VIEW_RENDERED events + * - Updates button UI states (data-active attributes) + * + * EVENT FLOW: + * =========== + * User clicks button → changeView() → validate → update state → emit event → update UI + * + * IMPLEMENTATION STATUS: + * ====================== + * - Week view: FULLY IMPLEMENTED + * - Day view: NOT IMPLEMENTED (button exists but no rendering) + * - Month view: NOT IMPLEMENTED (button exists but no rendering) + * + * SUBSCRIBERS: + * ============ + * - GridRenderer: Uses view parameter (currently only supports 'week') + * - Future: DayRenderer, MonthRenderer when implemented + */ +export class ViewSelectorManager { + private eventBus: IEventBus; + private config: Configuration; + private buttonListeners: Map = new Map(); + + constructor(eventBus: IEventBus, config: Configuration) { + this.eventBus = eventBus; + this.config = config; + + this.setupButtonListeners(); + this.setupEventListeners(); + } + + /** + * Setup click listeners on all view selector buttons + */ + private setupButtonListeners(): void { + const buttons = document.querySelectorAll('swp-view-button[data-view]'); + + buttons.forEach(button => { + const clickHandler = (event: Event) => { + event.preventDefault(); + const view = button.getAttribute('data-view'); + if (view && this.isValidView(view)) { + this.changeView(view as CalendarView); + } + }; + + button.addEventListener('click', clickHandler); + this.buttonListeners.set(button, clickHandler); + }); + + // Initialize button states + this.updateButtonStates(); + } + + /** + * Setup event bus listeners + */ + private setupEventListeners(): void { + this.eventBus.on(CoreEvents.INITIALIZED, () => { + this.initializeView(); + }); + + this.eventBus.on(CoreEvents.DATE_CHANGED, () => { + this.refreshCurrentView(); + }); + } + + /** + * Change the active view + */ + private changeView(newView: CalendarView): void { + if (newView === this.config.currentView) { + return; // No change + } + + const previousView = this.config.currentView; + this.config.currentView = newView; + + // Update button UI states + this.updateButtonStates(); + + // Emit event for subscribers + this.eventBus.emit(CoreEvents.VIEW_CHANGED, { + previousView, + currentView: newView + }); + } + + /** + * Update button states (data-active attributes) + */ + private updateButtonStates(): void { + const buttons = document.querySelectorAll('swp-view-button[data-view]'); + + buttons.forEach(button => { + const buttonView = button.getAttribute('data-view'); + + if (buttonView === this.config.currentView) { + button.setAttribute('data-active', 'true'); + } else { + button.removeAttribute('data-active'); + } + }); + } + + /** + * Initialize view on INITIALIZED event + */ + private initializeView(): void { + this.updateButtonStates(); + this.emitViewRendered(); + } + + /** + * Emit VIEW_RENDERED event + */ + private emitViewRendered(): void { + this.eventBus.emit(CoreEvents.VIEW_RENDERED, { + view: this.config.currentView + }); + } + + /** + * Refresh current view on DATE_CHANGED event + */ + private refreshCurrentView(): void { + this.emitViewRendered(); + } + + /** + * Validate if string is a valid CalendarView type + */ + private isValidView(view: string): view is CalendarView { + return ['day', 'week', 'month'].includes(view); + } +} diff --git a/wwwroot/data/calendar-config.json b/wwwroot/data/calendar-config.json index e4bd5a1..ec3fdd8 100644 --- a/wwwroot/data/calendar-config.json +++ b/wwwroot/data/calendar-config.json @@ -58,6 +58,7 @@ } }, "currentWorkWeek": "standard", + "currentView": "week", "scrollbar": { "width": 16, "color": "#666",