Calendar/src/managers/EventManager.ts

253 lines
8.8 KiB
TypeScript
Raw Normal View History

import { EventBus } from '../core/EventBus';
2025-08-07 00:15:44 +02:00
import { IEventBus, CalendarEvent, ResourceCalendarData } from '../types/CalendarTypes';
import { EventTypes } from '../constants/EventTypes';
import { StateEvents } from '../types/CalendarState';
2025-08-07 00:15:44 +02:00
import { calendarConfig } from '../core/CalendarConfig';
/**
* EventManager - Administrerer event lifecycle og CRUD operationer
* Håndterer mock data og event synchronization
*/
export class EventManager {
private eventBus: IEventBus;
private events: CalendarEvent[] = [];
constructor(eventBus: IEventBus) {
2025-08-07 00:15:44 +02:00
console.log('EventManager: Constructor called');
this.eventBus = eventBus;
this.setupEventListeners();
console.log('EventManager: Waiting for CALENDAR_INITIALIZED before loading data');
}
private setupEventListeners(): void {
// NOTE: Removed POC event listener to prevent interference with production code
// POC sliding animation should not trigger separate event rendering
// this.eventBus.on(EventTypes.WEEK_CONTENT_RENDERED, ...);
2025-08-09 01:16:04 +02:00
}
2025-08-09 01:16:04 +02:00
/**
* Public method to load data - called directly by CalendarManager
*/
public async loadData(): Promise<void> {
console.log('EventManager: Loading data via direct call');
await this.loadMockData();
console.log(`EventManager: Data loaded successfully - ${this.events.length} events`);
// Debug: Log first few events
if (this.events.length > 0) {
console.log('EventManager: First event:', {
title: this.events[0].title,
start: this.events[0].start,
end: this.events[0].end
});
}
}
2025-08-07 00:15:44 +02:00
private async loadMockData(): Promise<void> {
try {
const calendarType = calendarConfig.getCalendarMode();
2025-08-07 00:15:44 +02:00
let jsonFile: string;
console.log(`EventManager: Calendar type detected: '${calendarType}'`);
if (calendarType === 'resource') {
jsonFile = '/src/data/mock-resource-events.json';
} else {
jsonFile = '/src/data/mock-events.json';
}
2025-08-07 00:15:44 +02:00
console.log(`EventManager: Loading ${calendarType} calendar data from ${jsonFile}`);
const response = await fetch(jsonFile);
if (!response.ok) {
throw new Error(`Failed to load mock events: ${response.status}`);
}
const data = await response.json();
console.log(`EventManager: Loaded data for ${calendarType} calendar`);
2025-08-07 00:15:44 +02:00
// Store raw data for GridManager
this.rawData = data;
// Process data for internal use
this.processCalendarData(calendarType, data);
2025-08-07 00:15:44 +02:00
} catch (error) {
console.error('EventManager: Failed to load mock events:', error);
this.events = []; // Fallback to empty array
}
}
private processCalendarData(calendarType: string, data: any): void {
if (calendarType === 'resource') {
const resourceData = data as ResourceCalendarData;
this.events = resourceData.resources.flatMap(resource =>
resource.events.map(event => ({
...event,
resourceName: resource.name,
resourceDisplayName: resource.displayName,
resourceEmployeeId: resource.employeeId
}))
);
console.log(`EventManager: Processed ${this.events.length} events from ${resourceData.resources.length} resources`);
} else {
this.events = data as CalendarEvent[];
console.log(`EventManager: Processed ${this.events.length} date events`);
}
}
private syncEvents(): void {
// Events are now synced via StateEvents.DATA_LOADED during initialization
// This method maintained for internal state management only
console.log(`EventManager: Internal sync - ${this.events.length} events in memory`);
}
public getEvents(): CalendarEvent[] {
return [...this.events];
}
/**
* Get raw resource data for resource calendar mode
*/
public getResourceData(): any {
return this.rawData;
}
private rawData: any = null;
public getEventById(id: string): CalendarEvent | undefined {
return this.events.find(event => event.id === id);
}
public addEvent(event: Omit<CalendarEvent, 'id'>): CalendarEvent {
const newEvent: CalendarEvent = {
...event,
id: Date.now().toString()
};
this.events.push(newEvent);
this.syncEvents();
this.eventBus.emit(EventTypes.EVENT_CREATED, {
event: newEvent
});
return newEvent;
}
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.syncEvents();
this.eventBus.emit(EventTypes.EVENT_UPDATED, {
event: updatedEvent
});
return updatedEvent;
}
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.syncEvents();
this.eventBus.emit(EventTypes.EVENT_DELETED, {
event: deletedEvent
});
return true;
}
public refresh(): void {
this.syncEvents();
}
/**
* Load events for a specific week into a container (POC-style)
*/
private loadEventsForWeek(weekStart: Date, weekEnd: Date, container: HTMLElement): void {
console.log(`EventManager: Loading events for week ${weekStart.toDateString()} - ${weekEnd.toDateString()}`);
// Filter events for this week
const weekEvents = this.events.filter(event => {
const eventDate = new Date(event.start);
return eventDate >= weekStart && eventDate <= weekEnd;
});
console.log(`EventManager: Found ${weekEvents.length} events for this week`);
// Render events in the container (POC approach)
this.renderEventsInContainer(weekEvents, container);
}
/**
* Render events in a specific container (POC-style)
*/
private renderEventsInContainer(events: CalendarEvent[], container: HTMLElement): void {
const dayColumns = container.querySelectorAll('swp-day-column');
events.forEach(event => {
const eventDate = new Date(event.start);
const dayOfWeek = eventDate.getDay(); // 0 = Sunday
const column = dayColumns[dayOfWeek];
if (column) {
const eventsLayer = column.querySelector('swp-events-layer');
if (eventsLayer) {
this.renderEventInColumn(event, eventsLayer as HTMLElement);
}
}
});
}
/**
* Render a single event in a column (POC-style)
*/
private renderEventInColumn(event: CalendarEvent, eventsLayer: HTMLElement): void {
const eventElement = document.createElement('swp-event');
eventElement.dataset.type = event.type || 'meeting';
// Calculate position (simplified - assumes 7 AM start like POC)
const startTime = new Date(event.start);
const hours = startTime.getHours();
const minutes = startTime.getMinutes();
const startMinutes = (hours - 7) * 60 + minutes; // 7 is start hour like POC
// Calculate duration
const endTime = new Date(event.end);
const durationMs = endTime.getTime() - startTime.getTime();
const durationMinutes = Math.floor(durationMs / (1000 * 60));
eventElement.style.top = `${startMinutes}px`;
eventElement.style.height = `${durationMinutes}px`;
eventElement.innerHTML = `
<swp-event-time>${this.formatTime(hours, minutes)}</swp-event-time>
<swp-event-title>${event.title}</swp-event-title>
`;
eventsLayer.appendChild(eventElement);
}
/**
* Format time for display (POC-style)
*/
private formatTime(hours: number, minutes: number): string {
const period = hours >= 12 ? 'PM' : 'AM';
const displayHours = hours % 12 || 12;
return `${displayHours}:${String(minutes).padStart(2, '0')} ${period}`;
}
public destroy(): void {
this.events = [];
}
}