/* Resize case bookings through the existing versioned planner API. */ (() => { 'use strict'; const host = document.getElementById('casePlannerWeek'); if (!host) return; let drag = null; let saving = false; const duration = button => (new Date(button.dataset.end) - new Date(button.dataset.start)) / 60000; const endAt = (button, minutes) => new Date(new Date(button.dataset.start).getTime() + minutes * 60000); function preview(button, minutes) { button.closest('.case-planner-calendar-item').querySelector('strong').textContent = `${new Date(button.dataset.start).toLocaleTimeString('da-DK', {hour:'2-digit', minute:'2-digit'})}–${endAt(button, minutes).toLocaleTimeString('da-DK', {hour:'2-digit', minute:'2-digit'})} · ${minutes} min`; const card = button.closest('.v5-time-event'); if (card && button.dataset.pixelsPerStep) card.style.height = `${Math.max(24, card.offsetHeight + (minutes - Number(button.dataset.previewMinutes || duration(button))) * 1.6)}px`; button.dataset.previewMinutes = String(minutes); } async function save(button, minutes) { if (minutes === duration(button)) return; saving = true; button.disabled = true; const payload = {version: Number(button.dataset.version), ends_at: endAt(button, minutes).toISOString()}; try { for (;;) { const response = await fetch(`/api/v1/planner/allocations/${Number(button.dataset.allocationId)}`, { method: 'PATCH', credentials: 'include', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(payload), }); const body = await response.json(); if (response.ok) break; const message = typeof body.detail === 'string' ? body.detail : body.detail?.message; if (response.status === 409 && body.detail?.conflict_status && !payload.confirm_conflicts && window.confirm(`${message}. Vil du gemme alligevel?`)) { payload.confirm_conflicts = true; continue; } throw new Error(message || 'Kunne ikke ændre varigheden'); } window.showCaseFeedback('Aftalens varighed er gemt', 'success'); } catch (error) { window.showCaseFeedback(error.message); } finally { await window.loadEmbeddedCasePlanner(true); saving = false; button.disabled = false; } } host.addEventListener('click', event => { if (event.target.closest('.case-slot-resize')) event.stopPropagation(); }); host.addEventListener('pointerdown', event => { const button = event.target.closest('.case-slot-resize'); if (!button || saving || drag || (event.button !== 0 && event.pointerType !== 'touch')) return; event.preventDefault(); event.stopPropagation(); drag = {button, y: event.clientY, original: duration(button), minutes: duration(button), pointer: event.pointerId}; button.setPointerCapture(event.pointerId); button.classList.add('is-resizing'); }); host.addEventListener('pointermove', event => { if (!drag || drag.pointer !== event.pointerId) return; drag.minutes = Math.max(15, drag.original + Math.round((event.clientY - drag.y) / (Number(drag.button.dataset.pixelsPerStep) || 12)) * 15); preview(drag.button, drag.minutes); }); function finish(event, cancel) { if (!drag || drag.pointer !== event.pointerId) return; const current = drag; drag = null; current.button.classList.remove('is-resizing'); if (current.button.hasPointerCapture(event.pointerId)) current.button.releasePointerCapture(event.pointerId); if (cancel) preview(current.button, current.original); else void save(current.button, current.minutes); } host.addEventListener('pointerup', event => finish(event, false)); host.addEventListener('pointercancel', event => finish(event, true)); host.addEventListener('lostpointercapture', event => finish(event, true)); host.addEventListener('keydown', event => { const button = event.target.closest('.case-slot-resize'); if (!button || saving || drag || !['ArrowUp', 'ArrowDown'].includes(event.key)) return; event.preventDefault(); event.stopPropagation(); void save(button, Math.max(15, duration(button) + (event.key === 'ArrowDown' ? 15 : -15))); }); })();