Gemmes som faktisk arbejdstid til kontrol. Der oprettes ingen sag eller kalenderaftale.
`,async form=>{
if(!pending) {
const selectedCase=Number(form.elements.sag_id.value)||null;
const body={request_id:requestId,description:form.elements.description.value,starts_at:new Date(form.elements.start.value).toISOString(),minutes:Number(form.elements.minutes.value)};
pending=selectedCase?{path:planner+'/desk/time',body:{...body,sag_id:selectedCase}}:{path:'/api/v1/timetracking/quick-time',body:{...body,mode:'registered',user_ids:[uid]}};
}
try{await mutation(pending.path,'POST',pending.body);}catch(e){if(e.status && e.status<500){pending=null;}else{e.message+=' Tryk Gem igen for at genprøve samme registrering.';}throw e;}
status('Tiden er registreret.');
},'Gem tid');
}
function meetingPayload(form, requestId, minutes, customer, contact) {
const users=[...form.querySelectorAll('[name=person]:checked')].map(el=>Number(el.value));
if(!users.length)throw new Error('Vælg mindst én medarbejder');
if(![15,30,45,60].includes(minutes))throw new Error('Vælg 15, 30, 45 eller 60 minutter');
return {request_id:requestId,mode:'registered',description:form.elements.description.value.trim()||'Internt møde',
minutes,starts_at:new Date(Date.now()-minutes*60000).toISOString(),user_ids:users,customer_id:customer,contact_id:contact};
}
async function meeting() {
const options=await api('/api/v1/timetracking/quick-time/options');
const people=options.users.filter(u=>u.can_register);
let duration=30,customer=null,contact=null,pending=null;
const requestId=crypto.randomUUID();
dialog('Registrér mødetid',`${field('Beskrivelse · valgfrit','description','Internt møde','text','maxlength="2000"')}Kunde og kontakt · valgfrit${field('Kunde','customer','','search','autocomplete="off" placeholder="Søg kunde…"')}${field('Kontaktperson','contact','','search','autocomplete="off" placeholder="Søg kontakt…"')}
Registrerer afholdt tid, som slutter nu. Hver valgt medarbejder får det valgte antal minutter. Uden kunde gemmes tiden som internt arbejde hos jer.
`,async form=>{
if(!pending){
if(form.elements.customer.value.trim()&&!customer || form.elements.contact.value.trim()&&!contact)throw new Error('Vælg kunde og kontakt fra søgeresultaterne, eller ryd felterne');
pending=meetingPayload(form,requestId,duration,customer,contact);
}
try{await mutation('/api/v1/timetracking/quick-time','POST',pending);}catch(e){if(e.status&&e.status<500)pending=null;else e.message+=' Tryk Gem igen for at genprøve samme registrering.';throw e;}
status('Mødetiden er registreret for de valgte medarbejdere.');
},'Gem mødetid');
const form=$('deskForm');
form.querySelector('[data-meeting-durations]').onclick=event=>{
const button=event.target.closest('[data-duration]');if(!button)return;
duration=Number(button.dataset.duration);
form.querySelectorAll('[data-duration]').forEach(b=>{const active=Number(b.dataset.duration)===duration;b.classList.toggle('btn-primary',active);b.classList.toggle('btn-outline-primary',!active);b.setAttribute('aria-pressed',String(active));});
};
function search(name,type){
const input=form.elements[name],list=form.querySelector(type==='customers'?'[data-meeting-customers]':'[data-meeting-contacts]');
let generation=0,timer;
input.addEventListener('input',()=>{
const current=++generation;
if(type==='customers'){customer=null;contact=null;form.elements.contact.value='';form.querySelector('[data-meeting-contacts]').replaceChildren();}else contact=null;
clearTimeout(timer);list.replaceChildren();const term=input.value.trim();if(term.length<2)return;
timer=setTimeout(async()=>{
try{
const selectedCustomer=customer;
const data=await api(type==='contacts'&&selectedCustomer?`/api/v1/customers/${selectedCustomer}/contacts`:`/api/v1/search/${type}?q=${encodeURIComponent(term)}`);
if(current!==generation||!$('deskDialog').open||form.elements[name]!==input||type==='contacts'&&selectedCustomer!==customer)return;
let rows=Array.isArray(data)?data:data.results||data.items||[];
const label=r=>r.name||r.contact_name||[r.first_name,r.last_name].filter(Boolean).join(' ')||r.label||'';
if(type==='contacts'&&selectedCustomer)rows=rows.filter(r=>label(r).toLocaleLowerCase().includes(term.toLocaleLowerCase()));
list.replaceChildren();
for(const r of rows.slice(0,10)){
const button=document.createElement('button');button.type='button';button.className='list-group-item list-group-item-action';button.textContent=label(r);
button.onclick=()=>{if(type==='customers'){customer=Number(r.id);contact=null;form.elements.contact.value='';form.querySelector('[data-meeting-contacts]').replaceChildren();}else contact=Number(r.id);input.value=label(r);list.replaceChildren();};list.append(button);
}
}catch(e){$('deskFormError').textContent=e.message;}
},200);
});
}
search('customer','customers');search('contact','contacts');
}
async function registerMeeting(id) {
const details=await api(planner+`/desk/events/${id}/attendance`),event=details.event;
dialog('Registrér afholdt mødetid',`
${esc(event.title)}
Vælg de faktiske deltagere. Start og slut kan rettes for hver person. Allerede registreret tid kan ikke oprettes igen.
${details.participants.map(p=>``).join('')}`,async form=>{
const attendees=[...form.querySelectorAll('[data-attendee]')].filter(el=>el.querySelector('[name=attend]').checked).map(el=>({user_id:Number(el.dataset.attendee),starts_at:new Date(el.querySelector('[name^=from_]').value).toISOString(),ends_at:new Date(el.querySelector('[name^=to_]').value).toISOString()}));
if(!attendees.length)throw new Error('Vælg mindst én deltager, som ikke allerede er registreret');
await mutation(planner+`/desk/events/${id}/register`,'POST',{version:event.version,attendees});status('Afholdt mødetid er registreret.');
},'Registrér mødetid');
}
async function startTimer(sid=null,description='') {
if(busy)return;
busy=true;
try {await mutation(planner+'/desk/timer','POST',{sag_id:sid,description:description||desk.items.find(r=>r.sag_id===sid)?.titel||'Internt arbejde'});status('Timeren er startet.');await refresh();window.dispatchEvent(new CustomEvent('timer-state-changed'));}
finally {busy=false;}
}
async function loadRecent() {
try{recent=(await api(planner+'/desk/recent')).entries;$('deskContinue').disabled=!recent.length;}catch(e){$('deskContinue').disabled=true;}
}
function continueWork() {
const active=recent.find(r=>r.aktiv_timer&&!r.slut_tid);
if(active){status('Du har allerede en aktiv timer. Pause eller stop den i bundlinjen.');return;}
const paused=recent.find(r=>r.paused_at&&!r.slut_tid),last=paused||recent[0];
if(!last)return;
dialog('Fortsæt sidste arbejde',`