Refactor calendar V2 core with DI and new features

Introduces dependency injection container and composition root
Adds core services like DateService and NavigationAnimator
Simplifies CalendarOrchestrator with improved store handling
Implements mock stores and demo application for V2 calendar
This commit is contained in:
Janus C. H. Knudsen 2025-12-07 14:31:16 +01:00
parent 1ad7d10266
commit a0c0ef9e8d
17 changed files with 331 additions and 134 deletions

84
src/v2/demo/DemoApp.ts Normal file
View file

@ -0,0 +1,84 @@
import { CalendarOrchestrator } from '../core/CalendarOrchestrator';
import { TimeAxisRenderer } from '../features/timeaxis/TimeAxisRenderer';
import { NavigationAnimator } from '../core/NavigationAnimator';
import { DateService } from '../core/DateService';
import { ViewConfig } from '../core/ViewConfig';
export class DemoApp {
private animator!: NavigationAnimator;
private container!: HTMLElement;
private weekOffset = 0;
private views!: Record<string, ViewConfig>;
constructor(
private orchestrator: CalendarOrchestrator,
private timeAxisRenderer: TimeAxisRenderer,
private dateService: DateService
) {}
init(): void {
this.container = document.querySelector('swp-calendar-container') as HTMLElement;
// NavigationAnimator har DOM-dependencies - tilladt med new
this.animator = new NavigationAnimator(
document.querySelector('swp-header-track') as HTMLElement,
document.querySelector('swp-content-track') as HTMLElement
);
// View configs
const dates = this.dateService.getWeekDates();
this.views = {
simple: { templateId: 'simple', groupings: [{ type: 'date', values: dates }] },
resource: {
templateId: 'resource',
groupings: [
{ type: 'resource', values: ['alice', 'bob', 'carol'] },
{ type: 'date', values: dates.slice(0, 3) }
]
},
team: {
templateId: 'team',
groupings: [
{ type: 'team', values: ['alpha', 'beta'] },
{ type: 'resource', values: ['alice', 'bob', 'carol', 'dave'], parentKey: 'teamId' },
{ type: 'date', values: dates.slice(0, 3) }
]
}
};
// Render time axis
this.timeAxisRenderer.render(document.getElementById('time-axis') as HTMLElement);
// Setup event handlers
this.setupNavigation();
this.setupViewSwitchers();
// Initial render
this.orchestrator.render(this.views.simple, this.container);
}
private setupNavigation(): void {
document.getElementById('btn-prev')!.onclick = () => {
this.weekOffset--;
this.views.simple.groupings[0].values = this.dateService.getWeekDates(this.weekOffset);
this.animator.slide('right', () => this.orchestrator.render(this.views.simple, this.container));
};
document.getElementById('btn-next')!.onclick = () => {
this.weekOffset++;
this.views.simple.groupings[0].values = this.dateService.getWeekDates(this.weekOffset);
this.animator.slide('left', () => this.orchestrator.render(this.views.simple, this.container));
};
}
private setupViewSwitchers(): void {
document.getElementById('btn-simple')!.onclick = () =>
this.animator.slide('right', () => this.orchestrator.render(this.views.simple, this.container));
document.getElementById('btn-resource')!.onclick = () =>
this.animator.slide('left', () => this.orchestrator.render(this.views.resource, this.container));
document.getElementById('btn-team')!.onclick = () =>
this.animator.slide('left', () => this.orchestrator.render(this.views.team, this.container));
}
}

40
src/v2/demo/MockStores.ts Normal file
View file

@ -0,0 +1,40 @@
import { IGroupingStore } from '../core/IGroupingStore';
export interface Team {
id: string;
name: string;
}
export interface Resource {
id: string;
name: string;
teamId: string;
}
export class MockTeamStore implements IGroupingStore<Team> {
readonly type = 'team';
private teams: Team[] = [
{ id: 'alpha', name: 'Team Alpha' },
{ id: 'beta', name: 'Team Beta' }
];
getByIds(ids: string[]): Team[] {
return this.teams.filter(t => ids.includes(t.id));
}
}
export class MockResourceStore implements IGroupingStore<Resource> {
readonly type = 'resource';
private resources: Resource[] = [
{ id: 'alice', name: 'Alice', teamId: 'alpha' },
{ id: 'bob', name: 'Bob', teamId: 'alpha' },
{ id: 'carol', name: 'Carol', teamId: 'beta' },
{ id: 'dave', name: 'Dave', teamId: 'beta' }
];
getByIds(ids: string[]): Resource[] {
return this.resources.filter(r => ids.includes(r.id));
}
}

5
src/v2/demo/index.ts Normal file
View file

@ -0,0 +1,5 @@
import { createV2Container } from '../V2CompositionRoot';
import { DemoApp } from './DemoApp';
const app = createV2Container();
app.resolveType<DemoApp>().init();