import { from } from 'ts-linq-light'; import { ViewConfig, GroupingConfig } from './ViewConfig'; import { RenderContext } from './RenderContext'; import { IGroupingRenderer } from './IGroupingRenderer'; import { IGroupingStore } from './IGroupingStore'; import { EventRenderer } from '../features/event/EventRenderer'; interface HierarchyNode { type: string; id: string; data: unknown; parentId?: string; children: HierarchyNode[]; } interface GroupingData { items: { id: string; data: unknown }[]; byParent: Map | null; } export class CalendarOrchestrator { constructor( private renderers: IGroupingRenderer[], private stores: IGroupingStore[], private eventRenderer: EventRenderer ) {} private getRenderer(type: string): IGroupingRenderer | undefined { return from(this.renderers).firstOrDefault(r => r.type === type); } private getStore(type: string): IGroupingStore | undefined { return from(this.stores).firstOrDefault(s => s.type === type); } async render(viewConfig: ViewConfig, container: HTMLElement): Promise { const headerContainer = container.querySelector('swp-calendar-header') as HTMLElement; const columnContainer = container.querySelector('swp-day-columns') as HTMLElement; if (!headerContainer || !columnContainer) { throw new Error('Missing swp-calendar-header or swp-day-columns'); } const groupingData = this.fetchAllData(viewConfig.groupings); const hierarchy = this.buildHierarchy(viewConfig.groupings, groupingData); const totalColumns = this.countLeaves(hierarchy); container.style.setProperty('--grid-columns', String(totalColumns)); const types = from(viewConfig.groupings).select(g => g.type).toArray(); headerContainer.dataset.levels = types.join(' '); headerContainer.innerHTML = ''; columnContainer.innerHTML = ''; this.renderHierarchy(hierarchy, headerContainer, columnContainer); // Render events from IndexedDB const visibleDates = this.extractVisibleDates(viewConfig); await this.eventRenderer.render(container, visibleDates); } private extractVisibleDates(viewConfig: ViewConfig): string[] { return from(viewConfig.groupings).firstOrDefault(g => g.type === 'date')?.values || []; } private fetchAllData(groupings: GroupingConfig[]): Map { const result = new Map(); for (const g of groupings) { if (g.type === 'date') { result.set(g.type, { items: from(g.values).select(v => ({ id: v, data: { id: v } })).toArray(), byParent: null }); continue; } const store = this.getStore(g.type); if (!store) continue; const rawItems = store.getByIds(g.values); const items = from(rawItems).select((item: any) => ({ id: item.id, data: item })).toArray(); let byParent: Map | null = null; if (g.parentKey) { byParent = new Map(); const grouped = from(items) .where(item => (item.data as any)[g.parentKey!] != null) .groupBy(item => (item.data as any)[g.parentKey!] as string); for (const group of grouped) { byParent.set(group.key, group.toArray()); } } result.set(g.type, { items, byParent }); } return result; } private buildHierarchy( groupings: GroupingConfig[], data: Map, level = 0, parentId?: string ): HierarchyNode[] { if (level >= groupings.length) return []; const g = groupings[level]; const gData = data.get(g.type); if (!gData) return []; const items = parentId && gData.byParent ? gData.byParent.get(parentId) ?? [] : gData.items; return from(items).select(item => ({ type: g.type, id: item.id, data: item.data, parentId, children: this.buildHierarchy(groupings, data, level + 1, item.id) })).toArray(); } private countLeaves(nodes: HierarchyNode[]): number { return nodes.reduce((sum, n) => sum + (n.children.length ? this.countLeaves(n.children) : 1), 0); } private renderHierarchy( nodes: HierarchyNode[], headerContainer: HTMLElement, columnContainer: HTMLElement, headerRow = 1 ): void { for (const node of nodes) { const renderer = this.getRenderer(node.type); const colspan = this.countLeaves([node]) || 1; renderer?.render({ headerContainer, columnContainer, values: [node.id], headerRow, columnIndex: 0, // Not used - grid auto-places colspan, parentId: node.parentId } as RenderContext); if (node.children.length) { this.renderHierarchy(node.children, headerContainer, columnContainer, headerRow + 1); } } } }