diff --git a/src/managers/EventRenderer.ts b/src/managers/EventRenderer.ts index e4ff7dc..9bf1ddb 100644 --- a/src/managers/EventRenderer.ts +++ b/src/managers/EventRenderer.ts @@ -19,7 +19,14 @@ export class EventRenderer { this.eventBus.on(EventTypes.EVENTS_LOADED, (event: Event) => { const customEvent = event as CustomEvent; const { events } = customEvent.detail; - this.renderEvents(events); + // Store events but don't render yet - wait for grid to be ready + this.pendingEvents = events; + this.tryRenderEvents(); + }); + + this.eventBus.on(EventTypes.GRID_RENDERED, () => { + // Grid is ready, now we can render events + this.tryRenderEvents(); }); this.eventBus.on(EventTypes.VIEW_RENDERED, () => { @@ -28,6 +35,19 @@ export class EventRenderer { }); } + private pendingEvents: CalendarEvent[] = []; + + private tryRenderEvents(): void { + // Only render if we have both events and grid is ready + if (this.pendingEvents.length > 0) { + const dayColumns = document.querySelectorAll('swp-day-column'); + if (dayColumns.length > 0) { + this.renderEvents(this.pendingEvents); + this.pendingEvents = []; // Clear pending events after rendering + } + } + } + private renderEvents(events: CalendarEvent[]): void { console.log(`EventRenderer: Rendering ${events.length} events`); @@ -62,27 +82,53 @@ export class EventRenderer { } private renderDayEvents(dayIndex: number, events: CalendarEvent[]): void { - const dayColumns = document.querySelectorAll('swp-day-column'); - const dayColumn = dayColumns[dayIndex]; - if (!dayColumn) { - console.warn(`EventRenderer: Day column ${dayIndex} not found`); - return; - } - - const eventsLayer = dayColumn.querySelector('swp-events-layer'); - if (!eventsLayer) { - console.warn(`EventRenderer: Events layer not found for day ${dayIndex}`); - return; - } - // Sort events by start time const sortedEvents = events.sort((a, b) => a.start.localeCompare(b.start)); sortedEvents.forEach(event => { - this.renderEvent(event, eventsLayer); + // Find the appropriate events container for this event + const eventContainer = this.findEventContainer(event, dayIndex); + if (eventContainer) { + this.renderEvent(event, eventContainer); + } }); } + private findEventContainer(event: CalendarEvent, dayIndex: number): Element | null { + // Debug: Log what we're looking for + console.log(`EventRenderer: Looking for day ${dayIndex} using POC structure`); + + // Check what day columns actually exist + const dayColumns = document.querySelectorAll('swp-day-column'); + console.log(`EventRenderer: Found ${dayColumns.length} day columns total`); + + // Check first few columns to see their attributes + for (let i = 0; i < Math.min(3, dayColumns.length); i++) { + const column = dayColumns[i] as HTMLElement; + console.log(`Column ${i}:`, { + dayIndex: column.dataset.dayIndex, + date: column.dataset.date, + tagName: column.tagName + }); + } + + // Find the day column that corresponds to the event's day + const dayColumn = document.querySelector(`swp-day-column[data-dayIndex="${dayIndex}"]`); + if (!dayColumn) { + console.warn(`EventRenderer: Day column for day ${dayIndex} not found`); + return null; + } + + // Find the events layer within this day column + const eventsLayer = dayColumn.querySelector('swp-events-layer'); + if (!eventsLayer) { + console.warn(`EventRenderer: Events layer not found in day column for day ${dayIndex}`); + return null; + } + + return eventsLayer; + } + private renderEvent(event: CalendarEvent, container: Element): void { const eventElement = document.createElement('swp-event'); eventElement.dataset.eventId = event.id; @@ -90,8 +136,12 @@ export class EventRenderer { // Calculate position based on time const position = this.calculateEventPosition(event); + eventElement.style.position = 'absolute'; eventElement.style.top = `${position.top}px`; eventElement.style.height = `${position.height}px`; + eventElement.style.left = '2px'; + eventElement.style.right = '2px'; + eventElement.style.zIndex = '10'; // Format time for display const startTime = this.formatTime(event.start); diff --git a/src/managers/GridManager.ts b/src/managers/GridManager.ts index cc41364..30f514e 100644 --- a/src/managers/GridManager.ts +++ b/src/managers/GridManager.ts @@ -1,4 +1,4 @@ -// Grid structure management +// Grid structure management - Simple CSS Grid Implementation import { eventBus } from '../core/EventBus'; import { calendarConfig } from '../core/CalendarConfig'; @@ -15,15 +15,11 @@ interface GridPosition { } /** - * Manages the calendar grid structure + * Manages the calendar grid structure using simple CSS Grid */ export class GridManager { private container: HTMLElement | null = null; - private timeAxis: HTMLElement | null = null; - private weekHeader: HTMLElement | null = null; - private timeGrid: HTMLElement | null = null; - private dayColumns: HTMLElement | null = null; - private scrollableContent: HTMLElement | null = null; + private grid: HTMLElement | null = null; private currentWeek: Date | null = null; constructor() { @@ -33,15 +29,27 @@ export class GridManager { private init(): void { this.findElements(); this.subscribeToEvents(); - this.setupScrollSync(); + + // Set initial current week to today if not set + if (!this.currentWeek) { + this.currentWeek = this.getWeekStart(new Date()); + console.log('GridManager: Set initial currentWeek to', this.currentWeek); + // Render initial grid + this.render(); + } + } + + private getWeekStart(date: Date): Date { + const weekStart = new Date(date); + const day = weekStart.getDay(); + const diff = weekStart.getDate() - day; // Sunday is 0 + weekStart.setDate(diff); + weekStart.setHours(0, 0, 0, 0); + return weekStart; } private findElements(): void { - this.container = document.querySelector('swp-calendar-container'); - this.timeAxis = document.querySelector('swp-time-axis'); - this.weekHeader = document.querySelector('swp-week-header'); - this.timeGrid = document.querySelector('swp-time-grid'); - this.scrollableContent = document.querySelector('swp-scrollable-content'); + this.grid = document.querySelector('swp-calendar-container'); } private subscribeToEvents(): void { @@ -62,7 +70,7 @@ export class GridManager { eventBus.on(EventTypes.PERIOD_CHANGE, (e: Event) => { const detail = (e as CustomEvent).detail; this.currentWeek = detail.week; - this.renderHeaders(); + this.render(); }); // Handle week changes from NavigationManager @@ -72,12 +80,6 @@ export class GridManager { this.render(); }); - // Handle new week container creation - eventBus.on(EventTypes.WEEK_CONTAINER_CREATED, (e: Event) => { - const detail = (e as CustomEvent).detail; - this.renderGridForContainer(detail.container, detail.weekStart); - }); - // Handle grid clicks this.setupGridInteractions(); } @@ -86,129 +88,158 @@ export class GridManager { * Render the complete grid structure */ render(): void { - this.renderTimeAxis(); - this.renderHeaders(); + if (!this.grid) return; + + this.updateGridStyles(); this.renderGrid(); - this.renderGridLines(); // Emit grid rendered event eventBus.emit(EventTypes.GRID_RENDERED); } /** - * Render time axis (left side hours) - */ - private renderTimeAxis(): void { - if (!this.timeAxis) return; - - const startHour = calendarConfig.get('dayStartHour'); - const endHour = calendarConfig.get('dayEndHour'); - - this.timeAxis.innerHTML = ''; - - for (let hour = startHour; hour <= endHour; hour++) { - const marker = document.createElement('swp-hour-marker'); - marker.textContent = this.formatHour(hour); - (marker as any).dataset.hour = hour; - this.timeAxis.appendChild(marker); - } - } - - /** - * Render week headers - */ - private renderHeaders(): void { - if (!this.weekHeader || !this.currentWeek) return; - - const view = calendarConfig.get('view'); - const weekDays = calendarConfig.get('weekDays'); - - this.weekHeader.innerHTML = ''; - - if (view === 'week') { - const dates = this.getWeekDates(this.currentWeek); - const daysToShow = dates.slice(0, weekDays); - - daysToShow.forEach((date, index) => { - const header = document.createElement('swp-day-header'); - header.innerHTML = ` - ${this.getDayName(date)} - ${date.getDate()} - `; - (header as any).dataset.date = this.formatDate(date); - (header as any).dataset.dayIndex = index; - - // Mark today - if (this.isToday(date)) { - (header as any).dataset.today = 'true'; - } - - this.weekHeader!.appendChild(header); - }); - } - } - - /** - * Render the main grid structure + * Render the complete grid using POC structure */ private renderGrid(): void { - if (!this.timeGrid) return; - - // Clear existing columns - let dayColumns = this.timeGrid.querySelector('swp-day-columns'); - if (!dayColumns) { - dayColumns = document.createElement('swp-day-columns'); - this.timeGrid.appendChild(dayColumns); + console.log('GridManager: renderGrid called', { + hasGrid: !!this.grid, + hasCurrentWeek: !!this.currentWeek, + currentWeek: this.currentWeek + }); + + if (!this.grid || !this.currentWeek) { + console.warn('GridManager: Cannot render - missing grid or currentWeek'); + return; } + + // Clear existing grid and rebuild POC structure + this.grid.innerHTML = ''; - dayColumns.innerHTML = ''; + // Create POC structure: time-axis + week-container + this.createTimeAxis(); + this.createWeekContainer(); - const view = calendarConfig.get('view'); - const columnsCount = view === 'week' ? calendarConfig.get('weekDays') : 1; + console.log('GridManager: Grid rendered successfully with POC structure'); + } + + /** + * Create time axis (left column) like in POC + */ + private createTimeAxis(): void { + if (!this.grid) return; + + const timeAxis = document.createElement('swp-time-axis'); + const startHour = calendarConfig.get('dayStartHour'); + const endHour = calendarConfig.get('dayEndHour'); + + for (let hour = startHour; hour <= endHour; hour++) { + const marker = document.createElement('swp-hour-marker'); + const period = hour >= 12 ? 'PM' : 'AM'; + const displayHour = hour > 12 ? hour - 12 : (hour === 0 ? 12 : hour); + marker.textContent = `${displayHour} ${period}`; + timeAxis.appendChild(marker); + } + + this.grid.appendChild(timeAxis); + } + + /** + * Create week container with header and scrollable content like in POC + */ + private createWeekContainer(): void { + if (!this.grid || !this.currentWeek) return; + + const weekContainer = document.createElement('swp-week-container'); - // Create columns - for (let i = 0; i < columnsCount; i++) { - const column = document.createElement('swp-day-column'); - (column as any).dataset.columnIndex = i; - - if (this.currentWeek) { - const dates = this.getWeekDates(this.currentWeek); - if (dates[i]) { - (column as any).dataset.date = this.formatDate(dates[i]); - } + // Create week header + const weekHeader = document.createElement('swp-week-header'); + this.renderWeekHeaders(weekHeader); + weekContainer.appendChild(weekHeader); + + // Create scrollable content + const scrollableContent = document.createElement('swp-scrollable-content'); + const timeGrid = document.createElement('swp-time-grid'); + + // Add grid lines + const gridLines = document.createElement('swp-grid-lines'); + timeGrid.appendChild(gridLines); + + // Create day columns + const dayColumns = document.createElement('swp-day-columns'); + this.renderDayColumns(dayColumns); + timeGrid.appendChild(dayColumns); + + scrollableContent.appendChild(timeGrid); + weekContainer.appendChild(scrollableContent); + + this.grid.appendChild(weekContainer); + } + + /** + * Render week headers like in POC + */ + private renderWeekHeaders(weekHeader: HTMLElement): void { + if (!this.currentWeek) return; + + const dates = this.getWeekDates(this.currentWeek); + const weekDays = calendarConfig.get('weekDays'); + const daysToShow = dates.slice(0, weekDays); + + daysToShow.forEach((date) => { + const header = document.createElement('swp-day-header'); + if (this.isToday(date)) { + (header as any).dataset.today = 'true'; } - // Add events container + header.innerHTML = ` + ${this.getDayName(date)} + ${date.getDate()} + `; + (header as any).dataset.date = this.formatDate(date); + + weekHeader.appendChild(header); + }); + } + + /** + * Render day columns like in POC + */ + private renderDayColumns(dayColumns: HTMLElement): void { + console.log('GridManager: renderDayColumns called'); + if (!this.currentWeek) { + console.log('GridManager: No currentWeek, returning'); + return; + } + + const dates = this.getWeekDates(this.currentWeek); + const weekDays = calendarConfig.get('weekDays'); + const daysToShow = dates.slice(0, weekDays); + + console.log('GridManager: About to render', daysToShow.length, 'day columns'); + + daysToShow.forEach((date, dayIndex) => { + const column = document.createElement('swp-day-column'); + (column as any).dataset.date = this.formatDate(date); + (column as any).dataset.dayIndex = dayIndex.toString(); + + console.log(`GridManager: Creating day column ${dayIndex} for date ${this.formatDate(date)}`); + + // Add dummy content to force column width (temporary test) + const dummyContent = document.createElement('div'); + dummyContent.style.height = '20px'; + dummyContent.style.width = '100%'; + dummyContent.style.backgroundColor = 'red'; + dummyContent.style.color = 'white'; + dummyContent.style.fontSize = '12px'; + dummyContent.style.textAlign = 'center'; + dummyContent.textContent = `Day ${dayIndex + 1}`; + column.appendChild(dummyContent); + const eventsLayer = document.createElement('swp-events-layer'); column.appendChild(eventsLayer); dayColumns.appendChild(column); - } - - this.dayColumns = dayColumns as HTMLElement; - this.updateGridStyles(); - } - - /** - * Render grid lines - */ - private renderGridLines(): void { - if (!this.timeGrid) return; - - let gridLines = this.timeGrid.querySelector('swp-grid-lines'); - if (!gridLines) { - gridLines = document.createElement('swp-grid-lines'); - this.timeGrid.insertBefore(gridLines, this.timeGrid.firstChild); - } - - const totalHours = calendarConfig.totalHours; - const hourHeight = calendarConfig.get('hourHeight'); - - // Set CSS variables - this.timeGrid.style.setProperty('--total-hours', totalHours.toString()); - this.timeGrid.style.setProperty('--hour-height', `${hourHeight}px`); - - // Grid lines are handled by CSS + }); } /** @@ -226,84 +257,93 @@ export class GridManager { root.style.setProperty('--day-end-hour', config.dayEndHour.toString()); root.style.setProperty('--work-start-hour', config.workStartHour.toString()); root.style.setProperty('--work-end-hour', config.workEndHour.toString()); - - // Set grid height - const totalHeight = calendarConfig.totalHours * config.hourHeight; - if (this.timeGrid) { - this.timeGrid.style.height = `${totalHeight}px`; - } } /** - * Setup grid interaction handlers + * Setup grid interaction handlers for POC structure */ private setupGridInteractions(): void { - if (!this.timeGrid) return; + if (!this.grid) return; - // Click handler - this.timeGrid.addEventListener('click', (e: MouseEvent) => { + // Click handler for day columns + this.grid.addEventListener('click', (e: MouseEvent) => { // Ignore if clicking on an event if ((e.target as Element).closest('swp-event')) return; - const column = (e.target as Element).closest('swp-day-column') as HTMLElement; - if (!column) return; + const dayColumn = (e.target as Element).closest('swp-day-column') as HTMLElement; + if (!dayColumn) return; - const position = this.getClickPosition(e, column); + const position = this.getClickPosition(e, dayColumn); eventBus.emit(EventTypes.GRID_CLICK, { - date: (column as any).dataset.date, + date: (dayColumn as any).dataset.date, time: position.time, minutes: position.minutes, - columnIndex: parseInt((column as any).dataset.columnIndex) + dayIndex: parseInt((dayColumn as any).dataset.dayIndex) }); }); - // Double click handler - this.timeGrid.addEventListener('dblclick', (e: MouseEvent) => { + // Double click handler for day columns + this.grid.addEventListener('dblclick', (e: MouseEvent) => { // Ignore if clicking on an event if ((e.target as Element).closest('swp-event')) return; - const column = (e.target as Element).closest('swp-day-column') as HTMLElement; - if (!column) return; + const dayColumn = (e.target as Element).closest('swp-day-column') as HTMLElement; + if (!dayColumn) return; - const position = this.getClickPosition(e, column); + const position = this.getClickPosition(e, dayColumn); eventBus.emit(EventTypes.GRID_DBLCLICK, { - date: (column as any).dataset.date, + date: (dayColumn as any).dataset.date, time: position.time, minutes: position.minutes, - columnIndex: parseInt((column as any).dataset.columnIndex) + dayIndex: parseInt((dayColumn as any).dataset.dayIndex) }); }); } /** - * Get click position in grid + * Get click position in day column (POC structure) */ - private getClickPosition(event: MouseEvent, column: HTMLElement): GridPosition { - const rect = column.getBoundingClientRect(); - const y = event.clientY - rect.top + (this.scrollableContent?.scrollTop || 0); + private getClickPosition(event: MouseEvent, dayColumn: HTMLElement): GridPosition { + const rect = dayColumn.getBoundingClientRect(); + const y = event.clientY - rect.top; - const minuteHeight = calendarConfig.minuteHeight; + const hourHeight = calendarConfig.get('hourHeight'); + const minuteHeight = hourHeight / 60; const snapInterval = calendarConfig.get('snapInterval'); const dayStartHour = calendarConfig.get('dayStartHour'); - // Calculate minutes from start of day - let minutes = Math.floor(y / minuteHeight); + // Calculate total minutes from day start + let totalMinutes = Math.floor(y / minuteHeight); // Snap to interval - minutes = Math.round(minutes / snapInterval) * snapInterval; + totalMinutes = Math.round(totalMinutes / snapInterval) * snapInterval; // Add day start offset - const totalMinutes = (dayStartHour * 60) + minutes; + totalMinutes += dayStartHour * 60; return { minutes: totalMinutes, time: this.minutesToTime(totalMinutes), - y: minutes * minuteHeight + y: y }; } + /** + * Scroll to specific hour + */ + scrollToHour(hour: number): void { + if (!this.grid) return; + + const hourHeight = calendarConfig.get('hourHeight'); + const dayStartHour = calendarConfig.get('dayStartHour'); + const headerHeight = 80; // Header row height + const scrollTop = headerHeight + ((hour - dayStartHour) * hourHeight); + + this.grid.scrollTop = scrollTop; + } + /** * Utility methods */ @@ -346,241 +386,4 @@ export class GridManager { return `${displayHour}:${minutes.toString().padStart(2, '0')} ${period}`; } - - /** - * Scroll to specific hour - */ - scrollToHour(hour: number): void { - if (!this.scrollableContent) return; - - const hourHeight = calendarConfig.get('hourHeight'); - const dayStartHour = calendarConfig.get('dayStartHour'); - const scrollTop = (hour - dayStartHour) * hourHeight; - - this.scrollableContent.scrollTop = scrollTop; - } - - /** - * Render grid for a specific container (used during navigation transitions) - */ - private renderGridForContainer(container: HTMLElement, weekStart: Date): void { - // Find the week header and scrollable content within this container - const weekHeader = container.querySelector('swp-week-header'); - const scrollableContent = container.querySelector('swp-scrollable-content'); - const timeGrid = container.querySelector('swp-time-grid'); - - if (!weekHeader || !scrollableContent || !timeGrid) { - console.warn('GridManager: Required elements not found in container'); - return; - } - - // Render week header for this container - this.renderWeekHeaderForContainer(weekHeader as HTMLElement, weekStart); - - // Render grid content for this container - pass weekStart - this.renderGridForSpecificContainer(container, weekStart); - this.renderGridLinesForContainer(timeGrid as HTMLElement); - this.setupGridInteractionsForContainer(container); - - // Setup scroll sync for this new container - this.setupScrollSyncForContainer(scrollableContent as HTMLElement); - } - - /** - * Render week header for a specific container - */ - private renderWeekHeaderForContainer(weekHeader: HTMLElement, weekStart: Date): void { - const days = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']; - - weekHeader.innerHTML = ''; - - for (let i = 0; i < 7; i++) { - const date = new Date(weekStart); - date.setDate(date.getDate() + i); - - const header = document.createElement('swp-day-header'); - if (this.isToday(date)) { - (header as any).dataset.today = 'true'; - } - - header.innerHTML = ` - ${days[date.getDay()]} - ${date.getDate()} - `; - (header as any).dataset.date = this.formatDate(date); - - weekHeader.appendChild(header); - } - } - - /** - * Render grid structure for a specific container - */ - private renderGridForSpecificContainer(container: HTMLElement, weekStart?: Date): void { - const timeGrid = container.querySelector('swp-time-grid'); - if (!timeGrid) { - console.warn('GridManager: No time-grid found in container'); - return; - } - - // Use the weekStart parameter or fall back to currentWeek - const targetWeek = weekStart || this.currentWeek; - if (!targetWeek) { - console.warn('GridManager: No target week available'); - return; - } - - // Clear existing columns - let dayColumns = timeGrid.querySelector('swp-day-columns'); - if (!dayColumns) { - dayColumns = document.createElement('swp-day-columns'); - timeGrid.appendChild(dayColumns); - } - - dayColumns.innerHTML = ''; - - const view = calendarConfig.get('view'); - const columnsCount = view === 'week' ? calendarConfig.get('weekDays') : 1; - - // Create columns using the target week - for (let i = 0; i < columnsCount; i++) { - const column = document.createElement('swp-day-column'); - (column as any).dataset.columnIndex = i; - - const dates = this.getWeekDates(targetWeek); - if (dates[i]) { - (column as any).dataset.date = this.formatDate(dates[i]); - } - - // Add events container - const eventsLayer = document.createElement('swp-events-layer'); - column.appendChild(eventsLayer); - - dayColumns.appendChild(column); - } - - // Update grid styles for this container - const totalHeight = calendarConfig.totalHours * calendarConfig.get('hourHeight'); - (timeGrid as HTMLElement).style.height = `${totalHeight}px`; - } - - /** - * Render grid lines for a specific time grid - */ - private renderGridLinesForContainer(timeGrid: HTMLElement): void { - let gridLines = timeGrid.querySelector('swp-grid-lines'); - if (!gridLines) { - gridLines = document.createElement('swp-grid-lines'); - timeGrid.insertBefore(gridLines, timeGrid.firstChild); - } - - const totalHours = calendarConfig.totalHours; - const hourHeight = calendarConfig.get('hourHeight'); - - // Set CSS variables - timeGrid.style.setProperty('--total-hours', totalHours.toString()); - timeGrid.style.setProperty('--hour-height', `${hourHeight}px`); - } - - /** - * Setup grid interactions for a specific container - */ - private setupGridInteractionsForContainer(container: HTMLElement): void { - const timeGrid = container.querySelector('swp-time-grid'); - if (!timeGrid) return; - - // Click handler - timeGrid.addEventListener('click', (e: Event) => { - const mouseEvent = e as MouseEvent; - // Ignore if clicking on an event - if ((mouseEvent.target as Element).closest('swp-event')) return; - - const column = (mouseEvent.target as Element).closest('swp-day-column') as HTMLElement; - if (!column) return; - - const position = this.getClickPositionForContainer(mouseEvent, column, container); - - eventBus.emit(EventTypes.GRID_CLICK, { - date: (column as any).dataset.date, - time: position.time, - minutes: position.minutes, - columnIndex: parseInt((column as any).dataset.columnIndex) - }); - }); - - // Double click handler - timeGrid.addEventListener('dblclick', (e: Event) => { - const mouseEvent = e as MouseEvent; - // Ignore if clicking on an event - if ((mouseEvent.target as Element).closest('swp-event')) return; - - const column = (mouseEvent.target as Element).closest('swp-day-column') as HTMLElement; - if (!column) return; - - const position = this.getClickPositionForContainer(mouseEvent, column, container); - - eventBus.emit(EventTypes.GRID_DBLCLICK, { - date: (column as any).dataset.date, - time: position.time, - minutes: position.minutes, - columnIndex: parseInt((column as any).dataset.columnIndex) - }); - }); - } - - /** - * Get click position for a specific container - */ - private getClickPositionForContainer(event: MouseEvent, column: HTMLElement, container: HTMLElement): GridPosition { - const rect = column.getBoundingClientRect(); - const scrollableContent = container.querySelector('swp-scrollable-content') as HTMLElement; - const y = event.clientY - rect.top + (scrollableContent?.scrollTop || 0); - - const minuteHeight = calendarConfig.minuteHeight; - const snapInterval = calendarConfig.get('snapInterval'); - const dayStartHour = calendarConfig.get('dayStartHour'); - - // Calculate minutes from start of day - let minutes = Math.floor(y / minuteHeight); - - // Snap to interval - minutes = Math.round(minutes / snapInterval) * snapInterval; - - // Add day start offset - const totalMinutes = (dayStartHour * 60) + minutes; - - return { - minutes: totalMinutes, - time: this.minutesToTime(totalMinutes), - y: minutes * minuteHeight - }; - } - - /** - * Setup scroll synchronization between time-axis and scrollable content - */ - private setupScrollSync(): void { - if (!this.scrollableContent || !this.timeAxis) return; - - // Sync time-axis scroll with scrollable content - this.scrollableContent.addEventListener('scroll', () => { - if (this.timeAxis) { - this.timeAxis.scrollTop = this.scrollableContent!.scrollTop; - } - }); - } - - /** - * Setup scroll synchronization for a specific container's scrollable content - */ - private setupScrollSyncForContainer(scrollableContent: HTMLElement): void { - if (!this.timeAxis) return; - - // Sync time-axis scroll with this container's scrollable content - scrollableContent.addEventListener('scroll', () => { - if (this.timeAxis) { - this.timeAxis.scrollTop = scrollableContent.scrollTop; - } - }); - } } \ No newline at end of file diff --git a/src/managers/NavigationManager.ts b/src/managers/NavigationManager.ts index 8971e8f..44d66cb 100644 --- a/src/managers/NavigationManager.ts +++ b/src/managers/NavigationManager.ts @@ -4,7 +4,7 @@ import { EventTypes } from '../constants/EventTypes.js'; /** * NavigationManager handles calendar navigation (prev/next/today buttons) - * and week transitions with smooth animations + * with simplified CSS Grid approach */ export class NavigationManager { private eventBus: IEventBus; @@ -106,57 +106,22 @@ export class NavigationManager { private animateTransition(direction: 'prev' | 'next', targetWeek: Date): void { const calendarContainer = document.querySelector('swp-calendar-container'); - const currentWeekContainer = document.querySelector('swp-week-container'); - if (!calendarContainer || !currentWeekContainer) { - console.warn('NavigationManager: Required DOM elements not found'); + if (!calendarContainer) { + console.warn('NavigationManager: Calendar container not found'); return; } - // Create new week container (following POC structure) - const newWeekContainer = document.createElement('swp-week-container'); - newWeekContainer.innerHTML = ` - - - - - - - - `; + // Add transition class for visual feedback + calendarContainer.classList.add('week-transition'); - // Position new week off-screen - newWeekContainer.style.position = 'absolute'; - newWeekContainer.style.top = '0'; - newWeekContainer.style.left = '0'; - newWeekContainer.style.width = '100%'; - newWeekContainer.style.height = '100%'; - newWeekContainer.style.transform = direction === 'next' ? 'translateX(100%)' : 'translateX(-100%)'; - - // Add to calendar container - calendarContainer.appendChild(newWeekContainer); - - // Notify other managers to render content for the new week - this.eventBus.emit(EventTypes.WEEK_CONTAINER_CREATED, { - container: newWeekContainer, - weekStart: targetWeek - }); - - // Animate transition - requestAnimationFrame(() => { - // Slide out current week - (currentWeekContainer as HTMLElement).style.transform = direction === 'next' ? 'translateX(-100%)' : 'translateX(100%)'; - (currentWeekContainer as HTMLElement).style.opacity = '0.5'; + // Brief fade effect + setTimeout(() => { + calendarContainer.classList.add('week-transition-out'); - // Slide in new week - newWeekContainer.style.transform = 'translateX(0)'; - - // Clean up after animation + // Update the week after fade starts setTimeout(() => { - currentWeekContainer.remove(); - newWeekContainer.style.position = 'relative'; - - // Update currentWeek only after animation is complete + // Update currentWeek this.currentWeek = new Date(targetWeek); this.animationQueue--; @@ -172,8 +137,13 @@ export class NavigationManager { weekEnd: DateUtils.addDays(this.currentWeek, 6) }); - }, 400); // Match CSS transition duration - }); + // Remove transition classes + setTimeout(() => { + calendarContainer.classList.remove('week-transition', 'week-transition-out'); + }, 150); + + }, 150); // Half of transition duration + }, 50); } private updateWeekInfo(): void { diff --git a/src/managers/ViewManager.ts b/src/managers/ViewManager.ts index 298cd96..a121e51 100644 --- a/src/managers/ViewManager.ts +++ b/src/managers/ViewManager.ts @@ -49,9 +49,6 @@ export class ViewManager { } private initializeView(): void { - this.renderTimeAxis(); - this.renderWeekHeaders(); - this.renderDayColumns(); this.updateViewButtons(); this.eventBus.emit(EventTypes.VIEW_RENDERED, { @@ -75,65 +72,6 @@ export class ViewManager { }); } - private renderTimeAxis(): void { - const timeAxis = document.querySelector('swp-time-axis'); - if (!timeAxis) return; - - const startHour = calendarConfig.get('dayStartHour'); - const endHour = calendarConfig.get('dayEndHour'); - - timeAxis.innerHTML = ''; - - for (let hour = startHour; hour <= endHour; hour++) { - const marker = document.createElement('swp-hour-marker'); - const period = hour >= 12 ? 'PM' : 'AM'; - const displayHour = hour > 12 ? hour - 12 : (hour === 0 ? 12 : hour); - marker.textContent = `${displayHour} ${period}`; - timeAxis.appendChild(marker); - } - } - - private renderWeekHeaders(): void { - const weekHeader = document.querySelector('swp-week-header'); - if (!weekHeader) return; - - const days = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']; - - weekHeader.innerHTML = ''; - - for (let i = 0; i < 7; i++) { - const header = document.createElement('swp-day-header'); - header.innerHTML = ` - ${days[i]} - ${i + 1} - `; - header.dataset.dayIndex = i.toString(); - - // Check if today (this will be updated by NavigationManager later) - if (i === 1) { // Mock today as Monday for now - header.setAttribute('data-today', 'true'); - } - - weekHeader.appendChild(header); - } - } - - private renderDayColumns(): void { - const dayColumns = document.querySelector('swp-day-columns'); - if (!dayColumns) return; - - dayColumns.innerHTML = ''; - - for (let i = 0; i < 7; i++) { - const column = document.createElement('swp-day-column'); - column.dataset.dayIndex = i.toString(); - - const eventsLayer = document.createElement('swp-events-layer'); - column.appendChild(eventsLayer); - - dayColumns.appendChild(column); - } - } private updateViewButtons(): void { const viewButtons = document.querySelectorAll('swp-view-button[data-view]'); @@ -148,9 +86,6 @@ export class ViewManager { } private refreshCurrentView(): void { - this.renderWeekHeaders(); - this.renderDayColumns(); - this.eventBus.emit(EventTypes.VIEW_RENDERED, { view: this.currentView }); diff --git a/wwwroot/css/calendar-base-css.css b/wwwroot/css/calendar-base-css.css index b4db468..562ded2 100644 --- a/wwwroot/css/calendar-base-css.css +++ b/wwwroot/css/calendar-base-css.css @@ -86,17 +86,13 @@ body { swp-calendar, swp-calendar-nav, swp-calendar-container, -swp-time-axis, -swp-week-header, -swp-scrollable-content, -swp-time-grid, -swp-day-columns, -swp-day-column, -swp-events-layer, +swp-calendar-grid, +swp-header-cell, +swp-time-cell, +swp-day-cell, +swp-events-container, swp-event, swp-loading-overlay, -swp-week-container, -swp-grid-lines, swp-nav-group, swp-nav-button, swp-search-container, @@ -107,10 +103,8 @@ swp-view-button, swp-week-info, swp-week-number, swp-date-range, -swp-day-header, swp-day-name, swp-day-date, -swp-hour-marker, swp-event-time, swp-event-title, swp-spinner { diff --git a/wwwroot/css/calendar-layout-css.css b/wwwroot/css/calendar-layout-css.css index 3d0a86a..77b4e7a 100644 --- a/wwwroot/css/calendar-layout-css.css +++ b/wwwroot/css/calendar-layout-css.css @@ -1,4 +1,4 @@ -/* styles/layout.css */ +/* styles/layout.css - POC Structure Implementation */ /* Main calendar container */ swp-calendar { @@ -21,7 +21,7 @@ swp-calendar-nav { box-shadow: var(--shadow-sm); } -/* Calendar container grid (following POC structure) */ +/* Calendar container grid - POC structure */ swp-calendar-container { flex: 1; display: grid; @@ -31,19 +31,6 @@ swp-calendar-container { position: relative; } -/* Time axis (fixed, left side) */ -swp-time-axis { - grid-column: 1; - grid-row: 1; - background: var(--color-surface); - border-right: 1px solid var(--color-border); - position: sticky; - left: 0; - z-index: 4; - padding-top: 80px; /* Match header height */ - overflow-y: hidden; /* Hide scrollbar but allow programmatic scrolling */ - overflow-x: hidden; -} /* Week container for sliding */ swp-week-container { @@ -55,7 +42,40 @@ swp-week-container { transition: transform 400ms cubic-bezier(0.4, 0, 0.2, 1); } -/* Week header (inside week container) */ +/* Time axis */ +swp-time-axis { + grid-column: 1; + grid-row: 1; + background: var(--color-surface); + border-right: 1px solid var(--color-border); + position: sticky; + left: 0; + z-index: 4; + padding-top: 80px; /* Match header height */ +} + +swp-hour-marker { + height: var(--hour-height); + padding: 0 8px 8px 8px; + font-size: 0.75rem; + color: var(--color-text-secondary); + display: flex; + align-items: flex-start; + position: relative; +} + +swp-hour-marker::after { + content: ''; + position: absolute; + top: 0; + left: 100%; + width: 100vw; + height: 1px; + background: var(--color-grid-line); + pointer-events: none; +} + +/* Week header */ swp-week-header { display: grid; grid-template-columns: repeat(7, 1fr); @@ -67,57 +87,6 @@ swp-week-header { height: 80px; /* Fixed height */ } -/* Scrollable content */ -swp-scrollable-content { - overflow-y: auto; - overflow-x: hidden; - scroll-behavior: smooth; - position: relative; - flex: 1; - min-height: 0; /* Important for flex children to shrink */ - max-height: calc(100vh - 80px - 80px); /* Subtract nav height and week-header height */ -} - -swp-week-container.slide-out-left { - transform: translateX(-100%); -} - -swp-week-container.slide-out-right { - transform: translateX(100%); -} - -swp-week-container.slide-in-left { - transform: translateX(-100%); -} - -swp-week-container.slide-in-right { - transform: translateX(100%); -} - -swp-hour-marker { - height: var(--hour-height); - padding: 8px; - font-size: 0.75rem; - color: var(--color-text-secondary); - display: flex; - align-items: flex-start; - position: relative; - - /* Hour line extending into calendar */ - &::after { - content: ''; - position: absolute; - top: 0; - left: 100%; - width: 100vw; - height: 1px; - background: var(--color-grid-line); - pointer-events: none; - } -} - -/* Day header styling (inside week-header) */ - swp-day-header { padding: 12px; text-align: center; @@ -158,24 +127,19 @@ swp-day-header[data-today="true"] swp-day-date { margin: 4px auto 0; } -/* All-day events container */ -swp-allday-container { - position: sticky; - top: 0; - background: var(--color-background); - border-bottom: 1px solid var(--color-border); - min-height: 0; - z-index: 2; - - &:not(:empty) { - padding: 8px 0; - } +/* Scrollable content */ +swp-scrollable-content { + overflow-y: auto; + overflow-x: hidden; + scroll-behavior: smooth; + position: relative; + display: grid; } /* Time grid */ swp-time-grid { position: relative; - height: calc(24 * var(--hour-height)); + height: calc(12 * var(--hour-height)); } swp-time-grid::before { @@ -195,38 +159,13 @@ swp-grid-lines { inset: 0; pointer-events: none; z-index: var(--z-grid); - background-image: - /* Hour lines (stronger) */ - repeating-linear-gradient( - to bottom, - transparent, - transparent calc(var(--hour-height) - 1px), - var(--color-grid-line) calc(var(--hour-height) - 1px), - var(--color-grid-line) var(--hour-height) - ), - /* Quarter hour lines (lighter) */ - repeating-linear-gradient( - to bottom, - transparent, - transparent calc(var(--hour-height) / 4 - 1px), - var(--color-grid-line-light) calc(var(--hour-height) / 4 - 1px), - var(--color-grid-line-light) calc(var(--hour-height) / 4) - ); -} - -/* Ensure grid lines are visible during transitions */ -swp-week-container swp-grid-lines { - opacity: 1; - visibility: visible; -} - -/* Grid lines should remain visible even during animations */ -swp-week-container.slide-out-left swp-grid-lines, -swp-week-container.slide-out-right swp-grid-lines, -swp-week-container.slide-in-left swp-grid-lines, -swp-week-container.slide-in-right swp-grid-lines { - opacity: 1; - visibility: visible; + background-image: repeating-linear-gradient( + to bottom, + transparent, + transparent calc(var(--hour-height) / 4 - 1px), + var(--color-grid-line-light) calc(var(--hour-height) / 4 - 1px), + var(--color-grid-line-light) calc(var(--hour-height) / 4) + ); } /* Day columns */ @@ -251,6 +190,10 @@ swp-events-layer { inset: 0; } +swp-event { + pointer-events: auto; +} + /* Current time indicator */ swp-current-time-indicator { position: absolute; @@ -287,7 +230,13 @@ swp-current-time-indicator { border-radius: 50%; box-shadow: 0 0 0 2px rgba(255, 0, 0, 0.3); } - - /* Position based on current time */ - top: calc(var(--current-minutes) * var(--minute-height)); +} + +/* Week navigation animations - simplified */ +swp-calendar-container.week-transition { + transition: opacity 300ms ease; +} + +swp-calendar-container.week-transition-out { + opacity: 0.5; } \ No newline at end of file diff --git a/wwwroot/index.html b/wwwroot/index.html index e467812..6918d5a 100644 --- a/wwwroot/index.html +++ b/wwwroot/index.html @@ -49,20 +49,9 @@ - + - - - - - - - - - - - - +