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
This commit is contained in:
parent
7f6279a6f3
commit
400de8c9d5
10 changed files with 46 additions and 47 deletions
|
|
@ -1,5 +1,5 @@
|
||||||
import { Container } from '@novadi/core';
|
import { Container } from '@novadi/core';
|
||||||
import { Renderer } from './core/IGroupingRenderer';
|
import { IRenderer } from './core/IGroupingRenderer';
|
||||||
import { IGroupingStore } from './core/IGroupingStore';
|
import { IGroupingStore } from './core/IGroupingStore';
|
||||||
import { DateRenderer } from './features/date/DateRenderer';
|
import { DateRenderer } from './features/date/DateRenderer';
|
||||||
import { DateService } from './core/DateService';
|
import { DateService } from './core/DateService';
|
||||||
|
|
@ -111,9 +111,9 @@ export function createV2Container(): Container {
|
||||||
builder.registerType(EventRenderer).as<EventRenderer>();
|
builder.registerType(EventRenderer).as<EventRenderer>();
|
||||||
|
|
||||||
// Renderers - registreres som Renderer (array injection til CalendarOrchestrator)
|
// Renderers - registreres som Renderer (array injection til CalendarOrchestrator)
|
||||||
builder.registerType(DateRenderer).as<Renderer>();
|
builder.registerType(DateRenderer).as<IRenderer>();
|
||||||
builder.registerType(ResourceRenderer).as<Renderer>();
|
builder.registerType(ResourceRenderer).as<IRenderer>();
|
||||||
builder.registerType(TeamRenderer).as<Renderer>();
|
builder.registerType(TeamRenderer).as<IRenderer>();
|
||||||
|
|
||||||
// Stores - registreres som IGroupingStore
|
// Stores - registreres som IGroupingStore
|
||||||
builder.registerType(MockTeamStore).as<IGroupingStore>();
|
builder.registerType(MockTeamStore).as<IGroupingStore>();
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
import { Renderer, RenderContext } from './IGroupingRenderer';
|
import { IRenderer, IRenderContext } from './IGroupingRenderer';
|
||||||
import { buildPipeline } from './RenderBuilder';
|
import { buildPipeline } from './RenderBuilder';
|
||||||
import { EventRenderer } from '../features/event/EventRenderer';
|
import { EventRenderer } from '../features/event/EventRenderer';
|
||||||
import { ViewConfig } from './ViewConfig';
|
import { ViewConfig } from './ViewConfig';
|
||||||
|
|
||||||
export class CalendarOrchestrator {
|
export class CalendarOrchestrator {
|
||||||
constructor(
|
constructor(
|
||||||
private allRenderers: Renderer[],
|
private allRenderers: IRenderer[],
|
||||||
private eventRenderer: EventRenderer
|
private eventRenderer: EventRenderer
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
|
@ -22,7 +22,7 @@ export class CalendarOrchestrator {
|
||||||
filter[grouping.type] = grouping.values;
|
filter[grouping.type] = grouping.values;
|
||||||
}
|
}
|
||||||
|
|
||||||
const context: RenderContext = { headerContainer, columnContainer, filter };
|
const context: IRenderContext = { headerContainer, columnContainer, filter };
|
||||||
|
|
||||||
// Clear
|
// Clear
|
||||||
headerContainer.innerHTML = '';
|
headerContainer.innerHTML = '';
|
||||||
|
|
@ -41,18 +41,18 @@ export class CalendarOrchestrator {
|
||||||
|
|
||||||
// Byg og kør pipeline
|
// Byg og kør pipeline
|
||||||
const pipeline = buildPipeline(activeRenderers);
|
const pipeline = buildPipeline(activeRenderers);
|
||||||
pipeline.run(context);
|
await pipeline.run(context);
|
||||||
|
|
||||||
// Render events med hele filter (date + resource)
|
// Render events med hele filter (date + resource)
|
||||||
await this.eventRenderer.render(container, filter);
|
await this.eventRenderer.render(container, filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
private selectRenderers(viewConfig: ViewConfig): Renderer[] {
|
private selectRenderers(viewConfig: ViewConfig): IRenderer[] {
|
||||||
const types = viewConfig.groupings.map(g => g.type);
|
const types = viewConfig.groupings.map(g => g.type);
|
||||||
// Sortér renderers i samme rækkefølge som viewConfig.groupings
|
// Sortér renderers i samme rækkefølge som viewConfig.groupings
|
||||||
return types
|
return types
|
||||||
.map(type => this.allRenderers.find(r => r.type === type))
|
.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 {
|
private calculateTotalColumns(viewConfig: ViewConfig): number {
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
export interface RenderContext {
|
export interface IRenderContext {
|
||||||
headerContainer: HTMLElement;
|
headerContainer: HTMLElement;
|
||||||
columnContainer: HTMLElement;
|
columnContainer: HTMLElement;
|
||||||
filter: Record<string, string[]>; // { team: ['alpha'], resource: ['alice', 'bob'], date: [...] }
|
filter: Record<string, string[]>; // { team: ['alpha'], resource: ['alice', 'bob'], date: [...] }
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Renderer {
|
export interface IRenderer {
|
||||||
readonly type: string;
|
readonly type: string;
|
||||||
render(context: RenderContext): void;
|
render(context: IRenderContext): void | Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,14 @@
|
||||||
import { Renderer, RenderContext } from './IGroupingRenderer';
|
import { IRenderer, IRenderContext } from './IGroupingRenderer';
|
||||||
|
|
||||||
export interface Pipeline {
|
export interface Pipeline {
|
||||||
run(context: RenderContext): void;
|
run(context: IRenderContext): Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function buildPipeline(renderers: Renderer[]): Pipeline {
|
export function buildPipeline(renderers: IRenderer[]): Pipeline {
|
||||||
return {
|
return {
|
||||||
run(context: RenderContext) {
|
async run(context: IRenderContext) {
|
||||||
for (const renderer of renderers) {
|
for (const renderer of renderers) {
|
||||||
renderer.render(context);
|
await renderer.render(context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -73,7 +73,7 @@ export class DemoApp {
|
||||||
return {
|
return {
|
||||||
templateId: 'day',
|
templateId: 'day',
|
||||||
groupings: [
|
groupings: [
|
||||||
{ type: 'resource', values: ['res1', 'res2'] },
|
{ type: 'resource', values: ['EMP001', 'EMP002'] },
|
||||||
{ type: 'date', values: today }
|
{ type: 'date', values: today }
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
@ -90,7 +90,7 @@ export class DemoApp {
|
||||||
return {
|
return {
|
||||||
templateId: 'resource',
|
templateId: 'resource',
|
||||||
groupings: [
|
groupings: [
|
||||||
{ type: 'resource', values: ['res1', 'res2'] },
|
{ type: 'resource', values: ['EMP001', 'EMP002'] },
|
||||||
{ type: 'date', values: dates }
|
{ type: 'date', values: dates }
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
import { Renderer, RenderContext } from '../../core/IGroupingRenderer';
|
import { IRenderer, IRenderContext } from '../../core/IGroupingRenderer';
|
||||||
import { DateService } from '../../core/DateService';
|
import { DateService } from '../../core/DateService';
|
||||||
|
|
||||||
export class DateRenderer implements Renderer {
|
export class DateRenderer implements IRenderer {
|
||||||
readonly type = 'date';
|
readonly type = 'date';
|
||||||
|
|
||||||
constructor(private dateService: DateService) {}
|
constructor(private dateService: DateService) {}
|
||||||
|
|
||||||
render(context: RenderContext): void {
|
render(context: IRenderContext): void {
|
||||||
const dates = context.filter['date'] || [];
|
const dates = context.filter['date'] || [];
|
||||||
const resourceIds = context.filter['resource'] || [];
|
const resourceIds = context.filter['resource'] || [];
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,31 +1,20 @@
|
||||||
import { Renderer, RenderContext } from '../../core/IGroupingRenderer';
|
import { IRenderer, IRenderContext } from '../../core/IGroupingRenderer';
|
||||||
|
import { ResourceService } from '../../storage/resources/ResourceService';
|
||||||
|
|
||||||
interface Resource {
|
export class ResourceRenderer implements IRenderer {
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ResourceRenderer implements Renderer {
|
|
||||||
readonly type = 'resource';
|
readonly type = 'resource';
|
||||||
|
|
||||||
// Hardcoded data
|
constructor(private resourceService: ResourceService) {}
|
||||||
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));
|
|
||||||
|
|
||||||
|
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;
|
const dateCount = context.filter['date']?.length || 1;
|
||||||
|
|
||||||
// Render ALLE resource headers
|
for (const resource of resources) {
|
||||||
for (const resource of filteredResources) {
|
|
||||||
const header = document.createElement('swp-resource-header');
|
const header = document.createElement('swp-resource-header');
|
||||||
header.dataset.resourceId = resource.id;
|
header.dataset.resourceId = resource.id;
|
||||||
header.textContent = resource.name;
|
header.textContent = resource.displayName;
|
||||||
header.style.gridColumn = `span ${dateCount}`;
|
header.style.gridColumn = `span ${dateCount}`;
|
||||||
context.headerContainer.appendChild(header);
|
context.headerContainer.appendChild(header);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { Renderer, RenderContext } from '../../core/IGroupingRenderer';
|
import { IRenderer, IRenderContext } from '../../core/IGroupingRenderer';
|
||||||
|
|
||||||
interface Team {
|
interface Team {
|
||||||
id: string;
|
id: string;
|
||||||
|
|
@ -6,7 +6,7 @@ interface Team {
|
||||||
resourceIds: string[];
|
resourceIds: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export class TeamRenderer implements Renderer {
|
export class TeamRenderer implements IRenderer {
|
||||||
readonly type = 'team';
|
readonly type = 'team';
|
||||||
|
|
||||||
// Hardcoded data
|
// Hardcoded data
|
||||||
|
|
@ -15,7 +15,7 @@ export class TeamRenderer implements Renderer {
|
||||||
{ id: 'team2', name: 'Team Beta', resourceIds: ['res3'] }
|
{ id: 'team2', name: 'Team Beta', resourceIds: ['res3'] }
|
||||||
];
|
];
|
||||||
|
|
||||||
render(context: RenderContext): void {
|
render(context: IRenderContext): void {
|
||||||
const allowedIds = context.filter['team'] || [];
|
const allowedIds = context.filter['team'] || [];
|
||||||
const filteredTeams = this.teams.filter(t => allowedIds.includes(t.id));
|
const filteredTeams = this.teams.filter(t => allowedIds.includes(t.id));
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
// Core exports
|
// Core exports
|
||||||
export { ViewTemplate, ViewConfig, GroupingConfig } from './core/ViewConfig';
|
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 { IGroupingStore } from './core/IGroupingStore';
|
||||||
export { CalendarOrchestrator } from './core/CalendarOrchestrator';
|
export { CalendarOrchestrator } from './core/CalendarOrchestrator';
|
||||||
export { NavigationAnimator } from './core/NavigationAnimator';
|
export { NavigationAnimator } from './core/NavigationAnimator';
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,16 @@ export class ResourceService extends BaseEntityService<IResource> {
|
||||||
return all.filter(r => r.isActive !== false);
|
return all.filter(r => r.isActive !== false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get resources by IDs
|
||||||
|
*/
|
||||||
|
async getByIds(ids: string[]): Promise<IResource[]> {
|
||||||
|
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
|
* Get resources by type
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue