Enhances event layout engine with advanced rendering logic

Introduces sophisticated event layout algorithm for handling complex scheduling scenarios

Adds support for:
- Grid and stacked event rendering
- Automatic column allocation
- Nested event stacking
- Threshold-based event grouping

Improves visual representation of overlapping and concurrent events
This commit is contained in:
Janus C. H. Knudsen 2025-12-11 18:11:11 +01:00
parent 4e22fbc948
commit 70172e8f10
26 changed files with 2108 additions and 44 deletions

View file

@ -0,0 +1,116 @@
/**
* EventPersistenceManager - Persists event changes to IndexedDB
*
* Listens to drag/resize events and updates IndexedDB via EventService.
* This bridges the gap between UI interactions and data persistence.
*/
import { ICalendarEvent, IEventBus, IEventUpdatedPayload } from '../types/CalendarTypes';
import { EventService } from '../storage/events/EventService';
import { IGridConfig } from '../core/IGridConfig';
import { DateService } from '../core/DateService';
import { CoreEvents } from '../constants/CoreEvents';
import { IDragEndPayload } from '../types/DragTypes';
import { IResizeEndPayload } from '../types/ResizeTypes';
import { pixelsToMinutes } from '../utils/PositionUtils';
export class EventPersistenceManager {
constructor(
private eventService: EventService,
private eventBus: IEventBus,
private gridConfig: IGridConfig,
private dateService: DateService
) {
this.setupListeners();
}
private setupListeners(): void {
this.eventBus.on(CoreEvents.EVENT_DRAG_END, this.handleDragEnd);
this.eventBus.on(CoreEvents.EVENT_RESIZE_END, this.handleResizeEnd);
}
/**
* Handle drag end - update event position in IndexedDB
*/
private handleDragEnd = async (e: Event): Promise<void> => {
const payload = (e as CustomEvent<IDragEndPayload>).detail;
// Get existing event
const event = await this.eventService.get(payload.eventId);
if (!event) {
console.warn(`EventPersistenceManager: Event ${payload.eventId} not found`);
return;
}
// Calculate new start time from snappedY
const minutesFromDayStart = pixelsToMinutes(payload.snappedY, this.gridConfig);
const totalMinutes = (this.gridConfig.dayStartHour * 60) + minutesFromDayStart;
// Preserve duration
const durationMs = event.end.getTime() - event.start.getTime();
// Create new dates with correct day from dateKey
const newStart = new Date(payload.dateKey);
newStart.setHours(Math.floor(totalMinutes / 60), totalMinutes % 60, 0, 0);
const newEnd = new Date(newStart.getTime() + durationMs);
// Update and save
const updatedEvent: ICalendarEvent = {
...event,
start: newStart,
end: newEnd,
resourceId: payload.resourceId ?? event.resourceId,
syncStatus: 'pending'
};
await this.eventService.save(updatedEvent);
// Emit EVENT_UPDATED for EventRenderer to re-render affected columns
const updatePayload: IEventUpdatedPayload = {
eventId: updatedEvent.id,
sourceDateKey: payload.sourceDateKey,
sourceResourceId: payload.sourceResourceId,
targetDateKey: payload.dateKey,
targetResourceId: payload.resourceId
};
this.eventBus.emit(CoreEvents.EVENT_UPDATED, updatePayload);
};
/**
* Handle resize end - update event duration in IndexedDB
*/
private handleResizeEnd = async (e: Event): Promise<void> => {
const payload = (e as CustomEvent<IResizeEndPayload>).detail;
// Get existing event
const event = await this.eventService.get(payload.eventId);
if (!event) {
console.warn(`EventPersistenceManager: Event ${payload.eventId} not found`);
return;
}
// Calculate new end time
const newEnd = new Date(event.start.getTime() + payload.newDurationMinutes * 60 * 1000);
// Update and save
const updatedEvent: ICalendarEvent = {
...event,
end: newEnd,
syncStatus: 'pending'
};
await this.eventService.save(updatedEvent);
// Emit EVENT_UPDATED for EventRenderer to re-render the column
// Resize stays in same column, so source and target are the same
const dateKey = this.dateService.getDateKey(event.start);
const updatePayload: IEventUpdatedPayload = {
eventId: updatedEvent.id,
sourceDateKey: dateKey,
sourceResourceId: event.resourceId,
targetDateKey: dateKey,
targetResourceId: event.resourceId
};
this.eventBus.emit(CoreEvents.EVENT_UPDATED, updatePayload);
};
}