84 lines
4.5 KiB
JavaScript
84 lines
4.5 KiB
JavaScript
|
|
/* 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)));
|
|||
|
|
});
|
|||
|
|
})();
|