Adds resource-based calendar view mode

Introduces new ResourceColumnDataSource and ResourceHeaderRenderer to support column rendering by resources instead of dates

Enables dynamic calendar mode switching between date and resource views
Updates core managers and services to support async column retrieval
Refactors data source interfaces to use Promise-based methods

Improves calendar flexibility and resource management capabilities
This commit is contained in:
Janus C. H. Knudsen 2025-11-22 19:42:12 +01:00
parent a7d365b186
commit eeaeddeef8
19 changed files with 765 additions and 991 deletions

View file

@ -0,0 +1,46 @@
import { WorkHoursManager } from '../managers/WorkHoursManager';
import { IColumnRenderer, IColumnRenderContext } from './ColumnRenderer';
/**
* Resource-based column renderer
*
* In resource mode, columns represent resources (people, rooms, etc.)
* Work hours are hardcoded (09:00-18:00) for all columns.
* TODO: Each resource should have its own work hours.
*/
export class ResourceColumnRenderer implements IColumnRenderer {
private workHoursManager: WorkHoursManager;
constructor(workHoursManager: WorkHoursManager) {
this.workHoursManager = workHoursManager;
}
render(columnContainer: HTMLElement, context: IColumnRenderContext): void {
const { columns } = context;
// Hardcoded work hours for all resources: 09:00 - 18:00
const workHours = { start: 9, end: 18 };
columns.forEach((columnInfo) => {
const column = document.createElement('swp-day-column');
column.dataset.columnId = columnInfo.identifier;
// Apply hardcoded work hours to all resource columns
this.applyWorkHoursToColumn(column, workHours);
const eventsLayer = document.createElement('swp-events-layer');
column.appendChild(eventsLayer);
columnContainer.appendChild(column);
});
}
private applyWorkHoursToColumn(column: HTMLElement, workHours: { start: number; end: number }): void {
const nonWorkStyle = this.workHoursManager.calculateNonWorkHoursStyle(workHours);
if (nonWorkStyle) {
column.style.setProperty('--before-work-height', `${nonWorkStyle.beforeWorkHeight}px`);
column.style.setProperty('--after-work-top', `${nonWorkStyle.afterWorkTop}px`);
}
}
}

View file

@ -0,0 +1,58 @@
import { IHeaderRenderer, IHeaderRenderContext } from './DateHeaderRenderer';
import { IResource } from '../types/ResourceTypes';
/**
* ResourceHeaderRenderer - Renders resource-based headers
*
* Displays resource information (avatar, name) instead of dates.
* Used in resource mode where columns represent people/rooms/equipment.
*/
export class ResourceHeaderRenderer implements IHeaderRenderer {
render(calendarHeader: HTMLElement, context: IHeaderRenderContext): void {
const { columns } = context;
// Create all-day container (same structure as date mode)
const allDayContainer = document.createElement('swp-allday-container');
calendarHeader.appendChild(allDayContainer);
columns.forEach((columnInfo) => {
const resource = columnInfo.data as IResource;
const header = document.createElement('swp-day-header');
// Build header content
let avatarHtml = '';
if (resource.avatarUrl) {
avatarHtml = `<img class="swp-resource-avatar" src="${resource.avatarUrl}" alt="${resource.displayName}" />`;
} else {
// Fallback: initials
const initials = this.getInitials(resource.displayName);
const bgColor = resource.color || '#6366f1';
avatarHtml = `<span class="swp-resource-initials" style="background-color: ${bgColor}">${initials}</span>`;
}
header.innerHTML = `
<div class="swp-resource-header">
${avatarHtml}
<span class="swp-resource-name">${resource.displayName}</span>
</div>
`;
header.dataset.columnId = columnInfo.identifier;
header.dataset.resourceId = resource.id;
calendarHeader.appendChild(header);
});
}
/**
* Get initials from display name
*/
private getInitials(name: string): string {
return name
.split(' ')
.map(part => part.charAt(0))
.join('')
.toUpperCase()
.substring(0, 2);
}
}