const ex = el("button", { class: "btn sm secondary", onclick: () => { window.location = `/campaigns/${MON.id}/export`; } }, "Export CSV"); const pv = el("button", { class: "btn sm secondary", onclick: () => campaignPreview(MON.id) }, "Preview"); ctl.append(ex, pv); }).catch(() => {}); } async function drawRecipients() { let box = $("#recBox"); if (!box) { box = el("div", { id: "recBox" }); $("#monBody").append(box); } await loadRecipients(box); } async function loadRecipients(box) { const p = new URLSearchParams({ status: MON.statusF, search: MON.q, page: MON.page, page_size: 50 }); let d; try { d = await GET(`/campaigns/${MON.id}/recipients?` + p); } catch (e) { box.innerHTML = `
✕
${esc(e.message)}
`; return; } const rows = d.recipients || []; const RS_BADGE = { pending: "b-gray", queued: "b-blue", processing: "b-amber", sent: "b-green", temp_failed: "b-amber", perm_failed: "b-red", suppressed: "b-violet", skipped: "b-gray", bounced: "b-red" }; box.innerHTML = `

Per-recipient monitor

${d.total} recipients
${rows.length ? `
${rows.map(r => ``).join("")}
SeqEmailStatusAccountSubjectInvoiceAmountAttemptsSent atMessage IDLast error
${r.seq} ${esc(r.email)} ${esc(r.status)} ${esc(r.account || "—")} ${esc((r.subject || "—").slice(0, 60))} ${esc(r.invoice_number || "—")} ${r.amount != null ? fmtMoney(r.amount) : "—"} ${r.attempts ?? 0} ${fmtDT(r.sent_at)} ${r.message_id ? esc(r.message_id.slice(0, 14)) + "…" : "—"} ${esc((r.last_error || "—").slice(0, 80))}
` : `
◷

No recipients materialized yet — they are created when the campaign starts.

`}
`; $("#recF").onchange = () => { MON.statusF = $("#recF").value; MON.page = 1; loadRecipients(box); }; $("#recQ").onkeydown = e => { if (e.key === "Enter") { MON.q = $("#recQ").value.trim(); MON.page = 1; loadRecipients(box); } }; $("#recGo").onclick = () => { MON.statusF = $("#recF").value; MON.q = $("#recQ").value.trim(); MON.page = 1; loadRecipients(box); }; const pages = Math.max(1, Math.ceil(d.total / 50)); $("#recPager").innerHTML = pages > 1 ? ` Page ${MON.page} of ${pages} ` : ""; $$("#recPager [data-pg]").forEach(b => b.onclick = () => { MON.page = +b.dataset.pg; loadRecipients(box); }); } /* ==================== CAMPAIGN PREVIEW MODAL ==================== */ async function campaignPreview(cid, size) { let d;