Converts CalendarConfig to a pure static configuration management class with improved initialization and dependency handling Removes event bus dependencies and simplifies configuration loading Adds static methods for config management and initialization Improves flexibility and reduces class complexity
295 lines
No EOL
9.2 KiB
TypeScript
295 lines
No EOL
9.2 KiB
TypeScript
import { IEventBus, CalendarEvent, ResourceCalendarData } from '../types/CalendarTypes';
|
|
import { CoreEvents } from '../constants/CoreEvents';
|
|
import { CalendarConfig } from '../core/CalendarConfig';
|
|
import { DateService } from '../utils/DateService';
|
|
import { ResourceData } from '../types/ManagerTypes';
|
|
|
|
interface RawEventData {
|
|
id: string;
|
|
title: string;
|
|
start: string | Date;
|
|
end: string | Date;
|
|
type : string;
|
|
color?: string;
|
|
allDay?: boolean;
|
|
[key: string]: unknown;
|
|
}
|
|
|
|
/**
|
|
* EventManager - Optimized event lifecycle and CRUD operations
|
|
* Handles data loading with improved performance and caching
|
|
*/
|
|
export class EventManager {
|
|
|
|
private events: CalendarEvent[] = [];
|
|
private rawData: ResourceCalendarData | RawEventData[] | null = null;
|
|
private eventCache = new Map<string, CalendarEvent[]>(); // Cache for period queries
|
|
private lastCacheKey: string = '';
|
|
private dateService: DateService;
|
|
private config: CalendarConfig;
|
|
|
|
constructor(
|
|
private eventBus: IEventBus,
|
|
dateService: DateService,
|
|
config: CalendarConfig
|
|
) {
|
|
this.dateService = dateService;
|
|
this.config = config;
|
|
}
|
|
|
|
/**
|
|
* Optimized data loading with better error handling
|
|
*/
|
|
public async loadData(): Promise<void> {
|
|
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<void> {
|
|
const calendarType = this.config.getCalendarMode();
|
|
const jsonFile = calendarType === 'resource'
|
|
? '/data/mock-resource-events.json'
|
|
: '/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: ResourceCalendarData | RawEventData[]): CalendarEvent[] {
|
|
if (calendarType === 'resource') {
|
|
const resourceData = data as ResourceCalendarData;
|
|
return resourceData.resources.flatMap(resource =>
|
|
resource.events.map(event => ({
|
|
...event,
|
|
start: new Date(event.start),
|
|
end: new Date(event.end),
|
|
resourceName: resource.name,
|
|
resourceDisplayName: resource.displayName,
|
|
resourceEmployeeId: resource.employeeId
|
|
}))
|
|
);
|
|
}
|
|
|
|
const eventData = data as RawEventData[];
|
|
return eventData.map((event): CalendarEvent => ({
|
|
...event,
|
|
start: new Date(event.start),
|
|
end: new Date(event.end),
|
|
type : event.type,
|
|
allDay: event.allDay || false,
|
|
syncStatus: 'synced' as const
|
|
}));
|
|
}
|
|
|
|
/**
|
|
* 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(): ResourceData | null {
|
|
if (!this.rawData || !('resources' in this.rawData)) {
|
|
return null;
|
|
}
|
|
return {
|
|
resources: this.rawData.resources.map(r => ({
|
|
id: r.employeeId || r.name, // Use employeeId as id, fallback to name
|
|
name: r.name,
|
|
type: r.employeeId ? 'employee' : 'resource',
|
|
color: 'blue' // Default color since Resource interface doesn't have color
|
|
}))
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Get raw data for compatibility
|
|
*/
|
|
public getRawData(): ResourceCalendarData | RawEventData[] | null {
|
|
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;
|
|
}
|
|
|
|
// Validate event dates
|
|
const validation = this.dateService.validateDate(event.start);
|
|
if (!validation.valid) {
|
|
console.warn(`EventManager: Invalid event start date for event ${id}:`, validation.error);
|
|
return null;
|
|
}
|
|
|
|
// Validate date range
|
|
if (!this.dateService.isValidRange(event.start, event.end)) {
|
|
console.warn(`EventManager: Invalid date range for event ${id}: start must be before end`);
|
|
return null;
|
|
}
|
|
|
|
return {
|
|
event,
|
|
eventDate: event.start
|
|
};
|
|
}
|
|
|
|
/**
|
|
* 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 DateService
|
|
*/
|
|
public getEventsForPeriod(startDate: Date, endDate: Date): CalendarEvent[] {
|
|
// Create cache key using DateService for consistent formatting
|
|
const cacheKey = `${this.dateService.formatISODate(startDate)}_${this.dateService.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 => {
|
|
// Event overlaps period if it starts before period ends AND ends after period starts
|
|
return event.start <= endDate && event.end >= 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, 'id'>): 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>): 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<void> {
|
|
await this.loadData();
|
|
}
|
|
} |