Refactors event rendering to leverage CSS for styling. This change simplifies the event renderer by removing styling logic from the Typescript code and placing it in the CSS file. This improves maintainability and allows for easier customization of event appearance. Hover effects are also moved to CSS for consistency.
226 lines
No EOL
8.3 KiB
TypeScript
226 lines
No EOL
8.3 KiB
TypeScript
import { EventBus } from '../core/EventBus';
|
|
import { IEventBus, CalendarEvent } from '../types/CalendarTypes';
|
|
import { EventTypes } from '../constants/EventTypes';
|
|
import { calendarConfig } from '../core/CalendarConfig';
|
|
import { DateUtils } from '../utils/DateUtils';
|
|
|
|
/**
|
|
* EventRenderer - Render events i DOM med positionering
|
|
* Håndterer event positioning og overlap detection
|
|
*/
|
|
export class EventRenderer {
|
|
private eventBus: IEventBus;
|
|
|
|
constructor(eventBus: IEventBus) {
|
|
this.eventBus = eventBus;
|
|
this.setupEventListeners();
|
|
}
|
|
|
|
private setupEventListeners(): void {
|
|
this.eventBus.on(EventTypes.EVENTS_LOADED, (event: Event) => {
|
|
const customEvent = event as CustomEvent;
|
|
const { events } = customEvent.detail;
|
|
console.log('EventRenderer: Received EVENTS_LOADED with', events.length, '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, () => {
|
|
// Clear existing events when view changes
|
|
this.clearEvents();
|
|
});
|
|
}
|
|
|
|
private pendingEvents: CalendarEvent[] = [];
|
|
|
|
private tryRenderEvents(): void {
|
|
// Only render if we have both events and grid is ready
|
|
console.log('EventRenderer: tryRenderEvents called, pending events:', this.pendingEvents.length);
|
|
if (this.pendingEvents.length > 0) {
|
|
const dayColumns = document.querySelectorAll('swp-day-column');
|
|
console.log('EventRenderer: Found', dayColumns.length, 'day columns');
|
|
if (dayColumns.length > 0) {
|
|
this.renderEvents(this.pendingEvents);
|
|
this.pendingEvents = []; // Clear pending events after rendering
|
|
}
|
|
}
|
|
}
|
|
|
|
private renderEvents(events: CalendarEvent[]): void {
|
|
console.log('EventRenderer: renderEvents called with', events.length, 'events');
|
|
console.log('EventRenderer: All events:', events.map(e => ({ title: e.title, allDay: e.allDay, start: e.start })));
|
|
|
|
// Clear existing events first
|
|
this.clearEvents();
|
|
|
|
// Get current week dates for filtering
|
|
const currentWeekDates = this.getCurrentWeekDates();
|
|
// Filter events for current week and exclude all-day events (handled by GridManager)
|
|
const currentWeekEvents = events.filter(event => {
|
|
// Skip all-day events - they are handled by GridManager
|
|
if (event.allDay) {
|
|
return false;
|
|
}
|
|
|
|
const eventDate = new Date(event.start);
|
|
const eventDateStr = DateUtils.formatDate(eventDate);
|
|
const isInCurrentWeek = currentWeekDates.some(weekDate =>
|
|
DateUtils.formatDate(weekDate) === eventDateStr
|
|
);
|
|
return isInCurrentWeek;
|
|
});
|
|
|
|
// Render each event in the correct day column
|
|
currentWeekEvents.forEach(event => {
|
|
const eventDate = new Date(event.start);
|
|
const dayColumn = this.findDayColumn(eventDate);
|
|
|
|
if (dayColumn) {
|
|
const eventsLayer = dayColumn.querySelector('swp-events-layer');
|
|
if (eventsLayer) {
|
|
this.renderEvent(event, eventsLayer);
|
|
} else {
|
|
console.warn('EventRenderer: No events layer found in day column for', DateUtils.formatDate(eventDate));
|
|
}
|
|
} else {
|
|
console.warn('EventRenderer: No day column found for event date', DateUtils.formatDate(eventDate));
|
|
}
|
|
});
|
|
|
|
// Emit event rendered
|
|
this.eventBus.emit(EventTypes.EVENT_RENDERED, {
|
|
count: currentWeekEvents.length
|
|
});
|
|
|
|
}
|
|
|
|
/**
|
|
* Get current week dates (Sunday to Saturday)
|
|
*/
|
|
private getCurrentWeekDates(): Date[] {
|
|
const today = new Date();
|
|
const weekStart = DateUtils.getWeekStart(today, 0); // Sunday start
|
|
const dates: Date[] = [];
|
|
|
|
for (let i = 0; i < 7; i++) {
|
|
const date = DateUtils.addDays(weekStart, i);
|
|
dates.push(date);
|
|
}
|
|
|
|
return dates;
|
|
}
|
|
|
|
/**
|
|
* Find day column for specific date
|
|
*/
|
|
private findDayColumn(date: Date): HTMLElement | null {
|
|
const dateStr = DateUtils.formatDate(date);
|
|
const dayColumn = document.querySelector(`swp-day-column[data-date="${dateStr}"]`) as HTMLElement;
|
|
console.log('EventRenderer: Looking for day column with date', dateStr, 'found:', !!dayColumn);
|
|
return dayColumn;
|
|
}
|
|
|
|
private renderEvent(event: CalendarEvent, container: Element): 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);
|
|
eventElement.style.position = 'absolute';
|
|
eventElement.style.top = `${position.top + 1}px`;
|
|
eventElement.style.height = `${position.height - 1}px`;
|
|
|
|
// Only set positioning and color - rest is in CSS
|
|
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>
|
|
`;
|
|
|
|
// Add event listeners
|
|
this.addEventListeners(eventElement, event);
|
|
|
|
container.appendChild(eventElement);
|
|
|
|
// Event successfully rendered
|
|
}
|
|
|
|
private calculateEventPosition(event: CalendarEvent): { top: number; height: number } {
|
|
const startDate = new Date(event.start);
|
|
const endDate = new Date(event.end);
|
|
|
|
// Use dayStartHour to match time-axis positioning - this is the visible start hour (6 AM)
|
|
const dayStartHour = calendarConfig.get('dayStartHour'); // 6 (6 AM)
|
|
const hourHeight = calendarConfig.get('hourHeight');
|
|
|
|
// Calculate minutes from visible day start (6 AM, not midnight)
|
|
const eventHour = startDate.getHours();
|
|
const eventMinutes = startDate.getMinutes();
|
|
const startMinutes = (eventHour - dayStartHour) * 60 + eventMinutes;
|
|
|
|
// Calculate duration in minutes
|
|
const duration = (endDate.getTime() - startDate.getTime()) / (1000 * 60);
|
|
|
|
// Convert to pixels - position relative to visible time-grid (starts at dayStartHour)
|
|
const top = startMinutes * (hourHeight / 60);
|
|
const height = duration * (hourHeight / 60);
|
|
|
|
|
|
return { top, height };
|
|
}
|
|
|
|
private formatTime(isoString: string): string {
|
|
const date = new Date(isoString);
|
|
const hours = date.getHours();
|
|
const minutes = date.getMinutes();
|
|
|
|
const period = hours >= 12 ? 'PM' : 'AM';
|
|
const displayHours = hours % 12 || 12;
|
|
const displayMinutes = minutes.toString().padStart(2, '0');
|
|
|
|
return `${displayHours}:${displayMinutes} ${period}`;
|
|
}
|
|
|
|
private addEventListeners(eventElement: HTMLElement, event: CalendarEvent): void {
|
|
// Click handler
|
|
eventElement.addEventListener('click', (e) => {
|
|
e.stopPropagation();
|
|
this.eventBus.emit(EventTypes.EVENT_SELECTED, {
|
|
event,
|
|
element: eventElement
|
|
});
|
|
});
|
|
|
|
// Hover effects are handled by CSS
|
|
// Hover effects are now handled by CSS
|
|
}
|
|
|
|
private clearEvents(): void {
|
|
const eventsLayers = document.querySelectorAll('swp-events-layer');
|
|
eventsLayers.forEach(layer => {
|
|
layer.innerHTML = '';
|
|
});
|
|
}
|
|
|
|
public refresh(): void {
|
|
// Request fresh events from EventManager
|
|
this.eventBus.emit(EventTypes.REFRESH_REQUESTED);
|
|
}
|
|
|
|
public destroy(): void {
|
|
this.clearEvents();
|
|
}
|
|
} |