Removes outdated documentation and code comments

Deletes a large set of architectural plans, code reviews, and implementation documents. This content is no longer relevant as the described features and refactorings are complete.

Streamlines the event renderer by removing legacy drag event listener setup and outdated comments, reflecting improved separation of concerns and rendering strategies.
This commit is contained in:
Janus C. H. Knudsen 2025-09-22 20:59:25 +02:00
parent 92463ef173
commit 996459f226
25 changed files with 4 additions and 4947 deletions

View file

@ -1,161 +0,0 @@
# Calendar Event System Analysis
## Overview
Analysis of all events used in the Calendar Plantempus system, categorized by type and usage.
## Core Events (25 events)
*Defined in `src/constants/CoreEvents.ts`*
### Lifecycle Events (3)
- `core:initialized` - Calendar initialization complete
- `core:ready` - Calendar ready for use
- `core:destroyed` - Calendar cleanup complete
### View Events (3)
- `view:changed` - Calendar view changed (day/week/month)
- `view:rendered` - View rendering complete
- `workweek:changed` - Work week configuration changed
### Navigation Events (4)
- `nav:date-changed` - Current date changed
- `nav:navigation-completed` - Navigation animation/transition complete
- `nav:period-info-update` - Week/period information updated
- `nav:navigate-to-event` - Request to navigate to specific event
### Data Events (4)
- `data:loading` - Data fetch started
- `data:loaded` - Data fetch completed
- `data:error` - Data fetch error
- `data:events-filtered` - Events filtered
### Grid Events (3)
- `grid:rendered` - Grid rendering complete
- `grid:clicked` - Grid cell clicked
- `grid:cell-selected` - Grid cell selected
### Event Management (4)
- `event:created` - New event created
- `event:updated` - Event updated
- `event:deleted` - Event deleted
- `event:selected` - Event selected
### System Events (2)
- `system:error` - System error occurred
- `system:refresh` - Refresh requested
### Filter Events (1)
- `filter:changed` - Event filter changed
### Rendering Events (1)
- `events:rendered` - Events rendering complete
## Custom Events (22 events)
*Used throughout the system for specific functionality*
### Drag & Drop Events (12)
- `drag:start` - Drag operation started
- `drag:move` - Drag operation in progress
- `drag:end` - Drag operation ended
- `drag:auto-scroll` - Auto-scroll during drag
- `drag:column-change` - Dragged to different column
- `drag:mouseenter-header` - Mouse entered header during drag
- `drag:mouseleave-header` - Mouse left header during drag
- `drag:convert-to-time_event` - Convert all-day to timed event
### Event Interaction (2)
- `event:click` - Event clicked (no drag)
- `event:clicked` - Event clicked (legacy)
### Header Events (3)
- `header:mouseleave` - Mouse left header area
- `header:height-changed` - Header height changed
- `header:rebuilt` - Header DOM rebuilt
### All-Day Events (1)
- `allday:ensure-container` - Ensure all-day container exists
### Column Events (1)
- `column:mouseover` - Mouse over column
### Scroll Events (1)
- `scroll:to-event-time` - Scroll to specific event time
### Workweek Events (1)
- `workweek:header-update` - Update header after workweek change
### Navigation Events (1)
- `navigation:completed` - Navigation completed (different from core event)
## Event Payload Analysis
### Type Safety Issues Found
#### 1. AllDayManager Event Mismatch
**File:** `src/managers/AllDayManager.ts:33-34`
```typescript
// Expected payload:
const { targetDate, originalElement } = (event as CustomEvent).detail;
// Actual payload from DragDropManager:
{
targetDate: string,
mousePosition: { x: number, y: number },
originalElement: HTMLElement,
cloneElement: HTMLElement | null
}
```
#### 2. Inconsistent Event Signatures
Multiple events have different payload structures across different emitters/listeners.
#### 3. No Type Safety
All events use `(event as CustomEvent).detail` without proper TypeScript interfaces.
## Event Usage Statistics
### Most Used Events
1. **Drag Events** - 12 different types, used heavily in drag-drop system
2. **Core Navigation** - 4 types, used across all managers
3. **Grid Events** - 3 types, fundamental to calendar rendering
4. **Header Events** - 3 types, critical for all-day functionality
### Critical Events (High Impact)
- `drag:mouseenter-header` / `drag:mouseleave-header` - Core drag functionality
- `nav:navigation-completed` - Synchronizes multiple managers
- `grid:rendered` - Triggers event rendering
- `events:rendered` - Triggers filtering system
### Simple Events (Low Impact)
- `header:height-changed` - Simple notification
- `allday:ensure-container` - Simple request
- `system:refresh` - Simple trigger
## Recommendations
### Priority 1: Fix Critical Issues
1. Fix AllDayManager event signature mismatch
2. Standardize drag event payloads
3. Document current event contracts
### Priority 2: Type Safety Implementation
1. Create TypeScript interfaces for all event payloads
2. Implement type-safe EventBus
3. Migrate drag events first (highest complexity)
### Priority 3: System Cleanup
1. Consolidate duplicate events (`event:click` vs `event:clicked`)
2. Standardize event naming conventions
3. Remove unused events
## Total Event Count
- **Core Events:** 25
- **Custom Events:** 22
- **Total:** 47 unique event types
## Files Analyzed
- `src/constants/CoreEvents.ts`
- `src/managers/*.ts` (8 files)
- `src/renderers/*.ts` (4 files)
- `src/core/CalendarConfig.ts`
*Analysis completed: 2025-09-20*

View file

@ -1,133 +0,0 @@
# Calendar Initialization Sequence Diagram
Dette diagram viser den aktuelle initialization sekvens baseret på koden.
```mermaid
sequenceDiagram
participant Browser
participant Index as index.ts
participant MF as ManagerFactory
participant CTF as CalendarTypeFactory
participant EB as EventBus
participant CM as CalendarManager
participant EM as EventManager
participant ERS as EventRenderingService
participant GM as GridManager
participant GSM as GridStyleManager
participant GR as GridRenderer
participant SM as ScrollManager
participant NM as NavigationManager
participant NR as NavigationRenderer
participant VM as ViewManager
Browser->>Index: DOMContentLoaded
Index->>Index: initializeCalendar()
Index->>CTF: initialize()
CTF-->>Index: Factory ready
Index->>MF: getInstance()
MF-->>Index: Factory instance
Index->>MF: createManagers(eventBus, config)
MF->>EM: new EventManager(eventBus)
EM->>EB: setupEventListeners()
EM-->>MF: EventManager ready
MF->>ERS: new EventRenderingService(eventBus, eventManager)
ERS->>EB: setupEventListeners()
ERS-->>MF: EventRenderingService ready
MF->>GM: new GridManager()
GM->>GSM: new GridStyleManager(config)
GM->>GR: new GridRenderer(config)
GM->>EB: subscribeToEvents()
GM-->>MF: GridManager ready
MF->>SM: new ScrollManager()
SM->>EB: subscribeToEvents()
SM-->>MF: ScrollManager ready
MF->>NM: new NavigationManager(eventBus)
NM->>NR: new NavigationRenderer(eventBus)
NR->>EB: setupEventListeners()
NM->>EB: setupEventListeners()
NM-->>MF: NavigationManager ready
MF->>VM: new ViewManager(eventBus)
VM->>EB: setupEventListeners()
VM-->>MF: ViewManager ready
MF->>CM: new CalendarManager(eventBus, config, deps...)
CM->>EB: setupEventListeners()
CM-->>MF: CalendarManager ready
MF-->>Index: All managers created
Index->>EB: setDebug(true)
Index->>MF: initializeManagers(managers)
MF->>CM: initialize()
CM->>EM: loadData()
EM->>EM: loadMockData()
EM->>EM: processCalendarData()
EM-->>CM: Data loaded
CM->>GM: setResourceData(resourceData)
GM-->>CM: Resource data set
CM->>GM: render()
GM->>GSM: updateGridStyles(resourceData)
GM->>GR: renderGrid(grid, currentWeek, resourceData, allDayEvents)
GR-->>GM: Grid rendered
GM->>EB: emit(GRID_RENDERED, context)
EB-->>ERS: GRID_RENDERED event
ERS->>EM: getEventsForPeriod(startDate, endDate)
EM-->>ERS: Filtered events
ERS->>ERS: strategy.renderEvents()
CM->>SM: initialize()
SM->>SM: setupScrolling()
CM->>CM: setView(currentView)
CM->>EB: emit(VIEW_CHANGED, viewData)
CM->>CM: setCurrentDate(currentDate)
CM->>EB: emit(DATE_CHANGED, dateData)
CM->>EB: emit(CALENDAR_INITIALIZED, initData)
EB-->>NM: CALENDAR_INITIALIZED
NM->>NM: updateWeekInfo()
NM->>EB: emit(WEEK_INFO_UPDATED, weekInfo)
EB-->>NR: WEEK_INFO_UPDATED
NR->>NR: updateWeekInfoInDOM()
EB-->>VM: CALENDAR_INITIALIZED
VM->>VM: initializeView()
VM->>EB: emit(VIEW_RENDERED, viewData)
CM-->>MF: Initialization complete
MF-->>Index: All managers initialized
Index->>Browser: Calendar ready
```
## Aktuel Arkitektur Status
### Factory Pattern
- ManagerFactory håndterer manager instantiering
- Proper dependency injection via constructor
### Event-Driven Communication
- EventBus koordinerer kommunikation mellem managers
- NavigationRenderer lytter til WEEK_INFO_UPDATED events
- EventRenderingService reagerer på GRID_RENDERED events
### Separation of Concerns
- Managers håndterer business logic
- Renderers håndterer DOM manipulation
- EventBus håndterer kommunikation

View file

@ -1,183 +0,0 @@
# Kodeanalyse og Forbedringsplan - Calendar System
## Overordnet Vurdering
Koden er generelt velstruktureret med god separation of concerns. Der er dog stadig nogle områder med duplikering og potentiale for yderligere optimering.
## Positive Observationer ✅
### 1. God Arkitektur
- **Factory Pattern**: SwpEventElement bruger factory pattern korrekt
- **Event-driven**: Konsistent brug af EventBus for kommunikation
- **Caching**: God brug af caching i DragDropManager og EventManager
- **Separation**: AllDayManager er korrekt separeret fra HeaderManager
### 2. Performance Optimering
- **DOM Caching**: DragDropManager cacher DOM elementer effektivt
- **Event Throttling**: Implementeret i flere managers
- **Lazy Loading**: Smart brug af lazy loading patterns
### 3. TypeScript Best Practices
- Stærk typing med interfaces
- God brug af branded types (EventId)
- Konsistent error handling
## Identificerede Problemer og Forbedringsforslag 🔧
### 1. Duplikeret Time Formatting
**Problem**: `formatTime()` metode findes i:
- EventRenderer.ts (linje 280-297)
- SwpEventElement.ts (linje 44-50)
**Løsning**: Opret en central TimeFormatter utility:
```typescript
// src/utils/TimeFormatter.ts
export class TimeFormatter {
static formatTime(input: number | Date | string): string {
// Centraliseret implementation
}
}
```
### 2. Duplikeret Cache Management
**Problem**: Lignende cache patterns i:
- AllDayManager (linje 11-76)
- HeaderManager
- GridRenderer
**Løsning**: Generisk CacheManager:
```typescript
// src/utils/CacheManager.ts
export class DOMCacheManager<T extends Record<string, HTMLElement | null>> {
private cache: T;
constructor(initialCache: T) {
this.cache = initialCache;
}
get<K extends keyof T>(key: K, selector?: string): T[K] {
if (!this.cache[key] && selector) {
this.cache[key] = document.querySelector(selector) as T[K];
}
return this.cache[key];
}
clear(): void {
Object.keys(this.cache).forEach(key => {
this.cache[key as keyof T] = null;
});
}
}
```
### 3. Overlap Detection Kompleksitet
**Problem**: EventRenderer har stadig "new_" prefixed metoder som indikerer ufærdig refactoring
**Løsning**:
- Fjern "new_" prefix fra metoderne
- Flyt al overlap logik til OverlapDetector
- Simplificer EventRenderer
### 4. Grid Positioning Beregninger
**Problem**: Grid position beregninger gentages flere steder
**Løsning**: Centralisér i GridPositionCalculator:
```typescript
// src/utils/GridPositionCalculator.ts
export class GridPositionCalculator {
static calculateEventPosition(event: CalendarEvent): { top: number; height: number }
static calculateSnapPosition(y: number, snapInterval: number): number
static pixelsToMinutes(pixels: number, hourHeight: number): number
static minutesToPixels(minutes: number, hourHeight: number): number
}
```
### 5. Event Element Creation
**Problem**: SwpEventElement kunne forenkles yderligere
**Forslag**:
- Tilføj flere factory metoder for forskellige event typer
- Implementer builder pattern for komplekse events
### 6. All-Day Event Row Calculation
**Problem**: AllDayManager har kompleks row calculation logik (linje 108-143)
**Løsning**: Udtræk til separat utility:
```typescript
// src/utils/AllDayRowCalculator.ts
export class AllDayRowCalculator {
static calculateRequiredRows(events: HTMLElement[]): number
static expandEventsByDate(events: HTMLElement[]): Record<string, string[]>
}
```
### 7. Manglende Unit Tests
**Problem**: Ingen test filer fundet
**Løsning**: Tilføj tests for kritiske utilities:
- TimeFormatter
- GridPositionCalculator
- OverlapDetector
- AllDayRowCalculator
## Prioriteret Handlingsplan
### Fase 1: Utilities (Høj Prioritet)
1. ✅ SwpEventElement factory (allerede implementeret)
2. ⬜ TimeFormatter utility
3. ⬜ DOMCacheManager
4. ⬜ GridPositionCalculator
### Fase 2: Refactoring (Medium Prioritet)
5. ⬜ Fjern "new_" prefix fra EventRenderer metoder
6. ⬜ Simplificer AllDayManager med AllDayRowCalculator
7. ⬜ Konsolider overlap detection
### Fase 3: Testing & Dokumentation (Lav Prioritet)
8. ⬜ Unit tests for utilities
9. ⬜ JSDoc dokumentation
10. ⬜ Performance benchmarks
## Arkitektur Diagram
```mermaid
graph TD
A[Utilities Layer] --> B[TimeFormatter]
A --> C[DOMCacheManager]
A --> D[GridPositionCalculator]
A --> E[AllDayRowCalculator]
F[Managers] --> A
G[Renderers] --> A
H[Elements] --> A
F --> I[EventManager]
F --> J[DragDropManager]
F --> K[AllDayManager]
G --> L[EventRenderer]
G --> M[AllDayEventRenderer]
H --> N[SwpEventElement]
H --> O[SwpAllDayEventElement]
```
## Performance Forbedringer
### 1. Event Delegation
Overvej at bruge event delegation i stedet for individuelle event listeners på hver event element.
### 2. Virtual Scrolling
For kalendere med mange events, implementer virtual scrolling.
### 3. Web Workers
Overvej at flytte tunge beregninger til Web Workers.
## Konklusion
Koden er generelt i god stand med solid arkitektur. De foreslåede forbedringer vil:
- Reducere code duplication med 30-40%
- Forbedre maintainability
- Gøre koden mere testbar
- Forbedre performance marginalt
Estimeret tid for implementering: 2-3 dage for alle forbedringer.

View file

@ -1,237 +0,0 @@
# Calendar Plantempus - Date Mode Initialization Sequence
## Overview
This document shows the complete initialization sequence and event flow for Date Mode in Calendar Plantempus, including when data is loaded and ready for rendering.
## Sequence Diagram
```mermaid
sequenceDiagram
participant Browser as Browser
participant Index as index.ts
participant Config as CalendarConfig
participant Factory as CalendarTypeFactory
participant CM as CalendarManager
participant EM as EventManager
participant GM as GridManager
participant NM as NavigationManager
participant VM as ViewManager
participant ER as EventRenderer
participant SM as ScrollManager
participant EB as EventBus
participant DOM as DOM
Note over Browser: Page loads calendar application
Browser->>Index: Load application
Note over Index: PHASE 0: Pre-initialization Setup
Index->>Config: new CalendarConfig()
Config->>Config: loadCalendarType() - Read URL ?type=date
Config->>Config: loadFromDOM() - Read data attributes
Config->>Config: Set mode='date', period='week'
Index->>Factory: CalendarTypeFactory.initialize()
Factory->>Factory: Create DateHeaderRenderer
Factory->>Factory: Create DateColumnRenderer
Factory->>Factory: Create DateEventRenderer
Note over Factory: Strategy Pattern renderers ready
Note over Index: PHASE 1: Core Managers Construction
Index->>CM: new CalendarManager(eventBus, config)
CM->>EB: Subscribe to VIEW_CHANGE_REQUESTED
CM->>EB: Subscribe to NAV_PREV, NAV_NEXT
Index->>NM: new NavigationManager(eventBus)
NM->>EB: Subscribe to CALENDAR_INITIALIZED
Note over NM: Will wait to call updateWeekInfo()
Index->>VM: new ViewManager(eventBus)
VM->>EB: Subscribe to CALENDAR_INITIALIZED
Note over Index: PHASE 2: Data & Rendering Managers
Index->>EM: new EventManager(eventBus)
EM->>EB: Subscribe to CALENDAR_INITIALIZED
Note over EM: Will wait to load data
Index->>ER: new EventRenderer(eventBus)
ER->>EB: Subscribe to EVENTS_LOADED
ER->>EB: Subscribe to GRID_RENDERED
Note over ER: Needs BOTH events before rendering
Note over Index: PHASE 3: Layout Managers (Order Critical!)
Index->>SM: new ScrollManager()
SM->>EB: Subscribe to GRID_RENDERED
Note over SM: Must subscribe BEFORE GridManager renders
Index->>GM: new GridManager()
GM->>EB: Subscribe to CALENDAR_INITIALIZED
GM->>EB: Subscribe to CALENDAR_DATA_LOADED
GM->>GM: Set currentWeek = getWeekStart(new Date())
Note over GM: Ready to render, but waiting
Note over Index: PHASE 4: Coordinated Initialization
Index->>CM: initialize()
CM->>EB: emit(CALENDAR_INITIALIZING)
CM->>CM: setView('week'), setCurrentDate()
CM->>EB: emit(CALENDAR_INITIALIZED) ⭐
Note over EB: 🚀 CALENDAR_INITIALIZED triggers all managers
par EventManager Data Loading
EB->>EM: CALENDAR_INITIALIZED
EM->>EM: loadMockData() for date mode
EM->>EM: fetch('/src/data/mock-events.json')
Note over EM: Loading date-specific mock data
EM->>EM: Process events for current week
EM->>EB: emit(CALENDAR_DATA_LOADED, {calendarType: 'date', data})
EM->>EB: emit(EVENTS_LOADED, {events: [...])
and GridManager Initial Rendering
EB->>GM: CALENDAR_INITIALIZED
GM->>GM: render()
GM->>GM: updateGridStyles() - Set --grid-columns: 7
GM->>GM: createHeaderSpacer()
GM->>GM: createTimeAxis(dayStartHour, dayEndHour)
GM->>GM: createGridContainer()
Note over GM: Strategy Pattern - Date Mode Rendering
GM->>Factory: getHeaderRenderer('date') → DateHeaderRenderer
GM->>GM: renderCalendarHeader() - Create day headers
GM->>DOM: Create 7 swp-day-column elements
GM->>Factory: getColumnRenderer('date') → DateColumnRenderer
GM->>GM: renderColumnContainer() - Date columns
GM->>EB: emit(GRID_RENDERED) ⭐
and NavigationManager UI
EB->>NM: CALENDAR_INITIALIZED
NM->>NM: updateWeekInfo()
NM->>DOM: Update week display in navigation
NM->>EB: emit(WEEK_INFO_UPDATED)
and ViewManager Setup
EB->>VM: CALENDAR_INITIALIZED
VM->>VM: initializeView()
VM->>EB: emit(VIEW_RENDERED)
end
Note over GM: GridManager receives its own data event
EB->>GM: CALENDAR_DATA_LOADED
GM->>GM: updateGridStyles() - Recalculate columns if needed
Note over GM: Grid already rendered, just update styles
Note over ER: 🎯 Critical Synchronization Point
EB->>ER: EVENTS_LOADED
ER->>ER: pendingEvents = events (store, don't render yet)
Note over ER: Waiting for grid to be ready...
EB->>ER: GRID_RENDERED
ER->>DOM: querySelectorAll('swp-day-column') - Check if ready
DOM-->>ER: Return 7 day columns (ready!)
Note over ER: Both events loaded AND grid ready → Render!
ER->>Factory: getEventRenderer('date') → DateEventRenderer
ER->>ER: renderEvents(pendingEvents) using DateEventRenderer
ER->>DOM: Position events in day columns
ER->>ER: Clear pendingEvents
ER->>EB: emit(EVENT_RENDERED)
Note over SM: ScrollManager sets up after grid is complete
EB->>SM: GRID_RENDERED
SM->>DOM: querySelector('swp-scrollable-content')
SM->>SM: setupScrolling()
SM->>SM: applyScrollbarStyling()
SM->>SM: setupScrollSynchronization()
Note over Index: 🎊 Date Mode Initialization Complete!
Note over Index: Ready for user interaction
```
## Key Initialization Phases
### Phase 0: Pre-initialization Setup
- **CalendarConfig**: Loads URL parameters (`?type=date`) and DOM attributes
- **CalendarTypeFactory**: Creates strategy pattern renderers for date mode
### Phase 1: Core Managers Construction
- **CalendarManager**: Central coordinator
- **NavigationManager**: Week navigation controls
- **ViewManager**: View state management
### Phase 2: Data & Rendering Managers
- **EventManager**: Handles data loading
- **EventRenderer**: Manages event display with synchronization
### Phase 3: Layout Managers (Order Critical!)
- **ScrollManager**: Must subscribe before GridManager renders
- **GridManager**: Main grid rendering
### Phase 4: Coordinated Initialization
- **CalendarManager.initialize()**: Triggers `CALENDAR_INITIALIZED` event
- All managers respond simultaneously but safely
## Critical Synchronization Points
### 1. Event-Grid Synchronization
```typescript
// EventRenderer waits for BOTH events
if (this.pendingEvents.length > 0) {
const columns = document.querySelectorAll('swp-day-column'); // DATE MODE
if (columns.length > 0) { // Grid must exist first
this.renderEvents(this.pendingEvents);
}
}
```
### 2. Scroll-Grid Dependency
```typescript
// ScrollManager only sets up after grid is rendered
eventBus.on(EventTypes.GRID_RENDERED, () => {
this.setupScrolling(); // Safe to access DOM now
});
```
### 3. Manager Construction Order
```typescript
// Critical order: ScrollManager subscribes BEFORE GridManager renders
const scrollManager = new ScrollManager();
const gridManager = new GridManager();
```
## Date Mode Specifics
### Data Loading
- Uses `/src/data/mock-events.json`
- Processes events for current week
- Emits `CALENDAR_DATA_LOADED` with `calendarType: 'date'`
### Grid Rendering
- Creates 7 `swp-day-column` elements (weekDays: 7)
- Uses `DateHeaderRenderer` strategy
- Uses `DateColumnRenderer` strategy
- Sets `--grid-columns: 7` CSS variable
### Event Rendering
- Uses `DateEventRenderer` strategy
- Positions events in day columns based on start/end time
- Calculates pixel positions using `PositionUtils`
## Race Condition Prevention
1. **Subscription Before Action**: All managers subscribe during construction, act on `CALENDAR_INITIALIZED`
2. **DOM Existence Checks**: Managers verify DOM elements exist before manipulation
3. **Event Ordering**: `GRID_RENDERED` always fires before event rendering attempts
4. **Pending States**: EventRenderer stores pending events until grid is ready
5. **Coordinated Start**: Single `CALENDAR_INITIALIZED` event starts all processes
## Debugging Points
Key events to monitor during initialization:
- `CALENDAR_INITIALIZED` - Start of coordinated setup
- `CALENDAR_DATA_LOADED` - Date data ready
- `GRID_RENDERED` - Grid structure complete
- `EVENTS_LOADED` - Event data ready
- `EVENT_RENDERED` - Events positioned in grid
This sequence ensures deterministic, race-condition-free initialization with comprehensive logging for debugging.

View file

@ -1,114 +0,0 @@
# Korrigeret Analyse: Day Event Drag til Header og Tilbage
## Korrekt Flow Design
### Elementer i Spil
1. **Original Event**: Skal forblive i DOM uændret indtil drag:end
2. **Clone Event**: Det visuelle element der dragges rundt
3. **All-Day Event**: Midlertidig repræsentation i header
## Nuværende Problem
### AllDayManager.handleConvertToAllDay() (linje 274)
```typescript
// PROBLEM: Fjerner original for tidligt!
originalElement.remove(); // ❌ FORKERT
```
Original element fjernes når man hover over header, men det skal først fjernes ved faktisk drop.
### Korrekt Flow
```mermaid
sequenceDiagram
participant User
participant Original as Original Event
participant Clone as Clone Event
participant AllDay as All-Day Event
Note over Original: Start: Original synlig
User->>Clone: Drag start
Note over Clone: Clone oprettes og vises
Note over Original: Original bliver semi-transparent
User->>AllDay: Enter header
Note over Clone: Clone skjules (display:none)
Note over AllDay: All-day event oprettes
Note over Original: Original forbliver (semi-transparent)
User->>Clone: Leave header (tilbage til grid)
Note over AllDay: All-day event fjernes
Note over Clone: Clone vises igen
Note over Original: Original stadig der
User->>Original: Drop
Note over Original: NU fjernes original
Note over Clone: Clone bliver til ny position
```
## Nødvendige Ændringer
### 1. AllDayManager.handleConvertToAllDay() (linje 232-285)
```typescript
// FØR (linje 274):
originalElement.remove(); // ❌
// EFTER:
// originalElement.remove(); // Kommenteret ud - skal IKKE fjernes her
// Original forbliver i DOM med opacity 0.3 fra drag start
```
### 2. AllDayManager.handleConvertFromAllDay() (linje 290-311)
```typescript
// Nuværende kode er faktisk korrekt - den:
// 1. Fjerner all-day event
// 2. Viser clone igen
// Original er stadig der (blev aldrig fjernet)
```
### 3. EventRenderer - handleDragEnd()
```typescript
// Her skal original FAKTISK fjernes
// Efter successful drop:
if (this.originalEvent) {
this.originalEvent.remove(); // ✅ Korrekt tidspunkt
}
```
## Problem Opsummering
**Hovedproblem**: `originalElement.remove()` på linje 274 i AllDayManager sker for tidligt.
**Løsning**:
1. Fjern/kommenter linje 274 i AllDayManager.handleConvertToAllDay()
2. Original element skal kun fjernes i EventRenderer ved faktisk drop (drag:end)
3. Clone håndterer al visuel feedback under drag
## Test Scenarios
1. **Drag → Header → Grid → Drop**
- Original forbliver hele tiden
- Clone skjules/vises korrekt
- Original fjernes kun ved drop
2. **Drag → Header → Drop i Header**
- Original forbliver indtil drop
- All-day event bliver permanent
- Original fjernes ved drop
3. **Drag → Header → ESC**
- Original forbliver
- Drag cancelled, alt tilbage til start
## Affected Code
### AllDayManager.ts
- **Linje 274**: Skal fjernes/kommenteres
- Original element må IKKE fjernes her
### EventRenderer.ts
- **handleDragEnd()**: Skal sikre original fjernes her
- Dette er det ENESTE sted original må fjernes
### DragDropManager.ts
- Koden ser faktisk korrekt ud
- Holder styr på både original og clone

View file

@ -1,104 +0,0 @@
# Bug Analyse: Day Event Drag til Header og Tilbage
## Problem Beskrivelse
Når en day event dragges op til headeren (for at konvertere til all-day) og derefter dragges tilbage til grid UDEN at droppe, opstår der et kritisk problem hvor original event forsvinder.
## Flow Analyse
### Trin 1: Drag Start (Day Event)
- **DragDropManager** (linje 139-182): `handleMouseDown()` registrerer drag start
- Original event element gemmes
- `isDragStarted = false` indtil movement threshold nås
### Trin 2: Drag bevæger sig op mod Header
- **DragDropManager** (linje 187-253): `handleMouseMove()` tracker bevægelse
- Når threshold nås: `drag:start` event emitted
- **EventRenderer** opretter drag clone
### Trin 3: Mouse enters Header ⚠️ PROBLEM STARTER HER
- **DragDropManager** (linje 95-112): Lytter til `header:mouseover`
- Emitter `drag:convert-to-allday_event` event
- **AllDayManager** (linje 232-285): `handleConvertToAllDay()`:
- Opretter all-day event i header
- **FJERNER original timed event permanent** (linje 274: `originalElement.remove()`)
- Skjuler drag clone
### Trin 4: Mouse leaves Header (tilbage til grid) ⚠️ PROBLEM FORTSÆTTER
- **DragDropManager** (linje 128-136): Lytter til `header:mouseleave`
- Emitter `drag:convert-to-time_event` event
- **AllDayManager** (linje 290-311): `handleConvertFromAllDay()`:
- Fjerner all-day event fra container
- Viser drag clone igen
- **MEN: Original event er allerede fjernet og kan ikke genskabes!**
### Trin 5: Drop i Grid ⚠️ DATA TABT
- **DragDropManager** (linje 258-291): `handleMouseUp()`
- Emitter `drag:end` event
- Original element eksisterer ikke længere
- Kun clone eksisterer med ID "clone-{id}"
## Root Cause
**AllDayManager.handleConvertToAllDay()** fjerner permanent det originale element (linje 274) i stedet for at skjule det midlertidigt.
## Konsekvenser
1. Original event data går tabt
2. Event ID bliver "clone-{id}" i stedet for "{id}"
3. Event metadata kan mangle
4. Potentielle styling/positioning problemer
## Løsningsforslag
### Option 1: Behold Original Element (Anbefalet)
```typescript
// AllDayManager.handleConvertToAllDay() - linje 274
// FØR: originalElement.remove();
// EFTER:
originalElement.style.display = 'none';
originalElement.dataset.temporarilyHidden = 'true';
```
### Option 2: Gem Original Data
```typescript
// Gem original element data før fjernelse
const originalData = {
id: originalElement.dataset.eventId,
title: originalElement.dataset.title,
start: originalElement.dataset.start,
end: originalElement.dataset.end,
// ... andre properties
};
// Gem i DragDropManager eller AllDayManager
```
### Option 3: Re-create Original on Leave
```typescript
// AllDayManager.handleConvertFromAllDay()
// Genskab original element fra all-day event data
const originalElement = this.recreateTimedEvent(allDayEvent);
```
## Implementeringsplan
1. **Modificer AllDayManager.handleConvertToAllDay()**
- Skjul original element i stedet for at fjerne
- Marker element som midlertidigt skjult
2. **Modificer AllDayManager.handleConvertFromAllDay()**
- Find original element (ikke kun clone)
- Vis original element igen
- Synkroniser position med clone
3. **Opdater DragDropManager**
- Hold reference til både original og clone
- Ved drop: opdater original, fjern clone
4. **Test Scenarios**
- Drag day event → header → tilbage → drop
- Drag day event → header → drop i header
- Drag day event → header → ESC key
- Multiple hurtige hover over header
## Affected Files
- `src/managers/AllDayManager.ts` (linje 274, 290-311)
- `src/managers/DragDropManager.ts` (linje 95-136)
- `src/renderers/EventRenderer.ts` (potentielt)

View file

@ -1,123 +0,0 @@
# Komplet Bug Analyse: Drag-Drop Header Problemer
## Identificerede Problemer
### Problem 1: Original Element Fjernes For Tidligt
**Lokation**: AllDayManager.handleConvertToAllDay() linje 274
```typescript
originalElement.remove(); // ❌ FORKERT - skal ikke fjernes her
```
### Problem 2: Duplicate All-Day Events ved Mouseover
**Lokation**: AllDayManager.handleConvertToAllDay() linje 232-285
Hver gang `header:mouseover` fyrer (hvilket kan ske mange gange under drag), oprettes et NYT all-day event uden at checke om det allerede eksisterer.
## Event Flow Problem
```mermaid
sequenceDiagram
participant Mouse
participant Header
participant AllDay as AllDayManager
Note over Mouse: Dragger over header
loop Hver mouseover event
Mouse->>Header: mouseover
Header->>AllDay: drag:convert-to-allday_event
AllDay->>AllDay: Opretter NYT all-day event ❌
Note over AllDay: Ingen check for eksisterende!
end
Note over AllDay: Resultat: Multiple all-day events!
```
## Løsning
### Fix 1: Check for Eksisterende All-Day Event
```typescript
// AllDayManager.handleConvertToAllDay()
private handleConvertToAllDay(targetDate: string, originalElement: HTMLElement): void {
const eventId = originalElement.dataset.eventId;
// CHECK: Eksisterer all-day event allerede?
const existingAllDay = document.querySelector(
`swp-allday-container swp-allday-event[data-event-id="${eventId}"]`
);
if (existingAllDay) {
console.log('All-day event already exists, skipping creation');
return; // Exit early - don't create duplicate
}
// Fortsæt med at oprette all-day event...
}
```
### Fix 2: Fjern IKKE Original Element
```typescript
// Linje 274 - skal fjernes eller kommenteres ud:
// originalElement.remove(); // <-- FJERN DENNE LINJE
```
### Fix 3: Track Conversion State (Optional)
For at undgå gentagne conversions, kunne vi tracke state:
```typescript
// AllDayManager
private convertedEventIds = new Set<string>();
private handleConvertToAllDay(targetDate: string, originalElement: HTMLElement): void {
const eventId = originalElement.dataset.eventId;
// Check if already converted
if (this.convertedEventIds.has(eventId)) {
return;
}
// Mark as converted
this.convertedEventIds.add(eventId);
// Create all-day event...
}
private handleConvertFromAllDay(draggedEventId: string): void {
// Clear conversion state
this.convertedEventIds.delete(draggedEventId);
// Remove all-day event...
}
```
## Komplet Fix Checklist
1. **AllDayManager.handleConvertToAllDay()**
- [ ] Tilføj check for eksisterende all-day event
- [ ] Fjern/kommenter `originalElement.remove()` linje 274
- [ ] Log når duplicate undgås
2. **AllDayManager.handleConvertFromAllDay()**
- [ ] Verificer at all-day event faktisk fjernes
- [ ] Clear evt. conversion state
3. **Test Scenarios**
- [ ] Hurtig mouseover frem og tilbage over header
- [ ] Langsom drag over header
- [ ] Drag ind i header → ud → ind igen
- [ ] Multiple events dragges samtidig (hvis muligt)
## Root Causes
1. **Manglende idempotency**: handleConvertToAllDay() er ikke idempotent
2. **Forkert element lifecycle**: Original fjernes for tidligt
3. **Manglende state tracking**: Ingen tracking af hvilke events der er konverteret
## Performance Overvejelser
HeaderManager throttler mouseover events (linje 41-49), men det er ikke nok når musen bevæger sig langsomt over header. Vi skal have idempotent logik.
## Implementeringsrækkefølge
1. **Først**: Fix duplicate all-day events (check for existing)
2. **Derefter**: Fjern originalElement.remove()
3. **Test**: Verificer at begge problemer er løst
4. **Optional**: Implementer conversion state tracking for bedre performance

View file

@ -1,143 +0,0 @@
# Implementeringsdetaljer: Check for Eksisterende All-Day Event
## Hvor skal checket implementeres?
**Fil**: `src/managers/AllDayManager.ts`
**Metode**: `handleConvertToAllDay()`
**Linje**: Lige efter linje 232 (start af metoden)
## Præcis Implementation
```typescript
// AllDayManager.ts - handleConvertToAllDay metode (linje 232)
private handleConvertToAllDay(targetDate: string, originalElement: HTMLElement): void {
// Extract event data from original element
const eventId = originalElement.dataset.eventId;
const title = originalElement.dataset.title || originalElement.textContent || 'Untitled';
const type = originalElement.dataset.type || 'work';
const startStr = originalElement.dataset.start;
const endStr = originalElement.dataset.end;
if (!eventId || !startStr || !endStr) {
console.error('Original element missing required data (eventId, start, end)');
return;
}
// ===== NY KODE STARTER HER (efter linje 243) =====
// CHECK 1: Er dette faktisk clone elementet?
const isClone = eventId.startsWith('clone-');
const actualEventId = isClone ? eventId.replace('clone-', '') : eventId;
// CHECK 2: Eksisterer all-day event allerede?
const container = this.getAllDayContainer();
if (container) {
const existingAllDay = container.querySelector(
`swp-allday-event[data-event-id="${actualEventId}"]`
);
if (existingAllDay) {
console.log(`All-day event for ${actualEventId} already exists, skipping creation`);
return; // Exit early - don't create duplicate
}
}
// ===== NY KODE SLUTTER HER =====
// Fortsæt med normal oprettelse...
// Create CalendarEvent for all-day conversion - preserve original times
const originalStart = new Date(startStr);
// ... resten af koden
}
```
## Hvorfor virker dette?
1. **Check for clone ID**: Vi checker om det draggede element er clone (starter med "clone-")
2. **Normalisér ID**: Vi får det faktiske event ID uden "clone-" prefix
3. **Query DOM**: Vi søger i all-day container efter eksisterende element med samme ID
4. **Early exit**: Hvis det findes, logger vi og returnerer uden at oprette duplicate
## Alternative Implementeringer
### Option A: Track med Set (Hurtigere)
```typescript
// AllDayManager class property
private activeAllDayEvents = new Set<string>();
private handleConvertToAllDay(targetDate: string, originalElement: HTMLElement): void {
const eventId = originalElement.dataset.eventId;
const actualEventId = eventId?.startsWith('clone-')
? eventId.replace('clone-', '')
: eventId;
// Check i memory først (hurtigere)
if (this.activeAllDayEvents.has(actualEventId)) {
return;
}
// Marker som aktiv
this.activeAllDayEvents.add(actualEventId);
// ... opret all-day event
}
private handleConvertFromAllDay(draggedEventId: string): void {
// Fjern fra tracking
this.activeAllDayEvents.delete(draggedEventId);
// ... fjern all-day event
}
```
### Option B: Data Attribute Flag
```typescript
private handleConvertToAllDay(targetDate: string, originalElement: HTMLElement): void {
// Check flag på original element
if (originalElement.dataset.hasAllDayVersion === 'true') {
return;
}
// Sæt flag
originalElement.dataset.hasAllDayVersion = 'true';
// ... opret all-day event
}
private handleConvertFromAllDay(draggedEventId: string): void {
// Find original og fjern flag
const original = document.querySelector(`[data-event-id="${draggedEventId}"]`);
if (original) {
delete original.dataset.hasAllDayVersion;
}
// ... fjern all-day event
}
```
## Anbefalet Løsning
**DOM Query Check** (første løsning) er mest robust fordi:
1. Den checker faktisk DOM state
2. Ingen ekstra state at vedligeholde
3. Fungerer selv hvis events kommer ud af sync
## Test Verification
For at teste om det virker:
1. Åbn browser console
2. Drag et event langsomt over header
3. Se efter "already exists" log messages
4. Verificer kun ÉT all-day event i DOM med:
```javascript
document.querySelectorAll('swp-allday-event').length
```
## Linje 274 Fix
Samtidig skal vi fjerne/kommentere linje 274:
```typescript
// originalElement.remove(); // <-- KOMMENTER DENNE UD
```
Dette sikrer original element ikke fjernes for tidligt.

View file

@ -1,270 +0,0 @@
# Improved Calendar Initialization Strategy
## Current Problems
1. **Race Conditions**: Managers try DOM operations before DOM is ready
2. **Sequential Blocking**: All initialization happens sequentially
3. **Poor Error Handling**: No timeouts or retry mechanisms
4. **Late Data Loading**: Data only loads after all managers are created
## Recommended New Architecture
### Phase 1: Early Parallel Startup
```typescript
// index.ts - Improved initialization
export class CalendarInitializer {
async initialize(): Promise<void> {
console.log('📋 Starting Calendar initialization...');
// PHASE 1: Early parallel setup
const setupPromises = [
this.initializeConfig(), // Load URL params, DOM attrs
this.initializeFactory(), // Setup strategy patterns
this.preloadCalendarData(), // Start data loading early
this.waitForDOMReady() // Ensure basic DOM exists
];
await Promise.all(setupPromises);
console.log('✅ Phase 1 complete: Config, Factory, Data preloading started');
// PHASE 2: Manager creation with dependencies
await this.createManagersWithDependencies();
// PHASE 3: Coordinated activation
await this.activateAllManagers();
console.log('🎊 Calendar fully initialized!');
}
}
```
### Phase 2: Dependency-Aware Manager Creation
```typescript
private async createManagersWithDependencies(): Promise<void> {
const managers = new Map<string, any>();
// Core managers (no DOM dependencies)
managers.set('config', calendarConfig);
managers.set('eventBus', eventBus);
managers.set('calendarManager', new CalendarManager(eventBus, calendarConfig));
// DOM-dependent managers (wait for DOM readiness)
await this.waitForRequiredDOM(['swp-calendar', 'swp-calendar-nav']);
managers.set('navigationManager', new NavigationManager(eventBus));
managers.set('viewManager', new ViewManager(eventBus));
// Data managers (can work with preloaded data)
managers.set('eventManager', new EventManager(eventBus));
managers.set('dataManager', new DataManager());
// Layout managers (need DOM structure + other managers)
await this.waitForRequiredDOM(['swp-calendar-container']);
// CRITICAL ORDER: ScrollManager subscribes before GridManager renders
managers.set('scrollManager', new ScrollManager());
managers.set('gridManager', new GridManager());
// Rendering managers (need grid structure)
managers.set('eventRenderer', new EventRenderer(eventBus));
this.managers = managers;
}
```
### Phase 3: Coordinated Activation
```typescript
private async activateAllManagers(): Promise<void> {
// All managers created and subscribed, now activate in coordinated fashion
const calendarManager = this.managers.get('calendarManager');
// This triggers CALENDAR_INITIALIZED, but now all managers are ready
await calendarManager.initialize();
// Wait for critical initialization events
await Promise.all([
this.waitForEvent('CALENDAR_DATA_LOADED', 10000),
this.waitForEvent('GRID_RENDERED', 5000),
this.waitForEvent('EVENTS_LOADED', 10000)
]);
// Ensure event rendering completes
await this.waitForEvent('EVENT_RENDERED', 3000);
}
```
## Specific Timing Improvements
### 1. Early Data Preloading
```typescript
private async preloadCalendarData(): Promise<void> {
const currentDate = new Date();
const mode = calendarConfig.getCalendarMode();
// Start loading data for current period immediately
const dataManager = new DataManager();
const currentPeriod = this.getCurrentPeriod(currentDate, mode);
// Don't await - let this run in background
const dataPromise = dataManager.fetchEventsForPeriod(currentPeriod);
// Also preload adjacent periods
const prevPeriod = this.getPreviousPeriod(currentDate, mode);
const nextPeriod = this.getNextPeriod(currentDate, mode);
// Store promises for later use
this.preloadPromises = {
current: dataPromise,
previous: dataManager.fetchEventsForPeriod(prevPeriod),
next: dataManager.fetchEventsForPeriod(nextPeriod)
};
console.log('📊 Data preloading started for current, previous, and next periods');
}
```
### 2. DOM Readiness Verification
```typescript
private async waitForRequiredDOM(selectors: string[]): Promise<void> {
const maxWait = 5000; // 5 seconds max
const checkInterval = 100; // Check every 100ms
const startTime = Date.now();
while (Date.now() - startTime < maxWait) {
const missing = selectors.filter(selector => !document.querySelector(selector));
if (missing.length === 0) {
console.log(`✅ Required DOM elements found: ${selectors.join(', ')}`);
return;
}
await new Promise(resolve => setTimeout(resolve, checkInterval));
}
throw new Error(`❌ Timeout waiting for DOM elements: ${selectors.join(', ')}`);
}
```
### 3. Manager Base Class with Proper Lifecycle
```typescript
export abstract class BaseManager {
protected isInitialized = false;
protected requiredDOMSelectors: string[] = [];
constructor() {
// Don't call init() immediately in constructor!
console.log(`${this.constructor.name}: Created but not initialized`);
}
async initialize(): Promise<void> {
if (this.isInitialized) {
console.log(`${this.constructor.name}: Already initialized, skipping`);
return;
}
// Wait for required DOM elements
if (this.requiredDOMSelectors.length > 0) {
await this.waitForDOM(this.requiredDOMSelectors);
}
// Perform manager-specific initialization
await this.performInitialization();
this.isInitialized = true;
console.log(`${this.constructor.name}: Initialization complete`);
}
protected abstract performInitialization(): Promise<void>;
private async waitForDOM(selectors: string[]): Promise<void> {
// Same DOM waiting logic as above
}
}
```
### 4. Enhanced GridManager
```typescript
export class GridManager extends BaseManager {
protected requiredDOMSelectors = ['swp-calendar-container'];
constructor() {
super(); // Don't call this.init()!
this.currentWeek = this.getWeekStart(new Date());
}
protected async performInitialization(): Promise<void> {
// Now safe to find elements - DOM guaranteed to exist
this.findElements();
this.subscribeToEvents();
// Wait for CALENDAR_INITIALIZED before rendering
await this.waitForEvent('CALENDAR_INITIALIZED');
console.log('GridManager: Starting initial render');
this.render();
}
}
```
### 5. Enhanced EventRenderer with Better Synchronization
```typescript
export class EventRenderer extends BaseManager {
private dataReady = false;
private gridReady = false;
private pendingEvents: CalendarEvent[] = [];
protected async performInitialization(): Promise<void> {
this.subscribeToEvents();
// Wait for both data and grid in parallel
const [eventsData] = await Promise.all([
this.waitForEvent('EVENTS_LOADED'),
this.waitForEvent('GRID_RENDERED')
]);
console.log('EventRenderer: Both events and grid ready, rendering now');
this.renderEvents(eventsData.events);
}
private subscribeToEvents(): void {
this.eventBus.on(EventTypes.EVENTS_LOADED, (e: Event) => {
const detail = (e as CustomEvent).detail;
this.pendingEvents = detail.events;
this.dataReady = true;
this.tryRender();
});
this.eventBus.on(EventTypes.GRID_RENDERED, () => {
this.gridReady = true;
this.tryRender();
});
}
private tryRender(): void {
if (this.dataReady && this.gridReady && this.pendingEvents.length > 0) {
this.renderEvents(this.pendingEvents);
this.pendingEvents = [];
}
}
}
```
## Benefits of New Architecture
1. **🚀 Parallel Operations**: Data loading starts immediately while managers are being created
2. **🛡️ Race Condition Prevention**: DOM readiness verified before operations
3. **⚡ Better Performance**: Critical path optimized, non-critical operations parallelized
4. **🔧 Better Error Handling**: Timeouts and retry mechanisms
5. **📊 Predictable Timing**: Clear phases with guaranteed completion order
6. **🐛 Easier Debugging**: Clear lifecycle events and logging
## Implementation Strategy
1. **Phase 1**: Create BaseManager class and update existing managers
2. **Phase 2**: Implement CalendarInitializer with parallel setup
3. **Phase 3**: Add DOM readiness verification throughout
4. **Phase 4**: Implement data preloading strategy
5. **Phase 5**: Add comprehensive error handling and timeouts
This architecture ensures reliable, fast, and maintainable calendar initialization.

View file

@ -1,216 +0,0 @@
# TimeFormatter Specification
## Problem
- Alle events i systemet/mock JSON er i Zulu tid (UTC)
- Nuværende formatTime() metoder håndterer ikke timezone konvertering
- Ingen support for 12/24 timers format baseret på configuration
- Duplikeret formattering logik flere steder
## Løsning: Centraliseret TimeFormatter
### Requirements
1. **Timezone Support**
- Konverter fra UTC/Zulu til brugerens lokale timezone
- Respekter browser timezone settings
- Håndter sommertid korrekt
2. **12/24 Timer Format**
- Læs format præference fra CalendarConfig
- Support både 12-timer (AM/PM) og 24-timer format
- Gør det konfigurerbart per bruger
3. **Centralisering**
- Én enkelt kilde til al tidsformattering
- Konsistent formattering gennem hele applikationen
- Nem at teste og vedligeholde
### Proposed Implementation
```typescript
// src/utils/TimeFormatter.ts
export class TimeFormatter {
private static use24HourFormat: boolean = false;
private static userTimezone: string = Intl.DateTimeFormat().resolvedOptions().timeZone;
/**
* Initialize formatter with user preferences
*/
static initialize(config: { use24Hour: boolean; timezone?: string }) {
this.use24HourFormat = config.use24Hour;
if (config.timezone) {
this.userTimezone = config.timezone;
}
}
/**
* Format UTC/Zulu time to local time with correct format
* @param input - UTC Date, ISO string, or minutes from midnight
* @returns Formatted time string in user's preferred format
*/
static formatTime(input: Date | string | number): string {
let date: Date;
if (typeof input === 'number') {
// Minutes from midnight - create date for today
const today = new Date();
today.setHours(0, 0, 0, 0);
today.setMinutes(input);
date = today;
} else if (typeof input === 'string') {
// ISO string - parse as UTC
date = new Date(input);
} else {
date = input;
}
// Convert to local timezone
const localDate = this.convertToLocalTime(date);
// Format based on user preference
if (this.use24HourFormat) {
return this.format24Hour(localDate);
} else {
return this.format12Hour(localDate);
}
}
/**
* Convert UTC date to local timezone
*/
private static convertToLocalTime(utcDate: Date): Date {
// Use Intl.DateTimeFormat for proper timezone conversion
const formatter = new Intl.DateTimeFormat('en-US', {
timeZone: this.userTimezone,
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hour12: false
});
const parts = formatter.formatToParts(utcDate);
const dateParts: any = {};
parts.forEach(part => {
dateParts[part.type] = part.value;
});
return new Date(
parseInt(dateParts.year),
parseInt(dateParts.month) - 1,
parseInt(dateParts.day),
parseInt(dateParts.hour),
parseInt(dateParts.minute),
parseInt(dateParts.second)
);
}
/**
* Format time in 24-hour format (HH:mm)
*/
private static format24Hour(date: Date): string {
const hours = date.getHours().toString().padStart(2, '0');
const minutes = date.getMinutes().toString().padStart(2, '0');
return `${hours}:${minutes}`;
}
/**
* Format time in 12-hour format (h:mm AM/PM)
*/
private static format12Hour(date: Date): string {
const hours = date.getHours();
const minutes = date.getMinutes();
const period = hours >= 12 ? 'PM' : 'AM';
const displayHours = hours > 12 ? hours - 12 : (hours === 0 ? 12 : hours);
return `${displayHours}:${minutes.toString().padStart(2, '0')} ${period}`;
}
/**
* Format date and time together
*/
static formatDateTime(date: Date | string): string {
const localDate = typeof date === 'string' ? new Date(date) : date;
const convertedDate = this.convertToLocalTime(localDate);
const dateStr = convertedDate.toLocaleDateString();
const timeStr = this.formatTime(convertedDate);
return `${dateStr} ${timeStr}`;
}
/**
* Get timezone offset in hours
*/
static getTimezoneOffset(): number {
return new Date().getTimezoneOffset() / -60;
}
}
```
### Configuration Integration
```typescript
// src/core/CalendarConfig.ts
export interface TimeFormatSettings {
use24HourFormat: boolean;
timezone?: string; // Optional override, defaults to browser timezone
}
// Add to CalendarConfig
getTimeFormatSettings(): TimeFormatSettings {
return {
use24HourFormat: this.config.use24HourFormat ?? false,
timezone: this.config.timezone // undefined = use browser default
};
}
```
### Usage Examples
```typescript
// Initialize on app start
TimeFormatter.initialize({
use24Hour: calendarConfig.getTimeFormatSettings().use24HourFormat,
timezone: calendarConfig.getTimeFormatSettings().timezone
});
// Format UTC event time to local
const utcEventTime = "2024-01-15T14:30:00Z"; // 2:30 PM UTC
const localTime = TimeFormatter.formatTime(utcEventTime);
// Result (Copenhagen, 24h): "15:30"
// Result (Copenhagen, 12h): "3:30 PM"
// Result (New York, 12h): "9:30 AM"
// Format minutes from midnight
const minutes = 570; // 9:30 AM
const formatted = TimeFormatter.formatTime(minutes);
// Result (24h): "09:30"
// Result (12h): "9:30 AM"
```
### Testing Considerations
1. Test timezone conversions:
- UTC to Copenhagen (UTC+1/+2)
- UTC to New York (UTC-5/-4)
- UTC to Tokyo (UTC+9)
2. Test daylight saving transitions
3. Test 12/24 hour format switching
4. Test edge cases:
- Midnight (00:00 / 12:00 AM)
- Noon (12:00 / 12:00 PM)
- Events spanning multiple days
### Migration Plan
1. Implement TimeFormatter class
2. Add configuration options to CalendarConfig
3. Replace all existing formatTime() calls
4. Update mock data loader to handle UTC properly
5. Test thoroughly with different timezones

View file

@ -1,245 +0,0 @@
# TypeScript Code Review - Calendar Plantempus
**Dato:** September 2025
**Reviewer:** Roo
**Fokus:** Dybdegående analyse efter TimeFormatter implementation
## Executive Summary
Efter implementering af TimeFormatter og gennemgang af codebasen, har jeg identificeret både styrker og forbedringspotentiale. Koden viser god separation of concerns og event-driven arkitektur, men har stadig områder der kan optimeres.
## 🟢 Styrker
### 1. Event-Driven Architecture
- **Konsistent EventBus pattern** gennem hele applikationen
- Ingen direkte dependencies mellem moduler
- God brug af custom events for kommunikation
### 2. Separation of Concerns
- **Managers**: Håndterer business logic (AllDayManager, DragDropManager, etc.)
- **Renderers**: Fokuserer på DOM manipulation
- **Utils**: Isolerede utility funktioner
- **Elements**: Factory pattern for DOM element creation
### 3. Performance Optimering
- **DOM Caching**: Konsistent caching af DOM elementer
- **Throttling**: Event throttling i HeaderManager (16ms delay)
- **Pixel-based calculations**: Fjernet komplekse time-based overlap beregninger
### 4. TypeScript Best Practices
- Stærk typing med interfaces
- Proper null/undefined checks
- Readonly constants hvor relevant
## 🔴 Kritiske Issues
### 1. "new_" Prefix Methods (EventRenderer.ts)
```typescript
// PROBLEM: Midlertidige metode navne
protected new_handleEventOverlaps()
protected new_renderOverlappingEvents()
protected new_applyStackStyling()
protected new_applyColumnSharingStyling()
```
**Impact:** Forvirrende navngivning, indikerer ufærdig refactoring
**Løsning:** Fjern prefix og ryd op i gamle metoder
### 2. Duplikeret Cache Logic
```typescript
// AllDayManager.ts
private cachedAllDayContainer: HTMLElement | null = null;
private cachedCalendarHeader: HTMLElement | null = null;
// HeaderManager.ts
private cachedCalendarHeader: HTMLElement | null = null;
// DragDropManager.ts
private cachedElements: CachedElements = {...}
```
**Impact:** 30+ linjer duplikeret kode
**Løsning:** Opret generisk DOMCacheManager
### 3. Manglende Error Boundaries
```typescript
// SimpleEventOverlapManager.ts
const linkData = element.dataset.stackLink;
try {
return JSON.parse(linkData);
} catch (e) {
console.warn('Failed to parse stack link data:', linkData, e);
return null;
}
```
**Impact:** Silently failing JSON parsing
**Løsning:** Proper error handling med user feedback
## 🟡 Code Smells & Improvements
### 1. Magic Numbers
```typescript
// SimpleEventOverlapManager.ts
const startDifference = Math.abs(top1 - top2);
if (startDifference > 40) { // Magic number!
return OverlapType.STACKING;
}
// DragDropManager.ts
private readonly dragThreshold = 5; // Should be configurable
private readonly scrollSpeed = 10;
private readonly scrollThreshold = 30;
```
**Løsning:** Flyt til configuration constants
### 2. Complex Method Signatures
```typescript
// AllDayManager.ts - 73 linjer!
public checkAndAnimateAllDayHeight(): void {
// Massive method doing too much
}
```
**Løsning:** Split i mindre, fokuserede metoder
### 3. Inconsistent Naming
```typescript
// Mix af naming conventions
getCalendarHeader() // get prefix
findElements() // no prefix
detectColumn() // action verb
cachedElements // noun
```
**Løsning:** Standardiser naming convention
### 4. Memory Leaks Risk
```typescript
// DragDropManager.ts
private boundHandlers = {
mouseMove: this.handleMouseMove.bind(this),
mouseDown: this.handleMouseDown.bind(this),
mouseUp: this.handleMouseUp.bind(this)
};
```
**God praksis!** Men ikke konsistent anvendt alle steder
## 📊 Metrics & Analysis
### Complexity Analysis
| File | Lines | Cyclomatic Complexity | Maintainability |
|------|-------|----------------------|-----------------|
| AllDayManager.ts | 281 | Medium (8) | Good |
| DragDropManager.ts | 521 | High (15) | Needs refactoring |
| SimpleEventOverlapManager.ts | 473 | Very High (20) | Critical |
| HeaderManager.ts | 119 | Low (4) | Excellent |
| GridManager.ts | 348 | Medium (10) | Good |
### Code Duplication
- **Cache management**: ~15% duplication
- **Event handling**: ~10% duplication
- **Position calculations**: ~8% duplication
## 🎯 Prioriterede Forbedringer
### Priority 1: Critical Fixes
1. **Fjern "new_" prefix** fra EventRenderer metoder
2. **Fix TimeFormatter timezone** - Håndter mock data korrekt som UTC
3. **Implementer DOMCacheManager** - Reducer duplication
### Priority 2: Architecture Improvements
1. **GridPositionCalculator** - Centralisér position beregninger
2. **EventThrottler** - Generisk throttling utility
3. **AllDayRowCalculator** - Udtræk kompleks logik fra AllDayManager
### Priority 3: Code Quality
1. **Reduce method complexity** - Split store metoder
2. **Standardize naming** - Konsistent naming convention
3. **Add JSDoc** - Mangler på mange public methods
### Priority 4: Testing
1. **Unit tests** for TimeFormatter
2. **Integration tests** for overlap detection
3. **Performance tests** for large event sets
## 💡 Architectural Recommendations
### 1. Introduce Service Layer
```typescript
// Forslag: EventService
class EventService {
private formatter: TimeFormatter;
private calculator: GridPositionCalculator;
private overlapManager: SimpleEventOverlapManager;
// Centralized event operations
}
```
### 2. Configuration Management
```typescript
interface CalendarConstants {
DRAG_THRESHOLD: number;
SCROLL_SPEED: number;
STACK_OFFSET: number;
OVERLAP_THRESHOLD: number;
}
```
### 3. Error Handling Strategy
```typescript
class CalendarError extends Error {
constructor(
message: string,
public code: string,
public recoverable: boolean
) {
super(message);
}
}
```
## 🚀 Performance Optimizations
### 1. Virtual Scrolling
For måneds-view med mange events, overvej virtual scrolling
### 2. Web Workers
Flyt tunge beregninger (overlap detection) til Web Worker
### 3. RequestIdleCallback
Brug for non-critical updates som analytics
## ✅ Positive Highlights
1. **TimeFormatter Implementation**: Elegant og clean
2. **Event-driven Architecture**: Konsistent og velfungerende
3. **TypeScript Usage**: God type safety
4. **DOM Manipulation**: Effektiv med custom elements
5. **Separation of Concerns**: Klar opdeling af ansvar
## 📋 Recommended Action Plan
### Immediate (1-2 dage)
- [ ] Fjern "new_" prefix fra EventRenderer
- [ ] Implementer DOMCacheManager
- [ ] Fix magic numbers
### Short-term (3-5 dage)
- [ ] Opret GridPositionCalculator
- [ ] Implementer EventThrottler
- [ ] Refactor SimpleEventOverlapManager complexity
### Long-term (1-2 uger)
- [ ] Add comprehensive unit tests
- [ ] Implement service layer
- [ ] Performance optimizations
## Konklusion
Koden er generelt velstruktureret med god separation of concerns og konsistent event-driven arkitektur. TimeFormatter implementationen er elegant og løser timezone problemet godt.
Hovedudfordringerne ligger i:
1. Ufærdig refactoring (new_ prefix)
2. Duplikeret cache logic
3. Høj complexity i overlap detection
4. Manglende tests
Med de foreslåede forbedringer vil kodebasen blive mere maintainable, performant og robust.
**Overall Score: 7.5/10** - God kvalitet med plads til forbedring