diff --git a/src/pages/Claims.tsx b/src/pages/Claims.tsx index 7ddae40..66bc550 100644 --- a/src/pages/Claims.tsx +++ b/src/pages/Claims.tsx @@ -18,55 +18,76 @@ import { } from "@/components/ui/table"; import { ClaimStatusBadge } from "@/components/StatusBadge"; import { NewClaimDialog } from "@/components/NewClaimDialog"; +import { Skeleton } from "@/components/ui/skeleton"; +import { EmptyState } from "@/components/ui/empty-state"; +import { ErrorState } from "@/components/ui/error-state"; +import { FilterChips, type FilterChipOption } from "@/components/ui/filter-chips"; +import { Pagination } from "@/components/ui/pagination"; +import { useClaims } from "@/hooks/useClaims"; import { useAppStore } from "@/store"; import { fmt } from "@/lib/format"; import type { ClaimStatus } from "@/types"; +import { cn } from "@/lib/utils"; const ALL = "all" as const; -const statuses: (ClaimStatus | typeof ALL)[] = [ - ALL, - "submitted", - "accepted", - "denied", - "paid", - "pending", +const PAGE_SIZE = 25; + +const STATUS_OPTIONS: FilterChipOption[] = [ + { value: "all", label: "All" }, + { value: "submitted", label: "Submitted" }, + { value: "accepted", label: "Accepted" }, + { value: "denied", label: "Denied" }, + { value: "paid", label: "Paid" }, + { value: "pending", label: "Pending" }, ]; export function Claims() { - const claims = useAppStore((s) => s.claims); - const providers = useAppStore((s) => s.providers); - const [query, setQuery] = useState(""); const [status, setStatus] = useState(ALL); const [npi, setNpi] = useState(ALL); + const [query, setQuery] = useState(""); + const [page, setPage] = useState(1); const searchRef = useRef(null); + const providers = useAppStore((s) => s.providers); const providerMap = useMemo( () => Object.fromEntries(providers.map((p) => [p.npi, p])), [providers] ); - const filtered = useMemo(() => { + const params = { + status: status === ALL ? undefined : status, + provider_npi: npi === ALL ? undefined : npi, + sort: "billedAmount", + order: "desc" as const, + limit: PAGE_SIZE, + offset: (page - 1) * PAGE_SIZE, + }; + + const { data, isLoading, isError, error, refetch } = useClaims(params); + const items = data?.items ?? []; + + const totals = useMemo( + () => ({ + billed: items.reduce((s, c) => s + c.billedAmount, 0), + received: items.reduce((s, c) => s + c.receivedAmount, 0), + }), + [items] + ); + + // Local search filter on the current page (cheap; no extra fetch). + const visible = useMemo(() => { const q = query.trim().toLowerCase(); - return claims.filter((c) => { - if (status !== ALL && c.status !== status) return false; - if (npi !== ALL && c.providerNpi !== npi) return false; - if (!q) return true; - return ( + if (!q) return items; + return items.filter( + (c) => c.id.toLowerCase().includes(q) || c.patientName.toLowerCase().includes(q) || c.payerName.toLowerCase().includes(q) || c.cptCode.toLowerCase().includes(q) - ); - }); - }, [claims, query, status, npi]); + ); + }, [items, query]); - const totals = useMemo( - () => ({ - billed: filtered.reduce((s, c) => s + c.billedAmount, 0), - received: filtered.reduce((s, c) => s + c.receivedAmount, 0), - }), - [filtered] - ); + const filtered = status !== ALL || npi !== ALL; return (
@@ -81,6 +102,14 @@ export function Claims() { + {isError ? ( + refetch()} + /> + ) : null} +
@@ -101,35 +130,16 @@ export function Claims() { > - ) : ( - - )} + ) : null}
- -
+
+ { + setStatus((v as ClaimStatus | null) ?? ALL); + setPage(1); + }} + eyebrow="Status" + /> +
+
- {fmt.num(filtered.length)} + {fmt.num(data?.total ?? 0)} {" "} claims @@ -167,68 +189,92 @@ export function Claims() {
- - - - Claim - Patient - Provider - Payer - Billed - Received - Status - Submitted - - - - {filtered.length === 0 ? ( - - - No claims match the current filters. - - - ) : ( - filtered.map((c) => { - const provider = providerMap[c.providerNpi]; - return ( - - -
{c.id}
-
- CPT {c.cptCode} -
-
- {c.patientName} - -
{provider?.name ?? "Unknown"}
-
- {c.providerNpi} -
-
- - {c.payerName} - - - {fmt.usd(c.billedAmount)} - - - {c.receivedAmount > 0 ? fmt.usd(c.receivedAmount) : "—"} - - - - - - {fmt.dateShort(c.submissionDate)} - -
- ); - }) - )} -
-
+ {isLoading ? ( +
+ {Array.from({ length: 5 }).map((_, i) => ( + + ))} +
+ ) : items.length === 0 ? ( + + ) : ( + <> + + + + Claim + Patient + Provider + Payer + Billed + Received + Status + Submitted + + + + {visible.map((c) => { + const provider = providerMap[c.providerNpi]; + return ( + + +
{c.id}
+
+ CPT {c.cptCode} +
+
+ {c.patientName} + +
{provider?.name ?? "Unknown"}
+
+ {c.providerNpi} +
+
+ + {c.payerName} + + + {fmt.usd(c.billedAmount)} + + + {c.receivedAmount > 0 ? fmt.usd(c.receivedAmount) : "—"} + + + + + + {fmt.dateShort(c.submissionDate)} + +
+ ); + })} +
+
+
+ +
+ + )}
);