Files
cyclone/src/components/drill/PayerPeekContent.tsx
T

118 lines
3.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { Link } from "react-router-dom";
import { Skeleton } from "@/components/ui/skeleton";
import { fmt } from "@/lib/format";
import { usePayerSummary } from "@/hooks/usePayerSummary";
import type { PayerSummary } from "@/lib/api";
interface Props {
payerId: string;
}
/**
* Peek body for a payer — aggregate stats card shown inside the
* centered PeekModal (SP21 universal drill-down).
*
* Owns its own fetch via `usePayerSummary`; the parent PeekModal only
* concerns itself with open/close + title. We deliberately do NOT show
* an error state here — the peek is a low-stakes summary, so a silent
* retry + skeleton on failure is acceptable (the parent modal still
* closes correctly).
*
* `fmt.pct` does not multiply by 100 (it's just `n.toFixed(d)%`), so the
* API's fraction `denial_rate` (01) needs `* 100` before formatting —
* otherwise the UI would render "0.0%" for everything.
*/
export function PayerPeekContent({ payerId }: Props) {
const { data, isLoading } = usePayerSummary(payerId);
if (isLoading || !data) {
return (
<div className="space-y-2" aria-busy="true">
<Skeleton variant="row" />
<Skeleton variant="row" />
<Skeleton variant="row" />
</div>
);
}
return <Loaded payer={data} />;
}
function Loaded({ payer }: { payer: PayerSummary }) {
return (
<div className="space-y-4">
<div className="flex items-baseline justify-between gap-3">
<div className="display text-[15px] text-foreground truncate">
{payer.name}
</div>
<div className="mono text-[11px] text-muted-foreground">
{payer.payer_id}
</div>
</div>
<div className="grid grid-cols-2 gap-3">
<Stat label="Claims" value={fmt.num(payer.claim_count)} />
<Stat
label="Denial rate"
value={fmt.pct(payer.denial_rate * 100)}
/>
<Stat
label="Billed"
value={fmt.usd(payer.billed_total)}
accent="accent"
/>
<Stat
label="Received"
value={fmt.usd(payer.received_total)}
accent="success"
/>
</div>
{payer.top_providers.length > 0 ? (
<div>
<div className="eyebrow mb-1.5">Top providers</div>
<ul className="text-[12.5px] space-y-1">
{payer.top_providers.slice(0, 3).map((p) => (
<li
key={p.npi}
className="flex justify-between gap-3"
>
<span className="mono">{p.npi}</span>
<span className="mono text-muted-foreground">
{fmt.num(p.count)} claims
</span>
</li>
))}
</ul>
</div>
) : null}
<Link
to={`/claims?payer=${encodeURIComponent(payer.payer_id)}`}
className="text-[12.5px] text-accent hover:underline"
>
View all claims
</Link>
</div>
);
}
function Stat({
label,
value,
accent,
}: {
label: string;
value: string;
accent?: "accent" | "success" | "warning";
}) {
const color =
accent === "success"
? "text-[hsl(var(--success))]"
: accent === "warning"
? "text-[hsl(var(--warning))]"
: accent === "accent"
? "text-accent"
: "text-foreground";
return (
<div>
<div className="eyebrow">{label}</div>
<div className={`display mono text-[16px] mt-1 ${color}`}>{value}</div>
</div>
);
}