From 400de8c9d52b2184d4548a9445170e942d258198 Mon Sep 17 00:00:00 2001 From: "Janus C. H. Knudsen" Date: Tue, 9 Dec 2025 23:16:13 +0100 Subject: [PATCH] Refactors renderer interfaces and implementations Converts renderer interfaces to use 'I' prefix for better type clarity Adds async support for rendering pipeline Updates resource rendering to use ResourceService Removes hardcoded resource data Improves type safety and flexibility of rendering system --- src/v2/V2CompositionRoot.ts | 8 +++--- src/v2/core/CalendarOrchestrator.ts | 12 ++++---- src/v2/core/IGroupingRenderer.ts | 6 ++-- src/v2/core/RenderBuilder.ts | 10 +++---- src/v2/demo/DemoApp.ts | 4 +-- src/v2/features/date/DateRenderer.ts | 6 ++-- src/v2/features/resource/ResourceRenderer.ts | 29 ++++++-------------- src/v2/features/team/TeamRenderer.ts | 6 ++-- src/v2/index.ts | 2 +- src/v2/storage/resources/ResourceService.ts | 10 +++++++ 10 files changed, 46 insertions(+), 47 deletions(-) diff --git a/src/v2/V2CompositionRoot.ts b/src/v2/V2CompositionRoot.ts index a71d912..a0112b4 100644 --- a/src/v2/V2CompositionRoot.ts +++ b/src/v2/V2CompositionRoot.ts @@ -1,5 +1,5 @@ import { Container } from '@novadi/core'; -import { Renderer } from './core/IGroupingRenderer'; +import { IRenderer } from './core/IGroupingRenderer'; import { IGroupingStore } from './core/IGroupingStore'; import { DateRenderer } from './features/date/DateRenderer'; import { DateService } from './core/DateService'; @@ -111,9 +111,9 @@ export function createV2Container(): Container { builder.registerType(EventRenderer).as(); // Renderers - registreres som Renderer (array injection til CalendarOrchestrator) - builder.registerType(DateRenderer).as(); - builder.registerType(ResourceRenderer).as(); - builder.registerType(TeamRenderer).as(); + builder.registerType(DateRenderer).as(); + builder.registerType(ResourceRenderer).as(); + builder.registerType(TeamRenderer).as(); // Stores - registreres som IGroupingStore builder.registerType(MockTeamStore).as(); diff --git a/src/v2/core/CalendarOrchestrator.ts b/src/v2/core/CalendarOrchestrator.ts index 09f9c87..b78b4c3 100644 --- a/src/v2/core/CalendarOrchestrator.ts +++ b/src/v2/core/CalendarOrchestrator.ts @@ -1,11 +1,11 @@ -import { Renderer, RenderContext } from './IGroupingRenderer'; +import { IRenderer, IRenderContext } from './IGroupingRenderer'; import { buildPipeline } from './RenderBuilder'; import { EventRenderer } from '../features/event/EventRenderer'; import { ViewConfig } from './ViewConfig'; export class CalendarOrchestrator { constructor( - private allRenderers: Renderer[], + private allRenderers: IRenderer[], private eventRenderer: EventRenderer ) {} @@ -22,7 +22,7 @@ export class CalendarOrchestrator { filter[grouping.type] = grouping.values; } - const context: RenderContext = { headerContainer, columnContainer, filter }; + const context: IRenderContext = { headerContainer, columnContainer, filter }; // Clear headerContainer.innerHTML = ''; @@ -41,18 +41,18 @@ export class CalendarOrchestrator { // Byg og kør pipeline const pipeline = buildPipeline(activeRenderers); - pipeline.run(context); + await pipeline.run(context); // Render events med hele filter (date + resource) await this.eventRenderer.render(container, filter); } - private selectRenderers(viewConfig: ViewConfig): Renderer[] { + private selectRenderers(viewConfig: ViewConfig): IRenderer[] { const types = viewConfig.groupings.map(g => g.type); // Sortér renderers i samme rækkefølge som viewConfig.groupings return types .map(type => this.allRenderers.find(r => r.type === type)) - .filter((r): r is Renderer => r !== undefined); + .filter((r): r is IRenderer => r !== undefined); } private calculateTotalColumns(viewConfig: ViewConfig): number { diff --git a/src/v2/core/IGroupingRenderer.ts b/src/v2/core/IGroupingRenderer.ts index c6e5453..5c44c1b 100644 --- a/src/v2/core/IGroupingRenderer.ts +++ b/src/v2/core/IGroupingRenderer.ts @@ -1,10 +1,10 @@ -export interface RenderContext { +export interface IRenderContext { headerContainer: HTMLElement; columnContainer: HTMLElement; filter: Record; // { team: ['alpha'], resource: ['alice', 'bob'], date: [...] } } -export interface Renderer { +export interface IRenderer { readonly type: string; - render(context: RenderContext): void; + render(context: IRenderContext): void | Promise; } diff --git a/src/v2/core/RenderBuilder.ts b/src/v2/core/RenderBuilder.ts index b745883..68f0ee3 100644 --- a/src/v2/core/RenderBuilder.ts +++ b/src/v2/core/RenderBuilder.ts @@ -1,14 +1,14 @@ -import { Renderer, RenderContext } from './IGroupingRenderer'; +import { IRenderer, IRenderContext } from './IGroupingRenderer'; export interface Pipeline { - run(context: RenderContext): void; + run(context: IRenderContext): Promise; } -export function buildPipeline(renderers: Renderer[]): Pipeline { +export function buildPipeline(renderers: IRenderer[]): Pipeline { return { - run(context: RenderContext) { + async run(context: IRenderContext) { for (const renderer of renderers) { - renderer.render(context); + await renderer.render(context); } } }; diff --git a/src/v2/demo/DemoApp.ts b/src/v2/demo/DemoApp.ts index da5ec78..d8e535a 100644 --- a/src/v2/demo/DemoApp.ts +++ b/src/v2/demo/DemoApp.ts @@ -73,7 +73,7 @@ export class DemoApp { return { templateId: 'day', groupings: [ - { type: 'resource', values: ['res1', 'res2'] }, + { type: 'resource', values: ['EMP001', 'EMP002'] }, { type: 'date', values: today } ] }; @@ -90,7 +90,7 @@ export class DemoApp { return { templateId: 'resource', groupings: [ - { type: 'resource', values: ['res1', 'res2'] }, + { type: 'resource', values: ['EMP001', 'EMP002'] }, { type: 'date', values: dates } ] }; diff --git a/src/v2/features/date/DateRenderer.ts b/src/v2/features/date/DateRenderer.ts index 87a4622..9295c4d 100644 --- a/src/v2/features/date/DateRenderer.ts +++ b/src/v2/features/date/DateRenderer.ts @@ -1,12 +1,12 @@ -import { Renderer, RenderContext } from '../../core/IGroupingRenderer'; +import { IRenderer, IRenderContext } from '../../core/IGroupingRenderer'; import { DateService } from '../../core/DateService'; -export class DateRenderer implements Renderer { +export class DateRenderer implements IRenderer { readonly type = 'date'; constructor(private dateService: DateService) {} - render(context: RenderContext): void { + render(context: IRenderContext): void { const dates = context.filter['date'] || []; const resourceIds = context.filter['resource'] || []; diff --git a/src/v2/features/resource/ResourceRenderer.ts b/src/v2/features/resource/ResourceRenderer.ts index 79e7ab0..4262d4c 100644 --- a/src/v2/features/resource/ResourceRenderer.ts +++ b/src/v2/features/resource/ResourceRenderer.ts @@ -1,31 +1,20 @@ -import { Renderer, RenderContext } from '../../core/IGroupingRenderer'; +import { IRenderer, IRenderContext } from '../../core/IGroupingRenderer'; +import { ResourceService } from '../../storage/resources/ResourceService'; -interface Resource { - id: string; - name: string; -} - -export class ResourceRenderer implements Renderer { +export class ResourceRenderer implements IRenderer { readonly type = 'resource'; - // Hardcoded data - private resources: Resource[] = [ - { id: 'res1', name: 'Anders' }, - { id: 'res2', name: 'Bente' }, - { id: 'res3', name: 'Carsten' } - ]; - - render(context: RenderContext): void { - const allowedIds = context.filter['resource'] || []; - const filteredResources = this.resources.filter(r => allowedIds.includes(r.id)); + constructor(private resourceService: ResourceService) {} + async render(context: IRenderContext): Promise { + const resourceIds = context.filter['resource'] || []; + const resources = await this.resourceService.getByIds(resourceIds); const dateCount = context.filter['date']?.length || 1; - // Render ALLE resource headers - for (const resource of filteredResources) { + for (const resource of resources) { const header = document.createElement('swp-resource-header'); header.dataset.resourceId = resource.id; - header.textContent = resource.name; + header.textContent = resource.displayName; header.style.gridColumn = `span ${dateCount}`; context.headerContainer.appendChild(header); } diff --git a/src/v2/features/team/TeamRenderer.ts b/src/v2/features/team/TeamRenderer.ts index 953f3b5..0b2a5d1 100644 --- a/src/v2/features/team/TeamRenderer.ts +++ b/src/v2/features/team/TeamRenderer.ts @@ -1,4 +1,4 @@ -import { Renderer, RenderContext } from '../../core/IGroupingRenderer'; +import { IRenderer, IRenderContext } from '../../core/IGroupingRenderer'; interface Team { id: string; @@ -6,7 +6,7 @@ interface Team { resourceIds: string[]; } -export class TeamRenderer implements Renderer { +export class TeamRenderer implements IRenderer { readonly type = 'team'; // Hardcoded data @@ -15,7 +15,7 @@ export class TeamRenderer implements Renderer { { id: 'team2', name: 'Team Beta', resourceIds: ['res3'] } ]; - render(context: RenderContext): void { + render(context: IRenderContext): void { const allowedIds = context.filter['team'] || []; const filteredTeams = this.teams.filter(t => allowedIds.includes(t.id)); diff --git a/src/v2/index.ts b/src/v2/index.ts index 5976bd8..6e390a0 100644 --- a/src/v2/index.ts +++ b/src/v2/index.ts @@ -1,6 +1,6 @@ // Core exports export { ViewTemplate, ViewConfig, GroupingConfig } from './core/ViewConfig'; -export { Renderer, RenderContext } from './core/IGroupingRenderer'; +export { IRenderer as Renderer, IRenderContext as RenderContext } from './core/IGroupingRenderer'; export { IGroupingStore } from './core/IGroupingStore'; export { CalendarOrchestrator } from './core/CalendarOrchestrator'; export { NavigationAnimator } from './core/NavigationAnimator'; diff --git a/src/v2/storage/resources/ResourceService.ts b/src/v2/storage/resources/ResourceService.ts index 7bd7f2e..769210c 100644 --- a/src/v2/storage/resources/ResourceService.ts +++ b/src/v2/storage/resources/ResourceService.ts @@ -22,6 +22,16 @@ export class ResourceService extends BaseEntityService { return all.filter(r => r.isActive !== false); } + /** + * Get resources by IDs + */ + async getByIds(ids: string[]): Promise { + if (ids.length === 0) return []; + + const results = await Promise.all(ids.map(id => this.get(id))); + return results.filter((r): r is IResource => r !== null); + } + /** * Get resources by type */