2025-08-07 00:15:44 +02:00
|
|
|
// Event rendering strategy interface and implementations
|
|
|
|
|
|
|
|
|
|
import { CalendarEvent } from '../types/CalendarTypes';
|
|
|
|
|
import { CalendarConfig } from '../core/CalendarConfig';
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 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 {
|
|
|
|
|
console.log('BaseEventRenderer: renderEvents called with', events.length, 'events');
|
|
|
|
|
|
2025-08-12 00:31:02 +02:00
|
|
|
// NOTE: Removed clearEvents() to support sliding animation
|
|
|
|
|
// With sliding animation, multiple grid containers exist simultaneously
|
|
|
|
|
// clearEvents() would remove events from all containers, breaking the animation
|
|
|
|
|
// Events are now rendered directly into the new container without clearing
|
2025-08-07 00:15:44 +02:00
|
|
|
|
2025-08-12 00:07:39 +02:00
|
|
|
// Events should already be filtered by DataManager - no need to filter here
|
|
|
|
|
console.log('BaseEventRenderer: Rendering', events.length, 'pre-filtered events');
|
2025-08-07 00:15:44 +02:00
|
|
|
|
|
|
|
|
// Render each event in the correct column
|
2025-08-12 00:07:39 +02:00
|
|
|
events.forEach(event => {
|
2025-08-07 00:15:44 +02:00
|
|
|
const column = this.findColumn(event);
|
|
|
|
|
|
|
|
|
|
if (column) {
|
|
|
|
|
const eventsLayer = column.querySelector('swp-events-layer');
|
|
|
|
|
if (eventsLayer) {
|
2025-08-09 01:16:04 +02:00
|
|
|
console.log(`BaseEventRenderer: Rendering event "${event.title}" in events layer`);
|
2025-08-07 00:15:44 +02:00
|
|
|
this.renderEvent(event, eventsLayer, config);
|
2025-08-09 01:16:04 +02:00
|
|
|
|
|
|
|
|
// Debug: Verify event was actually added
|
|
|
|
|
const renderedEvents = eventsLayer.querySelectorAll('swp-event');
|
|
|
|
|
console.log(`BaseEventRenderer: Events layer now has ${renderedEvents.length} events`);
|
2025-08-07 00:15:44 +02:00
|
|
|
} else {
|
2025-08-09 01:16:04 +02:00
|
|
|
console.warn('BaseEventRenderer: No events layer found in column for event', event.title, 'Column:', column);
|
2025-08-07 00:15:44 +02:00
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
console.warn('BaseEventRenderer: No column found for event', event.title);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected renderEvent(event: CalendarEvent, container: Element, config: CalendarConfig): void {
|
|
|
|
|
const eventElement = document.createElement('swp-event');
|
|
|
|
|
eventElement.dataset.eventId = event.id;
|
|
|
|
|
eventElement.dataset.type = event.type;
|
|
|
|
|
|
|
|
|
|
// Calculate position based on time
|
|
|
|
|
const position = this.calculateEventPosition(event, config);
|
|
|
|
|
eventElement.style.position = 'absolute';
|
|
|
|
|
eventElement.style.top = `${position.top + 1}px`;
|
|
|
|
|
eventElement.style.height = `${position.height - 1}px`;
|
|
|
|
|
|
|
|
|
|
// Set color
|
|
|
|
|
eventElement.style.backgroundColor = event.metadata?.color || '#3498db';
|
|
|
|
|
|
|
|
|
|
// Format time for display
|
|
|
|
|
const startTime = this.formatTime(event.start);
|
|
|
|
|
const endTime = this.formatTime(event.end);
|
|
|
|
|
|
|
|
|
|
// Create event content
|
|
|
|
|
eventElement.innerHTML = `
|
|
|
|
|
<swp-event-time>${startTime} - ${endTime}</swp-event-time>
|
|
|
|
|
<swp-event-title>${event.title}</swp-event-title>
|
|
|
|
|
`;
|
|
|
|
|
|
|
|
|
|
container.appendChild(eventElement);
|
2025-08-09 01:16:04 +02:00
|
|
|
|
|
|
|
|
console.log(`BaseEventRenderer: Created event element for "${event.title}":`, {
|
|
|
|
|
top: eventElement.style.top,
|
|
|
|
|
height: eventElement.style.height,
|
|
|
|
|
backgroundColor: eventElement.style.backgroundColor,
|
|
|
|
|
position: eventElement.style.position,
|
|
|
|
|
innerHTML: eventElement.innerHTML
|
|
|
|
|
});
|
2025-08-07 00:15:44 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected calculateEventPosition(event: CalendarEvent, config: CalendarConfig): { top: number; height: number } {
|
|
|
|
|
const startDate = new Date(event.start);
|
|
|
|
|
const endDate = new Date(event.end);
|
|
|
|
|
|
2025-08-09 01:16:04 +02:00
|
|
|
const gridSettings = config.getGridSettings();
|
|
|
|
|
const dayStartHour = gridSettings.dayStartHour;
|
|
|
|
|
const hourHeight = gridSettings.hourHeight;
|
2025-08-07 00:15:44 +02:00
|
|
|
|
|
|
|
|
// Calculate minutes from visible day start
|
|
|
|
|
const startMinutes = startDate.getHours() * 60 + startDate.getMinutes();
|
|
|
|
|
const endMinutes = endDate.getHours() * 60 + endDate.getMinutes();
|
|
|
|
|
const dayStartMinutes = dayStartHour * 60;
|
|
|
|
|
|
|
|
|
|
// Calculate top position (subtract day start to align with time axis)
|
|
|
|
|
const top = ((startMinutes - dayStartMinutes) / 60) * hourHeight;
|
|
|
|
|
|
|
|
|
|
// Calculate height
|
|
|
|
|
const durationMinutes = endMinutes - startMinutes;
|
|
|
|
|
const height = (durationMinutes / 60) * hourHeight;
|
|
|
|
|
|
2025-08-09 01:16:04 +02:00
|
|
|
console.log('Event positioning calculation:', {
|
|
|
|
|
eventTime: `${startDate.getHours()}:${startDate.getMinutes()}`,
|
|
|
|
|
startMinutes,
|
|
|
|
|
endMinutes,
|
|
|
|
|
dayStartMinutes,
|
|
|
|
|
dayStartHour,
|
|
|
|
|
hourHeight,
|
|
|
|
|
top,
|
|
|
|
|
height
|
|
|
|
|
});
|
|
|
|
|
|
2025-08-07 00:15:44 +02:00
|
|
|
return { top, height };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected formatTime(isoString: string): string {
|
|
|
|
|
const date = new Date(isoString);
|
|
|
|
|
const hours = date.getHours();
|
|
|
|
|
const minutes = date.getMinutes();
|
|
|
|
|
const period = hours >= 12 ? 'PM' : 'AM';
|
|
|
|
|
const displayHour = hours > 12 ? hours - 12 : (hours === 0 ? 12 : hours);
|
|
|
|
|
|
|
|
|
|
return `${displayHour}:${minutes.toString().padStart(2, '0')} ${period}`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
clearEvents(): void {
|
|
|
|
|
const existingEvents = document.querySelectorAll('swp-event');
|
2025-08-09 01:16:04 +02:00
|
|
|
if (existingEvents.length > 0) {
|
|
|
|
|
console.warn(`🗑️ BaseEventRenderer: REMOVING ${existingEvents.length} events from DOM! Stack trace:`, new Error().stack);
|
|
|
|
|
}
|
2025-08-07 00:15:44 +02:00
|
|
|
existingEvents.forEach(event => event.remove());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 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;
|
2025-08-09 01:16:04 +02:00
|
|
|
|
|
|
|
|
// 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);
|
|
|
|
|
|
2025-08-07 00:15:44 +02:00
|
|
|
return dayColumn;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Resource-based event renderer
|
|
|
|
|
*/
|
|
|
|
|
export class ResourceEventRenderer extends BaseEventRenderer {
|
|
|
|
|
findColumn(event: CalendarEvent): HTMLElement | null {
|
2025-08-12 00:07:39 +02:00
|
|
|
const resourceName = event.resource?.name;
|
|
|
|
|
if (!resourceName) {
|
|
|
|
|
console.warn('ResourceEventRenderer: Event has no resource.name', event);
|
2025-08-07 00:15:44 +02:00
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-12 00:07:39 +02:00
|
|
|
const resourceColumn = document.querySelector(`swp-resource-column[data-resource="${resourceName}"]`) as HTMLElement;
|
|
|
|
|
console.log('ResourceEventRenderer: Looking for resource column with name', resourceName, 'found:', !!resourceColumn);
|
2025-08-07 00:15:44 +02:00
|
|
|
return resourceColumn;
|
|
|
|
|
}
|
|
|
|
|
}
|