{b.name}
++ {b.size} · {b.status} ·{" "} + {b.click_url && ( + + {b.click_url.slice(0, 80)}{b.click_url.length > 80 ? "…" : ""} + + )} +
+diff --git a/public/legacy/ads/Broadcast-Beat-Ad-March-2023_2f1904b5.png b/public/legacy/ads/Broadcast-Beat-Ad-March-2023_2f1904b5.png deleted file mode 100644 index 5a7a9a3..0000000 Binary files a/public/legacy/ads/Broadcast-Beat-Ad-March-2023_2f1904b5.png and /dev/null differ diff --git a/src/app/admin/banners/BannerRows.tsx b/src/app/admin/banners/BannerRows.tsx index 0a32952..a12607f 100644 --- a/src/app/admin/banners/BannerRows.tsx +++ b/src/app/admin/banners/BannerRows.tsx @@ -2,6 +2,7 @@ import { useState, useTransition } from "react"; import { useRouter } from "next/navigation"; +import Link from "next/link"; interface BannerRow { id: string; @@ -18,12 +19,54 @@ interface BannerRow { updated_at: string; } +interface StatBucket { + impressions: number; + clicks: number; +} +interface BannerStats { + d1: StatBucket; + d7: StatBucket; + lifetime: StatBucket; +} + function fmtDate(iso: string | null): string { if (!iso) return ""; return iso.slice(0, 10); } -export default function BannerRows({ banners }: { banners: BannerRow[] }) { +function pct(n: number, d: number): string { + if (!d) return "—"; + return ((n / d) * 100).toFixed(2) + "%"; +} + +function fmtNum(n: number): string { + return n.toLocaleString(); +} + +function StatBlock({ label, b }: { label: string; b: StatBucket }) { + return ( +
No data yet.
+ ) : ( +| {r.label} | +{r.count.toLocaleString()} | +
+ {b.size} · {b.status} ·{" "} + {b.click_url && ( + + {b.click_url.slice(0, 80)}{b.click_url.length > 80 ? "…" : ""} + + )} +
+- {banners.length} banners · live data from bb.banner_creatives. Edits - apply within 5 minutes (in-process cache TTL on the site renderer). + {banners.length} banners · live data from bb.banner_creatives. + Stats roll forward in real time from bb.ad_impressions / + bb.ad_clicks. Renderer cache TTL is 5 min — edits surface + after the next refresh.