(() => { 'use strict'; const $=id=>document.getElementById(id),form=$('absenceFilters'); const esc=v=>String(v??'').replace(/[&<>"']/g,c=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c])); const number=v=>Number(v).toLocaleString('da-DK',{maximumFractionDigits:2}); const hours=v=>number(v/60)+' t'; const date=v=>new Intl.DateTimeFormat('da-DK',{dateStyle:'short',timeStyle:'short',timeZone:'Europe/Copenhagen'}).format(new Date(v)); const day=v=>`${v.getFullYear()}-${String(v.getMonth()+1).padStart(2,'0')}-${String(v.getDate()).padStart(2,'0')}`; let offset=0,requestVersion=0,filters=null,data=null; const now=new Date();form.elements.start_date.value=day(new Date(now.getFullYear(),now.getMonth(),1));form.elements.end_date.value=day(new Date(now.getFullYear(),now.getMonth()+1,0)); window.BMCPlanningDates?.mount(form); async function get(params) { const response=await fetch('/api/v1/planner/absence-report?'+params,{headers:{Accept:'application/json'},credentials:'same-origin'}); const result=await response.json();if(!response.ok)throw new Error(typeof result.detail==='string'?result.detail:'Rapporten kunne ikke indlæses');return result; } function fail(err){$('absenceError').textContent=err.message;$('absenceError').hidden=false;} async function load(newFilters=false) { const version=++requestVersion;$('absenceError').hidden=true;$('absenceExport').disabled=true;$('absenceStatus').textContent='Henter fravær…'; if(newFilters){offset=0;filters=new URLSearchParams(new FormData(form));if(!filters.get('user_id'))filters.delete('user_id');filters.set('group_by','type');} const params=new URLSearchParams(filters);params.set('offset',offset);params.set('limit',50); try { const result=await get(params);if(version!==requestVersion)return;data=result; if(!form.elements.user_id.dataset.loaded){form.elements.user_id.insertAdjacentHTML('beforeend',result.employees.map(u=>``).join(''));form.elements.user_id.dataset.loaded='1';} $('absenceTotals').innerHTML=[['Arbejdsdage',number(result.totals.planned_days)],['Planlagte timer',hours(result.totals.planned_minutes)],['Faktisk registreret fravær',hours(result.totals.actual_minutes)]].map(([label,value])=>`

${label}

${value}
`).join(''); const values=r=>`${r.norm_configured?number(r.planned_days):'Ukendt'}${r.norm_configured?hours(r.planned_minutes):'Ukendt'}${hours(r.actual_minutes)}`; $('absenceSummary').innerHTML=(result.type_summary||[]).map(group=>`${esc(group.kind_label)} · ${group.periods} perioder${values(group)}`+group.employees.map(r=>`${esc(r.name)}${r.norm_configured?'':' · arbejdstid mangler'}${values(r)}`).join('')).join('')||'Intet fravær i den valgte periode.'; let previousKind=null; $('absenceRows').innerHTML=result.items.map(r=>{ const heading=previousKind!==r.kind?`${esc(r.kind_label)}`:'';previousKind=r.kind; return heading+`${esc(r.name)}${esc(r.kind_label)}${date(r.starts_at)}${date(r.ends_at)}${r.norm_configured?number(r.planned_days):'Ukendt'}${r.norm_configured?hours(r.planned_minutes):'Ukendt'}${hours(r.actual_minutes)}${r.registered?'Tid registreret':'Planlagt'}`; }).join('')||'Intet fravær i den valgte periode.'; $('absencePrev').disabled=offset===0;$('absenceNext').disabled=offset+result.items.length>=result.total; $('absencePage').textContent=`${number(result.totals.planned_days)} arbejdsdage i alt · `+(result.total?`${offset+1}–${offset+result.items.length} af ${result.total} perioder`:'0 perioder'); $('absenceStatus').textContent=`${result.total} fraværsperioder. Planlagt og faktisk fravær vises separat.`;$('absenceExport').disabled=false; }catch(err){if(version===requestVersion){data=null;$('absenceTotals').innerHTML='';$('absenceSummary').innerHTML='';$('absenceRows').innerHTML='';$('absencePrev').disabled=true;$('absenceNext').disabled=true;$('absenceStatus').textContent='';fail(err);}} } function csvCell(value) { let text=String(value??'');if(/^[\s]*[=+\-@]/.test(text))text="'"+text;return '"'+text.replaceAll('"','""')+'"'; } $('absenceExport').onclick=async()=>{ if(!data||!filters)return;$('absenceExport').disabled=true; const exportFilters=new URLSearchParams(filters); try { let rows=[],page=0,total; do {const params=new URLSearchParams(exportFilters);params.set('limit',500);params.set('offset',page);const result=await get(params);rows.push(...result.items);total=result.total;page+=result.items.length;if(!result.items.length&&page[r.kind_label,r.name,date(r.starts_at),date(r.ends_at),r.norm_configured?number(r.planned_days):'Ukendt',r.norm_configured?number(r.planned_minutes/60):'Ukendt',number(r.actual_minutes/60),r.registered?'Tid registreret':'Planlagt'])].map(r=>r.map(csvCell).join(';')).join('\r\n'); const url=URL.createObjectURL(new Blob(['\uFEFF'+content],{type:'text/csv;charset=utf-8'})),a=document.createElement('a');a.href=url;a.download=`fravaer-${exportFilters.get('start_date')}-${exportFilters.get('end_date')}.csv`;a.click();setTimeout(()=>URL.revokeObjectURL(url),1000); }catch(err){fail(err);}finally{$('absenceExport').disabled=!data;} }; form.onsubmit=e=>{e.preventDefault();load(true);};$('absencePrev').onclick=()=>{offset=Math.max(0,offset-50);load();};$('absenceNext').onclick=()=>{offset+=50;load();};load(true); })();