Moving away from Azure Devops #1
3 changed files with 182 additions and 132 deletions
|
|
@ -9,6 +9,7 @@ import { ResourceRenderer } from './features/resource/ResourceRenderer';
|
||||||
import { TeamRenderer } from './features/team/TeamRenderer';
|
import { TeamRenderer } from './features/team/TeamRenderer';
|
||||||
import { DepartmentRenderer } from './features/department/DepartmentRenderer';
|
import { DepartmentRenderer } from './features/department/DepartmentRenderer';
|
||||||
import { CalendarOrchestrator } from './core/CalendarOrchestrator';
|
import { CalendarOrchestrator } from './core/CalendarOrchestrator';
|
||||||
|
import { CalendarApp } from './core/CalendarApp';
|
||||||
import { TimeAxisRenderer } from './features/timeaxis/TimeAxisRenderer';
|
import { TimeAxisRenderer } from './features/timeaxis/TimeAxisRenderer';
|
||||||
import { ScrollManager } from './core/ScrollManager';
|
import { ScrollManager } from './core/ScrollManager';
|
||||||
import { HeaderDrawerManager } from './core/HeaderDrawerManager';
|
import { HeaderDrawerManager } from './core/HeaderDrawerManager';
|
||||||
|
|
@ -220,6 +221,9 @@ export function createV2Container(): Container {
|
||||||
builder.registerType(ResizeManager).as<ResizeManager>();
|
builder.registerType(ResizeManager).as<ResizeManager>();
|
||||||
builder.registerType(EventPersistenceManager).as<EventPersistenceManager>();
|
builder.registerType(EventPersistenceManager).as<EventPersistenceManager>();
|
||||||
|
|
||||||
|
// CalendarApp - genbrugelig kalenderkomponent
|
||||||
|
builder.registerType(CalendarApp).as<CalendarApp>();
|
||||||
|
|
||||||
// Demo app
|
// Demo app
|
||||||
builder.registerType(DemoApp).as<DemoApp>();
|
builder.registerType(DemoApp).as<DemoApp>();
|
||||||
|
|
||||||
|
|
|
||||||
140
src/v2/core/CalendarApp.ts
Normal file
140
src/v2/core/CalendarApp.ts
Normal 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
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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 { IndexedDBContext } from '../storage/IndexedDBContext';
|
||||||
import { DataSeeder } from '../workers/DataSeeder';
|
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 { AuditService } from '../storage/audit/AuditService';
|
||||||
import { SettingsService } from '../storage/settings/SettingsService';
|
import { CalendarApp } from '../core/CalendarApp';
|
||||||
import { ResourceService } from '../storage/resources/ResourceService';
|
import { DateService } from '../core/DateService';
|
||||||
import { ViewConfigService } from '../storage/viewconfigs/ViewConfigService';
|
|
||||||
import { IWorkweekPreset } from '../types/SettingsTypes';
|
|
||||||
|
|
||||||
export class DemoApp {
|
export class DemoApp {
|
||||||
private animator!: NavigationAnimator;
|
|
||||||
private container!: HTMLElement;
|
private container!: HTMLElement;
|
||||||
private weekOffset = 0;
|
private currentView = 'simple';
|
||||||
private currentView: 'day' | 'simple' | 'resource' | 'picker' | 'team' | 'department' = 'simple';
|
|
||||||
private workweekPreset: IWorkweekPreset | null = null;
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private orchestrator: CalendarOrchestrator,
|
|
||||||
private timeAxisRenderer: TimeAxisRenderer,
|
|
||||||
private dateService: DateService,
|
|
||||||
private scrollManager: ScrollManager,
|
|
||||||
private headerDrawerManager: HeaderDrawerManager,
|
|
||||||
private indexedDBContext: IndexedDBContext,
|
private indexedDBContext: IndexedDBContext,
|
||||||
private dataSeeder: DataSeeder,
|
private dataSeeder: DataSeeder,
|
||||||
private dragDropManager: DragDropManager,
|
|
||||||
private edgeScrollManager: EdgeScrollManager,
|
|
||||||
private resizeManager: ResizeManager,
|
|
||||||
private headerDrawerRenderer: HeaderDrawerRenderer,
|
|
||||||
private eventPersistenceManager: EventPersistenceManager,
|
|
||||||
private auditService: AuditService,
|
private auditService: AuditService,
|
||||||
private settingsService: SettingsService,
|
private calendarApp: CalendarApp,
|
||||||
private resourceService: ResourceService,
|
private dateService: DateService
|
||||||
private viewConfigService: ViewConfigService
|
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async init(): Promise<void> {
|
async init(): Promise<void> {
|
||||||
|
|
@ -56,145 +28,79 @@ export class DemoApp {
|
||||||
await this.dataSeeder.seedIfEmpty();
|
await this.dataSeeder.seedIfEmpty();
|
||||||
console.log('[DemoApp] Data seeding complete');
|
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;
|
this.container = document.querySelector('swp-calendar-container') as HTMLElement;
|
||||||
|
|
||||||
// NavigationAnimator har DOM-dependencies - tilladt med new
|
// Initialize CalendarApp
|
||||||
this.animator = new NavigationAnimator(
|
await this.calendarApp.init(this.container);
|
||||||
document.querySelector('swp-header-track') as HTMLElement,
|
console.log('[DemoApp] CalendarApp initialized');
|
||||||
document.querySelector('swp-content-track') as HTMLElement
|
|
||||||
);
|
|
||||||
|
|
||||||
// Render time axis (06:00 - 18:00)
|
// Setup demo UI handlers
|
||||||
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
|
|
||||||
this.setupNavigation();
|
this.setupNavigation();
|
||||||
this.setupDrawerToggle();
|
this.setupDrawerToggle();
|
||||||
this.setupViewSwitching();
|
this.setupViewSwitching();
|
||||||
|
|
||||||
// Setup resource selector for picker view
|
// Listen for calendar status events
|
||||||
await this.setupResourceSelector();
|
this.setupStatusListeners();
|
||||||
|
|
||||||
// Initial render
|
// Initial render
|
||||||
this.render();
|
this.emitRenderCommand(this.currentView);
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private setupNavigation(): void {
|
private setupNavigation(): void {
|
||||||
document.getElementById('btn-prev')!.onclick = () => {
|
document.getElementById('btn-prev')!.onclick = () => {
|
||||||
this.weekOffset--;
|
this.emitNavigateCommand(-1, 'right');
|
||||||
this.animator.slide('right', () => this.render());
|
|
||||||
};
|
};
|
||||||
|
|
||||||
document.getElementById('btn-next')!.onclick = () => {
|
document.getElementById('btn-next')!.onclick = () => {
|
||||||
this.weekOffset++;
|
this.emitNavigateCommand(1, 'left');
|
||||||
this.animator.slide('left', () => this.render());
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private setupViewSwitching(): void {
|
private setupViewSwitching(): void {
|
||||||
// View chip buttons
|
|
||||||
const chips = document.querySelectorAll('.view-chip');
|
const chips = document.querySelectorAll('.view-chip');
|
||||||
chips.forEach(chip => {
|
chips.forEach(chip => {
|
||||||
chip.addEventListener('click', () => {
|
chip.addEventListener('click', () => {
|
||||||
// Update active state
|
|
||||||
chips.forEach(c => c.classList.remove('active'));
|
chips.forEach(c => c.classList.remove('active'));
|
||||||
chip.classList.add('active');
|
chip.classList.add('active');
|
||||||
|
|
||||||
// Switch view
|
const view = (chip as HTMLElement).dataset.view;
|
||||||
const view = (chip as HTMLElement).dataset.view as typeof this.currentView;
|
|
||||||
if (view) {
|
if (view) {
|
||||||
this.currentView = view;
|
this.currentView = view;
|
||||||
this.updateSelectorVisibility();
|
this.emitRenderCommand(view);
|
||||||
this.render();
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// 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 {
|
private setupDrawerToggle(): void {
|
||||||
document.getElementById('btn-drawer')!.onclick = () => {
|
document.getElementById('btn-drawer')!.onclick = () => {
|
||||||
this.headerDrawerManager.toggle();
|
this.container.dispatchEvent(new CustomEvent('calendar:cmd:drawer:toggle'));
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private async setupResourceSelector(): Promise<void> {
|
private setupStatusListeners(): void {
|
||||||
const resources = await this.resourceService.getAll();
|
this.container.addEventListener('calendar:status:ready', () => {
|
||||||
const container = document.querySelector('.resource-checkboxes') as HTMLElement;
|
console.log('[DemoApp] Calendar ready');
|
||||||
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);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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 {
|
private emitRenderCommand(viewId: string, direction?: 'left' | 'right'): void {
|
||||||
const selector = document.querySelector('swp-resource-selector');
|
this.container.dispatchEvent(new CustomEvent('calendar:cmd:render', {
|
||||||
const showSelector = this.currentView === 'picker' || this.currentView === 'day';
|
detail: { viewId, direction }
|
||||||
selector?.classList.toggle('hidden', !showSelector);
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
private emitNavigateCommand(offset: number, direction: 'left' | 'right'): void {
|
||||||
|
this.container.dispatchEvent(new CustomEvent('calendar:cmd:navigate', {
|
||||||
|
detail: { offset, direction }
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue