52 lines
6.4 KiB
JavaScript
52 lines
6.4 KiB
JavaScript
(() => {
|
||
'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=>`<option value="${u.id}">${esc(u.name)}</option>`).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])=>`<div class="col-sm-4"><div class="card h-100"><div class="card-body"><p class="small text-secondary mb-1">${label}</p><strong class="h4">${value}</strong></div></div></div>`).join('');
|
||
const values=r=>`<td>${r.norm_configured?number(r.planned_days):'Ukendt'}</td><td>${r.norm_configured?hours(r.planned_minutes):'Ukendt'}</td><td>${hours(r.actual_minutes)}</td>`;
|
||
$('absenceSummary').innerHTML=(result.type_summary||[]).map(group=>`<tr class="table-light"><th scope="row">${esc(group.kind_label)} <span class="small text-secondary fw-normal">· ${group.periods} perioder</span></th>${values(group)}</tr>`+group.employees.map(r=>`<tr><td class="ps-4">${esc(r.name)}${r.norm_configured?'':' <span class="small text-secondary">· arbejdstid mangler</span>'}</td>${values(r)}</tr>`).join('')).join('')||'<tr><td colspan="4" class="text-secondary p-3">Intet fravær i den valgte periode.</td></tr>';
|
||
let previousKind=null;
|
||
$('absenceRows').innerHTML=result.items.map(r=>{
|
||
const heading=previousKind!==r.kind?`<tr class="table-light"><th scope="rowgroup" colspan="8">${esc(r.kind_label)}</th></tr>`:'';previousKind=r.kind;
|
||
return heading+`<tr><td>${esc(r.name)}</td><td>${esc(r.kind_label)}</td><td>${date(r.starts_at)}</td><td>${date(r.ends_at)}</td><td>${r.norm_configured?number(r.planned_days):'Ukendt'}</td><td>${r.norm_configured?hours(r.planned_minutes):'Ukendt'}</td><td>${hours(r.actual_minutes)}</td><td><span class="badge ${r.registered?'bg-success':'bg-secondary'}">${r.registered?'Tid registreret':'Planlagt'}</span></td></tr>`;
|
||
}).join('')||'<tr><td colspan="8" class="text-secondary text-center p-4">Intet fravær i den valgte periode.</td></tr>';
|
||
$('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<total)throw new Error('Listen blev ændret. Hent rapporten igen.');}while(page<total);
|
||
const content=[['Fraværstype','Medarbejder','Fra','Til','Arbejdsdage','Planlagte timer','Faktisk registrerede timer','Status'],...rows.map(r=>[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);
|
||
})();
|