const timeCaseId = {{ case.id }}; function minutesToLabel(minutes) { const value = Number(minutes || 0); const h = Math.floor(value / 60); const m = value % 60; return `${h}t ${m}m`; } function timeStatusBadge(status) { if (status === 'godkendt') return 'Godkendt'; if (status === 'kladde') return 'Kladde'; return 'Afventer'; } function renderTimeV1Timeline(entries) { const timeline = document.getElementById('timeTimelineColumns'); if (!timeline) return; if (!entries || entries.length === 0) { timeline.innerHTML = '
Ingen tidsregistreringer endnu
'; return; } const START_HOUR = 7; const TOTAL_HOURS = 10; // 07:00 to 17:00 const HOUR_HEIGHT = 60; // px const groupedByDate = {}; entries.forEach((entry) => { let dateKey = 'Ukendt dato'; if (entry.start_tid) { dateKey = entry.start_tid.split('T')[0]; } else if (entry.worked_date) { dateKey = entry.worked_date; } else if (entry.created_at) { dateKey = entry.created_at.split('T')[0]; } // Keep only first 10 chars for proper grouping if it's an ISO timestamp if (dateKey.length > 10) dateKey = dateKey.substring(0, 10); if (!groupedByDate[dateKey]) groupedByDate[dateKey] = []; groupedByDate[dateKey].push(entry); }); const sortedDates = Object.keys(groupedByDate).sort((a, b) => new Date(b) - new Date(a)); let html = ''; sortedDates.forEach(dateStr => { const dayEntries = groupedByDate[dateStr]; let formattedDateLab = dateStr; try { const d = new Date(dateStr); if (!isNaN(d.getTime())) { formattedDateLab = d.toLocaleDateString('da-DK', { weekday: 'long', day: 'numeric', month: 'long', year: 'numeric' }); formattedDateLab = formattedDateLab.charAt(0).toUpperCase() + formattedDateLab.slice(1); } } catch(e){} const techs = {}; const unplaced = []; dayEntries.forEach(entry => { const tech = entry.bruger_navn || entry.user_name || 'Ukendt'; if (!techs[tech]) techs[tech] = []; if (!entry.start_tid || entry.start_tid === null) { unplaced.push(entry); } else { techs[tech].push(entry); } }); const techNames = Object.keys(techs).sort(); html += `
${formattedDateLab}
`; for (let i = 0; i <= TOTAL_HOURS; i++) { const h = START_HOUR + i; const top = i * HOUR_HEIGHT; html += `
${h.toString().padStart(2, '0')}:00
`; } html += `
`; techNames.forEach(tech => { html += `
${escapeHtml(tech)}
`; techs[tech].forEach(entry => { const desc = escapeHtml(entry.beskrivelse || entry.description || 'Ingen beskrivelse'); const status = entry.entry_status || entry.status || 'kladde'; let cssClass = 'time-v1-entry-kladde'; if (status === 'afventer' || status === 'pending') cssClass = 'time-v1-entry-pending'; if (status === 'godkendt' || status === 'billed' || status === 'approved' || entry.fakturerbar_tid_min > 0) cssClass = 'time-v1-entry-godkendt'; const startObj = new Date(entry.start_tid); let durationMin = 30; // default length if (entry.faktisk_tid_min) { durationMin = parseInt(entry.faktisk_tid_min); } else if (entry.original_hours || entry.timer) { durationMin = Math.round(parseFloat(entry.original_hours || entry.timer) * 60); } let startH = startObj.getHours(); let startM = startObj.getMinutes(); if (startH < START_HOUR) { durationMin -= ((START_HOUR * 60) - (startH * 60 + startM)); startH = START_HOUR; startM = 0; } let topPx = ((startH - START_HOUR) + (startM / 60)) * HOUR_HEIGHT; let heightPx = (durationMin / 60) * HOUR_HEIGHT; if (topPx < 0) topPx = 0; if (topPx + heightPx > TOTAL_HOURS * HOUR_HEIGHT) { heightPx = (TOTAL_HOURS * HOUR_HEIGHT) - topPx; } if (heightPx > 5 && topPx < TOTAL_HOURS * HOUR_HEIGHT) { const endObj = new Date(startObj.getTime() + durationMin * 60000); const timeStr = `${startObj.getHours().toString().padStart(2,'0')}:${startObj.getMinutes().toString().padStart(2,'0')} - ${endObj.getHours().toString().padStart(2,'0')}:${endObj.getMinutes().toString().padStart(2,'0')}`; html += `
${timeStr}
${desc}
`; } }); html += `
`; }); html += `
`; if (unplaced.length > 0) { html += `
Uden tidsrum: `; unplaced.forEach(u => { const userName = escapeHtml(u.bruger_navn || u.user_name || 'Ukendt'); const hrs = u.original_hours || u.timer || 0; html += `
${userName} • ${hrs}t
`; }); html += `
`; } html += `
`; }); timeline.innerHTML = html; } async function loadTimeTrackingTab() { try { const res = await fetch(`/api/v1/timetracking/time?sag_id=${timeCaseId}`); if (!res.ok) throw new Error('Kunne ikke hente tidsforbrug'); const entries = await res.json(); renderTimeV1Timeline(entries || []); setModuleContentState('timetracking', (entries || []).length > 0); } catch (error) { console.error(error); const timeline = document.getElementById('timeTimelineColumns'); if (timeline) { timeline.innerHTML = '
Kunne ikke hente tidsforbrug.
'; } setModuleContentState('timetracking', true); } } async function startLiveTimerV1() { try { const res = await fetch('/api/v1/timetracking/time/start', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ sag_id: timeCaseId, entry_type: document.getElementById('timeV1Type')?.value || 'manuel', beskrivelse: document.getElementById('timeV1Description')?.value || null }) }); if (!res.ok) throw new Error(await res.text()); await loadTimeTrackingTab(); } catch (error) { alert('Kunne ikke starte timer: ' + (error.message || 'ukendt fejl')); } } async function stopLiveTimerV1(extra = {}) { try { const res = await fetch('/api/v1/timetracking/time/stop', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(extra || {}) }); if (!res.ok) throw new Error(await res.text()); await loadTimeTrackingTab(); } catch (error) { alert('Kunne ikke stoppe timer: ' + (error.message || 'ukendt fejl')); } } function bindTimeV1Calculations() { const startIn = document.getElementById('timeV1Start'); const endIn = document.getElementById('timeV1End'); const minIn = document.getElementById('timeV1Minutes'); if (!startIn || !endIn || !minIn) return; const parseTime = (val) => { if (!val) return null; const [h,m] = val.split(':').map(Number); return (h * 60) + m; }; const toTimeStr = (totalMins) => { const h = Math.floor(totalMins / 60) % 24; const m = totalMins % 60; return `${h.toString().padStart(2,'0')}:${m.toString().padStart(2,'0')}`; }; const recalculate = (trigger) => { const s = parseTime(startIn.value); const e = parseTime(endIn.value); const dur = parseInt(minIn.value); if (trigger === 'start' || trigger === 'end') { if (s !== null && e !== null) { let diff = e - s; if (diff < 0) diff += 24*60; minIn.value = diff; } else if (s !== null && !isNaN(dur) && dur > 0 && !endIn.value) { endIn.value = toTimeStr(s + dur); } else if (e !== null && !isNaN(dur) && dur > 0 && !startIn.value) { let base = e - dur; while (base < 0) base += 24*60; startIn.value = toTimeStr(base); } } else if (trigger === 'min') { if (s !== null && !isNaN(dur) && dur > 0) { endIn.value = toTimeStr(s + dur); } else if (e !== null && !isNaN(dur) && dur > 0 && !startIn.value) { let base = e - dur; while(base < 0) base+=24*60; startIn.value = toTimeStr(base); } } }; startIn.addEventListener('change', () => recalculate('start')); endIn.addEventListener('change', () => recalculate('end')); minIn.addEventListener('input', () => recalculate('min')); } async function createManualTimeV1(event) { event.preventDefault(); const minutes = Number(document.getElementById('timeV1Minutes')?.value || 0); if (minutes <= 0) { alert('Indtast minutter over 0'); return; } const dateVal = document.getElementById('timeV1Date')?.value || null; const tStart = document.getElementById('timeV1Start')?.value; const tEnd = document.getElementById('timeV1End')?.value; let startObj = null; let endObj = null; if (dateVal && tStart) { try { const l = new Date(`${dateVal}T${tStart}:00`); startObj = l.toISOString(); } catch(e){} } if (dateVal && tEnd) { try { const l = new Date(`${dateVal}T${tEnd}:00`); if (startObj && new Date(startObj) > l) { l.setDate(l.getDate() + 1); } endObj = l.toISOString(); } catch(e){} } const payload = { sag_id: timeCaseId, medarbejder_id: getTimeV1EmployeeId(), faktisk_tid_min: minutes, worked_date: dateVal, entry_type: document.getElementById('timeV1Type')?.value || 'manuel', entry_status: document.getElementById('timeV1Status')?.value || 'afventer', beskrivelse: document.getElementById('timeV1Description')?.value || null, kilde: 'manuel', start_tid: startObj, slut_tid: endObj }; try { const res = await fetch('/api/v1/timetracking/time/manual', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }); if (!res.ok) throw new Error(await res.text()); const minutesInput = document.getElementById('timeV1Minutes'); const descInput = document.getElementById('timeV1Description'); const startIn = document.getElementById('timeV1Start'); const endIn = document.getElementById('timeV1End'); if (minutesInput) minutesInput.value = ''; if (descInput) descInput.value = ''; if (startIn) startIn.value = ''; if (endIn) endIn.value = ''; await loadTimeTrackingTab(); } catch (error) { alert('Kunne ikke oprette tidsregistrering: ' + (error.message || 'ukendt fejl')); } } document.addEventListener('DOMContentLoaded', () => { bindTimeV1Calculations(); const dateInput = document.getElementById('timeV1Date'); if (dateInput && !dateInput.value) { dateInput.valueAsDate = new Date(); } });