bmc_hub/static/js/case-week-calendar.js

60 lines
4.9 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* Case-local time grid; the shared planner remains responsible for loading. */
(() => {
'use strict';
window.renderCaseWeekCalendar = function(host, anchor, items) {
const esc = value => String(value ?? '').replace(/[&<>"']/g, c => ({'&':'&amp;', '<':'&lt;', '>':'&gt;', '"':'&quot;', "'":'&#39;'}[c]));
const key = date => `${date.getFullYear()}-${String(date.getMonth()+1).padStart(2,'0')}-${String(date.getDate()).padStart(2,'0')}`;
const time = date => date.toLocaleTimeString('da-DK', {hour:'2-digit', minute:'2-digit'});
const monday = new Date(anchor);
monday.setHours(0,0,0,0);
const days = [];
let firstHour = 7, lastHour = 17;
for (let index = 0; index < 7; index++) {
const date = new Date(monday); date.setDate(date.getDate()+index);
const next = new Date(date); next.setDate(next.getDate()+1);
const segments = items.filter(item => new Date(item.starts_at) < next && new Date(item.ends_at) > date).map(item => {
const start = new Date(Math.max(new Date(item.starts_at).getTime(), date.getTime()));
const end = new Date(Math.min(new Date(item.ends_at).getTime(), next.getTime()));
const from = start.getHours()*60+start.getMinutes();
const to = end.getTime() === next.getTime() ? 1440 : end.getHours()*60+end.getMinutes();
firstHour = Math.min(firstHour, Math.floor(from/60));
lastHour = Math.max(lastHour, Math.ceil(to/60));
return {item, start, end, from, to};
}).sort((a,b) => a.from-b.from);
if (index < 5 || segments.length) days.push({date, segments});
}
const height = (lastHour-firstHour)*96;
host.classList.add('v5-time-grid');
host.style.setProperty('--week-days', days.length);
host.dataset.firstHour = String(firstHour);
host.dataset.lastHour = String(lastHour);
host.innerHTML = '<div class="v5-time-corner">Kl.</div>' + days.map(({date}) => `<div class="v5-time-heading ${key(date) === key(new Date()) ? 'is-today' : ''}">${esc(date.toLocaleDateString('da-DK',{weekday:'short',day:'numeric',month:'short'}))}</div>`).join('') +
`<div class="v5-time-labels" style="height:${height}px">${Array.from({length:lastHour-firstHour+1},(_,i)=>`<span style="top:${i*96}px">${String(firstHour+i).padStart(2,'0')}:00</span>`).join('')}</div>` +
days.map(({date,segments}) => {
const lanes = [];
for (const segment of segments) {
let lane = lanes.findIndex(end => end <= segment.from);
if (lane < 0) lane = lanes.length;
lanes[lane] = segment.to; segment.lane = lane;
}
return `<div class="v5-time-day" data-date="${key(date)}" style="height:${height}px">${segments.map(({item,start,end,from,to,lane}) => {
const canResize = !item.locked && new Date(item.ends_at).getTime() === end.getTime();
return `<div class="case-planner-calendar-item v5-time-event" style="top:${(from-firstHour*60)*1.6}px;height:${(to-from)*1.6}px;left:calc(${lane*100/lanes.length}% + 2px);width:calc(${100/lanes.length}% - 4px)"><strong>${time(start)}–${time(end)}</strong><div>${esc(item.user_name || 'Ukendt')}</div>${item.note ? `<div>${esc(item.note)}</div>` : ''}${item.conflict_status && item.conflict_status !== 'none' ? '<span class="badge text-bg-warning">Overlap</span>' : ''}${canResize ? `<button type="button" class="case-slot-resize" data-allocation-id="${Number(item.id)}" data-version="${Number(item.version)}" data-start="${esc(item.starts_at)}" data-end="${esc(item.ends_at)}" data-pixels-per-step="24" aria-label="Ændr varighed: træk eller brug piletaster" title="Træk for at ændre varighed">━</button>` : ''}</div>`;
}).join('')}</div>`;
}).join('');
const friday = days[days.length-1].date;
document.getElementById('casePlannerWeekLabel').textContent = `${monday.toLocaleDateString('da-DK',{day:'numeric',month:'short'})} – ${friday.toLocaleDateString('da-DK',{day:'numeric',month:'short',year:'numeric'})}`;
if (!host.dataset.timeGridBound) {
host.dataset.timeGridBound = 'true';
host.addEventListener('click', event => {
if (event.target.closest('.v5-time-event')) return;
const day = event.target.closest('.v5-time-day');
if (!day) return;
const minutes = Math.max(0, Math.min(1425, Number(host.dataset.firstHour)*60 + Math.floor((event.clientY-day.getBoundingClientRect().top)/24)*15));
window.openEmbeddedPlannerSlot(day.dataset.date);
document.getElementById('casePlannerSlotTime').value = `${String(Math.floor(minutes/60)).padStart(2,'0')}:${String(minutes%60).padStart(2,'0')}`;
});
}
};
})();