// admin-broadcasts.ts — read-only viewer for paliad.email_broadcasts. // // global_admin sees every row; senders see only their own. Authority is // enforced server-side; this client just renders whatever /api/admin/broadcasts // returns. Click a row → load detail (subject, body, recipient list). import { initI18n, onLangChange, t } from "./i18n"; import { initSidebar } from "./sidebar"; interface BroadcastRow { id: string; subject: string; sender_id: string; sender_name: string; sender_email: string; recipient_count: number; sent_at: string; template_key?: string; } interface BroadcastDetailRecipient { id: string; email: string; display_name: string; } interface BroadcastDetail extends BroadcastRow { body: string; recipient_filter: Record; send_report: { total: number; sent: number; failed: number }; recipients: BroadcastDetailRecipient[]; } let rows: BroadcastRow[] = []; function esc(s: string): string { const d = document.createElement("div"); d.textContent = s; return d.innerHTML; } function fmtDate(iso: string): string { const d = new Date(iso); if (isNaN(d.getTime())) return iso; return d.toLocaleString(); } async function load(): Promise { const tbody = document.getElementById("broadcasts-tbody")!; const empty = document.getElementById("broadcasts-empty")!; try { const res = await fetch("/api/admin/broadcasts"); if (!res.ok) { if (res.status === 403) { tbody.innerHTML = `${esc(t("common.forbidden") || "Zugriff verweigert.")}`; return; } tbody.innerHTML = `${esc(t("common.load_error") || "Fehler beim Laden.")}`; return; } rows = (await res.json()) as BroadcastRow[]; } catch { tbody.innerHTML = `${esc(t("common.load_error") || "Fehler beim Laden.")}`; return; } if (!rows.length) { tbody.innerHTML = ""; empty.style.display = "block"; return; } empty.style.display = "none"; tbody.innerHTML = rows .map( (r) => ` ${esc(fmtDate(r.sent_at))} ${esc(r.subject)} ${esc(r.sender_name || r.sender_email || "—")} ${r.recipient_count} `, ) .join(""); tbody.querySelectorAll("tr[data-broadcast-id]").forEach((tr) => { tr.addEventListener("click", () => loadDetail(tr.dataset.broadcastId!)); tr.style.cursor = "pointer"; }); } async function loadDetail(id: string): Promise { const detail = document.getElementById("broadcast-detail")!; detail.classList.remove("hidden"); detail.innerHTML = `

${esc(t("common.loading") || "Lade…")}

`; try { const res = await fetch(`/api/admin/broadcasts/${encodeURIComponent(id)}`); if (!res.ok) { detail.innerHTML = `

${esc(t("common.load_error") || "Fehler beim Laden.")}

`; return; } const d = (await res.json()) as BroadcastDetail; const recList = (d.recipients || []) .map( (r) => `
  • ${esc(r.display_name || "—")} <${esc(r.email)}>
  • `, ) .join(""); const report = d.send_report || { total: d.recipient_count, sent: d.recipient_count, failed: 0 }; detail.innerHTML = `

    ${esc(d.subject)}

    ${esc(t("admin.broadcasts.detail.sent_by") || "Gesendet von")} ${esc(d.sender_name || d.sender_email)} • ${esc(fmtDate(d.sent_at))} • ${report.sent}/${report.total} ${esc(t("admin.broadcasts.detail.delivered") || "versandt")} ${report.failed > 0 ? ` • ${report.failed} ${esc(t("admin.broadcasts.detail.failed") || "fehlgeschlagen")}` : ""}

    ${esc(d.body)}

    ${esc(t("admin.broadcasts.detail.recipients") || "Empfänger")} (${d.recipients?.length ?? 0})

      ${recList}
    `; detail.scrollIntoView({ behavior: "smooth", block: "nearest" }); } catch { detail.innerHTML = `

    ${esc(t("common.load_error") || "Fehler beim Laden.")}

    `; } } document.addEventListener("DOMContentLoaded", () => { initI18n(); initSidebar(); onLangChange(() => load()); load(); });