diff --git a/src/components/ClaimDrawer/ServiceLinesTable.test.tsx b/src/components/ClaimDrawer/ServiceLinesTable.test.tsx index e51a001..d3f2110 100644 --- a/src/components/ClaimDrawer/ServiceLinesTable.test.tsx +++ b/src/components/ClaimDrawer/ServiceLinesTable.test.tsx @@ -117,9 +117,9 @@ describe("ServiceLinesTable", () => { // Row carries the line number as a data attribute for test stability. expect(rows[0].getAttribute("data-line-number")).toBe("1"); - // Cells: line #, procedure, charge, units, date. + // Cells: line #, procedure, charge, units, date, paid, adjustments. const cells = rows[0].querySelectorAll("td"); - expect(cells.length).toBe(5); + expect(cells.length).toBe(7); const cellText = Array.from(cells).map((c) => c.textContent ?? ""); expect(cellText[0]).toContain("1"); // line # @@ -128,10 +128,52 @@ describe("ServiceLinesTable", () => { expect(cellText[3]).toContain("1"); // units count expect(cellText[3]).toContain("UN"); // units type expect(cellText[4]).toContain("2026"); // date (year at minimum) + // Paid + Adjustments default to em-dash when no lineReconciliation is provided. + expect(cellText[5]).toContain("—"); + expect(cellText[6]).toContain("—"); unmount(); }); + it("test_paid_and_adjustments_columns_render_from_lineReconciliation", () => { + const line = makeLine({ lineNumber: 1, charge: 100.0 }); + const lineReconciliation = [ + { lineNumber: 1, status: "matched" as const, paid: "80.00", adjustmentsSum: "20.00" }, + ]; + const { container, unmount } = renderIntoContainer( + + ); + const row = container.querySelector('[data-testid="service-line-row"]'); + expect(row).not.toBeNull(); + const paid = row?.querySelector('[data-testid="paid-cell"]'); + const adjustments = row?.querySelector('[data-testid="adjustments-cell"]'); + expect(paid?.textContent ?? "").toContain("$80.00"); + expect(adjustments?.textContent ?? "").toContain("$20.00"); + unmount(); + }); + + it("test_paid_and_adjustments_render_em_dash_when_null", () => { + const line = makeLine({ lineNumber: 1 }); + const lineReconciliation = [ + { lineNumber: 1, status: "unmatched_837_only" as const, paid: null, adjustmentsSum: null }, + ]; + const { container, unmount } = renderIntoContainer( + + ); + const row = container.querySelector('[data-testid="service-line-row"]'); + const paid = row?.querySelector('[data-testid="paid-cell"]'); + const adjustments = row?.querySelector('[data-testid="adjustments-cell"]'); + expect(paid?.textContent ?? "").toBe("—"); + expect(adjustments?.textContent ?? "").toBe("—"); + unmount(); + }); + it("test_multiple_lines_render_in_order", () => { const lines = [ makeLine({ lineNumber: 1, procedureCode: "99213" }), diff --git a/src/components/ClaimDrawer/ServiceLinesTable.tsx b/src/components/ClaimDrawer/ServiceLinesTable.tsx index fb630c9..b0d8911 100644 --- a/src/components/ClaimDrawer/ServiceLinesTable.tsx +++ b/src/components/ClaimDrawer/ServiceLinesTable.tsx @@ -1,9 +1,15 @@ import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; import { fmt } from "@/lib/format"; -import type { ClaimDetail } from "@/types"; +import type { ClaimDetail, ClaimDetailLineReconciliation } from "@/types"; type ServiceLinesTableProps = { serviceLines: ClaimDetail["serviceLines"]; + /** + * SP7: per-line Paid + Adjustments values from the slim projection + * embedded in the claim-detail response. Defaults to [] (renders + * em-dash placeholders in the new columns). + */ + lineReconciliation?: ClaimDetailLineReconciliation[]; }; function formatProcedure(line: ClaimDetail["serviceLines"][number]): string { @@ -16,14 +22,29 @@ function formatUnits(line: ClaimDetail["serviceLines"][number]): string { return `${line.units ?? 0} ${line.unitType ?? ""}`.trimEnd(); } +function formatMoneyOrDash(value: string | null): string { + if (value === null) return "—"; + const n = Number(value); + return Number.isFinite(n) ? fmt.usdPrecise(n) : value; +} + /** - * Service-lines table for the claim detail drawer (SP4). + * Service-lines table for the claim detail drawer (SP4 + SP7). * - * Five columns: line #, procedure (qualifier + code + modifiers), - * charge (mono, prominent), units, date. Charge is the headline — + * Seven columns: line #, procedure (qualifier + code + modifiers), + * charge (mono, prominent), units, date, paid (from 835 SVC), and + * adjustments (sum of per-line CAS rows). Charge is the headline — * big mono digits with tabular-nums so columns align across rows. */ -export function ServiceLinesTable({ serviceLines }: ServiceLinesTableProps) { +export function ServiceLinesTable({ + serviceLines, + lineReconciliation = [], +}: ServiceLinesTableProps) { + const lrByLine: Record = {}; + for (const lr of lineReconciliation) { + lrByLine[lr.lineNumber] = lr; + } + return (

Charge Units Date + Paid + Adjustments - {serviceLines.map((line) => ( - - - {line.lineNumber} - - - {formatProcedure(line)} - - { + const lr = lrByLine[line.lineNumber]; + return ( + - {fmt.usdPrecise(line.charge)} - - - {formatUnits(line)} - - - {line.serviceDate ? fmt.date(line.serviceDate) : ""} - - - ))} + + {line.lineNumber} + + + {formatProcedure(line)} + + + {fmt.usdPrecise(line.charge)} + + + {formatUnits(line)} + + + {line.serviceDate ? fmt.date(line.serviceDate) : ""} + + + {formatMoneyOrDash(lr?.paid ?? null)} + + + {formatMoneyOrDash(lr?.adjustmentsSum ?? null)} + + + ); + })} )}