178 lines
5.2 KiB
TypeScript
178 lines
5.2 KiB
TypeScript
|
|
import { CalendarEvent } from '../types/CalendarTypes';
|
||
|
|
import { calendarConfig } from '../core/CalendarConfig';
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Abstract base class for event DOM elements
|
||
|
|
*/
|
||
|
|
export abstract class BaseEventElement {
|
||
|
|
protected element: HTMLElement;
|
||
|
|
protected event: CalendarEvent;
|
||
|
|
|
||
|
|
protected constructor(event: CalendarEvent) {
|
||
|
|
this.event = event;
|
||
|
|
this.element = this.createElement();
|
||
|
|
this.setDataAttributes();
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Create the underlying DOM element
|
||
|
|
*/
|
||
|
|
protected abstract createElement(): HTMLElement;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Set standard data attributes on the element
|
||
|
|
*/
|
||
|
|
protected setDataAttributes(): void {
|
||
|
|
this.element.dataset.eventId = this.event.id;
|
||
|
|
this.element.dataset.title = this.event.title;
|
||
|
|
this.element.dataset.start = this.event.start.toISOString();
|
||
|
|
this.element.dataset.end = this.event.end.toISOString();
|
||
|
|
this.element.dataset.type = this.event.type;
|
||
|
|
this.element.dataset.duration = this.event.metadata?.duration?.toString() || '60';
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get the DOM element
|
||
|
|
*/
|
||
|
|
public getElement(): HTMLElement {
|
||
|
|
return this.element;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Format time for display
|
||
|
|
*/
|
||
|
|
protected formatTime(date: Date): string {
|
||
|
|
const hours = date.getHours();
|
||
|
|
const minutes = date.getMinutes();
|
||
|
|
const period = hours >= 12 ? 'PM' : 'AM';
|
||
|
|
const displayHours = hours > 12 ? hours - 12 : (hours === 0 ? 12 : hours);
|
||
|
|
return `${displayHours}:${minutes.toString().padStart(2, '0')} ${period}`;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Calculate event position for timed events
|
||
|
|
*/
|
||
|
|
protected calculateEventPosition(): { top: number; height: number } {
|
||
|
|
const gridSettings = calendarConfig.getGridSettings();
|
||
|
|
const dayStartHour = gridSettings.dayStartHour;
|
||
|
|
const hourHeight = gridSettings.hourHeight;
|
||
|
|
|
||
|
|
const startMinutes = this.event.start.getHours() * 60 + this.event.start.getMinutes();
|
||
|
|
const endMinutes = this.event.end.getHours() * 60 + this.event.end.getMinutes();
|
||
|
|
const dayStartMinutes = dayStartHour * 60;
|
||
|
|
|
||
|
|
const top = ((startMinutes - dayStartMinutes) / 60) * hourHeight;
|
||
|
|
const durationMinutes = endMinutes - startMinutes;
|
||
|
|
const height = (durationMinutes / 60) * hourHeight;
|
||
|
|
|
||
|
|
return { top, height };
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Timed event element (swp-event)
|
||
|
|
*/
|
||
|
|
export class SwpEventElement extends BaseEventElement {
|
||
|
|
private constructor(event: CalendarEvent) {
|
||
|
|
super(event);
|
||
|
|
this.createInnerStructure();
|
||
|
|
this.applyPositioning();
|
||
|
|
}
|
||
|
|
|
||
|
|
protected createElement(): HTMLElement {
|
||
|
|
return document.createElement('swp-event');
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Create inner HTML structure
|
||
|
|
*/
|
||
|
|
private createInnerStructure(): void {
|
||
|
|
const startTime = this.formatTime(this.event.start);
|
||
|
|
const endTime = this.formatTime(this.event.end);
|
||
|
|
const durationMinutes = (this.event.end.getTime() - this.event.start.getTime()) / (1000 * 60);
|
||
|
|
|
||
|
|
this.element.innerHTML = `
|
||
|
|
<swp-event-time data-duration="${durationMinutes}">${startTime} - ${endTime}</swp-event-time>
|
||
|
|
<swp-event-title>${this.event.title}</swp-event-title>
|
||
|
|
`;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Apply positioning styles
|
||
|
|
*/
|
||
|
|
private applyPositioning(): void {
|
||
|
|
const position = this.calculateEventPosition();
|
||
|
|
this.element.style.position = 'absolute';
|
||
|
|
this.element.style.top = `${position.top + 1}px`;
|
||
|
|
this.element.style.height = `${position.height - 3}px`;
|
||
|
|
this.element.style.left = '2px';
|
||
|
|
this.element.style.right = '2px';
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Factory method to create a SwpEventElement from a CalendarEvent
|
||
|
|
*/
|
||
|
|
public static fromCalendarEvent(event: CalendarEvent): SwpEventElement {
|
||
|
|
return new SwpEventElement(event);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* All-day event element (swp-allday-event)
|
||
|
|
*/
|
||
|
|
export class SwpAllDayEventElement extends BaseEventElement {
|
||
|
|
private columnIndex: number;
|
||
|
|
|
||
|
|
private constructor(event: CalendarEvent, columnIndex: number) {
|
||
|
|
super(event);
|
||
|
|
this.columnIndex = columnIndex;
|
||
|
|
this.setAllDayAttributes();
|
||
|
|
this.createInnerStructure();
|
||
|
|
this.applyGridPositioning();
|
||
|
|
}
|
||
|
|
|
||
|
|
protected createElement(): HTMLElement {
|
||
|
|
return document.createElement('swp-allday-event');
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Set all-day specific attributes
|
||
|
|
*/
|
||
|
|
private setAllDayAttributes(): void {
|
||
|
|
this.element.dataset.allDay = "true";
|
||
|
|
// Override start/end times to be full day
|
||
|
|
const dateStr = this.event.start.toISOString().split('T')[0];
|
||
|
|
this.element.dataset.start = `${dateStr}T00:00:00`;
|
||
|
|
this.element.dataset.end = `${dateStr}T23:59:59`;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Create inner structure (just text content for all-day events)
|
||
|
|
*/
|
||
|
|
private createInnerStructure(): void {
|
||
|
|
this.element.textContent = this.event.title;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Apply CSS grid positioning
|
||
|
|
*/
|
||
|
|
private applyGridPositioning(): void {
|
||
|
|
this.element.style.gridColumn = this.columnIndex.toString();
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Factory method to create from CalendarEvent and target date
|
||
|
|
*/
|
||
|
|
public static fromCalendarEvent(event: CalendarEvent, targetDate: string): SwpAllDayEventElement {
|
||
|
|
// Calculate column index
|
||
|
|
const dayHeaders = document.querySelectorAll('swp-day-header');
|
||
|
|
let columnIndex = 1;
|
||
|
|
dayHeaders.forEach((header, index) => {
|
||
|
|
if ((header as HTMLElement).dataset.date === targetDate) {
|
||
|
|
columnIndex = index + 1;
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
return new SwpAllDayEventElement(event, columnIndex);
|
||
|
|
}
|
||
|
|
}
|