109 lines
4.2 KiB
TypeScript
109 lines
4.2 KiB
TypeScript
import { Dialog, DialogContent } from "@/components/ui/dialog";
|
|
import { Tabs } from "@/components/ui/tabs";
|
|
import { ApiError } from "@/lib/api";
|
|
import { DrillDrawerHeader } from "@/components/drill/DrillDrawerHeader";
|
|
import { Skeleton } from "@/components/ui/skeleton";
|
|
import { useProviderDetail } from "@/hooks/useProviderDetail";
|
|
import { ProviderOverview } from "./ProviderOverview";
|
|
import { ProviderRecentClaims } from "./ProviderRecentClaims";
|
|
import { ProviderRecentActivity } from "./ProviderRecentActivity";
|
|
import { ProviderDrawerError } from "./ProviderDrawerError";
|
|
|
|
interface Props {
|
|
npi: string | null;
|
|
onClose: () => void;
|
|
}
|
|
|
|
/**
|
|
* Provider drill-down drawer (SP21 Task 2.2 + 3.1).
|
|
*
|
|
* Side-panel shell that consumes ``useProviderDetail(npi)`` and renders
|
|
* a three-tab body:
|
|
*
|
|
* - Overview — base provider fields (identity + activity shape)
|
|
* - Claims — top-10 claims joined to this provider
|
|
* (extended `/api/config/providers/{npi}.recent_claims`,
|
|
* populated by Task 1.6)
|
|
* - Activity — top-10 events joined to this provider's claims
|
|
* (extended `/api/config/providers/{npi}.recent_activity`)
|
|
*
|
|
* Layout mirrors the ClaimDrawer / RemitDrawer pattern: Radix Dialog
|
|
* repositioned to the right edge as a fixed-height side panel, with
|
|
* the shared ``DrillDrawerHeader`` on top and a scrollable body below.
|
|
* The DialogContent is the flex parent; the header and body stack
|
|
* naturally inside it without an explicit `calc(100% - 64px)` height
|
|
* that would couple to DrillDrawerHeader's padding/font sizes.
|
|
*
|
|
* Error branching mirrors the peer drawers:
|
|
* - `ApiError(404)` → "not_found" (no retry, the provider is gone)
|
|
* - anything else → "network" (retry available)
|
|
* - demo mode + unknown NPI → ApiError(404), routes to the same
|
|
* "not_found" branch (the hook raises `ApiError(404, "Provider not
|
|
* found")` in this case, matching the real backend 404 path).
|
|
*/
|
|
export function ProviderDrawer({ npi, onClose }: Props) {
|
|
const { data, isLoading, isError, error, refetch } = useProviderDetail(npi);
|
|
|
|
const errorKind: "not_found" | "network" | null = isError
|
|
? error instanceof ApiError && error.status === 404
|
|
? "not_found"
|
|
: "network"
|
|
: null;
|
|
|
|
return (
|
|
<Dialog open={npi !== null} onOpenChange={(o) => { if (!o) onClose(); }}>
|
|
<DialogContent
|
|
className="fixed right-0 top-0 flex h-full w-full max-w-2xl flex-col translate-x-0 translate-y-0 rounded-none border-l border-border bg-card p-0"
|
|
aria-describedby={undefined}
|
|
>
|
|
{errorKind ? (
|
|
<ProviderDrawerError
|
|
kind={errorKind}
|
|
onRetry={() => {
|
|
void refetch();
|
|
}}
|
|
onClose={onClose}
|
|
/>
|
|
) : isLoading || !data ? (
|
|
<div className="flex h-full flex-col overflow-y-auto">
|
|
<DrillDrawerHeader
|
|
eyebrow="Provider"
|
|
title="Loading…"
|
|
onClose={onClose}
|
|
/>
|
|
<div className="space-y-2 p-6">
|
|
<Skeleton variant="row" />
|
|
<Skeleton variant="row" />
|
|
<Skeleton variant="row" />
|
|
</div>
|
|
</div>
|
|
) : (
|
|
<div className="flex h-full flex-col overflow-y-auto">
|
|
<DrillDrawerHeader
|
|
eyebrow="Provider"
|
|
title={data.name}
|
|
onClose={onClose}
|
|
/>
|
|
<Tabs.Root defaultValue="overview" className="px-6 py-4">
|
|
<Tabs.List>
|
|
<Tabs.Trigger value="overview">Overview</Tabs.Trigger>
|
|
<Tabs.Trigger value="claims">Claims</Tabs.Trigger>
|
|
<Tabs.Trigger value="activity">Activity</Tabs.Trigger>
|
|
</Tabs.List>
|
|
<Tabs.Content value="overview">
|
|
<ProviderOverview provider={data} />
|
|
</Tabs.Content>
|
|
<Tabs.Content value="claims">
|
|
<ProviderRecentClaims provider={data} />
|
|
</Tabs.Content>
|
|
<Tabs.Content value="activity">
|
|
<ProviderRecentActivity provider={data} />
|
|
</Tabs.Content>
|
|
</Tabs.Root>
|
|
</div>
|
|
)}
|
|
</DialogContent>
|
|
</Dialog>
|
|
);
|
|
}
|