Renaming part 1

This commit is contained in:
Janus Knudsen 2025-08-07 00:15:44 +02:00
parent 36ac8d18ab
commit 29811fd4b5
20 changed files with 1424 additions and 582 deletions

View file

@ -1,9 +1,13 @@
// Grid structure management - Simple CSS Grid Implementation
// Grid structure management - Simple CSS Grid Implementation with Strategy Pattern
import { eventBus } from '../core/EventBus';
import { calendarConfig } from '../core/CalendarConfig';
import { EventTypes } from '../constants/EventTypes';
import { DateUtils } from '../utils/DateUtils';
import { ResourceCalendarData } from '../types/CalendarTypes';
import { CalendarTypeFactory } from '../factories/CalendarTypeFactory';
import { HeaderRenderContext } from '../renderers/HeaderRenderer';
import { ColumnRenderContext } from '../renderers/ColumnRenderer';
/**
* Grid position interface
@ -15,19 +19,23 @@ interface GridPosition {
}
/**
* Manages the calendar grid structure using simple CSS Grid
* Manages the calendar grid structure using simple CSS Grid with Strategy Pattern
*/
export class GridManager {
private container: HTMLElement | null = null;
private grid: HTMLElement | null = null;
private currentWeek: Date | null = null;
private allDayEvents: any[] = []; // Store all-day events for current week
private resourceData: ResourceCalendarData | null = null; // Store resource data for resource calendar
constructor() {
this.init();
}
private init(): void {
// Initialize the factory
CalendarTypeFactory.initialize();
this.findElements();
this.subscribeToEvents();
@ -58,6 +66,11 @@ export class GridManager {
}
});
// Re-render on calendar type change
eventBus.on(EventTypes.CALENDAR_TYPE_CHANGED, () => {
this.render();
});
// Re-render on view change
eventBus.on(EventTypes.VIEW_CHANGE, () => {
this.render();
@ -83,6 +96,21 @@ export class GridManager {
this.updateAllDayEvents(detail.events);
});
// Handle resource data loaded
eventBus.on(EventTypes.RESOURCE_DATA_LOADED, (e: Event) => {
const detail = (e as CustomEvent).detail;
this.resourceData = detail.resourceData;
console.log(`GridManager: Received resource data for ${this.resourceData!.resources.length} resources`);
// Update grid styles with new column count immediately
this.updateGridStyles();
// Re-render if grid is already rendered
if (this.grid && this.grid.children.length > 0) {
this.render();
}
});
// Handle grid clicks
this.setupGridInteractions();
}
@ -122,11 +150,8 @@ export class GridManager {
console.log('GridManager: First render - creating grid structure');
// Create POC structure: header-spacer + time-axis + week-container + right-column + bottom spacers
this.createHeaderSpacer();
this.createRightHeaderSpacer();
this.createTimeAxis();
this.createWeekContainer();
this.createRightColumn();
this.createBottomRow();
} else {
console.log('GridManager: Re-render - updating existing structure');
// Just update the week header for all-day events
@ -146,27 +171,6 @@ export class GridManager {
this.grid.appendChild(headerSpacer);
}
/**
* Create right header spacer for scrollbar alignment
*/
private createRightHeaderSpacer(): void {
if (!this.grid) return;
const rightHeaderSpacer = document.createElement('swp-right-header-spacer');
this.grid.appendChild(rightHeaderSpacer);
}
/**
* Create right column for scrollbar area
*/
private createRightColumn(): void {
if (!this.grid) return;
const rightColumn = document.createElement('swp-right-column');
this.grid.appendChild(rightColumn);
}
/**
* Create time axis (positioned beside week container) like in POC
*/
@ -192,15 +196,15 @@ export class GridManager {
}
/**
* Create week container with header and scrollable content like in POC
* Create week container with header and scrollable content using Strategy Pattern
*/
private createWeekContainer(): void {
if (!this.grid || !this.currentWeek) return;
const weekContainer = document.createElement('swp-week-container');
const weekContainer = document.createElement('swp-grid-container');
// Create week header
const weekHeader = document.createElement('swp-week-header');
// Create week header using Strategy Pattern
const weekHeader = document.createElement('swp-calendar-header');
this.renderWeekHeaders(weekHeader);
weekContainer.appendChild(weekHeader);
@ -212,7 +216,7 @@ export class GridManager {
const gridLines = document.createElement('swp-grid-lines');
timeGrid.appendChild(gridLines);
// Create day columns
// Create day columns using Strategy Pattern
const dayColumns = document.createElement('swp-day-columns');
this.renderDayColumns(dayColumns);
timeGrid.appendChild(dayColumns);
@ -224,74 +228,60 @@ export class GridManager {
}
/**
* Create bottom row with spacers
*/
private createBottomRow(): void {
if (!this.grid) return;
// Bottom spacer (left)
const bottomSpacer = document.createElement('swp-bottom-spacer');
this.grid.appendChild(bottomSpacer);
// Bottom middle spacer
const bottomMiddleSpacer = document.createElement('swp-bottom-middle-spacer');
this.grid.appendChild(bottomMiddleSpacer);
// Right bottom spacer
const rightBottomSpacer = document.createElement('swp-right-bottom-spacer');
this.grid.appendChild(rightBottomSpacer);
}
/**
* Render week headers like in POC
* Render week headers using Strategy Pattern
*/
private renderWeekHeaders(weekHeader: HTMLElement): void {
if (!this.currentWeek) return;
const dates = this.getWeekDates(this.currentWeek);
const weekDays = calendarConfig.get('weekDays');
const daysToShow = dates.slice(0, weekDays);
const calendarType = calendarConfig.getCalendarType();
const headerRenderer = CalendarTypeFactory.getHeaderRenderer(calendarType);
const context: HeaderRenderContext = {
currentWeek: this.currentWeek,
config: calendarConfig,
allDayEvents: this.allDayEvents,
resourceData: this.resourceData
};
daysToShow.forEach((date) => {
const header = document.createElement('swp-day-header');
if (this.isToday(date)) {
(header as any).dataset.today = 'true';
}
header.innerHTML = `
<swp-day-name>${this.getDayName(date)}</swp-day-name>
<swp-day-date>${date.getDate()}</swp-day-date>
`;
(header as any).dataset.date = this.formatDate(date);
weekHeader.appendChild(header);
});
// Render all-day events in row 2
this.renderAllDayEvents(weekHeader);
headerRenderer.render(weekHeader, context);
// Update spacer heights based on all-day events
this.updateSpacerHeights();
}
/**
* Render day columns using Strategy Pattern
*/
private renderDayColumns(dayColumns: HTMLElement): void {
if (!this.currentWeek) return;
console.log('GridManager: renderDayColumns called');
const calendarType = calendarConfig.getCalendarType();
const columnRenderer = CalendarTypeFactory.getColumnRenderer(calendarType);
const context: ColumnRenderContext = {
currentWeek: this.currentWeek,
config: calendarConfig,
resourceData: this.resourceData
};
columnRenderer.render(dayColumns, context);
}
/**
* Update only the week header (for all-day events) without rebuilding entire grid
*/
private updateWeekHeader(): void {
if (!this.grid || !this.currentWeek) return;
const weekHeader = this.grid.querySelector('swp-week-header');
const weekHeader = this.grid.querySelector('swp-calendar-header');
if (!weekHeader) return;
// Clear existing all-day events but keep day headers
const allDayEvents = weekHeader.querySelectorAll('swp-allday-event');
allDayEvents.forEach(event => event.remove());
// Clear existing content
weekHeader.innerHTML = '';
// Re-render all-day events
this.renderAllDayEvents(weekHeader as HTMLElement);
// Update spacer heights
this.updateSpacerHeights();
// Re-render headers using Strategy Pattern
this.renderWeekHeaders(weekHeader as HTMLElement);
}
/**
@ -320,64 +310,6 @@ export class GridManager {
}
}
/**
* Render all-day events in week header row 2
*/
private renderAllDayEvents(weekHeader: HTMLElement): void {
if (!this.currentWeek) return;
const dates = this.getWeekDates(this.currentWeek);
const weekDays = calendarConfig.get('weekDays');
const daysToShow = dates.slice(0, weekDays);
// Process each all-day event to calculate its span
this.allDayEvents.forEach(event => {
const startDate = new Date(event.start);
const endDate = new Date(event.end);
// Find start and end column indices
let startColumnIndex = -1;
let endColumnIndex = -1;
daysToShow.forEach((date, index) => {
const dateStr = this.formatDate(date);
const startDateStr = this.formatDate(startDate);
const endDateStr = this.formatDate(endDate);
if (dateStr === startDateStr) {
startColumnIndex = index;
}
// For end date, we need to check if the event spans to this day
// All-day events typically end at 23:59:59, so we check if this date is <= end date
if (date <= endDate) {
endColumnIndex = index;
}
});
// Only render if the event starts within the visible week
if (startColumnIndex >= 0) {
// If end column is not found or is before start, default to single day
if (endColumnIndex < startColumnIndex) {
endColumnIndex = startColumnIndex;
}
const allDayEvent = document.createElement('swp-allday-event');
allDayEvent.textContent = event.title;
// Set grid column span: start column (1-based) to end column + 1 (1-based)
const gridColumnStart = startColumnIndex + 1;
const gridColumnEnd = endColumnIndex + 2; // +2 because grid columns are 1-based and we want to include the end column
allDayEvent.style.gridColumn = `${gridColumnStart} / ${gridColumnEnd}`;
allDayEvent.style.backgroundColor = event.metadata?.color || '#ff9800';
console.log(`GridManager: All-day event "${event.title}" spans columns ${gridColumnStart} to ${gridColumnEnd-1} (${endColumnIndex - startColumnIndex + 1} days)`);
weekHeader.appendChild(allDayEvent);
}
});
}
/**
* Update spacer heights based on all-day events presence
*/
@ -393,33 +325,6 @@ export class GridManager {
console.log('GridManager: Updated --all-day-row-height to', `${allDayHeight}px`, 'for', allDayEventCount, 'events');
}
/**
* Render day columns like in POC
*/
private renderDayColumns(dayColumns: HTMLElement): void {
console.log('GridManager: renderDayColumns called');
if (!this.currentWeek) {
console.log('GridManager: No currentWeek, returning');
return;
}
const dates = this.getWeekDates(this.currentWeek);
const weekDays = calendarConfig.get('weekDays');
const daysToShow = dates.slice(0, weekDays);
console.log('GridManager: About to render', daysToShow.length, 'day columns');
daysToShow.forEach((date, dayIndex) => {
const column = document.createElement('swp-day-column');
(column as any).dataset.date = this.formatDate(date);
const eventsLayer = document.createElement('swp-events-layer');
column.appendChild(eventsLayer);
dayColumns.appendChild(column);
});
}
/**
* Update grid CSS variables
*/
@ -427,6 +332,7 @@ export class GridManager {
const root = document.documentElement;
const config = calendarConfig.getAll();
const calendar = document.querySelector('swp-calendar') as HTMLElement;
const calendarType = calendarConfig.getCalendarType();
// Set CSS variables
root.style.setProperty('--hour-height', `${config.hourHeight}px`);
@ -437,6 +343,15 @@ export class GridManager {
root.style.setProperty('--work-start-hour', config.workStartHour.toString());
root.style.setProperty('--work-end-hour', config.workEndHour.toString());
// Set number of columns based on calendar type
let columnCount = 7; // Default for date mode
if (calendarType === 'resource' && this.resourceData) {
columnCount = this.resourceData.resources.length;
} else if (calendarType === 'date') {
columnCount = config.weekDays;
}
root.style.setProperty('--grid-columns', columnCount.toString());
// Set day column min width based on fitToWidth setting
if (config.fitToWidth) {
root.style.setProperty('--day-column-min-width', '50px'); // Small min-width allows columns to fit available space
@ -448,6 +363,8 @@ export class GridManager {
if (calendar) {
calendar.setAttribute('data-fit-to-width', config.fitToWidth.toString());
}
console.log('GridManager: Updated grid styles with', columnCount, 'columns for', calendarType, 'calendar');
}
/**
@ -456,18 +373,20 @@ export class GridManager {
private setupGridInteractions(): void {
if (!this.grid) return;
// Click handler for day columns
// Click handler for day columns (works for both date and resource columns)
this.grid.addEventListener('click', (e: MouseEvent) => {
// Ignore if clicking on an event
if ((e.target as Element).closest('swp-event')) return;
const dayColumn = (e.target as Element).closest('swp-day-column') as HTMLElement;
const dayColumn = (e.target as Element).closest('swp-day-column, swp-resource-column') as HTMLElement;
if (!dayColumn) return;
const position = this.getClickPosition(e, dayColumn);
eventBus.emit(EventTypes.GRID_CLICK, {
date: (dayColumn as any).dataset.date,
resource: (dayColumn as any).dataset.resource,
employeeId: (dayColumn as any).dataset.employeeId,
time: position.time,
minutes: position.minutes
});
@ -478,13 +397,15 @@ export class GridManager {
// Ignore if clicking on an event
if ((e.target as Element).closest('swp-event')) return;
const dayColumn = (e.target as Element).closest('swp-day-column') as HTMLElement;
const dayColumn = (e.target as Element).closest('swp-day-column, swp-resource-column') as HTMLElement;
if (!dayColumn) return;
const position = this.getClickPosition(e, dayColumn);
eventBus.emit(EventTypes.GRID_DBLCLICK, {
date: (dayColumn as any).dataset.date,
resource: (dayColumn as any).dataset.resource,
employeeId: (dayColumn as any).dataset.employeeId,
time: position.time,
minutes: position.minutes
});
@ -537,36 +458,6 @@ export class GridManager {
* Utility methods
*/
private formatHour(hour: number): string {
const period = hour >= 12 ? 'PM' : 'AM';
const displayHour = hour > 12 ? hour - 12 : (hour === 0 ? 12 : hour);
return `${displayHour} ${period}`;
}
private formatDate(date: Date): string {
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`;
}
private getDayName(date: Date): string {
const days = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
return days[date.getDay()];
}
private getWeekDates(weekStart: Date): Date[] {
const dates: Date[] = [];
for (let i = 0; i < 7; i++) {
const date = new Date(weekStart);
date.setDate(weekStart.getDate() + i);
dates.push(date);
}
return dates;
}
private isToday(date: Date): boolean {
const today = new Date();
return date.toDateString() === today.toDateString();
}
private minutesToTime(totalMinutes: number): string {
const hours = Math.floor(totalMinutes / 60);
const minutes = totalMinutes % 60;