feat(frontend): ClaimDrawerHeader with state badge + close button

This commit is contained in:
Tyler
2026-06-20 11:15:49 -06:00
parent 67653f0da8
commit 8933596c77
2 changed files with 327 additions and 0 deletions
@@ -0,0 +1,101 @@
import { X } from "lucide-react";
import { Badge, type BadgeProps } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { fmt } from "@/lib/format";
import { cn } from "@/lib/utils";
import type { ClaimDetail } from "@/types";
type ClaimDrawerHeaderProps = {
claim: ClaimDetail;
onClose: () => void;
};
/**
* State → Badge variant. The state machine value (see
* `cyclone.db.ClaimState`) drives a color-coded chip so the user can
* scan the drawer's status at a glance:
*
* submitted → secondary (neutral, in-flight)
* accepted → default (brand-colored, accepted by payer)
* matched → success (paired with an ERA)
* paid → success (funds received)
* denied → destructive (needs attention)
* pending → warning (waiting on something)
* anything else → muted
*/
const STATE_VARIANT: Record<string, BadgeProps["variant"]> = {
submitted: "secondary",
accepted: "default",
matched: "success",
paid: "success",
denied: "destructive",
pending: "warning",
};
function badgeVariantFor(state: string): BadgeProps["variant"] {
return STATE_VARIANT[state] ?? "muted";
}
/**
* Header band for the claim detail drawer (SP4).
*
* Top-left: instrument-style "Claim" eyebrow + large mono ID.
* Top-right: state badge + total billed amount + close button. The
* badge color encodes state so the user can read the drawer's status
* at a glance without scrolling.
*/
export function ClaimDrawerHeader({ claim, onClose }: ClaimDrawerHeaderProps) {
return (
<header
className={cn(
"flex items-start justify-between gap-4 px-6 py-5",
"border-b border-[color:var(--m-border-heavy)]",
"bg-[color:var(--m-surface)]"
)}
data-testid="claim-drawer-header"
>
{/* Left: eyebrow + mono claim ID */}
<div className="flex flex-col gap-1 min-w-0">
<span className="text-[10.5px] font-semibold uppercase tracking-[0.18em] text-[color:var(--m-ink-tertiary)]">
Claim
</span>
<span
data-testid="header-id"
className="font-mono text-2xl font-semibold tracking-tight text-[color:var(--m-ink-primary)]"
style={{ fontFamily: "var(--m-font-mono)" }}
>
{claim.id}
</span>
</div>
{/* Right: state badge + total amount + close */}
<div className="flex items-start gap-4">
<div className="flex flex-col items-end gap-1">
<Badge
variant={badgeVariantFor(claim.state)}
data-testid="header-state"
className="uppercase tracking-[0.14em]"
>
{claim.stateLabel}
</Badge>
<span
data-testid="header-amount"
className="font-mono text-lg tabular-nums text-[color:var(--m-ink-primary)]"
style={{ fontFamily: "var(--m-font-mono)" }}
>
{fmt.usdPrecise(claim.billedAmount)}
</span>
</div>
<Button
variant="ghost"
size="icon"
onClick={onClose}
aria-label="Close drawer"
data-testid="header-close"
>
<X className="h-4 w-4" strokeWidth={1.75} />
</Button>
</div>
</header>
);
}