Implements FilterTemplate system for event matching
Introduces flexible key-based filtering for calendar events across different view configurations Adds new FilterTemplate class to: - Define event matching rules based on view configuration - Support multi-level grouping (team/resource/date) - Handle dynamic key generation for columns and events Enhances view configuration with explicit id properties and derived fields
This commit is contained in:
parent
c2f7564f8e
commit
dd647acab8
8 changed files with 331 additions and 41 deletions
180
docs/filter-template.md
Normal file
180
docs/filter-template.md
Normal file
|
|
@ -0,0 +1,180 @@
|
||||||
|
# FilterTemplate System
|
||||||
|
|
||||||
|
## Problem
|
||||||
|
|
||||||
|
En kolonne har en unik nøgle baseret på view-konfigurationen (f.eks. team + resource + date).
|
||||||
|
Events skal matches mod denne nøgle - men kun på de felter viewet definerer.
|
||||||
|
|
||||||
|
## Løsning: FilterTemplate
|
||||||
|
|
||||||
|
ViewConfig definerer hvilke felter (idProperties) der indgår i kolonnens nøgle.
|
||||||
|
Samme template bruges til at bygge nøgle for både kolonne og event.
|
||||||
|
|
||||||
|
**Princip:** Kolonnens nøgle-template bestemmer hvad der matches på.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ViewConfig med idProperty
|
||||||
|
|
||||||
|
ViewConfig er kilden til sandhed - den definerer grupper OG deres relations-id property.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface GroupingConfig {
|
||||||
|
type: string; // 'team', 'resource', 'date'
|
||||||
|
values: string[]; // ['EMP001', 'EMP002']
|
||||||
|
idProperty: string; // property-navn på event (eks. 'resourceId')
|
||||||
|
derivedFrom?: string; // for date: udledes fra 'start'
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Eksempler
|
||||||
|
|
||||||
|
**Team → Resource → Date view:**
|
||||||
|
```typescript
|
||||||
|
{
|
||||||
|
groupings: [
|
||||||
|
{ type: 'team', values: ['team-a'], idProperty: 'teamId' },
|
||||||
|
{ type: 'resource', values: ['EMP001', 'EMP002'], idProperty: 'resourceId' },
|
||||||
|
{ type: 'date', values: ['2025-12-09', '2025-12-10'], idProperty: 'date', derivedFrom: 'start' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Simple date-only view:**
|
||||||
|
```typescript
|
||||||
|
{
|
||||||
|
groupings: [
|
||||||
|
{ type: 'date', values: ['2025-12-09', '2025-12-10'], idProperty: 'date', derivedFrom: 'start' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## FilterTemplate Klasse
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
class FilterTemplate {
|
||||||
|
private fields: Array<{
|
||||||
|
idProperty: string;
|
||||||
|
derivedFrom?: string;
|
||||||
|
}> = [];
|
||||||
|
|
||||||
|
addField(idProperty: string, derivedFrom?: string): this {
|
||||||
|
this.fields.push({ idProperty, derivedFrom });
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
buildKeyFromColumn(column: HTMLElement): string {
|
||||||
|
return this.fields
|
||||||
|
.map(f => column.dataset[f.idProperty] || '')
|
||||||
|
.join(':');
|
||||||
|
}
|
||||||
|
|
||||||
|
buildKeyFromEvent(event: ICalendarEvent, dateService: DateService): string {
|
||||||
|
return this.fields
|
||||||
|
.map(f => {
|
||||||
|
if (f.derivedFrom) {
|
||||||
|
return dateService.getDateKey((event as any)[f.derivedFrom]);
|
||||||
|
}
|
||||||
|
return (event as any)[f.idProperty] || '';
|
||||||
|
})
|
||||||
|
.join(':');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Flow
|
||||||
|
|
||||||
|
```
|
||||||
|
Orchestrator
|
||||||
|
│
|
||||||
|
├── Læs ViewConfig.groupings
|
||||||
|
│
|
||||||
|
├── Byg FilterTemplate fra groupings:
|
||||||
|
│ for (grouping of viewConfig.groupings) {
|
||||||
|
│ template.addField(grouping.idProperty, grouping.derivedFrom);
|
||||||
|
│ }
|
||||||
|
│
|
||||||
|
├── Kør group-renderers (bygger headers + kolonner)
|
||||||
|
│ └── DateRenderer sætter column.dataset[idProperty] for ALLE grupperinger
|
||||||
|
│
|
||||||
|
└── EventRenderer.render(ctx, template)
|
||||||
|
│
|
||||||
|
└── for each column:
|
||||||
|
columnKey = template.buildKeyFromColumn(column)
|
||||||
|
columnEvents = events.filter(e =>
|
||||||
|
template.buildKeyFromEvent(e) === columnKey
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Eksempler
|
||||||
|
|
||||||
|
### 3-niveau view: Team → Resource → Date
|
||||||
|
|
||||||
|
**ViewConfig:**
|
||||||
|
```typescript
|
||||||
|
groupings: [
|
||||||
|
{ type: 'team', values: ['team-a'], idProperty: 'teamId' },
|
||||||
|
{ type: 'resource', values: ['EMP001'], idProperty: 'resourceId' },
|
||||||
|
{ type: 'date', values: ['2025-12-09'], idProperty: 'date', derivedFrom: 'start' }
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Template:** `['teamId', 'resourceId', 'date']`
|
||||||
|
|
||||||
|
**Kolonne-nøgle:** `"team-a:EMP001:2025-12-09"`
|
||||||
|
**Event-nøgle:** `"team-a:EMP001:2025-12-09"`
|
||||||
|
|
||||||
|
**Match!**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2-niveau view: Resource → Date
|
||||||
|
|
||||||
|
**ViewConfig:**
|
||||||
|
```typescript
|
||||||
|
groupings: [
|
||||||
|
{ type: 'resource', values: ['EMP001'], idProperty: 'resourceId' },
|
||||||
|
{ type: 'date', values: ['2025-12-09'], idProperty: 'date', derivedFrom: 'start' }
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Template:** `['resourceId', 'date']`
|
||||||
|
|
||||||
|
**Kolonne-nøgle:** `"EMP001:2025-12-09"`
|
||||||
|
**Event-nøgle:** `"EMP001:2025-12-09"` (teamId ignoreres - ikke i template)
|
||||||
|
|
||||||
|
**Match!**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 1-niveau view: Kun Date
|
||||||
|
|
||||||
|
**ViewConfig:**
|
||||||
|
```typescript
|
||||||
|
groupings: [
|
||||||
|
{ type: 'date', values: ['2025-12-09'], idProperty: 'date', derivedFrom: 'start' }
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Template:** `['date']`
|
||||||
|
|
||||||
|
**Kolonne-nøgle:** `"2025-12-09"`
|
||||||
|
**Event-nøgle:** `"2025-12-09"` (alle andre felter ignoreres)
|
||||||
|
|
||||||
|
**Match!** Samme event vises i alle views - kun de relevante felter indgår i matching.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Kerneprincipper
|
||||||
|
|
||||||
|
1. **ViewConfig definerer nøgle-template** - hvilke idProperties der indgår
|
||||||
|
2. **Samme template til kolonne og event** - sikrer konsistent matching
|
||||||
|
3. **Felter udenfor template ignoreres** - event med ekstra felter matcher stadig
|
||||||
|
4. **idProperty** - eksplicit mapping mellem gruppering og event-felt
|
||||||
|
5. **derivedFrom** - håndterer felter der udledes (f.eks. date fra start)
|
||||||
|
|
@ -4,13 +4,16 @@ import { EventRenderer } from '../features/event/EventRenderer';
|
||||||
import { ScheduleRenderer } from '../features/schedule/ScheduleRenderer';
|
import { ScheduleRenderer } from '../features/schedule/ScheduleRenderer';
|
||||||
import { HeaderDrawerRenderer } from '../features/headerdrawer/HeaderDrawerRenderer';
|
import { HeaderDrawerRenderer } from '../features/headerdrawer/HeaderDrawerRenderer';
|
||||||
import { ViewConfig } from './ViewConfig';
|
import { ViewConfig } from './ViewConfig';
|
||||||
|
import { FilterTemplate } from './FilterTemplate';
|
||||||
|
import { DateService } from './DateService';
|
||||||
|
|
||||||
export class CalendarOrchestrator {
|
export class CalendarOrchestrator {
|
||||||
constructor(
|
constructor(
|
||||||
private allRenderers: IRenderer[],
|
private allRenderers: IRenderer[],
|
||||||
private eventRenderer: EventRenderer,
|
private eventRenderer: EventRenderer,
|
||||||
private scheduleRenderer: ScheduleRenderer,
|
private scheduleRenderer: ScheduleRenderer,
|
||||||
private headerDrawerRenderer: HeaderDrawerRenderer
|
private headerDrawerRenderer: HeaderDrawerRenderer,
|
||||||
|
private dateService: DateService
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async render(viewConfig: ViewConfig, container: HTMLElement): Promise<void> {
|
async render(viewConfig: ViewConfig, container: HTMLElement): Promise<void> {
|
||||||
|
|
@ -26,6 +29,12 @@ export class CalendarOrchestrator {
|
||||||
filter[grouping.type] = grouping.values;
|
filter[grouping.type] = grouping.values;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Byg FilterTemplate fra viewConfig groupings
|
||||||
|
const filterTemplate = new FilterTemplate(this.dateService);
|
||||||
|
for (const grouping of viewConfig.groupings) {
|
||||||
|
filterTemplate.addField(grouping.idProperty, grouping.derivedFrom);
|
||||||
|
}
|
||||||
|
|
||||||
const context: IRenderContext = { headerContainer, columnContainer, filter };
|
const context: IRenderContext = { headerContainer, columnContainer, filter };
|
||||||
|
|
||||||
// Clear
|
// Clear
|
||||||
|
|
@ -50,11 +59,11 @@ export class CalendarOrchestrator {
|
||||||
// Render schedule unavailable zones (før events)
|
// Render schedule unavailable zones (før events)
|
||||||
await this.scheduleRenderer.render(container, filter);
|
await this.scheduleRenderer.render(container, filter);
|
||||||
|
|
||||||
// Render timed events in grid
|
// Render timed events in grid (med filterTemplate til matching)
|
||||||
await this.eventRenderer.render(container, filter);
|
await this.eventRenderer.render(container, filter, filterTemplate);
|
||||||
|
|
||||||
// Render allDay events in header drawer
|
// Render allDay events in header drawer (med filterTemplate til matching)
|
||||||
await this.headerDrawerRenderer.render(container, filter);
|
await this.headerDrawerRenderer.render(container, filter, filterTemplate);
|
||||||
}
|
}
|
||||||
|
|
||||||
private selectRenderers(viewConfig: ViewConfig): IRenderer[] {
|
private selectRenderers(viewConfig: ViewConfig): IRenderer[] {
|
||||||
|
|
|
||||||
|
|
@ -11,9 +11,26 @@ dayjs.extend(isoWeek);
|
||||||
|
|
||||||
export class DateService {
|
export class DateService {
|
||||||
private timezone: string;
|
private timezone: string;
|
||||||
|
private baseDate: dayjs.Dayjs;
|
||||||
|
|
||||||
constructor(private config: ITimeFormatConfig) {
|
constructor(private config: ITimeFormatConfig, baseDate?: Date) {
|
||||||
this.timezone = config.timezone;
|
this.timezone = config.timezone;
|
||||||
|
// Allow setting a fixed base date for demo/testing purposes
|
||||||
|
this.baseDate = baseDate ? dayjs(baseDate) : dayjs();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a fixed base date (useful for demos with static mock data)
|
||||||
|
*/
|
||||||
|
setBaseDate(date: Date): void {
|
||||||
|
this.baseDate = dayjs(date);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current base date (either fixed or today)
|
||||||
|
*/
|
||||||
|
getBaseDate(): Date {
|
||||||
|
return this.baseDate.toDate();
|
||||||
}
|
}
|
||||||
|
|
||||||
parseISO(isoString: string): Date {
|
parseISO(isoString: string): Date {
|
||||||
|
|
@ -25,7 +42,7 @@ export class DateService {
|
||||||
}
|
}
|
||||||
|
|
||||||
getWeekDates(offset = 0, days = 7): string[] {
|
getWeekDates(offset = 0, days = 7): string[] {
|
||||||
const monday = dayjs().startOf('week').add(1, 'day').add(offset, 'week');
|
const monday = this.baseDate.startOf('week').add(1, 'day').add(offset, 'week');
|
||||||
return Array.from({ length: days }, (_, i) =>
|
return Array.from({ length: days }, (_, i) =>
|
||||||
monday.add(i, 'day').format('YYYY-MM-DD')
|
monday.add(i, 'day').format('YYYY-MM-DD')
|
||||||
);
|
);
|
||||||
|
|
|
||||||
75
src/v2/core/FilterTemplate.ts
Normal file
75
src/v2/core/FilterTemplate.ts
Normal file
|
|
@ -0,0 +1,75 @@
|
||||||
|
import { ICalendarEvent } from '../types/CalendarTypes';
|
||||||
|
import { DateService } from './DateService';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Field definition for FilterTemplate
|
||||||
|
*/
|
||||||
|
interface IFilterField {
|
||||||
|
idProperty: string;
|
||||||
|
derivedFrom?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FilterTemplate - Bygger nøgler til event-kolonne matching
|
||||||
|
*
|
||||||
|
* ViewConfig definerer hvilke felter (idProperties) der indgår i kolonnens nøgle.
|
||||||
|
* Samme template bruges til at bygge nøgle for både kolonne og event.
|
||||||
|
*
|
||||||
|
* Princip: Kolonnens nøgle-template bestemmer hvad der matches på.
|
||||||
|
*
|
||||||
|
* @see docs/filter-template.md
|
||||||
|
*/
|
||||||
|
export class FilterTemplate {
|
||||||
|
private fields: IFilterField[] = [];
|
||||||
|
|
||||||
|
constructor(private dateService: DateService) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tilføj felt til template
|
||||||
|
* @param idProperty - Property-navn (bruges på både event og column.dataset)
|
||||||
|
* @param derivedFrom - Hvis feltet udledes fra anden property (f.eks. date fra start)
|
||||||
|
*/
|
||||||
|
addField(idProperty: string, derivedFrom?: string): this {
|
||||||
|
this.fields.push({ idProperty, derivedFrom });
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Byg nøgle fra kolonne
|
||||||
|
* Læser værdier fra column.dataset[idProperty]
|
||||||
|
*/
|
||||||
|
buildKeyFromColumn(column: HTMLElement): string {
|
||||||
|
return this.fields
|
||||||
|
.map(f => column.dataset[f.idProperty] || '')
|
||||||
|
.join(':');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Byg nøgle fra event
|
||||||
|
* Læser værdier fra event[idProperty] eller udleder fra derivedFrom
|
||||||
|
*/
|
||||||
|
buildKeyFromEvent(event: ICalendarEvent): string {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
const eventRecord = event as any;
|
||||||
|
return this.fields
|
||||||
|
.map(f => {
|
||||||
|
if (f.derivedFrom) {
|
||||||
|
// Udled værdi (f.eks. date fra start)
|
||||||
|
const sourceValue = eventRecord[f.derivedFrom];
|
||||||
|
if (sourceValue instanceof Date) {
|
||||||
|
return this.dateService.getDateKey(sourceValue);
|
||||||
|
}
|
||||||
|
return String(sourceValue || '');
|
||||||
|
}
|
||||||
|
return String(eventRecord[f.idProperty] || '');
|
||||||
|
})
|
||||||
|
.join(':');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Match event mod kolonne
|
||||||
|
*/
|
||||||
|
matches(event: ICalendarEvent, column: HTMLElement): boolean {
|
||||||
|
return this.buildKeyFromEvent(event) === this.buildKeyFromColumn(column);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -12,5 +12,7 @@ export interface ViewConfig {
|
||||||
export interface GroupingConfig {
|
export interface GroupingConfig {
|
||||||
type: string;
|
type: string;
|
||||||
values: string[];
|
values: string[];
|
||||||
|
idProperty: string; // Property-navn på event (f.eks. 'resourceId', 'teamId')
|
||||||
|
derivedFrom?: string; // Hvis feltet udledes fra anden property (f.eks. 'date' fra 'start')
|
||||||
parentKey?: string;
|
parentKey?: string;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,9 @@ export class DemoApp {
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async init(): Promise<void> {
|
async init(): Promise<void> {
|
||||||
|
// Set base date to match mock data (8. december 2025 = mandag)
|
||||||
|
this.dateService.setBaseDate(new Date('2025-12-08'));
|
||||||
|
|
||||||
// Initialize IndexedDB
|
// Initialize IndexedDB
|
||||||
await this.indexedDBContext.initialize();
|
await this.indexedDBContext.initialize();
|
||||||
console.log('[DemoApp] IndexedDB initialized');
|
console.log('[DemoApp] IndexedDB initialized');
|
||||||
|
|
@ -95,8 +98,8 @@ export class DemoApp {
|
||||||
return {
|
return {
|
||||||
templateId: 'day',
|
templateId: 'day',
|
||||||
groupings: [
|
groupings: [
|
||||||
{ type: 'resource', values: ['EMP001', 'EMP002'] },
|
{ type: 'resource', values: ['EMP001', 'EMP002'], idProperty: 'resourceId' },
|
||||||
{ type: 'date', values: today }
|
{ type: 'date', values: today, idProperty: 'date', derivedFrom: 'start' }
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -104,7 +107,7 @@ export class DemoApp {
|
||||||
return {
|
return {
|
||||||
templateId: 'simple',
|
templateId: 'simple',
|
||||||
groupings: [
|
groupings: [
|
||||||
{ type: 'date', values: dates }
|
{ type: 'date', values: dates, idProperty: 'date', derivedFrom: 'start' }
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -112,8 +115,8 @@ export class DemoApp {
|
||||||
return {
|
return {
|
||||||
templateId: 'resource',
|
templateId: 'resource',
|
||||||
groupings: [
|
groupings: [
|
||||||
{ type: 'resource', values: ['EMP001', 'EMP002'] },
|
{ type: 'resource', values: ['EMP001', 'EMP002'], idProperty: 'resourceId' },
|
||||||
{ type: 'date', values: dates }
|
{ type: 'date', values: dates, idProperty: 'date', derivedFrom: 'start' }
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -121,9 +124,9 @@ export class DemoApp {
|
||||||
return {
|
return {
|
||||||
templateId: 'team',
|
templateId: 'team',
|
||||||
groupings: [
|
groupings: [
|
||||||
{ type: 'team', values: ['team1', 'team2'] },
|
{ type: 'team', values: ['team1', 'team2'], idProperty: 'teamId' },
|
||||||
{ type: 'resource', values: ['res1', 'res2', 'res3'] },
|
{ type: 'resource', values: ['res1', 'res2', 'res3'], idProperty: 'resourceId' },
|
||||||
{ type: 'date', values: dates }
|
{ type: 'date', values: dates, idProperty: 'date', derivedFrom: 'start' }
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import { CoreEvents } from '../../constants/CoreEvents';
|
||||||
import { IDragColumnChangePayload, IDragMovePayload, IDragEndPayload, IDragLeaveHeaderPayload } from '../../types/DragTypes';
|
import { IDragColumnChangePayload, IDragMovePayload, IDragEndPayload, IDragLeaveHeaderPayload } from '../../types/DragTypes';
|
||||||
import { calculateColumnLayout } from './EventLayoutEngine';
|
import { calculateColumnLayout } from './EventLayoutEngine';
|
||||||
import { IGridGroupLayout } from './EventLayoutTypes';
|
import { IGridGroupLayout } from './EventLayoutTypes';
|
||||||
|
import { FilterTemplate } from '../../core/FilterTemplate';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* EventRenderer - Renders calendar events to the DOM
|
* EventRenderer - Renders calendar events to the DOM
|
||||||
|
|
@ -238,8 +239,9 @@ export class EventRenderer {
|
||||||
* Render events for visible dates into day columns
|
* Render events for visible dates into day columns
|
||||||
* @param container - Calendar container element
|
* @param container - Calendar container element
|
||||||
* @param filter - Filter with 'date' and optionally 'resource' arrays
|
* @param filter - Filter with 'date' and optionally 'resource' arrays
|
||||||
|
* @param filterTemplate - Template for matching events to columns
|
||||||
*/
|
*/
|
||||||
async render(container: HTMLElement, filter: Record<string, string[]>): Promise<void> {
|
async render(container: HTMLElement, filter: Record<string, string[]>, filterTemplate: FilterTemplate): Promise<void> {
|
||||||
// Store container reference for later re-renders
|
// Store container reference for later re-renders
|
||||||
this.container = container;
|
this.container = container;
|
||||||
|
|
||||||
|
|
@ -261,26 +263,12 @@ export class EventRenderer {
|
||||||
|
|
||||||
const columns = dayColumns.querySelectorAll('swp-day-column');
|
const columns = dayColumns.querySelectorAll('swp-day-column');
|
||||||
|
|
||||||
// Render events into each column based on data attributes
|
// Render events into each column based on FilterTemplate matching
|
||||||
columns.forEach(column => {
|
columns.forEach(column => {
|
||||||
const date = (column as HTMLElement).dataset.date;
|
const columnEl = column as HTMLElement;
|
||||||
const columnResourceId = (column as HTMLElement).dataset.resourceId;
|
|
||||||
|
|
||||||
if (!date) return;
|
// Use FilterTemplate for matching - only fields in template are checked
|
||||||
|
const columnEvents = events.filter(event => filterTemplate.matches(event, columnEl));
|
||||||
// Filter events for this column
|
|
||||||
const columnEvents = events.filter(event => {
|
|
||||||
// Must match date
|
|
||||||
if (this.dateService.getDateKey(event.start) !== date) return false;
|
|
||||||
|
|
||||||
// If column has resourceId, event must match
|
|
||||||
if (columnResourceId && event.resourceId !== columnResourceId) return false;
|
|
||||||
|
|
||||||
// If no resourceId on column but resources in filter, show all
|
|
||||||
// (this handles 'simple' view without resources)
|
|
||||||
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Get or create events layer
|
// Get or create events layer
|
||||||
let eventsLayer = column.querySelector('swp-events-layer');
|
let eventsLayer = column.querySelector('swp-events-layer');
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import { CoreEvents } from '../../constants/CoreEvents';
|
||||||
import { HeaderDrawerManager } from '../../core/HeaderDrawerManager';
|
import { HeaderDrawerManager } from '../../core/HeaderDrawerManager';
|
||||||
import { EventService } from '../../storage/events/EventService';
|
import { EventService } from '../../storage/events/EventService';
|
||||||
import { DateService } from '../../core/DateService';
|
import { DateService } from '../../core/DateService';
|
||||||
|
import { FilterTemplate } from '../../core/FilterTemplate';
|
||||||
import {
|
import {
|
||||||
IDragEnterHeaderPayload,
|
IDragEnterHeaderPayload,
|
||||||
IDragMoveHeaderPayload,
|
IDragMoveHeaderPayload,
|
||||||
|
|
@ -36,6 +37,7 @@ export class HeaderDrawerRenderer {
|
||||||
private container: HTMLElement | null = null;
|
private container: HTMLElement | null = null;
|
||||||
private sourceElement: HTMLElement | null = null;
|
private sourceElement: HTMLElement | null = null;
|
||||||
private wasExpandedBeforeDrag = false;
|
private wasExpandedBeforeDrag = false;
|
||||||
|
private filterTemplate: FilterTemplate | null = null;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private eventBus: IEventBus,
|
private eventBus: IEventBus,
|
||||||
|
|
@ -49,8 +51,12 @@ export class HeaderDrawerRenderer {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render allDay events into the header drawer with row stacking
|
* Render allDay events into the header drawer with row stacking
|
||||||
|
* @param filterTemplate - Template for matching events to columns
|
||||||
*/
|
*/
|
||||||
async render(container: HTMLElement, filter: Record<string, string[]>): Promise<void> {
|
async render(container: HTMLElement, filter: Record<string, string[]>, filterTemplate: FilterTemplate): Promise<void> {
|
||||||
|
// Store filterTemplate for buildColumnKeyFromEvent
|
||||||
|
this.filterTemplate = filterTemplate;
|
||||||
|
|
||||||
const drawer = container.querySelector('swp-header-drawer');
|
const drawer = container.querySelector('swp-header-drawer');
|
||||||
if (!drawer) return;
|
if (!drawer) return;
|
||||||
|
|
||||||
|
|
@ -150,14 +156,24 @@ export class HeaderDrawerRenderer {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build columnKey from event fields
|
* Build columnKey from event using FilterTemplate
|
||||||
* This is the only place we construct columnKey from event data
|
* Uses the same template that columns use for matching
|
||||||
*/
|
*/
|
||||||
private buildColumnKeyFromEvent(event: ICalendarEvent, date?: Date): string {
|
private buildColumnKeyFromEvent(event: ICalendarEvent, date?: Date): string {
|
||||||
const dateStr = this.dateService.getDateKey(date || event.start);
|
if (!this.filterTemplate) {
|
||||||
const segments: Record<string, string> = { date: dateStr };
|
// Fallback if no template - shouldn't happen in normal flow
|
||||||
if (event.resourceId) segments.resource = event.resourceId;
|
const dateStr = this.dateService.getDateKey(date || event.start);
|
||||||
return this.dateService.buildColumnKey(segments);
|
return dateStr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For multi-day events, we need to override the date in the event
|
||||||
|
if (date && date.getTime() !== event.start.getTime()) {
|
||||||
|
// Create temporary event with overridden start for key generation
|
||||||
|
const tempEvent = { ...event, start: date };
|
||||||
|
return this.filterTemplate.buildKeyFromEvent(tempEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.filterTemplate.buildKeyFromEvent(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue