2025-11-22 23:38:52 +01:00
|
|
|
import { IEventBus } from '../types/CalendarTypes';
|
|
|
|
|
import { IColumnInfo, IColumnDataSource } from '../types/ColumnDataSource';
|
2025-08-20 19:52:18 +02:00
|
|
|
import { CoreEvents } from '../constants/CoreEvents';
|
2025-08-17 22:54:00 +02:00
|
|
|
import { EventManager } from '../managers/EventManager';
|
2025-11-01 16:28:45 +01:00
|
|
|
import { IEventRenderer } from './EventRenderer';
|
2025-09-19 00:20:30 +02:00
|
|
|
import { SwpEventElement } from '../elements/SwpEventElement';
|
2025-11-22 23:38:52 +01:00
|
|
|
import { IDragStartEventPayload, IDragMoveEventPayload, IDragEndEventPayload, IDragMouseEnterHeaderEventPayload, IDragMouseLeaveHeaderEventPayload, IDragMouseEnterColumnEventPayload, IDragColumnChangeEventPayload, IResizeEndEventPayload } from '../types/EventTypes';
|
2025-10-08 22:18:06 +02:00
|
|
|
import { DateService } from '../utils/DateService';
|
2025-11-22 23:38:52 +01:00
|
|
|
|
2025-07-24 22:17:38 +02:00
|
|
|
/**
|
2025-08-17 23:44:30 +02:00
|
|
|
* EventRenderingService - Render events i DOM med positionering using Strategy Pattern
|
2025-07-24 22:17:38 +02:00
|
|
|
* Håndterer event positioning og overlap detection
|
|
|
|
|
*/
|
2025-08-17 23:44:30 +02:00
|
|
|
export class EventRenderingService {
|
2025-07-24 22:17:38 +02:00
|
|
|
private eventBus: IEventBus;
|
2025-08-16 00:51:12 +02:00
|
|
|
private eventManager: EventManager;
|
2025-11-01 16:28:45 +01:00
|
|
|
private strategy: IEventRenderer;
|
2025-11-22 23:38:52 +01:00
|
|
|
private dataSource: IColumnDataSource;
|
2025-10-08 22:18:06 +02:00
|
|
|
private dateService: DateService;
|
2025-07-24 22:17:38 +02:00
|
|
|
|
2025-09-21 15:48:13 +02:00
|
|
|
private dragMouseLeaveHeaderListener: ((event: Event) => void) | null = null;
|
|
|
|
|
|
2025-10-30 23:47:30 +01:00
|
|
|
constructor(
|
|
|
|
|
eventBus: IEventBus,
|
|
|
|
|
eventManager: EventManager,
|
2025-11-01 16:28:45 +01:00
|
|
|
strategy: IEventRenderer,
|
2025-11-22 23:38:52 +01:00
|
|
|
dataSource: IColumnDataSource,
|
2025-10-30 23:47:30 +01:00
|
|
|
dateService: DateService
|
|
|
|
|
) {
|
2025-07-24 22:17:38 +02:00
|
|
|
this.eventBus = eventBus;
|
2025-08-16 00:51:12 +02:00
|
|
|
this.eventManager = eventManager;
|
2025-10-15 00:58:29 +02:00
|
|
|
this.strategy = strategy;
|
2025-11-22 23:38:52 +01:00
|
|
|
this.dataSource = dataSource;
|
2025-10-30 23:47:30 +01:00
|
|
|
this.dateService = dateService;
|
2025-10-08 22:18:06 +02:00
|
|
|
|
2025-07-24 22:17:38 +02:00
|
|
|
this.setupEventListeners();
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-09 01:16:04 +02:00
|
|
|
private setupEventListeners(): void {
|
2025-09-21 15:48:13 +02:00
|
|
|
|
2025-08-20 19:52:18 +02:00
|
|
|
this.eventBus.on(CoreEvents.GRID_RENDERED, (event: Event) => {
|
2025-08-16 00:51:12 +02:00
|
|
|
this.handleGridRendered(event as CustomEvent);
|
|
|
|
|
});
|
|
|
|
|
|
2025-08-20 20:22:51 +02:00
|
|
|
this.eventBus.on(CoreEvents.VIEW_CHANGED, (event: Event) => {
|
2025-08-16 00:51:12 +02:00
|
|
|
this.handleViewChanged(event as CustomEvent);
|
2025-08-09 01:16:04 +02:00
|
|
|
});
|
2025-09-19 00:20:30 +02:00
|
|
|
|
2025-09-22 21:53:18 +02:00
|
|
|
|
2025-09-20 09:40:56 +02:00
|
|
|
// Handle all drag events and delegate to appropriate renderer
|
|
|
|
|
this.setupDragEventListeners();
|
2025-09-19 00:20:30 +02:00
|
|
|
|
2025-07-24 22:17:38 +02:00
|
|
|
}
|
|
|
|
|
|
2025-08-09 01:16:04 +02:00
|
|
|
|
2025-08-16 00:51:12 +02:00
|
|
|
/**
|
|
|
|
|
* Handle GRID_RENDERED event - render events in the current grid
|
2025-11-22 23:38:52 +01:00
|
|
|
* Events are now pre-filtered per column by IColumnDataSource
|
2025-08-16 00:51:12 +02:00
|
|
|
*/
|
|
|
|
|
private handleGridRendered(event: CustomEvent): void {
|
2025-11-14 16:25:03 +01:00
|
|
|
const { container, columns } = event.detail;
|
2025-09-21 15:48:13 +02:00
|
|
|
|
2025-11-14 16:25:03 +01:00
|
|
|
if (!container || !columns || columns.length === 0) {
|
2025-08-20 19:52:18 +02:00
|
|
|
return;
|
|
|
|
|
}
|
2025-09-21 15:48:13 +02:00
|
|
|
|
2025-11-22 23:38:52 +01:00
|
|
|
// Render events directly from columns (pre-filtered by IColumnDataSource)
|
|
|
|
|
this.renderEventsFromColumns(container, columns);
|
|
|
|
|
}
|
2025-11-14 16:25:03 +01:00
|
|
|
|
2025-11-22 23:38:52 +01:00
|
|
|
/**
|
|
|
|
|
* Render events from pre-filtered columns
|
|
|
|
|
* Each column already contains its events (filtered by IColumnDataSource)
|
|
|
|
|
*/
|
|
|
|
|
private renderEventsFromColumns(container: HTMLElement, columns: IColumnInfo[]): void {
|
|
|
|
|
this.strategy.clearEvents(container);
|
|
|
|
|
this.strategy.renderEvents(columns, container);
|
2025-11-13 21:22:28 +01:00
|
|
|
|
2025-11-22 23:38:52 +01:00
|
|
|
// Emit EVENTS_RENDERED for filtering system
|
|
|
|
|
const allEvents = columns.flatMap(col => col.events);
|
|
|
|
|
this.eventBus.emit(CoreEvents.EVENTS_RENDERED, {
|
|
|
|
|
events: allEvents,
|
|
|
|
|
container: container
|
2025-08-16 00:51:12 +02:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-24 22:17:38 +02:00
|
|
|
|
2025-08-16 00:51:12 +02:00
|
|
|
/**
|
|
|
|
|
* Handle VIEW_CHANGED event - clear and re-render for new view
|
|
|
|
|
*/
|
|
|
|
|
private handleViewChanged(event: CustomEvent): void {
|
|
|
|
|
// Clear all existing events since view structure may have changed
|
2025-08-09 01:16:04 +02:00
|
|
|
this.clearEvents();
|
2025-09-21 15:48:13 +02:00
|
|
|
|
2025-08-16 00:51:12 +02:00
|
|
|
// New rendering will be triggered by subsequent GRID_RENDERED event
|
|
|
|
|
}
|
2025-09-19 00:20:30 +02:00
|
|
|
|
|
|
|
|
|
2025-09-20 09:40:56 +02:00
|
|
|
/**
|
|
|
|
|
* Setup all drag event listeners - moved from EventRenderer for better separation of concerns
|
|
|
|
|
*/
|
|
|
|
|
private setupDragEventListeners(): void {
|
2025-10-08 23:29:56 +02:00
|
|
|
this.setupDragStartListener();
|
|
|
|
|
this.setupDragMoveListener();
|
|
|
|
|
this.setupDragEndListener();
|
|
|
|
|
this.setupDragColumnChangeListener();
|
|
|
|
|
this.setupDragMouseLeaveHeaderListener();
|
2025-10-10 19:37:10 +02:00
|
|
|
this.setupDragMouseEnterColumnListener();
|
2025-10-08 23:29:56 +02:00
|
|
|
this.setupResizeEndListener();
|
|
|
|
|
this.setupNavigationCompletedListener();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private setupDragStartListener(): void {
|
2025-09-20 09:40:56 +02:00
|
|
|
this.eventBus.on('drag:start', (event: Event) => {
|
2025-11-03 21:30:50 +01:00
|
|
|
const dragStartPayload = (event as CustomEvent<IDragStartEventPayload>).detail;
|
2025-10-08 23:29:56 +02:00
|
|
|
|
2025-10-12 22:00:02 +02:00
|
|
|
if (dragStartPayload.originalElement.hasAttribute('data-allday')) {
|
2025-10-08 23:29:56 +02:00
|
|
|
return;
|
2025-10-02 23:11:26 +02:00
|
|
|
}
|
|
|
|
|
|
2025-10-12 22:00:02 +02:00
|
|
|
if (dragStartPayload.originalElement && this.strategy.handleDragStart && dragStartPayload.columnBounds) {
|
2025-09-26 22:53:49 +02:00
|
|
|
this.strategy.handleDragStart(dragStartPayload);
|
2025-09-20 09:40:56 +02:00
|
|
|
}
|
|
|
|
|
});
|
2025-10-08 23:29:56 +02:00
|
|
|
}
|
2025-09-20 09:40:56 +02:00
|
|
|
|
2025-10-08 23:29:56 +02:00
|
|
|
private setupDragMoveListener(): void {
|
2025-09-20 09:40:56 +02:00
|
|
|
this.eventBus.on('drag:move', (event: Event) => {
|
2025-11-03 21:30:50 +01:00
|
|
|
let dragEvent = (event as CustomEvent<IDragMoveEventPayload>).detail;
|
2025-09-28 13:25:09 +02:00
|
|
|
|
2025-10-11 01:30:41 +02:00
|
|
|
if (dragEvent.draggedClone.hasAttribute('data-allday')) {
|
2025-10-08 23:29:56 +02:00
|
|
|
return;
|
2025-09-26 22:11:57 +02:00
|
|
|
}
|
2025-09-28 13:25:09 +02:00
|
|
|
if (this.strategy.handleDragMove) {
|
|
|
|
|
this.strategy.handleDragMove(dragEvent);
|
2025-09-20 09:40:56 +02:00
|
|
|
}
|
|
|
|
|
});
|
2025-10-08 23:29:56 +02:00
|
|
|
}
|
2025-09-20 09:40:56 +02:00
|
|
|
|
2025-10-08 23:29:56 +02:00
|
|
|
private setupDragEndListener(): void {
|
2025-11-05 00:37:57 +01:00
|
|
|
this.eventBus.on('drag:end', async (event: Event) => {
|
2025-11-22 23:38:52 +01:00
|
|
|
const { originalElement, draggedClone, finalPosition, target } = (event as CustomEvent<IDragEndEventPayload>).detail;
|
2025-09-21 15:48:13 +02:00
|
|
|
const finalColumn = finalPosition.column;
|
|
|
|
|
const finalY = finalPosition.snappedY;
|
|
|
|
|
|
2025-11-22 23:38:52 +01:00
|
|
|
// Only handle day column drops
|
2025-09-21 15:48:13 +02:00
|
|
|
if (target === 'swp-day-column' && finalColumn) {
|
2025-11-22 23:38:52 +01:00
|
|
|
const element = draggedClone as SwpEventElement;
|
2025-09-21 15:48:13 +02:00
|
|
|
|
2025-11-06 21:11:22 +01:00
|
|
|
if (originalElement && draggedClone && this.strategy.handleDragEnd) {
|
|
|
|
|
this.strategy.handleDragEnd(originalElement, draggedClone, finalColumn, finalY);
|
2025-09-20 09:40:56 +02:00
|
|
|
}
|
2025-10-08 22:18:06 +02:00
|
|
|
|
2025-11-22 23:38:52 +01:00
|
|
|
// Build update payload based on mode
|
|
|
|
|
const updatePayload: { start: Date; end: Date; allDay: boolean; resourceId?: string } = {
|
2025-11-06 21:11:22 +01:00
|
|
|
start: element.start,
|
|
|
|
|
end: element.end,
|
|
|
|
|
allDay: false
|
2025-11-22 23:38:52 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (this.dataSource.isResource()) {
|
|
|
|
|
// Resource mode: update resourceId, keep existing date
|
|
|
|
|
updatePayload.resourceId = finalColumn.identifier;
|
|
|
|
|
} else {
|
|
|
|
|
// Date mode: update date from column, keep existing time
|
|
|
|
|
const newDate = this.dateService.parseISO(finalColumn.identifier);
|
|
|
|
|
const startTimeMinutes = this.dateService.getMinutesSinceMidnight(element.start);
|
|
|
|
|
const endTimeMinutes = this.dateService.getMinutesSinceMidnight(element.end);
|
|
|
|
|
updatePayload.start = this.dateService.createDateAtTime(newDate, startTimeMinutes);
|
|
|
|
|
updatePayload.end = this.dateService.createDateAtTime(newDate, endTimeMinutes);
|
|
|
|
|
}
|
2025-10-08 22:18:06 +02:00
|
|
|
|
2025-11-22 23:38:52 +01:00
|
|
|
await this.eventManager.updateEvent(element.eventId, updatePayload);
|
2025-09-21 15:48:13 +02:00
|
|
|
|
2025-11-22 23:38:52 +01:00
|
|
|
// Trigger full refresh to re-render with updated data
|
|
|
|
|
this.eventBus.emit(CoreEvents.REFRESH_REQUESTED, {});
|
|
|
|
|
}
|
2025-09-20 09:40:56 +02:00
|
|
|
});
|
2025-10-08 23:29:56 +02:00
|
|
|
}
|
2025-09-20 09:40:56 +02:00
|
|
|
|
2025-10-08 23:29:56 +02:00
|
|
|
private setupDragColumnChangeListener(): void {
|
2025-09-20 09:40:56 +02:00
|
|
|
this.eventBus.on('drag:column-change', (event: Event) => {
|
2025-11-03 21:30:50 +01:00
|
|
|
let columnChangeEvent = (event as CustomEvent<IDragColumnChangeEventPayload>).detail;
|
2025-09-28 13:25:09 +02:00
|
|
|
|
2025-09-26 22:53:49 +02:00
|
|
|
// Filter: Only handle events where clone is NOT an all-day event (normal timed events)
|
2025-09-28 13:25:09 +02:00
|
|
|
if (columnChangeEvent.draggedClone && columnChangeEvent.draggedClone.hasAttribute('data-allday')) {
|
2025-10-01 21:27:13 +02:00
|
|
|
return;
|
2025-09-26 22:53:49 +02:00
|
|
|
}
|
2025-09-28 13:25:09 +02:00
|
|
|
|
2025-09-20 09:40:56 +02:00
|
|
|
if (this.strategy.handleColumnChange) {
|
2025-10-01 21:27:13 +02:00
|
|
|
this.strategy.handleColumnChange(columnChangeEvent);
|
2025-09-20 09:40:56 +02:00
|
|
|
}
|
|
|
|
|
});
|
2025-10-08 23:29:56 +02:00
|
|
|
}
|
2025-09-20 09:40:56 +02:00
|
|
|
|
2025-10-08 23:29:56 +02:00
|
|
|
private setupDragMouseLeaveHeaderListener(): void {
|
2025-11-06 21:11:22 +01:00
|
|
|
|
2025-09-21 15:48:13 +02:00
|
|
|
this.dragMouseLeaveHeaderListener = (event: Event) => {
|
2025-11-18 16:37:33 +01:00
|
|
|
const { targetColumn, mousePosition, originalElement, draggedClone: cloneElement } = (event as CustomEvent<IDragMouseLeaveHeaderEventPayload>).detail;
|
2025-09-21 15:48:13 +02:00
|
|
|
|
|
|
|
|
if (cloneElement)
|
|
|
|
|
cloneElement.style.display = '';
|
|
|
|
|
|
|
|
|
|
console.log('🚪 EventRendererManager: Received drag:mouseleave-header', {
|
2025-11-18 16:43:50 +01:00
|
|
|
targetColumn: targetColumn?.identifier,
|
2025-09-21 15:48:13 +02:00
|
|
|
originalElement: originalElement,
|
|
|
|
|
cloneElement: cloneElement
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
this.eventBus.on('drag:mouseleave-header', this.dragMouseLeaveHeaderListener);
|
2025-10-08 23:29:56 +02:00
|
|
|
}
|
2025-09-21 15:48:13 +02:00
|
|
|
|
2025-10-10 19:37:10 +02:00
|
|
|
private setupDragMouseEnterColumnListener(): void {
|
|
|
|
|
this.eventBus.on('drag:mouseenter-column', (event: Event) => {
|
2025-11-03 21:30:50 +01:00
|
|
|
const payload = (event as CustomEvent<IDragMouseEnterColumnEventPayload>).detail;
|
2025-10-10 19:37:10 +02:00
|
|
|
|
|
|
|
|
// Only handle if clone is an all-day event
|
|
|
|
|
if (!payload.draggedClone.hasAttribute('data-allday')) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
console.log('🎯 EventRendererManager: Received drag:mouseenter-column', {
|
|
|
|
|
targetColumn: payload.targetColumn,
|
|
|
|
|
snappedY: payload.snappedY,
|
|
|
|
|
calendarEvent: payload.calendarEvent
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Delegate to strategy for conversion
|
|
|
|
|
if (this.strategy.handleConvertAllDayToTimed) {
|
|
|
|
|
this.strategy.handleConvertAllDayToTimed(payload);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-08 23:29:56 +02:00
|
|
|
private setupResizeEndListener(): void {
|
2025-11-05 00:37:57 +01:00
|
|
|
this.eventBus.on('resize:end', async (event: Event) => {
|
2025-11-03 21:30:50 +01:00
|
|
|
const { eventId, element } = (event as CustomEvent<IResizeEndEventPayload>).detail;
|
2025-10-08 22:18:06 +02:00
|
|
|
|
|
|
|
|
const swpEvent = element as SwpEventElement;
|
2025-11-05 00:37:57 +01:00
|
|
|
await this.eventManager.updateEvent(eventId, {
|
2025-11-22 23:38:52 +01:00
|
|
|
start: swpEvent.start,
|
|
|
|
|
end: swpEvent.end
|
2025-10-08 22:18:06 +02:00
|
|
|
});
|
|
|
|
|
|
2025-11-22 23:38:52 +01:00
|
|
|
// Trigger full refresh to re-render with updated data
|
|
|
|
|
this.eventBus.emit(CoreEvents.REFRESH_REQUESTED, {});
|
2025-10-08 22:18:06 +02:00
|
|
|
});
|
2025-10-08 23:29:56 +02:00
|
|
|
}
|
2025-10-08 22:18:06 +02:00
|
|
|
|
2025-10-08 23:29:56 +02:00
|
|
|
private setupNavigationCompletedListener(): void {
|
2025-09-20 09:40:56 +02:00
|
|
|
this.eventBus.on(CoreEvents.NAVIGATION_COMPLETED, () => {
|
|
|
|
|
// Delegate to strategy if it handles navigation
|
|
|
|
|
if (this.strategy.handleNavigationCompleted) {
|
|
|
|
|
this.strategy.handleNavigationCompleted();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
2025-11-06 21:11:22 +01:00
|
|
|
|
2025-09-19 00:20:30 +02:00
|
|
|
|
2025-09-22 23:37:43 +02:00
|
|
|
private clearEvents(container?: HTMLElement): void {
|
|
|
|
|
this.strategy.clearEvents(container);
|
2025-08-16 00:51:12 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public refresh(container?: HTMLElement): void {
|
|
|
|
|
this.clearEvents(container);
|
2025-07-24 22:17:38 +02:00
|
|
|
}
|
|
|
|
|
}
|