Calendar/docs/filter-template-spec.md
Janus C. H. Knudsen ad2df353b5 Adds workweek settings and dynamic view configuration
Introduces settings service for managing tenant-specific calendar configurations

Enables dynamic workweek presets with configurable work days
Improves view switching with enhanced UI components
Adds flexible calendar rendering based on tenant settings

Extends DateService to support workweek date generation
2025-12-15 22:24:32 +01:00

8.4 KiB

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.

// 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

// 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

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:

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:

<!-- 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:

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

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

// 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

// 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

// 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

// 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

// FORKERT
const key = `${resourceId}:${dateStr}`;

// KORREKT
const key = filterTemplate.buildKeyFromEvent(event);

2. Direkte Dataset Læsning for Matching

// FORKERT
const columnKey = column.dataset.columnKey;

// KORREKT
const columnKey = filterTemplate.buildKeyFromColumn(column);

3. Hardcoded Field Order

// FORKERT
const key = [event.resourceId, dateStr].join(':');

// KORREKT
// Lad FilterTemplate håndtere rækkefølge fra ViewConfig

4. Separate Key-Formater

// 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