feat(frontend): ClaimDrawerHeader with state badge + close button
This commit is contained in:
@@ -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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user