Adds hierarchical grouping and entity resolution support

Enhances calendar rendering with dynamic parent-child relationships between entities

Introduces EntityResolver for dot-notation references
Supports belongsTo configuration in grouping
Implements flexible filtering across hierarchical entities

Improves rendering flexibility for complex organizational structures
This commit is contained in:
Janus C. H. Knudsen 2025-12-15 17:10:43 +01:00
parent dd647acab8
commit d4249eecfb
17 changed files with 403 additions and 44 deletions

View file

@ -12,6 +12,7 @@ export class DateRenderer implements IRenderer {
// Render dates for HVER resource (eller 1 gang hvis ingen resources)
const iterations = resourceIds.length || 1;
let columnCount = 0;
for (let r = 0; r < iterations; r++) {
const resourceId = resourceIds[r]; // undefined hvis ingen resources
@ -46,7 +47,15 @@ export class DateRenderer implements IRenderer {
}
column.innerHTML = '<swp-events-layer></swp-events-layer>';
context.columnContainer.appendChild(column);
columnCount++;
}
}
// Set grid columns on container
const container = context.columnContainer.closest('swp-calendar-container');
if (container) {
(container as HTMLElement).style.setProperty('--grid-columns', String(columnCount));
}
}
}

View file

@ -8,10 +8,36 @@ export class ResourceRenderer implements IRenderer {
async render(context: IRenderContext): Promise<void> {
const resourceIds = context.filter['resource'] || [];
const resources = await this.resourceService.getByIds(resourceIds);
const dateCount = context.filter['date']?.length || 1;
for (const resource of resources) {
// Determine render order based on parentChildMap
// If parentChildMap exists, render resources grouped by parent (e.g., team)
// Otherwise, render in filter order
let orderedResourceIds: string[];
if (context.parentChildMap) {
// Render resources in parent-child order
orderedResourceIds = [];
for (const childIds of Object.values(context.parentChildMap)) {
for (const childId of childIds) {
if (resourceIds.includes(childId)) {
orderedResourceIds.push(childId);
}
}
}
} else {
orderedResourceIds = resourceIds;
}
const resources = await this.resourceService.getByIds(orderedResourceIds);
// Create a map for quick lookup to preserve order
const resourceMap = new Map(resources.map(r => [r.id, r]));
for (const resourceId of orderedResourceIds) {
const resource = resourceMap.get(resourceId);
if (!resource) continue;
const header = document.createElement('swp-resource-header');
header.dataset.resourceId = resource.id;
header.textContent = resource.displayName;

View file

@ -1,32 +1,31 @@
import { IRenderer, IRenderContext } from '../../core/IGroupingRenderer';
interface Team {
id: string;
name: string;
resourceIds: string[];
}
import { TeamService } from '../../storage/teams/TeamService';
export class TeamRenderer implements IRenderer {
readonly type = 'team';
// Hardcoded data
private teams: Team[] = [
{ id: 'team1', name: 'Team Alpha', resourceIds: ['res1', 'res2'] },
{ id: 'team2', name: 'Team Beta', resourceIds: ['res3'] }
];
constructor(private teamService: TeamService) {}
render(context: IRenderContext): void {
const allowedIds = context.filter['team'] || [];
const filteredTeams = this.teams.filter(t => allowedIds.includes(t.id));
async render(context: IRenderContext): Promise<void> {
const allowedIds = context.filter[this.type] || [];
if (allowedIds.length === 0) return;
// Fetch teams from IndexedDB (only for name display)
const teams = await this.teamService.getByIds(allowedIds);
const dateCount = context.filter['date']?.length || 1;
const resourceIds = context.filter['resource'] || [];
// Render ALLE team headers først
for (const team of filteredTeams) {
// Tæl resources der tilhører dette team OG er i filter
const teamResourceCount = team.resourceIds.filter(id => resourceIds.includes(id)).length;
const colspan = teamResourceCount * dateCount;
// Get child filter values using childType from context (not hardcoded)
const childIds = context.childType ? context.filter[context.childType] || [] : [];
// Render team headers
for (const team of teams) {
// Get children from parentChildMap (resolved from belongsTo config)
const teamChildIds = context.parentChildMap?.[team.id] || [];
// Count children that belong to this team AND are in the filter
const childCount = teamChildIds.filter(id => childIds.includes(id)).length;
const colspan = childCount * dateCount;
const header = document.createElement('swp-team-header');
header.dataset.teamId = team.id;