Calendar/docs/filter-template-spec.md

343 lines
8.4 KiB
Markdown
Raw Permalink Normal View History

# 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
<!-- Simple view (kun date) -->
<swp-day-column data-date="2025-12-09"></swp-day-column>
<!-- Resource view (resource + date) -->
<swp-day-column
data-date="2025-12-09"
data-resource-id="EMP001">
</swp-day-column>
<!-- Team view (team + resource + date) -->
<swp-day-column
data-date="2025-12-09"
data-resource-id="EMP001"
data-team-id="team-1">
</swp-day-column>
```
### 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 |