174 lines
5.4 KiB
TypeScript
174 lines
5.4 KiB
TypeScript
|
|
// All-day event rendering using factory pattern
|
||
|
|
|
||
|
|
import { CalendarEvent } from '../types/CalendarTypes';
|
||
|
|
import { SwpAllDayEventElement } from '../elements/SwpEventElement';
|
||
|
|
import { DateCalculator } from '../utils/DateCalculator';
|
||
|
|
|
||
|
|
/**
|
||
|
|
* AllDayEventRenderer - Handles rendering of all-day events in header row
|
||
|
|
* Uses factory pattern with SwpAllDayEventElement for clean DOM creation
|
||
|
|
*/
|
||
|
|
export class AllDayEventRenderer {
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Render all-day events in the header container
|
||
|
|
*/
|
||
|
|
public renderAllDayEvents(events: CalendarEvent[], container: HTMLElement): void {
|
||
|
|
const allDayEvents = events.filter(event => event.allDay);
|
||
|
|
|
||
|
|
// Find the calendar header
|
||
|
|
const calendarHeader = container.querySelector('swp-calendar-header');
|
||
|
|
if (!calendarHeader) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Find or create all-day container
|
||
|
|
let allDayContainer = calendarHeader.querySelector('swp-allday-container') as HTMLElement;
|
||
|
|
if (!allDayContainer) {
|
||
|
|
allDayContainer = document.createElement('swp-allday-container');
|
||
|
|
calendarHeader.appendChild(allDayContainer);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Clear existing events
|
||
|
|
allDayContainer.innerHTML = '';
|
||
|
|
|
||
|
|
if (allDayEvents.length === 0) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Build date to column mapping
|
||
|
|
const dayHeaders = calendarHeader.querySelectorAll('swp-day-header');
|
||
|
|
const dateToColumnMap = new Map<string, number>();
|
||
|
|
|
||
|
|
dayHeaders.forEach((header, index) => {
|
||
|
|
const dateStr = (header as HTMLElement).dataset.date;
|
||
|
|
if (dateStr) {
|
||
|
|
dateToColumnMap.set(dateStr, index + 1);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
// Calculate grid positioning for events
|
||
|
|
const eventPlacements = this.calculateEventPlacements(allDayEvents, dateToColumnMap);
|
||
|
|
|
||
|
|
// Render events using factory pattern
|
||
|
|
eventPlacements.forEach(({ event, gridColumn, gridRow }) => {
|
||
|
|
const eventDateStr = DateCalculator.formatISODate(event.start);
|
||
|
|
const swpAllDayEvent = SwpAllDayEventElement.fromCalendarEvent(event, eventDateStr);
|
||
|
|
const allDayElement = swpAllDayEvent.getElement();
|
||
|
|
|
||
|
|
// Apply grid positioning
|
||
|
|
(allDayElement as HTMLElement).style.gridColumn = gridColumn;
|
||
|
|
(allDayElement as HTMLElement).style.gridRow = gridRow.toString();
|
||
|
|
|
||
|
|
// Use event metadata for color if available
|
||
|
|
if (event.metadata?.color) {
|
||
|
|
(allDayElement as HTMLElement).style.backgroundColor = event.metadata.color;
|
||
|
|
}
|
||
|
|
|
||
|
|
allDayContainer.appendChild(allDayElement);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Calculate grid positioning for all-day events with overlap detection
|
||
|
|
*/
|
||
|
|
private calculateEventPlacements(events: CalendarEvent[], dateToColumnMap: Map<string, number>) {
|
||
|
|
// Calculate spans for each event
|
||
|
|
const eventItems = events.map(event => {
|
||
|
|
const eventDateStr = DateCalculator.formatISODate(event.start);
|
||
|
|
const endDateStr = DateCalculator.formatISODate(event.end);
|
||
|
|
|
||
|
|
const startColumn = dateToColumnMap.get(eventDateStr);
|
||
|
|
const endColumn = dateToColumnMap.get(endDateStr);
|
||
|
|
|
||
|
|
if (startColumn === undefined) {
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
|
||
|
|
const columnSpan = endColumn !== undefined && endColumn >= startColumn
|
||
|
|
? endColumn - startColumn + 1
|
||
|
|
: 1;
|
||
|
|
|
||
|
|
return {
|
||
|
|
event,
|
||
|
|
span: {
|
||
|
|
startColumn: startColumn,
|
||
|
|
columnSpan: columnSpan
|
||
|
|
}
|
||
|
|
};
|
||
|
|
}).filter(item => item !== null) as Array<{
|
||
|
|
event: CalendarEvent;
|
||
|
|
span: { startColumn: number; columnSpan: number };
|
||
|
|
}>;
|
||
|
|
|
||
|
|
// Calculate row placement to avoid overlaps
|
||
|
|
interface EventPlacement {
|
||
|
|
event: CalendarEvent;
|
||
|
|
gridColumn: string;
|
||
|
|
gridRow: number;
|
||
|
|
}
|
||
|
|
|
||
|
|
const eventPlacements: EventPlacement[] = [];
|
||
|
|
|
||
|
|
eventItems.forEach(eventItem => {
|
||
|
|
let assignedRow = 1;
|
||
|
|
|
||
|
|
// Find first available row
|
||
|
|
while (true) {
|
||
|
|
// Check if this row has any conflicts
|
||
|
|
const rowEvents = eventPlacements.filter(p => p.gridRow === assignedRow);
|
||
|
|
|
||
|
|
const hasOverlap = rowEvents.some(rowEvent => {
|
||
|
|
// Parse the existing grid column to check overlap
|
||
|
|
const existingSpan = this.parseGridColumn(rowEvent.gridColumn);
|
||
|
|
return this.spansOverlap(eventItem.span, existingSpan);
|
||
|
|
});
|
||
|
|
|
||
|
|
if (!hasOverlap) {
|
||
|
|
break; // Found available row
|
||
|
|
}
|
||
|
|
assignedRow++;
|
||
|
|
}
|
||
|
|
|
||
|
|
const gridColumn = eventItem.span.columnSpan > 1
|
||
|
|
? `${eventItem.span.startColumn} / span ${eventItem.span.columnSpan}`
|
||
|
|
: `${eventItem.span.startColumn}`;
|
||
|
|
|
||
|
|
eventPlacements.push({
|
||
|
|
event: eventItem.event,
|
||
|
|
gridColumn,
|
||
|
|
gridRow: assignedRow
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
return eventPlacements;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Check if two column spans overlap
|
||
|
|
*/
|
||
|
|
private spansOverlap(span1: { startColumn: number; columnSpan: number }, span2: { startColumn: number; columnSpan: number }): boolean {
|
||
|
|
const span1End = span1.startColumn + span1.columnSpan - 1;
|
||
|
|
const span2End = span2.startColumn + span2.columnSpan - 1;
|
||
|
|
|
||
|
|
return !(span1End < span2.startColumn || span2End < span1.startColumn);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Parse grid column string back to span object
|
||
|
|
*/
|
||
|
|
private parseGridColumn(gridColumn: string): { startColumn: number; columnSpan: number } {
|
||
|
|
if (gridColumn.includes('span')) {
|
||
|
|
const parts = gridColumn.split(' / span ');
|
||
|
|
return {
|
||
|
|
startColumn: parseInt(parts[0]),
|
||
|
|
columnSpan: parseInt(parts[1])
|
||
|
|
};
|
||
|
|
} else {
|
||
|
|
return {
|
||
|
|
startColumn: parseInt(gridColumn),
|
||
|
|
columnSpan: 1
|
||
|
|
};
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|