Calendar/wwwroot/js/features/all-day/AllDayCoordinator.js
2026-02-03 00:02:25 +01:00

168 lines
No EOL
7.8 KiB
JavaScript

/**
* AllDayCoordinator - Orchestrates all-day event functionality
*
* NO STATE - Only coordinates between services
* - Listens to EventBus events
* - Delegates to specialized services
* - Manages service lifecycle
*/
import { eventBus } from '../../core/EventBus';
import { ALL_DAY_CONSTANTS } from '../../configurations/CalendarConfig';
import { AllDayLayoutEngine } from '../../utils/AllDayLayoutEngine';
import { CoreEvents } from '../../constants/CoreEvents';
import { AllDayDomReader } from './AllDayDomReader';
import { ColumnDetectionUtils } from '../../utils/ColumnDetectionUtils';
/**
* AllDayCoordinator - Orchestrates all-day event functionality
* Replaces the monolithic AllDayManager with a coordinated service architecture
*/
export class AllDayCoordinator {
constructor(eventManager, allDayEventRenderer, dateService, heightService, collapseService, dragService) {
this.eventManager = eventManager;
this.allDayEventRenderer = allDayEventRenderer;
this.dateService = dateService;
this.heightService = heightService;
this.collapseService = collapseService;
this.dragService = dragService;
// Sync CSS variable with TypeScript constant
document.documentElement.style.setProperty('--single-row-height', `${ALL_DAY_CONSTANTS.EVENT_HEIGHT}px`);
this.setupEventListeners();
}
/**
* Setup event listeners and delegate to services
*/
setupEventListeners() {
// Timed → All-day conversion
eventBus.on('drag:mouseenter-header', (event) => {
const payload = event.detail;
if (payload.draggedClone.hasAttribute('data-allday'))
return;
console.log('🔄 AllDayCoordinator: Received drag:mouseenter-header', {
targetDate: payload.targetColumn,
originalElementId: payload.originalElement?.dataset?.eventId,
originalElementTag: payload.originalElement?.tagName
});
this.dragService.handleConvertToAllDay(payload);
// Recalculate layouts and height after timed → all-day conversion
this.recalculateLayoutsAndHeight();
});
eventBus.on('drag:mouseleave-header', (event) => {
const { originalElement } = event.detail;
console.log('🚪 AllDayCoordinator: Received drag:mouseleave-header', {
originalElementId: originalElement?.dataset?.eventId
});
});
// All-day drag start
eventBus.on('drag:start', (event) => {
const payload = event.detail;
if (!payload.draggedClone?.hasAttribute('data-allday'))
return;
this.allDayEventRenderer.handleDragStart(payload);
});
// All-day column change
eventBus.on('drag:column-change', (event) => {
const payload = event.detail;
if (!payload.draggedClone?.hasAttribute('data-allday'))
return;
this.dragService.handleColumnChange(payload);
});
// Drag end
eventBus.on('drag:end', (event) => {
const dragEndPayload = event.detail;
console.log('🎯 AllDayCoordinator: drag:end received', {
target: dragEndPayload.target,
originalElementTag: dragEndPayload.originalElement?.tagName,
hasAllDayAttribute: dragEndPayload.originalElement?.hasAttribute('data-allday'),
eventId: dragEndPayload.originalElement?.dataset.eventId
});
// Handle all-day → all-day drops (within header)
if (dragEndPayload.target === 'swp-day-header') {
console.log('✅ AllDayCoordinator: Handling all-day → all-day drop');
this.dragService.handleDragEnd(dragEndPayload);
// Recalculate layouts and height after all-day → all-day repositioning
this.recalculateLayoutsAndHeight();
return;
}
// Handle all-day → timed conversion (dropped in column)
if (dragEndPayload.target === 'swp-day-column' &&
dragEndPayload.originalElement?.hasAttribute('data-allday')) {
const eventId = dragEndPayload.originalElement.dataset.eventId;
console.log('🔄 AllDayCoordinator: All-day → timed conversion', {
eventId
});
// Remove event element from DOM
const container = AllDayDomReader.getAllDayContainer();
const eventElement = container?.querySelector(`[data-event-id="${eventId}"]`);
if (eventElement) {
eventElement.remove();
}
// Recalculate layouts and height after event removal
this.recalculateLayoutsAndHeight();
}
});
// Drag cancelled
eventBus.on('drag:cancelled', (event) => {
const { draggedElement, reason } = event.detail;
console.log('🚫 AllDayCoordinator: Drag cancelled', {
eventId: draggedElement?.dataset?.eventId,
reason
});
});
// Header ready - render all-day events
eventBus.on('header:ready', async (event) => {
const headerReadyEventPayload = event.detail;
const startDate = new Date(headerReadyEventPayload.headerElements.at(0).date);
const endDate = new Date(headerReadyEventPayload.headerElements.at(-1).date);
const events = await this.eventManager.getEventsForPeriod(startDate, endDate);
// Filter for all-day events
const allDayEvents = events.filter(event => event.allDay);
// Calculate layouts
const layouts = this.calculateAllDayEventsLayout(allDayEvents, headerReadyEventPayload.headerElements);
// Render events
this.allDayEventRenderer.renderAllDayEventsForPeriod(layouts);
// Initialize collapse/expand UI and calculate height
this.collapseService.initializeUI();
});
// View changed
eventBus.on(CoreEvents.VIEW_CHANGED, (event) => {
this.allDayEventRenderer.handleViewChanged(event);
});
}
/**
* Calculate layout for ALL all-day events using AllDayLayoutEngine
*/
calculateAllDayEventsLayout(events, dayHeaders) {
// Initialize layout engine with provided week dates
const layoutEngine = new AllDayLayoutEngine(dayHeaders.map(column => column.date));
// Calculate layout for all events together
return layoutEngine.calculateLayout(events);
}
/**
* Recalculate layouts and update height
* Called after events are added/removed/moved in all-day area
* Uses AllDayLayoutEngine to optimally reorganize all events
*/
recalculateLayoutsAndHeight() {
// 1. Read current events from DOM
const events = AllDayDomReader.getEventsAsData();
const weekDates = ColumnDetectionUtils.getColumns();
// 2. Calculate optimal layouts using greedy algorithm
const layouts = this.calculateAllDayEventsLayout(events, weekDates);
// 3. Apply layouts to DOM
this.dragService.applyLayoutUpdates(layouts);
// 4. Calculate max row from NEW layouts
const maxRow = layouts.length > 0 ? Math.max(...layouts.map(l => l.row)) : 0;
// 5. Check if collapsed state should be maintained
const isExpanded = AllDayDomReader.isExpanded();
const targetRows = isExpanded ? maxRow : Math.min(maxRow, ALL_DAY_CONSTANTS.MAX_COLLAPSED_ROWS);
// 6. Animate height to target
this.heightService.animateToRows(targetRows);
}
/**
* Public API for collapsing all-day row
*/
collapseAllDayRow() {
this.heightService.collapseAllDayRow();
}
}
//# sourceMappingURL=AllDayCoordinator.js.map