Moving away from Azure Devops #1

Merged
Janus007 merged 113 commits from refac into master 2026-02-03 00:04:27 +01:00
10 changed files with 46 additions and 47 deletions
Showing only changes of commit 400de8c9d5 - Show all commits

View file

@ -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>();

View file

@ -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 {

View file

@ -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>;
} }

View file

@ -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);
} }
} }
}; };

View file

@ -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 }
] ]
}; };

View file

@ -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'] || [];

View file

@ -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);
} }

View file

@ -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));

View file

@ -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';

View file

@ -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
*/ */