feat(upload): streamed claim cards offer drill to persisted entity

This commit is contained in:
Tyler
2026-06-21 17:48:41 -06:00
parent 5c7e9b6168
commit 6c773c1159
2 changed files with 345 additions and 2 deletions
+75 -2
View File
@@ -1,4 +1,5 @@
import { useMemo, useRef, useState } from "react";
import { useNavigate } from "react-router-dom";
import {
AlertTriangle,
ArrowRight,
@@ -138,10 +139,22 @@ function StatPill({
);
}
function ClaimCard837({ claim }: { claim: ClaimOutput }) {
export function ClaimCard837({ claim }: { claim: ClaimOutput }) {
const [open, setOpen] = useState(false);
const passed = claim.validation.passed;
const hasWarnings = claim.validation.warnings.length > 0;
// SP21 Phase 5 Task 5.7: a "See claim in detail →" link drills to
// /claims?claim=ID — but ONLY when this streamed claim_id has
// actually been persisted to a parsed batch. The Upload page
// streams claims as the parser emits them; until the user clicks
// "Save batch" the claim isn't visible to ClaimDrawer.
const parsedBatches = useAppStore((s) => s.parsedBatches);
const persistedClaimIds = useMemo(
() => new Set(parsedBatches.flatMap((b) => b.claimIds)),
[parsedBatches],
);
const canDrill = persistedClaimIds.has(claim.claim_id);
const navigate = useNavigate();
return (
<div
className="rounded-lg overflow-hidden border"
@@ -331,6 +344,31 @@ function ClaimCard837({ claim }: { claim: ClaimOutput }) {
</ul>
</div>
) : null}
{/* SP21 Phase 5 Task 5.7: drill to the persisted claim. Only
renders when this streamed claim_id has been saved into
a parsed batch (so ClaimDrawer can find it). Before
"Save batch" the claim is streaming-only and the link
would 404. */}
{canDrill ? (
<div className="pt-1">
<button
type="button"
onClick={() =>
navigate(
`/claims?claim=${encodeURIComponent(claim.claim_id)}`,
)
}
data-testid="upload-claim-drill"
aria-label={`See claim ${claim.claim_id} in detail`}
className="inline-flex items-center gap-1.5 text-[12px] mono font-semibold cursor-pointer rounded-sm px-1 -mx-1 hover:underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1"
style={{ color: "hsl(var(--surface-ink))" }}
>
See claim in detail
<ArrowRight className="h-3 w-3" strokeWidth={2} />
</button>
</div>
) : null}
</div>
) : null}
</div>
@@ -385,9 +423,19 @@ function ServiceLine837Row({ line }: { line: ServiceLine }) {
);
}
function ClaimCard835({ claim }: { claim: ClaimPayment }) {
export function ClaimCard835({ claim }: { claim: ClaimPayment }) {
const [open, setOpen] = useState(false);
const passed = claim.service_payments.length > 0;
// SP21 Phase 5 Task 5.7: same drill gate as ClaimCard837. Only show
// "See claim in detail" once the claim_id is in the persisted
// batches (otherwise ClaimDrawer would 404).
const parsedBatches = useAppStore((s) => s.parsedBatches);
const persistedClaimIds = useMemo(
() => new Set(parsedBatches.flatMap((b) => b.claimIds)),
[parsedBatches],
);
const canDrill = persistedClaimIds.has(claim.payer_claim_control_number);
const navigate = useNavigate();
return (
<div
className="rounded-lg overflow-hidden border"
@@ -518,6 +566,31 @@ function ClaimCard835({ claim }: { claim: ClaimPayment }) {
</div>
</div>
) : null}
{/* SP21 Phase 5 Task 5.7: drill to the persisted remit. The
835 cards have no separate "claim" (they're the payment
side), so the link goes to /remittances?remit=PCN which
opens the RemitDrawer for this payment. Same persisted-
gate: only show when the PCN is in a saved batch. */}
{canDrill ? (
<div className="pt-1">
<button
type="button"
onClick={() =>
navigate(
`/remittances?remit=${encodeURIComponent(claim.payer_claim_control_number)}`,
)
}
data-testid="upload-remit-drill"
aria-label={`See remittance ${claim.payer_claim_control_number} in detail`}
className="inline-flex items-center gap-1.5 text-[12px] mono font-semibold cursor-pointer rounded-sm px-1 -mx-1 hover:underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1"
style={{ color: "hsl(var(--surface-ink))" }}
>
See claim in detail
<ArrowRight className="h-3 w-3" strokeWidth={2} />
</button>
</div>
) : null}
</div>
) : null}
</div>