diff --git a/src/data/mock-events.json b/src/data/mock-events.json index ff7bb6f..904cf74 100644 --- a/src/data/mock-events.json +++ b/src/data/mock-events.json @@ -1208,5 +1208,475 @@ "allDay": false, "syncStatus": "synced", "metadata": { "duration": 120, "color": "#2196f3" } + }, + { + "id": "122", + "title": "Multi-Day Conference", + "start": "2025-09-22T00:00:00", + "end": "2025-09-24T23:59:59", + "type": "meeting", + "allDay": true, + "syncStatus": "synced", + "metadata": { "duration": 4320, "color": "#4caf50" } + }, + { + "id": "123", + "title": "Project Sprint", + "start": "2025-09-23T00:00:00", + "end": "2025-09-25T23:59:59", + "type": "work", + "allDay": true, + "syncStatus": "synced", + "metadata": { "duration": 4320, "color": "#2196f3" } + }, + { + "id": "124", + "title": "Training Week", + "start": "2025-09-29T00:00:00", + "end": "2025-10-03T23:59:59", + "type": "meeting", + "allDay": true, + "syncStatus": "synced", + "metadata": { "duration": 7200, "color": "#9c27b0" } + }, + { + "id": "125", + "title": "Holiday Weekend", + "start": "2025-10-04T00:00:00", + "end": "2025-10-06T23:59:59", + "type": "milestone", + "allDay": true, + "syncStatus": "synced", + "metadata": { "duration": 4320, "color": "#ff6f00" } + }, + { + "id": "126", + "title": "Client Visit", + "start": "2025-10-07T00:00:00", + "end": "2025-10-09T23:59:59", + "type": "meeting", + "allDay": true, + "syncStatus": "synced", + "metadata": { "duration": 4320, "color": "#e91e63" } + }, + { + "id": "127", + "title": "Development Marathon", + "start": "2025-10-13T00:00:00", + "end": "2025-10-15T23:59:59", + "type": "work", + "allDay": true, + "syncStatus": "synced", + "metadata": { "duration": 4320, "color": "#3f51b5" } + }, + { + "id": "128", + "title": "Morgen Standup", + "start": "2025-09-22T09:00:00", + "end": "2025-09-22T09:30:00", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { "duration": 30, "color": "#ff5722" } + }, + { + "id": "129", + "title": "Klient Præsentation", + "start": "2025-09-22T14:00:00", + "end": "2025-09-22T15:30:00", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { "duration": 90, "color": "#e91e63" } + }, + { + "id": "130", + "title": "Eftermiddags Kodning", + "start": "2025-09-22T16:00:00", + "end": "2025-09-22T18:00:00", + "type": "work", + "allDay": false, + "syncStatus": "synced", + "metadata": { "duration": 120, "color": "#2196f3" } + }, + { + "id": "131", + "title": "Team Standup", + "start": "2025-09-23T09:00:00", + "end": "2025-09-23T09:30:00", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { "duration": 30, "color": "#ff5722" } + }, + { + "id": "132", + "title": "Arkitektur Review", + "start": "2025-09-23T11:00:00", + "end": "2025-09-23T12:30:00", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { "duration": 90, "color": "#009688" } + }, + { + "id": "133", + "title": "Frokost & Læring", + "start": "2025-09-23T12:30:00", + "end": "2025-09-23T13:30:00", + "type": "meal", + "allDay": false, + "syncStatus": "synced", + "metadata": { "duration": 60, "color": "#ff9800" } + }, + { + "id": "134", + "title": "Team Standup", + "start": "2025-09-24T09:00:00", + "end": "2025-09-24T09:30:00", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { "duration": 30, "color": "#ff5722" } + }, + { + "id": "135", + "title": "Database Optimering", + "start": "2025-09-24T10:00:00", + "end": "2025-09-24T12:00:00", + "type": "work", + "allDay": false, + "syncStatus": "synced", + "metadata": { "duration": 120, "color": "#3f51b5" } + }, + { + "id": "136", + "title": "Klient Opkald", + "start": "2025-09-24T15:00:00", + "end": "2025-09-24T16:00:00", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { "duration": 60, "color": "#795548" } + }, + { + "id": "137", + "title": "Team Standup", + "start": "2025-09-25T09:00:00", + "end": "2025-09-25T09:30:00", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { "duration": 30, "color": "#ff5722" } + }, + { + "id": "138", + "title": "Sprint Review", + "start": "2025-09-25T14:00:00", + "end": "2025-09-25T15:00:00", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { "duration": 60, "color": "#607d8b" } + }, + { + "id": "139", + "title": "Retrospektiv", + "start": "2025-09-25T15:30:00", + "end": "2025-09-25T16:30:00", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { "duration": 60, "color": "#9c27b0" } + }, + { + "id": "140", + "title": "Team Standup", + "start": "2025-09-26T09:00:00", + "end": "2025-09-26T09:30:00", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { "duration": 30, "color": "#ff5722" } + }, + { + "id": "141", + "title": "Ny Feature Udvikling", + "start": "2025-09-26T10:00:00", + "end": "2025-09-26T12:00:00", + "type": "work", + "allDay": false, + "syncStatus": "synced", + "metadata": { "duration": 120, "color": "#4caf50" } + }, + { + "id": "142", + "title": "Sikkerhedsgennemgang", + "start": "2025-09-26T14:00:00", + "end": "2025-09-26T15:30:00", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { "duration": 90, "color": "#f44336" } + }, + { + "id": "143", + "title": "Weekend Hackathon", + "start": "2025-09-27T00:00:00", + "end": "2025-09-28T23:59:59", + "type": "work", + "allDay": true, + "syncStatus": "synced", + "metadata": { "duration": 2880, "color": "#673ab7" } + }, + { + "id": "144", + "title": "Team Standup", + "start": "2025-09-29T09:00:00", + "end": "2025-09-29T09:30:00", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { "duration": 30, "color": "#ff5722" } + }, + { + "id": "145", + "title": "Månedlig Planlægning", + "start": "2025-09-29T10:00:00", + "end": "2025-09-29T12:00:00", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { "duration": 120, "color": "#9c27b0" } + }, + { + "id": "146", + "title": "Performance Test", + "start": "2025-09-29T14:00:00", + "end": "2025-09-29T16:00:00", + "type": "work", + "allDay": false, + "syncStatus": "synced", + "metadata": { "duration": 120, "color": "#00bcd4" } + }, + { + "id": "147", + "title": "Team Standup", + "start": "2025-09-30T09:00:00", + "end": "2025-09-30T09:30:00", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { "duration": 30, "color": "#ff5722" } + }, + { + "id": "148", + "title": "Kvartal Afslutning", + "start": "2025-09-30T15:00:00", + "end": "2025-09-30T17:00:00", + "type": "milestone", + "allDay": false, + "syncStatus": "synced", + "metadata": { "duration": 120, "color": "#f44336" } + }, + { + "id": "149", + "title": "Oktober Kickoff", + "start": "2025-10-01T09:00:00", + "end": "2025-10-01T10:00:00", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { "duration": 60, "color": "#4caf50" } + }, + { + "id": "150", + "title": "Sprint Planlægning", + "start": "2025-10-01T10:30:00", + "end": "2025-10-01T12:00:00", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { "duration": 90, "color": "#673ab7" } + }, + { + "id": "151", + "title": "Eftermiddags Kodning", + "start": "2025-10-01T14:00:00", + "end": "2025-10-01T17:00:00", + "type": "work", + "allDay": false, + "syncStatus": "synced", + "metadata": { "duration": 180, "color": "#2196f3" } + }, + { + "id": "152", + "title": "Team Standup", + "start": "2025-10-02T09:00:00", + "end": "2025-10-02T09:30:00", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { "duration": 30, "color": "#ff5722" } + }, + { + "id": "153", + "title": "API Design Workshop", + "start": "2025-10-02T11:00:00", + "end": "2025-10-02T12:30:00", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { "duration": 90, "color": "#009688" } + }, + { + "id": "154", + "title": "Bug Fixing Session", + "start": "2025-10-02T15:00:00", + "end": "2025-10-02T17:00:00", + "type": "work", + "allDay": false, + "syncStatus": "synced", + "metadata": { "duration": 120, "color": "#ff5722" } + }, + { + "id": "155", + "title": "Team Standup", + "start": "2025-10-03T09:00:00", + "end": "2025-10-03T09:30:00", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { "duration": 30, "color": "#ff5722" } + }, + { + "id": "156", + "title": "Klient Demo", + "start": "2025-10-03T14:00:00", + "end": "2025-10-03T15:00:00", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { "duration": 60, "color": "#e91e63" } + }, + { + "id": "157", + "title": "Code Review Session", + "start": "2025-10-03T16:00:00", + "end": "2025-10-03T17:00:00", + "type": "work", + "allDay": false, + "syncStatus": "synced", + "metadata": { "duration": 60, "color": "#009688" } + }, + { + "id": "158", + "title": "Fredag Standup", + "start": "2025-10-04T09:00:00", + "end": "2025-10-04T09:30:00", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { "duration": 30, "color": "#ff5722" } + }, + { + "id": "159", + "title": "Uge Retrospektiv", + "start": "2025-10-04T15:00:00", + "end": "2025-10-04T16:00:00", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { "duration": 60, "color": "#9c27b0" } + }, + { + "id": "160", + "title": "Weekend Projekt", + "start": "2025-10-05T10:00:00", + "end": "2025-10-05T14:00:00", + "type": "work", + "allDay": false, + "syncStatus": "synced", + "metadata": { "duration": 240, "color": "#3f51b5" } + }, + { + "id": "161", + "title": "Teknisk Workshop", + "start": "2025-09-24T00:00:00", + "end": "2025-09-26T23:59:59", + "type": "meeting", + "allDay": true, + "syncStatus": "synced", + "metadata": { "duration": 4320, "color": "#795548" } + }, + { + "id": "162", + "title": "Produktudvikling Sprint", + "start": "2025-10-01T00:00:00", + "end": "2025-10-03T23:59:59", + "type": "work", + "allDay": true, + "syncStatus": "synced", + "metadata": { "duration": 4320, "color": "#cddc39" } + }, + { + "id": "163", + "title": "Tidlig Morgen Træning", + "start": "2025-09-23T06:30:00", + "end": "2025-09-23T07:30:00", + "type": "work", + "allDay": false, + "syncStatus": "synced", + "metadata": { "duration": 60, "color": "#00bcd4" } + }, + { + "id": "164", + "title": "Sen Aften Deploy", + "start": "2025-09-25T22:00:00", + "end": "2025-09-26T00:30:00", + "type": "work", + "allDay": false, + "syncStatus": "synced", + "metadata": { "duration": 150, "color": "#ffc107" } + }, + { + "id": "165", + "title": "Overlappende Møde A", + "start": "2025-09-30T10:00:00", + "end": "2025-09-30T11:30:00", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { "duration": 90, "color": "#8bc34a" } + }, + { + "id": "166", + "title": "Overlappende Møde B", + "start": "2025-09-30T10:30:00", + "end": "2025-09-30T12:00:00", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { "duration": 90, "color": "#ff6f00" } + }, + { + "id": "167", + "title": "Kort Check-in", + "start": "2025-10-02T09:45:00", + "end": "2025-10-02T10:00:00", + "type": "meeting", + "allDay": false, + "syncStatus": "synced", + "metadata": { "duration": 15, "color": "#607d8b" } + }, + { + "id": "168", + "title": "Lang Udviklingssession", + "start": "2025-10-04T09:00:00", + "end": "2025-10-04T13:00:00", + "type": "work", + "allDay": false, + "syncStatus": "synced", + "metadata": { "duration": 240, "color": "#2196f3" } } ] \ No newline at end of file diff --git a/src/managers/AllDayManager.ts b/src/managers/AllDayManager.ts index e0887cc..c88f775 100644 --- a/src/managers/AllDayManager.ts +++ b/src/managers/AllDayManager.ts @@ -107,6 +107,19 @@ export class AllDayManager { console.log('🎯 AllDayManager: Ending drag for all-day event', { eventId }); this.handleDragEnd(draggedElement, dragClone as HTMLElement, finalPosition.column); }); + + // Listen for drag cancellation to recalculate height + eventBus.on('drag:cancelled', (event) => { + const { draggedElement, reason } = (event as CustomEvent).detail; + + console.log('🚫 AllDayManager: Drag cancelled', { + eventId: draggedElement?.dataset?.eventId, + reason + }); + + // Recalculate all-day height since clones may have been removed + this.checkAndAnimateAllDayHeight(); + }); } /** diff --git a/src/managers/DragDropManager.ts b/src/managers/DragDropManager.ts index b4004b0..8db9840 100644 --- a/src/managers/DragDropManager.ts +++ b/src/managers/DragDropManager.ts @@ -62,6 +62,7 @@ export class DragDropManager { // Column bounds cache for coordinate-based column detection private columnBoundsCache: ColumnBounds[] = []; + // Auto-scroll properties private autoScrollAnimationId: number | null = null; private readonly scrollSpeed = 10; // pixels per frame @@ -108,6 +109,16 @@ export class DragDropManager { document.body.addEventListener('mousedown', this.boundHandlers.mouseDown); document.body.addEventListener('mouseup', this.boundHandlers.mouseUp); + // Add mouseleave listener to calendar container for drag cancellation + const calendarContainer = document.querySelector('swp-calendar-container'); + if (calendarContainer) { + calendarContainer.addEventListener('mouseleave', () => { + if (this.draggedElement && this.isDragStarted) { + this.cancelDrag(); + } + }); + } + // Initialize column bounds cache this.updateColumnBoundsCache(); @@ -303,7 +314,41 @@ export class DragDropManager { private cleanupAllClones(): void { // Remove clones from all possible locations const allClones = document.querySelectorAll('[data-event-id^="clone"]'); - allClones.forEach(clone => clone.remove()); + + if (allClones.length > 0) { + console.log(`🧹 DragDropManager: Removing ${allClones.length} clone(s)`); + allClones.forEach(clone => clone.remove()); + } + } + + /** + * Cancel drag operation when mouse leaves grid container + */ + private cancelDrag(): void { + if (!this.draggedElement) return; + + console.log('🚫 DragDropManager: Cancelling drag - mouse left grid container'); + + const draggedElement = this.draggedElement; + + // 1. Remove all clones + this.cleanupAllClones(); + + // 2. Restore original element + if (draggedElement) { + draggedElement.style.opacity = ''; + draggedElement.style.cursor = ''; + } + + // 3. Emit cancellation event + this.eventBus.emit('drag:cancelled', { + draggedElement: draggedElement, + reason: 'mouse-left-grid' + }); + + // 4. Clean up state + this.cleanupDragState(); + this.stopAutoScroll(); } /** diff --git a/src/managers/HeaderManager.ts b/src/managers/HeaderManager.ts index 1f9d1b3..8ac9204 100644 --- a/src/managers/HeaderManager.ts +++ b/src/managers/HeaderManager.ts @@ -11,7 +11,6 @@ import { DragMouseEnterHeaderEventPayload, DragMouseLeaveHeaderEventPayload } fr * Separates event handling from rendering concerns */ export class HeaderManager { - private cachedCalendarHeader: HTMLElement | null = null; // Event listeners for drag events private dragMouseEnterHeaderListener: ((event: Event) => void) | null = null; @@ -40,10 +39,7 @@ export class HeaderManager { * Get cached calendar header element */ private getCalendarHeader(): HTMLElement | null { - if (!this.cachedCalendarHeader) { - this.cachedCalendarHeader = document.querySelector('swp-calendar-header'); - } - return this.cachedCalendarHeader; + return document.querySelector('swp-calendar-header'); } /** @@ -196,21 +192,13 @@ export class HeaderManager { calendarHeader = document.createElement('swp-calendar-header'); // Insert header as first child gridContainer.insertBefore(calendarHeader, gridContainer.firstChild); - this.cachedCalendarHeader = calendarHeader; + } } return calendarHeader; } - - /** - * Clear cached header reference - */ - public clearCache(): void { - this.cachedCalendarHeader = null; - } - /** * Clean up resources and event listeners */ @@ -228,6 +216,5 @@ export class HeaderManager { this.dragMouseEnterHeaderListener = null; this.dragMouseLeaveHeaderListener = null; - this.clearCache(); } } \ No newline at end of file