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)
|
||||
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
|
||||
headerContainer.innerHTML = '';
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
import { GroupingConfig } from './ViewConfig';
|
||||
|
||||
export interface IRenderContext {
|
||||
headerContainer: HTMLElement;
|
||||
columnContainer: HTMLElement;
|
||||
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'] }
|
||||
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
|
||||
derivedFrom?: string; // Hvis feltet udledes fra anden property (f.eks. 'date' fra 'start')
|
||||
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 { AuditService } from '../storage/audit/AuditService';
|
||||
import { SettingsService } from '../storage/settings/SettingsService';
|
||||
import { ResourceService } from '../storage/resources/ResourceService';
|
||||
import { IWorkweekPreset } from '../types/SettingsTypes';
|
||||
|
||||
export class DemoApp {
|
||||
private animator!: NavigationAnimator;
|
||||
private container!: HTMLElement;
|
||||
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 selectedResourceIds: string[] = [];
|
||||
|
||||
constructor(
|
||||
private orchestrator: CalendarOrchestrator,
|
||||
|
|
@ -37,7 +39,8 @@ export class DemoApp {
|
|||
private headerDrawerRenderer: HeaderDrawerRenderer,
|
||||
private eventPersistenceManager: EventPersistenceManager,
|
||||
private auditService: AuditService,
|
||||
private settingsService: SettingsService
|
||||
private settingsService: SettingsService,
|
||||
private resourceService: ResourceService
|
||||
) {}
|
||||
|
||||
async init(): Promise<void> {
|
||||
|
|
@ -88,6 +91,9 @@ export class DemoApp {
|
|||
this.setupDrawerToggle();
|
||||
this.setupViewSwitching();
|
||||
|
||||
// Setup resource selector for picker view
|
||||
await this.setupResourceSelector();
|
||||
|
||||
// Initial render
|
||||
this.render();
|
||||
}
|
||||
|
|
@ -109,7 +115,7 @@ export class DemoApp {
|
|||
templateId: 'day',
|
||||
groupings: [
|
||||
{ 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' }
|
||||
]
|
||||
};
|
||||
|
||||
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;
|
||||
if (view) {
|
||||
this.currentView = view;
|
||||
this.updateSelectorVisibility();
|
||||
this.render();
|
||||
}
|
||||
});
|
||||
|
|
@ -199,4 +215,38 @@ export class DemoApp {
|
|||
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 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)
|
||||
const iterations = resourceIds.length || 1;
|
||||
let columnCount = 0;
|
||||
|
|
@ -32,6 +36,9 @@ export class DateRenderer implements IRenderer {
|
|||
if (resourceId) {
|
||||
header.dataset.resourceId = resourceId;
|
||||
}
|
||||
if (hideHeader) {
|
||||
header.dataset.hidden = 'true';
|
||||
}
|
||||
header.innerHTML = `
|
||||
<swp-day-name>${this.dateService.getDayName(date, 'short')}</swp-day-name>
|
||||
<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; }
|
||||
}
|
||||
|
||||
/* 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 */
|
||||
swp-nav-group {
|
||||
display: flex;
|
||||
|
|
@ -289,6 +331,10 @@ swp-day-header {
|
|||
font-size: 24px;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
&[data-hidden="true"] {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* Scrollable content */
|
||||
|
|
|
|||
|
|
@ -12,13 +12,13 @@
|
|||
},
|
||||
"compressed": {
|
||||
"id": "compressed",
|
||||
"workDays": [1, 2, 3, 4],
|
||||
"label": "Man-Tor"
|
||||
"workDays": [1, 2, 3],
|
||||
"label": "Man-Ons"
|
||||
},
|
||||
"midweek": {
|
||||
"id": "midweek",
|
||||
"workDays": [3, 4, 5],
|
||||
"label": "Ons-Fre"
|
||||
"workDays": [4, 5],
|
||||
"label": "Tors-Fre"
|
||||
},
|
||||
"weekend": {
|
||||
"id": "weekend",
|
||||
|
|
|
|||
|
|
@ -15,15 +15,24 @@
|
|||
<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="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="department">Dept</button>
|
||||
</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 -->
|
||||
<select id="workweek-select" class="workweek-dropdown">
|
||||
<option value="standard">Man-Fre</option>
|
||||
<option value="compressed">Man-Tor</option>
|
||||
<option value="midweek">Ons-Fre</option>
|
||||
<option value="compressed">Man-Ons</option>
|
||||
<option value="midweek">Tors-Fre</option>
|
||||
<option value="weekend">Weekend</option>
|
||||
<option value="fullweek">Alle dage</option>
|
||||
</select>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue