From 996459f2266c3c9f6693c19bb20714851073d2e7 Mon Sep 17 00:00:00 2001 From: "Janus C. H. Knudsen" Date: Mon, 22 Sep 2025 20:59:25 +0200 Subject: [PATCH] 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. --- architecture/code-review-2025-01-UPDATED.md | 155 ------ architecture/code-review-2025-01.md | 282 ----------- architecture/month-view-plan-UPDATED.md | 270 ---------- architecture/month-view-refactoring-plan.md | 456 ----------------- calendar-complete-specification.md | 460 ------------------ code_review.md | 398 --------------- complexity_comparison.md | 182 ------- data_attribute_solution.md | 221 --------- docs/EventSystem-Analysis.md | 161 ------ docs/calendar-initialization-sequence.md | 133 ----- docs/code-improvement-plan.md | 183 ------- docs/date-mode-initialization-sequence.md | 237 --------- ...drag-drop-header-bug-analysis-corrected.md | 114 ----- docs/drag-drop-header-bug-analysis.md | 104 ---- .../drag-drop-header-complete-bug-analysis.md | 123 ----- ...drag-drop-header-implementation-details.md | 143 ------ docs/implementation-todo.md | 0 docs/improved-initialization-strategy.md | 270 ---------- docs/timeformatter-specification.md | 216 -------- docs/typescript-code-review-2025.md | 245 ---------- event-overlap-implementation-plan.md | 173 ------- overlap-fix-plan.md | 48 -- refactored-header-manager.md | 184 ------- resource-calendar-structure.md | 166 ------- src/renderers/EventRenderer.ts | 27 +- 25 files changed, 4 insertions(+), 4947 deletions(-) delete mode 100644 architecture/code-review-2025-01-UPDATED.md delete mode 100644 architecture/code-review-2025-01.md delete mode 100644 architecture/month-view-plan-UPDATED.md delete mode 100644 architecture/month-view-refactoring-plan.md delete mode 100644 calendar-complete-specification.md delete mode 100644 code_review.md delete mode 100644 complexity_comparison.md delete mode 100644 data_attribute_solution.md delete mode 100644 docs/EventSystem-Analysis.md delete mode 100644 docs/calendar-initialization-sequence.md delete mode 100644 docs/code-improvement-plan.md delete mode 100644 docs/date-mode-initialization-sequence.md delete mode 100644 docs/drag-drop-header-bug-analysis-corrected.md delete mode 100644 docs/drag-drop-header-bug-analysis.md delete mode 100644 docs/drag-drop-header-complete-bug-analysis.md delete mode 100644 docs/drag-drop-header-implementation-details.md delete mode 100644 docs/implementation-todo.md delete mode 100644 docs/improved-initialization-strategy.md delete mode 100644 docs/timeformatter-specification.md delete mode 100644 docs/typescript-code-review-2025.md delete mode 100644 event-overlap-implementation-plan.md delete mode 100644 overlap-fix-plan.md delete mode 100644 refactored-header-manager.md delete mode 100644 resource-calendar-structure.md diff --git a/architecture/code-review-2025-01-UPDATED.md b/architecture/code-review-2025-01-UPDATED.md deleted file mode 100644 index ee3aafd..0000000 --- a/architecture/code-review-2025-01-UPDATED.md +++ /dev/null @@ -1,155 +0,0 @@ -# Critical Code Review - Calendar Plantempus (UPDATED) -**Date:** January 2025 -**Reviewer:** Code Analysis Assistant -**Scope:** Full TypeScript/JavaScript codebase -**Update:** Post-refactoring status - -## Executive Summary -This code review identified 14+ critical issues. After immediate refactoring, 7 critical issues have been resolved, significantly improving code quality and maintainability. - -## ✅ RESOLVED ISSUES (January 2025) - -### 1. ~~Inconsistent File Structure~~ ✅ FIXED -**Resolution:** -- ✅ Deleted `src/utils/PositionUtils.js` (legacy JavaScript) -- ✅ Fixed `tsconfig.json` output directory to `./wwwroot/js` -- ✅ Build pipeline now consistent - -### 2. ~~Event System Overcomplexity~~ ✅ PARTIALLY FIXED -**Resolution:** -- ✅ Deleted unused `CalendarState.ts` (170 lines of dead code) -- ✅ Created `CoreEvents.ts` with only 20 essential events -- ✅ Added migration map for gradual transition -- ⚠️ Still need to migrate all code to use CoreEvents - -### 3. ~~Missing Error Handling~~ ✅ PARTIALLY FIXED -**Resolution:** -- ✅ Added `validateDate()` method to DateCalculator -- ✅ All date methods now validate inputs -- ⚠️ Still need error boundaries in UI components - -### 4. ~~Memory Leak Potential~~ ✅ PARTIALLY FIXED -**Resolution:** -- ✅ ViewManager now tracks all listeners -- ✅ Proper `destroy()` method implementation -- ⚠️ Other managers still need cleanup methods - -### 7. ~~Type Safety Issues~~ ✅ FIXED -**Resolution:** -- ✅ Replaced `any[]` with `AllDayEvent[]` type -- ✅ Created proper event type definitions -- ✅ No more type casting in fixed files - ---- - -## 🚨 REMAINING CRITICAL ISSUES - -### 5. Single Responsibility Violations -**Severity:** High -**Impact:** Unmaintainable code, difficult to test - -**Still Present:** -- GridManager: 311 lines handling multiple responsibilities -- CalendarConfig: Config + state management mixed - -**Recommendation:** Implement strategy pattern for different views - ---- - -### 6. Dependency Injection Missing -**Severity:** Medium -**Impact:** Untestable code, tight coupling - -**Still Present:** -- Singleton imports in 15+ files -- Circular dependencies through EventBus - -**Recommendation:** Use constructor injection pattern - ---- - -### 8. Performance Problems -**Severity:** Medium -**Impact:** Sluggish UI with many events - -**Still Present:** -- DOM queries not cached -- Full re-renders on every change - -**Recommendation:** Implement virtual scrolling and caching - ---- - -## 📊 IMPROVEMENT METRICS - -### Before Refactoring -- **Event Types:** 102 + StateEvents -- **Dead Code:** ~200 lines (CalendarState.ts) -- **Type Safety:** Multiple `any` types -- **Error Handling:** None -- **Memory Leaks:** All managers - -### After Refactoring -- **Event Types:** 20 core events (80% reduction!) -- **Dead Code:** 0 lines removed -- **Type Safety:** Proper types defined -- **Error Handling:** Date validation added -- **Memory Leaks:** ViewManager fixed - -### Code Quality Scores (Updated) -- **Maintainability:** ~~3/10~~ → **5/10** ⬆️ -- **Testability:** ~~2/10~~ → **4/10** ⬆️ -- **Performance:** 5/10 (unchanged) -- **Type Safety:** ~~4/10~~ → **7/10** ⬆️ -- **Architecture:** ~~3/10~~ → **4/10** ⬆️ - ---- - -## 🎯 NEXT STEPS - -### Phase 1: Architecture (Priority) -1. Implement ViewStrategy pattern for month view -2. Split GridManager using strategy pattern -3. Add dependency injection - -### Phase 2: Performance -4. Cache DOM queries -5. Implement selective rendering -6. Add virtual scrolling for large datasets - -### Phase 3: Testing -7. Add unit tests for DateCalculator -8. Add integration tests for event system -9. Add E2E tests for critical user flows - ---- - -## Files Modified - -### Deleted Files -- `src/utils/PositionUtils.js` - Legacy JavaScript removed -- `src/types/CalendarState.ts` - Unused state management - -### Created Files -- `src/constants/CoreEvents.ts` - Consolidated event system -- `src/types/EventTypes.ts` - Proper type definitions - -### Modified Files -- `tsconfig.json` - Fixed output directory -- `src/utils/DateCalculator.ts` - Added validation -- `src/managers/ViewManager.ts` - Added cleanup -- `src/managers/GridManager.ts` - Fixed types -- `src/renderers/GridRenderer.ts` - Fixed types -- 4 files - Removed StateEvents imports - ---- - -## Conclusion - -The immediate refactoring has addressed 50% of critical issues with minimal effort (~1 hour of work). The codebase is now: -- **Cleaner:** 200+ lines of dead code removed -- **Safer:** Type safety and validation improved -- **Simpler:** Event system reduced by 80% -- **More maintainable:** Clear separation emerging - -The remaining issues require architectural changes but the foundation is now stronger for implementing month view and other features. \ No newline at end of file diff --git a/architecture/code-review-2025-01.md b/architecture/code-review-2025-01.md deleted file mode 100644 index 7628ccb..0000000 --- a/architecture/code-review-2025-01.md +++ /dev/null @@ -1,282 +0,0 @@ -# Critical Code Review - Calendar Plantempus -**Date:** January 2025 -**Reviewer:** Code Analysis Assistant -**Scope:** Full TypeScript/JavaScript codebase - -## Executive Summary -This code review identifies 14+ critical issues that impact maintainability, performance, and the ability to add new features (especially month view). The codebase shows signs of rapid development without architectural planning, resulting in significant technical debt. - ---- - -## 🚨 CRITICAL ISSUES - -### 1. Inconsistent File Structure -**Severity:** High -**Impact:** Development confusion, build issues - -**Problems:** -- Duplicate TypeScript/JavaScript files exist (`src/utils/PositionUtils.js` and `.ts`) -- Mixed compiled and source code in `wwwroot/js/` -- Legacy files in root directory (`calendar-*.js`) - -**Evidence:** -``` -src/utils/PositionUtils.js (JavaScript) -src/utils/PositionUtils.ts (TypeScript) -calendar-grid-manager.js (Root legacy file) -``` - -**Recommendation:** Delete all `.js` files in `src/`, remove legacy root files, keep only TypeScript sources. - ---- - -### 2. Event System Overcomplexity -**Severity:** Critical -**Impact:** Impossible to maintain, performance degradation - -**Problems:** -- Two overlapping event systems (`EventTypes.ts` with 102 events + `CalendarState.ts`) -- Unclear separation of concerns -- Legacy events marked as "removed" but still present - -**Evidence:** -```typescript -// EventTypes.ts - 102 constants! -export const EventTypes = { - CONFIG_UPDATE: 'calendar:configupdate', - CALENDAR_TYPE_CHANGED: 'calendar:calendartypechanged', - // ... 100 more events -} - -// CalendarState.ts - Another event system -export const StateEvents = { - CALENDAR_STATE_CHANGED: 'calendar:state:changed', - // ... more events -} -``` - -**Recommendation:** Consolidate to ~20 core events with clear ownership. - ---- - -### 3. Missing Error Handling -**Severity:** High -**Impact:** Silent failures, poor user experience - -**Problems:** -- No try-catch blocks in critical paths -- No error boundaries for component failures -- DateCalculator assumes all inputs are valid - -**Evidence:** -```typescript -// DateCalculator.ts - No validation -getISOWeekStart(date: Date): Date { - const monday = new Date(date); // What if date is invalid? - const currentDay = monday.getDay(); - // ... continues without checks -} -``` - -**Recommendation:** Add comprehensive error handling and validation. - ---- - -### 4. Memory Leak Potential -**Severity:** Critical -**Impact:** Browser performance degradation over time - -**Problems:** -- Event listeners never cleaned up -- DOM references held indefinitely -- Multiple DateCalculator instances created - -**Evidence:** -```typescript -// ViewManager.ts - No cleanup -constructor(eventBus: IEventBus) { - this.eventBus = eventBus; - this.setupEventListeners(); // Never removed! -} -// No destroy() method exists -``` - -**Recommendation:** Implement proper cleanup in all managers. - ---- - -### 5. Single Responsibility Violations -**Severity:** High -**Impact:** Unmaintainable code, difficult to test - -**Problems:** -- `GridManager`: Handles rendering, events, styling, positioning (311 lines!) -- `CalendarConfig`: Config, state management, and factory logic mixed -- `NavigationRenderer`: DOM manipulation and event rendering - -**Evidence:** -```typescript -// GridManager doing everything: -- subscribeToEvents() -- render() -- setupGridInteractions() -- getClickPosition() -- scrollToHour() -- minutesToTime() -``` - -**Recommendation:** Split into focused, single-purpose classes. - ---- - -### 6. Dependency Injection Missing -**Severity:** Medium -**Impact:** Untestable code, tight coupling - -**Problems:** -- Hard-coded singleton imports everywhere -- `calendarConfig` imported directly in 15+ files -- Circular dependencies through EventBus - -**Evidence:** -```typescript -import { calendarConfig } from '../core/CalendarConfig'; // Singleton -import { eventBus } from '../core/EventBus'; // Another singleton -``` - -**Recommendation:** Use constructor injection pattern. - ---- - -### 7. Performance Problems -**Severity:** Medium -**Impact:** Sluggish UI, especially with many events - -**Problems:** -- `document.querySelector` called repeatedly -- No caching of DOM elements -- Full re-renders on every change - -**Evidence:** -```typescript -// Called multiple times per render: -const scrollableContent = document.querySelector('swp-scrollable-content'); -``` - -**Recommendation:** Cache DOM queries, implement selective rendering. - ---- - -### 8. Type Safety Issues -**Severity:** High -**Impact:** Runtime errors, hidden bugs - -**Problems:** -- `any` types used extensively -- Type casting to bypass checks -- Missing interfaces for data structures - -**Evidence:** -```typescript -private allDayEvents: any[] = []; // No type safety -(header as any).dataset.today = 'true'; // Bypassing TypeScript -``` - -**Recommendation:** Define proper TypeScript interfaces for all data. - ---- - -## 🔧 TECHNICAL DEBT - -### 9. Redundant Code -- Duplicate date logic in DateCalculator and PositionUtils -- Headers rendered in multiple places -- Similar event handling patterns copy-pasted - -### 10. Testability Issues -- No dependency injection makes mocking impossible -- Direct DOM manipulation prevents unit testing -- Global state makes tests brittle - -### 11. Documentation Problems -- Mixed Danish/English comments -- Missing JSDoc for public APIs -- Outdated comments that don't match code - ---- - -## ⚡ ARCHITECTURE ISSUES - -### 12. Massive Interfaces -- `CalendarTypes.ts`: Too many interfaces in one file -- `EventTypes`: 102 constants is unmanageable -- Manager interfaces too broad - -### 13. Coupling Problems -- High coupling between managers -- Everything communicates via events (performance hit) -- All components depend on global config - -### 14. Naming Inconsistency -- Mixed language conventions -- Unclear event names (`REFRESH_REQUESTED` vs `CALENDAR_REFRESH_REQUESTED`) -- `swp-` prefix unexplained - ---- - -## 📊 METRICS - -### Code Quality Scores -- **Maintainability:** 3/10 -- **Testability:** 2/10 -- **Performance:** 5/10 -- **Type Safety:** 4/10 -- **Architecture:** 3/10 - -### File Statistics -- **Total TypeScript files:** 24 -- **Total JavaScript files:** 8 (should be 0) -- **Average file size:** ~200 lines (acceptable) -- **Largest file:** GridManager.ts (311 lines) -- **Event types defined:** 102 (should be ~20) - ---- - -## 🎯 RECOMMENDATIONS - -### Immediate Actions (Week 1) -1. **Remove duplicate files** - Clean up `.js` duplicates -2. **Add error boundaries** - Prevent cascade failures -3. **Fix memory leaks** - Add cleanup methods - -### Short Term (Month 1) -4. **Consolidate events** - Reduce to core 20 events -5. **Implement DI** - Remove singleton dependencies -6. **Split mega-classes** - Apply Single Responsibility - -### Long Term (Quarter 1) -7. **Add comprehensive tests** - Aim for 80% coverage -8. **Performance optimization** - Virtual scrolling, caching -9. **Complete documentation** - JSDoc all public APIs - ---- - -## Impact on Month View Implementation - -**Without refactoring:** -- 🔴 ~2000 lines of new code -- 🔴 3-4 weeks implementation -- 🔴 High bug risk - -**With minimal refactoring:** -- ✅ ~500 lines of new code -- ✅ 1 week implementation -- ✅ Reusable components - ---- - -## Conclusion - -The codebase requires significant refactoring to support new features efficiently. The identified issues, particularly the lack of strategy pattern and hardcoded week/day assumptions, make adding month view unnecessarily complex. - -**Priority:** Focus on minimal refactoring that enables month view (Strategy pattern, config split, event consolidation) before attempting to add new features. \ No newline at end of file diff --git a/architecture/month-view-plan-UPDATED.md b/architecture/month-view-plan-UPDATED.md deleted file mode 100644 index 210fe93..0000000 --- a/architecture/month-view-plan-UPDATED.md +++ /dev/null @@ -1,270 +0,0 @@ -# Month View Implementation Plan (POST-REFACTORING) -**Updated:** January 2025 -**Status:** Ready to implement - Foundation cleaned up -**Timeline:** 2 days (reduced from 3) - -## Pre-Work Completed ✅ - -The following critical issues have been resolved, making month view implementation much easier: - -### ✅ Foundation Improvements Done -- **Event system simplified**: 102 → 20 events with CoreEvents.ts -- **Dead code removed**: CalendarState.ts (170 lines) deleted -- **Type safety improved**: Proper event interfaces defined -- **Error handling added**: Date validation in DateCalculator -- **Build fixed**: tsconfig.json output directory corrected - -### ✅ Impact on Month View -- **Clearer event system**: Know exactly which events to use -- **No confusing StateEvents**: Removed competing event system -- **Better types**: AllDayEvent interface ready for month events -- **Reliable dates**: DateCalculator won't crash on bad input - ---- - -## Revised Implementation Plan - -### Phase 1: Strategy Pattern (4 hours → 2 hours) -*Time saved: Dead code removed, events clarified* - -#### 1.1 Create ViewStrategy Interface ✨ -**New file:** `src/strategies/ViewStrategy.ts` -```typescript -import { CoreEvents } from '../constants/CoreEvents'; // Use new events! - -export interface ViewStrategy { - renderGrid(container: HTMLElement, context: ViewContext): void; - renderEvents(events: AllDayEvent[], container: HTMLElement): void; // Use proper types! - getLayoutConfig(): ViewLayoutConfig; - handleNavigation(date: Date): Date; // Now validated! -} -``` - -#### 1.2 Extract WeekViewStrategy ✨ -**New file:** `src/strategies/WeekViewStrategy.ts` -- Move existing logic from GridManager -- Use CoreEvents instead of EventTypes -- Leverage improved type safety - -#### 1.3 Create MonthViewStrategy -**New file:** `src/strategies/MonthViewStrategy.ts` -```typescript -export class MonthViewStrategy implements ViewStrategy { - renderGrid(container: HTMLElement, context: ViewContext): void { - // 7x6 month grid - no time axis needed - this.createMonthGrid(container, context.currentDate); - } - - renderEvents(events: AllDayEvent[], container: HTMLElement): void { - // Use proper AllDayEvent types (now defined!) - // Simple day cell rendering - } -} -``` - -#### 1.4 Update GridManager -**Modify:** `src/managers/GridManager.ts` -```typescript -export class GridManager { - private strategy: ViewStrategy; - - setViewStrategy(strategy: ViewStrategy): void { - this.strategy = strategy; - // No memory leaks - cleanup is now handled! - } - - render(): void { - // Emit CoreEvents.GRID_RENDERED instead of old events - this.eventBus.emit(CoreEvents.GRID_RENDERED, {...}); - } -} -``` - ---- - -### Phase 2: Month Components (2 hours → 1.5 hours) -*Time saved: Better types, no conflicting events* - -#### 2.1 MonthGridRenderer -**New file:** `src/renderers/MonthGridRenderer.ts` -```typescript -import { AllDayEvent } from '../types/EventTypes'; // Proper types! -import { CoreEvents } from '../constants/CoreEvents'; - -export class MonthGridRenderer { - renderMonth(container: HTMLElement, date: Date): void { - // DateCalculator.validateDate() prevents crashes - this.dateCalculator.validateDate(date, 'renderMonth'); - - // Create 7x6 grid with proper types - } -} -``` - -#### 2.2 MonthEventRenderer -**New file:** `src/renderers/MonthEventRenderer.ts` -```typescript -export class MonthEventRenderer { - render(events: AllDayEvent[], container: HTMLElement): void { - // Use AllDayEvent interface - no more any! - // Clean event filtering using proper types - } -} -``` - ---- - -### Phase 3: Integration (2 hours → 1 hour) -*Time saved: Clear events, no StateEvents confusion* - -#### 3.1 Wire ViewManager -**Modify:** `src/managers/ViewManager.ts` -```typescript -private changeView(newView: CalendarView): void { - let strategy: ViewStrategy; - - switch(newView) { - case 'month': - strategy = new MonthViewStrategy(); - break; - // ... other views - } - - this.gridManager.setViewStrategy(strategy); - - // Use CoreEvents - no confusion about which events! - this.eventBus.emit(CoreEvents.VIEW_CHANGED, { view: newView }); -} -``` - -#### 3.2 Update HTML & CSS -**Modify:** `wwwroot/index.html` -```html - -Month -``` - -**New:** `wwwroot/css/calendar-month.css` -```css -.month-grid { - display: grid; - grid-template-columns: repeat(7, 1fr); - grid-template-rows: auto repeat(6, 1fr); -} - -.month-day-cell { - border: 1px solid var(--border-color); - min-height: 120px; - padding: 4px; -} - -.month-event { - font-size: 0.75rem; - padding: 1px 4px; - margin: 1px 0; - border-radius: 2px; - background: var(--event-color); - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -``` - ---- - -## Updated Timeline - -### Day 1 (Reduced from full day) -**Morning (2 hours)** -- ✅ Foundation already clean -- Implement ViewStrategy interface -- Extract WeekViewStrategy -- Create MonthViewStrategy skeleton - -**Afternoon (1 hour)** -- Wire strategies in GridManager -- Test view switching works - -### Day 2 -**Morning (1.5 hours)** -- Implement MonthGridRenderer -- Implement MonthEventRenderer -- Create month CSS - -**Afternoon (1 hour)** -- Final integration -- Enable month button -- Test and polish - -**Total: 5.5 hours instead of 16+ hours!** - ---- - -## Benefits from Pre-Refactoring - -### 🚀 **Development Speed** -- **No conflicting events**: Clear which events to use -- **No dead code confusion**: CalendarState removed -- **Proper types**: AllDayEvent interface ready -- **Reliable foundation**: DateCalculator validation prevents crashes - -### 🎯 **Quality** -- **Consistent patterns**: Following established CoreEvents -- **Type safety**: No more `any` types to debug -- **Memory management**: Cleanup patterns established -- **Error handling**: Built-in date validation - -### 🔧 **Maintainability** -- **Single event system**: No EventTypes vs StateEvents confusion -- **Clean codebase**: 200+ lines of cruft removed -- **Clear interfaces**: AllDayEvent, ViewStrategy defined -- **Proper separation**: Strategy pattern foundation laid - ---- - -## Success Metrics (Updated) - -### ✅ **Foundation Quality** -- [x] Event system consolidated (20 events) -- [x] Dead code removed -- [x] Types properly defined -- [x] Date validation added -- [x] Build configuration fixed - -### 🎯 **Month View Goals** -- [ ] Month grid displays 6 weeks correctly -- [ ] Events show in day cells (max 3 + "more") -- [ ] Navigation works (prev/next month) -- [ ] View switching between week/month -- [ ] No regressions in existing views -- [ ] Under 400 lines of new code (down from 750!) - -### 📊 **Expected Results** -- **Implementation time**: 5.5 hours (67% reduction) -- **Code quality**: Higher (proper types, clear events) -- **Maintainability**: Much improved (clean foundation) -- **Bug risk**: Lower (validation, proper cleanup) - ---- - -## Risk Assessment (Much Improved) - -### ✅ **Risks Eliminated** -- ~~Event system conflicts~~ → Single CoreEvents system -- ~~Type errors~~ → Proper AllDayEvent interface -- ~~Date crashes~~ → DateCalculator validation -- ~~Memory leaks~~ → Cleanup patterns established -- ~~Dead code confusion~~ → CalendarState removed - -### ⚠️ **Remaining Risks (Low)** -1. **CSS conflicts**: Mitigated with namespaced `.month-view` classes -2. **Performance with many events**: Can implement virtualization later -3. **Browser compatibility**: CSS Grid widely supported - ---- - -## Conclusion - -The pre-refactoring work has transformed this from a difficult, error-prone implementation into a straightforward feature addition. The month view can now be implemented cleanly in ~5.5 hours with high confidence and low risk. - -**Ready to proceed!** 🚀 \ No newline at end of file diff --git a/architecture/month-view-refactoring-plan.md b/architecture/month-view-refactoring-plan.md deleted file mode 100644 index bd9226c..0000000 --- a/architecture/month-view-refactoring-plan.md +++ /dev/null @@ -1,456 +0,0 @@ -# Month View Refactoring Plan -**Purpose:** Enable month view with minimal refactoring -**Timeline:** 3 days (6 hours of focused work) -**Priority:** High - Blocks new feature development - -## Overview - -This plan addresses only the critical architectural issues that prevent month view implementation. By focusing on the minimal necessary changes, we can add month view in ~500 lines instead of ~2000 lines. - ---- - -## Current Blockers for Month View - -### 🚫 Why Month View Can't Be Added Now - -1. **GridManager is hardcoded for time-based views** - - Assumes everything is hours and columns - - Time axis doesn't make sense for months - - Hour-based scrolling irrelevant - -2. **No strategy pattern for different view types** - - Would need entirely new managers - - Massive code duplication - - Inconsistent behavior - -3. **Config assumes time-based views** - ```typescript - hourHeight: 60, - dayStartHour: 0, - snapInterval: 15 - // These are meaningless for month view! - ``` - -4. **Event rendering tied to time positions** - - Events positioned by minutes - - No concept of day cells - - Can't handle multi-day spans properly - ---- - -## Phase 1: View Strategy Pattern (2 hours) - -### 1.1 Create ViewStrategy Interface -**New file:** `src/strategies/ViewStrategy.ts` - -```typescript -export interface ViewStrategy { - // Core rendering methods - renderGrid(container: HTMLElement, context: ViewContext): void; - renderEvents(events: CalendarEvent[], container: HTMLElement): void; - - // Configuration - getLayoutConfig(): ViewLayoutConfig; - getRequiredConfig(): string[]; // Which config keys this view needs - - // Navigation - getNextPeriod(currentDate: Date): Date; - getPreviousPeriod(currentDate: Date): Date; - getPeriodLabel(date: Date): string; -} - -export interface ViewContext { - currentDate: Date; - config: CalendarConfig; - events: CalendarEvent[]; - container: HTMLElement; -} -``` - -### 1.2 Extract WeekViewStrategy -**New file:** `src/strategies/WeekViewStrategy.ts` - -- Move existing logic from GridRenderer -- Keep all time-based rendering -- Minimal changes to existing code - -```typescript -export class WeekViewStrategy implements ViewStrategy { - renderGrid(container: HTMLElement, context: ViewContext): void { - // Move existing GridRenderer.renderGrid() here - this.createTimeAxis(container); - this.createDayColumns(container, context); - this.createTimeSlots(container); - } - - renderEvents(events: CalendarEvent[], container: HTMLElement): void { - // Move existing EventRenderer logic - // Position by time as before - } -} -``` - -### 1.3 Create MonthViewStrategy -**New file:** `src/strategies/MonthViewStrategy.ts` - -```typescript -export class MonthViewStrategy implements ViewStrategy { - renderGrid(container: HTMLElement, context: ViewContext): void { - // Create 7x6 grid - this.createMonthHeader(container); // Mon-Sun - this.createWeekRows(container, context); - } - - renderEvents(events: CalendarEvent[], container: HTMLElement): void { - // Render as small blocks in day cells - // Handle multi-day spanning - } -} -``` - -### 1.4 Update GridManager -**Modify:** `src/managers/GridManager.ts` - -```typescript -export class GridManager { - private strategy: ViewStrategy; - - setViewStrategy(strategy: ViewStrategy): void { - this.strategy = strategy; - } - - render(): void { - // Delegate to strategy - this.strategy.renderGrid(this.grid, { - currentDate: this.currentWeek, - config: this.config, - events: this.events, - container: this.grid - }); - } -} -``` - ---- - -## Phase 2: Configuration Split (1 hour) - -### 2.1 View-Specific Configs -**New file:** `src/core/ViewConfigs.ts` - -```typescript -// Shared by all views -export interface BaseViewConfig { - locale: string; - firstDayOfWeek: number; - dateFormat: string; - eventColors: Record; -} - -// Week/Day views only -export interface TimeViewConfig extends BaseViewConfig { - hourHeight: number; - dayStartHour: number; - dayEndHour: number; - snapInterval: number; - showCurrentTime: boolean; -} - -// Month view only -export interface MonthViewConfig extends BaseViewConfig { - weeksToShow: number; // Usually 6 - showWeekNumbers: boolean; - compactMode: boolean; - eventLimit: number; // Max events shown per day - showMoreText: string; // "+2 more" -} -``` - -### 2.2 Update CalendarConfig -**Modify:** `src/core/CalendarConfig.ts` - -```typescript -export class CalendarConfig { - private viewConfigs: Map = new Map(); - - constructor() { - // Set defaults for each view - this.viewConfigs.set('week', defaultWeekConfig); - this.viewConfigs.set('month', defaultMonthConfig); - } - - getViewConfig(view: string): T { - return this.viewConfigs.get(view) as T; - } -} -``` - ---- - -## Phase 3: Event Consolidation (1 hour) - -### 3.1 Core Events Only -**New file:** `src/constants/CoreEvents.ts` - -```typescript -export const CoreEvents = { - // View lifecycle (5 events) - VIEW_CHANGED: 'view:changed', - VIEW_RENDERED: 'view:rendered', - - // Navigation (3 events) - DATE_CHANGED: 'date:changed', - PERIOD_CHANGED: 'period:changed', - - // Data (4 events) - EVENTS_LOADING: 'events:loading', - EVENTS_LOADED: 'events:loaded', - EVENT_CLICKED: 'event:clicked', - EVENT_UPDATED: 'event:updated', - - // UI State (3 events) - LOADING_START: 'ui:loading:start', - LOADING_END: 'ui:loading:end', - ERROR: 'ui:error', - - // Grid (3 events) - GRID_RENDERED: 'grid:rendered', - GRID_CLICKED: 'grid:clicked', - CELL_CLICKED: 'cell:clicked' -}; -// Total: ~18 events instead of 102! -``` - -### 3.2 Migration Map -**Modify:** `src/constants/EventTypes.ts` - -```typescript -// Keep old events but map to new ones -export const EventTypes = { - VIEW_CHANGED: CoreEvents.VIEW_CHANGED, // Direct mapping - WEEK_CHANGED: CoreEvents.PERIOD_CHANGED, // Renamed - // ... etc -} as const; -``` - ---- - -## Phase 4: Month-Specific Renderers (2 hours) - -### 4.1 MonthGridRenderer -**New file:** `src/renderers/MonthGridRenderer.ts` - -```typescript -export class MonthGridRenderer { - render(container: HTMLElement, date: Date): void { - const grid = this.createGrid(); - - // Add day headers - ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'].forEach(day => { - grid.appendChild(this.createDayHeader(day)); - }); - - // Add 6 weeks of days - const dates = this.getMonthDates(date); - dates.forEach(weekDates => { - weekDates.forEach(date => { - grid.appendChild(this.createDayCell(date)); - }); - }); - - container.appendChild(grid); - } - - private createGrid(): HTMLElement { - const grid = document.createElement('div'); - grid.className = 'month-grid'; - grid.style.display = 'grid'; - grid.style.gridTemplateColumns = 'repeat(7, 1fr)'; - return grid; - } -} -``` - -### 4.2 MonthEventRenderer -**New file:** `src/renderers/MonthEventRenderer.ts` - -```typescript -export class MonthEventRenderer { - render(events: CalendarEvent[], container: HTMLElement): void { - const dayMap = this.groupEventsByDay(events); - - dayMap.forEach((dayEvents, dateStr) => { - const dayCell = container.querySelector(`[data-date="${dateStr}"]`); - if (!dayCell) return; - - const limited = dayEvents.slice(0, 3); // Show max 3 - limited.forEach(event => { - dayCell.appendChild(this.createEventBlock(event)); - }); - - if (dayEvents.length > 3) { - dayCell.appendChild(this.createMoreIndicator(dayEvents.length - 3)); - } - }); - } -} -``` - ---- - -## Phase 5: Integration (1 hour) - -### 5.1 Wire ViewManager -**Modify:** `src/managers/ViewManager.ts` - -```typescript -private changeView(newView: CalendarView): void { - // Create appropriate strategy - let strategy: ViewStrategy; - - switch(newView) { - case 'week': - case 'day': - strategy = new WeekViewStrategy(); - break; - case 'month': - strategy = new MonthViewStrategy(); - break; - } - - // Update GridManager - this.gridManager.setViewStrategy(strategy); - - // Trigger re-render - this.eventBus.emit(CoreEvents.VIEW_CHANGED, { view: newView }); -} -``` - -### 5.2 Enable Month Button -**Modify:** `wwwroot/index.html` - -```html - -Month -``` - -### 5.3 Add Month Styles -**New file:** `wwwroot/css/calendar-month-css.css` - -```css -.month-grid { - display: grid; - grid-template-columns: repeat(7, 1fr); - gap: 1px; - background: var(--color-border); -} - -.month-day-cell { - background: white; - min-height: 100px; - padding: 4px; - position: relative; -} - -.month-day-number { - font-weight: bold; - margin-bottom: 4px; -} - -.month-event { - font-size: 0.75rem; - padding: 2px 4px; - margin: 1px 0; - border-radius: 2px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.month-more-indicator { - font-size: 0.7rem; - color: var(--color-text-secondary); - cursor: pointer; -} -``` - ---- - -## Implementation Timeline - -### Day 1 (Monday) -**Morning (2 hours)** -- [ ] Implement ViewStrategy interface -- [ ] Extract WeekViewStrategy -- [ ] Create MonthViewStrategy skeleton - -**Afternoon (1 hour)** -- [ ] Split configuration -- [ ] Update CalendarConfig - -### Day 2 (Tuesday) -**Morning (2 hours)** -- [ ] Consolidate events to CoreEvents -- [ ] Create migration mappings -- [ ] Update critical event listeners - -**Afternoon (2 hours)** -- [ ] Implement MonthGridRenderer -- [ ] Implement MonthEventRenderer - -### Day 3 (Wednesday) -**Morning (2 hours)** -- [ ] Wire everything in ViewManager -- [ ] Update HTML and CSS -- [ ] Test month view -- [ ] Fix edge cases - ---- - -## Success Metrics - -### ✅ Definition of Done -- [ ] Month view displays 6 weeks correctly -- [ ] Events show in day cells (max 3 + "more") -- [ ] Navigation works (prev/next month) -- [ ] Switching between week/month works -- [ ] No regression in week view -- [ ] Under 750 lines of new code - -### 📊 Expected Impact -- **New code:** ~500-750 lines (vs 2000 without refactoring) -- **Reusability:** 80% of components shared -- **Future views:** Day view = 100 lines, Year view = 200 lines -- **Test coverage:** Easy to test strategies independently -- **Performance:** No impact on existing views - ---- - -## Risk Mitigation - -### Potential Issues & Solutions - -1. **CSS conflicts between views** - - Solution: Namespace all month CSS with `.month-view` - -2. **Event overlap in month cells** - - Solution: Implement "more" indicator after 3 events - -3. **Performance with many events** - - Solution: Only render visible month - -4. **Browser compatibility** - - Solution: Use CSS Grid with flexbox fallback - ---- - -## Next Steps After Month View - -Once this refactoring is complete, adding new views becomes trivial: - -- **Day View:** ~100 lines (reuse WeekViewStrategy with 1 column) -- **Year View:** ~200 lines (12 small month grids) -- **Agenda View:** ~150 lines (list layout) -- **Timeline View:** ~300 lines (horizontal time axis) - -The strategy pattern makes the calendar truly extensible! \ No newline at end of file diff --git a/calendar-complete-specification.md b/calendar-complete-specification.md deleted file mode 100644 index 9ddb463..0000000 --- a/calendar-complete-specification.md +++ /dev/null @@ -1,460 +0,0 @@ -# Complete Calendar Component Specification - -## 1. Project Overview - -### Purpose -Build a professional calendar component with week, day, and month views, featuring drag-and-drop functionality, event management, and real-time synchronization. - -### Technology Stack -- **Frontend**: Vanilla JavaScript (ES Modules), ready for TypeScript conversion -- **Styling**: CSS with nested selectors, CSS Grid/Flexbox -- **Backend** (planned): .NET Core with SignalR -- **Architecture**: Modular manager-based system with event-driven communication - -### Design Principles -1. **Modularity**: Each manager handles one specific concern -2. **Loose Coupling**: Communication via custom events on document -3. **No External Dependencies**: Pure JavaScript implementation -4. **Custom HTML Tags**: Semantic markup without Web Components registration -5. **CSS-based Positioning**: Events positioned using CSS calc() and variables - -## 2. What Has Been Implemented - -### 2.1 Core Infrastructure - -#### EventBus.js ✅ -- Central event dispatcher for all calendar events -- Publish/subscribe pattern implementation -- Debug logging capabilities -- Event history tracking -- Priority-based listeners - -#### CalendarConfig.js ✅ -- Centralized configuration management -- Default values for all settings -- DOM data-attribute reading -- Computed value calculations (minuteHeight, totalSlots, etc.) -- Configuration change events - -#### EventTypes.js ✅ -- All event type constants defined -- Organized by category (view, CRUD, interaction, UI, data, state) -- Consistent naming convention - -### 2.2 Managers - -#### GridManager.js ✅ -- Renders time axis with configurable hours -- Creates week headers with day names and dates -- Generates day columns for events -- Sets up grid interactions (click, dblclick) -- Updates CSS variables for dynamic styling -- Handles grid click position calculations with snap - -#### DataManager.js ✅ -- Mock data generation for testing -- API request preparation (ready for backend) -- Cache management -- Event CRUD operations -- Loading state management -- Sync status handling - -### 2.3 Utilities - -#### DateUtils.js ✅ -- Week start/end calculations -- Date/time formatting (12/24 hour) -- Duration calculations -- Time-to-minutes conversions -- Week number calculation (ISO standard) -- Snap-to-interval logic - -### 2.4 Styles - -#### base.css ✅ -- CSS reset and variables -- Color scheme definition -- Grid measurements -- Animation keyframes -- Utility classes - -#### layout.css ✅ -- Main calendar container structure -- CSS Grid layout for calendar -- Time axis styling -- Week headers with sticky positioning -- Scrollable content area -- Work hours background indication - -#### navigation.css ✅ -- Top navigation bar layout -- Button styling (prev/next/today) -- View selector (day/week/month) -- Search box with icons -- Week info display - -#### events.css ✅ -- Event card styling by type -- Hover and active states -- Resize handles design -- Multi-day event styling -- Sync status indicators -- CSS-based positioning system - -#### popup.css ✅ -- Event popup styling -- Chevron arrow positioning -- Action buttons -- Loading overlay -- Snap indicators - -### 2.5 HTML Structure ✅ -- Semantic custom HTML tags -- Modular component structure -- No inline styles or JavaScript -- Data attributes for configuration - -## 3. Implementation Details - -### 3.1 Event Positioning System -```css -swp-event { - /* Position via CSS variables */ - top: calc(var(--start-minutes) * var(--minute-height)); - height: calc(var(--duration-minutes) * var(--minute-height)); -} -``` - -### 3.2 Custom Event Flow -```javascript -// Example event flow for drag operation -1. User mousedown on event -2. DragManager → emit('calendar:dragstart') -3. ResizeManager → disable() -4. GridManager → show snap lines -5. User mousemove -6. DragManager → emit('calendar:dragmove') -7. EventRenderer → update ghost position -8. User mouseup -9. DragManager → emit('calendar:dragend') -10. EventManager → update event data -11. DataManager → sync to backend -``` - -### 3.3 Configuration Options -```javascript -{ - view: 'week', // 'day' | 'week' | 'month' - weekDays: 7, // 4-7 days for week view - dayStartHour: 7, // Calendar start time - dayEndHour: 19, // Calendar end time - workStartHour: 8, // Work hours highlighting - workEndHour: 17, - snapInterval: 15, // Minutes: 5, 10, 15, 30, 60 - hourHeight: 60, // Pixels per hour - showCurrentTime: true, - allowDrag: true, - allowResize: true, - allowCreate: true -} -``` - -## 4. What Needs to Be Implemented - -### 4.1 Missing Managers - -#### CalendarManager.js 🔲 -**Purpose**: Main coordinator for all managers -```javascript -class CalendarManager { - - Initialize all managers in correct order - - Handle app lifecycle (start, destroy) - - Coordinate cross-manager operations - - Global error handling - - State persistence -} -``` - -#### ViewManager.js 🔲 -**Purpose**: Handle view mode changes -```javascript -class ViewManager { - - Switch between day/week/month views - - Calculate visible date range - - Update grid structure for view - - Emit view change events - - Handle view-specific settings -} -``` - -#### NavigationManager.js 🔲 -**Purpose**: Handle navigation controls -```javascript -class NavigationManager { - - Previous/Next period navigation - - Today button functionality - - Update week info display - - Coordinate with animations - - Handle navigation limits -} -``` - -#### EventManager.js 🔲 -**Purpose**: Manage event lifecycle -```javascript -class EventManager { - - Store events in memory - - Handle event CRUD operations - - Manage event selection - - Calculate event overlaps - - Validate event constraints -} -``` - -#### EventRenderer.js 🔲 -**Purpose**: Render events in DOM -```javascript -class EventRenderer { - - Create event DOM elements - - Calculate pixel positions - - Handle collision layouts - - Render multi-day events - - Update event appearance -} -``` - -#### DragManager.js 🔲 -**Purpose**: Handle drag operations -```javascript -class DragManager { - - Track drag state - - Create ghost element - - Calculate snap positions - - Validate drop targets - - Handle multi-select drag -} -``` - -#### ResizeManager.js 🔲 -**Purpose**: Handle resize operations -```javascript -class ResizeManager { - - Add/remove resize handles - - Track resize direction - - Calculate new duration - - Enforce min/max limits - - Snap to intervals -} -``` - -#### PopupManager.js 🔲 -**Purpose**: Show event details popup -```javascript -class PopupManager { - - Show/hide popup - - Smart positioning (left/right) - - Update popup content - - Handle action buttons - - Click-outside detection -} -``` - -#### SearchManager.js 🔲 -**Purpose**: Search functionality -```javascript -class SearchManager { - - Real-time search - - Highlight matching events - - Update transparency - - Clear search - - Search history -} -``` - -#### TimeManager.js 🔲 -**Purpose**: Current time indicator -```javascript -class TimeManager { - - Show red line at current time - - Update position every minute - - Auto-scroll to current time - - Show/hide based on view -} -``` - -#### LoadingManager.js 🔲 -**Purpose**: Loading states -```javascript -class LoadingManager { - - Show/hide spinner - - Block interactions - - Show error states - - Progress indication -} -``` - -### 4.2 Missing Utilities - -#### PositionUtils.js 🔲 -```javascript -- pixelsToMinutes(y, config) -- minutesToPixels(minutes, config) -- getEventBounds(element) -- detectCollisions(events) -- calculateOverlapGroups(events) -``` - -#### SnapUtils.js 🔲 -```javascript -- snapToInterval(value, interval) -- getNearestSlot(position, interval) -- calculateSnapPoints(config) -- isValidSnapPosition(position) -``` - -#### DOMUtils.js 🔲 -```javascript -- createElement(tag, attributes, children) -- toggleClass(element, className, force) -- findParent(element, selector) -- batchUpdate(updates) -``` - -### 4.3 Missing Features - -#### Animation System 🔲 -- Week-to-week slide transition (as shown in POC) -- Smooth state transitions -- Drag preview animations -- Loading animations - -#### Collision Detection System 🔲 -```javascript -// Two strategies needed: -1. Side-by-side: Events share column width -2. Overlay: Events stack with z-index -``` - -#### Multi-day Event Support 🔲 -- Events spanning multiple days -- Visual continuation indicators -- Proper positioning in week header area - -#### Touch Support 🔲 -- Touch drag/drop -- Pinch to zoom -- Swipe navigation -- Long press for context menu - -#### Keyboard Navigation 🔲 -- Tab through events -- Arrow keys for selection -- Enter to edit -- Delete key support - -#### Context Menu 🔲 -- Right-click on events -- Right-click on empty slots -- Quick actions menu - -#### Event Creation 🔲 -- Double-click empty slot -- Drag to create -- Default duration -- Inline editing - -#### Advanced Features 🔲 -- Undo/redo stack -- Copy/paste events -- Bulk operations -- Print view -- Export (iCal, PDF) -- Recurring events UI -- Event templates -- Color customization -- Resource scheduling -- Timezone support - -## 5. Integration Points - -### 5.1 Backend API Endpoints -``` -GET /api/events?start={date}&end={date}&view={view} -POST /api/events -PATCH /api/events/{id} -DELETE /api/events/{id} -GET /api/events/search?q={query} -``` - -### 5.2 SignalR Events -``` -- EventCreated -- EventUpdated -- EventDeleted -- EventsReloaded -``` - -### 5.3 Data Models -```typescript -interface CalendarEvent { - id: string; - title: string; - start: string; // ISO 8601 - end: string; // ISO 8601 - type: 'meeting' | 'meal' | 'work' | 'milestone'; - allDay: boolean; - syncStatus: 'synced' | 'pending' | 'error'; - recurringId?: string; - resources?: string[]; - metadata?: Record; -} -``` - -## 6. Performance Considerations - -1. **Virtual Scrolling**: For large date ranges -2. **Event Pooling**: Reuse DOM elements -3. **Throttled Updates**: During drag/resize -4. **Batch Operations**: For multiple changes -5. **Lazy Loading**: Load events as needed -6. **Web Workers**: For heavy calculations - -## 7. Testing Strategy - -1. **Unit Tests**: Each manager/utility -2. **Integration Tests**: Manager interactions -3. **E2E Tests**: User workflows -4. **Performance Tests**: Large datasets -5. **Accessibility Tests**: Keyboard/screen reader - -## 8. Deployment Considerations - -1. **Build Process**: Bundle modules -2. **Minification**: Reduce file size -3. **Code Splitting**: Load on demand -4. **CDN**: Static assets -5. **Monitoring**: Error tracking -6. **Analytics**: Usage patterns - -## 9. Future Enhancements - -1. **AI Integration**: Smart scheduling -2. **Mobile Apps**: Native wrappers -3. **Offline Support**: Service workers -4. **Collaboration**: Real-time cursors -5. **Advanced Analytics**: Usage insights -6. **Third-party Integrations**: Google Calendar, Outlook - -## 10. Migration Path - -### From POC to Production: -1. Extract animation logic from POC -2. Implement missing managers -3. Add error boundaries -4. Implement loading states -5. Add accessibility -6. Performance optimization -7. Security hardening -8. Documentation -9. Testing suite -10. Deployment pipeline \ No newline at end of file diff --git a/code_review.md b/code_review.md deleted file mode 100644 index cbf0fdb..0000000 --- a/code_review.md +++ /dev/null @@ -1,398 +0,0 @@ -# Calendar Plantempus - Comprehensive TypeScript Code Review - -## Executive Summary - -This is a well-architected calendar application built with vanilla TypeScript and DOM APIs, implementing sophisticated event-driven communication patterns and drag-and-drop functionality. The codebase demonstrates advanced TypeScript usage, clean separation of concerns, and performance-optimized DOM manipulation. - -## Architecture Overview - -### Core Design Patterns - -**Event-Driven Architecture**: The application uses a centralized EventBus system with DOM CustomEvents for all inter-component communication. This eliminates tight coupling and provides excellent separation of concerns. - -**Manager Pattern**: Each domain responsibility is encapsulated in dedicated managers, creating a modular architecture that's easy to maintain and extend. - -**Strategy Pattern**: View rendering uses strategy pattern with `DateEventRenderer` and `ResourceEventRenderer` implementations. - -**Factory Pattern**: Used for creating managers and calendar types, promoting loose coupling. - -### Key Architectural Strengths - -1. **Pure DOM/TypeScript Implementation**: No external frameworks reduces bundle size and complexity -2. **Centralized Configuration**: Singleton pattern for configuration management -3. **Type Safety**: Comprehensive TypeScript types with proper union types and interfaces -4. **Performance Optimizations**: Extensive use of caching, batching, and optimized DOM queries - ---- - -## Core System Analysis - -### 1. EventBus System (`src/core/EventBus.ts`) ⭐⭐⭐⭐⭐ - -**Strengths:** -- Pure DOM CustomEvents implementation - elegant and leverages browser event system -- Comprehensive logging with categorization and filtering -- Proper memory management with listener tracking -- Singleton pattern with clean API -- Built-in debug mode with visual categorization - -**Code Quality:** -```typescript -// Excellent event emission with proper validation -emit(eventType: string, detail: any = {}): boolean { - if (!eventType || typeof eventType !== 'string') { - return false; - } - const event = new CustomEvent(eventType, { - detail, - bubbles: true, - cancelable: true - }); - return !document.dispatchEvent(event); -} -``` - -**Minor Improvements:** -- `logEventWithGrouping` method is incomplete (line 105) -- Could benefit from TypeScript generics for type-safe detail objects - -### 2. Type System (`src/types/*.ts`) ⭐⭐⭐⭐⭐ - -**Exceptional Type Safety:** -```typescript -export interface CalendarEvent { - id: string; - title: string; - start: string; // ISO 8601 - end: string; // ISO 8601 - type: string; - allDay: boolean; - syncStatus: SyncStatus; - resource?: Resource; - recurringId?: string; - metadata?: Record; -} -``` - -**Highlights:** -- Union types for view management (`ViewPeriod`, `CalendarMode`) -- Discriminated unions with `DateModeContext` and `ResourceModeContext` -- Proper interface segregation -- Consistent ISO 8601 date handling - ---- - -## Drag and Drop System Deep Dive ⭐⭐⭐⭐⭐ - -### DragDropManager (`src/managers/DragDropManager.ts`) - -This is the crown jewel of the codebase - a sophisticated, performance-optimized drag-and-drop system. - -#### Technical Excellence: - -**1. Performance Optimizations:** -```typescript -// Consolidated position calculations to reduce DOM queries -private calculateDragPosition(mousePosition: Position): { column: string | null; snappedY: number } { - const column = this.detectColumn(mousePosition.x, mousePosition.y); - const snappedY = this.calculateSnapPosition(mousePosition.y, column); - return { column, snappedY }; -} -``` - -**2. Intelligent Caching:** -```typescript -private cachedElements: CachedElements = { - scrollContainer: null, - currentColumn: null, - lastColumnDate: null -}; -``` - -**3. Smooth Auto-Scroll:** -```typescript -private startAutoScroll(direction: 'up' | 'down'): void { - const scroll = () => { - const scrollAmount = direction === 'up' ? -this.scrollSpeed : this.scrollSpeed; - this.cachedElements.scrollContainer!.scrollTop += scrollAmount; - this.autoScrollAnimationId = requestAnimationFrame(scroll); - }; - this.autoScrollAnimationId = requestAnimationFrame(scroll); -} -``` - -**4. Advanced Features:** -- **Grid Snapping**: Intelligent snapping to 15-minute intervals -- **Column Detection**: Efficient column switching with caching -- **Auto-scroll**: Smooth scrolling when dragging near edges -- **All-day Conversion**: Seamless conversion from timed to all-day events -- **Mouse Offset Preservation**: Maintains grab point during drag - -#### Event Flow Architecture: -``` -MouseDown → DragStart → DragMove → (Auto-scroll) → DragEnd - ↓ ↓ ↓ ↓ ↓ -EventBus → EventRenderer → Visual Update → Position → Finalize -``` - -**Minor Issues:** -- Some hardcoded values (40px for stacking threshold at line 379) -- Mixed Danish and English comments - ---- - -## Event Rendering System ⭐⭐⭐⭐ - -### EventRenderer (`src/renderers/EventRenderer.ts`) - -**Sophisticated Overlap Management:** -```typescript -// Intelligent overlap detection with pixel-perfect precision -private detectPixelOverlap(element1: HTMLElement, element2: HTMLElement): OverlapType { - const top1 = parseFloat(element1.style.top) || 0; - const height1 = parseFloat(element1.style.height) || 0; - const bottom1 = top1 + height1; - - const top2 = parseFloat(element2.style.top) || 0; - const height2 = parseFloat(element2.style.height) || 0; - const bottom2 = top2 + height2; - - if (bottom1 <= top2 || bottom2 <= top1) { - return OverlapType.NONE; - } - - const startDifference = Math.abs(top1 - top2); - return startDifference > 40 ? OverlapType.STACKING : OverlapType.COLUMN_SHARING; -} -``` - -**Advanced Drag Integration:** -- Real-time timestamp updates during drag -- Seamless event cloning with proper cleanup -- Intelligent overlap re-calculation after drops - -**Architectural Strengths:** -- Strategy pattern with `DateEventRenderer` and `ResourceEventRenderer` -- Proper separation of rendering logic from positioning -- Clean drag state management - -### SimpleEventOverlapManager (`src/managers/SimpleEventOverlapManager.ts`) - -**Clean, Data-Attribute Based Overlap System:** -```typescript -public detectOverlap(event1: CalendarEvent, event2: CalendarEvent): OverlapType { - if (!this.eventsOverlapInTime(event1, event2)) { - return OverlapType.NONE; - } - - const timeDiffMinutes = Math.abs( - new Date(event1.start).getTime() - new Date(event2.start).getTime() - ) / (1000 * 60); - - return timeDiffMinutes > 30 ? OverlapType.STACKING : OverlapType.COLUMN_SHARING; -} -``` - -**Key Improvements Over Legacy System:** -- **Data-Attribute Tracking**: Uses `data-stack-link` instead of in-memory Maps -- **Simplified State Management**: DOM is the single source of truth -- **51% Less Code**: Eliminated complex linked list management -- **Zero State Sync Bugs**: No memory/DOM synchronization issues - -**Visual Layout Strategies:** -- **Column Sharing**: Flexbox layout for concurrent events -- **Stacking**: Margin-left offsets with z-index management via data attributes -- **Dynamic Grouping**: Real-time group creation and cleanup - ---- - -## Manager System Analysis - -### CalendarManager (`src/managers/CalendarManager.ts`) ⭐⭐⭐⭐ - -**Excellent Orchestration:** -- Clean initialization sequence with proper error handling -- Intelligent view and date management -- WorkWeek change handling with full grid rebuilds - -**Smart Period Calculations:** -```typescript -private calculateCurrentPeriod(): { start: string; end: string } { - switch (this.currentView) { - case 'week': - const weekStart = new Date(current); - const dayOfWeek = weekStart.getDay(); - const daysToMonday = dayOfWeek === 0 ? 6 : dayOfWeek - 1; - weekStart.setDate(weekStart.getDate() - daysToMonday); - // ... proper ISO week calculation - } -} -``` - -### EventManager (`src/managers/EventManager.ts`) ⭐⭐⭐⭐ - -**Performance Optimizations:** -```typescript -// Intelligent caching for period queries -public getEventsForPeriod(startDate: Date, endDate: Date): CalendarEvent[] { - const cacheKey = `${DateCalculator.formatISODate(startDate)}_${DateCalculator.formatISODate(endDate)}`; - - if (this.lastCacheKey === cacheKey && this.eventCache.has(cacheKey)) { - return this.eventCache.get(cacheKey)!; - } - // ... filter and cache logic -} -``` - -**Strengths:** -- Resource and date calendar support -- Proper cache invalidation -- Event navigation with error handling -- Mock data loading with proper async patterns - -### ViewManager (`src/managers/ViewManager.ts`) ⭐⭐⭐⭐ - -**Clean State Management:** -```typescript -// Generic button group setup eliminates duplicate code -private setupButtonGroup(selector: string, attribute: string, handler: (value: string) => void): void { - const buttons = document.querySelectorAll(selector); - buttons.forEach(button => { - const clickHandler = (event: Event) => { - event.preventDefault(); - const value = button.getAttribute(attribute); - if (value) handler(value); - }; - button.addEventListener('click', clickHandler); - this.buttonListeners.set(button, clickHandler); - }); -} -``` - -**Performance Features:** -- Button caching with cache invalidation (5-second TTL) -- Consolidated button update logic -- Proper event listener cleanup - ---- - -## Utility System Excellence - -### DateCalculator (`src/utils/DateCalculator.ts`) ⭐⭐⭐⭐⭐ - -**Exceptional Date Handling:** -```typescript -// Proper ISO 8601 week calculation -static getWeekNumber(date: Date): number { - const d = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate())); - const dayNum = d.getUTCDay() || 7; - d.setUTCDate(d.getUTCDate() + 4 - dayNum); - const yearStart = new Date(Date.UTC(d.getUTCFullYear(),0,1)); - return Math.ceil((((d.getTime() - yearStart.getTime()) / 86400000) + 1)/7); -} -``` - -**Features:** -- Static class pattern for performance -- Comprehensive date validation -- ISO week handling (Monday start) -- Internationalization support with `Intl.DateTimeFormat` -- Proper timezone handling - -### PositionUtils (`src/utils/PositionUtils.ts`) ⭐⭐⭐⭐ - -**Pixel-Perfect Calculations:** -```typescript -public static snapToGrid(pixels: number): number { - const gridSettings = calendarConfig.getGridSettings(); - const snapInterval = gridSettings.snapInterval; - const snapPixels = PositionUtils.minutesToPixels(snapInterval); - - return Math.round(pixels / snapPixels) * snapPixels; -} -``` - -**Strengths:** -- Delegate date operations to DateCalculator (proper separation) -- Comprehensive position/time conversions -- Grid snapping with configurable intervals -- Work hours validation - ---- - -## Performance Analysis - -### Optimizations Implemented: - -1. **DOM Query Caching**: Cached elements with TTL-based invalidation -2. **Event Batching**: Consolidated position calculations in drag system -3. **Efficient Event Filtering**: Map-based caching for period queries -4. **Lazy Loading**: Components only query DOM when needed -5. **Memory Management**: Proper cleanup of event listeners and cached references - -### Performance Metrics: -- Drag operations: ~60fps through requestAnimationFrame -- Event rendering: O(n log n) complexity with overlap grouping -- View switching: Cached button states prevent unnecessary DOM queries - ---- - -## Code Quality Assessment - -### Strengths: -- **Type Safety**: Comprehensive TypeScript with no `any` types -- **Error Handling**: Proper validation and graceful degradation -- **Memory Management**: Cleanup methods in all managers -- **Documentation**: Good inline documentation and method signatures -- **Consistency**: Uniform coding patterns throughout - -### Technical Debt: -1. **Mixed Languages**: Danish and English comments/variables -2. **Hardcoded Values**: Some magic numbers (40px threshold, 5s cache TTL) -3. **Configuration**: Some values should be configurable -4. **Testing**: No visible test suite - -### Security Considerations: -- No eval() usage -- Proper DOM sanitization in event rendering -- No direct innerHTML with user data - ---- - -## Architecture Recommendations - -### Immediate Improvements: -1. **Internationalization**: Standardize to English or implement proper i18n -2. **Configuration**: Move hardcoded values to configuration -3. **Testing**: Add unit tests for critical drag-and-drop logic -4. **Documentation**: Add architectural decision records (ADRs) - -### Future Enhancements: -1. **Web Workers**: Move heavy calculations off main thread -2. **Virtual Scrolling**: For large event sets -3. **Touch Support**: Enhanced mobile drag-and-drop -4. **Accessibility**: ARIA labels and keyboard navigation - ---- - -## Conclusion - -This is an exceptionally well-crafted calendar application that demonstrates: - -- **Advanced TypeScript Usage**: Proper types, interfaces, and modern patterns -- **Performance Excellence**: Sophisticated caching, batching, and optimization -- **Clean Architecture**: Event-driven design with proper separation of concerns -- **Production Ready**: Comprehensive error handling and memory management - -**Overall Rating: ⭐⭐⭐⭐⭐ (Exceptional)** - -The drag-and-drop system, in particular, is a masterclass in performance optimization and user experience design. The EventBus architecture provides a solid foundation for future enhancements. - -**Key Technical Achievements:** -- Zero-framework implementation with modern browser APIs -- Sophisticated event overlap detection and rendering -- Performance-optimized drag operations with smooth auto-scroll -- Comprehensive date/time handling with internationalization support -- Clean, maintainable codebase with excellent type safety - -This codebase serves as an excellent example of how to build complex DOM applications with vanilla TypeScript while maintaining high performance and code quality standards. \ No newline at end of file diff --git a/complexity_comparison.md b/complexity_comparison.md deleted file mode 100644 index a6372fa..0000000 --- a/complexity_comparison.md +++ /dev/null @@ -1,182 +0,0 @@ -# EventOverlapManager Complexity Comparison - -## Original vs Simplified Implementation - -### **Lines of Code Comparison** - -| Aspect | Original | Simplified | Reduction | -|--------|----------|------------|-----------| -| Total Lines | ~453 lines | ~220 lines | **51% reduction** | -| Stack Management | ~150 lines | ~20 lines | **87% reduction** | -| State Tracking | Complex Map + linked list | Simple DOM queries | **100% elimination** | - ---- - -## **Key Simplifications** - -### 1. **Eliminated Complex State Tracking** - -**Before:** -```typescript -// Complex linked list tracking -private stackChains = new Map(); - -private removeFromStackChain(eventId: string): string[] { - // 25+ lines of linked list manipulation - const chainInfo = this.stackChains.get(eventId); - // Complex prev/next linking logic... -} -``` - -**After:** -```typescript -// Simple DOM-based approach -public restackEventsInContainer(container: HTMLElement): void { - const stackedEvents = Array.from(container.querySelectorAll('swp-event')) - .filter(el => this.isStackedEvent(el as HTMLElement)); - - stackedEvents.forEach((element, index) => { - element.style.marginLeft = `${(index + 1) * 15}px`; - }); -} -``` - -### 2. **Simplified Event Detection** - -**Before:** -```typescript -public isStackedEvent(element: HTMLElement): boolean { - const eventId = element.dataset.eventId; - const hasMarginLeft = element.style.marginLeft !== ''; - const isInStackChain = eventId ? this.stackChains.has(eventId) : false; - - // Two different ways to track the same thing - return hasMarginLeft || isInStackChain; -} -``` - -**After:** -```typescript -public isStackedEvent(element: HTMLElement): boolean { - const marginLeft = element.style.marginLeft; - return marginLeft !== '' && marginLeft !== '0px'; -} -``` - -### 3. **Cleaner Group Management** - -**Before:** -```typescript -public removeFromEventGroup(container: HTMLElement, eventId: string): boolean { - // 50+ lines including: - // - Stack chain checking - // - Complex position calculations - // - Multiple cleanup scenarios - // - Affected event re-stacking -} -``` - -**After:** -```typescript -public removeFromEventGroup(container: HTMLElement, eventId: string): boolean { - // 20 lines of clean, focused logic: - // - Remove element - // - Handle remaining events - // - Simple container cleanup -} -``` - ---- - -## **Benefits of Simplified Approach** - -### ✅ **Maintainability** -- **No complex state synchronization** -- **Single source of truth (DOM)** -- **Easier to debug and understand** - -### ✅ **Performance** -- **No Map lookups or linked list traversal** -- **Direct DOM queries when needed** -- **Simpler memory management** - -### ✅ **Reliability** -- **No state desynchronization bugs** -- **Fewer edge cases** -- **More predictable behavior** - -### ✅ **Code Quality** -- **51% fewer lines of code** -- **Simpler mental model** -- **Better separation of concerns** - ---- - -## **What Was Eliminated** - -### 🗑️ **Removed Complexity** -1. **Linked List Management**: Complex `next`/`prev` chain tracking -2. **State Synchronization**: Keeping DOM and Map in sync -3. **Chain Reconstruction**: Complex re-linking after removals -4. **Dual Tracking**: Both style-based and Map-based state -5. **Edge Case Handling**: Complex scenarios from state mismatches - -### 🎯 **Retained Functionality** -1. **Column Sharing**: Flexbox groups work exactly the same -2. **Event Stacking**: Visual stacking with margin-left offsets -3. **Overlap Detection**: Same time-based algorithm -4. **Drag and Drop**: Full drag support maintained -5. **Visual Appearance**: Identical user experience - ---- - -## **Risk Assessment** - -### ⚠️ **Potential Concerns** -1. **DOM Query Performance**: More DOM queries vs Map lookups - - **Mitigation**: Queries are scoped to specific containers - - **Reality**: Minimal impact for typical calendar usage - -2. **State Reconstruction**: Re-calculating vs cached state - - **Mitigation**: DOM is the single source of truth - - **Reality**: Eliminates sync bugs completely - -### ✅ **Benefits Outweigh Risks** -- **Dramatically simpler codebase** -- **Eliminated entire class of state sync bugs** -- **Much easier to debug and maintain** -- **Better separation of concerns** - ---- - -## **Migration Strategy** - -1. ✅ **Created SimpleEventOverlapManager** -2. ✅ **Updated EventRenderer imports** -3. ✅ **Simplified drag handling methods** -4. ✅ **Maintained API compatibility** -5. ✅ **Testing phase completed** -6. ✅ **Removed old EventOverlapManager** (legacy code eliminated) - ---- - -## **Conclusion** - -The simplified approach provides **identical functionality** with: -- **51% less code** -- **87% simpler stack management** -- **Zero state synchronization bugs** -- **Much easier maintenance** - -**Migration completed successfully** - the old EventOverlapManager has been removed and the system now uses the cleaner SimpleEventOverlapManager implementation. - -This is a perfect example of how **complexity often accumulates unnecessarily** and how a **DOM-first approach** can be both simpler and more reliable than complex state management. - -## **See Also** - -- [Stack Binding System Documentation](docs/stack-binding-system.md) - Detailed explanation of how events are linked together -- [`SimpleEventOverlapManager.ts`](src/managers/SimpleEventOverlapManager.ts) - Current implementation \ No newline at end of file diff --git a/data_attribute_solution.md b/data_attribute_solution.md deleted file mode 100644 index 2b6c8cb..0000000 --- a/data_attribute_solution.md +++ /dev/null @@ -1,221 +0,0 @@ -# Data-Attribute Stack Tracking Solution - -## Implementation Summary - -Vi har nu implementeret stack tracking via data attributes i stedet for komplekse Map-baserede linked lists. - -### 🎯 **How it works:** - -#### **Stack Links via Data Attributes** -```html - - - - - - - - - - - -``` - -### 🔧 **Key Methods:** - -#### **createStackedEvent()** -```typescript -// Links new event to end of chain -let lastElement = underlyingElement; -while (lastLink?.next) { - lastElement = this.findElementById(lastLink.next); - lastLink = this.getStackLink(lastElement); -} - -// Create bidirectional link -this.setStackLink(lastElement, { ...lastLink, next: eventId }); -this.setStackLink(eventElement, { prev: lastElementId, stackLevel }); -``` - -#### **removeStackedStyling()** -```typescript -// Re-link prev and next -if (link.prev && link.next) { - this.setStackLink(prevElement, { ...prevLink, next: link.next }); - this.setStackLink(nextElement, { ...nextLink, prev: link.prev }); -} - -// Update subsequent stack levels -this.updateSubsequentStackLevels(link.next, -1); -``` - -#### **restackEventsInContainer()** -```typescript -// Group by stack chains (not all stacked events together!) -for (const element of stackedEvents) { - // Find root of chain - while (rootLink?.prev) { - rootElement = this.findElementById(rootLink.prev); - } - - // Collect entire chain - // Re-stack each chain separately -} -``` - ---- - -## 🏆 **Advantages vs Map Solution:** - -### ✅ **Simplified State Management** -| Aspect | Map + Linked List | Data Attributes | -|--------|------------------|-----------------| -| **State Location** | Separate Map object | In DOM elements | -| **Synchronization** | Manual sync required | Automatic with DOM | -| **Memory Cleanup** | Manual Map cleanup | Automatic with element removal | -| **Debugging** | Console logs only | DevTools inspection | -| **State Consistency** | Possible sync bugs | Always consistent | - -### ✅ **Code Complexity Reduction** -```typescript -// OLD: Complex Map management -private stackChains = new Map(); - -// Find last event in chain - complex iteration -let lastEventId = underlyingId; -while (this.stackChains.has(lastEventId) && this.stackChains.get(lastEventId)?.next) { - lastEventId = this.stackChains.get(lastEventId)!.next!; -} - -// Link events - error prone -this.stackChains.get(lastEventId)!.next = eventId; -this.stackChains.set(eventId, { prev: lastEventId, stackLevel }); - -// NEW: Simple data attribute management -let lastElement = underlyingElement; -while (lastLink?.next) { - lastElement = this.findElementById(lastLink.next); -} - -this.setStackLink(lastElement, { ...lastLink, next: eventId }); -this.setStackLink(eventElement, { prev: lastElementId, stackLevel }); -``` - -### ✅ **Better Error Handling** -```typescript -// DOM elements can't get out of sync with their own attributes -// When element is removed, its state automatically disappears -// No orphaned Map entries -``` - ---- - -## 🧪 **Test Scenarios:** - -### **Scenario 1: Multiple Separate Stacks** -``` -Column has: -Stack A: Event1 → Event2 → Event3 (times: 09:00-10:00, 09:15-10:15, 09:30-10:30) -Stack B: Event4 → Event5 (times: 14:00-15:00, 14:10-15:10) - -Remove Event2 (middle of Stack A): -✅ Expected: Event1 → Event3 (Event3 moves to 15px margin) -✅ Expected: Stack B unchanged (Event4→Event5 still at 0px→15px) -❌ Old naive approach: Would group all events together -``` - -### **Scenario 2: Remove Base Event** -``` -Stack: EventA(base) → EventB → EventC - -Remove EventA: -✅ Expected: EventB becomes base (0px), EventC moves to 15px -✅ Data-attribute solution: EventB.stackLevel = 0, EventC.stackLevel = 1 -``` - -### **Scenario 3: Drag and Drop** -``` -Drag Event2 from Stack A to new position: -✅ removeStackedStyling() handles re-linking -✅ Other stack events maintain their relationships -✅ No Map synchronization issues -``` - ---- - -## 🔍 **Debugging Benefits:** - -### **Browser DevTools Inspection:** -```html - - - - -``` - -### **Console Debugging:** -```javascript -// Easy to inspect stack chains -const element = document.querySelector('[data-event-id="456"]'); -const link = JSON.parse(element.dataset.stackLink); -console.log('Stack chain:', link); -``` - ---- - -## 📊 **Performance Comparison:** - -| Operation | Map Solution | Data-Attribute Solution | -|-----------|--------------|-------------------------| -| **Create Stack** | Map.set() + element.style | JSON.stringify() + element.style | -| **Remove Stack** | Map manipulation + DOM queries | JSON.parse/stringify + DOM queries | -| **Find Chain** | Map iteration | DOM traversal | -| **Memory Usage** | Map + DOM | DOM only | -| **Sync Overhead** | High (keep Map in sync) | None (DOM is source) | - -### **Performance Notes:** -- **JSON.parse/stringify**: Very fast for small objects (~10 properties max) -- **DOM traversal**: Limited by chain length (typically 2-5 events) -- **Memory**: Significant reduction (no separate Map) -- **Garbage collection**: Better (automatic cleanup) - ---- - -## ✅ **Solution Status:** - -### **Completed:** -- [x] StackLink interface definition -- [x] Helper methods (getStackLink, setStackLink, findElementById) -- [x] createStackedEvent with data-attribute linking -- [x] removeStackedStyling with proper re-linking -- [x] restackEventsInContainer respects separate chains -- [x] isStackedEvent checks both style and data-attributes -- [x] Compilation successful - -### **Ready for Testing:** -- [ ] Manual UI testing of stack behavior -- [ ] Drag and drop stacked events -- [ ] Multiple stacks in same column -- [ ] Edge cases (remove first/middle/last) - ---- - -## 🎉 **Conclusion:** - -This data-attribute solution provides: -1. **Same functionality** as the Map-based approach -2. **Simpler implementation** (DOM as single source of truth) -3. **Better debugging experience** (DevTools visibility) -4. **Automatic memory management** (no manual cleanup) -5. **No synchronization bugs** (state follows element) - -The solution maintains all the precision of the original complex system while dramatically simplifying the implementation and eliminating entire classes of potential bugs. \ No newline at end of file diff --git a/docs/EventSystem-Analysis.md b/docs/EventSystem-Analysis.md deleted file mode 100644 index 5d70f46..0000000 --- a/docs/EventSystem-Analysis.md +++ /dev/null @@ -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* \ No newline at end of file diff --git a/docs/calendar-initialization-sequence.md b/docs/calendar-initialization-sequence.md deleted file mode 100644 index 22c29df..0000000 --- a/docs/calendar-initialization-sequence.md +++ /dev/null @@ -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 \ No newline at end of file diff --git a/docs/code-improvement-plan.md b/docs/code-improvement-plan.md deleted file mode 100644 index 33901f0..0000000 --- a/docs/code-improvement-plan.md +++ /dev/null @@ -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> { - private cache: T; - - constructor(initialCache: T) { - this.cache = initialCache; - } - - get(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 -} -``` - -### 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. \ No newline at end of file diff --git a/docs/date-mode-initialization-sequence.md b/docs/date-mode-initialization-sequence.md deleted file mode 100644 index 64dce9b..0000000 --- a/docs/date-mode-initialization-sequence.md +++ /dev/null @@ -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. \ No newline at end of file diff --git a/docs/drag-drop-header-bug-analysis-corrected.md b/docs/drag-drop-header-bug-analysis-corrected.md deleted file mode 100644 index d0b1ceb..0000000 --- a/docs/drag-drop-header-bug-analysis-corrected.md +++ /dev/null @@ -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 \ No newline at end of file diff --git a/docs/drag-drop-header-bug-analysis.md b/docs/drag-drop-header-bug-analysis.md deleted file mode 100644 index e2aef1b..0000000 --- a/docs/drag-drop-header-bug-analysis.md +++ /dev/null @@ -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) \ No newline at end of file diff --git a/docs/drag-drop-header-complete-bug-analysis.md b/docs/drag-drop-header-complete-bug-analysis.md deleted file mode 100644 index 85796a1..0000000 --- a/docs/drag-drop-header-complete-bug-analysis.md +++ /dev/null @@ -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(); - -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 \ No newline at end of file diff --git a/docs/drag-drop-header-implementation-details.md b/docs/drag-drop-header-implementation-details.md deleted file mode 100644 index 3da6b43..0000000 --- a/docs/drag-drop-header-implementation-details.md +++ /dev/null @@ -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(); - -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. \ No newline at end of file diff --git a/docs/implementation-todo.md b/docs/implementation-todo.md deleted file mode 100644 index e69de29..0000000 diff --git a/docs/improved-initialization-strategy.md b/docs/improved-initialization-strategy.md deleted file mode 100644 index dec3b03..0000000 --- a/docs/improved-initialization-strategy.md +++ /dev/null @@ -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 { - 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 { - const managers = new Map(); - - // 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 { - // 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 { - 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 { - 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 { - 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; - - private async waitForDOM(selectors: string[]): Promise { - // 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 { - // 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 { - 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. \ No newline at end of file diff --git a/docs/timeformatter-specification.md b/docs/timeformatter-specification.md deleted file mode 100644 index 5bb5ede..0000000 --- a/docs/timeformatter-specification.md +++ /dev/null @@ -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 \ No newline at end of file diff --git a/docs/typescript-code-review-2025.md b/docs/typescript-code-review-2025.md deleted file mode 100644 index 0af9efa..0000000 --- a/docs/typescript-code-review-2025.md +++ /dev/null @@ -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 \ No newline at end of file diff --git a/event-overlap-implementation-plan.md b/event-overlap-implementation-plan.md deleted file mode 100644 index d15ee0c..0000000 --- a/event-overlap-implementation-plan.md +++ /dev/null @@ -1,173 +0,0 @@ -# Event Overlap Rendering Implementation Plan - COMPLETED ✅ - -## Status: IMPLEMENTATION COMPLETED - -This implementation plan has been **successfully completed** using `SimpleEventOverlapManager`. The system now supports both overlap patterns with a clean, data-attribute based approach. - -## Current Implementation - -The system uses `SimpleEventOverlapManager` which provides: -1. **Column Sharing**: Events with similar start times share width using flexbox -2. **Stacking**: Events with >30 min difference stack with margin-left offsets -3. **Data-Attribute Tracking**: Uses `data-stack-link` for chain management -4. **Zero State Sync Issues**: DOM is the single source of truth - -## Oversigt (Original Requirements - COMPLETED) -✅ **Column Sharing**: Events med samme start tid deles om bredden med flexbox -✅ **Stacking**: Events med >30 min forskel ligger oven på med reduceret bredde - -## Test Scenarier (fra mock-events.json) - -### September 2 - Stacking Test -- Event 93: "Team Standup" 09:00-09:30 -- Event 94: "Product Planning" 14:00-16:00 -- Event 96: "Deep Work" 15:00-15:30 (>30 min efter standup, skal være 15px mindre) - -### September 4 - Column Sharing Test -- Event 97: "Team Standup" 09:00-09:30 -- Event 98: "Technical Review" 15:00-16:30 -- Event 100: "Sprint Review" 15:00-16:00 (samme start tid som Technical Review - skal deles 50/50) - -## Teknisk Arkitektur - -### 1. SimpleEventOverlapManager Klasse ✅ IMPLEMENTED -```typescript -class SimpleEventOverlapManager { - detectOverlap(event1: CalendarEvent, event2: CalendarEvent): OverlapType - groupOverlappingEvents(events: CalendarEvent[]): OverlapGroup[] - createEventGroup(events: CalendarEvent[], position: {top: number, height: number}): HTMLElement - addToEventGroup(container: HTMLElement, eventElement: HTMLElement): void - removeFromEventGroup(container: HTMLElement, eventId: string): boolean - createStackedEvent(eventElement: HTMLElement, underlyingElement: HTMLElement, stackLevel: number): void - // Data-attribute based stack tracking - getStackLink(element: HTMLElement): StackLink | null - isStackedEvent(element: HTMLElement): boolean -} -``` - -### 2. CSS Struktur -```css -.event-group { - position: absolute; - display: flex; - gap: 1px; - width: 100%; -} - -.event-group swp-event { - flex: 1; - position: relative; -} - -.stacked-event { - position: absolute; - width: calc(100% - 15px); - right: 0; - z-index: var(--z-stacked-event); -} -``` - -### 3. DOM Struktur -```html - -Single Event - - -
- Event 1 - Event 2 -
- - -Stacked Event -``` - -## Implementation Status ✅ ALL PHASES COMPLETED - -### Phase 1: Core Infrastructure ✅ COMPLETED -1. ✅ Oprettet SimpleEventOverlapManager klasse -2. ✅ Implementeret overlap detection algoritme med proper time overlap checking -3. ✅ Tilføjet CSS klasser for event-group og stacked-event - -### Phase 2: Column Sharing (Flexbox) ✅ COMPLETED -4. ✅ Implementeret createEventGroup metode med flexbox -5. ✅ Implementeret addToEventGroup og removeFromEventGroup -6. ✅ Integreret i BaseEventRenderer.renderEvent - -### Phase 3: Stacking Logic ✅ COMPLETED -7. ✅ Implementeret stacking detection (>30 min forskel) -8. ✅ Implementeret createStackedEvent med margin-left offset -9. ✅ Tilføjet z-index management via data-attributes - -### Phase 4: Drag & Drop Integration ✅ COMPLETED -10. ✅ Modificeret drag & drop handleDragEnd til overlap detection -11. ✅ Implementeret event repositioning ved drop på eksisterende events -12. ✅ Tilføjet cleanup logik for tomme event-group containers - -### Phase 5: Testing & Optimization ✅ COMPLETED -13. ✅ Testet column sharing med events med samme start tid -14. ✅ Testet stacking med events med >30 min forskel -15. ✅ Testet kombinerede scenarier -16. ✅ Performance optimering og cleanup gennemført - -## Algoritmer - -### Overlap Detection -```typescript -function detectOverlap(events: CalendarEvent[]): OverlapType { - const timeDiff = Math.abs(event1.startTime - event2.startTime); - - if (timeDiff === 0) return 'COLUMN_SHARING'; - if (timeDiff > 30 * 60 * 1000) return 'STACKING'; - return 'NORMAL'; -} -``` - -### Column Sharing Calculation -```typescript -function calculateColumnSharing(events: CalendarEvent[]) { - const eventCount = events.length; - // Flexbox håndterer automatisk: flex: 1 på hver event - return { width: `${100 / eventCount}%`, flex: 1 }; -} -``` - -### Stacking Calculation -```typescript -function calculateStacking(underlyingEvent: HTMLElement) { - const underlyingWidth = underlyingEvent.offsetWidth; - return { - width: underlyingWidth - 15, - right: 0, - zIndex: getNextZIndex() - }; -} -``` - -## Event Bus Integration -- `overlap:detected` - Når overlap detekteres -- `overlap:group-created` - Når event-group oprettes -- `overlap:event-stacked` - Når event stacks oven på andet -- `overlap:group-cleanup` - Når tom group fjernes - -## Success Criteria ✅ ALL COMPLETED -- ✅ **Column Sharing**: Events with same start time share width 50/50 -- ✅ **Stacking**: Overlapping events stack with 15px margin-left offset -- ✅ **Drag & Drop**: Full drag & drop support with overlap detection -- ✅ **Cleanup**: Automatic cleanup of empty event-group containers -- ✅ **Z-index Management**: Proper layering with data-attribute tracking -- ✅ **Performance**: 51% code reduction with zero state sync bugs - -## Current Documentation - -- [Stack Binding System](docs/stack-binding-system.md) - Detailed explanation of event linking -- [Complexity Comparison](complexity_comparison.md) - Before/after analysis -- [`SimpleEventOverlapManager.ts`](src/managers/SimpleEventOverlapManager.ts) - Current implementation -- [Code Review](code_review.md) - Technical analysis of the system - -## Key Improvements Achieved - -- **Simplified Architecture**: Data-attribute based instead of complex in-memory Maps -- **Better Reliability**: Zero state synchronization bugs -- **Easier Maintenance**: 51% less code, much cleaner logic -- **Same Functionality**: Identical user experience with better performance \ No newline at end of file diff --git a/overlap-fix-plan.md b/overlap-fix-plan.md deleted file mode 100644 index 64aa43d..0000000 --- a/overlap-fix-plan.md +++ /dev/null @@ -1,48 +0,0 @@ -# Overlap Detection Fix Plan - DEPRECATED - -⚠️ **DEPRECATED**: This plan has been completed and superseded by SimpleEventOverlapManager. - -## Status: COMPLETED ✅ - -The overlap detection issues described in this document have been resolved through the implementation of `SimpleEventOverlapManager`, which replaced the complex `EventOverlapManager`. - -## What Was Implemented - -✅ **Fixed overlap detection logic** - Now properly checks for time overlap before determining overlap type -✅ **Simplified state management** - Uses data-attributes instead of complex in-memory Maps -✅ **Eliminated unnecessary complexity** - 51% reduction in code complexity -✅ **Improved reliability** - Zero state synchronization bugs - -## Current Implementation - -The system now uses `SimpleEventOverlapManager` with: -- **Data-attribute based tracking** via `data-stack-link` -- **Proper time overlap detection** before classification -- **Clean separation** between column sharing and stacking logic -- **Simplified cleanup** and maintenance - -## See Current Documentation - -- [Stack Binding System](docs/stack-binding-system.md) - How events are linked together -- [Complexity Comparison](complexity_comparison.md) - Before/after analysis -- [`SimpleEventOverlapManager.ts`](src/managers/SimpleEventOverlapManager.ts) - Current implementation - ---- - -## Original Problem (RESOLVED) - -~~Den nuværende overlap detection logik i EventOverlapManager tjekker kun på tidsforskel mellem start tidspunkter, men ikke om events faktisk overlapper i tid. Dette resulterer i forkert stacking behavior.~~ - -**Resolution**: SimpleEventOverlapManager now properly checks `eventsOverlapInTime()` before determining overlap type. - -## Original Implementation Plan (COMPLETED) - -All items from the original plan have been implemented in SimpleEventOverlapManager: - -✅ Fixed detectOverlap() method with proper time overlap checking -✅ Added eventsOverlapInTime() method -✅ Removed unnecessary data attributes -✅ Simplified event styling and cleanup -✅ Comprehensive testing completed - -The new implementation provides identical functionality with much cleaner, more maintainable code. \ No newline at end of file diff --git a/refactored-header-manager.md b/refactored-header-manager.md deleted file mode 100644 index 0d77314..0000000 --- a/refactored-header-manager.md +++ /dev/null @@ -1,184 +0,0 @@ -# Refactored HeaderManager - Fjern Ghost Columns - -## 1. HeaderManager Ændringer - -```typescript -// src/managers/HeaderManager.ts - -/** - * Setup header drag event listeners - REFACTORED VERSION - */ -public setupHeaderDragListeners(): void { - const calendarHeader = this.getCalendarHeader(); - if (!calendarHeader) return; - - // Use mouseenter instead of mouseover to avoid continuous firing - this.headerEventListener = (event: Event) => { - const target = event.target as HTMLElement; - - // Check if we're entering the all-day container - const allDayContainer = target.closest('swp-allday-container'); - if (allDayContainer) { - // Calculate target date from mouse X coordinate - const targetDate = this.calculateTargetDateFromMouseX(event as MouseEvent); - - if (targetDate) { - const calendarType = calendarConfig.getCalendarMode(); - const headerRenderer = CalendarTypeFactory.getHeaderRenderer(calendarType); - - eventBus.emit('header:mouseover', { - element: allDayContainer, - targetDate, - headerRenderer - }); - } - } - }; - - // Header mouseleave listener - unchanged - this.headerMouseLeaveListener = (event: Event) => { - eventBus.emit('header:mouseleave', { - element: event.target as HTMLElement - }); - }; - - // Use mouseenter instead of mouseover - calendarHeader.addEventListener('mouseenter', this.headerEventListener, true); - calendarHeader.addEventListener('mouseleave', this.headerMouseLeaveListener); -} - -/** - * Calculate target date from mouse X coordinate - */ -private calculateTargetDateFromMouseX(event: MouseEvent): string | null { - const dayHeaders = document.querySelectorAll('swp-day-header'); - const mouseX = event.clientX; - - for (const header of dayHeaders) { - const headerElement = header as HTMLElement; - const rect = headerElement.getBoundingClientRect(); - - // Check if mouse X is within this header's bounds - if (mouseX >= rect.left && mouseX <= rect.right) { - return headerElement.dataset.date || null; - } - } - - return null; -} - -/** - * Remove event listeners from header - UPDATED - */ -private removeEventListeners(): void { - const calendarHeader = this.getCalendarHeader(); - if (!calendarHeader) return; - - if (this.headerEventListener) { - // Remove mouseenter listener - calendarHeader.removeEventListener('mouseenter', this.headerEventListener, true); - } - - if (this.headerMouseLeaveListener) { - calendarHeader.removeEventListener('mouseleave', this.headerMouseLeaveListener); - } -} -``` - -## 2. AllDayEventRenderer Ændringer - -```typescript -// src/renderers/AllDayEventRenderer.ts - -/** - * Get or cache all-day container, create if it doesn't exist - SIMPLIFIED - */ -private getContainer(): HTMLElement | null { - if (!this.container) { - const header = document.querySelector('swp-calendar-header'); - if (header) { - // Try to find existing container - this.container = header.querySelector('swp-allday-container'); - - // If not found, create it - if (!this.container) { - this.container = document.createElement('swp-allday-container'); - header.appendChild(this.container); - - // NO MORE GHOST COLUMNS! 🎉 - // Mouse detection handled by HeaderManager coordinate calculation - } - } - } - return this.container; -} - -// REMOVE this method entirely: -// private createGhostColumns(): void { ... } -``` - -## 3. DragDropManager Ændringer - -```typescript -// src/managers/DragDropManager.ts - -// In constructor, update the header:mouseover listener -eventBus.on('header:mouseover', (event) => { - const { targetDate, element } = (event as CustomEvent).detail; - - if (this.draggedEventId && targetDate) { - // Only proceed if we're actually dragging and have a valid target date - const draggedElement = document.querySelector(`swp-event[data-event-id="${this.draggedEventId}"]`); - - if (draggedElement) { - console.log('🎯 Converting to all-day for date:', targetDate); - - this.eventBus.emit('drag:convert-to-allday_event', { - targetDate, - originalElement: draggedElement, - headerRenderer: (event as CustomEvent).detail.headerRenderer - }); - } - } -}); -``` - -## 4. CSS Ændringer (hvis nødvendigt) - -```css -/* Ensure all-day container is properly positioned for mouse events */ -swp-allday-container { - position: relative; - width: 100%; - min-height: var(--all-day-row-height, 0px); - display: grid; - grid-template-columns: repeat(7, 1fr); /* Match day columns */ - pointer-events: all; /* Ensure mouse events work */ -} - -/* Remove any ghost column styles */ -/* swp-allday-column styles can be removed if they were only for ghosts */ -``` - -## 5. Fordele ved denne løsning: - -✅ **Performance**: Ingen kontinuerlige mouseover events -✅ **Simplicity**: Fjerner ghost column kompleksitet -✅ **Accuracy**: Direkte coordinate-baseret detection -✅ **Maintainability**: Mindre kode at vedligeholde -✅ **Debugging**: Lettere at følge event flow - -## 6. Potentielle udfordringer: - -⚠️ **Event Bubbling**: `mouseenter` med `capture: true` for at fange events tidligt -⚠️ **Coordinate Precision**: Skal teste at coordinate beregning er præcis -⚠️ **Multi-day Events**: Skal stadig håndteres korrekt ved drop - -## 7. Test Scenarie: - -1. Drag et day-event -2. Træk musen ind i all-day området -3. `mouseenter` fyrer én gang og beregner target date -4. Event konverteres til all-day -5. Træk musen ud af all-day området -6. `mouseleave` fyrer og konverterer tilbage diff --git a/resource-calendar-structure.md b/resource-calendar-structure.md deleted file mode 100644 index 42e7346..0000000 --- a/resource-calendar-structure.md +++ /dev/null @@ -1,166 +0,0 @@ -# Resource Calendar JSON Structure (Opdateret) - -Her er den opdaterede JSON struktur med resources som array og detaljerede resource informationer: - -```json -{ - "date": "2025-08-05", - "resources": [ - { - "name": "karina.knudsen", - "displayName": "Karina Knudsen", - "avatarUrl": "/avatars/karina.jpg", - "employeeId": "EMP001", - "events": [ - { - "id": "1", - "title": "Balayage langt hår", - "start": "2025-08-05T10:00:00", - "end": "2025-08-05T11:00:00", - "type": "work", - "allDay": false, - "syncStatus": "synced", - "metadata": { "duration": 60, "color": "#9c27b0" } - }, - { - "id": "2", - "title": "Klipning og styling", - "start": "2025-08-05T14:00:00", - "end": "2025-08-05T15:30:00", - "type": "work", - "allDay": false, - "syncStatus": "synced", - "metadata": { "duration": 90, "color": "#e91e63" } - } - ] - }, - { - "name": "maria.hansen", - "displayName": "Maria Hansen", - "avatarUrl": "/avatars/maria.jpg", - "employeeId": "EMP002", - "events": [ - { - "id": "3", - "title": "Permanent", - "start": "2025-08-05T09:00:00", - "end": "2025-08-05T11:00:00", - "type": "work", - "allDay": false, - "syncStatus": "synced", - "metadata": { "duration": 120, "color": "#3f51b5" } - }, - { - "id": "4", - "title": "Farve behandling", - "start": "2025-08-05T13:00:00", - "end": "2025-08-05T15:00:00", - "type": "work", - "allDay": false, - "syncStatus": "synced", - "metadata": { "duration": 120, "color": "#ff9800" } - } - ] - }, - { - "name": "lars.nielsen", - "displayName": "Lars Nielsen", - "avatarUrl": "/avatars/lars.jpg", - "employeeId": "EMP003", - "events": [ - { - "id": "5", - "title": "Herreklipning", - "start": "2025-08-05T11:00:00", - "end": "2025-08-05T11:30:00", - "type": "work", - "allDay": false, - "syncStatus": "synced", - "metadata": { "duration": 30, "color": "#795548" } - }, - { - "id": "6", - "title": "Skæg trimning", - "start": "2025-08-05T16:00:00", - "end": "2025-08-05T16:30:00", - "type": "work", - "allDay": false, - "syncStatus": "synced", - "metadata": { "duration": 30, "color": "#607d8b" } - } - ] - }, - { - "name": "anna.petersen", - "displayName": "Anna Petersen", - "avatarUrl": "/avatars/anna.jpg", - "employeeId": "EMP004", - "events": [ - { - "id": "7", - "title": "Bryllupsfrisure", - "start": "2025-08-05T08:00:00", - "end": "2025-08-05T10:00:00", - "type": "work", - "allDay": false, - "syncStatus": "synced", - "metadata": { "duration": 120, "color": "#009688" } - } - ] - }, - { - "name": "thomas.olsen", - "displayName": "Thomas Olsen", - "avatarUrl": "/avatars/thomas.jpg", - "employeeId": "EMP005", - "events": [ - { - "id": "8", - "title": "Highlights", - "start": "2025-08-05T12:00:00", - "end": "2025-08-05T14:00:00", - "type": "work", - "allDay": false, - "syncStatus": "synced", - "metadata": { "duration": 120, "color": "#8bc34a" } - }, - { - "id": "9", - "title": "Styling konsultation", - "start": "2025-08-05T15:00:00", - "end": "2025-08-05T15:30:00", - "type": "meeting", - "allDay": false, - "syncStatus": "synced", - "metadata": { "duration": 30, "color": "#cddc39" } - } - ] - } - ] -} -``` - -## Struktur Forklaring - -- **date**: Den specifikke dato for resource calendar visningen -- **resources**: Array af resource objekter -- **Resource objekt**: - - **name**: Unikt navn/ID (kebab-case) - - **displayName**: Navn til visning i UI - - **avatarUrl**: URL til profilbillede - - **employeeId**: Medarbejder ID - - **events**: Array af events for denne resource - -## Fordele ved denne struktur: - -1. **Fleksibel**: Nemt at tilføje flere resource felter -2. **Skalerbar**: Kan håndtere mange resources -3. **UI-venlig**: DisplayName og avatar til visning -4. **Struktureret**: Klar separation mellem resource info og events -5. **Søgbar**: Name og employeeId til filtrering/søgning - -Denne struktur gør det nemt at: -- Vise resource info i headers (displayName, avatar) -- Filtrere events per resource -- Håndtere kun én dato ad gangen i resource mode -- Udvide med flere resource felter senere \ No newline at end of file diff --git a/src/renderers/EventRenderer.ts b/src/renderers/EventRenderer.ts index e2e7234..41c657e 100644 --- a/src/renderers/EventRenderer.ts +++ b/src/renderers/EventRenderer.ts @@ -94,16 +94,6 @@ export abstract class BaseEventRenderer implements EventRendererStrategy { } - /** - * Setup listeners for drag events from DragDropManager - * NOTE: Event listeners moved to EventRendererManager for better separation of concerns - */ - protected setupDragEventListeners(): void { - // All event listeners now handled by EventRendererManager - // This method kept for backward compatibility but does nothing - } - - /** * Cleanup method for proper resource management */ @@ -177,10 +167,10 @@ export abstract class BaseEventRenderer implements EventRendererStrategy { // Create SwpEventElement from existing DOM element and clone it const originalSwpEvent = SwpEventElement.fromExistingElement(originalElement); const clonedSwpEvent = originalSwpEvent.createClone(); - + // Get the cloned DOM element this.draggedClone = clonedSwpEvent.getElement(); - + // Apply drag styling this.applyDragStyling(this.draggedClone); @@ -551,16 +541,7 @@ export abstract class BaseEventRenderer implements EventRendererStrategy { renderEvents(events: CalendarEvent[], container: HTMLElement): void { - - // NOTE: Removed clearEvents() to support sliding animation - // With sliding animation, multiple grid containers exist simultaneously - // clearEvents() would remove events from all containers, breaking the animation - // Events are now rendered directly into the new container without clearing - - // Only handle regular (non-all-day) events - - - + // Find columns in the specific container for regular events const columns = this.getColumns(container); @@ -569,7 +550,7 @@ export abstract class BaseEventRenderer implements EventRendererStrategy { const eventsLayer = column.querySelector('swp-events-layer'); if (eventsLayer) { - // NY TILGANG: Kald vores nye overlap handling + this.handleEventOverlaps(columnEvents, eventsLayer as HTMLElement); } });