Contractless-GUI-Wallet/frontend/js/marketing_campaign_page.js

291 lines
11 KiB
JavaScript
Raw Permalink Normal View History

2026-07-01 16:01:30 +00:00
const MARKETING_CAMPAIGN_PAGE_SIZE = 100;
let marketingCampaignPage = 1;
let marketingCampaignTotalRows = 0;
let marketingCampaignLastQuery = null;
let marketingCampaignLoading = false;
function setMarketingCampaignState(message, mode = "") {
if (!marketingCampaignState) {
return;
}
marketingCampaignState.textContent = message;
marketingCampaignState.classList.toggle("hidden", !message);
marketingCampaignState.classList.toggle("loading", mode === "loading");
marketingCampaignState.classList.toggle("error", mode === "error");
}
function clearMarketingCampaignRows() {
if (marketingCampaignRows) {
marketingCampaignRows.innerHTML = "";
}
if (marketingCampaignCards) {
marketingCampaignCards.innerHTML = "";
}
}
function marketingBigInt(value) {
try {
return BigInt(String(value || "0"));
} catch (_) {
return 0n;
}
}
function formatMarketingInteger(value) {
const text = marketingBigInt(value).toString();
return text.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
}
function formatMarketingCents(value) {
const cents = marketingBigInt(value);
const sign = cents < 0n ? "-" : "";
const absolute = cents < 0n ? -cents : cents;
const whole = absolute / 100n;
const fraction = String(absolute % 100n).padStart(2, "0");
return `${sign}${formatMarketingInteger(whole)}.${fraction}`;
}
function formatMarketingAverage(totalValue, count) {
const total = marketingBigInt(totalValue);
const divisor = marketingBigInt(count);
if (divisor === 0n) {
return "--";
}
return formatMarketingCents(total / divisor);
}
function setMarketingCampaignSummary(data = null) {
const totalRecords = marketingBigInt(data?.total_records);
const totalImpressions = marketingBigInt(data?.total_impressions);
const totalClicks = marketingBigInt(data?.total_clicks);
const totalImpressionValue = marketingBigInt(data?.total_impression_value);
const totalClickValue = marketingBigInt(data?.total_click_value);
const totalValue = totalImpressionValue + totalClickValue;
if (marketingTotalRecords) {
marketingTotalRecords.textContent = data ? formatMarketingInteger(totalRecords) : "--";
}
if (marketingTotalImpressions) {
marketingTotalImpressions.textContent = data ? formatMarketingInteger(totalImpressions) : "--";
}
if (marketingTotalClicks) {
marketingTotalClicks.textContent = data ? formatMarketingInteger(totalClicks) : "--";
}
if (marketingAvgImpressionValue) {
marketingAvgImpressionValue.textContent = data
? formatMarketingAverage(totalImpressionValue, totalImpressions)
: "--";
}
if (marketingAvgClickValue) {
marketingAvgClickValue.textContent = data
? formatMarketingAverage(totalClickValue, totalClicks)
: "--";
}
if (marketingTotalValue) {
marketingTotalValue.textContent = data ? formatMarketingCents(totalValue) : "--";
}
}
function setMarketingCampaignFooter(visibleCount = 0, totalCount = 0, page = 1) {
const totalPages = Math.max(1, Math.ceil(totalCount / MARKETING_CAMPAIGN_PAGE_SIZE));
const pageStart = totalCount && visibleCount ? (page - 1) * MARKETING_CAMPAIGN_PAGE_SIZE : 0;
if (marketingCampaignCount) {
marketingCampaignCount.textContent = totalCount
? `Showing ${formatMarketingInteger(pageStart + 1)}-${formatMarketingInteger(pageStart + visibleCount)} of ${formatMarketingInteger(totalCount)}`
: "";
}
if (marketingCampaignPagination) {
marketingCampaignPagination.hidden = totalPages <= 1;
marketingCampaignPagination.querySelectorAll("[data-marketing-campaign-page]").forEach((button) => {
const action = button.dataset.marketingCampaignPage;
button.disabled = action === "first" || action === "previous"
? page <= 1
: page >= totalPages;
});
}
if (marketingCampaignPageInput) {
marketingCampaignPageInput.value = String(page);
marketingCampaignPageInput.max = String(totalPages);
marketingCampaignPageInput.disabled = totalPages <= 1;
}
if (marketingCampaignPageTotal) {
marketingCampaignPageTotal.textContent = formatNumber(totalPages);
}
}
function marketingStatusClass(status) {
return String(status || "").toLowerCase() === "pending" ? "pending" : "confirmed";
}
function renderMarketingCampaignRows(rows = []) {
clearMarketingCampaignRows();
const tableFragment = document.createDocumentFragment();
const cardFragment = document.createDocumentFragment();
rows.forEach((row) => {
const tr = document.createElement("tr");
tr.innerHTML = `
<td>${formatHistoryDate(row.time)}</td>
<td>${htmlEscape(row.campaign)}</td>
<td>${htmlEscape(row.ad_type)}</td>
<td>${htmlEscape(row.keyword)}</td>
<td class="marketing-displayed-cell" title="${htmlEscape(row.displayed)}">${htmlEscape(row.displayed)}</td>
<td>${formatMarketingInteger(row.impression || 0)}</td>
<td>${formatMarketingInteger(row.click || 0)}</td>
<td>${formatMarketingCents(row.impression_value || 0)}</td>
<td>${formatMarketingCents(row.click_value || 0)}</td>
<td><span class="table-status ${marketingStatusClass(row.status)}">${htmlEscape(row.status)}</span></td>
`;
tableFragment.appendChild(tr);
const card = document.createElement("article");
card.className = "history-card";
card.innerHTML = `
<div><span>Time</span><b>${formatHistoryDate(row.time)}</b></div>
<div><span>Campaign</span><b>${htmlEscape(row.campaign)}</b></div>
<div><span>Ad Type</span><b>${htmlEscape(row.ad_type)}</b></div>
<div><span>Keyword</span><b>${htmlEscape(row.keyword)}</b></div>
<div><span>Displayed</span><b>${htmlEscape(row.displayed)}</b></div>
<div><span>Impressions</span><b>${formatMarketingInteger(row.impression || 0)}</b></div>
<div><span>Clicks</span><b>${formatMarketingInteger(row.click || 0)}</b></div>
<div><span>Status</span><b>${htmlEscape(row.status)}</b></div>
`;
cardFragment.appendChild(card);
});
marketingCampaignRows?.appendChild(tableFragment);
marketingCampaignCards?.appendChild(cardFragment);
}
function validateMarketingCampaignQuery() {
const advertiser = String(marketingCampaignAdvertiserInput?.value || "").trim();
const campaign = String(marketingCampaignIdInput?.value || "").trim();
if (!walletLoaded) {
return { error: "Load a wallet before viewing campaign data." };
}
if (!activeBroadcastNode()) {
return { error: "Select a broadcast node before viewing campaign data." };
}
if (!advertiser) {
return { error: "Agency wallet address is required." };
}
if (!/^\d+$/.test(campaign)) {
return { error: "Campaign ID must be a whole number." };
}
return { advertiser, campaign };
}
async function loadMarketingCampaignPage(page = marketingCampaignPage) {
if (marketingCampaignLoading) {
return;
}
const query = validateMarketingCampaignQuery();
if (query.error) {
clearMarketingCampaignRows();
setMarketingCampaignSummary(null);
setMarketingCampaignFooter(0, 0, 1);
setMarketingCampaignState(query.error, "error");
return;
}
const invoke = window.__TAURI__?.core?.invoke;
if (!invoke) {
setMarketingCampaignState("Wallet bridge is not available.", "error");
return;
}
marketingCampaignLoading = true;
marketingCampaignLoadButton?.toggleAttribute("disabled", true);
marketingCampaignRefreshButton?.toggleAttribute("disabled", true);
setMarketingCampaignState("Loading campaign activity...", "loading");
try {
const normalizedPage = Math.max(1, Number.parseInt(page || 1, 10));
const skip = (normalizedPage - 1) * MARKETING_CAMPAIGN_PAGE_SIZE;
const data = await invoke("marketing_campaign_history", {
broadcastNode: activeBroadcastNode(),
advertiser: query.advertiser,
campaign: query.campaign,
skip,
limit: MARKETING_CAMPAIGN_PAGE_SIZE
});
marketingCampaignLastQuery = query;
marketingCampaignPage = normalizedPage;
marketingCampaignTotalRows = Number(marketingBigInt(data?.total_records));
setMarketingCampaignSummary(data);
renderMarketingCampaignRows(data?.rows || []);
setMarketingCampaignFooter((data?.rows || []).length, marketingCampaignTotalRows, marketingCampaignPage);
setMarketingCampaignState((data?.rows || []).length ? "" : "No campaign records found.");
} catch (error) {
setMarketingCampaignState(String(error), "error");
} finally {
marketingCampaignLoading = false;
marketingCampaignLoadButton?.toggleAttribute("disabled", false);
marketingCampaignRefreshButton?.toggleAttribute("disabled", false);
}
}
function prepareMarketingCampaignPage() {
if (!marketingCampaignLastQuery) {
setMarketingCampaignSummary(null);
setMarketingCampaignFooter(0, 0, 1);
if (!marketingCampaignRows?.children.length) {
setMarketingCampaignState("Enter an agency wallet and campaign ID to load campaign activity.");
}
}
}
function resetMarketingCampaignPage() {
marketingCampaignPage = 1;
marketingCampaignTotalRows = 0;
marketingCampaignLastQuery = null;
marketingCampaignLoading = false;
if (marketingCampaignAdvertiserInput) {
marketingCampaignAdvertiserInput.value = "";
}
if (marketingCampaignIdInput) {
marketingCampaignIdInput.value = "";
}
clearMarketingCampaignRows();
setMarketingCampaignSummary(null);
setMarketingCampaignFooter(0, 0, 1);
setMarketingCampaignState("Enter an agency wallet and campaign ID to load campaign activity.");
}
marketingCampaignForm?.addEventListener("submit", (event) => {
event.preventDefault();
marketingCampaignPage = 1;
loadMarketingCampaignPage(1);
});
marketingCampaignRefreshButton?.addEventListener("click", () => {
loadMarketingCampaignPage(marketingCampaignPage);
});
marketingCampaignPagination?.querySelectorAll("[data-marketing-campaign-page]").forEach((button) => {
button.addEventListener("click", () => {
const action = button.dataset.marketingCampaignPage;
const totalPages = Math.max(1, Math.ceil(marketingCampaignTotalRows / MARKETING_CAMPAIGN_PAGE_SIZE));
if (action === "first") {
loadMarketingCampaignPage(1);
} else if (action === "previous") {
loadMarketingCampaignPage(Math.max(1, marketingCampaignPage - 1));
} else if (action === "next") {
loadMarketingCampaignPage(Math.min(totalPages, marketingCampaignPage + 1));
} else if (action === "last") {
loadMarketingCampaignPage(totalPages);
}
});
});
marketingCampaignPageInput?.addEventListener("change", () => {
const totalPages = Math.max(1, Math.ceil(marketingCampaignTotalRows / MARKETING_CAMPAIGN_PAGE_SIZE));
const requestedPage = Math.min(
totalPages,
Math.max(1, Number.parseInt(marketingCampaignPageInput.value || "1", 10))
);
loadMarketingCampaignPage(requestedPage);
});