// 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(); 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) { // 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 }; } } }