import { ViewConfig, GroupingConfig } from './ViewConfig'; import { RenderContext } from './RenderContext'; import { RendererRegistry } from './RendererRegistry'; import { IStoreRegistry } from './IGroupingStore'; 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 rendererRegistry: RendererRegistry, private storeRegistry: IStoreRegistry ) {} 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 = viewConfig.groupings.map(g => g.type); headerContainer.dataset.levels = types.join(' '); headerContainer.innerHTML = ''; columnContainer.innerHTML = ''; this.renderHierarchy(hierarchy, headerContainer, columnContainer); const eventRenderer = this.rendererRegistry.get('event'); eventRenderer?.render({ headerContainer, columnContainer, values: [], headerRow: viewConfig.groupings.length + 1, columnIndex: 1, colspan: 1 }); } private fetchAllData(groupings: GroupingConfig[]): Map { const result = new Map(); for (const g of groupings) { if (g.type === 'date') { result.set(g.type, { items: g.values.map(v => ({ id: v, data: { id: v } })), byParent: null }); continue; } const rawItems = this.storeRegistry.get(g.type).getByIds(g.values); const items = rawItems.map((item: any) => ({ id: item.id, data: item })); const byParent = g.parentKey ? this.groupBy(items, item => (item.data as any)[g.parentKey!]) : null; 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 items.map(item => ({ type: g.type, id: item.id, data: item.data, parentId, children: this.buildHierarchy(groupings, data, level + 1, item.id) })); } private groupBy(items: T[], keyFn: (item: T) => string): Map { const map = new Map(); for (const item of items) { const key = keyFn(item); if (key == null) continue; if (!map.has(key)) map.set(key, []); map.get(key)!.push(item); } return map; } 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.rendererRegistry.get(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); } } } }