(() => {
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
`).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(' · '));
});
});
})();