Files
DentalManagement2025/apps/Frontend/src/components/reports/patients-balances-list.tsx

140 lines
4.2 KiB
TypeScript

import React from "react";
import { DollarSign } from "lucide-react";
import PaginationControls from "./pagination-controls";
export type GenericRow = {
id: string | number;
name: string;
currentBalance: number;
totalCharges: number;
totalPayments: number;
};
export default function PatientsBalancesList({
rows,
reportType,
loading,
error,
emptyMessage,
pageIndex = 1, // 1-based
perPage = 10,
total, // optional totalCount from backend
onPrev,
onNext,
hasPrev,
hasNext,
headerRight, // optional UI node to render in header
}: {
rows: GenericRow[];
reportType?: string | null;
loading?: boolean;
error?: string | boolean;
emptyMessage?: string;
// cursor props (required)
pageIndex?: number;
perPage?: number;
total?: number | undefined;
onPrev: () => void;
onNext: () => void;
hasPrev: boolean;
hasNext: boolean;
headerRight?: React.ReactNode;
}) {
const fmt = (v: number) =>
new Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD",
}).format(v);
const reportTypeTitle = (rt?: string | null) => {
switch (rt) {
case "patients_with_balance":
return "Patients with Outstanding Balances";
case "patients_no_balance":
return "Patients with Zero Balance";
case "monthly_collections":
return "Monthly Collections";
case "collections_by_doctor":
return "Collections by Doctor";
default:
return "Balances";
}
};
return (
<div className="space-y-4">
<div className="bg-white rounded-lg border">
<div className="px-4 py-3 border-b bg-gray-50 flex items-center justify-between">
<h3 className="font-medium text-gray-900">
{reportTypeTitle(reportType)}
</h3>
{/* headerRight rendered here (if provided) */}
<div>{headerRight ?? null}</div>
</div>
<div className="divide-y min-h-[120px]">
{loading ? (
<div className="p-8 text-center text-gray-600">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary mx-auto mb-4"></div>
<div>Loading {reportType ?? "data"}</div>
</div>
) : error ? (
<div className="p-8 text-center text-red-600">
<div className="mb-2 font-semibold">Could not fetch data</div>
<div className="text-sm text-red-500">
{typeof error === "string"
? error
: "An error occurred while loading the report."}
</div>
</div>
) : rows.length === 0 ? (
<div className="p-8 text-center text-gray-500">
<DollarSign className="h-12 w-12 mx-auto mb-3 text-gray-300" />
<p>{emptyMessage ?? "No rows for this report."}</p>
</div>
) : (
rows.map((r) => (
<div key={String(r.id)} className="p-4 hover:bg-gray-50">
<div className="flex justify-between items-center">
<div>
<h4 className="font-medium text-gray-900">{r.name}</h4>
<p className="text-sm text-gray-500">ID: {r.id}</p>
</div>
<div className="text-right">
<div
className={`text-lg font-semibold ${
r.currentBalance > 0 ? "text-red-600" : "text-green-600"
}`}
>
{fmt(r.currentBalance)}
</div>
<div className="text-sm text-gray-500">
Charges: {fmt(r.totalCharges)} · Collected:{" "}
{fmt(r.totalPayments)}
</div>
</div>
</div>
</div>
))
)}
</div>
{/* Cursor pagination footer (cursor-only) */}
<div className="bg-white px-4 py-3 border-t border-gray-200">
<PaginationControls
pageIndex={pageIndex}
perPage={perPage}
total={total}
onPrev={onPrev}
onNext={onNext}
hasPrev={hasPrev}
hasNext={hasNext}
/>
</div>
</div>
</div>
);
}