Calendar/src/v2/core/CalendarOrchestrator.ts

154 lines
4.5 KiB
TypeScript
Raw Normal View History

2025-12-06 01:22:04 +01:00
import { ViewConfig, GroupingConfig } from './ViewConfig';
import { RenderContext } from './RenderContext';
import { RendererRegistry } from './RendererRegistry';
import { IGroupingStore } from './IGroupingStore';
2025-12-06 01:22:04 +01:00
interface HierarchyNode {
type: string;
id: string;
data: unknown;
parentId?: string;
children: HierarchyNode[];
}
interface GroupingData {
items: { id: string; data: unknown }[];
byParent: Map<string, { id: string; data: unknown }[]> | null;
}
export class CalendarOrchestrator {
constructor(
private rendererRegistry: RendererRegistry,
private stores: IGroupingStore[]
2025-12-06 01:22:04 +01:00
) {}
private getStore(type: string): IGroupingStore | undefined {
return this.stores.find(s => s.type === type);
}
2025-12-06 01:22:04 +01:00
async render(viewConfig: ViewConfig, container: HTMLElement): Promise<void> {
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<string, GroupingData> {
const result = new Map<string, GroupingData>();
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 store = this.getStore(g.type);
if (!store) continue;
const rawItems = store.getByIds(g.values);
2025-12-06 01:22:04 +01:00
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<string, GroupingData>,
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<T>(items: T[], keyFn: (item: T) => string): Map<string, T[]> {
const map = new Map<string, T[]>();
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);
}
}
}
}