Moving away from Azure Devops #1
8 changed files with 126 additions and 10 deletions
|
|
@ -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 = '';
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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 */
|
||||||
|
|
|
||||||
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue