(function () { function money(n) { if (n === null || n === undefined || n === "") return "—"; var v = Number(n); if (Number.isNaN(v)) return "—"; return "$" + v.toFixed(2); } function initPostcardRoutes(opts) { var form = document.getElementById("campaign-compose"); if (!form) return; var audience = document.getElementById("id_audience"); var picker = document.getElementById("postcard-route-picker"); var listCost = document.getElementById("postcard-list-cost"); var hidden = document.getElementById("id_route_targeting"); var findBtn = document.getElementById("route-find-btn"); var status = document.getElementById("route-status"); var tableWrap = document.getElementById("route-table-wrap"); var tbody = document.getElementById("route-table-body"); var selectedBox = document.getElementById("route-selected-cost"); var quoteUrl = opts.quoteUrl; var csrfToken = opts.csrfToken; var maxRoutes = 2; var routes = []; function isPostcardAudience() { var v = (audience && audience.value) || ""; return v === "postcard_opt_in" || v === "carrier_route"; } function isRouteAudience() { return ((audience && audience.value) || "") === "carrier_route"; } function templateId() { return (document.getElementById("id_template_id") || {}).value || ""; } function setStatus(text, isError) { if (!status) return; status.textContent = text || ""; status.classList.toggle("error", !!isError); } function selectedCodes() { return Array.prototype.map.call( form.querySelectorAll('input[name="route_code"]:checked'), function (el) { return el.value; } ); } function postQuote(body) { return fetch(quoteUrl, { method: "POST", headers: { "Content-Type": "application/json", "X-CSRFToken": csrfToken, Accept: "application/json" }, credentials: "same-origin", body: JSON.stringify(body) }).then(function (res) { return res.json().then(function (json) { if (!res.ok) throw new Error(json.detail || "Quote failed"); return json; }); }); } function renderListCost(data) { if (!listCost) return; if (!isPostcardAudience() || isRouteAudience()) { listCost.hidden = true; return; } listCost.hidden = false; var src = data.price_source === "fallback" ? " (env fallback rate)" : ""; if (data.per_piece == null) { listCost.innerHTML = "Estimated cost: " + data.count + " household" + (data.count === 1 ? "" : "s") + " · per-piece price unavailable. Set PCM_POSTCARD_PRICE_STANDARD / FIRSTCLASS."; return; } listCost.innerHTML = "Estimated cost: " + data.count + " × " + money(data.per_piece) + " = " + money(data.total) + " (" + data.mail_class + src + ")"; } function refreshListCost() { if (!isPostcardAudience() || isRouteAudience()) { if (listCost) listCost.hidden = true; return; } postQuote({ action: "mailing_list", template_id: templateId() }) .then(renderListCost) .catch(function (err) { if (listCost) { listCost.hidden = false; listCost.textContent = err.message || "Could not load mailing-list cost."; } }); } function writeHidden(payload) { if (hidden) hidden.value = payload ? JSON.stringify(payload) : ""; } function renderSelected(data) { if (!selectedBox) return; if (!data) { selectedBox.hidden = true; writeHidden(""); return; } selectedBox.hidden = false; var src = data.price_source === "fallback" ? " using env fallback rate" : ""; var missing = data.per_piece == null ? " Per-piece price unavailable — set PCM_POSTCARD_PRICE_* before sending." : ""; selectedBox.innerHTML = "Selected: " + (data.record_count || 0) + " household" + ((data.record_count || 0) === 1 ? "" : "s") + " on " + (data.selected_codes || []).join(", ") + " · " + (data.mail_class || "") + " · est. " + money(data.total) + src + "." + missing; writeHidden({ selected_codes: data.selected_codes, list_count_id: data.list_count_id, record_count: data.record_count, mail_class: data.mail_class, estimated: data.estimated, route_rows: data.route_rows || data.routes }); } function recount() { var codes = selectedCodes(); if (!codes.length) { renderSelected(null); setStatus("Select one or two routes."); return; } setStatus("Updating quote…"); postQuote({ action: "select", template_id: templateId(), routes: codes }).then(function (data) { setStatus(""); renderSelected(data); }).catch(function (err) { renderSelected(null); setStatus(err.message || "Could not quote selected routes.", true); }); } function renderRoutes(list) { routes = list || []; if (!tbody || !tableWrap) return; if (!routes.length) { tableWrap.hidden = true; tbody.innerHTML = ""; return; } tableWrap.hidden = false; tbody.innerHTML = routes.map(function (row) { return "