Refactor column key handling and event positioning
Introduces more robust column key management across renderers and drag/resize operations Decouples column key parsing from date extraction Simplifies event positioning logic Improves multi-resource view compatibility
This commit is contained in:
parent
0eb3bacb41
commit
c2f7564f8e
7 changed files with 81 additions and 71 deletions
|
|
@ -131,12 +131,15 @@ export class EventRenderer {
|
||||||
const column = this.findColumn(columnKey);
|
const column = this.findColumn(columnKey);
|
||||||
if (!column) return;
|
if (!column) return;
|
||||||
|
|
||||||
// Parse dateKey and resourceId from columnKey
|
// Read date and resourceId directly from column attributes (columnKey is opaque)
|
||||||
const { date: dateKey, resource: resourceId } = this.dateService.parseColumnKey(columnKey);
|
const date = column.dataset.date;
|
||||||
|
const resourceId = column.dataset.resourceId;
|
||||||
|
|
||||||
|
if (!date) return;
|
||||||
|
|
||||||
// Get date range for this day
|
// Get date range for this day
|
||||||
const startDate = new Date(dateKey);
|
const startDate = new Date(date);
|
||||||
const endDate = new Date(dateKey);
|
const endDate = new Date(date);
|
||||||
endDate.setHours(23, 59, 59, 999);
|
endDate.setHours(23, 59, 59, 999);
|
||||||
|
|
||||||
// Fetch events from IndexedDB
|
// Fetch events from IndexedDB
|
||||||
|
|
@ -144,9 +147,9 @@ export class EventRenderer {
|
||||||
? await this.eventService.getByResourceAndDateRange(resourceId, startDate, endDate)
|
? await this.eventService.getByResourceAndDateRange(resourceId, startDate, endDate)
|
||||||
: await this.eventService.getByDateRange(startDate, endDate);
|
: await this.eventService.getByDateRange(startDate, endDate);
|
||||||
|
|
||||||
// Filter to timed events and match dateKey exactly
|
// Filter to timed events and match date exactly
|
||||||
const timedEvents = events.filter(event =>
|
const timedEvents = events.filter(event =>
|
||||||
!event.allDay && this.dateService.getDateKey(event.start) === dateKey
|
!event.allDay && this.dateService.getDateKey(event.start) === date
|
||||||
);
|
);
|
||||||
|
|
||||||
// Get or create events layer
|
// Get or create events layer
|
||||||
|
|
@ -260,15 +263,15 @@ export class EventRenderer {
|
||||||
|
|
||||||
// Render events into each column based on data attributes
|
// Render events into each column based on data attributes
|
||||||
columns.forEach(column => {
|
columns.forEach(column => {
|
||||||
const dateKey = (column as HTMLElement).dataset.date;
|
const date = (column as HTMLElement).dataset.date;
|
||||||
const columnResourceId = (column as HTMLElement).dataset.resourceId;
|
const columnResourceId = (column as HTMLElement).dataset.resourceId;
|
||||||
|
|
||||||
if (!dateKey) return;
|
if (!date) return;
|
||||||
|
|
||||||
// Filter events for this column
|
// Filter events for this column
|
||||||
const columnEvents = events.filter(event => {
|
const columnEvents = events.filter(event => {
|
||||||
// Must match date
|
// Must match date
|
||||||
if (this.dateService.getDateKey(event.start) !== dateKey) return false;
|
if (this.dateService.getDateKey(event.start) !== date) return false;
|
||||||
|
|
||||||
// If column has resourceId, event must match
|
// If column has resourceId, event must match
|
||||||
if (columnResourceId && event.resourceId !== columnResourceId) return false;
|
if (columnResourceId && event.resourceId !== columnResourceId) return false;
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ import {
|
||||||
*/
|
*/
|
||||||
interface IHeaderItemLayout {
|
interface IHeaderItemLayout {
|
||||||
event: ICalendarEvent;
|
event: ICalendarEvent;
|
||||||
|
columnKey: string; // Opaque column identifier
|
||||||
row: number; // 1-indexed
|
row: number; // 1-indexed
|
||||||
colStart: number; // 1-indexed
|
colStart: number; // 1-indexed
|
||||||
colEnd: number; // exclusive
|
colEnd: number; // exclusive
|
||||||
|
|
@ -56,6 +57,10 @@ export class HeaderDrawerRenderer {
|
||||||
const visibleDates = filter['date'] || [];
|
const visibleDates = filter['date'] || [];
|
||||||
if (visibleDates.length === 0) return;
|
if (visibleDates.length === 0) return;
|
||||||
|
|
||||||
|
// Get column keys from DOM for correct multi-resource positioning
|
||||||
|
const visibleColumnKeys = this.getVisibleColumnKeysFromDOM();
|
||||||
|
if (visibleColumnKeys.length === 0) return;
|
||||||
|
|
||||||
// Fetch events for date range
|
// Fetch events for date range
|
||||||
const startDate = new Date(visibleDates[0]);
|
const startDate = new Date(visibleDates[0]);
|
||||||
const endDate = new Date(visibleDates[visibleDates.length - 1]);
|
const endDate = new Date(visibleDates[visibleDates.length - 1]);
|
||||||
|
|
@ -71,8 +76,8 @@ export class HeaderDrawerRenderer {
|
||||||
|
|
||||||
if (allDayEvents.length === 0) return;
|
if (allDayEvents.length === 0) return;
|
||||||
|
|
||||||
// Calculate layout with row stacking
|
// Calculate layout with row stacking using columnKeys
|
||||||
const layouts = this.calculateLayout(allDayEvents, visibleDates);
|
const layouts = this.calculateLayout(allDayEvents, visibleColumnKeys);
|
||||||
const rowCount = Math.max(1, ...layouts.map(l => l.row));
|
const rowCount = Math.max(1, ...layouts.map(l => l.row));
|
||||||
|
|
||||||
// Render each item with layout
|
// Render each item with layout
|
||||||
|
|
@ -89,13 +94,14 @@ export class HeaderDrawerRenderer {
|
||||||
* Create a header item element from layout
|
* Create a header item element from layout
|
||||||
*/
|
*/
|
||||||
private createHeaderItem(layout: IHeaderItemLayout): HTMLElement {
|
private createHeaderItem(layout: IHeaderItemLayout): HTMLElement {
|
||||||
const { event, row, colStart, colEnd } = layout;
|
const { event, columnKey, row, colStart, colEnd } = layout;
|
||||||
|
|
||||||
const item = document.createElement('swp-header-item');
|
const item = document.createElement('swp-header-item');
|
||||||
item.dataset.eventId = event.id;
|
item.dataset.eventId = event.id;
|
||||||
item.dataset.itemType = 'event';
|
item.dataset.itemType = 'event';
|
||||||
item.dataset.start = event.start.toISOString();
|
item.dataset.start = event.start.toISOString();
|
||||||
item.dataset.end = event.end.toISOString();
|
item.dataset.end = event.end.toISOString();
|
||||||
|
item.dataset.columnKey = columnKey;
|
||||||
item.textContent = event.title;
|
item.textContent = event.title;
|
||||||
|
|
||||||
// Color class
|
// Color class
|
||||||
|
|
@ -112,19 +118,22 @@ export class HeaderDrawerRenderer {
|
||||||
* Calculate layout for all events with row stacking
|
* Calculate layout for all events with row stacking
|
||||||
* Uses track-based algorithm to find available rows for overlapping events
|
* Uses track-based algorithm to find available rows for overlapping events
|
||||||
*/
|
*/
|
||||||
private calculateLayout(events: ICalendarEvent[], visibleDates: string[]): IHeaderItemLayout[] {
|
private calculateLayout(events: ICalendarEvent[], visibleColumnKeys: string[]): IHeaderItemLayout[] {
|
||||||
// tracks[row][col] = occupied
|
// tracks[row][col] = occupied
|
||||||
const tracks: boolean[][] = [new Array(visibleDates.length).fill(false)];
|
const tracks: boolean[][] = [new Array(visibleColumnKeys.length).fill(false)];
|
||||||
const layouts: IHeaderItemLayout[] = [];
|
const layouts: IHeaderItemLayout[] = [];
|
||||||
|
|
||||||
for (const event of events) {
|
for (const event of events) {
|
||||||
const startCol = this.getColIndex(event.start, visibleDates);
|
// Build columnKey from event fields (only place we need to construct it)
|
||||||
const endCol = this.getColIndex(event.end, visibleDates);
|
const columnKey = this.buildColumnKeyFromEvent(event);
|
||||||
|
const startCol = visibleColumnKeys.indexOf(columnKey);
|
||||||
|
const endColumnKey = this.buildColumnKeyFromEvent(event, event.end);
|
||||||
|
const endCol = visibleColumnKeys.indexOf(endColumnKey);
|
||||||
if (startCol === -1 && endCol === -1) continue;
|
if (startCol === -1 && endCol === -1) continue;
|
||||||
|
|
||||||
// Clamp til synlige kolonner
|
// Clamp til synlige kolonner
|
||||||
const colStart = Math.max(0, startCol);
|
const colStart = Math.max(0, startCol);
|
||||||
const colEnd = (endCol !== -1 ? endCol : visibleDates.length - 1) + 1;
|
const colEnd = (endCol !== -1 ? endCol : visibleColumnKeys.length - 1) + 1;
|
||||||
|
|
||||||
// Find ledig række
|
// Find ledig række
|
||||||
const row = this.findAvailableRow(tracks, colStart, colEnd);
|
const row = this.findAvailableRow(tracks, colStart, colEnd);
|
||||||
|
|
@ -134,12 +143,23 @@ export class HeaderDrawerRenderer {
|
||||||
tracks[row][c] = true;
|
tracks[row][c] = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
layouts.push({ event, row: row + 1, colStart: colStart + 1, colEnd: colEnd + 1 });
|
layouts.push({ event, columnKey, row: row + 1, colStart: colStart + 1, colEnd: colEnd + 1 });
|
||||||
}
|
}
|
||||||
|
|
||||||
return layouts;
|
return layouts;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build columnKey from event fields
|
||||||
|
* This is the only place we construct columnKey from event data
|
||||||
|
*/
|
||||||
|
private buildColumnKeyFromEvent(event: ICalendarEvent, date?: Date): string {
|
||||||
|
const dateStr = this.dateService.getDateKey(date || event.start);
|
||||||
|
const segments: Record<string, string> = { date: dateStr };
|
||||||
|
if (event.resourceId) segments.resource = event.resourceId;
|
||||||
|
return this.dateService.buildColumnKey(segments);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find available row for event spanning columns [colStart, colEnd)
|
* Find available row for event spanning columns [colStart, colEnd)
|
||||||
*/
|
*/
|
||||||
|
|
@ -156,14 +176,6 @@ export class HeaderDrawerRenderer {
|
||||||
return tracks.length - 1;
|
return tracks.length - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get column index for a date (0-indexed, -1 if not found)
|
|
||||||
*/
|
|
||||||
private getColIndex(date: Date, visibleDates: string[]): number {
|
|
||||||
const dateKey = this.dateService.getDateKey(date);
|
|
||||||
return visibleDates.indexOf(dateKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get color class based on event metadata or type
|
* Get color class based on event metadata or type
|
||||||
*/
|
*/
|
||||||
|
|
@ -233,15 +245,9 @@ export class HeaderDrawerRenderer {
|
||||||
item.dataset.eventId = payload.eventId;
|
item.dataset.eventId = payload.eventId;
|
||||||
item.dataset.itemType = payload.itemType;
|
item.dataset.itemType = payload.itemType;
|
||||||
item.dataset.duration = String(payload.duration);
|
item.dataset.duration = String(payload.duration);
|
||||||
|
item.dataset.columnKey = payload.sourceColumnKey;
|
||||||
item.textContent = payload.title;
|
item.textContent = payload.title;
|
||||||
|
|
||||||
// Set start/end as ISO dates (for recalculateDrawerLayout)
|
|
||||||
const startDate = new Date(payload.sourceDate);
|
|
||||||
const endDate = new Date(payload.sourceDate);
|
|
||||||
endDate.setDate(endDate.getDate() + payload.duration - 1);
|
|
||||||
item.dataset.start = startDate.toISOString();
|
|
||||||
item.dataset.end = endDate.toISOString();
|
|
||||||
|
|
||||||
// Apply color class if present
|
// Apply color class if present
|
||||||
if (payload.colorClass) {
|
if (payload.colorClass) {
|
||||||
item.classList.add(payload.colorClass);
|
item.classList.add(payload.colorClass);
|
||||||
|
|
@ -276,12 +282,8 @@ export class HeaderDrawerRenderer {
|
||||||
|
|
||||||
this.currentItem.style.gridArea = `1 / ${col} / 2 / ${endCol}`;
|
this.currentItem.style.gridArea = `1 / ${col} / 2 / ${endCol}`;
|
||||||
|
|
||||||
// Update start/end dates based on new position
|
// Update columnKey to new position
|
||||||
const startDate = new Date(payload.dateKey);
|
this.currentItem.dataset.columnKey = payload.columnKey;
|
||||||
const endDate = new Date(payload.dateKey);
|
|
||||||
endDate.setDate(endDate.getDate() + duration - 1);
|
|
||||||
this.currentItem.dataset.start = startDate.toISOString();
|
|
||||||
this.currentItem.dataset.end = endDate.toISOString();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -327,26 +329,27 @@ export class HeaderDrawerRenderer {
|
||||||
const items = Array.from(drawer.querySelectorAll('swp-header-item')) as HTMLElement[];
|
const items = Array.from(drawer.querySelectorAll('swp-header-item')) as HTMLElement[];
|
||||||
if (items.length === 0) return;
|
if (items.length === 0) return;
|
||||||
|
|
||||||
// Get visible dates from existing items
|
// Get visible column keys for correct multi-resource positioning
|
||||||
const visibleDates = this.getVisibleDatesFromDOM();
|
const visibleColumnKeys = this.getVisibleColumnKeysFromDOM();
|
||||||
if (visibleDates.length === 0) return;
|
if (visibleColumnKeys.length === 0) return;
|
||||||
|
|
||||||
// Build layout data from DOM items
|
// Build layout data from DOM items - use columnKey directly (opaque matching)
|
||||||
const itemData = items.map(item => ({
|
const itemData = items.map(item => ({
|
||||||
element: item,
|
element: item,
|
||||||
start: new Date(item.dataset.start || ''),
|
columnKey: item.dataset.columnKey || '',
|
||||||
end: new Date(item.dataset.end || '')
|
duration: parseInt(item.dataset.duration || '1', 10)
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Calculate new layout using track algorithm
|
// Calculate new layout using track algorithm
|
||||||
const tracks: boolean[][] = [new Array(visibleDates.length).fill(false)];
|
const tracks: boolean[][] = [new Array(visibleColumnKeys.length).fill(false)];
|
||||||
|
|
||||||
for (const item of itemData) {
|
for (const item of itemData) {
|
||||||
const startCol = this.getColIndex(item.start, visibleDates);
|
// Direct columnKey matching - no parsing or construction needed
|
||||||
const endCol = this.getColIndex(item.end, visibleDates);
|
const startCol = visibleColumnKeys.indexOf(item.columnKey);
|
||||||
|
if (startCol === -1) continue;
|
||||||
|
|
||||||
const colStart = Math.max(0, startCol);
|
const colStart = startCol;
|
||||||
const colEnd = (endCol !== -1 ? endCol : visibleDates.length - 1) + 1;
|
const colEnd = Math.min(startCol + item.duration, visibleColumnKeys.length);
|
||||||
|
|
||||||
const row = this.findAvailableRow(tracks, colStart, colEnd);
|
const row = this.findAvailableRow(tracks, colStart, colEnd);
|
||||||
|
|
||||||
|
|
@ -364,16 +367,16 @@ export class HeaderDrawerRenderer {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get visible dates from header columns in DOM
|
* Get visible column keys from DOM (preserves order for multi-resource views)
|
||||||
*/
|
*/
|
||||||
private getVisibleDatesFromDOM(): string[] {
|
private getVisibleColumnKeysFromDOM(): string[] {
|
||||||
const columns = document.querySelectorAll('swp-day-column');
|
const columns = document.querySelectorAll('swp-day-column');
|
||||||
const dates: string[] = [];
|
const columnKeys: string[] = [];
|
||||||
columns.forEach(col => {
|
columns.forEach(col => {
|
||||||
const date = (col as HTMLElement).dataset.date;
|
const columnKey = (col as HTMLElement).dataset.columnKey;
|
||||||
if (date && !dates.includes(date)) dates.push(date);
|
if (columnKey) columnKeys.push(columnKey);
|
||||||
});
|
});
|
||||||
return dates;
|
return columnKeys;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -36,10 +36,10 @@ export class ScheduleRenderer {
|
||||||
const columns = dayColumns.querySelectorAll('swp-day-column');
|
const columns = dayColumns.querySelectorAll('swp-day-column');
|
||||||
|
|
||||||
for (const column of columns) {
|
for (const column of columns) {
|
||||||
const dateKey = (column as HTMLElement).dataset.date;
|
const date = (column as HTMLElement).dataset.date;
|
||||||
const resourceId = (column as HTMLElement).dataset.resourceId;
|
const resourceId = (column as HTMLElement).dataset.resourceId;
|
||||||
|
|
||||||
if (!dateKey || !resourceId) continue;
|
if (!date || !resourceId) continue;
|
||||||
|
|
||||||
// Get or create unavailable layer
|
// Get or create unavailable layer
|
||||||
let unavailableLayer = column.querySelector('swp-unavailable-layer');
|
let unavailableLayer = column.querySelector('swp-unavailable-layer');
|
||||||
|
|
@ -52,7 +52,7 @@ export class ScheduleRenderer {
|
||||||
unavailableLayer.innerHTML = '';
|
unavailableLayer.innerHTML = '';
|
||||||
|
|
||||||
// Get schedule for this resource/date
|
// Get schedule for this resource/date
|
||||||
const schedule = await this.scheduleService.getScheduleForDate(resourceId, dateKey);
|
const schedule = await this.scheduleService.getScheduleForDate(resourceId, date);
|
||||||
|
|
||||||
// Render unavailable zones
|
// Render unavailable zones
|
||||||
this.renderUnavailableZones(unavailableLayer as HTMLElement, schedule);
|
this.renderUnavailableZones(unavailableLayer as HTMLElement, schedule);
|
||||||
|
|
|
||||||
|
|
@ -176,7 +176,8 @@ export class DragDropManager {
|
||||||
|
|
||||||
if (gridEvent) {
|
if (gridEvent) {
|
||||||
const columnKey = this.dragState.currentColumn.dataset.columnKey || '';
|
const columnKey = this.dragState.currentColumn.dataset.columnKey || '';
|
||||||
const swpEvent = SwpEvent.fromElement(gridEvent, columnKey, this.gridConfig);
|
const date = this.dragState.currentColumn.dataset.date || '';
|
||||||
|
const swpEvent = SwpEvent.fromElement(gridEvent, columnKey, date, this.gridConfig);
|
||||||
|
|
||||||
const payload: IDragEndPayload = {
|
const payload: IDragEndPayload = {
|
||||||
swpEvent,
|
swpEvent,
|
||||||
|
|
@ -203,13 +204,15 @@ export class DragDropManager {
|
||||||
// Remove ghost
|
// Remove ghost
|
||||||
this.dragState.ghostElement?.remove();
|
this.dragState.ghostElement?.remove();
|
||||||
|
|
||||||
// Get columnKey from target column
|
// Get columnKey and date from target column
|
||||||
const columnKey = this.dragState.columnElement.dataset.columnKey || '';
|
const columnKey = this.dragState.columnElement.dataset.columnKey || '';
|
||||||
|
const date = this.dragState.columnElement.dataset.date || '';
|
||||||
|
|
||||||
// Create SwpEvent from element (reads top/height/eventId from element)
|
// Create SwpEvent from element (reads top/height/eventId from element)
|
||||||
const swpEvent = SwpEvent.fromElement(
|
const swpEvent = SwpEvent.fromElement(
|
||||||
this.dragState.element,
|
this.dragState.element,
|
||||||
columnKey,
|
columnKey,
|
||||||
|
date,
|
||||||
this.gridConfig
|
this.gridConfig
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -406,7 +409,7 @@ export class DragDropManager {
|
||||||
eventId: this.dragState.eventId,
|
eventId: this.dragState.eventId,
|
||||||
element: this.dragState.element,
|
element: this.dragState.element,
|
||||||
sourceColumnIndex: this.getColumnIndex(this.dragState.columnElement),
|
sourceColumnIndex: this.getColumnIndex(this.dragState.columnElement),
|
||||||
sourceDate: this.dragState.columnElement.dataset.date || '',
|
sourceColumnKey: this.dragState.columnElement.dataset.columnKey || '',
|
||||||
title: this.dragState.element.querySelector('swp-event-title')?.textContent || '',
|
title: this.dragState.element.querySelector('swp-event-title')?.textContent || '',
|
||||||
colorClass: [...this.dragState.element.classList].find(c => c.startsWith('is-')),
|
colorClass: [...this.dragState.element.classList].find(c => c.startsWith('is-')),
|
||||||
itemType: 'event',
|
itemType: 'event',
|
||||||
|
|
@ -468,7 +471,7 @@ export class DragDropManager {
|
||||||
const payload: IDragMoveHeaderPayload = {
|
const payload: IDragMoveHeaderPayload = {
|
||||||
eventId: this.dragState.eventId,
|
eventId: this.dragState.eventId,
|
||||||
columnIndex: this.getColumnIndex(column),
|
columnIndex: this.getColumnIndex(column),
|
||||||
dateKey: column.dataset.date || ''
|
columnKey: column.dataset.columnKey || ''
|
||||||
};
|
};
|
||||||
|
|
||||||
this.eventBus.emit(CoreEvents.EVENT_DRAG_MOVE_HEADER, payload);
|
this.eventBus.emit(CoreEvents.EVENT_DRAG_MOVE_HEADER, payload);
|
||||||
|
|
|
||||||
|
|
@ -251,14 +251,16 @@ export class ResizeManager {
|
||||||
// Remove global resizing class
|
// Remove global resizing class
|
||||||
document.documentElement.classList.remove('swp--resizing');
|
document.documentElement.classList.remove('swp--resizing');
|
||||||
|
|
||||||
// Get columnKey from parent column
|
// Get columnKey and date from parent column
|
||||||
const column = this.resizeState.element.closest('swp-day-column') as HTMLElement;
|
const column = this.resizeState.element.closest('swp-day-column') as HTMLElement;
|
||||||
const columnKey = column?.dataset.columnKey || '';
|
const columnKey = column?.dataset.columnKey || '';
|
||||||
|
const date = column?.dataset.date || '';
|
||||||
|
|
||||||
// Create SwpEvent from element (reads top/height/eventId from element)
|
// Create SwpEvent from element (reads top/height/eventId from element)
|
||||||
const swpEvent = SwpEvent.fromElement(
|
const swpEvent = SwpEvent.fromElement(
|
||||||
this.resizeState.element,
|
this.resizeState.element,
|
||||||
columnKey,
|
columnKey,
|
||||||
|
date,
|
||||||
this.gridConfig
|
this.gridConfig
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,7 @@ export interface IDragEnterHeaderPayload {
|
||||||
eventId: string;
|
eventId: string;
|
||||||
element: HTMLElement; // Original dragged element
|
element: HTMLElement; // Original dragged element
|
||||||
sourceColumnIndex: number;
|
sourceColumnIndex: number;
|
||||||
sourceDate: string;
|
sourceColumnKey: string; // Opaque column identifier (for matching only)
|
||||||
title: string;
|
title: string;
|
||||||
colorClass?: string;
|
colorClass?: string;
|
||||||
itemType: 'event' | 'reminder';
|
itemType: 'event' | 'reminder';
|
||||||
|
|
@ -60,7 +60,7 @@ export interface IDragEnterHeaderPayload {
|
||||||
export interface IDragMoveHeaderPayload {
|
export interface IDragMoveHeaderPayload {
|
||||||
eventId: string;
|
eventId: string;
|
||||||
columnIndex: number;
|
columnIndex: number;
|
||||||
dateKey: string;
|
columnKey: string; // Opaque column identifier (for matching only)
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IDragLeaveHeaderPayload {
|
export interface IDragLeaveHeaderPayload {
|
||||||
|
|
|
||||||
|
|
@ -51,24 +51,23 @@ export class SwpEvent {
|
||||||
/**
|
/**
|
||||||
* Factory: Create SwpEvent from element + columnKey
|
* Factory: Create SwpEvent from element + columnKey
|
||||||
* Reads top/height from element.style to calculate start/end
|
* Reads top/height from element.style to calculate start/end
|
||||||
* @param columnKey - Uniform column identifier (e.g. "2025-12-09" or "2025-12-09:EMP001")
|
* @param columnKey - Opaque column identifier (do NOT parse - use only for matching)
|
||||||
|
* @param date - Date string (YYYY-MM-DD) for time calculations
|
||||||
*/
|
*/
|
||||||
static fromElement(
|
static fromElement(
|
||||||
element: HTMLElement,
|
element: HTMLElement,
|
||||||
columnKey: string,
|
columnKey: string,
|
||||||
|
date: string,
|
||||||
gridConfig: IGridConfig
|
gridConfig: IGridConfig
|
||||||
): SwpEvent {
|
): SwpEvent {
|
||||||
const topPixels = parseFloat(element.style.top) || 0;
|
const topPixels = parseFloat(element.style.top) || 0;
|
||||||
const heightPixels = parseFloat(element.style.height) || 0;
|
const heightPixels = parseFloat(element.style.height) || 0;
|
||||||
|
|
||||||
// Extract dateKey from columnKey (first segment)
|
|
||||||
const dateKey = columnKey.split(':')[0];
|
|
||||||
|
|
||||||
// Calculate start from top position
|
// Calculate start from top position
|
||||||
const startMinutesFromGrid = (topPixels / gridConfig.hourHeight) * 60;
|
const startMinutesFromGrid = (topPixels / gridConfig.hourHeight) * 60;
|
||||||
const totalMinutes = (gridConfig.dayStartHour * 60) + startMinutesFromGrid;
|
const totalMinutes = (gridConfig.dayStartHour * 60) + startMinutesFromGrid;
|
||||||
|
|
||||||
const start = new Date(dateKey);
|
const start = new Date(date);
|
||||||
start.setHours(Math.floor(totalMinutes / 60), totalMinutes % 60, 0, 0);
|
start.setHours(Math.floor(totalMinutes / 60), totalMinutes % 60, 0, 0);
|
||||||
|
|
||||||
// Calculate end from height
|
// Calculate end from height
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue