const m = mapper(it); const on = cur.includes(m.v); return ``; }).join("") + ""; } function bindStep() { if (step === 1) $("#w_name").oninput = e => C.name = e.target.value; if (step === 2) bindPick("recipient_list_id", true); if (step === 3) { bindPick("subject_set_ids"); $$('input[name="subjRot"]').forEach(r => r.onchange = () => C.subject_rotation = r.value); } if (step === 4) { bindPick("template_ids"); $$('input[name="tplMode"]').forEach(r => r.onchange = () => C.template_sequence_mode = r.value); } if (step === 5) { $$('input[name="attMode"]').forEach(r => r.onchange = () => { C.attach_mode = r.value; if (C.attach_mode !== "pdf") C.attach_pdf = false; bindAttExtra(); renderStep(); }); } if (step === 6) { const keep = (k, id) => { const n = $(id); if (n) n.oninput = () => C.variable_config[k] = n.value; }; keep("invoice_prefix", "#w_invoice_prefix"); keep("invoice_format", "#w_invoice_format"); ["amount_min", "amount_max", "shipping_min", "shipping_max"].forEach(k => { const n = $("#w_" + k); if (n) n.oninput = () => C.variable_config[k] = n.value === "" ? undefined : parseFloat(n.value); }); } if (step === 7) bindPick("account_ids"); if (step === 8) $$('input[name="rotStrat"]').forEach(r => r.onchange = () => { C.rotation_strategy = r.value; bindRotExtra(); }); if (step === 9) ["max_per_account", "send_interval_seconds", "rate_per_minute", "max_retries", "retry_delay_seconds"].forEach(k => { const n = $("#w_" + k); if (n) n.oninput = () => C[k] = n.value === "" ? undefined : parseFloat(n.value); }); if (step === 10) { $$('input[name="phSel"]').forEach(r => r.onchange = () => { C.phone_selection = r.value; renderStep(); }); bindPick("phone_fixed", true); $$('#pk_phone_fixed input').forEach(i => i.onchange = () => { C.variable_config.phone_id = +i.value; }); } if (step === 11) { $("#w_unsub").onchange = e => C.unsubscribe_enabled = e.target.checked; $("#w_pss").oninput = e => C.preview_sample_size = parseInt(e.target.value, 10) || 5; } } function bindPick(key, radio = false) { const inputs = $$(`#pk_${key} input`); inputs.forEach(i => i.onchange = () => { if (radio) { C[key] = +i.value; } else { C[key] = inputs.filter(x => x.checked).map(x => +x.value); } }); } function bindAttExtra() { const box = $("#attExtra"); if (!box) return; const mode = C.attach_mode; if (mode === "none") { box.innerHTML = ""; return; } if (mode === "pdf") { C.attach_pdf = true; box.innerHTML = `` + pickList("templates", C.attach_pdf_template_id, t => ({ v: t.id, main: t.name, sub: "" }), "attach_pdf_template_id", true); bindPick("attach_pdf_template_id", true); } else { box.innerHTML = `` + pickList("attachments", C.attachment_id, a => ({ v: a.id, main: a.name + " (" + a.kind + ")", sub: a.filename }), "attachment_id", true); bindPick("attachment_id", true); } } function bindRotExtra() { const box = $("#rotExtra"); if (!box) return; if (C.rotation_strategy !== "weighted") { box.innerHTML = ""; return; } const accs = (OPT.accounts || []).filter(a => C.account_ids.includes(a.id)); if (!accs.length) { box.innerHTML = `
⚠
Select accounts in Step 7 first, then return here to set weights.
`; return; } box.innerHTML = `` + accs.map(a => `
`).join(""); $$("#rotExtra input[data-acc]").forEach(inp => inp.oninput = () => { C.rotation_weights[String(inp.dataset.acc)] = parseInt(inp.value, 10) || 1; }); } function collect() { // re-read any uncommitted inputs if (step === 1) C.name = ($("#w_name")?.value || "").trim(); const keyMap = { 6: ["amount_min", "amount_max", "shipping_min", "shipping_max"], 9: ["max_per_account", "send_interval_seconds", "rate_per_minute", "max_retries", "retry_delay_seconds"] }; (keyMap[step] || []).forEach(k => { const n = $("#w_" + k); if (n) C[k] = parseFloat(n.value); }); } async function next() { collect(); if (step < 12) { // client+server validation between steps const serverStep = step; try { const d = await POST("/campaigns/validate-step", { step: serverStep, data: snapshotFor(serverStep) }); if (!d.valid) { toast(d.errors.join(" · "), "err", 6000); return; } } catch (e) { toast(e.message, "err", 6000); return; } step++; renderStep(); return; } // step 12 → create or save const payload = payloadFromC(); try { let saved; if (editingId) { saved = await POST(`/campaigns/${editingId}/patch`, payload); toast("Campaign updated", "ok"); } else { saved = await POST("/campaigns/create", payload); toast(`Campaign #${saved.id} created`, "ok"); } closeModal(); loadList(); openMonitor(saved.id); } catch (e) { const errs = e.data?.detail?.errors; toast(errs && errs.length ? errs.join(" · ") : e.message, "err", 7000); } } function snapshotFor(s) { return { name: C.name, recipient_list_id: C.recipient_list_id, subject_set_ids: C.subject_set_ids, subject_rotation: C.subject_rotation, template_ids: C.template_ids, template_sequence_mode: C.template_sequence_mode, attach_mode: C.attach_mode, attach_pdf_template_id: C.attach_pdf_template_id, attachment_id: C.attachment_id, variable_config: cleanVC(), account_ids: C.account_ids, rotation_strategy: C.rotation_strategy, rotation_weights: C.rotation_weights, max_per_account: C.max_per_account, send_interval_seconds: C.send_interval_seconds, phone_selection: C.phone_selection, unsubscribe_enabled: C.unsubscribe_enabled }; } function cleanVC() { const out = {}; for (const [k, v] of Object.entries(C.variable_config || {})) { if (v === undefined || v === null || v === "") continue; out[k] = v; } return out; } function payloadFromC() { const vc = cleanVC(); const payload = { name: C.name, recipient_list_id: C.recipient_list_id, subject_set_ids: C.subject_set_ids, template_ids: C.template_ids, message_type: C.message_type, attach_mode: C.attach_mode, attach_pdf: C.attach_mode === "pdf", attach_pdf_template_id: C.attach_mode === "pdf" ? C.attach_pdf_template_id : null, attachment_id: (C.attach_mode === "file" || C.attach_mode === "image") ? C.attachment_id : null, variable_config: vc, account_ids: C.account_ids, rotation_strategy: C.rotation_strategy, rotation_weights: C.rotation_strategy === "weighted" ? C.rotation_weights : {}, subject_rotation: C.subject_rotation, template_sequence_mode: C.template_sequence_mode, phone_selection: C.phone_selection, max_per_account: C.max_per_account, send_interval_seconds: C.send_interval_seconds, }; payload.rate_per_minute = C.rate_per_minute; payload.max_retries = C.max_retries; payload.retry_delay_seconds = C.retry_delay_seconds; payload.unsubscribe_enabled = C.unsubscribe_enabled; payload.preview_sample_size = C.preview_sample_size; return payload; } /* ---------------- wire-up ---------------- */ $("#btnNew").onclick = () => openWizard(null); $("#btnClear").onclick = () => confirmedAction("Clear ALL campaigns? Drafts and finished campaigns are removed. This cannot be undone.", async () => { const r = await POST("/campaigns/clear-all"); loadList(); return `Cleared ${r.deleted ?? "?"} campaign(s)`; }, { title: "Clear all campaigns", confirmLabel: "Clear all" }); $("#fGo").onclick = () => { lst.status = $("#fStatus").value; lst.q = $("#fSearch").value.trim(); lst.page = 1; loadList(); }; $("#fSearch").onkeydown = e => { if (e.key === "Enter") $("#fGo").click(); }; $$("#modeTabs .mode-tab").forEach(t => t.onclick = () => setMode(t.dataset.mode)); await loadList(); };