Files
cyclone/src/pages/Providers.tsx
T

109 lines
4.3 KiB
TypeScript

import { Building2, MapPin, Phone } from "lucide-react";
import { Skeleton } from "@/components/ui/skeleton";
import { EmptyState } from "@/components/ui/empty-state";
import { ErrorState } from "@/components/ui/error-state";
import { PageHeader } from "@/components/PageHeader";
import { ProviderDrawer } from "@/components/ProviderDrawer";
import { useProviderDrawerUrlState } from "@/hooks/useProviderDrawerUrlState";
import { useProviders } from "@/hooks/useProviders";
import { fmt } from "@/lib/format";
export function Providers() {
const { data, isLoading, isError, error, refetch } = useProviders();
const items = data?.items ?? [];
const { providerNpi, open, close } = useProviderDrawerUrlState();
return (
<div className="space-y-6 lg:space-y-8 animate-fade-in">
<PageHeader
eyebrow="Providers"
title={<>Provider <span className="display italic text-muted-foreground">directory</span></>}
subtitle="NPIs registered with the clearinghouse for 837P submission and 835 remittance retrieval."
/>
{isError ? (
<ErrorState
message="Couldn't load providers from the backend."
detail={error instanceof Error ? error.message : String(error)}
onRetry={() => refetch()}
/>
) : null}
{isLoading ? (
<div className="grid gap-4 md:grid-cols-2 xl:grid-cols-3">
{Array.from({ length: 6 }).map((_, i) => (
<Skeleton key={i} variant="default" height={180} />
))}
</div>
) : items.length === 0 ? (
<div className="surface rounded-xl">
<EmptyState
eyebrow="Providers · directory empty"
message="Providers appear here as soon as an 837P file is parsed."
/>
</div>
) : (
<div className="grid gap-3.5 md:grid-cols-2 xl:grid-cols-3">
{items.map((p) => (
<article
key={p.npi}
onClick={() => open(p.npi)}
onKeyDown={(e) => {
if (e.key === "Enter") open(p.npi);
}}
role="button"
tabIndex={0}
aria-label={`View provider ${p.name}`}
className="group drillable surface-2 rounded-xl p-5 flex flex-col gap-4 transition-colors hover:bg-muted/20 cursor-pointer"
>
<div className="flex items-start gap-3">
<div className="h-10 w-10 rounded-md bg-muted/60 ring-1 ring-inset ring-border/40 flex items-center justify-center text-foreground">
<Building2 className="h-4 w-4" strokeWidth={1.5} />
</div>
<div className="min-w-0 flex-1">
<h3 className="display text-[15px] text-foreground tracking-tight">
{p.name}
</h3>
<p className="mono text-[10.5px] text-muted-foreground mt-0.5">
NPI {p.npi} · TIN {p.taxId}
</p>
</div>
</div>
<div className="space-y-1.5 text-[12.5px] text-muted-foreground">
<div className="flex items-start gap-2">
<MapPin className="h-3.5 w-3.5 mt-0.5 shrink-0" strokeWidth={1.75} />
<span>
{p.address}, {p.city}, {p.state} {p.zip}
</span>
</div>
<div className="flex items-center gap-2">
<Phone className="h-3.5 w-3.5" strokeWidth={1.75} />
<span className="mono">{p.phone}</span>
</div>
</div>
<div className="grid grid-cols-2 gap-4 pt-4 border-t border-border/30">
<div>
<div className="eyebrow">Claims</div>
<div className="display mono text-[20px] leading-none mt-2">
{fmt.num(p.claimCount)}
</div>
</div>
<div>
<div className="eyebrow">Outstanding AR</div>
<div className="display mono text-[20px] leading-none mt-2">
{fmt.usd(p.outstandingAr)}
</div>
</div>
</div>
</article>
))}
</div>
)}
<ProviderDrawer npi={providerNpi} onClose={close} />
</div>
);
}