import { EventBus } from '../core/EventBus'; import { IEventBus, CalendarEvent, ResourceCalendarData } from '../types/CalendarTypes'; import { CoreEvents } from '../constants/CoreEvents'; import { calendarConfig } from '../core/CalendarConfig'; import { DateCalculator } from '../utils/DateCalculator'; /** * EventManager - Optimized event lifecycle and CRUD operations * Handles data loading with improved performance and caching */ export class EventManager { private eventBus: IEventBus; private events: CalendarEvent[] = []; private rawData: any = null; private eventCache = new Map(); // Cache for period queries private lastCacheKey: string = ''; constructor(eventBus: IEventBus) { this.eventBus = eventBus; } /** * Optimized data loading with better error handling */ public async loadData(): Promise { try { await this.loadMockData(); this.clearCache(); // Clear cache when new data is loaded } catch (error) { console.error('Failed to load event data:', error); this.events = []; this.rawData = null; } } /** * Optimized mock data loading with better resource handling */ private async loadMockData(): Promise { const calendarType = calendarConfig.getCalendarMode(); const jsonFile = calendarType === 'resource' ? '/src/data/mock-resource-events.json' : '/src/data/mock-events.json'; const response = await fetch(jsonFile); if (!response.ok) { throw new Error(`Failed to load mock events: ${response.status} ${response.statusText}`); } const data = await response.json(); // Store raw data and process in one operation this.rawData = data; this.events = this.processCalendarData(calendarType, data); } /** * Optimized data processing with better type safety */ private processCalendarData(calendarType: string, data: any): CalendarEvent[] { if (calendarType === 'resource') { const resourceData = data as ResourceCalendarData; return resourceData.resources.flatMap(resource => resource.events.map(event => ({ ...event, resourceName: resource.name, resourceDisplayName: resource.displayName, resourceEmployeeId: resource.employeeId })) ); } return data as CalendarEvent[]; } /** * Clear event cache when data changes */ private clearCache(): void { this.eventCache.clear(); this.lastCacheKey = ''; } /** * Get events with optional copying for performance */ public getEvents(copy: boolean = false): CalendarEvent[] { return copy ? [...this.events] : this.events; } /** * Get raw resource data for resource calendar mode */ public getResourceData(): any { return this.rawData; } /** * Optimized event lookup with early return */ public getEventById(id: string): CalendarEvent | undefined { // Use find for better performance than filter + first return this.events.find(event => event.id === id); } /** * Get event by ID and return event info for navigation * @param id Event ID to find * @returns Event with navigation info or null if not found */ public getEventForNavigation(id: string): { event: CalendarEvent; eventDate: Date } | null { const event = this.getEventById(id); if (!event) { return null; } try { const eventDate = new Date(event.start); if (isNaN(eventDate.getTime())) { console.warn(`EventManager: Invalid event start date for event ${id}:`, event.start); return null; } return { event, eventDate }; } catch (error) { console.warn(`EventManager: Failed to parse event date for event ${id}:`, error); return null; } } /** * Navigate to specific event by ID * Emits navigation events for other managers to handle * @param eventId Event ID to navigate to * @returns true if event found and navigation initiated, false otherwise */ public navigateToEvent(eventId: string): boolean { const eventInfo = this.getEventForNavigation(eventId); if (!eventInfo) { console.warn(`EventManager: Event with ID ${eventId} not found`); return false; } const { event, eventDate } = eventInfo; // Emit navigation request event this.eventBus.emit(CoreEvents.NAVIGATE_TO_EVENT, { eventId, event, eventDate, eventStartTime: event.start }); return true; } /** * Optimized events for period with caching and DateCalculator */ public getEventsForPeriod(startDate: Date, endDate: Date): CalendarEvent[] { // Create cache key using DateCalculator for consistent formatting const cacheKey = `${DateCalculator.formatISODate(startDate)}_${DateCalculator.formatISODate(endDate)}`; // Return cached result if available if (this.lastCacheKey === cacheKey && this.eventCache.has(cacheKey)) { return this.eventCache.get(cacheKey)!; } // Filter events using optimized date operations const filteredEvents = this.events.filter(event => { // Use DateCalculator for consistent date parsing const eventStart = new Date(event.start); const eventEnd = new Date(event.end); // Event overlaps period if it starts before period ends AND ends after period starts return eventStart <= endDate && eventEnd >= startDate; }); // Cache the result this.eventCache.set(cacheKey, filteredEvents); this.lastCacheKey = cacheKey; return filteredEvents; } /** * Optimized event creation with better ID generation */ public addEvent(event: Omit): CalendarEvent { const newEvent: CalendarEvent = { ...event, id: `event_${Date.now()}_${Math.random().toString(36).substr(2, 9)}` }; this.events.push(newEvent); this.clearCache(); // Clear cache when data changes this.eventBus.emit(CoreEvents.EVENT_CREATED, { event: newEvent }); return newEvent; } /** * Optimized event update with validation */ public updateEvent(id: string, updates: Partial): CalendarEvent | null { const eventIndex = this.events.findIndex(event => event.id === id); if (eventIndex === -1) return null; const updatedEvent = { ...this.events[eventIndex], ...updates }; this.events[eventIndex] = updatedEvent; this.clearCache(); // Clear cache when data changes this.eventBus.emit(CoreEvents.EVENT_UPDATED, { event: updatedEvent }); return updatedEvent; } /** * Optimized event deletion with better error handling */ public deleteEvent(id: string): boolean { const eventIndex = this.events.findIndex(event => event.id === id); if (eventIndex === -1) return false; const deletedEvent = this.events[eventIndex]; this.events.splice(eventIndex, 1); this.clearCache(); // Clear cache when data changes this.eventBus.emit(CoreEvents.EVENT_DELETED, { event: deletedEvent }); return true; } /** * Refresh data by reloading from source */ public async refresh(): Promise { await this.loadData(); } /** * Clean up resources and clear caches */ public destroy(): void { this.events = []; this.rawData = null; this.clearCache(); } }