$("#gal").innerHTML = `
❑
${all.length ? "No templates match the filters." : "No saved templates yet. Create or upload templates on the HTML Templates page."}
`;
return;
}
$("#gal").innerHTML = rows.map(t => `
${esc(t.name)}
${esc(t.source || "—")}
${t.subject_hint ? `
${esc(t.subject_hint)}
` : ""}
${thumb(t)}
${(t.variables || []).slice(0, 10).map(v => `${esc(v)}`).join("")}
${(t.variables || []).length > 10 ? `+${t.variables.length - 10} more` : ""}
${(t.unknown_variables || []).length ? `${t.unknown_variables.length} unknown` : ""}
`).join("");
$$("#gal [data-prev]").forEach(b => b.onclick = () => previewTpl(+b.dataset.prev));
$$("#gal [data-pdf]").forEach(b => b.onclick = () => testPdf(+b.dataset.pdf));
$$("#gal [data-dup]").forEach(b => b.onclick = async () => {
try { const d = await POST(`/templates/${b.dataset.dup}/duplicate`); toast(`Duplicated — new template #${d.id}`, "ok"); load(); }
catch (e) { toast(e.message, "err", 6000); }
});
$$("#gal [data-del]").forEach(b => b.onclick = () =>
confirmedAction(`Delete this template? Campaigns that reference it will show a warning at start.`,
async () => { await DEL(`/templates/${b.dataset.del}`); load(); return "Template deleted"; },
{ title: "Delete template", confirmLabel: "Delete" }));
}
async function previewTpl(id) {
let d;
try { d = await POST(`/templates/${id}/preview`, {}); }
catch (e) { toast(e.message, "err", 6000); return; }
const node = el("div", {},
(d.unknown_variables || []).length || (d.missing_variables || []).length || (d.warnings || []).length ?
el("div", { class: "notice warn mb-1", html: `⚠` +
[].concat(d.unknown_variables || []).map(v => `Unknown variable: ${esc(v)}`).join("
") +
[].concat(d.missing_variables || []).map(v => `Missing data: ${esc(v)}`).join("
") +
[].concat(d.warnings || []).map(w => `Validation: ${esc(String(w))}`).join("
") + `
` }) : null,
el("div", { class: "kv-inline" },
d.generated && d.generated.invoice_number ? el("span", { class: "badge" }, esc(d.generated.invoice_number)) : null,
d.generated && d.generated.amount != null ? el("span", { class: "badge" }, fmtMoney(d.generated.amount)) : null,
d.generated && d.generated.shipping != null ? el("span", { class: "badge" }, "ship " + fmtMoney(d.generated.shipping)) : null),
el("iframe", { class: "preview-frame", srcdoc: d.rendered_html }));
openModal(`Preview — ${((all.find(t => t.id === id) || {}).name) || "#" + id}`, node, { xw: true });
}
async function testPdf(id) {
toast("Rendering PDF via WeasyPrint…", "info");
try {
const d = await POST(`/templates/${id}/test-pdf`, {});
const blob = b64Blob(d.pdf_base64, "application/pdf");
dlBlob(blob, `template_${id}_test.pdf`);
toast(`PDF generated — ${fmtBytes(d.size_bytes)} (${d.engine})`, "ok");
} catch (e) { toast(e.message, "err", 6000); }
}
$("#fSearch").oninput = debounce(() => { q = $("#fSearch").value.trim(); render(); }, 250);
$("#fSrc").onchange = () => { src = $("#fSrc").value; render(); };
$("#btnClear").onclick = () =>
confirmedAction("Clear ALL saved templates? Every template will be permanently deleted. Campaigns referencing them will fail validation.",
async () => { const r = await POST("/templates/clear-all"); load(); return `Cleared ${r.deleted ?? 0} template(s)`; },
{ title: "Clear all templates", confirmLabel: "Delete everything" });
load();
};