Adds dynamic header hiding for date groupings
Introduces hideHeader option for date grouping configurations Enables suppressing date headers in specific views like day view Improves calendar view flexibility by conditionally rendering headers
This commit is contained in:
parent
ad2df353b5
commit
ee46593a5a
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