# Calendar V2 Architecture

## Oversigt

Calendar V2 er bygget med en event-driven arkitektur og et fleksibelt grouping-system der tillader forskellige kalendervisninger (dag, uge, ressource, team).

---

## Event System

### CoreEvents Katalog

Alle events er defineret i `src/v2/constants/CoreEvents.ts`.

#### Drag-Drop Events

| Event | Payload | Emitter | Subscribers |
|-------|---------|---------|-------------|
| `event:drag-start` | `IDragStartPayload` | DragDropManager | EdgeScrollManager |
| `event:drag-move` | `IDragMovePayload` | DragDropManager | EventRenderer |
| `event:drag-end` | `IDragEndPayload` | DragDropManager | EdgeScrollManager |
| `event:drag-cancel` | `IDragCancelPayload` | DragDropManager | EdgeScrollManager |
| `event:drag-column-change` | `IDragColumnChangePayload` | DragDropManager | EventRenderer |

#### Resize Events

| Event | Payload | Emitter | Subscribers |
|-------|---------|---------|-------------|
| `event:resize-start` | `IResizeStartPayload` | ResizeManager | - |
| `event:resize-end` | `IResizeEndPayload` | ResizeManager | - |

#### Edge Scroll Events

| Event | Payload | Emitter | Subscribers |
|-------|---------|---------|-------------|
| `edge-scroll:tick` | `{ scrollDelta: number }` | EdgeScrollManager | DragDropManager |
| `edge-scroll:started` | `{}` | EdgeScrollManager | - |
| `edge-scroll:stopped` | `{}` | EdgeScrollManager | - |

#### Lifecycle Events

| Event | Purpose |
|-------|---------|
| `core:initialized` | DI container klar |
| `core:ready` | Kalender fuldt initialiseret |
| `core:destroyed` | Cleanup færdig |

#### Data Events

| Event | Purpose |
|-------|---------|
| `data:loading` | Data fetch startet |
| `data:loaded` | Data fetch færdig |
| `data:error` | Data fetch fejlet |
| `entity:saved` | Entity gemt i IndexedDB |
| `entity:deleted` | Entity slettet fra IndexedDB |

#### View Events

| Event | Purpose |
|-------|---------|
| `view:changed` | View/grouping ændret |
| `view:rendered` | View rendering færdig |
| `events:rendered` | Events renderet til DOM |
| `grid:rendered` | Time grid renderet |

---

## Event Flows

### Drag-Drop Flow

```
pointerdown på event
    └── DragDropManager.handlePointerDown()
        └── Gem mouseDownPosition, capture pointer

pointermove (>5px)
    └── DragDropManager.initializeDrag()
        ├── Opret ghost clone (opacity 0.3)
        ├── Marker original med .dragging
        ├── EMIT: event:drag-start
        │   └── EdgeScrollManager starter scrollTick loop
        └── Start animateDrag() RAF loop

pointermove (under drag)
    ├── DragDropManager.updateDragTarget()
    │   ├── Detect kolonneændring
    │   │   └── EMIT: event:drag-column-change
    │   │       └── EventRenderer flytter element til ny kolonne
    │   └── Beregn targetY for smooth interpolation
    │
    └── DragDropManager.animateDrag() (RAF)
        ├── Interpoler currentY mod targetY (factor 0.3)
        ├── Opdater element.style.top
        └── EMIT: event:drag-move
            └── EventRenderer.updateDragTimestamp()
                └── Beregn snapped tid og opdater visning

EdgeScrollManager.scrollTick() (parallel RAF)
    ├── Beregn velocity baseret på museafstand til kanter
    │   - Inner zone (0-50px): 640 px/sek
    │   - Outer zone (50-100px): 140 px/sek
    ├── Scroll viewport: scrollTop += scrollDelta
    └── EMIT: edge-scroll:tick
        └── DragDropManager kompenserer element position

pointerup
    └── DragDropManager.handlePointerUp()
        ├── Snap currentY til grid (15-min intervaller)
        ├── Fjern ghost element
        ├── EMIT: event:drag-end
        │   └── EdgeScrollManager stopper scroll
        └── Persist ændringer til IndexedDB
```

---

## Grouping System

### ViewConfig

```typescript
interface ViewConfig {
  templateId: string;           // 'day' | 'simple' | 'resource' | 'team'
  groupings: GroupingConfig[];
}

interface GroupingConfig {
  type: string;                 // 'date' | 'resource' | 'team'
  values: string[];             // IDs der skal vises
}
```

