feat(frontend): refactor Providers page to useProviders + primitives
This commit is contained in:
+74
-47
@@ -1,9 +1,13 @@
|
|||||||
import { Building2, MapPin, Phone } from "lucide-react";
|
import { Building2, MapPin, Phone } from "lucide-react";
|
||||||
import { useAppStore } from "@/store";
|
import { Skeleton } from "@/components/ui/skeleton";
|
||||||
|
import { EmptyState } from "@/components/ui/empty-state";
|
||||||
|
import { ErrorState } from "@/components/ui/error-state";
|
||||||
|
import { useProviders } from "@/hooks/useProviders";
|
||||||
import { fmt } from "@/lib/format";
|
import { fmt } from "@/lib/format";
|
||||||
|
|
||||||
export function Providers() {
|
export function Providers() {
|
||||||
const providers = useAppStore((s) => s.providers);
|
const { data, isLoading, isError, error, refetch } = useProviders();
|
||||||
|
const items = data?.items ?? [];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-8 animate-fade-in">
|
<div className="space-y-8 animate-fade-in">
|
||||||
@@ -19,60 +23,83 @@ export function Providers() {
|
|||||||
</p>
|
</p>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div className="grid gap-4 md:grid-cols-2 xl:grid-cols-3">
|
{isError ? (
|
||||||
{providers.map((p) => (
|
<ErrorState
|
||||||
<article
|
message="Couldn't load providers from the backend."
|
||||||
key={p.npi}
|
detail={error instanceof Error ? error.message : String(error)}
|
||||||
className="surface rounded-xl p-6 flex flex-col gap-4 hover:bg-muted/20 transition-colors"
|
onRetry={() => refetch()}
|
||||||
>
|
/>
|
||||||
<div className="flex items-start gap-3">
|
) : null}
|
||||||
<div className="h-10 w-10 rounded-lg bg-muted/60 flex items-center justify-center text-foreground">
|
|
||||||
<Building2 className="h-5 w-5" strokeWidth={1.5} />
|
|
||||||
</div>
|
|
||||||
<div className="min-w-0 flex-1">
|
|
||||||
<h3 className="text-[15px] font-semibold tracking-tight">
|
|
||||||
{p.name}
|
|
||||||
</h3>
|
|
||||||
<p className="text-[11px] text-muted-foreground num mt-0.5">
|
|
||||||
NPI {p.npi} · TIN {p.taxId}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-1.5 text-[13px] text-muted-foreground">
|
{isLoading ? (
|
||||||
<div className="flex items-center gap-2">
|
<div className="grid gap-4 md:grid-cols-2 xl:grid-cols-3">
|
||||||
<MapPin className="h-3.5 w-3.5" strokeWidth={1.75} />
|
{Array.from({ length: 6 }).map((_, i) => (
|
||||||
<span>
|
<Skeleton key={i} variant="default" height={180} />
|
||||||
{p.address}, {p.city}, {p.state} {p.zip}
|
))}
|
||||||
</span>
|
</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-4 md:grid-cols-2 xl:grid-cols-3">
|
||||||
|
{items.map((p) => (
|
||||||
|
<article
|
||||||
|
key={p.npi}
|
||||||
|
className="surface rounded-xl p-6 flex flex-col gap-4 hover:bg-muted/20 transition-colors"
|
||||||
|
>
|
||||||
|
<div className="flex items-start gap-3">
|
||||||
|
<div className="h-10 w-10 rounded-lg bg-muted/60 flex items-center justify-center text-foreground">
|
||||||
|
<Building2 className="h-5 w-5" strokeWidth={1.5} />
|
||||||
|
</div>
|
||||||
|
<div className="min-w-0 flex-1">
|
||||||
|
<h3 className="text-[15px] font-semibold tracking-tight">
|
||||||
|
{p.name}
|
||||||
|
</h3>
|
||||||
|
<p className="text-[11px] text-muted-foreground num mt-0.5">
|
||||||
|
NPI {p.npi} · TIN {p.taxId}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<Phone className="h-3.5 w-3.5" strokeWidth={1.75} />
|
|
||||||
<span className="num">{p.phone}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="grid grid-cols-2 gap-4 pt-4 border-t border-border/40">
|
<div className="space-y-1.5 text-[13px] text-muted-foreground">
|
||||||
<div>
|
<div className="flex items-center gap-2">
|
||||||
<div className="text-[11px] uppercase tracking-wider text-muted-foreground">
|
<MapPin className="h-3.5 w-3.5" strokeWidth={1.75} />
|
||||||
Claims
|
<span>
|
||||||
|
{p.address}, {p.city}, {p.state} {p.zip}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="display text-[20px] leading-none mt-1.5">
|
<div className="flex items-center gap-2">
|
||||||
{fmt.num(p.claimCount)}
|
<Phone className="h-3.5 w-3.5" strokeWidth={1.75} />
|
||||||
|
<span className="num">{p.phone}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
|
||||||
<div className="text-[11px] uppercase tracking-wider text-muted-foreground">
|
<div className="grid grid-cols-2 gap-4 pt-4 border-t border-border/40">
|
||||||
Outstanding AR
|
<div>
|
||||||
|
<div className="text-[11px] uppercase tracking-wider text-muted-foreground">
|
||||||
|
Claims
|
||||||
|
</div>
|
||||||
|
<div className="display text-[20px] leading-none mt-1.5">
|
||||||
|
{fmt.num(p.claimCount)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="display text-[20px] leading-none mt-1.5">
|
<div>
|
||||||
{fmt.usd(p.outstandingAr)}
|
<div className="text-[11px] uppercase tracking-wider text-muted-foreground">
|
||||||
|
Outstanding AR
|
||||||
|
</div>
|
||||||
|
<div className="display text-[20px] leading-none mt-1.5">
|
||||||
|
{fmt.usd(p.outstandingAr)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</article>
|
||||||
</article>
|
))}
|
||||||
))}
|
</div>
|
||||||
</div>
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user