Refactors event rendering to be event-driven

Moves event rendering logic into a dedicated EventRenderer class that uses a strategy pattern for different calendar types.

The rendering is now triggered by `GRID_RENDERED` and `CONTAINER_READY_FOR_EVENTS` events, emitted by the GridManager and NavigationManager respectively.

This change decouples the CalendarManager from direct event rendering and allows for more flexible and efficient event updates. The EventManager now has a method to fetch events for a given time period.

Removes direct calls to event rendering from CalendarManager. Improves animation transitions by using pre-rendered containers in the NavigationManager.
This commit is contained in:
Janus Knudsen 2025-08-16 00:51:12 +02:00
parent afe5b6b899
commit a03f314c4a
9 changed files with 271 additions and 166 deletions

View file

@ -8,18 +8,15 @@ import { DateUtils } from '../utils/DateUtils';
* Interface for event rendering strategies
*/
export interface EventRendererStrategy {
findColumn(event: CalendarEvent): HTMLElement | null;
renderEvents(events: CalendarEvent[], config: CalendarConfig): void;
clearEvents(): void;
renderEvents(events: CalendarEvent[], container: HTMLElement, config: CalendarConfig): void;
clearEvents(container?: HTMLElement): void;
}
/**
* Base class for event renderers with common functionality
*/
export abstract class BaseEventRenderer implements EventRendererStrategy {
abstract findColumn(event: CalendarEvent): HTMLElement | null;
renderEvents(events: CalendarEvent[], config: CalendarConfig): void {
renderEvents(events: CalendarEvent[], container: HTMLElement, config: CalendarConfig): void {
console.log('BaseEventRenderer: renderEvents called with', events.length, 'events');
// NOTE: Removed clearEvents() to support sliding animation
@ -30,10 +27,9 @@ export abstract class BaseEventRenderer implements EventRendererStrategy {
// Events should already be filtered by DataManager - no need to filter here
console.log('BaseEventRenderer: Rendering', events.length, 'pre-filtered events');
// OPTIMIZATION: Column-first rendering instead of event-first
// This is much more efficient when there are many events
const columns = this.getColumns();
console.log(`BaseEventRenderer: Found ${columns.length} columns to render events in`);
// Find columns in the specific container
const columns = this.getColumns(container);
console.log(`BaseEventRenderer: Found ${columns.length} columns in container`);
columns.forEach(column => {
const columnEvents = this.getEventsForColumn(column, events);
@ -55,8 +51,8 @@ export abstract class BaseEventRenderer implements EventRendererStrategy {
});
}
// Abstract methods that subclasses must implement for column-first rendering
protected abstract getColumns(): HTMLElement[];
// Abstract methods that subclasses must implement
protected abstract getColumns(container: HTMLElement): HTMLElement[];
protected abstract getEventsForColumn(column: HTMLElement, events: CalendarEvent[]): CalendarEvent[];
protected renderEvent(event: CalendarEvent, container: Element, config: CalendarConfig): void {
@ -138,10 +134,15 @@ export abstract class BaseEventRenderer implements EventRendererStrategy {
return `${displayHour}:${minutes.toString().padStart(2, '0')} ${period}`;
}
clearEvents(): void {
const existingEvents = document.querySelectorAll('swp-event');
clearEvents(container?: HTMLElement): void {
const selector = 'swp-event';
const existingEvents = container
? container.querySelectorAll(selector)
: document.querySelectorAll(selector);
if (existingEvents.length > 0) {
console.warn(`🗑️ BaseEventRenderer: REMOVING ${existingEvents.length} events from DOM! Stack trace:`, new Error().stack);
console.log(`BaseEventRenderer: Clearing ${existingEvents.length} events`,
container ? 'from container' : 'globally');
}
existingEvents.forEach(event => event.remove());
}
@ -151,26 +152,9 @@ export abstract class BaseEventRenderer implements EventRendererStrategy {
* Date-based event renderer
*/
export class DateEventRenderer extends BaseEventRenderer {
findColumn(event: CalendarEvent): HTMLElement | null {
const eventDate = new Date(event.start);
const dateStr = DateUtils.formatDate(eventDate);
const dayColumn = document.querySelector(`swp-day-column[data-date="${dateStr}"]`) as HTMLElement;
// Debug: Check all available columns
const allColumns = document.querySelectorAll('swp-day-column');
const availableDates = Array.from(allColumns).map(col => (col as HTMLElement).dataset.date);
console.log('DateEventRenderer: Event', event.title, 'start:', event.start);
console.log('DateEventRenderer: Looking for date:', dateStr);
console.log('DateEventRenderer: Available columns with dates:', availableDates);
console.log('DateEventRenderer: Found column:', !!dayColumn);
return dayColumn;
}
protected getColumns(): HTMLElement[] {
const columns = document.querySelectorAll('swp-day-column');
console.log('DateEventRenderer: Found', columns.length, 'day columns in DOM');
protected getColumns(container: HTMLElement): HTMLElement[] {
const columns = container.querySelectorAll('swp-day-column');
console.log('DateEventRenderer: Found', columns.length, 'day columns in container');
return Array.from(columns) as HTMLElement[];
}
@ -193,21 +177,9 @@ export class DateEventRenderer extends BaseEventRenderer {
* Resource-based event renderer
*/
export class ResourceEventRenderer extends BaseEventRenderer {
findColumn(event: CalendarEvent): HTMLElement | null {
const resourceName = event.resource?.name;
if (!resourceName) {
console.warn('ResourceEventRenderer: Event has no resource.name', event);
return null;
}
const resourceColumn = document.querySelector(`swp-resource-column[data-resource="${resourceName}"]`) as HTMLElement;
console.log('ResourceEventRenderer: Looking for resource column with name', resourceName, 'found:', !!resourceColumn);
return resourceColumn;
}
protected getColumns(): HTMLElement[] {
const columns = document.querySelectorAll('swp-resource-column');
console.log('ResourceEventRenderer: Found', columns.length, 'resource columns in DOM');
protected getColumns(container: HTMLElement): HTMLElement[] {
const columns = container.querySelectorAll('swp-resource-column');
console.log('ResourceEventRenderer: Found', columns.length, 'resource columns in container');
return Array.from(columns) as HTMLElement[];
}