Moving away from Azure Devops #1

Merged
Janus007 merged 113 commits from refac into master 2026-02-03 00:04:27 +01:00
3 changed files with 182 additions and 132 deletions
Showing only changes of commit 8161b3c42a - Show all commits

View file

@ -9,6 +9,7 @@ import { ResourceRenderer } from './features/resource/ResourceRenderer';
import { TeamRenderer } from './features/team/TeamRenderer';
import { DepartmentRenderer } from './features/department/DepartmentRenderer';
import { CalendarOrchestrator } from './core/CalendarOrchestrator';
import { CalendarApp } from './core/CalendarApp';
import { TimeAxisRenderer } from './features/timeaxis/TimeAxisRenderer';
import { ScrollManager } from './core/ScrollManager';
import { HeaderDrawerManager } from './core/HeaderDrawerManager';
@ -220,6 +221,9 @@ export function createV2Container(): Container {
builder.registerType(ResizeManager).as<ResizeManager>();
builder.registerType(EventPersistenceManager).as<EventPersistenceManager>();
// CalendarApp - genbrugelig kalenderkomponent
builder.registerType(CalendarApp).as<CalendarApp>();
// Demo app
builder.registerType(DemoApp).as<DemoApp>();

140
src/v2/core/CalendarApp.ts Normal file
View file

@ -0,0 +1,140 @@
import { CalendarOrchestrator } from './CalendarOrchestrator';
import { TimeAxisRenderer } from '../features/timeaxis/TimeAxisRenderer';
import { NavigationAnimator } from './NavigationAnimator';
import { DateService } from './DateService';
import { ScrollManager } from './ScrollManager';
import { HeaderDrawerManager } from './HeaderDrawerManager';
import { ViewConfig } from './ViewConfig';
import { DragDropManager } from '../managers/DragDropManager';
import { EdgeScrollManager } from '../managers/EdgeScrollManager';
import { ResizeManager } from '../managers/ResizeManager';
import { EventPersistenceManager } from '../managers/EventPersistenceManager';
import { HeaderDrawerRenderer } from '../features/headerdrawer/HeaderDrawerRenderer';
import { SettingsService } from '../storage/settings/SettingsService';
import { ResourceService } from '../storage/resources/ResourceService';
import { ViewConfigService } from '../storage/viewconfigs/ViewConfigService';
import { IWorkweekPreset } from '../types/SettingsTypes';
export class CalendarApp {
private animator!: NavigationAnimator;
private container!: HTMLElement;
private weekOffset = 0;
private currentViewId = 'simple';
private workweekPreset: IWorkweekPreset | null = null;
constructor(
private orchestrator: CalendarOrchestrator,
private timeAxisRenderer: TimeAxisRenderer,
private dateService: DateService,
private scrollManager: ScrollManager,
private headerDrawerManager: HeaderDrawerManager,
private dragDropManager: DragDropManager,
private edgeScrollManager: EdgeScrollManager,
private resizeManager: ResizeManager,
private headerDrawerRenderer: HeaderDrawerRenderer,
private eventPersistenceManager: EventPersistenceManager,
private settingsService: SettingsService,
private resourceService: ResourceService,
private viewConfigService: ViewConfigService
) {}
async init(container: HTMLElement): Promise<void> {
this.container = container;
// Load default workweek preset from settings
this.workweekPreset = await this.settingsService.getDefaultWorkweekPreset();
// Create NavigationAnimator with DOM elements
this.animator = new NavigationAnimator(
container.querySelector('swp-header-track') as HTMLElement,
container.querySelector('swp-content-track') as HTMLElement
);
// Render time axis (from settings later, hardcoded for now)
this.timeAxisRenderer.render(
container.querySelector('#time-axis') as HTMLElement,
6,
18
);
// Init managers
this.scrollManager.init(container);
this.headerDrawerManager.init(container);
this.dragDropManager.init(container);
this.resizeManager.init(container);
const scrollableContent = container.querySelector('swp-scrollable-content') as HTMLElement;
this.edgeScrollManager.init(scrollableContent);
// Setup command event listeners
this.setupEventListeners();
// Emit ready status
this.emitStatus('ready');
}
private setupEventListeners(): void {
this.container.addEventListener('calendar:cmd:render', ((e: CustomEvent) => {
const { viewId, direction } = e.detail;
this.handleRenderCommand(viewId, direction);
}) as EventListener);
this.container.addEventListener('calendar:cmd:navigate', ((e: CustomEvent) => {
const { offset, direction } = e.detail;
this.handleNavigateCommand(offset, direction);
}) as EventListener);
this.container.addEventListener('calendar:cmd:drawer:toggle', (() => {
this.headerDrawerManager.toggle();
}) as EventListener);
}
private async handleRenderCommand(viewId: string, direction?: 'left' | 'right'): Promise<void> {
this.currentViewId = viewId;
if (direction) {
await this.animator.slide(direction, () => this.render());
} else {
await this.render();
}
this.emitStatus('rendered', { viewId });
}
private async handleNavigateCommand(offset: number, direction: 'left' | 'right'): Promise<void> {
this.weekOffset += offset;
await this.animator.slide(direction, () => this.render());
this.emitStatus('rendered', { viewId: this.currentViewId });
}
private async render(): Promise<void> {
const storedConfig = await this.viewConfigService.getById(this.currentViewId);
if (!storedConfig) {
this.emitStatus('error', { message: `ViewConfig not found: ${this.currentViewId}` });
return;
}
// Populate date values based on workweek and offset
const workDays = this.workweekPreset?.workDays || [1, 2, 3, 4, 5];
const dates = this.currentViewId === 'day'
? this.dateService.getWeekDates(this.weekOffset, 1)
: this.dateService.getWorkWeekDates(this.weekOffset, workDays);
// Clone config and populate dates
const viewConfig: ViewConfig = {
...storedConfig,
groupings: storedConfig.groupings.map(g =>
g.type === 'date' ? { ...g, values: dates } : g
)
};
await this.orchestrator.render(viewConfig, this.container);
}
private emitStatus(status: string, detail?: object): void {
this.container.dispatchEvent(new CustomEvent(`calendar:status:${status}`, {
detail,
bubbles: true
}));
}
}

View file

@ -1,47 +1,19 @@
import { CalendarOrchestrator } from '../core/CalendarOrchestrator';
import { TimeAxisRenderer } from '../features/timeaxis/TimeAxisRenderer';
import { NavigationAnimator } from '../core/NavigationAnimator';
import { DateService } from '../core/DateService';
import { ScrollManager } from '../core/ScrollManager';
import { HeaderDrawerManager } from '../core/HeaderDrawerManager';
import { IndexedDBContext } from '../storage/IndexedDBContext';
import { DataSeeder } from '../workers/DataSeeder';
import { ViewConfig } from '../core/ViewConfig';
import { DragDropManager } from '../managers/DragDropManager';
import { EdgeScrollManager } from '../managers/EdgeScrollManager';
import { ResizeManager } from '../managers/ResizeManager';
import { EventPersistenceManager } from '../managers/EventPersistenceManager';
import { HeaderDrawerRenderer } from '../features/headerdrawer/HeaderDrawerRenderer';
import { AuditService } from '../storage/audit/AuditService';
import { SettingsService } from '../storage/settings/SettingsService';
import { ResourceService } from '../storage/resources/ResourceService';
import { ViewConfigService } from '../storage/viewconfigs/ViewConfigService';
import { IWorkweekPreset } from '../types/SettingsTypes';
import { CalendarApp } from '../core/CalendarApp';
import { DateService } from '../core/DateService';
export class DemoApp {
private animator!: NavigationAnimator;
private container!: HTMLElement;
private weekOffset = 0;
private currentView: 'day' | 'simple' | 'resource' | 'picker' | 'team' | 'department' = 'simple';
private workweekPreset: IWorkweekPreset | null = null;
private currentView = 'simple';
constructor(
private orchestrator: CalendarOrchestrator,
private timeAxisRenderer: TimeAxisRenderer,
private dateService: DateService,
private scrollManager: ScrollManager,
private headerDrawerManager: HeaderDrawerManager,
private indexedDBContext: IndexedDBContext,
private dataSeeder: DataSeeder,
private dragDropManager: DragDropManager,
private edgeScrollManager: EdgeScrollManager,
private resizeManager: ResizeManager,
private headerDrawerRenderer: HeaderDrawerRenderer,
private eventPersistenceManager: EventPersistenceManager,
private auditService: AuditService,
private settingsService: SettingsService,
private resourceService: ResourceService,
private viewConfigService: ViewConfigService
private calendarApp: CalendarApp,
private dateService: DateService
) {}
async init(): Promise<void> {
@ -56,145 +28,79 @@ export class DemoApp {
await this.dataSeeder.seedIfEmpty();
console.log('[DemoApp] Data seeding complete');
// Load default workweek preset from settings
this.workweekPreset = await this.settingsService.getDefaultWorkweekPreset();
console.log('[DemoApp] Workweek preset loaded:', this.workweekPreset?.id);
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
);
// Initialize CalendarApp
await this.calendarApp.init(this.container);
console.log('[DemoApp] CalendarApp initialized');
// Render time axis (06:00 - 18:00)
this.timeAxisRenderer.render(document.getElementById('time-axis') as HTMLElement, 6, 18);
// Init scroll synkronisering
this.scrollManager.init(this.container);
// Init header drawer
this.headerDrawerManager.init(this.container);
// Init drag-drop
this.dragDropManager.init(this.container);
// Init edge scroll
const scrollableContent = this.container.querySelector('swp-scrollable-content') as HTMLElement;
this.edgeScrollManager.init(scrollableContent);
// Init resize
this.resizeManager.init(this.container);
// Setup event handlers
// Setup demo UI handlers
this.setupNavigation();
this.setupDrawerToggle();
this.setupViewSwitching();
// Setup resource selector for picker view
await this.setupResourceSelector();
// Listen for calendar status events
this.setupStatusListeners();
// Initial render
this.render();
}
private async render(): Promise<void> {
// Load ViewConfig from IndexedDB
const storedConfig = await this.viewConfigService.getById(this.currentView);
if (!storedConfig) {
console.error(`[DemoApp] ViewConfig not found for templateId: ${this.currentView}`);
return;
}
// Populate date values based on workweek and offset
const workDays = this.workweekPreset?.workDays || [1, 2, 3, 4, 5];
const dates = this.currentView === 'day'
? this.dateService.getWeekDates(this.weekOffset, 1)
: this.dateService.getWorkWeekDates(this.weekOffset, workDays);
// Clone config and populate dates
const viewConfig: ViewConfig = {
...storedConfig,
groupings: storedConfig.groupings.map(g =>
g.type === 'date' ? { ...g, values: dates } : g
)
};
await this.orchestrator.render(viewConfig, this.container);
this.emitRenderCommand(this.currentView);
}
private setupNavigation(): void {
document.getElementById('btn-prev')!.onclick = () => {
this.weekOffset--;
this.animator.slide('right', () => this.render());
this.emitNavigateCommand(-1, 'right');
};
document.getElementById('btn-next')!.onclick = () => {
this.weekOffset++;
this.animator.slide('left', () => this.render());
this.emitNavigateCommand(1, 'left');
};
}
private setupViewSwitching(): void {
// View chip buttons
const chips = document.querySelectorAll('.view-chip');
chips.forEach(chip => {
chip.addEventListener('click', () => {
// Update active state
chips.forEach(c => c.classList.remove('active'));
chip.classList.add('active');
// Switch view
const view = (chip as HTMLElement).dataset.view as typeof this.currentView;
const view = (chip as HTMLElement).dataset.view;
if (view) {
this.currentView = view;
this.updateSelectorVisibility();
this.render();
this.emitRenderCommand(view);
}
});
});
// Workweek preset dropdown
const workweekSelect = document.getElementById('workweek-select') as HTMLSelectElement;
workweekSelect?.addEventListener('change', async () => {
const presetId = workweekSelect.value;
const preset = await this.settingsService.getWorkweekPreset(presetId);
if (preset) {
this.workweekPreset = preset;
this.render();
}
});
}
private setupDrawerToggle(): void {
document.getElementById('btn-drawer')!.onclick = () => {
this.headerDrawerManager.toggle();
this.container.dispatchEvent(new CustomEvent('calendar:cmd:drawer:toggle'));
};
}
private async setupResourceSelector(): Promise<void> {
const resources = await this.resourceService.getAll();
const container = document.querySelector('.resource-checkboxes') as HTMLElement;
if (!container) return;
// Clear existing
container.innerHTML = '';
// Display resources (read-only, values are stored in ViewConfig)
resources.forEach(r => {
const label = document.createElement('label');
label.innerHTML = `
<input type="checkbox" value="${r.id}" checked disabled>
${r.displayName}
`;
container.appendChild(label);
private setupStatusListeners(): void {
this.container.addEventListener('calendar:status:ready', () => {
console.log('[DemoApp] Calendar ready');
});
this.container.addEventListener('calendar:status:rendered', ((e: CustomEvent) => {
console.log('[DemoApp] Calendar rendered:', e.detail.viewId);
}) as EventListener);
this.container.addEventListener('calendar:status:error', ((e: CustomEvent) => {
console.error('[DemoApp] Calendar error:', e.detail.message);
}) as EventListener);
}
private updateSelectorVisibility(): void {
const selector = document.querySelector('swp-resource-selector');
const showSelector = this.currentView === 'picker' || this.currentView === 'day';
selector?.classList.toggle('hidden', !showSelector);
private emitRenderCommand(viewId: string, direction?: 'left' | 'right'): void {
this.container.dispatchEvent(new CustomEvent('calendar:cmd:render', {
detail: { viewId, direction }
}));
}
private emitNavigateCommand(offset: number, direction: 'left' | 'right'): void {
this.container.dispatchEvent(new CustomEvent('calendar:cmd:navigate', {
detail: { offset, direction }
}));
}
}