Refactors all-day event conversion during drag and drop to use the event payload, improving code clarity and reducing redundancy. Removes unnecessary style settings and fixes column detection logic. Addresses an issue where event removal occurred before successful placement.
652 lines
No EOL
22 KiB
TypeScript
652 lines
No EOL
22 KiB
TypeScript
// Event rendering strategy interface and implementations
|
|
|
|
import { CalendarEvent } from '../types/CalendarTypes';
|
|
import { calendarConfig } from '../core/CalendarConfig';
|
|
import { DateCalculator } from '../utils/DateCalculator';
|
|
import { eventBus } from '../core/EventBus';
|
|
import { OverlapDetector, OverlapResult } from '../utils/OverlapDetector';
|
|
import { SwpEventElement } from '../elements/SwpEventElement';
|
|
import { TimeFormatter } from '../utils/TimeFormatter';
|
|
import { PositionUtils } from '../utils/PositionUtils';
|
|
import { DragOffset, StackLinkData } from '../types/DragDropTypes';
|
|
import { ColumnBounds } from '../utils/ColumnDetectionUtils';
|
|
import { DragColumnChangeEventPayload, DragMoveEventPayload, DragStartEventPayload } from '../types/EventTypes';
|
|
|
|
/**
|
|
* Interface for event rendering strategies
|
|
*/
|
|
export interface EventRendererStrategy {
|
|
renderEvents(events: CalendarEvent[], container: HTMLElement): void;
|
|
clearEvents(container?: HTMLElement): void;
|
|
handleDragStart?(payload: DragStartEventPayload): void;
|
|
handleDragMove?(payload: DragMoveEventPayload): void;
|
|
handleDragAutoScroll?(eventId: string, snappedY: number): void;
|
|
handleDragEnd?(eventId: string, originalElement: HTMLElement, draggedClone: HTMLElement, finalColumn: ColumnBounds, finalY: number): void;
|
|
handleEventClick?(eventId: string, originalElement: HTMLElement): void;
|
|
handleColumnChange?(payload: DragColumnChangeEventPayload): void;
|
|
handleNavigationCompleted?(): void;
|
|
}
|
|
|
|
/**
|
|
* Base class for event renderers with common functionality
|
|
*/
|
|
export abstract class BaseEventRenderer implements EventRendererStrategy {
|
|
protected dateCalculator: DateCalculator;
|
|
|
|
// Drag and drop state
|
|
private draggedClone: HTMLElement | null = null;
|
|
private originalEvent: HTMLElement | null = null;
|
|
|
|
// Resize manager
|
|
|
|
constructor(dateCalculator?: DateCalculator) {
|
|
if (!dateCalculator) {
|
|
DateCalculator.initialize(calendarConfig);
|
|
}
|
|
this.dateCalculator = dateCalculator || new DateCalculator();
|
|
}
|
|
|
|
// ============================================
|
|
// NEW OVERLAP DETECTION SYSTEM
|
|
// All new functions prefixed with new_
|
|
// ============================================
|
|
|
|
protected overlapDetector = new OverlapDetector();
|
|
|
|
/**
|
|
* Ny hovedfunktion til at håndtere event overlaps
|
|
* @param events - Events der skal renderes i kolonnen
|
|
* @param container - Container element at rendere i
|
|
*/
|
|
protected handleEventOverlaps(events: CalendarEvent[], container: HTMLElement): void {
|
|
if (events.length === 0) return;
|
|
|
|
if (events.length === 1) {
|
|
const element = this.renderEvent(events[0]);
|
|
container.appendChild(element);
|
|
return;
|
|
}
|
|
|
|
// Track hvilke events der allerede er blevet processeret
|
|
const processedEvents = new Set<string>();
|
|
|
|
// Gå gennem hvert event og find overlaps
|
|
events.forEach((currentEvent, index) => {
|
|
// Skip events der allerede er processeret som del af en overlap gruppe
|
|
if (processedEvents.has(currentEvent.id)) {
|
|
return;
|
|
}
|
|
|
|
const remainingEvents = events.slice(index + 1);
|
|
const overlappingEvents = this.overlapDetector.resolveOverlap(currentEvent, remainingEvents);
|
|
|
|
if (overlappingEvents.length > 0) {
|
|
// Der er overlaps - opret stack links
|
|
const result = this.overlapDetector.decorateWithStackLinks(currentEvent, overlappingEvents);
|
|
this.renderOverlappingEvents(result, container);
|
|
|
|
// Marker alle events i overlap gruppen som processeret
|
|
overlappingEvents.forEach(event => processedEvents.add(event.id));
|
|
} else {
|
|
// Intet overlap - render normalt
|
|
const element = this.renderEvent(currentEvent);
|
|
container.appendChild(element);
|
|
processedEvents.add(currentEvent.id);
|
|
}
|
|
});
|
|
}
|
|
|
|
|
|
/**
|
|
* Cleanup method for proper resource management
|
|
*/
|
|
public destroy(): void {
|
|
this.draggedClone = null;
|
|
this.originalEvent = null;
|
|
}
|
|
|
|
|
|
/**
|
|
* Apply common drag styling to an element
|
|
*/
|
|
private applyDragStyling(element: HTMLElement): void {
|
|
element.classList.add('dragging');
|
|
}
|
|
|
|
|
|
/**
|
|
* Update clone timestamp based on new position
|
|
*/
|
|
private updateCloneTimestamp(clone: HTMLElement, snappedY: number): void {
|
|
|
|
//important as events can pile up, so they will still fire after event has been converted to another rendered type
|
|
if (clone.dataset.allDay == "true") return;
|
|
|
|
const gridSettings = calendarConfig.getGridSettings();
|
|
const hourHeight = gridSettings.hourHeight;
|
|
const dayStartHour = gridSettings.dayStartHour;
|
|
const snapInterval = gridSettings.snapInterval;
|
|
|
|
// Calculate minutes from grid start (not from midnight)
|
|
const minutesFromGridStart = (snappedY / hourHeight) * 60;
|
|
|
|
// Add dayStartHour offset to get actual time
|
|
const actualStartMinutes = (dayStartHour * 60) + minutesFromGridStart;
|
|
|
|
// Snap to interval
|
|
const snappedStartMinutes = Math.round(actualStartMinutes / snapInterval) * snapInterval;
|
|
|
|
|
|
if(!clone.dataset.originalDuration)
|
|
throw new DOMException("missing clone.dataset.originalDuration")
|
|
|
|
const endTotalMinutes = snappedStartMinutes + parseInt(clone.dataset.originalDuration);
|
|
|
|
// Update visual time display only
|
|
const timeElement = clone.querySelector('swp-event-time');
|
|
if (timeElement) {
|
|
let startTime = TimeFormatter.formatTimeFromMinutes(snappedStartMinutes);
|
|
let endTime = TimeFormatter.formatTimeFromMinutes(endTotalMinutes);
|
|
timeElement.textContent = `${startTime} - ${endTime}`;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle drag start event
|
|
*/
|
|
public handleDragStart(payload: DragStartEventPayload): void {
|
|
|
|
this.originalEvent = payload.draggedElement;;
|
|
|
|
// Use the clone from the payload instead of creating a new one
|
|
this.draggedClone = payload.draggedClone;
|
|
|
|
if (this.draggedClone) {
|
|
// Apply drag styling
|
|
this.applyDragStyling(this.draggedClone);
|
|
|
|
// Add to current column's events layer (not directly to column)
|
|
const eventsLayer = payload.columnBounds?.element.querySelector('swp-events-layer');
|
|
if (eventsLayer) {
|
|
eventsLayer.appendChild(this.draggedClone);
|
|
}
|
|
}
|
|
|
|
// Make original semi-transparent
|
|
this.originalEvent.style.opacity = '0.3';
|
|
this.originalEvent.style.userSelect = 'none';
|
|
|
|
}
|
|
|
|
/**
|
|
* Handle drag move event
|
|
*/
|
|
public handleDragMove(payload: DragMoveEventPayload): void {
|
|
if (!this.draggedClone) return;
|
|
|
|
// Update position
|
|
this.draggedClone.style.top = (payload.snappedY - payload.mouseOffset.y) + 'px';
|
|
|
|
// Update timestamp display
|
|
this.updateCloneTimestamp(this.draggedClone, payload.snappedY);
|
|
|
|
}
|
|
|
|
/**
|
|
* Handle drag auto-scroll event
|
|
*/
|
|
public handleDragAutoScroll(eventId: string, snappedY: number): void {
|
|
if (!this.draggedClone) return;
|
|
|
|
// Update position directly using the calculated snapped position
|
|
this.draggedClone.style.top = snappedY + 'px';
|
|
|
|
// Update timestamp display
|
|
this.updateCloneTimestamp(this.draggedClone, snappedY);
|
|
}
|
|
|
|
/**
|
|
* Handle column change during drag
|
|
*/
|
|
public handleColumnChange(dragColumnChangeEvent: DragColumnChangeEventPayload): void {
|
|
if (!this.draggedClone) return;
|
|
|
|
const eventsLayer = dragColumnChangeEvent.newColumn.element.querySelector('swp-events-layer');
|
|
if (eventsLayer && this.draggedClone.parentElement !== eventsLayer) {
|
|
eventsLayer.appendChild(this.draggedClone);
|
|
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle drag end event
|
|
*/
|
|
public handleDragEnd(eventId: string, originalElement: HTMLElement, draggedClone: HTMLElement, finalColumn: ColumnBounds, finalY: number): void {
|
|
|
|
if (!draggedClone || !originalElement) {
|
|
console.warn('Missing draggedClone or originalElement');
|
|
return;
|
|
}
|
|
|
|
// Check om original event var del af en stack
|
|
const originalStackLink = originalElement.dataset.stackLink;
|
|
|
|
if (originalStackLink) {
|
|
try {
|
|
const stackData = JSON.parse(originalStackLink);
|
|
|
|
// Saml ALLE event IDs fra hele stack chain
|
|
const allStackEventIds: Set<string> = new Set();
|
|
|
|
// Recursive funktion til at traversere stack chain
|
|
const traverseStack = (linkData: StackLinkData, visitedIds: Set<string>) => {
|
|
if (linkData.prev && !visitedIds.has(linkData.prev)) {
|
|
visitedIds.add(linkData.prev);
|
|
const prevElement = document.querySelector(`swp-time-grid [data-event-id="${linkData.prev}"]`) as HTMLElement;
|
|
if (prevElement?.dataset.stackLink) {
|
|
try {
|
|
const prevLinkData = JSON.parse(prevElement.dataset.stackLink);
|
|
traverseStack(prevLinkData, visitedIds);
|
|
} catch (e) { }
|
|
}
|
|
}
|
|
|
|
if (linkData.next && !visitedIds.has(linkData.next)) {
|
|
visitedIds.add(linkData.next);
|
|
const nextElement = document.querySelector(`swp-time-grid [data-event-id="${linkData.next}"]`) as HTMLElement;
|
|
if (nextElement?.dataset.stackLink) {
|
|
try {
|
|
const nextLinkData = JSON.parse(nextElement.dataset.stackLink);
|
|
traverseStack(nextLinkData, visitedIds);
|
|
} catch (e) { }
|
|
}
|
|
}
|
|
};
|
|
|
|
// Start traversering fra original event's stackLink
|
|
traverseStack(stackData, allStackEventIds);
|
|
|
|
// Fjern original eventId da det bliver flyttet
|
|
allStackEventIds.delete(eventId);
|
|
|
|
// Find alle stack events og fjern dem
|
|
const stackEvents: CalendarEvent[] = [];
|
|
let container: HTMLElement | null = null;
|
|
|
|
allStackEventIds.forEach(id => {
|
|
const element = document.querySelector(`swp-time-grid [data-event-id="${id}"]`) as HTMLElement;
|
|
if (element) {
|
|
// Gem container reference fra første element
|
|
if (!container) {
|
|
container = element.closest('swp-events-layer') as HTMLElement;
|
|
}
|
|
|
|
const event = SwpEventElement.extractCalendarEventFromElement(element);
|
|
if (event) {
|
|
stackEvents.push(event);
|
|
}
|
|
|
|
// Fjern elementet
|
|
element.remove();
|
|
}
|
|
});
|
|
|
|
// Re-render stack events hvis vi fandt nogle
|
|
if (stackEvents.length > 0 && container) {
|
|
this.handleEventOverlaps(stackEvents, container);
|
|
}
|
|
} catch (e) {
|
|
console.warn('Failed to parse stackLink data:', e);
|
|
}
|
|
}
|
|
|
|
// Remove original event from any existing groups first
|
|
this.removeEventFromExistingGroups(originalElement);
|
|
|
|
// Fade out original
|
|
// TODO: this should be changed into a subscriber which only after a succesful placement is fired, not just mouseup as this can remove elements that are not placed.
|
|
this.fadeOutAndRemove(originalElement);
|
|
|
|
// Remove clone prefix and normalize clone to be a regular event
|
|
const cloneId = draggedClone.dataset.eventId;
|
|
if (cloneId && cloneId.startsWith('clone-')) {
|
|
draggedClone.dataset.eventId = cloneId.replace('clone-', '');
|
|
}
|
|
|
|
// Fully normalize the clone to be a regular event
|
|
draggedClone.classList.remove('dragging');
|
|
// Behold z-index hvis det er et stacked event
|
|
|
|
// Update dataset with new times after successful drop (only for timed events)
|
|
if (draggedClone.dataset.displayType !== 'allday') {
|
|
const newEvent = SwpEventElement.extractCalendarEventFromElement(draggedClone);
|
|
if (newEvent) {
|
|
draggedClone.dataset.start = newEvent.start.toISOString();
|
|
draggedClone.dataset.end = newEvent.end.toISOString();
|
|
}
|
|
}
|
|
|
|
// Detect overlaps with other events in the target column and reposition if needed
|
|
this.handleDragDropOverlaps(draggedClone, finalColumn);
|
|
|
|
// Fjern stackLink data fra dropped element
|
|
if (draggedClone.dataset.stackLink) {
|
|
delete draggedClone.dataset.stackLink;
|
|
}
|
|
|
|
// Clean up instance state (no longer needed since we get elements as parameters)
|
|
this.draggedClone = null;
|
|
this.originalEvent = null;
|
|
|
|
}
|
|
|
|
/**
|
|
* Handle event click (when drag threshold not reached)
|
|
*/
|
|
public handleEventClick(eventId: string, originalElement: HTMLElement): void {
|
|
console.log('handleEventClick:', eventId);
|
|
|
|
// Clean up any drag artifacts from failed drag attempt
|
|
if (this.draggedClone) {
|
|
this.draggedClone.classList.remove('dragging');
|
|
this.draggedClone.remove();
|
|
this.draggedClone = null;
|
|
}
|
|
|
|
// Restore original element styling if it was modified
|
|
if (this.originalEvent) {
|
|
this.originalEvent.style.opacity = '';
|
|
this.originalEvent.style.userSelect = '';
|
|
this.originalEvent = null;
|
|
}
|
|
|
|
// Emit a clean click event for other components to handle
|
|
eventBus.emit('event:clicked', {
|
|
eventId: eventId,
|
|
element: originalElement
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Handle navigation completed event
|
|
*/
|
|
public handleNavigationCompleted(): void {
|
|
// Default implementation - can be overridden by subclasses
|
|
}
|
|
|
|
/**
|
|
* Handle overlap detection and re-rendering after drag-drop
|
|
*/
|
|
private handleDragDropOverlaps(droppedElement: HTMLElement, targetColumn: ColumnBounds): void {
|
|
|
|
const eventsLayer = targetColumn.element.querySelector('swp-events-layer') as HTMLElement;
|
|
if (!eventsLayer) return;
|
|
|
|
// Convert dropped element to CalendarEvent with new position
|
|
const droppedEvent = SwpEventElement.extractCalendarEventFromElement(droppedElement);
|
|
if (!droppedEvent) return;
|
|
|
|
// Get existing events in the column (excluding the dropped element)
|
|
const existingEvents = this.getEventsInColumn(eventsLayer, droppedElement.dataset.eventId);
|
|
|
|
// Find overlaps with the dropped event
|
|
const overlappingEvents = this.overlapDetector.resolveOverlap(droppedEvent, existingEvents);
|
|
|
|
if (overlappingEvents.length > 0) {
|
|
// Remove only affected events from DOM
|
|
const affectedEventIds = [droppedEvent.id, ...overlappingEvents.map(e => e.id)];
|
|
eventsLayer.querySelectorAll('swp-event').forEach(el => {
|
|
const eventId = (el as HTMLElement).dataset.eventId;
|
|
if (eventId && affectedEventIds.includes(eventId)) {
|
|
el.remove();
|
|
}
|
|
});
|
|
|
|
// Re-render affected events with overlap handling
|
|
const affectedEvents = [droppedEvent, ...overlappingEvents];
|
|
this.handleEventOverlaps(affectedEvents, eventsLayer);
|
|
} else {
|
|
// Reset z-index for non-overlapping events
|
|
droppedElement.style.zIndex = '';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get all events in a column as CalendarEvent objects
|
|
*/
|
|
private getEventsInColumn(eventsLayer: HTMLElement, excludeEventId?: string): CalendarEvent[] {
|
|
const eventElements = eventsLayer.querySelectorAll('swp-event');
|
|
const events: CalendarEvent[] = [];
|
|
|
|
eventElements.forEach(el => {
|
|
const element = el as HTMLElement;
|
|
const eventId = element.dataset.eventId;
|
|
|
|
// Skip the excluded event (e.g., the dropped event)
|
|
if (excludeEventId && eventId === excludeEventId) {
|
|
return;
|
|
}
|
|
|
|
const event = SwpEventElement.extractCalendarEventFromElement(element);
|
|
if (event) {
|
|
events.push(event);
|
|
}
|
|
});
|
|
|
|
return events;
|
|
}
|
|
|
|
/**
|
|
* Remove event from any existing groups and cleanup empty containers
|
|
* In the new system, this is handled automatically by re-rendering overlaps
|
|
*/
|
|
private removeEventFromExistingGroups(eventElement: HTMLElement): void {
|
|
// With the new system, overlap relationships are recalculated on drop
|
|
// No need to manually track and remove from groups
|
|
}
|
|
|
|
|
|
/**
|
|
* Handle conversion to all-day event
|
|
*/
|
|
|
|
/**
|
|
* Fade out and remove element
|
|
*/
|
|
private fadeOutAndRemove(element: HTMLElement): void {
|
|
element.style.transition = 'opacity 0.3s ease-out';
|
|
element.style.opacity = '0';
|
|
|
|
setTimeout(() => {
|
|
element.remove();
|
|
}, 300);
|
|
}
|
|
|
|
|
|
renderEvents(events: CalendarEvent[], container: HTMLElement): void {
|
|
|
|
// Filter out all-day events - they should be handled by AllDayEventRenderer
|
|
const timedEvents = events.filter(event => !event.allDay);
|
|
|
|
console.log('🎯 EventRenderer: Filtering events', {
|
|
totalEvents: events.length,
|
|
timedEvents: timedEvents.length,
|
|
filteredOutAllDay: events.length - timedEvents.length
|
|
});
|
|
|
|
// Find columns in the specific container for regular events
|
|
const columns = this.getColumns(container);
|
|
|
|
columns.forEach(column => {
|
|
const columnEvents = this.getEventsForColumn(column, timedEvents);
|
|
|
|
const eventsLayer = column.querySelector('swp-events-layer');
|
|
if (eventsLayer) {
|
|
|
|
this.handleEventOverlaps(columnEvents, eventsLayer as HTMLElement);
|
|
}
|
|
});
|
|
}
|
|
|
|
// Abstract methods that subclasses must implement
|
|
protected abstract getColumns(container: HTMLElement): HTMLElement[];
|
|
protected abstract getEventsForColumn(column: HTMLElement, events: CalendarEvent[]): CalendarEvent[];
|
|
|
|
|
|
protected renderEvent(event: CalendarEvent): HTMLElement {
|
|
const swpEvent = SwpEventElement.fromCalendarEvent(event);
|
|
const eventElement = swpEvent.getElement();
|
|
|
|
// Setup resize handles on first mouseover only
|
|
eventElement.addEventListener('mouseover', () => {
|
|
if (eventElement.dataset.hasResizeHandlers !== 'true') {
|
|
eventElement.dataset.hasResizeHandlers = 'true';
|
|
}
|
|
}, { once: true });
|
|
|
|
return eventElement;
|
|
}
|
|
|
|
protected calculateEventPosition(event: CalendarEvent): { top: number; height: number } {
|
|
// Delegate to PositionUtils for centralized position calculation
|
|
return PositionUtils.calculateEventPosition(event.start, event.end);
|
|
}
|
|
|
|
clearEvents(container?: HTMLElement): void {
|
|
const selector = 'swp-event, swp-event-group';
|
|
const existingEvents = container
|
|
? container.querySelectorAll(selector)
|
|
: document.querySelectorAll(selector);
|
|
|
|
existingEvents.forEach(event => event.remove());
|
|
}
|
|
|
|
/**
|
|
* Renderer overlappende events baseret på OverlapResult
|
|
* @param result - OverlapResult med events og stack links
|
|
* @param container - Container at rendere i
|
|
*/
|
|
protected renderOverlappingEvents(result: OverlapResult, container: HTMLElement): void {
|
|
// Iterate direkte gennem stackLinks - allerede sorteret fra decorateWithStackLinks
|
|
for (const [eventId, stackLink] of result.stackLinks.entries()) {
|
|
const event = result.overlappingEvents.find(e => e.id === eventId);
|
|
if (!event) continue;
|
|
|
|
const element = this.renderEvent(event);
|
|
|
|
// Gem stack link information på DOM elementet
|
|
element.dataset.stackLink = JSON.stringify({
|
|
prev: stackLink.prev,
|
|
next: stackLink.next,
|
|
stackLevel: stackLink.stackLevel
|
|
});
|
|
|
|
// Check om dette event deler kolonne med foregående (samme start tid)
|
|
if (stackLink.prev) {
|
|
const prevEvent = result.overlappingEvents.find(e => e.id === stackLink.prev);
|
|
if (prevEvent && prevEvent.start.getTime() === event.start.getTime()) {
|
|
// Samme start tid - del kolonne (side by side)
|
|
this.new_applyColumnSharingStyling([element]);
|
|
} else {
|
|
// Forskellige start tider - stack vertikalt
|
|
this.new_applyStackStyling(element, stackLink.stackLevel);
|
|
}
|
|
} else {
|
|
// Første event i stack
|
|
this.new_applyStackStyling(element, stackLink.stackLevel);
|
|
}
|
|
|
|
container.appendChild(element);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Applicerer stack styling (margin-left og z-index)
|
|
* @param element - Event element
|
|
* @param stackLevel - Stack niveau
|
|
*/
|
|
protected new_applyStackStyling(element: HTMLElement, stackLevel: number): void {
|
|
element.style.marginLeft = `${stackLevel * 15}px`;
|
|
element.style.zIndex = `${100 + stackLevel}`;
|
|
}
|
|
|
|
/**
|
|
* Applicerer column sharing styling (flexbox)
|
|
* @param elements - Event elements der skal dele plads
|
|
*/
|
|
protected new_applyColumnSharingStyling(elements: HTMLElement[]): void {
|
|
elements.forEach(element => {
|
|
element.style.flex = '1';
|
|
element.style.minWidth = '50px';
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Date-based event renderer
|
|
*/
|
|
export class DateEventRenderer extends BaseEventRenderer {
|
|
constructor(dateCalculator?: DateCalculator) {
|
|
super(dateCalculator);
|
|
this.setupDragEventListeners();
|
|
}
|
|
|
|
/**
|
|
* Setup drag event listeners - placeholder method
|
|
*/
|
|
private setupDragEventListeners(): void {
|
|
// Drag event listeners are handled by EventRendererManager
|
|
// This method exists for compatibility
|
|
}
|
|
|
|
protected getColumns(container: HTMLElement): HTMLElement[] {
|
|
const columns = container.querySelectorAll('swp-day-column');
|
|
return Array.from(columns) as HTMLElement[];
|
|
}
|
|
|
|
protected getEventsForColumn(column: HTMLElement, events: CalendarEvent[]): CalendarEvent[] {
|
|
const columnDate = column.dataset.date;
|
|
if (!columnDate) {
|
|
return [];
|
|
}
|
|
|
|
const columnEvents = events.filter(event => {
|
|
const eventDateStr = DateCalculator.formatISODate(event.start);
|
|
const matches = eventDateStr === columnDate;
|
|
|
|
|
|
return matches;
|
|
});
|
|
|
|
return columnEvents;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Resource-based event renderer
|
|
*/
|
|
export class ResourceEventRenderer extends BaseEventRenderer {
|
|
protected getColumns(container: HTMLElement): HTMLElement[] {
|
|
const columns = container.querySelectorAll('swp-resource-column');
|
|
return Array.from(columns) as HTMLElement[];
|
|
}
|
|
|
|
protected getEventsForColumn(column: HTMLElement, events: CalendarEvent[]): CalendarEvent[] {
|
|
const resourceName = column.dataset.resource;
|
|
if (!resourceName) return [];
|
|
|
|
const columnEvents = events.filter(event => {
|
|
return event.resource?.name === resourceName;
|
|
});
|
|
|
|
return columnEvents;
|
|
}
|
|
|
|
// ============================================
|
|
// NEW OVERLAP DETECTION SYSTEM
|
|
// All new functions prefixed with new_
|
|
// ============================================
|
|
|
|
protected overlapDetector = new OverlapDetector();
|
|
|
|
} |