Skip to content
122 changes: 47 additions & 75 deletions static/gsAdmin/views/invoiceComparison.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,24 +28,22 @@ 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_invoice_count: number;
legacy_invoice_guid: string | null;
legacy_invoice_guids: string[];
Comment thread
armcknight marked this conversation as resolved.
Outdated
organization_id: number;
organization_slug: string | null;
platform_amount: number | null;
platform_invoice_count: number;
platform_invoice_guid: string | null;
platform_invoice_guids: string[];
status: RowStatus;
};

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;
Expand All @@ -65,8 +63,7 @@ type UnmatchedSide = 'legacy_only' | 'platform_only';
type UnmatchedRow = {
amount: number;
invoice_count: number;
// Present only when the org has exactly one invoice on its one side.
invoice_guid: string | null;
invoice_guids: string[];
organization_id: number;
organization_slug: string | null;
side: UnmatchedSide;
Expand All @@ -93,6 +90,26 @@ function formatDollars(cents: number | null) {
return `$${dollars.toLocaleString(undefined, {minimumFractionDigits: 2, maximumFractionDigits: 2})}`;
}

Comment thread
sentry[bot] marked this conversation as resolved.
function receiptUrl(orgSlug: string, guid: string) {
return `/settings/${orgSlug}/billing/receipts/${guid}/`;
}

// Render the $ amount as a link to the receipt details page when we can
// pick a single invoice to point at. Multiple-invoice rows fall back to
// plain text — the count badge next to the cell already signals there's
// more than one, and per-invoice drill-down isn't a common workflow.
Comment thread
armcknight marked this conversation as resolved.
Outdated
//
// Uses a plain `<a>` (not router `<Link>`) because gsAdmin is a separate
// app bundle from the org-facing settings UI; the receipts page lives in
// the latter, so navigating there has to be a full page load.
function renderAmountCell(cents: number | null, guids: string[], orgSlug: string | null) {
const dollars = formatDollars(cents);
if (!orgSlug || guids.length !== 1) {
return dollars;
}
return <a href={receiptUrl(orgSlug, guids[0]!)}>{dollars}</a>;
}

function formatPercent(pct: number | null) {
if (pct === null) {
// No legacy baseline — sorts to top of the list.
Expand All @@ -101,28 +118,6 @@ function formatPercent(pct: number | null) {
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
// alike by guid. This is a plain anchor — not a router Link — because gsAdmin
// is a separate app bundle from the org-facing settings UI, so navigating
// there is a full page load (see the cross-app links in dataRequests.tsx).
function InvoiceAmount({
cents,
guid,
orgSlug,
}: {
cents: number | null;
guid: string | null;
orgSlug: string | null;
}) {
const amount = formatDollars(cents);
if (!guid || !orgSlug) {
return amount;
}
return <a href={`/settings/${orgSlug}/billing/receipts/${guid}/`}>{amount}</a>;
}

// `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 {
Expand Down Expand Up @@ -433,11 +428,11 @@ export function InvoiceComparison() {
<PanelHeader>Summary</PanelHeader>
<PanelBody withPadding>
<Grid
columns="repeat(7, 1fr)"
columns="repeat(4, 1fr)"
gap="xl"
css={css`
@media (max-width: 900px) {
grid-template-columns: repeat(3, 1fr);
grid-template-columns: repeat(2, 1fr);
}
`}
>
Expand All @@ -459,36 +454,13 @@ export function InvoiceComparison() {
</Flex>
<Flex direction="column">
<Text size="sm" variant="muted">
Legacy total
</Text>
<Text size="lg" bold>
{formatDollars(data.summary.legacy_total_cents)}
</Text>
</Flex>
<Flex direction="column">
<Text size="sm" variant="muted">
Platform total
{'>1% diff'}
</Text>
Comment thread
sentry[bot] marked this conversation as resolved.
<Text size="lg" bold>
{formatDollars(data.summary.platform_total_cents)}
</Text>
</Flex>
<Flex direction="column">
<Text size="sm" variant="muted">
Total delta
</Text>
<Text size="lg" bold>
{formatDollars(
data.summary.legacy_total_cents - data.summary.platform_total_cents
)}
</Text>
</Flex>
<Flex direction="column">
<Text size="sm" variant="muted">
Rows
</Text>
<Text size="lg" bold>
{data.summary.row_count}
{formatPercent(data.summary.over_threshold_pct)}
<TruncatedNote size="sm" variant="muted">
({data.summary.over_threshold_count} of {data.summary.row_count})
Comment thread
sentry[bot] marked this conversation as resolved.
Outdated
</TruncatedNote>
</Text>
</Flex>
<Flex direction="column">
Expand Down Expand Up @@ -550,21 +522,21 @@ export function InvoiceComparison() {
)}
</td>
<RightCell>
<InvoiceAmount
cents={row.legacy_amount}
guid={row.legacy_invoice_guid}
orgSlug={row.organization_slug}
/>{' '}
{renderAmountCell(
row.legacy_amount,
row.legacy_invoice_guids,
row.organization_slug
)}{' '}
<Text size="sm" variant="muted">
({row.legacy_invoice_count})
</Text>
</RightCell>
<RightCell>
<InvoiceAmount
cents={row.platform_amount}
guid={row.platform_invoice_guid}
orgSlug={row.organization_slug}
/>{' '}
{renderAmountCell(
row.platform_amount,
row.platform_invoice_guids,
row.organization_slug
)}{' '}
<Text size="sm" variant="muted">
({row.platform_invoice_count})
</Text>
Expand Down Expand Up @@ -623,11 +595,11 @@ export function InvoiceComparison() {
<Tag variant="danger">{row.side}</Tag>
</td>
<RightCell>
<InvoiceAmount
cents={row.amount}
guid={row.invoice_guid}
orgSlug={row.organization_slug}
/>
{renderAmountCell(
row.amount,
row.invoice_guids,
row.organization_slug
)}
</RightCell>
<RightCell>{row.invoice_count}</RightCell>
</tr>
Expand Down
Loading