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