# FilterTemplate & Grouping System Specification > **Version:** 1.0 > **Dato:** 2025-12-15 > **Status:** Godkendt ## Formål Dette dokument specificerer hvordan kalenderen matcher events til kolonner i alle view-typer (Simple, Dag, Resource, Team, Department). Formålet er at sikre konsistent opførsel og undgå fremtidige hacks. --- ## Kerneprincipper ### 1. Én sandhedskilde for key-format **FilterTemplate** er den ENESTE kilde til key-format for event-kolonne matching. ``` KORREKT: filterTemplate.buildKeyFromColumn(column) KORREKT: filterTemplate.buildKeyFromEvent(event) FORKERT: column.dataset.columnKey (bruger DateService-format) FORKERT: Manuel key-konstruktion med string concatenation ``` ### 2. Fields-rækkefølge bestemmer key-format Keys bygges fra `fields` array i samme rækkefølge som defineret i ViewConfig groupings. ```typescript // ViewConfig groupings: [resource, date] // Resultat: "EMP001:2025-12-09" // ViewConfig groupings: [date, resource] // Resultat: "2025-12-09:EMP001" ``` ### 3. Kolonner og events bruger SAMME template ```typescript // Opret template fra ViewConfig const filterTemplate = new FilterTemplate(dateService); for (const grouping of viewConfig.groupings) { if (grouping.idProperty) { filterTemplate.addField(grouping.idProperty, grouping.derivedFrom); } } // Brug til BÅDE kolonner og events const columnKey = filterTemplate.buildKeyFromColumn(column); const eventKey = filterTemplate.buildKeyFromEvent(event); const matches = columnKey === eventKey; ``` --- ## API Kontrakt ### FilterTemplate ```typescript class FilterTemplate { constructor(dateService: DateService, entityResolver?: IEntityResolver) // Tilføj felt til template addField(idProperty: string, derivedFrom?: string): this // Byg key fra kolonne (læser fra dataset) buildKeyFromColumn(column: HTMLElement): string // Byg key fra event (læser fra event properties) buildKeyFromEvent(event: ICalendarEvent): string // Convenience: matcher event mod kolonne matches(event: ICalendarEvent, column: HTMLElement): boolean } ``` ### Field Definition | Parameter | Type | Beskrivelse | |-----------|------|-------------| | `idProperty` | `string` | Property-navn på event ELLER dot-notation | | `derivedFrom` | `string?` | Kilde-property hvis værdi skal udledes | ### Dot-Notation For hierarkiske relationer bruges dot-notation: ```typescript idProperty: 'resource.teamId' // Betyder: event.resourceId → opslag i resource → teamId ``` **Convention:** `{entityType}.{property}` → foreignKey er `{entityType}Id` --- ## Kolonne Dataset Krav Kolonner (`swp-day-column`) SKAL have dataset-attributter for alle felter i template: ```html ``` ### Dataset Key Mapping | idProperty | Dataset Key | Eksempel | |------------|-------------|----------| | `date` | `data-date` | `"2025-12-09"` | | `resourceId` | `data-resource-id` | `"EMP001"` | | `resource.teamId` | `data-team-id` | `"team-1"` | **Regel:** Dot-notation bruger sidste segment som dataset-key. --- ## Event Property Krav Events SKAL have properties der matcher template fields: ```typescript interface ICalendarEvent { id: string; start: Date; // derivedFrom: 'start' → date key end: Date; resourceId?: string; // Direkte match // ... andre properties } ``` ### Derived Values | idProperty | derivedFrom | Transformation | |------------|-------------|----------------| | `date` | `start` | `Date → "YYYY-MM-DD"` | --- ## ViewConfig Groupings ### Struktur ```typescript interface GroupingConfig { type: string; // 'date', 'resource', 'team', 'department' values: string[]; // Synlige værdier idProperty?: string; // Felt til key-matching derivedFrom?: string; // Kilde hvis udledt belongsTo?: string; // Parent-child relation } ``` ### Eksempler ```typescript // Simple view groupings: [ { type: 'date', values: ['2025-12-09', ...], idProperty: 'date', derivedFrom: 'start' } ] // Resource view groupings: [ { type: 'resource', values: ['EMP001', 'EMP002'], idProperty: 'resourceId' }, { type: 'date', values: ['2025-12-09', ...], idProperty: 'date', derivedFrom: 'start' } ] // Team view groupings: [ { type: 'team', values: ['team-1', 'team-2'], idProperty: 'resource.teamId' }, { type: 'resource', values: ['EMP001', ...], idProperty: 'resourceId', belongsTo: 'team.resourceIds' }, { type: 'date', values: ['2025-12-09', ...], idProperty: 'date', derivedFrom: 'start' } ] ``` --- ## BelongsTo Resolution ### Formål `belongsTo` definerer parent-child relationer for nested groupings. ### Syntax ``` belongsTo: '{parentEntityType}.{childArrayProperty}' ``` ### Eksempel ```typescript // Team har resourceIds array { type: 'resource', belongsTo: 'team.resourceIds' } // Resolver: // 1. Hent team entities fra filter['team'] // 2. For hver team, læs team.resourceIds // 3. Byg map: { 'team-1': ['EMP001', 'EMP002'], 'team-2': ['EMP003'] } ``` ### Implementering ```typescript // CalendarOrchestrator.resolveBelongsTo() const [entityType, property] = belongsTo.split('.'); const service = entityServices.find(s => s.entityType === entityType); const entities = await service.getAll(); // Byg parent-child map ``` --- ## HeaderDrawerRenderer Regler ### Key Matching for AllDay Events ```typescript // KORREKT: Brug FilterTemplate private getVisibleColumnKeysFromDOM(): string[] { const columns = document.querySelectorAll('swp-day-column'); return Array.from(columns).map(col => this.filterTemplate.buildKeyFromColumn(col as HTMLElement) ); } // FORKERT: Læs dataset.columnKey direkte // (bruger DateService-format som ikke matcher FilterTemplate) ``` ### Layout Beregning 1. Hent synlige columnKeys via `getVisibleColumnKeysFromDOM()` 2. For hver event, byg key via `filterTemplate.buildKeyFromEvent(event)` 3. Find kolonne-index via `columnKeys.indexOf(eventKey)` 4. Beregn row via track-algoritme --- ## Anti-Patterns (UNDGÅ) ### 1. Manuel Key Konstruktion ```typescript // FORKERT const key = `${resourceId}:${dateStr}`; // KORREKT const key = filterTemplate.buildKeyFromEvent(event); ``` ### 2. Direkte Dataset Læsning for Matching ```typescript // FORKERT const columnKey = column.dataset.columnKey; // KORREKT const columnKey = filterTemplate.buildKeyFromColumn(column); ``` ### 3. Hardcoded Field Order ```typescript // FORKERT const key = [event.resourceId, dateStr].join(':'); // KORREKT // Lad FilterTemplate håndtere rækkefølge fra ViewConfig ``` ### 4. Separate Key-Formater ```typescript // FORKERT: DateService til kolonner, FilterTemplate til events DateService.buildColumnKey(segments) // "2025-12-09:EMP001" FilterTemplate.buildKeyFromEvent(e) // "EMP001:2025-12-09" // KORREKT: FilterTemplate til begge FilterTemplate.buildKeyFromColumn(col) FilterTemplate.buildKeyFromEvent(event) ``` --- ## Testcases ### TC1: Simple View Matching ``` Given: ViewConfig med [date] grouping When: Event har start=2025-12-09 Then: Event matcher kolonne med data-date="2025-12-09" ``` ### TC2: Resource View Matching ``` Given: ViewConfig med [resource, date] groupings When: Event har resourceId=EMP001, start=2025-12-09 Then: Event matcher kolonne med data-resource-id="EMP001" OG data-date="2025-12-09" ``` ### TC3: Team View Matching ``` Given: ViewConfig med [team, resource, date] groupings Resource EMP001 tilhører team-1 When: Event har resourceId=EMP001, start=2025-12-09 Then: Event matcher kolonne med data-team-id="team-1" OG data-resource-id="EMP001" OG data-date="2025-12-09" ``` ### TC4: Multi-Day Event ``` Given: Event spænder 2025-12-09 til 2025-12-11 When: HeaderDrawerRenderer beregner layout Then: Event vises fra kolonne 09 til kolonne 11 (inclusive) ``` --- ## Ændringslog | Version | Dato | Ændring | |---------|------|---------| | 1.0 | 2025-12-15 | Initial specifikation |