refactor(claim-drawer): mount on shared DrillDrawerHeader shell
This commit is contained in:
@@ -131,14 +131,17 @@ describe("ClaimDrawerHeader", () => {
|
|||||||
it("test_renders_claim_id_label_and_value", () => {
|
it("test_renders_claim_id_label_and_value", () => {
|
||||||
const { container, unmount } = renderHeader({ id: "CLM-42" });
|
const { container, unmount } = renderHeader({ id: "CLM-42" });
|
||||||
|
|
||||||
// The eyebrow label "Claim" is rendered as uppercase text.
|
// The eyebrow label "Claim" is rendered as uppercase text by
|
||||||
|
// DrillDrawerHeader.
|
||||||
const text = (container.textContent ?? "").toLowerCase();
|
const text = (container.textContent ?? "").toLowerCase();
|
||||||
expect(text).toContain("claim");
|
expect(text).toContain("claim");
|
||||||
|
|
||||||
// The claim id sits in a node tagged with data-testid="header-id".
|
// SP21 Phase 5 Task 5.10: the claim id is now the title passed
|
||||||
const idEl = container.querySelector('[data-testid="header-id"]');
|
// to DrillDrawerHeader, which renders it inside an <h2>. Find
|
||||||
expect(idEl).not.toBeNull();
|
// the h2 and assert its text matches the claim id.
|
||||||
expect(idEl?.textContent).toBe("CLM-42");
|
const titleEl = container.querySelector("h2");
|
||||||
|
expect(titleEl).not.toBeNull();
|
||||||
|
expect(titleEl?.textContent).toBe("CLM-42");
|
||||||
|
|
||||||
unmount();
|
unmount();
|
||||||
});
|
});
|
||||||
@@ -212,8 +215,11 @@ describe("ClaimDrawerHeader", () => {
|
|||||||
const onClose = vi.fn();
|
const onClose = vi.fn();
|
||||||
const { container, unmount } = renderHeader({}, onClose);
|
const { container, unmount } = renderHeader({}, onClose);
|
||||||
|
|
||||||
|
// SP21 Phase 5 Task 5.10: the close button is now rendered by
|
||||||
|
// DrillDrawerHeader (no `data-testid`); find it via its
|
||||||
|
// accessible name instead.
|
||||||
const closeBtn = container.querySelector(
|
const closeBtn = container.querySelector(
|
||||||
'[data-testid="header-close"]'
|
'button[aria-label="Close drawer"]'
|
||||||
) as HTMLButtonElement | null;
|
) as HTMLButtonElement | null;
|
||||||
expect(closeBtn).not.toBeNull();
|
expect(closeBtn).not.toBeNull();
|
||||||
expect(onClose).not.toHaveBeenCalled();
|
expect(onClose).not.toHaveBeenCalled();
|
||||||
@@ -226,18 +232,25 @@ describe("ClaimDrawerHeader", () => {
|
|||||||
unmount();
|
unmount();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("test_uses_modern_palette_surface", () => {
|
it("test_uses_shared_drilldrawerheader_shell", () => {
|
||||||
// The drawer header anchors itself on the light surface palette
|
// SP21 Phase 5 Task 5.10: the header is now a thin wrapper
|
||||||
// token (matches ClaimDrawerSkeleton / ClaimDrawerError). The root
|
// around DrillDrawerHeader — verify the wrapper is present and
|
||||||
// <header> element is tagged with data-testid="claim-drawer-header"
|
// that the underlying shell is the shared one (an h2 with the
|
||||||
// so we can sniff its className without coupling to the badge
|
// expected Tailwind treatment). The "Claim" eyebrow + claim id
|
||||||
// or close-button wrappers.
|
// title prove the shell rendered.
|
||||||
const { container, unmount } = renderHeader({});
|
const { container, unmount } = renderHeader({ id: "CLM-42" });
|
||||||
|
|
||||||
const root = container.querySelector('[data-testid="claim-drawer-header"]');
|
const root = container.querySelector('[data-testid="claim-drawer-header"]');
|
||||||
expect(root).not.toBeNull();
|
expect(root).not.toBeNull();
|
||||||
const cls = root?.className ?? "";
|
|
||||||
expect(cls).toContain("bg-[color:var(--m-surface)]");
|
// The h2 is DrillDrawerHeader's title slot.
|
||||||
|
const titleEl = root?.querySelector("h2");
|
||||||
|
expect(titleEl).not.toBeNull();
|
||||||
|
expect(titleEl?.textContent).toBe("CLM-42");
|
||||||
|
// The className pattern DrillDrawerHeader uses for the title.
|
||||||
|
const cls = titleEl?.className ?? "";
|
||||||
|
expect(cls).toContain("text-[18px]");
|
||||||
|
expect(cls).toContain("font-semibold");
|
||||||
|
|
||||||
unmount();
|
unmount();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { Download, X } from "lucide-react";
|
import { Download } from "lucide-react";
|
||||||
import { Badge, type BadgeProps } from "@/components/ui/badge";
|
import { Badge, type BadgeProps } from "@/components/ui/badge";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { DrillDrawerHeader } from "@/components/drill/DrillDrawerHeader";
|
||||||
import { api } from "@/lib/api";
|
import { api } from "@/lib/api";
|
||||||
import { downloadTextFile } from "@/lib/download";
|
import { downloadTextFile } from "@/lib/download";
|
||||||
import { fmt } from "@/lib/format";
|
import { fmt } from "@/lib/format";
|
||||||
import { cn } from "@/lib/utils";
|
|
||||||
import type { ClaimDetail } from "@/types";
|
import type { ClaimDetail } from "@/types";
|
||||||
|
|
||||||
type ClaimDrawerHeaderProps = {
|
type ClaimDrawerHeaderProps = {
|
||||||
@@ -47,12 +47,19 @@ function badgeVariantFor(state: string): BadgeProps["variant"] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Header band for the claim detail drawer (SP4).
|
* Header band for the claim detail drawer (SP4 → refactored SP21
|
||||||
|
* Phase 5 Task 5.10).
|
||||||
*
|
*
|
||||||
* Top-left: instrument-style "Claim" eyebrow + large mono ID.
|
* The shell is now the shared `DrillDrawerHeader` (same as
|
||||||
* Top-right: state badge + total billed amount + close button. The
|
* `ProviderDrawer` / `AckDrawer`) — eyebrow + title on the left,
|
||||||
* badge color encodes state so the user can read the drawer's status
|
* close button on the right. The right-side `action` slot carries
|
||||||
* at a glance without scrolling.
|
* the state badge, total billed amount, and the "Download 837"
|
||||||
|
* button, all of which used to live in a custom <header> block.
|
||||||
|
*
|
||||||
|
* Top-left: instrument-style "Claim" eyebrow + the claim ID.
|
||||||
|
* Top-right: state badge + total billed amount + download button.
|
||||||
|
* The badge color encodes state so the user can read the drawer's
|
||||||
|
* status at a glance without scrolling.
|
||||||
*/
|
*/
|
||||||
export function ClaimDrawerHeader({
|
export function ClaimDrawerHeader({
|
||||||
claim,
|
claim,
|
||||||
@@ -80,31 +87,12 @@ export function ClaimDrawerHeader({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
// The action slot is rendered by DrillDrawerHeader to the left of
|
||||||
<header
|
// the close button. Group the three action pieces (badge, amount,
|
||||||
className={cn(
|
// download) in a single flex row so they read as a unit.
|
||||||
"flex items-start justify-between gap-4 px-6 py-5",
|
const action = (
|
||||||
"border-b border-[color:var(--m-border-heavy)]/40",
|
<div className="flex items-center gap-2" data-testid="claim-header-actions">
|
||||||
"bg-[color:var(--m-surface)]"
|
<div className="flex items-center gap-2">
|
||||||
)}
|
|
||||||
data-testid="claim-drawer-header"
|
|
||||||
>
|
|
||||||
{/* Left: eyebrow + mono claim ID */}
|
|
||||||
<div className="flex flex-col gap-1 min-w-0">
|
|
||||||
<span className="eyebrow text-[color:var(--m-ink-tertiary)]">
|
|
||||||
Claim
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
data-testid="header-id"
|
|
||||||
className="mono text-2xl font-semibold tracking-tight text-[color:var(--m-ink-primary)]"
|
|
||||||
>
|
|
||||||
{claim.id}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Right: state badge + total amount + download + close */}
|
|
||||||
<div className="flex items-start gap-2">
|
|
||||||
<div className="flex flex-col items-end gap-1">
|
|
||||||
<Badge
|
<Badge
|
||||||
variant={badgeVariantFor(claim.state)}
|
variant={badgeVariantFor(claim.state)}
|
||||||
data-testid="header-state"
|
data-testid="header-state"
|
||||||
@@ -114,7 +102,7 @@ export function ClaimDrawerHeader({
|
|||||||
</Badge>
|
</Badge>
|
||||||
<span
|
<span
|
||||||
data-testid="header-amount"
|
data-testid="header-amount"
|
||||||
className="mono text-lg tabular-nums text-[color:var(--m-ink-primary)]"
|
className="mono text-sm tabular-nums text-muted-foreground"
|
||||||
>
|
>
|
||||||
{fmt.usdPrecise(claim.billedAmount)}
|
{fmt.usdPrecise(claim.billedAmount)}
|
||||||
</span>
|
</span>
|
||||||
@@ -130,16 +118,17 @@ export function ClaimDrawerHeader({
|
|||||||
>
|
>
|
||||||
<Download className="h-4 w-4" strokeWidth={1.75} />
|
<Download className="h-4 w-4" strokeWidth={1.75} />
|
||||||
</Button>
|
</Button>
|
||||||
<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>
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<header data-testid="claim-drawer-header">
|
||||||
|
<DrillDrawerHeader
|
||||||
|
eyebrow="Claim"
|
||||||
|
title={claim.id}
|
||||||
|
onClose={onClose}
|
||||||
|
action={action}
|
||||||
|
/>
|
||||||
</header>
|
</header>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user