2025-07-24 22:17:38 +02:00
|
|
|
import { EventBus } from '../core/EventBus';
|
2025-08-07 00:15:44 +02:00
|
|
|
import { IEventBus, CalendarEvent, ResourceCalendarData } from '../types/CalendarTypes';
|
2025-08-20 20:22:51 +02:00
|
|
|
import { CoreEvents } from '../constants/CoreEvents';
|
2025-08-07 00:15:44 +02:00
|
|
|
import { calendarConfig } from '../core/CalendarConfig';
|
2025-09-03 18:51:19 +02:00
|
|
|
import { DateCalculator } from '../utils/DateCalculator';
|
2025-07-24 22:17:38 +02:00
|
|
|
|
|
|
|
|
/**
|
2025-09-03 18:51:19 +02:00
|
|
|
* EventManager - Optimized event lifecycle and CRUD operations
|
|
|
|
|
* Handles data loading with improved performance and caching
|
2025-07-24 22:17:38 +02:00
|
|
|
*/
|
|
|
|
|
export class EventManager {
|
|
|
|
|
private eventBus: IEventBus;
|
|
|
|
|
private events: CalendarEvent[] = [];
|
2025-09-03 18:51:19 +02:00
|
|
|
private rawData: any = null;
|
|
|
|
|
private eventCache = new Map<string, CalendarEvent[]>(); // Cache for period queries
|
|
|
|
|
private lastCacheKey: string = '';
|
2025-07-24 22:17:38 +02:00
|
|
|
|
|
|
|
|
constructor(eventBus: IEventBus) {
|
|
|
|
|
this.eventBus = eventBus;
|
2025-08-09 01:16:04 +02:00
|
|
|
}
|
2025-08-09 00:31:44 +02:00
|
|
|
|
2025-08-09 01:16:04 +02:00
|
|
|
/**
|
2025-09-03 18:51:19 +02:00
|
|
|
* Optimized data loading with better error handling
|
2025-08-09 01:16:04 +02:00
|
|
|
*/
|
|
|
|
|
public async loadData(): Promise<void> {
|
2025-09-03 18:51:19 +02:00
|
|
|
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;
|
2025-08-09 01:16:04 +02:00
|
|
|
}
|
2025-07-24 22:17:38 +02:00
|
|
|
}
|
|
|
|
|
|
2025-09-03 18:51:19 +02:00
|
|
|
/**
|
|
|
|
|
* Optimized mock data loading with better resource handling
|
|
|
|
|
*/
|
2025-08-07 00:15:44 +02:00
|
|
|
private async loadMockData(): Promise<void> {
|
2025-09-03 18:51:19 +02:00
|
|
|
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}`);
|
2025-08-07 00:15:44 +02:00
|
|
|
}
|
2025-09-03 18:51:19 +02:00
|
|
|
|
|
|
|
|
const data = await response.json();
|
|
|
|
|
|
|
|
|
|
// Store raw data and process in one operation
|
|
|
|
|
this.rawData = data;
|
|
|
|
|
this.events = this.processCalendarData(calendarType, data);
|
2025-07-24 22:17:38 +02:00
|
|
|
}
|
|
|
|
|
|
2025-09-03 18:51:19 +02:00
|
|
|
/**
|
|
|
|
|
* Optimized data processing with better type safety
|
|
|
|
|
*/
|
|
|
|
|
private processCalendarData(calendarType: string, data: any): CalendarEvent[] {
|
2025-08-09 00:31:44 +02:00
|
|
|
if (calendarType === 'resource') {
|
|
|
|
|
const resourceData = data as ResourceCalendarData;
|
2025-09-03 18:51:19 +02:00
|
|
|
return resourceData.resources.flatMap(resource =>
|
2025-08-09 00:31:44 +02:00
|
|
|
resource.events.map(event => ({
|
|
|
|
|
...event,
|
|
|
|
|
resourceName: resource.name,
|
|
|
|
|
resourceDisplayName: resource.displayName,
|
|
|
|
|
resourceEmployeeId: resource.employeeId
|
|
|
|
|
}))
|
|
|
|
|
);
|
|
|
|
|
}
|
2025-09-03 18:51:19 +02:00
|
|
|
|
|
|
|
|
return data as CalendarEvent[];
|
2025-08-09 00:31:44 +02:00
|
|
|
}
|
2025-07-24 22:17:38 +02:00
|
|
|
|
2025-09-03 18:51:19 +02:00
|
|
|
/**
|
|
|
|
|
* Clear event cache when data changes
|
|
|
|
|
*/
|
|
|
|
|
private clearCache(): void {
|
|
|
|
|
this.eventCache.clear();
|
|
|
|
|
this.lastCacheKey = '';
|
2025-07-24 22:17:38 +02:00
|
|
|
}
|
|
|
|
|
|
2025-09-03 18:51:19 +02:00
|
|
|
/**
|
|
|
|
|
* Get events with optional copying for performance
|
|
|
|
|
*/
|
|
|
|
|
public getEvents(copy: boolean = false): CalendarEvent[] {
|
|
|
|
|
return copy ? [...this.events] : this.events;
|
2025-07-24 22:17:38 +02:00
|
|
|
}
|
|
|
|
|
|
2025-08-10 23:06:03 +02:00
|
|
|
/**
|
|
|
|
|
* Get raw resource data for resource calendar mode
|
|
|
|
|
*/
|
|
|
|
|
public getResourceData(): any {
|
|
|
|
|
return this.rawData;
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-03 18:51:19 +02:00
|
|
|
/**
|
|
|
|
|
* Optimized event lookup with early return
|
|
|
|
|
*/
|
2025-07-24 22:17:38 +02:00
|
|
|
public getEventById(id: string): CalendarEvent | undefined {
|
2025-09-03 18:51:19 +02:00
|
|
|
// Use find for better performance than filter + first
|
2025-07-24 22:17:38 +02:00
|
|
|
return this.events.find(event => event.id === id);
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-04 00:16:35 +02:00
|
|
|
/**
|
|
|
|
|
* 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;
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-16 00:51:12 +02:00
|
|
|
/**
|
2025-09-03 18:51:19 +02:00
|
|
|
* Optimized events for period with caching and DateCalculator
|
2025-08-16 00:51:12 +02:00
|
|
|
*/
|
|
|
|
|
public getEventsForPeriod(startDate: Date, endDate: Date): CalendarEvent[] {
|
2025-09-03 18:51:19 +02:00
|
|
|
// 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
|
2025-08-16 00:51:12 +02:00
|
|
|
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;
|
|
|
|
|
});
|
2025-09-03 18:51:19 +02:00
|
|
|
|
|
|
|
|
// Cache the result
|
|
|
|
|
this.eventCache.set(cacheKey, filteredEvents);
|
|
|
|
|
this.lastCacheKey = cacheKey;
|
|
|
|
|
|
|
|
|
|
return filteredEvents;
|
2025-08-16 00:51:12 +02:00
|
|
|
}
|
|
|
|
|
|
2025-09-03 18:51:19 +02:00
|
|
|
/**
|
|
|
|
|
* Optimized event creation with better ID generation
|
|
|
|
|
*/
|
2025-07-24 22:17:38 +02:00
|
|
|
public addEvent(event: Omit<CalendarEvent, 'id'>): CalendarEvent {
|
|
|
|
|
const newEvent: CalendarEvent = {
|
|
|
|
|
...event,
|
2025-09-03 18:51:19 +02:00
|
|
|
id: `event_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`
|
2025-07-24 22:17:38 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
this.events.push(newEvent);
|
2025-09-03 18:51:19 +02:00
|
|
|
this.clearCache(); // Clear cache when data changes
|
2025-07-24 22:17:38 +02:00
|
|
|
|
2025-08-20 20:22:51 +02:00
|
|
|
this.eventBus.emit(CoreEvents.EVENT_CREATED, {
|
2025-07-24 22:17:38 +02:00
|
|
|
event: newEvent
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return newEvent;
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-03 18:51:19 +02:00
|
|
|
/**
|
|
|
|
|
* Optimized event update with validation
|
|
|
|
|
*/
|
2025-07-24 22:17:38 +02:00
|
|
|
public updateEvent(id: string, updates: Partial<CalendarEvent>): 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;
|
|
|
|
|
|
2025-09-03 18:51:19 +02:00
|
|
|
this.clearCache(); // Clear cache when data changes
|
2025-07-24 22:17:38 +02:00
|
|
|
|
2025-08-20 20:22:51 +02:00
|
|
|
this.eventBus.emit(CoreEvents.EVENT_UPDATED, {
|
2025-07-24 22:17:38 +02:00
|
|
|
event: updatedEvent
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return updatedEvent;
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-03 18:51:19 +02:00
|
|
|
/**
|
|
|
|
|
* Optimized event deletion with better error handling
|
|
|
|
|
*/
|
2025-07-24 22:17:38 +02:00
|
|
|
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);
|
|
|
|
|
|
2025-09-03 18:51:19 +02:00
|
|
|
this.clearCache(); // Clear cache when data changes
|
2025-07-24 22:17:38 +02:00
|
|
|
|
2025-08-20 20:22:51 +02:00
|
|
|
this.eventBus.emit(CoreEvents.EVENT_DELETED, {
|
2025-07-24 22:17:38 +02:00
|
|
|
event: deletedEvent
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-03 18:51:19 +02:00
|
|
|
/**
|
|
|
|
|
* Refresh data by reloading from source
|
|
|
|
|
*/
|
|
|
|
|
public async refresh(): Promise<void> {
|
|
|
|
|
await this.loadData();
|
2025-07-24 22:17:38 +02:00
|
|
|
}
|
|
|
|
|
|
2025-09-03 18:51:19 +02:00
|
|
|
/**
|
|
|
|
|
* Clean up resources and clear caches
|
|
|
|
|
*/
|
2025-07-24 22:17:38 +02:00
|
|
|
public destroy(): void {
|
|
|
|
|
this.events = [];
|
2025-09-03 18:51:19 +02:00
|
|
|
this.rawData = null;
|
|
|
|
|
this.clearCache();
|
2025-07-24 22:17:38 +02:00
|
|
|
}
|
|
|
|
|
}
|