Refactor event handling with SwpEvent wrapper

Introduces SwpEvent class to centralize event element data and calculations

Improves drag and resize event handling by extracting common logic
Simplifies calculation of event start/end times from element positions
Enhances type safety and reduces code complexity in event managers

Removes direct pixel/minute calculations from multiple managers
This commit is contained in:
Janus C. H. Knudsen 2025-12-11 22:45:33 +01:00
parent 9e568fbd8e
commit f670598e7a
7 changed files with 132 additions and 62 deletions

View file

@ -13,6 +13,7 @@ import {
IDragMoveHeaderPayload,
IDragLeaveHeaderPayload
} from '../types/DragTypes';
import { SwpEvent } from '../types/SwpEvent';
interface DragState {
eventId: string;
@ -146,18 +147,19 @@ export class DragDropManager {
// Remove ghost
this.dragState.ghostElement.remove();
// Get column data (target = current column, source = where drag started)
// Get dateKey from target column
const dateKey = this.dragState.columnElement.dataset.date || '';
const resourceId = this.dragState.columnElement.dataset.resourceId;
// Create SwpEvent from element (reads top/height/eventId/resourceId from element)
const swpEvent = SwpEvent.fromElement(
this.dragState.element,
dateKey,
this.gridConfig
);
// Emit drag:end
const payload: IDragEndPayload = {
eventId: this.dragState.eventId,
element: this.dragState.element,
snappedY,
columnElement: this.dragState.columnElement,
dateKey,
resourceId,
swpEvent,
sourceDateKey: this.dragState.sourceDateKey,
sourceResourceId: this.dragState.sourceResourceId,
target: this.inHeader ? 'header' : 'grid'

View file

@ -7,18 +7,15 @@
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();
@ -34,44 +31,34 @@ export class EventPersistenceManager {
*/
private handleDragEnd = async (e: Event): Promise<void> => {
const payload = (e as CustomEvent<IDragEndPayload>).detail;
const { swpEvent } = payload;
// Get existing event
const event = await this.eventService.get(payload.eventId);
// Get existing event to merge with
const event = await this.eventService.get(swpEvent.eventId);
if (!event) {
console.warn(`EventPersistenceManager: Event ${payload.eventId} not found`);
console.warn(`EventPersistenceManager: Event ${swpEvent.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
// Update and save - start/end already calculated in SwpEvent
const updatedEvent: ICalendarEvent = {
...event,
start: newStart,
end: newEnd,
resourceId: payload.resourceId ?? event.resourceId,
start: swpEvent.start,
end: swpEvent.end,
resourceId: swpEvent.resourceId ?? event.resourceId,
syncStatus: 'pending'
};
await this.eventService.save(updatedEvent);
// Emit EVENT_UPDATED for EventRenderer to re-render affected columns
const targetDateKey = this.dateService.getDateKey(swpEvent.start);
const updatePayload: IEventUpdatedPayload = {
eventId: updatedEvent.id,
sourceDateKey: payload.sourceDateKey,
sourceResourceId: payload.sourceResourceId,
targetDateKey: payload.dateKey,
targetResourceId: payload.resourceId
targetDateKey,
targetResourceId: swpEvent.resourceId
};
this.eventBus.emit(CoreEvents.EVENT_UPDATED, updatePayload);
};
@ -81,21 +68,19 @@ export class EventPersistenceManager {
*/
private handleResizeEnd = async (e: Event): Promise<void> => {
const payload = (e as CustomEvent<IResizeEndPayload>).detail;
const { swpEvent } = payload;
// Get existing event
const event = await this.eventService.get(payload.eventId);
// Get existing event to merge with
const event = await this.eventService.get(swpEvent.eventId);
if (!event) {
console.warn(`EventPersistenceManager: Event ${payload.eventId} not found`);
console.warn(`EventPersistenceManager: Event ${swpEvent.eventId} not found`);
return;
}
// Calculate new end time
const newEnd = new Date(event.start.getTime() + payload.newDurationMinutes * 60 * 1000);
// Update and save
// Update and save - end already calculated in SwpEvent
const updatedEvent: ICalendarEvent = {
...event,
end: newEnd,
end: swpEvent.end,
syncStatus: 'pending'
};
@ -103,13 +88,13 @@ export class EventPersistenceManager {
// 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 dateKey = this.dateService.getDateKey(swpEvent.start);
const updatePayload: IEventUpdatedPayload = {
eventId: updatedEvent.id,
sourceDateKey: dateKey,
sourceResourceId: event.resourceId,
sourceResourceId: swpEvent.resourceId,
targetDateKey: dateKey,
targetResourceId: event.resourceId
targetResourceId: swpEvent.resourceId
};
this.eventBus.emit(CoreEvents.EVENT_UPDATED, updatePayload);
};

View file

@ -4,6 +4,7 @@ import { pixelsToMinutes, minutesToPixels, snapToGrid } from '../utils/PositionU
import { DateService } from '../core/DateService';
import { CoreEvents } from '../constants/CoreEvents';
import { IResizeStartPayload, IResizeEndPayload } from '../types/ResizeTypes';
import { SwpEvent } from '../types/SwpEvent';
/**
* ResizeManager - Handles resize of calendar events
@ -250,15 +251,20 @@ export class ResizeManager {
// Remove global resizing class
document.documentElement.classList.remove('swp--resizing');
// Emit resize end event
const newHeight = this.resizeState.currentHeight;
const newDurationMinutes = pixelsToMinutes(newHeight, this.gridConfig);
// Get dateKey from parent column
const column = this.resizeState.element.closest('swp-day-column') as HTMLElement;
const dateKey = column?.dataset.date || '';
// Create SwpEvent from element (reads top/height/eventId/resourceId from element)
const swpEvent = SwpEvent.fromElement(
this.resizeState.element,
dateKey,
this.gridConfig
);
// Emit resize end event
this.eventBus.emit(CoreEvents.EVENT_RESIZE_END, {
eventId: this.resizeState.eventId,
element: this.resizeState.element,
newHeight,
newDurationMinutes
swpEvent
} as IResizeEndPayload);
// Reset state