Simplifies all-day event rendering by streamlining the layout calculation and event placement process, using the AllDayLayoutEngine to determine the grid positions. This removes deprecated methods and improves overall code clarity.
142 lines
No EOL
4.6 KiB
TypeScript
142 lines
No EOL
4.6 KiB
TypeScript
import { CalendarEvent } from '../types/CalendarTypes';
|
|
|
|
export interface EventLayout {
|
|
calenderEvent: CalendarEvent;
|
|
gridArea: string; // "row-start / col-start / row-end / col-end"
|
|
startColumn: number;
|
|
endColumn: number;
|
|
row: number;
|
|
columnSpan: number;
|
|
}
|
|
|
|
export class AllDayLayoutEngine {
|
|
private weekDates: string[];
|
|
private tracks: boolean[][];
|
|
|
|
constructor(weekDates: string[]) {
|
|
this.weekDates = weekDates;
|
|
this.tracks = [];
|
|
}
|
|
|
|
/**
|
|
* Calculate layout for all events using clean day-based logic
|
|
*/
|
|
public calculateLayout(events: CalendarEvent[]): EventLayout[] {
|
|
|
|
let layouts: EventLayout[] = [];
|
|
// Reset tracks for new calculation
|
|
this.tracks = [new Array(this.weekDates.length).fill(false)];
|
|
|
|
// Filter to only visible events
|
|
const visibleEvents = events.filter(event => this.isEventVisible(event));
|
|
|
|
// Process events in input order (no sorting)
|
|
for (const event of visibleEvents) {
|
|
const startDay = this.getEventStartDay(event);
|
|
const endDay = this.getEventEndDay(event);
|
|
|
|
if (startDay > 0 && endDay > 0) {
|
|
const track = this.findAvailableTrack(startDay - 1, endDay - 1); // Convert to 0-based for tracks
|
|
|
|
// Mark days as occupied
|
|
for (let day = startDay - 1; day <= endDay - 1; day++) {
|
|
this.tracks[track][day] = true;
|
|
}
|
|
|
|
const layout: EventLayout = {
|
|
calenderEvent: event,
|
|
gridArea: `${track + 1} / ${startDay} / ${track + 2} / ${endDay + 1}`,
|
|
startColumn: startDay,
|
|
endColumn: endDay,
|
|
row: track + 1,
|
|
columnSpan: endDay - startDay + 1
|
|
};
|
|
layouts.push(layout);
|
|
|
|
}
|
|
}
|
|
|
|
return layouts;
|
|
}
|
|
|
|
/**
|
|
* Find available track for event spanning from startDay to endDay (0-based indices)
|
|
*/
|
|
private findAvailableTrack(startDay: number, endDay: number): number {
|
|
for (let trackIndex = 0; trackIndex < this.tracks.length; trackIndex++) {
|
|
if (this.isTrackAvailable(trackIndex, startDay, endDay)) {
|
|
return trackIndex;
|
|
}
|
|
}
|
|
|
|
// Create new track if none available
|
|
this.tracks.push(new Array(this.weekDates.length).fill(false));
|
|
return this.tracks.length - 1;
|
|
}
|
|
|
|
/**
|
|
* Check if track is available for the given day range (0-based indices)
|
|
*/
|
|
private isTrackAvailable(trackIndex: number, startDay: number, endDay: number): boolean {
|
|
for (let day = startDay; day <= endDay; day++) {
|
|
if (this.tracks[trackIndex][day]) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Get start day index for event (1-based, 0 if not visible)
|
|
*/
|
|
private getEventStartDay(event: CalendarEvent): number {
|
|
const eventStartDate = this.formatDate(event.start);
|
|
const firstVisibleDate = this.weekDates[0];
|
|
|
|
// If event starts before visible range, clip to first visible day
|
|
const clippedStartDate = eventStartDate < firstVisibleDate ? firstVisibleDate : eventStartDate;
|
|
|
|
const dayIndex = this.weekDates.indexOf(clippedStartDate);
|
|
return dayIndex >= 0 ? dayIndex + 1 : 0;
|
|
}
|
|
|
|
/**
|
|
* Get end day index for event (1-based, 0 if not visible)
|
|
*/
|
|
private getEventEndDay(event: CalendarEvent): number {
|
|
const eventEndDate = this.formatDate(event.end);
|
|
const lastVisibleDate = this.weekDates[this.weekDates.length - 1];
|
|
|
|
// If event ends after visible range, clip to last visible day
|
|
const clippedEndDate = eventEndDate > lastVisibleDate ? lastVisibleDate : eventEndDate;
|
|
|
|
const dayIndex = this.weekDates.indexOf(clippedEndDate);
|
|
return dayIndex >= 0 ? dayIndex + 1 : 0;
|
|
}
|
|
|
|
/**
|
|
* Check if event is visible in the current date range
|
|
*/
|
|
private isEventVisible(event: CalendarEvent): boolean {
|
|
if (this.weekDates.length === 0) return false;
|
|
|
|
const eventStartDate = this.formatDate(event.start);
|
|
const eventEndDate = this.formatDate(event.end);
|
|
const firstVisibleDate = this.weekDates[0];
|
|
const lastVisibleDate = this.weekDates[this.weekDates.length - 1];
|
|
|
|
// Event overlaps if it doesn't end before visible range starts
|
|
// AND doesn't start after visible range ends
|
|
return !(eventEndDate < firstVisibleDate || eventStartDate > lastVisibleDate);
|
|
}
|
|
|
|
/**
|
|
* Format date to YYYY-MM-DD string using local date
|
|
*/
|
|
private formatDate(date: Date): string {
|
|
const year = date.getFullYear();
|
|
const month = String(date.getMonth() + 1).padStart(2, '0');
|
|
const day = String(date.getDate()).padStart(2, '0');
|
|
return `${year}-${month}-${day}`;
|
|
}
|
|
} |