- 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.
60 lines
5.2 KiB
JavaScript
60 lines
5.2 KiB
JavaScript
/* 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 => ({'&':'&', '<':'<', '>':'>', '"':'"', "'":'''}[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')}`;
|
||
});
|
||
}
|
||
};
|
||
})();
|