import { IEventBus } from '../types/CalendarTypes';
import { CoreEvents } from '../constants/CoreEvents';
import { CalendarConfig } from '../core/CalendarConfig';
import { DateCalculator } from '../utils/DateCalculator';
import { EventRenderingService } from './EventRendererManager';
import { CalendarTypeFactory } from '../factories/CalendarTypeFactory';
import { eventBus } from '../core/EventBus';
/**
* NavigationRenderer - Handles DOM rendering for navigation containers
* Separated from NavigationManager to follow Single Responsibility Principle
*/
export class NavigationRenderer {
private eventBus: IEventBus;
private config: CalendarConfig;
private dateCalculator: DateCalculator;
private eventRenderer: EventRenderingService;
constructor(eventBus: IEventBus, config: CalendarConfig, eventRenderer: EventRenderingService) {
this.eventBus = eventBus;
this.config = config;
this.eventRenderer = eventRenderer;
this.dateCalculator = new DateCalculator(config);
this.setupEventListeners();
}
/**
* Setup event listeners for DOM updates
*/
private setupEventListeners(): void {
this.eventBus.on(CoreEvents.WEEK_CHANGED, (event: Event) => {
const customEvent = event as CustomEvent;
const { weekNumber, dateRange } = customEvent.detail;
this.updateWeekInfoInDOM(weekNumber, dateRange);
});
}
/**
* Update week info in DOM elements
*/
private updateWeekInfoInDOM(weekNumber: number, dateRange: string): void {
const weekNumberElement = document.querySelector('swp-week-number');
const dateRangeElement = document.querySelector('swp-date-range');
if (weekNumberElement) {
weekNumberElement.textContent = `Week ${weekNumber}`;
}
if (dateRangeElement) {
dateRangeElement.textContent = dateRange;
}
}
/**
* Apply filter state to pre-rendered grids
*/
public applyFilterToPreRenderedGrids(filterState: { active: boolean; matchingIds: string[] }): void {
// Find all grid containers (including pre-rendered ones)
const allGridContainers = document.querySelectorAll('swp-grid-container');
allGridContainers.forEach(container => {
const eventsLayers = container.querySelectorAll('swp-events-layer');
eventsLayers.forEach(layer => {
if (filterState.active) {
// Apply filter active state
layer.setAttribute('data-filter-active', 'true');
// Mark matching events in this layer
const events = layer.querySelectorAll('swp-event');
events.forEach(event => {
const eventId = event.getAttribute('data-event-id');
if (eventId && filterState.matchingIds.includes(eventId)) {
event.setAttribute('data-matches', 'true');
} else {
event.removeAttribute('data-matches');
}
});
} else {
// Remove filter state
layer.removeAttribute('data-filter-active');
// Remove all match attributes
const events = layer.querySelectorAll('swp-event');
events.forEach(event => {
event.removeAttribute('data-matches');
});
}
});
});
}
/**
* Render a complete container with content and events
*/
public renderContainer(parentContainer: HTMLElement, weekStart: Date): HTMLElement {
const weekEnd = this.dateCalculator.addDays(weekStart, 6);
console.group(`🎨 RENDERING CONTAINER: ${weekStart.toDateString()} - ${weekEnd.toDateString()}`);
console.log('1. Creating grid structure...');
// Create new grid container
const newGrid = document.createElement('swp-grid-container');
newGrid.innerHTML = `
`;
// Position new grid - NO transform here, let Animation API handle it
newGrid.style.position = 'absolute';
newGrid.style.top = '0';
newGrid.style.left = '0';
newGrid.style.width = '100%';
newGrid.style.height = '100%';
// Add to parent container
parentContainer.appendChild(newGrid);
console.log('2. Rendering headers and columns...');
this.renderWeekContentInContainer(newGrid, weekStart);
console.log('3. Emitting GRID_RENDERED for navigation container...');
this.eventBus.emit(CoreEvents.GRID_RENDERED, {
container: newGrid, // Specific grid container, not parent
currentDate: weekStart,
startDate: weekStart,
endDate: weekEnd,
isNavigation: true // Flag to indicate this is navigation rendering
});
console.log('✅ Container ready with GRID_RENDERED event emitted');
console.groupEnd();
return newGrid;
}
/**
* Render week content in specific container
*/
private renderWeekContentInContainer(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 = '';
// Get dates using DateCalculator
const dates = this.dateCalculator.getWorkWeekDates(weekStart);
// Render headers for target week
dates.forEach((date, i) => {
const headerElement = document.createElement('swp-day-header');
if (this.dateCalculator.isToday(date)) {
headerElement.dataset.today = 'true';
}
const dayName = this.dateCalculator.getDayName(date, 'short');
headerElement.innerHTML = `
${dayName}
${date.getDate()}
`;
headerElement.dataset.date = this.dateCalculator.formatISODate(date);
header.appendChild(headerElement);
});
// Always ensure all-day containers exist for all days
const headerRenderer = CalendarTypeFactory.getHeaderRenderer(this.config.getCalendarMode());
headerRenderer.ensureAllDayContainers(header as HTMLElement);
// Add event delegation listener for drag & drop functionality
this.setupHeaderEventListener(header as HTMLElement);
// Render day columns for target week
dates.forEach(date => {
const column = document.createElement('swp-day-column');
column.dataset.date = this.dateCalculator.formatISODate(date);
const eventsLayer = document.createElement('swp-events-layer');
column.appendChild(eventsLayer);
dayColumns.appendChild(column);
});
}
/**
* Setup event delegation listener for header mouseover (same logic as GridRenderer)
*/
private setupHeaderEventListener(calendarHeader: HTMLElement): void {
calendarHeader.addEventListener('mouseover', (event) => {
const target = event.target as HTMLElement;
// Check what was hovered - could be day-header OR all-day-container
const dayHeader = target.closest('swp-day-header');
const allDayContainer = target.closest('swp-allday-container');
if (dayHeader || allDayContainer) {
let hoveredElement: HTMLElement;
let targetDate: string | undefined;
if (dayHeader) {
hoveredElement = dayHeader as HTMLElement;
targetDate = hoveredElement.dataset.date;
} else if (allDayContainer) {
// For all-day areas, we need to determine which day column we're over
hoveredElement = allDayContainer as HTMLElement;
// Calculate which day we're hovering over based on mouse position
const headerRect = calendarHeader.getBoundingClientRect();
const dayHeaders = calendarHeader.querySelectorAll('swp-day-header');
const mouseX = (event as MouseEvent).clientX - headerRect.left;
const dayWidth = headerRect.width / dayHeaders.length;
const dayIndex = Math.floor(mouseX / dayWidth);
const targetDayHeader = dayHeaders[dayIndex] as HTMLElement;
targetDate = targetDayHeader?.dataset.date;
} else {
return; // No valid element found
}
console.log('NavigationRenderer: Detected hover over:', {
elementType: dayHeader ? 'day-header' : 'all-day-container',
targetDate,
element: hoveredElement
});
// Get the header renderer for addToAllDay functionality
const calendarType = this.config.getCalendarMode();
const headerRenderer = CalendarTypeFactory.getHeaderRenderer(calendarType);
eventBus.emit('header:mouseover', {
element: hoveredElement,
targetDate,
headerRenderer
});
}
});
}
}