Refactor calendar datasource and event management

Enhances calendar flexibility by introducing group-based column spanning and improving cross-mode event handling

Adds support for:
- Dynamic column grouping in date and resource modes
- Consistent event drag-and-drop across different calendar views
- More robust all-day event layout calculations

Improves event management logic to handle resource and date mode transitions more elegantly
This commit is contained in:
Janus C. H. Knudsen 2025-11-25 19:04:06 +01:00
parent 17909696ed
commit d8b9f6dabd
16 changed files with 192 additions and 79 deletions

View file

@ -18,6 +18,7 @@ export interface IColumnRenderer {
export interface IColumnRenderContext {
columns: IColumnInfo[];
config: Configuration;
currentDate?: Date; // Optional: Only used by ResourceColumnRenderer in resource mode
}
/**
@ -43,6 +44,7 @@ export class DateColumnRenderer implements IColumnRenderer {
const column = document.createElement('swp-day-column');
column.dataset.columnId = columnInfo.identifier;
column.dataset.date = this.dateService.formatISODate(date);
// Apply work hours styling
this.applyWorkHoursToColumn(column, date);

View file

@ -53,6 +53,7 @@ export class DateHeaderRenderer implements IHeaderRenderer {
`;
header.dataset.columnId = columnInfo.identifier;
header.dataset.groupId = columnInfo.groupId;
calendarHeader.appendChild(header);
});

View file

@ -165,7 +165,7 @@ export class GridRenderer {
fragment.appendChild(timeAxis);
// Create grid container with caching
const gridContainer = this.createOptimizedGridContainer(columns);
const gridContainer = this.createOptimizedGridContainer(columns, currentDate);
this.cachedGridContainer = gridContainer;
fragment.appendChild(gridContainer);
@ -211,10 +211,12 @@ export class GridRenderer {
* - Column container - created here, populated by ColumnRenderer
*
* @param columns - Array of columns to render (each column contains its events)
* @param currentDate - Current view date
* @returns Complete grid container element
*/
private createOptimizedGridContainer(
columns: IColumnInfo[]
columns: IColumnInfo[],
currentDate: Date
): HTMLElement {
const gridContainer = document.createElement('swp-grid-container');
@ -232,7 +234,7 @@ export class GridRenderer {
// Create column container
const columnContainer = document.createElement('swp-day-columns');
this.renderColumnContainer(columnContainer, columns);
this.renderColumnContainer(columnContainer, columns, currentDate);
timeGrid.appendChild(columnContainer);
scrollableContent.appendChild(timeGrid);
@ -250,15 +252,18 @@ export class GridRenderer {
*
* @param columnContainer - Empty container to populate
* @param columns - Array of columns to render (each column contains its events)
* @param currentDate - Current view date
*/
private renderColumnContainer(
columnContainer: HTMLElement,
columns: IColumnInfo[]
columns: IColumnInfo[],
currentDate: Date
): void {
// Delegate to ColumnRenderer
this.columnRenderer.render(columnContainer, {
columns: columns,
config: this.config
config: this.config,
currentDate: currentDate
});
}
@ -283,7 +288,7 @@ export class GridRenderer {
const columnContainer = grid.querySelector('swp-day-columns');
if (columnContainer) {
columnContainer.innerHTML = '';
this.renderColumnContainer(columnContainer as HTMLElement, columns);
this.renderColumnContainer(columnContainer as HTMLElement, columns, currentDate);
}
}
/**
@ -296,12 +301,13 @@ export class GridRenderer {
* Events will be rendered by EventRenderingService when GRID_RENDERED emits.
*
* @param parentContainer - Container for the new grid
* @param dates - Array of dates to render
* @param columns - Array of columns to render
* @param currentDate - Current view date
* @returns New grid element ready for animation
*/
public createNavigationGrid(parentContainer: HTMLElement, columns: IColumnInfo[]): HTMLElement {
public createNavigationGrid(parentContainer: HTMLElement, columns: IColumnInfo[], currentDate: Date): HTMLElement {
// Create grid structure (events are in columns, rendered by EventRenderingService)
const newGrid = this.createOptimizedGridContainer(columns);
const newGrid = this.createOptimizedGridContainer(columns, currentDate);
// Position new grid for animation - NO transform here, let Animation API handle it
newGrid.style.position = 'absolute';

View file

@ -1,5 +1,6 @@
import { WorkHoursManager } from '../managers/WorkHoursManager';
import { IColumnRenderer, IColumnRenderContext } from './ColumnRenderer';
import { DateService } from '../utils/DateService';
/**
* Resource-based column renderer
@ -10,13 +11,19 @@ import { IColumnRenderer, IColumnRenderContext } from './ColumnRenderer';
*/
export class ResourceColumnRenderer implements IColumnRenderer {
private workHoursManager: WorkHoursManager;
private dateService: DateService;
constructor(workHoursManager: WorkHoursManager) {
constructor(workHoursManager: WorkHoursManager, dateService: DateService) {
this.workHoursManager = workHoursManager;
this.dateService = dateService;
}
render(columnContainer: HTMLElement, context: IColumnRenderContext): void {
const { columns } = context;
const { columns, currentDate } = context;
if (!currentDate) {
throw new Error('ResourceColumnRenderer requires currentDate in context');
}
// Hardcoded work hours for all resources: 09:00 - 18:00
const workHours = { start: 9, end: 18 };
@ -25,6 +32,7 @@ export class ResourceColumnRenderer implements IColumnRenderer {
const column = document.createElement('swp-day-column');
column.dataset.columnId = columnInfo.identifier;
column.dataset.date = this.dateService.formatISODate(currentDate);
// Apply hardcoded work hours to all resource columns
this.applyWorkHoursToColumn(column, workHours);

View file

@ -39,6 +39,7 @@ export class ResourceHeaderRenderer implements IHeaderRenderer {
header.dataset.columnId = columnInfo.identifier;
header.dataset.resourceId = resource.id;
header.dataset.groupId = columnInfo.groupId;
calendarHeader.appendChild(header);
});