diff --git a/static/gsAdmin/views/invoiceComparison.tsx b/static/gsAdmin/views/invoiceComparison.tsx index 773bb528996dba..eb724538c4f11c 100644 --- a/static/gsAdmin/views/invoiceComparison.tsx +++ b/static/gsAdmin/views/invoiceComparison.tsx @@ -27,9 +27,9 @@ type RowStatus = 'match' | 'mismatch' | 'legacy_only' | 'platform_only'; type Row = { delta_cents: number; delta_pct: number | null; - legacy_amount: number | null; // guid is present only when the side has exactly one invoice in the window // (otherwise there's no single invoice to deep-link to). + legacy_amount: number | null; legacy_invoice_count: number; legacy_invoice_guid: string | null; organization_id: number; @@ -43,9 +43,9 @@ type Row = { type Summary = { end: string; legacy_count: number; - legacy_total_cents: number; + over_threshold_count: number; + over_threshold_pct: number; platform_count: number; - platform_total_cents: number; queried_at: string; row_count: number; rows_page: number; @@ -93,14 +93,6 @@ function formatDollars(cents: number | null) { return `$${dollars.toLocaleString(undefined, {minimumFractionDigits: 2, maximumFractionDigits: 2})}`; } -function formatPercent(pct: number | null) { - if (pct === null) { - // No legacy baseline — sorts to top of the list. - return ; - } - return `${(pct * 100).toLocaleString(undefined, {minimumFractionDigits: 1, maximumFractionDigits: 1})}%`; -} - // Render a dollar amount, deep-linking to the org-scoped invoice detail page // when we have both the org slug and a single invoice's guid. The receipts // page (CustomerInvoiceDetailsEndpoint) resolves legacy and platform invoices @@ -123,6 +115,43 @@ function InvoiceAmount({ return {amount}; } +function formatPercent(pct: number | null) { + if (pct === null) { + // No legacy baseline — sorts to top of the list. + return ; + } + return `${(pct * 100).toLocaleString(undefined, {minimumFractionDigits: 1, maximumFractionDigits: 1})}%`; +} + +// `formatPercent` renders ``null`` as ``∞`` because for ``delta_pct`` rows +// a missing percentage means "undefined drift" (legacy=$0 with non-zero +// platform — sorts to the top of the list). That semantic doesn't apply +// to summary ratios like ``over_threshold_pct`` / ``unmatched_invoice_pct``, +// where a runtime ``null``/``undefined`` would just mean the backend +// didn't populate the field (deploy-window race, response-shape drift). +// Render those as ``N/A`` instead of pretending the metric blew up. +function formatPercentOrNA(pct: number | null | undefined) { + if (pct === null || pct === undefined) { + return N/A; + } + return `${(pct * 100).toLocaleString(undefined, {minimumFractionDigits: 1, maximumFractionDigits: 1})}%`; +} + +// Same deploy-window-race rationale as `formatPercentOrNA`: the type +// declares these as `number` but a stale-cached response or any future +// shape drift could leave them runtime-`undefined`, which would render +// the literal string "undefined" in the UI. NaN is also caught — it +// shows up when arithmetic propagates an undefined operand (e.g. +// `legacy_count + platform_count` for the Unmatched denominator). +// Falling back to an em dash matches the missing-value affordance +// `formatDollars` already uses. +function formatCountOrNA(n: number | null | undefined) { + if (n === null || n === undefined || Number.isNaN(n)) { + return ; + } + return n.toLocaleString(); +} + // `datetime-local` inputs use the user's local timezone with no offset // in the string (e.g. "2026-05-26T22:30"). Format a Date for that field. function toDatetimeLocalValue(d: Date): string { @@ -433,11 +462,11 @@ export function InvoiceComparison() { Summary @@ -446,7 +475,7 @@ export function InvoiceComparison() { Legacy invoices - {data.summary.legacy_count} + {formatCountOrNA(data.summary.legacy_count)} @@ -454,41 +483,19 @@ export function InvoiceComparison() { Platform invoices - {data.summary.platform_count} - - - - - Legacy total - - - {formatDollars(data.summary.legacy_total_cents)} + {formatCountOrNA(data.summary.platform_count)} - Platform total + {'>1% diff'} - {formatDollars(data.summary.platform_total_cents)} - - - - - Total delta - - - {formatDollars( - data.summary.legacy_total_cents - data.summary.platform_total_cents - )} - - - - - Rows - - - {data.summary.row_count} + {formatPercentOrNA(data.summary.over_threshold_pct)} + + ({formatCountOrNA(data.summary.over_threshold_count)} of{' '} + {formatCountOrNA(data.summary.row_count)}) + @@ -496,10 +503,13 @@ export function InvoiceComparison() { Unmatched - {formatPercent(data.summary.unmatched_invoice_pct)} + {formatPercentOrNA(data.summary.unmatched_invoice_pct)} - ({data.summary.unmatched_invoice_count} of{' '} - {data.summary.legacy_count + data.summary.platform_count}) + ({formatCountOrNA(data.summary.unmatched_invoice_count)} of{' '} + {formatCountOrNA( + data.summary.legacy_count + data.summary.platform_count + )} + )