diff --git a/src/v2/features/event/EventRenderer.ts b/src/v2/features/event/EventRenderer.ts index a6af2e1..e92f7e3 100644 --- a/src/v2/features/event/EventRenderer.ts +++ b/src/v2/features/event/EventRenderer.ts @@ -59,7 +59,7 @@ export class EventRenderer { private handleDragEnd(payload: IDragEndPayload): void { if (payload.target === 'header') { // Event was dropped in header drawer - remove from grid - const element = this.container?.querySelector(`swp-content-viewport swp-event[data-event-id="${payload.eventId}"]`); + const element = this.container?.querySelector(`swp-content-viewport swp-event[data-event-id="${payload.swpEvent.eventId}"]`); element?.remove(); } } @@ -284,8 +284,11 @@ export class EventRenderer { private createEventElement(event: ICalendarEvent): HTMLElement { const element = document.createElement('swp-event'); - // Only essential data attribute (eventId for DragDropManager compatibility) + // Data attributes for SwpEvent compatibility element.dataset.eventId = event.id; + if (event.resourceId) { + element.dataset.resourceId = event.resourceId; + } // Calculate position const position = calculateEventPosition(event.start, event.end, this.gridConfig); diff --git a/src/v2/managers/DragDropManager.ts b/src/v2/managers/DragDropManager.ts index 4fa732e..9449c0b 100644 --- a/src/v2/managers/DragDropManager.ts +++ b/src/v2/managers/DragDropManager.ts @@ -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' diff --git a/src/v2/managers/EventPersistenceManager.ts b/src/v2/managers/EventPersistenceManager.ts index 5241779..8e5907d 100644 --- a/src/v2/managers/EventPersistenceManager.ts +++ b/src/v2/managers/EventPersistenceManager.ts @@ -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 => { const payload = (e as CustomEvent).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 => { const payload = (e as CustomEvent).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); }; diff --git a/src/v2/managers/ResizeManager.ts b/src/v2/managers/ResizeManager.ts index e557bfe..b33d82c 100644 --- a/src/v2/managers/ResizeManager.ts +++ b/src/v2/managers/ResizeManager.ts @@ -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 diff --git a/src/v2/types/DragTypes.ts b/src/v2/types/DragTypes.ts index 1549965..31de064 100644 --- a/src/v2/types/DragTypes.ts +++ b/src/v2/types/DragTypes.ts @@ -2,6 +2,8 @@ * DragTypes - Event payloads for drag-drop operations */ +import { SwpEvent } from './SwpEvent'; + export interface IMousePosition { x: number; y: number; @@ -24,12 +26,7 @@ export interface IDragMovePayload { } export interface IDragEndPayload { - eventId: string; - element: HTMLElement; - snappedY: number; // Final snapped position - columnElement: HTMLElement; - dateKey: string; // Target column date (from dataset) - resourceId?: string; // Target column resource (resource mode) + swpEvent: SwpEvent; // Wrapper with element, start, end, eventId, resourceId sourceDateKey: string; // Source column date (where drag started) sourceResourceId?: string; // Source column resource (where drag started) target: 'grid' | 'header'; // Where the event was dropped diff --git a/src/v2/types/ResizeTypes.ts b/src/v2/types/ResizeTypes.ts index db544b1..75caf6b 100644 --- a/src/v2/types/ResizeTypes.ts +++ b/src/v2/types/ResizeTypes.ts @@ -2,6 +2,8 @@ * ResizeTypes - Event payloads for resize operations */ +import { SwpEvent } from './SwpEvent'; + export interface IResizeStartPayload { eventId: string; element: HTMLElement; @@ -9,8 +11,5 @@ export interface IResizeStartPayload { } export interface IResizeEndPayload { - eventId: string; - element: HTMLElement; - newHeight: number; - newDurationMinutes: number; + swpEvent: SwpEvent; // Wrapper with element, start, end, eventId, resourceId } diff --git a/src/v2/types/SwpEvent.ts b/src/v2/types/SwpEvent.ts new file mode 100644 index 0000000..9a956ce --- /dev/null +++ b/src/v2/types/SwpEvent.ts @@ -0,0 +1,78 @@ +import { IGridConfig } from '../core/IGridConfig'; + +/** + * SwpEvent - Wrapper class for calendar event elements + * + * Encapsulates an HTMLElement and provides computed properties + * for start/end times based on element position and grid config. + * + * Usage: + * - All data (eventId, resourceId) is read from element.dataset + * - Position (top, height) is read from element.style + * - Factory method `fromElement()` calculates Date objects + */ +export class SwpEvent { + readonly element: HTMLElement; + private _start: Date; + private _end: Date; + + constructor(element: HTMLElement, start: Date, end: Date) { + this.element = element; + this._start = start; + this._end = end; + } + + /** Event ID from element.dataset.eventId */ + get eventId(): string { + return this.element.dataset.eventId || ''; + } + + /** Resource ID from element.dataset.resourceId */ + get resourceId(): string | undefined { + return this.element.dataset.resourceId; + } + + get start(): Date { + return this._start; + } + + get end(): Date { + return this._end; + } + + /** Duration in minutes */ + get durationMinutes(): number { + return (this._end.getTime() - this._start.getTime()) / (1000 * 60); + } + + /** Duration in milliseconds */ + get durationMs(): number { + return this._end.getTime() - this._start.getTime(); + } + + /** + * Factory: Create SwpEvent from element + dateKey + * Reads top/height from element.style to calculate start/end + */ + static fromElement( + element: HTMLElement, + dateKey: string, + gridConfig: IGridConfig + ): SwpEvent { + const topPixels = parseFloat(element.style.top) || 0; + const heightPixels = parseFloat(element.style.height) || 0; + + // Calculate start from top position + const startMinutesFromGrid = (topPixels / gridConfig.hourHeight) * 60; + const totalMinutes = (gridConfig.dayStartHour * 60) + startMinutesFromGrid; + + const start = new Date(dateKey); + start.setHours(Math.floor(totalMinutes / 60), totalMinutes % 60, 0, 0); + + // Calculate end from height + const durationMinutes = (heightPixels / gridConfig.hourHeight) * 60; + const end = new Date(start.getTime() + durationMinutes * 60 * 1000); + + return new SwpEvent(element, start, end); + } +}