Refactors all-day event drag-and-drop handling
Improves event conversion logic between timed and all-day events during drag operations Simplifies event movement and conversion algorithms Adds specific handling for timed → all-day and all-day → all-day drops Enhances event repositioning and layout recalculation
This commit is contained in:
parent
9987873601
commit
03746afca4
1 changed files with 86 additions and 115 deletions
|
|
@ -116,12 +116,19 @@ export class AllDayManager {
|
|||
});
|
||||
|
||||
// Handle all-day → all-day drops (within header)
|
||||
if (dragEndPayload.target === 'swp-day-header') {
|
||||
if (dragEndPayload.target === 'swp-day-header' && dragEndPayload.originalElement?.hasAttribute('data-allday')) {
|
||||
console.log('✅ AllDayManager: Handling all-day → all-day drop');
|
||||
this.handleDragEnd(dragEndPayload);
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle timed → all-day conversion (dropped in header)
|
||||
if (dragEndPayload.target === 'swp-day-header' && !dragEndPayload.originalElement?.hasAttribute('data-allday')) {
|
||||
console.log('🔄 AllDayManager: Timed → all-day conversion on drop');
|
||||
this.handleTimedToAllDayDrop(dragEndPayload);
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle all-day → timed conversion (dropped in column)
|
||||
if (dragEndPayload.target === 'swp-day-column' && dragEndPayload.originalElement?.hasAttribute('data-allday')) {
|
||||
const eventId = dragEndPayload.originalElement.dataset.eventId;
|
||||
|
|
@ -474,130 +481,94 @@ export class AllDayManager {
|
|||
}
|
||||
|
||||
|
||||
private async handleDragEnd(dragEndEvent: IDragEndEventPayload): Promise<void> {
|
||||
/**
|
||||
* Handle timed → all-day conversion on drop
|
||||
*/
|
||||
private async handleTimedToAllDayDrop(dragEndEvent: IDragEndEventPayload): Promise<void> {
|
||||
if (!dragEndEvent.draggedClone || !dragEndEvent.finalPosition.column) return;
|
||||
|
||||
const getEventDurationDays = (start: string | undefined, end: string | undefined): number => {
|
||||
const clone = dragEndEvent.draggedClone as SwpAllDayEventElement;
|
||||
const eventId = clone.eventId.replace('clone-', '');
|
||||
const targetDate = dragEndEvent.finalPosition.column.date;
|
||||
|
||||
if (!start || !end)
|
||||
throw new Error('Undefined start or end - date');
|
||||
console.log('🔄 AllDayManager: Converting timed event to all-day', { eventId, targetDate });
|
||||
|
||||
const startDate = new Date(start);
|
||||
const endDate = new Date(end);
|
||||
// Create new dates preserving time
|
||||
const newStart = new Date(targetDate);
|
||||
newStart.setHours(clone.start.getHours(), clone.start.getMinutes(), 0, 0);
|
||||
|
||||
if (isNaN(startDate.getTime()) || isNaN(endDate.getTime())) {
|
||||
throw new Error('Ugyldig start eller slut-dato i dataset');
|
||||
}
|
||||
const newEnd = new Date(targetDate);
|
||||
newEnd.setHours(clone.end.getHours(), clone.end.getMinutes(), 0, 0);
|
||||
|
||||
// Use differenceInCalendarDays for proper calendar day calculation
|
||||
// This correctly handles timezone differences and DST changes
|
||||
return differenceInCalendarDays(endDate, startDate);
|
||||
};
|
||||
// Update event in repository
|
||||
await this.eventManager.updateEvent(eventId, {
|
||||
start: newStart,
|
||||
end: newEnd,
|
||||
allDay: true
|
||||
});
|
||||
|
||||
if (dragEndEvent.draggedClone == null)
|
||||
return;
|
||||
// Remove original timed event
|
||||
this.fadeOutAndRemove(dragEndEvent.originalElement);
|
||||
|
||||
// 2. Normalize clone ID
|
||||
dragEndEvent.draggedClone.dataset.eventId = dragEndEvent.draggedClone.dataset.eventId?.replace('clone-', '');
|
||||
dragEndEvent.draggedClone.style.pointerEvents = ''; // Re-enable pointer events
|
||||
dragEndEvent.originalElement.dataset.eventId += '_';
|
||||
|
||||
let eventId = dragEndEvent.draggedClone.dataset.eventId;
|
||||
let eventDate = dragEndEvent.finalPosition.column?.date;
|
||||
let eventType = dragEndEvent.draggedClone.dataset.type;
|
||||
|
||||
if (eventDate == null || eventId == null || eventType == null)
|
||||
return;
|
||||
|
||||
const durationDays = getEventDurationDays(dragEndEvent.draggedClone.dataset.start, dragEndEvent.draggedClone.dataset.end);
|
||||
|
||||
// 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 (convert to UTC)
|
||||
dragEndEvent.draggedClone.dataset.start = this.dateService.toUTC(newStartDate);
|
||||
dragEndEvent.draggedClone.dataset.end = this.dateService.toUTC(newEndDate);
|
||||
|
||||
const droppedEvent: ICalendarEvent = {
|
||||
// Add to current all-day events and recalculate layout
|
||||
const newEvent: ICalendarEvent = {
|
||||
id: eventId,
|
||||
title: dragEndEvent.draggedClone.dataset.title || '',
|
||||
start: newStartDate,
|
||||
end: newEndDate,
|
||||
type: eventType,
|
||||
title: clone.title,
|
||||
start: newStart,
|
||||
end: newEnd,
|
||||
type: clone.type,
|
||||
allDay: true,
|
||||
syncStatus: 'synced'
|
||||
};
|
||||
|
||||
// Use current events + dropped event for calculation
|
||||
const tempEvents = [
|
||||
...this.currentAllDayEvents.filter(event => event.id !== eventId),
|
||||
droppedEvent
|
||||
];
|
||||
const updatedEvents = [...this.currentAllDayEvents, newEvent];
|
||||
const newLayouts = this.calculateAllDayEventsLayout(updatedEvents, this.currentWeekDates);
|
||||
this.allDayEventRenderer.renderAllDayEventsForPeriod(newLayouts);
|
||||
|
||||
// 4. Calculate new layouts for ALL events
|
||||
const newLayouts = this.calculateAllDayEventsLayout(tempEvents, this.currentWeekDates);
|
||||
// Animate height
|
||||
this.checkAndAnimateAllDayHeight();
|
||||
}
|
||||
|
||||
// 5. Apply differential updates - compare with DOM instead of currentLayouts
|
||||
let container = this.getAllDayContainer();
|
||||
newLayouts.forEach((layout) => {
|
||||
// Get current gridArea from DOM
|
||||
const currentGridArea = this.getGridAreaFromDOM(layout.calenderEvent.id);
|
||||
/**
|
||||
* Handle all-day → all-day drop (moving within header)
|
||||
*/
|
||||
private async handleDragEnd(dragEndEvent: IDragEndEventPayload): Promise<void> {
|
||||
if (!dragEndEvent.draggedClone || !dragEndEvent.finalPosition.column) return;
|
||||
|
||||
if (currentGridArea !== layout.gridArea) {
|
||||
let element = container?.querySelector(`[data-event-id="${layout.calenderEvent.id}"]`) as HTMLElement;
|
||||
if (element) {
|
||||
const clone = dragEndEvent.draggedClone as SwpAllDayEventElement;
|
||||
const eventId = clone.eventId.replace('clone-', '');
|
||||
const targetDate = dragEndEvent.finalPosition.column.date;
|
||||
|
||||
element.classList.add('transitioning');
|
||||
element.style.gridArea = layout.gridArea;
|
||||
element.style.gridRow = layout.row.toString();
|
||||
element.style.gridColumn = `${layout.startColumn} / ${layout.endColumn + 1}`;
|
||||
// Calculate duration in days
|
||||
const durationDays = differenceInCalendarDays(clone.end, clone.start);
|
||||
|
||||
element.classList.remove('max-event-overflow-hide');
|
||||
element.classList.remove('max-event-overflow-show');
|
||||
// Create new dates preserving time
|
||||
const newStart = new Date(targetDate);
|
||||
newStart.setHours(clone.start.getHours(), clone.start.getMinutes(), 0, 0);
|
||||
|
||||
if (layout.row > ALL_DAY_CONSTANTS.MAX_COLLAPSED_ROWS)
|
||||
if (!this.isExpanded)
|
||||
element.classList.add('max-event-overflow-hide');
|
||||
else
|
||||
element.classList.add('max-event-overflow-show');
|
||||
const newEnd = new Date(targetDate);
|
||||
newEnd.setDate(newEnd.getDate() + durationDays);
|
||||
newEnd.setHours(clone.end.getHours(), clone.end.getMinutes(), 0, 0);
|
||||
|
||||
// Remove transition class after animation
|
||||
setTimeout(() => element.classList.remove('transitioning'), 200);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 6. Clean up drag styles from the dropped clone
|
||||
dragEndEvent.draggedClone.classList.remove('dragging');
|
||||
dragEndEvent.draggedClone.style.zIndex = '';
|
||||
dragEndEvent.draggedClone.style.cursor = '';
|
||||
dragEndEvent.draggedClone.style.opacity = '';
|
||||
|
||||
// 7. Apply highlight class to show the dropped event with highlight color
|
||||
dragEndEvent.draggedClone.classList.add('highlight');
|
||||
|
||||
// 8. CRITICAL FIX: Update event in repository to mark as allDay=true
|
||||
// This ensures EventManager's repository has correct state
|
||||
// Without this, the event will reappear in grid on re-render
|
||||
// Update event in repository
|
||||
await this.eventManager.updateEvent(eventId, {
|
||||
start: newStartDate,
|
||||
end: newEndDate,
|
||||
start: newStart,
|
||||
end: newEnd,
|
||||
allDay: true
|
||||
});
|
||||
|
||||
// Remove original and fade out
|
||||
this.fadeOutAndRemove(dragEndEvent.originalElement);
|
||||
|
||||
this.checkAndAnimateAllDayHeight();
|
||||
// Recalculate and re-render ALL events
|
||||
const updatedEvents = this.currentAllDayEvents.map(e =>
|
||||
e.id === eventId ? { ...e, start: newStart, end: newEnd } : e
|
||||
);
|
||||
const newLayouts = this.calculateAllDayEventsLayout(updatedEvents, this.currentWeekDates);
|
||||
this.allDayEventRenderer.renderAllDayEventsForPeriod(newLayouts);
|
||||
|
||||
// Animate height - this also handles overflow classes!
|
||||
this.checkAndAnimateAllDayHeight();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue