Calendar/docs/V2-ARCHITECTURE.md

409 lines
12 KiB
Markdown
Raw Normal View History

# 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
```