### Sådan bestemmer groupings strukturen

1. **Kolonneantal**: Produkt af alle grouping dimensioner
   - Eksempel: `5 datoer × 2 ressourcer = 10 kolonner`

2. **Header layout**: CSS grid bruger `data-levels` til at stakke headers
   - `data-levels="date"` → 1 header række
   - `data-levels="resource date"` → 2 header rækker
   - `data-levels="team resource date"` → 3 header rækker

3. **Renderer selektion**: Grouping types matches til tilgængelige renderers

### Konfigurationseksempler

#### Simple Date View (3 dage, ingen ressourcer)

```typescript
{
  templateId: 'simple',
  groupings: [
    { type: 'date', values: ['2024-01-15', '2024-01-16', '2024-01-17'] }
  ]
}
```

**Resultat:** 3 kolonner, 1 header række

```
┌─── Date1 ───┬─── Date2 ───┬─── Date3 ───┐
│             │             │             │
```

#### Resource View (2 ressourcer, 3 dage)

```typescript
{
  templateId: 'resource',
  groupings: [
    { type: 'resource', values: ['EMP001', 'EMP002'] },
    { type: 'date', values: ['2024-01-15', '2024-01-16', '2024-01-17'] }
  ]
}
```

**Resultat:** 6 kolonner, 2 header rækker

```
┌───── Resource1 (span 3) ─────┬───── Resource2 (span 3) ─────┐
├─── D1 ───┬─── D2 ───┬─── D3 ─┼─── D1 ───┬─── D2 ───┬─── D3 ─┤
│          │          │        │          │          │        │
```

#### Team View (2 teams, 2 ressourcer, 3 dage)

```typescript
{
  templateId: 'team',
  groupings: [
    { type: 'team', values: ['team1', 'team2'] },
    { type: 'resource', values: ['res1', 'res2'] },
    { type: 'date', values: ['2024-01-15', '2024-01-16', '2024-01-17'] }
  ]
}
```

**Resultat:** 12 kolonner, 3 header rækker

```
┌─────────── Team1 (span 6) ───────────┬─────────── Team2 (span 6) ───────────┐
├───── Res1 (span 3) ──┬── Res2 (3) ───┼───── Res1 (span 3) ──┬── Res2 (3) ───┤
├── D1 ─┬── D2 ─┬── D3 ┼── D1 ─┬── D2 ─┼── D1 ─┬── D2 ─┬── D3 ┼── D1 ─┬── D2 ─┤
│       │       │      │       │       │       │       │      │       │       │
```

---

## Header Rendering

### data-levels Attribut

`swp-calendar-header` modtager `data-levels` som bestemmer header row layout:

```typescript
// I CalendarOrchestrator.render()
const levels = viewConfig.groupings.map(g => g.type).join(' ');
headerContainer.dataset.levels = levels;  // "resource date" eller "team resource date"
```

### CSS Grid Styling

```css
swp-calendar-header[data-levels="date"] > swp-day-header {
  grid-row: 1;
}

swp-calendar-header[data-levels="resource date"] {
  > swp-resource-header { grid-row: 1; }
  > swp-day-header { grid-row: 2; }
}

swp-calendar-header[data-levels="team resource date"] {
  > swp-team-header { grid-row: 1; }
  > swp-resource-header { grid-row: 2; }
  > swp-day-header { grid-row: 3; }
}
```

### Rendering Pipeline

1. Renderers eksekveres i den rækkefølge de står i `viewConfig.groupings`
2. Hver renderer APPENDER sine headers til `headerContainer`
3. CSS bruger `data-levels` + element type til at positionere i korrekt række

---

## Renderers

### DateRenderer

```typescript
class DateRenderer implements IRenderer {
  readonly type = 'date';

  render(context: IRenderContext): void {
    // For HVER ressource (eller én gang hvis ingen):
    //   For HVER dato i filter['date']:
    //     Opret swp-day-header + swp-day-column
    //     Sæt dataset.date & dataset.resourceId
  }
}
```

### ResourceRenderer

```typescript
class ResourceRenderer implements IRenderer {
  readonly type = 'resource';

  async render(context: IRenderContext): Promise<void> {
    // Load IResource[] fra ResourceService
    // For HVER ressource:
    //   Opret swp-resource-header
    //   Sæt grid-column: span ${dateCount}
  }
}
```

