(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 hhSize(n) { if (n === null || n === undefined || n === "") return "—"; var v = Number(n); if (Number.isNaN(v)) return "—"; return v.toFixed(1); } function income(n) { if (n === null || n === undefined || n === "") return "—"; var v = Number(n); if (Number.isNaN(v)) return "—"; return "$" + Math.round(v).toLocaleString("en-US"); } function count(n) { if (n === null || n === undefined || n === "") return "—"; var v = Number(n); if (Number.isNaN(v)) return "—"; return String(v); } 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 eddmLink = document.getElementById("eddm-map-link"); 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 "" + "" + "" + String(row.text || "") + "" + "" + String(row.total) + "" + "" + hhSize(row.household_size) + "" + "" + income(row.median_income) + "" + "" + count(row.businesses) + "" + "" + money(row.cost) + ""; }).join(""); tbody.querySelectorAll('input[name="route_code"]').forEach(function (box) { box.addEventListener("change", function () { var checked = selectedCodes(); if (checked.length > maxRoutes) { box.checked = false; setStatus("Pick at most two routes.", true); return; } recount(); }); }); } function findRoutes() { var zip = (document.getElementById("route-zip") || {}).value || ""; var address = (document.getElementById("route-address") || {}).value || ""; var city = (document.getElementById("route-city") || {}).value || ""; var state = (document.getElementById("route-state") || {}).value || ""; var radius = (document.getElementById("route-radius") || {}).value || ""; setStatus("Looking up routes…"); tableWrap.hidden = true; renderSelected(null); postQuote({ action: "search", template_id: templateId(), zip: zip, address: address, city: city, state: state, radius: radius }).then(function (data) { renderRoutes(data.routes || []); if (eddmLink && data.eddm_map_url) { eddmLink.href = data.eddm_map_url; } if (!(data.routes || []).length) { setStatus("No carrier routes for that search.", true); } else { setStatus("Select one or two routes. Cost uses " + (data.mail_class || "Standard") + "."); } }).catch(function (err) { renderRoutes([]); setStatus(err.message || "Route lookup failed.", true); }); } function syncVisibility() { if (picker) picker.hidden = !isRouteAudience(); if (!isRouteAudience()) { writeHidden(""); if (selectedBox) selectedBox.hidden = true; } refreshListCost(); } if (findBtn) findBtn.addEventListener("click", function (e) { e.preventDefault(); findRoutes(); }); var tmpl = document.getElementById("id_template_id"); if (tmpl) tmpl.addEventListener("change", function () { syncVisibility(); if (isRouteAudience() && selectedCodes().length) recount(); }); if (audience) audience.addEventListener("change", syncVisibility); syncVisibility(); window.MonicaPostcardRoutes = { isPostcardAudience: isPostcardAudience, isRouteAudience: isRouteAudience, syncVisibility: syncVisibility }; } window.initPostcardRoutes = initPostcardRoutes; })();