From b538c0939b934173a3b52f242c336d68bcb8eaaa Mon Sep 17 00:00:00 2001 From: Tyler Date: Sat, 20 Jun 2026 19:47:11 -0600 Subject: [PATCH] =?UTF-8?q?feat(sp7):=20MatchedRemitCard=20=E2=80=94=20lin?= =?UTF-8?q?e-count=20badge?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ClaimDrawer/MatchedRemitCard.test.tsx | 55 +++++++++++++++++++ .../ClaimDrawer/MatchedRemitCard.tsx | 18 ++++++ src/types/index.ts | 7 +++ 3 files changed, 80 insertions(+) diff --git a/src/components/ClaimDrawer/MatchedRemitCard.test.tsx b/src/components/ClaimDrawer/MatchedRemitCard.test.tsx index bf72a1d..886463e 100644 --- a/src/components/ClaimDrawer/MatchedRemitCard.test.tsx +++ b/src/components/ClaimDrawer/MatchedRemitCard.test.tsx @@ -138,6 +138,61 @@ describe("MatchedRemitCard", () => { unmount(); }); + it("test_full_match_badge_renders_when_matchedLines_equals_totalLines", () => { + const { container, unmount } = renderIntoContainer( + + ); + const badge = container.querySelector( + '[data-testid="matched-remit-line-counts"]' + ); + expect(badge).not.toBeNull(); + expect(badge?.textContent ?? "").toMatch(/4\/4 matched/); + expect(badge?.getAttribute("data-all-matched")).toBe("true"); + unmount(); + }); + + it("test_partial_match_badge_renders_unmatched_count", () => { + const { container, unmount } = renderIntoContainer( + + ); + const badge = container.querySelector( + '[data-testid="matched-remit-line-counts"]' + ); + expect(badge).not.toBeNull(); + expect(badge?.textContent ?? "").toMatch(/3\/4 matched/); + expect(badge?.textContent ?? "").toMatch(/1 unmatched/); + expect(badge?.getAttribute("data-all-matched")).toBe("false"); + unmount(); + }); + + it("test_badge_omitted_when_line_counts_undefined", () => { + // Backward compatibility: pre-SP7 claim-detail responses don't + // include matchedLines / totalLines. The card should still render + // without the badge. + const { container, unmount } = renderIntoContainer( + + ); + expect( + container.querySelector('[data-testid="matched-remit-line-counts"]') + ).toBeNull(); + unmount(); + }); + it("test_view_remittance_link_sets_window_location_href", () => { // Spy on the `href` setter so the click doesn't actually navigate // away in the test runner, and so we can assert what was assigned. diff --git a/src/components/ClaimDrawer/MatchedRemitCard.tsx b/src/components/ClaimDrawer/MatchedRemitCard.tsx index f725393..9cb7974 100644 --- a/src/components/ClaimDrawer/MatchedRemitCard.tsx +++ b/src/components/ClaimDrawer/MatchedRemitCard.tsx @@ -38,6 +38,12 @@ export function MatchedRemitCard({ matchedRemittance }: MatchedRemitCardProps) { if (matchedRemittance === null) return null; const { id, totalPaid, status, receivedAt } = matchedRemittance; + // SP7: per-line counts may be missing on pre-SP7 claim-detail responses. + const matchedLines = matchedRemittance.matchedLines; + const totalLines = matchedRemittance.totalLines; + const hasLineCounts = + typeof matchedLines === "number" && typeof totalLines === "number"; + const allMatched = hasLineCounts && matchedLines === totalLines; const handleViewRemittance = () => { window.location.href = `/remittances?id=${id}`; @@ -109,6 +115,18 @@ export function MatchedRemitCard({ matchedRemittance }: MatchedRemitCardProps) { + + {hasLineCounts ? ( + + lines: {matchedLines}/{totalLines} matched + {!allMatched ? ` (${totalLines! - matchedLines!} unmatched)` : null} + + ) : null} ); } diff --git a/src/types/index.ts b/src/types/index.ts index 7ae5af6..d3b6011 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -537,6 +537,13 @@ export interface ClaimDetailMatchedRemittance { /** Mirrors the same `"received" | "reconciled"` mapping as the list endpoint. */ status: string; receivedAt: string; // ISO Z + /** + * SP7: per-line counts for the badge. Optional for backward + * compatibility — older claim-detail responses (pre-SP7) won't have + * these fields. The card omits the badge when undefined. + */ + matchedLines?: number; + totalLines?: number; } export interface ClaimDetailStateHistoryEvent {