Improves all-day event drag and drop

Addresses issues with all-day event duration calculation and positioning during drag and drop.

- Uses `differenceInCalendarDays` to correctly calculate event duration across timezone and DST boundaries.
- Preserves the original event time when moving events to a different day.
- Snaps dragged event to the top of the target time slot.
This commit is contained in:
Janus C. H. Knudsen 2025-10-03 19:09:44 +02:00
parent 53cf097a47
commit 4fea01c76b
3 changed files with 26 additions and 5 deletions

View file

@ -17,6 +17,7 @@ import {
import { DragOffset, MousePosition } from '../types/DragDropTypes'; import { DragOffset, MousePosition } from '../types/DragDropTypes';
import { CoreEvents } from '../constants/CoreEvents'; import { CoreEvents } from '../constants/CoreEvents';
import { EventManager } from './EventManager'; import { EventManager } from './EventManager';
import { differenceInCalendarDays } from 'date-fns';
/** /**
* AllDayManager - Handles all-day row height animations and management * AllDayManager - Handles all-day row height animations and management
@ -379,7 +380,9 @@ export class AllDayManager {
throw new Error('Ugyldig start eller slut-dato i dataset'); throw new Error('Ugyldig start eller slut-dato i dataset');
} }
return Math.round((endDate.getTime() - startDate.getTime()) / (1000 * 60 * 60 * 24)); // Use differenceInCalendarDays for proper calendar day calculation
// This correctly handles timezone differences and DST changes
return differenceInCalendarDays(endDate, startDate);
}; };
if (dragEndEvent.draggedClone == null) if (dragEndEvent.draggedClone == null)
@ -403,10 +406,23 @@ export class AllDayManager {
const durationDays = getEventDurationDays(dragEndEvent.draggedClone.dataset.start, dragEndEvent.draggedClone.dataset.end); const durationDays = getEventDurationDays(dragEndEvent.draggedClone.dataset.start, dragEndEvent.draggedClone.dataset.end);
const newStartDate = new Date(eventDate);
const newEndDate = new Date(newStartDate);
newEndDate.setDate(newEndDate.getDate() + durationDays);
// Get original dates to preserve time
const originalStartDate = new Date(dragEndEvent.draggedClone.dataset.start!);
const originalEndDate = new Date(dragEndEvent.draggedClone.dataset.end!);
// Create new start date with the new day but preserve original time
const newStartDate = new Date(eventDate);
newStartDate.setHours(originalStartDate.getHours(), originalStartDate.getMinutes(), originalStartDate.getSeconds(), originalStartDate.getMilliseconds());
// Create new end date with the new day + duration, preserving original end time
const newEndDate = new Date(eventDate);
newEndDate.setDate(newEndDate.getDate() + durationDays);
newEndDate.setHours(originalEndDate.getHours(), originalEndDate.getMinutes(), originalEndDate.getSeconds(), originalEndDate.getMilliseconds());
// Update data attributes with new dates
dragEndEvent.draggedClone.dataset.start = newStartDate.toISOString();
dragEndEvent.draggedClone.dataset.end = newEndDate.toISOString();
const droppedEvent: CalendarEvent = { const droppedEvent: CalendarEvent = {
id: eventId, id: eventId,

View file

@ -367,7 +367,11 @@ export class DragDropManager {
* Optimized snap position calculation using PositionUtils * Optimized snap position calculation using PositionUtils
*/ */
private calculateSnapPosition(mouseY: number, column: ColumnBounds): number { private calculateSnapPosition(mouseY: number, column: ColumnBounds): number {
const snappedY = PositionUtils.getPositionFromCoordinate(mouseY, column); // Calculate where the event top would be (accounting for mouse offset)
const eventTopY = mouseY - this.mouseOffset.y;
// Snap the event top position, not the mouse position
const snappedY = PositionUtils.getPositionFromCoordinate(eventTopY, column);
return Math.max(0, snappedY); return Math.max(0, snappedY);
} }

View file

@ -239,8 +239,9 @@ export class DateEventRenderer implements EventRendererStrategy {
public handleDragMove(payload: DragMoveEventPayload): void { public handleDragMove(payload: DragMoveEventPayload): void {
if (!this.draggedClone) return; if (!this.draggedClone) return;
// Update position // Update position - snappedY is already the event top position
this.draggedClone.style.top = (payload.snappedY - payload.mouseOffset.y) + 'px'; // Add +1px to match the initial positioning offset from SwpEventElement
this.draggedClone.style.top = (payload.snappedY + 1) + 'px';
// Update timestamp display // Update timestamp display
this.updateCloneTimestamp(payload); this.updateCloneTimestamp(payload);