import { IEventBus } from '../types/CalendarTypes.js'; import { DateUtils } from '../utils/DateUtils.js'; import { EventTypes } from '../constants/EventTypes.js'; /** * NavigationManager handles calendar navigation (prev/next/today buttons) * with simplified CSS Grid approach */ export class NavigationManager { private eventBus: IEventBus; private currentWeek: Date; private targetWeek: Date; private animationQueue: number = 0; constructor(eventBus: IEventBus) { console.log('🧭 NavigationManager: Constructor called'); this.eventBus = eventBus; this.currentWeek = DateUtils.getWeekStart(new Date(), 0); // Sunday start like POC this.targetWeek = new Date(this.currentWeek); this.init(); } private init(): void { this.setupEventListeners(); // Don't update week info immediately - wait for DOM to be ready console.log('NavigationManager: Waiting for CALENDAR_INITIALIZED before updating DOM'); } private setupEventListeners(): void { // Initial DOM update when calendar is initialized this.eventBus.on(EventTypes.CALENDAR_INITIALIZED, () => { console.log('NavigationManager: Received CALENDAR_INITIALIZED, updating week info'); this.updateWeekInfo(); }); // Listen for navigation button clicks document.addEventListener('click', (e) => { const target = e.target as HTMLElement; const navButton = target.closest('[data-action]') as HTMLElement; if (!navButton) return; const action = navButton.dataset.action; switch (action) { case 'prev': this.navigateToPreviousWeek(); break; case 'next': this.navigateToNextWeek(); break; case 'today': this.navigateToToday(); break; } }); // Listen for external navigation requests this.eventBus.on(EventTypes.NAVIGATE_TO_DATE, (event: Event) => { const customEvent = event as CustomEvent; const targetDate = new Date(customEvent.detail.date); this.navigateToDate(targetDate); }); } private navigateToPreviousWeek(): void { this.targetWeek.setDate(this.targetWeek.getDate() - 7); const weekToShow = new Date(this.targetWeek); this.animationQueue++; this.animateTransition('prev', weekToShow); } private navigateToNextWeek(): void { this.targetWeek.setDate(this.targetWeek.getDate() + 7); const weekToShow = new Date(this.targetWeek); this.animationQueue++; this.animateTransition('next', weekToShow); } private navigateToToday(): void { const today = new Date(); const todayWeekStart = DateUtils.getWeekStart(today, 0); // Reset to today this.targetWeek = new Date(todayWeekStart); const currentTime = this.currentWeek.getTime(); const targetTime = todayWeekStart.getTime(); if (currentTime < targetTime) { this.animationQueue++; this.animateTransition('next', todayWeekStart); } else if (currentTime > targetTime) { this.animationQueue++; this.animateTransition('prev', todayWeekStart); } } private navigateToDate(date: Date): void { const weekStart = DateUtils.getWeekStart(date, 0); this.targetWeek = new Date(weekStart); const currentTime = this.currentWeek.getTime(); const targetTime = weekStart.getTime(); if (currentTime < targetTime) { this.animationQueue++; this.animateTransition('next', weekStart); } else if (currentTime > targetTime) { this.animationQueue++; this.animateTransition('prev', weekStart); } } /** * POC-style animation transition - creates new grid container and slides it in */ private animateTransition(direction: 'prev' | 'next', targetWeek: Date): void { const container = document.querySelector('swp-calendar-container'); const currentGrid = container?.querySelector('swp-grid-container'); if (!container || !currentGrid) { console.warn('NavigationManager: Required DOM elements not found'); return; } console.log(`NavigationManager: Starting ${direction} animation to ${targetWeek.toDateString()}`); // Create new grid container (POC approach) const newGrid = document.createElement('swp-grid-container'); newGrid.innerHTML = ` `; // Position new grid off-screen (POC positioning) newGrid.style.position = 'absolute'; newGrid.style.top = '0'; newGrid.style.left = '0'; newGrid.style.width = '100%'; newGrid.style.height = '100%'; newGrid.style.transform = direction === 'next' ? 'translateX(100%)' : 'translateX(-100%)'; // Add to container container.appendChild(newGrid); // Render new content for target week this.renderWeekContent(newGrid, targetWeek); // Animate transition (POC animation) requestAnimationFrame(() => { // Slide out current grid (currentGrid as HTMLElement).style.transform = direction === 'next' ? 'translateX(-100%)' : 'translateX(100%)'; (currentGrid as HTMLElement).style.opacity = '0.5'; // Cleanup: Remove all old grids except the new one after animation setTimeout(() => { const allGrids = container.querySelectorAll('swp-grid-container'); // Keep only the newest grid (last one), remove all others for (let i = 0; i < allGrids.length - 1; i++) { allGrids[i].remove(); } }, 450); // Slide in new grid newGrid.style.transform = 'translateX(0)'; // Wait for new grid animation to complete before updating state setTimeout(() => { newGrid.style.position = 'relative'; // Only now is the animation truly complete this.currentWeek = new Date(targetWeek); this.animationQueue--; // If this was the last queued animation, ensure we're in sync if (this.animationQueue === 0) { this.currentWeek = new Date(this.targetWeek); } // Update week info and notify other managers this.updateWeekInfo(); this.eventBus.emit(EventTypes.WEEK_CHANGED, { weekStart: this.currentWeek, weekEnd: DateUtils.addDays(this.currentWeek, 6) }); console.log(`NavigationManager: Completed ${direction} animation`); }, 400); // Wait for slide-in animation to complete }); } /** * Render week content in the new grid container */ private renderWeekContent(gridContainer: HTMLElement, weekStart: Date): void { const header = gridContainer.querySelector('swp-calendar-header'); const dayColumns = gridContainer.querySelector('swp-day-columns'); if (!header || !dayColumns) return; // Clear existing content header.innerHTML = ''; dayColumns.innerHTML = ''; // Render headers for target week const days = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']; for (let i = 0; i < 7; i++) { const date = new Date(weekStart); date.setDate(date.getDate() + i); const headerElement = document.createElement('swp-day-header'); if (this.isToday(date)) { headerElement.dataset.today = 'true'; } headerElement.innerHTML = ` ${days[date.getDay()]} ${date.getDate()} `; headerElement.dataset.date = this.formatDate(date); header.appendChild(headerElement); } // Render day columns for target week for (let i = 0; i < 7; i++) { const column = document.createElement('swp-day-column'); const date = new Date(weekStart); date.setDate(date.getDate() + i); column.dataset.date = this.formatDate(date); const eventsLayer = document.createElement('swp-events-layer'); column.appendChild(eventsLayer); dayColumns.appendChild(column); } // NOTE: Removed POC event emission to prevent interference with production code // POC events should not trigger production event rendering // this.eventBus.emit(EventTypes.WEEK_CONTENT_RENDERED, { ... }); } // Utility functions (from POC) private formatDate(date: Date): string { return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`; } private isToday(date: Date): boolean { const today = new Date(); return date.toDateString() === today.toDateString(); } private updateWeekInfo(): void { const weekNumber = DateUtils.getWeekNumber(this.currentWeek); const weekEnd = DateUtils.addDays(this.currentWeek, 6); const dateRange = DateUtils.formatDateRange(this.currentWeek, weekEnd); // Update week info in DOM const weekNumberElement = document.querySelector('swp-week-number'); const dateRangeElement = document.querySelector('swp-date-range'); if (weekNumberElement) { weekNumberElement.textContent = `Week ${weekNumber}`; console.log('NavigationManager: Updated week number:', `Week ${weekNumber}`); } else { console.warn('NavigationManager: swp-week-number element not found in DOM'); } if (dateRangeElement) { dateRangeElement.textContent = dateRange; console.log('NavigationManager: Updated date range:', dateRange); } else { console.warn('NavigationManager: swp-date-range element not found in DOM'); } // Notify other managers about week info update this.eventBus.emit(EventTypes.WEEK_INFO_UPDATED, { weekNumber, dateRange, weekStart: this.currentWeek, weekEnd }); } /** * Get current week start date */ getCurrentWeek(): Date { return new Date(this.currentWeek); } /** * Get target week (where navigation is heading) */ getTargetWeek(): Date { return new Date(this.targetWeek); } /** * Check if navigation animation is in progress */ isAnimating(): boolean { return this.animationQueue > 0; } /** * Force navigation to specific week without animation */ setWeek(weekStart: Date): void { this.currentWeek = new Date(weekStart); this.targetWeek = new Date(weekStart); this.updateWeekInfo(); this.eventBus.emit(EventTypes.WEEK_CHANGED, { weekStart: this.currentWeek, weekEnd: DateUtils.addDays(this.currentWeek, 6) }); } }