import { eventBus } from '../core/EventBus'; import { calendarConfig } from '../core/CalendarConfig'; import { CalendarTypeFactory } from '../factories/CalendarTypeFactory'; import { CoreEvents } from '../constants/CoreEvents'; import { HeaderRenderContext } from '../renderers/HeaderRenderer'; import { ResourceCalendarData } from '../types/CalendarTypes'; /** * HeaderManager - Handles all header-related event logic * Separates event handling from rendering concerns */ export class HeaderManager { private headerEventListener: ((event: Event) => void) | null = null; private headerMouseLeaveListener: ((event: Event) => void) | null = null; private cachedCalendarHeader: HTMLElement | null = null; constructor() { // Bind methods for event listeners this.setupHeaderDragListeners = this.setupHeaderDragListeners.bind(this); this.destroy = this.destroy.bind(this); // Listen for navigation events to update header this.setupNavigationListener(); } /** * Initialize header with initial date */ public initializeHeader(currentDate: Date, resourceData: ResourceCalendarData | null = null): void { this.updateHeader(currentDate, resourceData); } /** * Get cached calendar header element */ private getCalendarHeader(): HTMLElement | null { if (!this.cachedCalendarHeader) { this.cachedCalendarHeader = document.querySelector('swp-calendar-header'); } return this.cachedCalendarHeader; } /** * Setup header drag event listeners - REFACTORED to use mouseenter */ public setupHeaderDragListeners(): void { const calendarHeader = this.getCalendarHeader(); if (!calendarHeader) return; console.log('๐ŸŽฏ HeaderManager: Setting up drag listeners with mouseenter'); // Track last processed date to avoid duplicates let lastProcessedDate: string | null = null; let lastProcessedTime = 0; // Use mouseenter instead of mouseover to avoid continuous firing this.headerEventListener = (event: Event) => { const target = event.target as HTMLElement; console.log('๐Ÿ–ฑ๏ธ HeaderManager: mouseenter detected on:', target.tagName, target.className); // Check if we're entering the all-day container OR the header area where container should be let allDayContainer = target.closest('swp-allday-container'); // If no container exists, check if we're in the header and should create one via AllDayManager if (!allDayContainer && target.closest('swp-calendar-header')) { console.log('๐Ÿ“ HeaderManager: In header area but no all-day container exists, requesting creation...'); // Emit event to AllDayManager to create container eventBus.emit('allday:ensure-container'); // Try to find it again after creation allDayContainer = target.closest('swp-calendar-header')?.querySelector('swp-allday-container') as HTMLElement; } if (allDayContainer) { // SMART CHECK: Only calculate target date if there's an active drag operation const isDragActive = document.querySelector('.dragging') !== null; if (!isDragActive) { console.log('โญ๏ธ HeaderManager: No active drag operation, skipping target date calculation'); return; } console.log('๐Ÿ“ HeaderManager: Active drag detected, calculating target date...'); // Calculate target date from mouse X coordinate const targetDate = this.calculateTargetDateFromMouseX(event as MouseEvent); console.log('๐ŸŽฏ HeaderManager: Calculated target date:', targetDate); if (targetDate) { const calendarType = calendarConfig.getCalendarMode(); const headerRenderer = CalendarTypeFactory.getHeaderRenderer(calendarType); console.log('โœ… HeaderManager: Emitting header:mouseover with targetDate:', targetDate); eventBus.emit('header:mouseover', { element: allDayContainer, targetDate, headerRenderer }); } else { console.log('โŒ HeaderManager: Could not calculate target date from mouse position'); } } }; // Header mouseleave listener this.headerMouseLeaveListener = (event: Event) => { console.log('๐Ÿšช HeaderManager: mouseleave detected'); eventBus.emit('header:mouseleave', { element: event.target as HTMLElement }); }; // Use mouseenter with capture to catch events early calendarHeader.addEventListener('mouseenter', this.headerEventListener, true); calendarHeader.addEventListener('mouseleave', this.headerMouseLeaveListener); console.log('โœ… HeaderManager: Event listeners attached (mouseenter + mouseleave)'); } /** * Calculate target date from mouse X coordinate */ private calculateTargetDateFromMouseX(event: MouseEvent): string | null { const dayHeaders = document.querySelectorAll('swp-day-header'); const mouseX = event.clientX; console.log('๐Ÿงฎ HeaderManager: Calculating target date from mouseX:', mouseX); console.log('๐Ÿ“Š HeaderManager: Found', dayHeaders.length, 'day headers'); for (const header of dayHeaders) { const headerElement = header as HTMLElement; const rect = headerElement.getBoundingClientRect(); const headerDate = headerElement.dataset.date; console.log('๐Ÿ“ HeaderManager: Checking header', headerDate, 'bounds:', { left: rect.left, right: rect.right, mouseX: mouseX, isWithin: mouseX >= rect.left && mouseX <= rect.right }); // Check if mouse X is within this header's bounds if (mouseX >= rect.left && mouseX <= rect.right) { console.log('๐ŸŽฏ HeaderManager: Found matching header for date:', headerDate); return headerDate || null; } } console.log('โŒ HeaderManager: No matching header found for mouseX:', mouseX); return null; } /** * Remove event listeners from header - UPDATED for mouseenter */ private removeEventListeners(): void { const calendarHeader = this.getCalendarHeader(); if (!calendarHeader) return; console.log('๐Ÿงน HeaderManager: Removing event listeners'); if (this.headerEventListener) { // Remove mouseenter listener with capture flag calendarHeader.removeEventListener('mouseenter', this.headerEventListener, true); console.log('โœ… HeaderManager: Removed mouseenter listener'); } if (this.headerMouseLeaveListener) { calendarHeader.removeEventListener('mouseleave', this.headerMouseLeaveListener); console.log('โœ… HeaderManager: Removed mouseleave listener'); } } /** * Setup navigation event listener */ private setupNavigationListener(): void { eventBus.on(CoreEvents.NAVIGATION_COMPLETED, (event) => { const { currentDate, resourceData } = (event as CustomEvent).detail; this.updateHeader(currentDate, resourceData); }); // Also listen for date changes (including initial setup) eventBus.on(CoreEvents.DATE_CHANGED, (event) => { const { currentDate } = (event as CustomEvent).detail; this.updateHeader(currentDate, null); }); // Listen for workweek header updates after grid rebuild eventBus.on('workweek:header-update', (event) => { const { currentDate } = (event as CustomEvent).detail; this.clearCache(); // Clear cache since DOM was cleared this.updateHeader(currentDate, null); }); } /** * Update header content for navigation */ private updateHeader(currentDate: Date, resourceData: ResourceCalendarData | null = null): void { const calendarHeader = this.getOrCreateCalendarHeader(); if (!calendarHeader) return; // Remove existing event listeners BEFORE clearing content this.removeEventListeners(); // Clear existing content calendarHeader.innerHTML = ''; // Render new header content const calendarType = calendarConfig.getCalendarMode(); const headerRenderer = CalendarTypeFactory.getHeaderRenderer(calendarType); const context: HeaderRenderContext = { currentWeek: currentDate, config: calendarConfig, resourceData: resourceData }; headerRenderer.render(calendarHeader, context); // Setup event listeners on the new content this.setupHeaderDragListeners(); // Notify other managers that header was rebuilt eventBus.emit('header:rebuilt', { headerElement: calendarHeader }); } /** * Get or create calendar header element */ private getOrCreateCalendarHeader(): HTMLElement | null { let calendarHeader = this.getCalendarHeader(); if (!calendarHeader) { // Find grid container and create header const gridContainer = document.querySelector('swp-grid-container'); if (gridContainer) { calendarHeader = document.createElement('swp-calendar-header'); // Insert header as first child gridContainer.insertBefore(calendarHeader, gridContainer.firstChild); this.cachedCalendarHeader = calendarHeader; } } return calendarHeader; } /** * Clear cached header reference */ public clearCache(): void { this.cachedCalendarHeader = null; } /** * Clean up resources and event listeners */ public destroy(): void { this.removeEventListeners(); // Clear references this.headerEventListener = null; this.headerMouseLeaveListener = null; this.clearCache(); } }