import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; import { fmt } from "@/lib/format"; import type { RemitDetail } from "@/hooks/useRemitDetail"; type ClaimPaymentsTableProps = { remit: RemitDetail; }; /** * Format a CARC reason code as `GROUP-REASON` (e.g. `CO-45`). Both * halves come straight off the backend `CasAdjustment` row; the group * is the 2-letter ANSI code (CO/PR/OA/PI/CR) and the reason is the * numeric code within that group. */ function carcKey(group: string, reason: string): string { return `${group}-${reason}`; } /** * Aggregate per-CARC adjustment totals so the table footer can show * the dollar impact per reason code. The remit's flat `adjustments` * array is summed in-place — no server round-trip. */ function summarizeAdjustments(remit: RemitDetail): Array<{ group: string; reason: string; label: string; total: number; count: number; }> { const byKey = new Map< string, { group: string; reason: string; label: string; total: number; count: number } >(); for (const adj of remit.adjustments ?? []) { const key = carcKey(adj.group, adj.reason); const existing = byKey.get(key); if (existing) { existing.total += adj.amount; existing.count += 1; } else { byKey.set(key, { group: adj.group, reason: adj.reason, label: adj.label, total: adj.amount, count: 1, }); } } // Largest dollar impact first — easy to scan for the biggest hit. return Array.from(byKey.values()).sort((a, b) => Math.abs(b.total) - Math.abs(a.total)); } /** * Claim payments table for the remittance detail drawer. * * The current backend `/api/remittances/{id}` payload exposes one * `ClaimPayment` per remit (the persistence layer keys adjustments by * remittance, not by claim), so the table renders one row carrying the * remit's claim identity (PCN + claim id) plus the headline figures * (paid + adjustments). Below the headline row we surface a per-CARC * breakdown — one row per (group, reason) pair, with the cumulative * amount and the reason-code label — so the user can see WHY the * adjustment totals moved without leaving the drawer. * * When the backend grows to expose per-`ClaimPayment.service_payment` * rows in the detail payload, the table grows naturally: a parent row * per claim, expandable to per-service-payment children. The current * single-row layout is the v1 shape that works against the v1 API. */ export function ClaimPaymentsTable({ remit }: ClaimPaymentsTableProps) { const summary = summarizeAdjustments(remit); const claimLabel = remit.payerClaimControlNumber ?? remit.claimId ?? remit.id; const hasCharge = typeof remit.totalCharge === "number" && Number.isFinite(remit.totalCharge); return (

Claim Payments (1)

Claim Status Charge Paid Adjustments {claimLabel} {remit.status} {hasCharge ? fmt.usdPrecise(remit.totalCharge as number) : "—"} {fmt.usdPrecise(remit.paidAmount)} {Math.abs(remit.adjustmentAmount) > 0 ? fmt.usdPrecise(remit.adjustmentAmount) : "—"}
{summary.length > 0 ? (

CARC breakdown

CARC Reason Hits Amount {summary.map((row) => ( {carcKey(row.group, row.reason)} {row.label} ×{row.count} {fmt.usdPrecise(row.total)} ))}
) : null}
); }