bmc_hub/static/js/case-week-calendar.js
Christian 1fb5b4a191 Add comprehensive tests for email and drift functionalities
- Introduced tests for drift operations UI and backend to ensure proper handling of ignore rules and UI elements.
- Added tests for email identity API to validate email linking and identity retrieval without updates.
- Implemented tests for email purchase link service to verify correct handling of purchase mail links and conflict scenarios.
- Created tests for email reply automation to ensure proper routing and processing of replies based on classification.
- Developed tests for email sender creation to validate suggestions and handling of existing contacts and companies.
- Added tests for email sender identity to ensure correct matching of contacts and domains.
- Implemented tests for work desk functionalities to validate task management, meeting registrations, and timer functionalities.
2026-11-03 01:16:12 +01:00

60 lines
5.2 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" ${!item.locked ? `draggable="true" data-move-id="${Number(item.id)}" data-version="${Number(item.version)}" data-start="${esc(item.starts_at)}" data-end="${esc(item.ends_at)}" data-segment-start="${esc(start.toISOString())}" title="Træk aftalen til en anden dag eller tid"` : ''} 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')}`;
});
}
};
})();