### TeamRenderer

```typescript
class TeamRenderer implements IRenderer {
  readonly type = 'team';

  render(context: IRenderContext): void {
    // For HVERT team:
    //   Tæl ressourcer der tilhører team
    //   Opret swp-team-header
    //   Sæt grid-column: span ${colspan}
  }
}
```

---

## Grid Konfiguration

### IGridConfig

```typescript
interface IGridConfig {
  hourHeight: number;      // pixels per time (fx 60)
  dayStartHour: number;    // fx 6 (06:00)
  dayEndHour: number;      // fx 18 (18:00)
  snapInterval: number;    // minutter (fx 15)
}
```

### Position Beregning

```typescript
function calculateEventPosition(start: Date, end: Date, config: IGridConfig): EventPosition {
  const startMinutes = start.getHours() * 60 + start.getMinutes();
  const endMinutes = end.getHours() * 60 + end.getMinutes();

  const dayStartMinutes = config.dayStartHour * 60;
  const minuteHeight = config.hourHeight / 60;

  return {
    top: (startMinutes - dayStartMinutes) * minuteHeight,
    height: (endMinutes - startMinutes) * minuteHeight
  };
}
```

**Eksempel:** Event 09:00-10:00 med dayStartHour=6, hourHeight=60
- startMinutes = 540, dayStartMinutes = 360
- top = (540 - 360) × 1 = **180px**
- height = (600 - 540) × 1 = **60px**

---

## Z-Index Stack

```
z-index: 10  ← Events (interaktive, draggable)
z-index: 5   ← Unavailable zones (visuel, pointer-events: none)
z-index: 2   ← Time linjer (baggrund)
z-index: 1   ← Kvarter linjer (baggrund)
z-index: 0   ← Grid baggrund
```

---

## Komponenter

| Komponent | Ansvar | Fil |
|-----------|--------|-----|
| **CalendarOrchestrator** | Orkestrerer renderer pipeline | `core/CalendarOrchestrator.ts` |
| **DateRenderer** | Opretter dag-headers og kolonner | `features/date/DateRenderer.ts` |
| **ResourceRenderer** | Opretter ressource-headers | `features/resource/ResourceRenderer.ts` |
| **TeamRenderer** | Opretter team-headers | `features/team/TeamRenderer.ts` |
| **EventRenderer** | Renderer events med positioner | `features/event/EventRenderer.ts` |
| **ScheduleRenderer** | Renderer unavailable zoner | `features/schedule/ScheduleRenderer.ts` |
| **DragDropManager** | Håndterer drag-drop | `managers/DragDropManager.ts` |
| **ResizeManager** | Håndterer event resize | `managers/ResizeManager.ts` |
| **EdgeScrollManager** | Auto-scroll ved kanter | `managers/EdgeScrollManager.ts` |
| **ScrollManager** | Synkroniserer scroll | `core/ScrollManager.ts` |
| **EventBus** | Central event dispatcher | `core/EventBus.ts` |
| **DateService** | Dato formatering og beregning | `core/DateService.ts` |

---

## Filer

```
src/v2/
├── constants/
│   └── CoreEvents.ts          # Alle event konstanter
├── core/
│   ├── CalendarOrchestrator.ts
│   ├── DateService.ts
│   ├── EventBus.ts
│   ├── IGridConfig.ts
│   ├── IGroupingRenderer.ts
│   ├── RenderBuilder.ts
│   ├── ScrollManager.ts
│   └── ViewConfig.ts
├── features/
│   ├── date/
│   │   └── DateRenderer.ts
│   ├── event/
│   │   └── EventRenderer.ts
│   ├── resource/
│   │   └── ResourceRenderer.ts
│   ├── schedule/
│   │   └── ScheduleRenderer.ts
│   └── team/
│       └── TeamRenderer.ts
├── managers/
│   ├── DragDropManager.ts
│   └── EdgeScrollManager.ts
├── storage/
│   ├── BaseEntityService.ts
│   ├── IndexedDBContext.ts
│   └── events/
│       ├── EventService.ts
│       └── EventStore.ts
├── types/
│   ├── CalendarTypes.ts
│   ├── DragTypes.ts
│   └── ScheduleTypes.ts
└── utils/
    └── PositionUtils.ts
```
