diff --git a/src/managers/AllDayManager.ts b/src/managers/AllDayManager.ts index 24047c1..c24b67b 100644 --- a/src/managers/AllDayManager.ts +++ b/src/managers/AllDayManager.ts @@ -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 { - - const getEventDurationDays = (start: string | undefined, end: string | undefined): number => { - - if (!start || !end) - throw new Error('Undefined start or end - date'); - - const startDate = new Date(start); - const endDate = new Date(end); - - if (isNaN(startDate.getTime()) || isNaN(endDate.getTime())) { - throw new Error('Ugyldig start eller slut-dato i dataset'); - } - - // Use differenceInCalendarDays for proper calendar day calculation - // This correctly handles timezone differences and DST changes - return differenceInCalendarDays(endDate, startDate); - }; - - if (dragEndEvent.draggedClone == null) - return; - - // 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 = { + /** + * Handle timed → all-day conversion on drop + */ + private async handleTimedToAllDayDrop(dragEndEvent: IDragEndEventPayload): Promise { + if (!dragEndEvent.draggedClone || !dragEndEvent.finalPosition.column) return; + + const clone = dragEndEvent.draggedClone as SwpAllDayEventElement; + const eventId = clone.eventId.replace('clone-', ''); + const targetDate = dragEndEvent.finalPosition.column.date; + + console.log('🔄 AllDayManager: Converting timed event to all-day', { eventId, targetDate }); + + // Create new dates preserving time + const newStart = new Date(targetDate); + newStart.setHours(clone.start.getHours(), clone.start.getMinutes(), 0, 0); + + const newEnd = new Date(targetDate); + newEnd.setHours(clone.end.getHours(), clone.end.getMinutes(), 0, 0); + + // Update event in repository + await this.eventManager.updateEvent(eventId, { + start: newStart, + end: newEnd, + allDay: true + }); + + // Remove original timed event + this.fadeOutAndRemove(dragEndEvent.originalElement); + + // 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' }; + + const updatedEvents = [...this.currentAllDayEvents, newEvent]; + const newLayouts = this.calculateAllDayEventsLayout(updatedEvents, this.currentWeekDates); + this.allDayEventRenderer.renderAllDayEventsForPeriod(newLayouts); + + // Animate height + this.checkAndAnimateAllDayHeight(); + } - // Use current events + dropped event for calculation - const tempEvents = [ - ...this.currentAllDayEvents.filter(event => event.id !== eventId), - droppedEvent - ]; - - // 4. Calculate new layouts for ALL events - const newLayouts = this.calculateAllDayEventsLayout(tempEvents, this.currentWeekDates); - - // 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); - - if (currentGridArea !== layout.gridArea) { - let element = container?.querySelector(`[data-event-id="${layout.calenderEvent.id}"]`) as HTMLElement; - if (element) { - - element.classList.add('transitioning'); - element.style.gridArea = layout.gridArea; - element.style.gridRow = layout.row.toString(); - element.style.gridColumn = `${layout.startColumn} / ${layout.endColumn + 1}`; - - element.classList.remove('max-event-overflow-hide'); - element.classList.remove('max-event-overflow-show'); - - 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'); - - // 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 + /** + * Handle all-day → all-day drop (moving within header) + */ + private async handleDragEnd(dragEndEvent: IDragEndEventPayload): Promise { + if (!dragEndEvent.draggedClone || !dragEndEvent.finalPosition.column) return; + + const clone = dragEndEvent.draggedClone as SwpAllDayEventElement; + const eventId = clone.eventId.replace('clone-', ''); + const targetDate = dragEndEvent.finalPosition.column.date; + + // Calculate duration in days + const durationDays = differenceInCalendarDays(clone.end, clone.start); + + // Create new dates preserving time + const newStart = new Date(targetDate); + newStart.setHours(clone.start.getHours(), clone.start.getMinutes(), 0, 0); + + const newEnd = new Date(targetDate); + newEnd.setDate(newEnd.getDate() + durationDays); + newEnd.setHours(clone.end.getHours(), clone.end.getMinutes(), 0, 0); + + // 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); - + + // 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(); - } /**