import React, { useCallback, useEffect, useState } from "react"; import { useQuery } from "@tanstack/react-query"; import { apiRequest } from "@/lib/queryClient"; import PatientsBalancesList, { GenericRow } from "./patients-balances-list"; import { Card, CardContent } from "@/components/ui/card"; import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import ExportReportButton from "./export-button"; type StaffOption = { id: number; name: string }; function fmtCurrency(v: number) { return new Intl.NumberFormat("en-US", { style: "currency", currency: "USD", }).format(v); } export default function CollectionsByDoctorReport({ startDate, endDate, }: { startDate: string; endDate: string; }) { const [staffId, setStaffId] = useState(""); const perPage = 10; const [cursorStack, setCursorStack] = useState<(string | null)[]>([null]); const [cursorIndex, setCursorIndex] = useState(0); const currentCursor = cursorStack[cursorIndex] ?? null; const pageIndex = cursorIndex + 1; // load staffs list for selector const { data: staffs } = useQuery({ queryKey: ["staffs"], queryFn: async () => { const res = await apiRequest("GET", "/api/staffs"); if (!res.ok) { const b = await res .json() .catch(() => ({ message: "Failed to load staffs" })); throw new Error(b.message || "Failed to load staffs"); } return res.json(); }, staleTime: 60_000, }); // --- balances query (paged rows) --- const { data: balancesResult, isLoading: isLoadingBalances, isError: isErrorBalances, refetch: refetchBalances, isFetching: isFetchingBalances, } = useQuery< { balances: any[]; totalCount: number; nextCursor: string | null; hasMore: boolean; }, Error >({ queryKey: [ "collections-by-doctor-balances", staffId, currentCursor, perPage, startDate, endDate, ], queryFn: async () => { const params = new URLSearchParams(); params.set("limit", String(perPage)); if (currentCursor) params.set("cursor", currentCursor); if (staffId) params.set("staffId", staffId); if (startDate) params.set("from", startDate); if (endDate) params.set("to", endDate); const res = await apiRequest( "GET", `/api/payments-reports/by-doctor/balances?${params.toString()}` ); if (!res.ok) { const b = await res .json() .catch(() => ({ message: "Failed to load collections balances" })); throw new Error(b.message || "Failed to load collections balances"); } return res.json(); }, enabled: Boolean(staffId), }); // --- summary query (staff summary) --- const { data: summaryData, isLoading: isLoadingSummary, isError: isErrorSummary, refetch: refetchSummary, isFetching: isFetchingSummary, } = useQuery< { totalPatients: number; totalOutstanding: number | string; totalCollected: number | string; patientsWithBalance: number; }, Error >({ queryKey: ["collections-by-doctor-summary", staffId, startDate, endDate], queryFn: async () => { const params = new URLSearchParams(); if (staffId) params.set("staffId", staffId); if (startDate) params.set("from", startDate); if (endDate) params.set("to", endDate); const res = await apiRequest( "GET", `/api/payments-reports/by-doctor/summary?${params.toString()}` ); if (!res.ok) { const b = await res .json() .catch(() => ({ message: "Failed to load collections summary" })); throw new Error(b.message || "Failed to load collections summary"); } return res.json(); }, enabled: Boolean(staffId), }); const balances = balancesResult?.balances ?? []; const totalCount = balancesResult?.totalCount ?? undefined; const serverNextCursor = balancesResult?.nextCursor ?? null; const hasMore = Boolean(balancesResult?.hasMore ?? false); const summary = summaryData ?? null; const isLoadingRows = isLoadingBalances; const isErrorRows = isErrorBalances; const isFetching = isFetchingBalances || isFetchingSummary; // Reset pagination when filters change useEffect(() => { setCursorStack([null]); setCursorIndex(0); }, [staffId, startDate, endDate]); const handlePrev = useCallback(() => { setCursorIndex((i) => Math.max(0, i - 1)); }, []); const handleNext = useCallback(() => { const idx = cursorIndex; const isLastKnown = idx === cursorStack.length - 1; if (isLastKnown) { if (serverNextCursor) { setCursorStack((s) => [...s, serverNextCursor]); setCursorIndex((i) => i + 1); // React Query will fetch automatically because queryKey includes currentCursor } } else { setCursorIndex((i) => i + 1); } }, [cursorIndex, cursorStack.length, serverNextCursor]); // Map server rows to GenericRow const genericRows: GenericRow[] = balances.map((r) => { const totalCharges = Number(r.totalCharges ?? 0); const totalPayments = Number(r.totalPayments ?? 0); const currentBalance = Number(r.currentBalance ?? 0); const name = `${r.firstName ?? ""} ${r.lastName ?? ""}`.trim() || "Unknown"; return { id: String(r.patientId), name, currentBalance, totalCharges, totalPayments, }; }); return (
{/* Summary card (time-window based) */} {staffId && (

Doctor summary

Data covers the selected time frame

{summary ? Number(summary.totalPatients ?? 0) : "—"}

Total Patients (in window)

{summary ? Number(summary.patientsWithBalance ?? 0) : "—"}

With Balance

{summary ? Math.max( 0, Number(summary.totalPatients ?? 0) - Number(summary.patientsWithBalance ?? 0) ) : "—"}

Zero Balance

{summary ? fmtCurrency(Number(summary.totalOutstanding ?? 0)) : "—"}

Outstanding

{summary ? fmtCurrency(Number(summary.totalCollected ?? 0)) : "—"}

Collected

)} {/* List (shows all patients under doctor but per-row totals are time-filtered) */} {!staffId ? (
Please select a doctor to load collections.
) : ( 0} hasNext={hasMore} headerRight={ } /> )}
); }