diff --git a/.claude/settings.local.json b/.claude/settings.local.json
index 096895c..c19c12e 100644
--- a/.claude/settings.local.json
+++ b/.claude/settings.local.json
@@ -12,7 +12,8 @@
"Bash(npm test)",
"Bash(cat:*)",
"Bash(npm run test:run:*)",
- "Bash(npx tsc)"
+ "Bash(npx tsc)",
+ "Bash(npx tsc:*)"
],
"deny": []
}
diff --git a/workweek-preset-sequence-AFTER.md b/workweek-preset-sequence-AFTER.md
deleted file mode 100644
index 36b80a1..0000000
--- a/workweek-preset-sequence-AFTER.md
+++ /dev/null
@@ -1,81 +0,0 @@
-# Workweek Preset Click Sequence Diagram - EFTER REFAKTORERING
-
-Dette diagram viser hvad der sker når brugeren klikker på en workweek preset knap EFTER refaktoreringen.
-
-```mermaid
-sequenceDiagram
- actor User
- participant HTML as swp-preset-button
- participant WPM as WorkweekPresetsManager
- participant Config as Configuration
- participant EventBus
- participant CM as ConfigManager
- participant GM as GridManager
- participant GR as GridRenderer
- participant HM as HeaderManager
- participant HR as HeaderRenderer
- participant DOM
-
- User->>HTML: Click på preset button
(data-workweek="compressed")
- HTML->>WPM: click event
-
- Note over WPM: setupButtonListeners handler
- WPM->>WPM: changePreset("compressed")
-
- WPM->>Config: Validate WORK_WEEK_PRESETS["compressed"]
- Note over WPM: Guard: if (!WORK_WEEK_PRESETS[presetId]) return
-
- WPM->>Config: Check if (presetId === currentWorkWeek)
- Note over WPM: Guard: No change? Return early
-
- WPM->>Config: config.currentWorkWeek = "compressed"
- Note over Config: State updated: "standard" → "compressed"
-
- WPM->>WPM: updateButtonStates()
- WPM->>DOM: querySelectorAll('swp-preset-button')
- WPM->>DOM: Update data-active attributes
- Note over DOM: Compressed button får active
Andre mister active
-
- WPM->>EventBus: emit(WORKWEEK_CHANGED, payload)
- Note over EventBus: Event: 'workweek:changed'
Payload: {
workWeekId: "compressed",
previousWorkWeekId: "standard",
settings: { totalDays: 4, ... }
}
-
- par Parallel Event Subscribers
- EventBus->>CM: WORKWEEK_CHANGED event
- Note over CM: setupEventListeners listener
- CM->>CM: syncWorkweekCSSVariables(settings)
- CM->>DOM: setProperty('--grid-columns', '4')
- Note over DOM: CSS variable opdateret
-
- and
- EventBus->>GM: WORKWEEK_CHANGED event
- Note over GM: subscribeToEvents listener
- GM->>GM: render()
- GM->>GR: renderGrid(container, currentDate)
-
- alt Grid allerede eksisterer
- GR->>GR: updateGridContent()
- GR->>DOM: Update 4 columns (Mon-Thu)
- else First render
- GR->>GR: createCompleteGridStructure()
- GR->>DOM: Create 4 columns (Mon-Thu)
- end
-
- GM->>EventBus: emit(GRID_RENDERED)
-
- and
- EventBus->>CalendarManager: WORKWEEK_CHANGED event
- Note over CalendarManager: handleWorkweekChange listener
- CalendarManager->>EventBus: emit('workweek:header-update')
-
- EventBus->>HM: 'workweek:header-update' event
- Note over HM: setupNavigationListener
- HM->>HM: updateHeader(currentDate)
- HM->>HR: render(context)
- HR->>Config: getWorkWeekSettings()
- Config-->>HR: { totalDays: 4, workDays: [1,2,3,4] }
- HR->>DOM: Render 4 day headers
(Mon, Tue, Wed, Thu)
- end
-
- Note over DOM: Grid viser nu kun
Man-Tor (4 dage)
med opdaterede headers
-
- DOM-->>User: Visuelt feedback:
4-dages arbejdsuge
diff --git a/workweek-preset-sequence.md b/workweek-preset-sequence.md
deleted file mode 100644
index 931ba6c..0000000
--- a/workweek-preset-sequence.md
+++ /dev/null
@@ -1,72 +0,0 @@
-# Workweek Preset Click Sequence Diagram
-
-Dette diagram viser hvad der sker når brugeren klikker på en workweek preset knap (f.eks. "Mon-Fri", "Mon-Thu", etc.)
-
-```mermaid
-sequenceDiagram
- actor User
- participant HTML as swp-preset-button
- participant VM as ViewManager
- participant Config as Configuration
- participant CM as ConfigManager
- participant EventBus
- participant GM as GridManager
- participant GR as GridRenderer
- participant HM as HeaderManager
- participant HR as HeaderRenderer
- participant DOM
-
- User->>HTML: Click på preset button
(data-workweek="compressed")
- HTML->>VM: click event
-
- Note over VM: setupButtonGroup handler
- VM->>VM: getAttribute('data-workweek')
→ "compressed"
- VM->>VM: changeWorkweek("compressed")
-
- VM->>Config: setWorkWeek("compressed")
- Note over Config: Opdaterer currentWorkWeek
og workweek settings
-
- VM->>CM: updateCSSProperties(config)
- Note over CM: Opdaterer CSS custom properties
- CM->>DOM: setProperty('--grid-columns', '4')
- CM->>DOM: setProperty('--hour-height', '80px')
- CM->>DOM: setProperty('--day-start-hour', '6')
- CM->>DOM: setProperty('--work-start-hour', '8')
- Note over DOM: CSS grid layout opdateres
-
- VM->>VM: updateAllButtons()
- VM->>DOM: Update data-active attributter
på alle preset buttons
- Note over DOM: Compressed knap får
data-active="true"
Andre knapper mister active
-
- VM->>Config: getWorkWeekSettings()
- Config-->>VM: { id: 'compressed',
workDays: [1,2,3,4],
totalDays: 4 }
-
- VM->>EventBus: emit(WORKWEEK_CHANGED, payload)
- Note over EventBus: Event: 'workweek:changed'
Payload: { workWeekId, settings }
-
- EventBus->>GM: WORKWEEK_CHANGED event
- Note over GM: Listener setup i subscribeToEvents()
- GM->>GM: render()
- GM->>GR: renderGrid(container, currentDate)
-
- alt First render (empty grid)
- GR->>GR: createCompleteGridStructure()
- GR->>DOM: Create time axis
- GR->>DOM: Create grid container
- GR->>DOM: Create 4 columns (Mon-Thu)
- else Update existing grid
- GR->>GR: updateGridContent()
- GR->>DOM: Update existing columns
- end
-
- GM->>EventBus: emit(GRID_RENDERED)
-
- EventBus->>HM: WORKWEEK_CHANGED event
- Note over HM: Via 'workweek:header-update'
from CalendarManager
- HM->>HM: updateHeader(currentDate)
- HM->>HR: render(context)
- HR->>DOM: Update header med 4 dage
(Mon, Tue, Wed, Thu)
-
- Note over DOM: Grid viser nu kun
Man-Tor (4 dage)
med opdaterede headers
-
- DOM-->>User: Visuelt feedback:
4-dages arbejdsuge
diff --git a/workweek-refactoring-comparison.md b/workweek-refactoring-comparison.md
deleted file mode 100644
index 61ab8a0..0000000
--- a/workweek-refactoring-comparison.md
+++ /dev/null
@@ -1,394 +0,0 @@
-# Workweek Presets Refactoring - FØR vs EFTER Sammenligning
-
-## Side-by-Side Comparison
-
-| Aspekt | FØR Refaktorering | EFTER Refaktorering | Forbedring |
-|--------|-------------------|---------------------|------------|
-| **Ansvarlig Manager** | ViewManager | WorkweekPresetsManager | ✅ Dedicated manager per UI element |
-| **Button Setup** | ViewManager.setupButtonGroup() | WorkweekPresetsManager.setupButtonListeners() | ✅ Isolated ansvar |
-| **State Management** | ViewManager + Configuration | Configuration (via WorkweekPresetsManager) | ✅ Simplere |
-| **CSS Opdatering** | ViewManager kalder ConfigManager.updateCSSProperties() | ConfigManager lytter til WORKWEEK_CHANGED event | ✅ Event-drevet, løsere kobling |
-| **Config Mutation** | ViewManager → config.setWorkWeek() | WorkweekPresetsManager → config.currentWorkWeek = | ⚠️ Direkte mutation |
-| **ViewManager Ansvar** | View selector + Workweek presets | Kun view selector | ✅ Single Responsibility |
-| **Code Duplication** | 35% (static + instance CSS metoder) | 0% | ✅ DRY princip |
-
----
-
-## Kode Sammenligning
-
-### 1. Button Click Handling
-
-#### FØR - ViewManager
-```typescript
-// ViewManager.ts
-private setupButtonHandlers(): void {
- this.setupButtonGroup('swp-view-button[data-view]', 'data-view', (value) => {
- if (this.isValidView(value)) {
- this.changeView(value as CalendarView);
- }
- });
-
- // WORKWEEK LOGIK HER - forkert ansvar
- this.setupButtonGroup('swp-preset-button[data-workweek]', 'data-workweek', (value) => {
- this.changeWorkweek(value);
- });
-}
-
-private changeWorkweek(workweekId: string): void {
- this.config.setWorkWeek(workweekId);
-
- // DIREKTE KALD - tight coupling
- ConfigManager.updateCSSProperties(this.config);
-
- this.updateAllButtons();
-
- const settings = this.config.getWorkWeekSettings();
-
- this.eventBus.emit(CoreEvents.WORKWEEK_CHANGED, {
- workWeekId: workweekId,
- settings: settings
- });
-}
-```
-
-#### EFTER - WorkweekPresetsManager
-```typescript
-// WorkweekPresetsManager.ts
-private setupButtonListeners(): void {
- const buttons = document.querySelectorAll('swp-preset-button[data-workweek]');
-
- buttons.forEach(button => {
- const clickHandler = (event: Event) => {
- event.preventDefault();
- const presetId = button.getAttribute('data-workweek');
- if (presetId) {
- this.changePreset(presetId);
- }
- };
-
- button.addEventListener('click', clickHandler);
- this.buttonListeners.set(button, clickHandler);
- });
-
- this.updateButtonStates();
-}
-
-private changePreset(presetId: string): void {
- if (!WORK_WEEK_PRESETS[presetId]) {
- console.warn(`Invalid preset ID "${presetId}"`);
- return;
- }
-
- if (presetId === this.config.currentWorkWeek) {
- return;
- }
-
- const previousPresetId = this.config.currentWorkWeek;
- this.config.currentWorkWeek = presetId;
-
- const settings = WORK_WEEK_PRESETS[presetId];
-
- this.updateButtonStates();
-
- // Emit event - CSS opdatering sker automatisk via ConfigManager listener
- this.eventBus.emit(CoreEvents.WORKWEEK_CHANGED, {
- workWeekId: presetId,
- previousWorkWeekId: previousPresetId,
- settings: settings
- });
-}
-```
-
----
-
-### 2. CSS Opdatering
-
-#### FØR - ConfigManager
-```typescript
-// ConfigManager.ts - DUPLIKERET KODE!
-
-// Static metode kaldt fra ViewManager
-static updateCSSProperties(config: Configuration): void {
- const gridSettings = config.gridSettings;
- const workWeekSettings = config.getWorkWeekSettings();
-
- // 6 CSS properties sat
- document.documentElement.style.setProperty('--hour-height', `${gridSettings.hourHeight}px`);
- document.documentElement.style.setProperty('--day-start-hour', gridSettings.dayStartHour.toString());
- document.documentElement.style.setProperty('--day-end-hour', gridSettings.dayEndHour.toString());
- document.documentElement.style.setProperty('--work-start-hour', gridSettings.workStartHour.toString());
- document.documentElement.style.setProperty('--work-end-hour', gridSettings.workEndHour.toString());
- document.documentElement.style.setProperty('--grid-columns', workWeekSettings.totalDays.toString());
-}
-
-// Instance metode i constructor - SAMME KODE!
-public updateAllCSSProperties(): void {
- const gridSettings = this.config.gridSettings;
-
- // 5 CSS properties sat (mangler --grid-columns!)
- document.documentElement.style.setProperty('--hour-height', `${gridSettings.hourHeight}px`);
- document.documentElement.style.setProperty('--day-start-hour', gridSettings.dayStartHour.toString());
- document.documentElement.style.setProperty('--day-end-hour', gridSettings.dayEndHour.toString());
- document.documentElement.style.setProperty('--work-start-hour', gridSettings.workStartHour.toString());
- document.documentElement.style.setProperty('--work-end-hour', gridSettings.workEndHour.toString());
-}
-```
-
-#### EFTER - ConfigManager
-```typescript
-// ConfigManager.ts - INGEN DUPLICATION!
-
-constructor(eventBus: IEventBus, config: Configuration) {
- this.eventBus = eventBus;
- this.config = config;
-
- this.setupEventListeners();
- this.syncGridCSSVariables(); // Kaldt ved initialization
- this.syncWorkweekCSSVariables(); // Kaldt ved initialization
-}
-
-private setupEventListeners(): void {
- // Lyt til events - REACTIVE!
- this.eventBus.on(CoreEvents.WORKWEEK_CHANGED, (event: Event) => {
- const { settings } = (event as CustomEvent<{ settings: IWorkWeekSettings }>).detail;
- this.syncWorkweekCSSVariables(settings);
- });
-}
-
-private syncGridCSSVariables(): void {
- const gridSettings = this.config.gridSettings;
-
- document.documentElement.style.setProperty('--hour-height', `${gridSettings.hourHeight}px`);
- document.documentElement.style.setProperty('--day-start-hour', gridSettings.dayStartHour.toString());
- document.documentElement.style.setProperty('--day-end-hour', gridSettings.dayEndHour.toString());
- document.documentElement.style.setProperty('--work-start-hour', gridSettings.workStartHour.toString());
- document.documentElement.style.setProperty('--work-end-hour', gridSettings.workEndHour.toString());
-}
-
-private syncWorkweekCSSVariables(workWeekSettings?: IWorkWeekSettings): void {
- const settings = workWeekSettings || this.config.getWorkWeekSettings();
- document.documentElement.style.setProperty('--grid-columns', settings.totalDays.toString());
-}
-
-// STATIC METODE FJERNET! Ingen duplication!
-```
-
----
-
-### 3. Configuration Management
-
-#### FØR - Configuration
-```typescript
-// CalendarConfig.ts
-export class Configuration {
- public currentWorkWeek: string;
-
- constructor(
- config: ICalendarConfig,
- gridSettings: IGridSettings,
- dateViewSettings: IDateViewSettings,
- timeFormatConfig: ITimeFormatConfig,
- currentWorkWeek: string,
- selectedDate: Date = new Date()
- ) {
- // ...
- this.currentWorkWeek = currentWorkWeek;
- }
-
- // Metode med side effect
- setWorkWeek(workWeekId: string): void {
- if (WORK_WEEK_PRESETS[workWeekId]) {
- this.currentWorkWeek = workWeekId;
- this.dateViewSettings.weekDays = WORK_WEEK_PRESETS[workWeekId].totalDays; // SIDE EFFECT!
- }
- }
-
- getWorkWeekSettings(): IWorkWeekSettings {
- return WORK_WEEK_PRESETS[this.currentWorkWeek] || WORK_WEEK_PRESETS['standard'];
- }
-}
-```
-
-#### EFTER - Configuration
-```typescript
-// CalendarConfig.ts
-export class Configuration {
- public currentWorkWeek: string;
-
- constructor(
- config: ICalendarConfig,
- gridSettings: IGridSettings,
- dateViewSettings: IDateViewSettings,
- timeFormatConfig: ITimeFormatConfig,
- currentWorkWeek: string,
- selectedDate: Date = new Date()
- ) {
- // ...
- this.currentWorkWeek = currentWorkWeek;
- }
-
- // setWorkWeek() FJERNET - WorkweekPresetsManager opdaterer direkte
-
- getWorkWeekSettings(): IWorkWeekSettings {
- return WORK_WEEK_PRESETS[this.currentWorkWeek] || WORK_WEEK_PRESETS['standard'];
- }
-}
-```
-
----
-
-## Arkitektur Diagrammer
-
-### FØR - Tight Coupling
-```
-User Click
- ↓
-ViewManager (håndterer BÅDE view OG workweek)
- ↓
- ├─→ Configuration.setWorkWeek() (side effect på dateViewSettings!)
- ├─→ ConfigManager.updateCSSProperties() (direkte kald - tight coupling)
- ├─→ updateAllButtons() (view + workweek blandet)
- └─→ EventBus.emit(WORKWEEK_CHANGED)
- ↓
- ├─→ GridManager
- ├─→ CalendarManager → HeaderManager
- └─→ ConfigManager (gør INGENTING - CSS allerede sat!)
-```
-
-### EFTER - Loose Coupling
-```
-User Click
- ↓
-WorkweekPresetsManager (dedicated ansvar)
- ↓
- ├─→ config.currentWorkWeek = presetId (simpel state update)
- ├─→ updateButtonStates() (kun workweek buttons)
- └─→ EventBus.emit(WORKWEEK_CHANGED)
- ↓
- ├─→ ConfigManager.syncWorkweekCSSVariables() (event-drevet!)
- ├─→ GridManager.render()
- └─→ CalendarManager → HeaderManager
-```
-
----
-
-## Metrics Sammenligning
-
-| Metric | FØR | EFTER | Forbedring |
-|--------|-----|-------|------------|
-| **Lines of Code** | | | |
-| ViewManager | 155 linjer | 117 linjer | ✅ -24% (38 linjer) |
-| ConfigManager | 122 linjer | 103 linjer | ✅ -16% (19 linjer) |
-| WorkweekPresetsManager | 0 linjer | 115 linjer | ➕ Ny fil |
-| **Code Duplication** | 35% | 0% | ✅ -35% |
-| **Cyclomatic Complexity** | | | |
-| ViewManager.changeWorkweek() | 2 | N/A (fjernet) | ✅ |
-| WorkweekPresetsManager.changePreset() | N/A | 3 | ➕ |
-| ConfigManager (avg) | 1.5 | 1.0 | ✅ Simplere |
-| **Coupling** | Tight (direkte kald) | Loose (event-drevet) | ✅ |
-| **Cohesion** | Lav (mixed concerns) | Høj (single responsibility) | ✅ |
-
----
-
-## Dependencies Graf
-
-### FØR
-```
-ViewManager
- ├─→ Configuration (read + write via setWorkWeek)
- ├─→ ConfigManager (direct static call - TIGHT COUPLING)
- ├─→ CoreEvents
- └─→ EventBus
-
-ConfigManager
- ├─→ Configuration (read only)
- ├─→ EventBus (NO LISTENER! CSS sat via direct call)
- └─→ TimeFormatter
-```
-
-### EFTER
-```
-WorkweekPresetsManager
- ├─→ Configuration (read + direct mutation)
- ├─→ WORK_WEEK_PRESETS (import fra CalendarConfig)
- ├─→ CoreEvents
- └─→ EventBus
-
-ViewManager
- ├─→ Configuration (read only)
- ├─→ CoreEvents
- └─→ EventBus
-
-ConfigManager
- ├─→ Configuration (read only)
- ├─→ EventBus (LISTENER for WORKWEEK_CHANGED - LOOSE COUPLING)
- ├─→ CoreEvents
- └─→ TimeFormatter
-```
-
----
-
-## Fordele ved Refaktorering
-
-### ✅ Single Responsibility Principle
-- **ViewManager**: Fokuserer kun på view selector (day/week/month)
-- **WorkweekPresetsManager**: Dedikeret til workweek presets UI
-- **ConfigManager**: CSS synchronization manager
-
-### ✅ Event-Drevet Arkitektur
-- CSS opdatering sker reaktivt via events
-- Ingen direkte metode kald mellem managers
-- Loose coupling mellem komponenter
-
-### ✅ DRY Princip
-- Fjernet 35% code duplication
-- Ingen static + instance duplication længere
-- CSS sættes præcis 1 gang (ikke 2 gange)
-
-### ✅ Maintainability
-- Nemmere at finde workweek logik (én dedikeret fil)
-- Ændringer i workweek påvirker ikke view selector
-- Klar separation of concerns
-
-### ✅ Testability
-- WorkweekPresetsManager kan testes isoleret
-- ConfigManager event listeners kan mockes
-- Ingen hidden dependencies via static calls
-
----
-
-## Ulemper / Trade-offs
-
-### ⚠️ Flere Filer
-- +1 ny manager fil (WorkweekPresetsManager.ts)
-- Men bedre organisation
-
-### ⚠️ Direkte State Mutation
-```typescript
-this.config.currentWorkWeek = presetId; // Ikke via setter
-```
-- Configuration har ingen kontrol over mutation
-- Men simplere og mere direkte
-
-### ⚠️ DOM-afhængighed i Constructor
-```typescript
-constructor(...) {
- this.setupButtonListeners(); // Kalder document.querySelectorAll
-}
-```
-- Kan ikke unit testes uden DOM
-- Men fungerer perfekt da DI sker efter DOMContentLoaded
-
----
-
-## Konklusion
-
-Refaktoreringen følger princippet **"Each UI element has its own manager"** og resulterer i:
-
-✅ **Bedre struktur**: Klar separation mellem view og workweek
-✅ **Mindre kobling**: Event-drevet i stedet for direkte kald
-✅ **Mindre duplication**: Fra 35% til 0%
-✅ **Simplere kode**: Mindre kompleksitet i hver manager
-✅ **Nemmere at udvide**: Kan nemt tilføje ViewSelectorManager, NavigationGroupManager etc.
-
-**Trade-off**: Lidt flere filer, men meget bedre organisation og maintainability.