2025-11-03 21:30:50 +01:00
|
|
|
import { IEventBus, ICalendarEvent } from '../types/CalendarTypes';
|
2025-08-20 20:22:51 +02:00
|
|
|
import { CoreEvents } from '../constants/CoreEvents';
|
2025-11-03 22:04:37 +01:00
|
|
|
import { Configuration } from '../configurations/CalendarConfig';
|
2025-10-03 20:50:40 +02:00
|
|
|
import { DateService } from '../utils/DateService';
|
2025-11-03 14:54:57 +01:00
|
|
|
import { IEventRepository } from '../repositories/IEventRepository';
|
2025-07-24 22:17:38 +02:00
|
|
|
|
|
|
|
|
/**
|
2025-11-01 21:07:07 +01:00
|
|
|
* EventManager - Event lifecycle and CRUD operations
|
2025-11-05 00:37:57 +01:00
|
|
|
* Delegates all data operations to IEventRepository
|
|
|
|
|
* No longer maintains in-memory cache - repository is single source of truth
|
2025-07-24 22:17:38 +02:00
|
|
|
*/
|
|
|
|
|
export class EventManager {
|
2025-10-30 23:47:30 +01:00
|
|
|
|
2025-10-03 20:50:40 +02:00
|
|
|
private dateService: DateService;
|
2025-11-03 21:30:50 +01:00
|
|
|
private config: Configuration;
|
2025-11-03 14:54:57 +01:00
|
|
|
private repository: IEventRepository;
|
2025-07-24 22:17:38 +02:00
|
|
|
|
2025-10-30 23:47:30 +01:00
|
|
|
constructor(
|
|
|
|
|
private eventBus: IEventBus,
|
|
|
|
|
dateService: DateService,
|
2025-11-03 21:30:50 +01:00
|
|
|
config: Configuration,
|
2025-11-03 14:54:57 +01:00
|
|
|
repository: IEventRepository
|
2025-10-30 23:47:30 +01:00
|
|
|
) {
|
|
|
|
|
this.dateService = dateService;
|
|
|
|
|
this.config = config;
|
2025-11-03 14:54:57 +01:00
|
|
|
this.repository = repository;
|
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-11-03 14:54:57 +01:00
|
|
|
* Load event data from repository
|
2025-11-05 00:37:57 +01:00
|
|
|
* No longer caches - delegates to repository
|
2025-08-09 01:16:04 +02:00
|
|
|
*/
|
|
|
|
|
public async loadData(): Promise<void> {
|
2025-09-03 18:51:19 +02:00
|
|
|
try {
|
2025-11-05 00:37:57 +01:00
|
|
|
// Just ensure repository is ready - no caching
|
|
|
|
|
await this.repository.loadEvents();
|
2025-09-03 18:51:19 +02:00
|
|
|
} catch (error) {
|
|
|
|
|
console.error('Failed to load event data:', error);
|
2025-11-03 14:54:57 +01:00
|
|
|
throw error;
|
2025-08-07 00:15:44 +02:00
|
|
|
}
|
2025-08-09 00:31:44 +02:00
|
|
|
}
|
2025-07-24 22:17:38 +02:00
|
|
|
|
2025-09-03 18:51:19 +02:00
|
|
|
/**
|
2025-11-05 00:37:57 +01:00
|
|
|
* Get all events from repository
|
2025-09-03 18:51:19 +02:00
|
|
|
*/
|
2025-11-05 00:37:57 +01:00
|
|
|
public async getEvents(copy: boolean = false): Promise<ICalendarEvent[]> {
|
|
|
|
|
const events = await this.repository.loadEvents();
|
|
|
|
|
return copy ? [...events] : events;
|
2025-07-24 22:17:38 +02:00
|
|
|
}
|
|
|
|
|
|
2025-09-03 18:51:19 +02:00
|
|
|
/**
|
2025-11-05 00:37:57 +01:00
|
|
|
* Get event by ID from repository
|
2025-09-03 18:51:19 +02:00
|
|
|
*/
|
2025-11-05 00:37:57 +01:00
|
|
|
public async getEventById(id: string): Promise<ICalendarEvent | undefined> {
|
|
|
|
|
const events = await this.repository.loadEvents();
|
|
|
|
|
return events.find(event => event.id === id);
|
2025-07-24 22:17:38 +02:00
|
|
|
}
|
|
|
|
|
|
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
|
|
|
|
|
*/
|
2025-11-05 00:37:57 +01:00
|
|
|
public async getEventForNavigation(id: string): Promise<{ event: ICalendarEvent; eventDate: Date } | null> {
|
|
|
|
|
const event = await this.getEventById(id);
|
2025-09-04 00:16:35 +02:00
|
|
|
if (!event) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-04 00:32:26 +02:00
|
|
|
// 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`);
|
2025-09-04 00:16:35 +02:00
|
|
|
return null;
|
|
|
|
|
}
|
2025-10-04 00:32:26 +02:00
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
event,
|
|
|
|
|
eventDate: event.start
|
|
|
|
|
};
|
2025-09-04 00:16:35 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 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
|
|
|
|
|
*/
|
2025-11-05 00:37:57 +01:00
|
|
|
public async navigateToEvent(eventId: string): Promise<boolean> {
|
|
|
|
|
const eventInfo = await this.getEventForNavigation(eventId);
|
2025-09-04 00:16:35 +02:00
|
|
|
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-11-01 21:07:07 +01:00
|
|
|
* Get events that overlap with a given time period
|
2025-08-16 00:51:12 +02:00
|
|
|
*/
|
2025-11-05 00:37:57 +01:00
|
|
|
public async getEventsForPeriod(startDate: Date, endDate: Date): Promise<ICalendarEvent[]> {
|
|
|
|
|
const events = await this.repository.loadEvents();
|
2025-11-01 21:07:07 +01:00
|
|
|
// Event overlaps period if it starts before period ends AND ends after period starts
|
2025-11-05 00:37:57 +01:00
|
|
|
return events.filter(event => {
|
2025-09-09 14:35:21 +02:00
|
|
|
return event.start <= endDate && event.end >= startDate;
|
2025-08-16 00:51:12 +02:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-03 18:51:19 +02:00
|
|
|
/**
|
2025-11-01 21:07:07 +01:00
|
|
|
* Create a new event and add it to the calendar
|
2025-11-05 00:37:57 +01:00
|
|
|
* Delegates to repository with source='local'
|
2025-09-03 18:51:19 +02:00
|
|
|
*/
|
2025-11-05 00:37:57 +01:00
|
|
|
public async addEvent(event: Omit<ICalendarEvent, 'id'>): Promise<ICalendarEvent> {
|
|
|
|
|
const newEvent = await this.repository.createEvent(event, 'local');
|
2025-11-01 21:07:07 +01: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
|
|
|
/**
|
2025-11-01 21:07:07 +01:00
|
|
|
* Update an existing event
|
2025-11-05 00:37:57 +01:00
|
|
|
* Delegates to repository with source='local'
|
2025-09-03 18:51:19 +02:00
|
|
|
*/
|
2025-11-05 00:37:57 +01:00
|
|
|
public async updateEvent(id: string, updates: Partial<ICalendarEvent>): Promise<ICalendarEvent | null> {
|
|
|
|
|
try {
|
|
|
|
|
const updatedEvent = await this.repository.updateEvent(id, updates, 'local');
|
2025-07-24 22:17:38 +02:00
|
|
|
|
2025-11-05 00:37:57 +01:00
|
|
|
this.eventBus.emit(CoreEvents.EVENT_UPDATED, {
|
|
|
|
|
event: updatedEvent
|
|
|
|
|
});
|
2025-11-01 21:07:07 +01:00
|
|
|
|
2025-11-05 00:37:57 +01:00
|
|
|
return updatedEvent;
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error(`Failed to update event ${id}:`, error);
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Delete an event
|
|
|
|
|
* Delegates to repository with source='local'
|
|
|
|
|
*/
|
|
|
|
|
public async deleteEvent(id: string): Promise<boolean> {
|
|
|
|
|
try {
|
|
|
|
|
await this.repository.deleteEvent(id, 'local');
|
|
|
|
|
|
|
|
|
|
this.eventBus.emit(CoreEvents.EVENT_DELETED, {
|
|
|
|
|
eventId: id
|
|
|
|
|
});
|
2025-07-24 22:17:38 +02:00
|
|
|
|
2025-11-05 00:37:57 +01:00
|
|
|
return true;
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error(`Failed to delete event ${id}:`, error);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Handle remote update from SignalR
|
|
|
|
|
* Delegates to repository with source='remote'
|
|
|
|
|
*/
|
|
|
|
|
public async handleRemoteUpdate(event: ICalendarEvent): Promise<void> {
|
|
|
|
|
try {
|
|
|
|
|
await this.repository.updateEvent(event.id, event, 'remote');
|
|
|
|
|
|
|
|
|
|
this.eventBus.emit(CoreEvents.REMOTE_UPDATE_RECEIVED, {
|
|
|
|
|
event
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
this.eventBus.emit(CoreEvents.EVENT_UPDATED, {
|
|
|
|
|
event
|
|
|
|
|
});
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error(`Failed to handle remote update for event ${event.id}:`, error);
|
|
|
|
|
}
|
2025-07-24 22:17:38 +02:00
|
|
|
}
|
2025-11-01 21:07:07 +01:00
|
|
|
}
|