Moving away from Azure Devops #1

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

View file

@ -43,7 +43,7 @@ export class CalendarOrchestrator {
// Resolve belongsTo relations (e.g., team.resourceIds) // Resolve belongsTo relations (e.g., team.resourceIds)
const { parentChildMap, childType } = await this.resolveBelongsTo(viewConfig.groupings, filter); const { parentChildMap, childType } = await this.resolveBelongsTo(viewConfig.groupings, filter);
const context: IRenderContext = { headerContainer, columnContainer, filter, parentChildMap, childType }; const context: IRenderContext = { headerContainer, columnContainer, filter, groupings: viewConfig.groupings, parentChildMap, childType };
// Clear // Clear
headerContainer.innerHTML = ''; headerContainer.innerHTML = '';

View file

@ -1,7 +1,10 @@
import { GroupingConfig } from './ViewConfig';
export interface IRenderContext { 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: [...] }
groupings?: GroupingConfig[]; // Full grouping configs (for hideHeader etc.)
parentChildMap?: Record<string, string[]>; // { team1: ['EMP001', 'EMP002'], team2: ['EMP003', 'EMP004'] } parentChildMap?: Record<string, string[]>; // { team1: ['EMP001', 'EMP002'], team2: ['EMP003', 'EMP004'] }
childType?: string; // The type of the child grouping (e.g., 'resource' when team has belongsTo) childType?: string; // The type of the child grouping (e.g., 'resource' when team has belongsTo)
} }

View file

@ -15,4 +15,5 @@ export interface GroupingConfig {
idProperty?: string; // Property-navn på event (f.eks. 'resourceId') - kun for event matching idProperty?: string; // Property-navn på event (f.eks. 'resourceId') - kun for event matching
derivedFrom?: string; // Hvis feltet udledes fra anden property (f.eks. 'date' fra 'start') derivedFrom?: string; // Hvis feltet udledes fra anden property (f.eks. 'date' fra 'start')
belongsTo?: string; // Parent-child relation (f.eks. 'team.resourceIds') belongsTo?: string; // Parent-child relation (f.eks. 'team.resourceIds')
hideHeader?: boolean; // Skjul header-rækken for denne grouping (f.eks. dato i day-view)
} }

View file

@ -14,14 +14,16 @@ import { EventPersistenceManager } from '../managers/EventPersistenceManager';
import { HeaderDrawerRenderer } from '../features/headerdrawer/HeaderDrawerRenderer'; 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 { SettingsService } from '../storage/settings/SettingsService';
import { ResourceService } from '../storage/resources/ResourceService';
import { IWorkweekPreset } from '../types/SettingsTypes'; import { IWorkweekPreset } from '../types/SettingsTypes';
export class DemoApp { export class DemoApp {
private animator!: NavigationAnimator; private animator!: NavigationAnimator;
private container!: HTMLElement; private container!: HTMLElement;
private weekOffset = 0; private weekOffset = 0;
private currentView: 'day' | 'simple' | 'resource' | 'team' | 'department' = 'simple'; private currentView: 'day' | 'simple' | 'resource' | 'picker' | 'team' | 'department' = 'simple';
private workweekPreset: IWorkweekPreset | null = null; private workweekPreset: IWorkweekPreset | null = null;
private selectedResourceIds: string[] = [];
constructor( constructor(
private orchestrator: CalendarOrchestrator, private orchestrator: CalendarOrchestrator,
@ -37,7 +39,8 @@ export class DemoApp {
private headerDrawerRenderer: HeaderDrawerRenderer, private headerDrawerRenderer: HeaderDrawerRenderer,
private eventPersistenceManager: EventPersistenceManager, private eventPersistenceManager: EventPersistenceManager,
private auditService: AuditService, private auditService: AuditService,
private settingsService: SettingsService private settingsService: SettingsService,
private resourceService: ResourceService
) {} ) {}
async init(): Promise<void> { async init(): Promise<void> {
@ -88,6 +91,9 @@ export class DemoApp {
this.setupDrawerToggle(); this.setupDrawerToggle();
this.setupViewSwitching(); this.setupViewSwitching();
// Setup resource selector for picker view
await this.setupResourceSelector();
// Initial render // Initial render
this.render(); this.render();
} }
@ -109,7 +115,7 @@ export class DemoApp {
templateId: 'day', templateId: 'day',
groupings: [ groupings: [
{ type: 'resource', values: ['EMP001', 'EMP002'], idProperty: 'resourceId' }, { type: 'resource', values: ['EMP001', 'EMP002'], idProperty: 'resourceId' },
{ type: 'date', values: today, idProperty: 'date', derivedFrom: 'start' } { type: 'date', values: today, idProperty: 'date', derivedFrom: 'start', hideHeader: true }
] ]
}; };
@ -149,6 +155,15 @@ export class DemoApp {
{ type: 'date', values: dates, idProperty: 'date', derivedFrom: 'start' } { type: 'date', values: dates, idProperty: 'date', derivedFrom: 'start' }
] ]
}; };
case 'picker':
return {
templateId: 'picker',
groupings: [
{ type: 'resource', values: this.selectedResourceIds, idProperty: 'resourceId' },
{ type: 'date', values: dates, idProperty: 'date', derivedFrom: 'start' }
]
};
} }
} }
@ -177,6 +192,7 @@ export class DemoApp {
const view = (chip as HTMLElement).dataset.view as typeof this.currentView; const view = (chip as HTMLElement).dataset.view as typeof this.currentView;
if (view) { if (view) {
this.currentView = view; this.currentView = view;
this.updateSelectorVisibility();
this.render(); this.render();
} }
}); });
@ -199,4 +215,38 @@ export class DemoApp {
this.headerDrawerManager.toggle(); this.headerDrawerManager.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 = '';
// Create checkboxes for each resource
resources.forEach(r => {
const label = document.createElement('label');
label.innerHTML = `
<input type="checkbox" value="${r.id}" checked>
${r.displayName}
`;
container.appendChild(label);
});
// Default: all selected
this.selectedResourceIds = resources.map(r => r.id);
// Event listener for checkbox changes
container.addEventListener('change', () => {
const checked = container.querySelectorAll('input:checked') as NodeListOf<HTMLInputElement>;
this.selectedResourceIds = Array.from(checked).map(cb => cb.value);
this.render();
});
}
private updateSelectorVisibility(): void {
const selector = document.querySelector('swp-resource-selector');
selector?.classList.toggle('hidden', this.currentView !== 'picker');
}
} }

View file

@ -10,6 +10,10 @@ export class DateRenderer implements IRenderer {
const dates = context.filter['date'] || []; const dates = context.filter['date'] || [];
const resourceIds = context.filter['resource'] || []; const resourceIds = context.filter['resource'] || [];
// Check if date headers should be hidden (e.g., in day view)
const dateGrouping = context.groupings?.find(g => g.type === 'date');
const hideHeader = dateGrouping?.hideHeader === true;
// Render dates for HVER resource (eller 1 gang hvis ingen resources) // Render dates for HVER resource (eller 1 gang hvis ingen resources)
const iterations = resourceIds.length || 1; const iterations = resourceIds.length || 1;
let columnCount = 0; let columnCount = 0;
@ -32,6 +36,9 @@ export class DateRenderer implements IRenderer {
if (resourceId) { if (resourceId) {
header.dataset.resourceId = resourceId; header.dataset.resourceId = resourceId;
} }
if (hideHeader) {
header.dataset.hidden = 'true';
}
header.innerHTML = ` header.innerHTML = `
<swp-day-name>${this.dateService.getDayName(date, 'short')}</swp-day-name> <swp-day-name>${this.dateService.getDayName(date, 'short')}</swp-day-name>
<swp-day-date>${date.getDate()}</swp-day-date> <swp-day-date>${date.getDate()}</swp-day-date>

View file

@ -68,6 +68,48 @@ swp-view-switcher {
&:focus { outline: 2px solid var(--color-primary); outline-offset: 1px; } &:focus { outline: 2px solid var(--color-primary); outline-offset: 1px; }
} }
/* Resource selector (picker view) */
swp-resource-selector {
&.hidden { display: none; }
fieldset {
border: 1px solid var(--color-border);
border-radius: 6px;
padding: 6px 12px;
margin: 0;
}
legend {
font-size: 11px;
font-weight: 500;
color: var(--color-text-secondary);
padding: 0 6px;
}
.resource-checkboxes {
display: flex;
flex-wrap: wrap;
gap: 4px 16px;
}
label {
display: flex;
align-items: center;
gap: 4px;
font-size: 12px;
cursor: pointer;
white-space: nowrap;
&:hover { color: var(--color-primary); }
}
input[type="checkbox"] {
width: 14px;
height: 14px;
cursor: pointer;
}
}
/* Navigation group */ /* Navigation group */
swp-nav-group { swp-nav-group {
display: flex; display: flex;
@ -289,6 +331,10 @@ swp-day-header {
font-size: 24px; font-size: 24px;
font-weight: 300; font-weight: 300;
} }
&[data-hidden="true"] {
display: none;
}
} }
/* Scrollable content */ /* Scrollable content */

View file

@ -12,13 +12,13 @@
}, },
"compressed": { "compressed": {
"id": "compressed", "id": "compressed",
"workDays": [1, 2, 3, 4], "workDays": [1, 2, 3],
"label": "Man-Tor" "label": "Man-Ons"
}, },
"midweek": { "midweek": {
"id": "midweek", "id": "midweek",
"workDays": [3, 4, 5], "workDays": [4, 5],
"label": "Ons-Fre" "label": "Tors-Fre"
}, },
"weekend": { "weekend": {
"id": "weekend", "id": "weekend",

View file

@ -15,15 +15,24 @@
<button class="view-chip active" data-view="simple">Datoer</button> <button class="view-chip active" data-view="simple">Datoer</button>
<button class="view-chip" data-view="day">Dag</button> <button class="view-chip" data-view="day">Dag</button>
<button class="view-chip" data-view="resource">Resource</button> <button class="view-chip" data-view="resource">Resource</button>
<button class="view-chip" data-view="picker">Picker</button>
<button class="view-chip" data-view="team">Team</button> <button class="view-chip" data-view="team">Team</button>
<button class="view-chip" data-view="department">Dept</button> <button class="view-chip" data-view="department">Dept</button>
</swp-view-switcher> </swp-view-switcher>
<!-- Resource selector (only visible in picker view) -->
<swp-resource-selector class="hidden">
<fieldset>
<legend>Vælg resources</legend>
<div class="resource-checkboxes"></div>
</fieldset>
</swp-resource-selector>
<!-- Workweek preset dropdown --> <!-- Workweek preset dropdown -->
<select id="workweek-select" class="workweek-dropdown"> <select id="workweek-select" class="workweek-dropdown">
<option value="standard">Man-Fre</option> <option value="standard">Man-Fre</option>
<option value="compressed">Man-Tor</option> <option value="compressed">Man-Ons</option>
<option value="midweek">Ons-Fre</option> <option value="midweek">Tors-Fre</option>
<option value="weekend">Weekend</option> <option value="weekend">Weekend</option>
<option value="fullweek">Alle dage</option> <option value="fullweek">Alle dage</option>
</select> </select>