(() => { const base = `/api/v1/vendors/${vendorId}`; const esc = value => String(value ?? '').replace(/[&<>"']/g, c => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c])); const date = value => value ? new Date(value).toLocaleDateString('da-DK') : '—'; async function api(url, options) { const response = await fetch(url, options); if (!response.ok) { const body = await response.json().catch(() => ({})); throw new Error(body.detail || 'Kunne ikke hente data'); } return response.json(); } const post = (url, payload) => api(url, {method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify(payload)}); const message = text => { document.getElementById('vendorResourcesStatus').textContent = text; }; let mailOffset = 0; async function loadFiles() { const rows = await api(`${base}/files`); document.getElementById('vendorFilesList').innerHTML = rows.length ? rows.map(f => `
${esc(f.filename)}
${f.source === 'email' ? 'Mailvedhæftning' : 'Uploadet'} · ${date(f.created_at)} · ${Math.ceil((f.size_bytes || 0)/1024)} KB
ÅbnDownload${f.email_id ? `` : ''}
`).join('') : '

Ingen filer endnu.

'; document.querySelectorAll('#vendorFilesList [data-mail]').forEach(b => b.onclick = () => openMail(Number(b.dataset.mail))); } async function openMail(id) { try { const mail = await api(`${base}/emails/${id}`); document.querySelector('[data-tab="email"]').click(); const host = document.getElementById('vendorMailPreview'); host.innerHTML = `
${esc(mail.subject || '(Uden emne)')}
Fra: ${esc(mail.sender_email)}
Til: ${esc(mail.recipient_email)}${mail.cc ? `
Cc: ${esc(mail.cc)}` : ''}
${esc(mail.body_text || '')}
`; if (!mail.body_text && mail.body_html) { const frame = document.createElement('iframe'); frame.setAttribute('sandbox',''); frame.title = 'Mailindhold'; frame.style.cssText = 'width:100%;height:420px;border:0'; frame.srcdoc = '' + mail.body_html; host.append(frame); } host.scrollIntoView({block:'nearest'}); } catch(e) { message(e.message); } } async function loadEmails() { const rows = await api(`${base}/emails?limit=50&offset=${mailOffset}`); const groups = new Map(); rows.forEach(m => { const key = m.thread_key || `mail:${m.id}`; if (!groups.has(key)) groups.set(key, []); groups.get(key).push(m); }); document.getElementById('vendorEmailsList').innerHTML = rows.length ? [...groups.values()].map(items => `
${esc(items[0].subject || '(Uden emne)')}
${items.map(m => `${m.linked_case_id ? `Sag #${Number(m.linked_case_id)}` : ''}`).join('')}
`).join('') : '

Ingen mails på denne side.

'; document.querySelectorAll('#vendorEmailsList [data-mail]').forEach(b => b.onclick = () => openMail(Number(b.dataset.mail))); document.getElementById('vendorMailPrevious').disabled = mailOffset === 0; document.getElementById('vendorMailNext').disabled = rows.length < 50; document.getElementById('vendorMailPage').textContent = `Side ${mailOffset / 50 + 1}`; } async function loadCases() { const rows = await api(`${base}/cases`); document.getElementById('vendorCasesList').innerHTML = rows.length ? rows.map(s => `#${Number(s.id)} · ${esc(s.titel)}
${esc(s.customer_name)} · ${esc(s.status)} · ${s.explicit_link ? 'Direkte kobling' : 'Via mail'} · ${date(s.updated_at)}
`).join('') : '

Ingen tilknyttede sager.

'; } window.showAddVendorContact = () => { location.href=`/contacts?vendor_id=${vendorId}&create=1&return_to=${encodeURIComponent(`/vendors/${vendorId}#kontakter`)}`; }; let contactsData = [], onlyCallable = false; const contactName = c => [c.first_name,c.last_name].filter(Boolean).join(' ') || '—'; const phoneValue = value => { const clean=String(value || '').trim(); return /[0-9]/.test(clean) && !['-', '—', 'null', 'none', 'n/a'].includes(clean.toLowerCase()) ? clean : ''; }; function renderContacts() { const query = document.getElementById('contactsSearchInput').value.trim().toLowerCase(); const helpers={name:contactName,phone:c=>phoneValue(c.phone),mobile:c=>phoneValue(c.mobile)}; const rows = CompanyContacts.filter(contactsData,query,onlyCallable,helpers); document.getElementById('contactsResultCount').textContent = `${rows.length} / ${contactsData.length}`; const actions = (number,name,id) => `
`; const host = document.getElementById('contactsContainer'); host.innerHTML = rows.length ? CompanyContacts.table(CompanyContacts.rows(rows,{...helpers,actions})) : `
${contactsData.length ? 'Ingen kontakter matcher søgningen' : 'Ingen kontakter endnu'}
`; const unlinkHost=document.getElementById('vendorContactUnlinkList'); unlinkHost.innerHTML=contactsData.map(c=>`
${esc(contactName(c))}
`).join(''); host.querySelectorAll('[data-sms]').forEach(b=>b.onclick=()=>openSmsPrompt(b.dataset.sms,b.dataset.name,Number(b.dataset.contact))); host.querySelectorAll('[data-call]').forEach(b=>b.onclick=async()=>{ b.disabled=true; try { const me=await api('/api/v1/auth/me'); await post('/api/v1/telefoni/click-to-call',{number:b.dataset.call,user_id:Number(me.id)||null}); message('Ringer ud via Yealink…'); } catch(e){message(e.message);} finally{b.disabled=false;} }); unlinkHost.querySelectorAll('[data-unlink]').forEach(b => b.onclick = async () => { try { await api(`${base}/contacts/${b.dataset.unlink}`, {method:'DELETE'}); await loadContacts(); message('Kontaktkobling fjernet. Kontaktpersonen er bevaret.'); } catch(e) { message(e.message); } }); } async function loadContacts() { contactsData = await api(`${base}/contacts`); renderContacts(); } function search(inputId, resultsId, endpoint, render, select) { let timer, revision = 0; document.getElementById(inputId).oninput = event => { const q = event.target.value.trim(), current = ++revision; clearTimeout(timer); const host = document.getElementById(resultsId); host.replaceChildren(); if (q.length < 2) return; timer = setTimeout(async () => { try { const result = await api(endpoint + encodeURIComponent(q)); if (current !== revision) return; const rows = Array.isArray(result) ? result : result.contacts || result.items || []; host.innerHTML = rows.length ? rows.slice(0,20).map((row,i) => ``).join('') : '
Ingen resultater.
'; host.querySelectorAll('button').forEach(b => b.onclick = async () => { try { await select(rows[Number(b.dataset.index)]); host.replaceChildren(); message('Koblingen er gemt.'); } catch(e) { message(e.message); } }); } catch(e) { message(e.message); } },250); }; } document.addEventListener('DOMContentLoaded', () => { document.getElementById('contactsSearchInput').oninput=renderContacts; document.getElementById('contactsOnlyCallableToggle').onclick=event=>{ onlyCallable=!onlyCallable; event.target.classList.toggle('btn-primary',onlyCallable); event.target.classList.toggle('btn-outline-secondary',!onlyCallable); renderContacts(); }; document.getElementById('contactsClearFilters').onclick=()=>{ document.getElementById('contactsSearchInput').value=''; onlyCallable=false; const button=document.getElementById('contactsOnlyCallableToggle'); button.classList.remove('btn-primary'); button.classList.add('btn-outline-secondary'); renderContacts(); }; document.getElementById('vendorFileUpload').onsubmit = async event => { event.preventDefault(); const button = event.target.querySelector('button'); button.disabled = true; try { await api(`${base}/files`, {method:'POST',body:new FormData(event.target)}); event.target.reset(); await loadFiles(); message('Filen er uploadet.'); } catch(e) { message(e.message); } finally { button.disabled=false; } }; document.getElementById('vendorMailPrevious').onclick = () => { mailOffset=Math.max(0,mailOffset-50); loadEmails().catch(e=>message(e.message)); }; document.getElementById('vendorMailNext').onclick = () => { mailOffset+=50; loadEmails().catch(e=>message(e.message)); }; search('vendorCaseSearch','vendorCaseResults','/api/v1/emails/search-sager?q=', s=>`#${s.id} · ${s.titel || s.title}`, async s=>{ await post(`${base}/cases`, {sag_id:s.id}); await loadCases(); }); search('vendorContactSearch','vendorContactResults','/api/v1/contacts?limit=20&search=', c=>`${c.first_name} ${c.last_name} · ${c.email || ''}`, async c=>{ await post(`${base}/contacts`, {contact_id:c.id,role:document.getElementById('vendorContactRole').value,is_primary:document.getElementById('vendorContactPrimary').checked}); await loadContacts(); }); Promise.allSettled([loadFiles(),loadEmails(),loadCases(),loadContacts()]).then(results => { const errors=results.filter(r=>r.status==='rejected').map(r=>r.reason.message); if(errors.length) message(errors.join(' · ')); }); }); })();