feat: add Check Payments Online section and multi-select checkboxes on payments page

- Added Check Payments Online card above Payment's Records with From/To date pickers and Check All MH Payment button (logic TBD)
- Added multi-select checkboxes to each payment record row with select-all header checkbox
- When records are checked, a Check MH Payment action bar appears with count and clear option (logic TBD)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Gitead
2026-05-06 07:54:24 -04:00
parent ceb95f1915
commit 8c162d7040
2 changed files with 169 additions and 9 deletions

View File

@@ -96,9 +96,8 @@ export default function PaymentsRecentTable({
const [currentPayment, setCurrentPayment] = useState< const [currentPayment, setCurrentPayment] = useState<
PaymentWithExtras | undefined PaymentWithExtras | undefined
>(undefined); >(undefined);
const [selectedPaymentId, setSelectedPaymentId] = useState<number | null>( const [selectedPaymentId, setSelectedPaymentId] = useState<number | null>(null);
null const [checkedPaymentIds, setCheckedPaymentIds] = useState<Set<number>>(new Set());
);
const [isRevertOpen, setIsRevertOpen] = useState(false); const [isRevertOpen, setIsRevertOpen] = useState(false);
const [revertPaymentId, setRevertPaymentId] = useState<number | null>(null); const [revertPaymentId, setRevertPaymentId] = useState<number | null>(null);
@@ -112,6 +111,18 @@ export default function PaymentsRecentTable({
} }
}; };
const handleToggleCheck = (paymentId: number) => {
setCheckedPaymentIds((prev) => {
const next = new Set(prev);
if (next.has(paymentId)) {
next.delete(paymentId);
} else {
next.add(paymentId);
}
return next;
});
};
const queryKey = qkPaymentsRecent({ const queryKey = qkPaymentsRecent({
patientId: patientId ?? undefined, patientId: patientId ?? undefined,
page: currentPage, page: currentPage,
@@ -138,6 +149,25 @@ export default function PaymentsRecentTable({
placeholderData: { payments: [], totalCount: 0 }, placeholderData: { payments: [], totalCount: 0 },
}); });
const currentPageIds = (paymentsData?.payments ?? []).map((p) => p.id);
const allOnPageChecked =
currentPageIds.length > 0 &&
currentPageIds.every((id) => checkedPaymentIds.has(id));
const someOnPageChecked =
!allOnPageChecked && currentPageIds.some((id) => checkedPaymentIds.has(id));
const handleToggleAll = () => {
setCheckedPaymentIds((prev) => {
const next = new Set(prev);
if (allOnPageChecked) {
currentPageIds.forEach((id) => next.delete(id));
} else {
currentPageIds.forEach((id) => next.add(id));
}
return next;
});
};
const updatePaymentMutation = useMutation({ const updatePaymentMutation = useMutation({
mutationFn: async (data: NewTransactionPayload) => { mutationFn: async (data: NewTransactionPayload) => {
const response = await apiRequest( const response = await apiRequest(
@@ -478,11 +508,46 @@ export default function PaymentsRecentTable({
return ( return (
<div className="bg-white shadow rounded-lg overflow-hidden"> <div className="bg-white shadow rounded-lg overflow-hidden">
{/* Check MH Payment action bar */}
{allowCheckbox && checkedPaymentIds.size > 0 && (
<div className="flex items-center gap-3 px-4 py-2 bg-blue-50 border-b border-blue-200">
<span className="text-sm text-blue-700 font-medium">
{checkedPaymentIds.size} record{checkedPaymentIds.size > 1 ? "s" : ""} selected
</span>
<Button
size="sm"
variant="default"
onClick={() => {
// Logic to be defined later
}}
>
Check MH Payment
</Button>
<Button
size="sm"
variant="ghost"
className="text-blue-600"
onClick={() => setCheckedPaymentIds(new Set())}
>
Clear
</Button>
</div>
)}
<div className="overflow-x-auto"> <div className="overflow-x-auto">
<Table> <Table>
<TableHeader> <TableHeader>
<TableRow> <TableRow>
{allowCheckbox && <TableHead>Select</TableHead>} {allowCheckbox && (
<TableHead className="w-10">
<Checkbox
checked={allOnPageChecked}
data-state={someOnPageChecked ? "indeterminate" : undefined}
onCheckedChange={handleToggleAll}
aria-label="Select all on page"
/>
</TableHead>
)}
<TableHead>Payment ID</TableHead> <TableHead>Payment ID</TableHead>
<TableHead>Claim ID</TableHead> <TableHead>Claim ID</TableHead>
<TableHead>Patient Name</TableHead> <TableHead>Patient Name</TableHead>
@@ -538,10 +603,11 @@ export default function PaymentsRecentTable({
return ( return (
<TableRow key={payment.id}> <TableRow key={payment.id}>
{allowCheckbox && ( {allowCheckbox && (
<TableCell> <TableCell className="w-10">
<Checkbox <Checkbox
checked={selectedPaymentId === payment.id} checked={checkedPaymentIds.has(payment.id)}
onCheckedChange={() => handleSelectPayment(payment)} onCheckedChange={() => handleToggleCheck(payment.id)}
aria-label={`Select payment ${payment.id}`}
/> />
</TableCell> </TableCell>
)} )}

View File

@@ -6,7 +6,8 @@ import {
CardContent, CardContent,
CardDescription, CardDescription,
} from "@/components/ui/card"; } from "@/components/ui/card";
import { DollarSign } from "lucide-react"; import { Button } from "@/components/ui/button";
import { DollarSign, CalendarIcon } from "lucide-react";
import { import {
Select, Select,
SelectContent, SelectContent,
@@ -14,6 +15,12 @@ import {
SelectTrigger, SelectTrigger,
SelectValue, SelectValue,
} from "@/components/ui/select"; } from "@/components/ui/select";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import { Calendar } from "@/components/ui/calendar";
import PaymentsRecentTable from "@/components/payments/payments-recent-table"; import PaymentsRecentTable from "@/components/payments/payments-recent-table";
import PaymentsOfPatientModal from "@/components/payments/payments-of-patient-table"; import PaymentsOfPatientModal from "@/components/payments/payments-of-patient-table";
import PaymentOCRBlock from "@/components/payments/payment-ocr-block"; import PaymentOCRBlock from "@/components/payments/payment-ocr-block";
@@ -26,6 +33,12 @@ import PaymentEditModal from "@/components/payments/payment-edit-modal";
export default function PaymentsPage() { export default function PaymentsPage() {
const [paymentPeriod, setPaymentPeriod] = useState<string>("all-time"); const [paymentPeriod, setPaymentPeriod] = useState<string>("all-time");
// Check Payments Online date range
const [mhFromDate, setMhFromDate] = useState<Date | undefined>(undefined);
const [mhToDate, setMhToDate] = useState<Date | undefined>(undefined);
const [fromCalendarOpen, setFromCalendarOpen] = useState(false);
const [toCalendarOpen, setToCalendarOpen] = useState(false);
// for auto-open from appointment redirect // for auto-open from appointment redirect
const [location] = useLocation(); const [location] = useLocation();
const [initialPatientForModal, setInitialPatientForModal] = const [initialPatientForModal, setInitialPatientForModal] =
@@ -212,6 +225,87 @@ export default function PaymentsPage() {
</Card> </Card>
</div> </div>
{/* Check Payments Online */}
<Card className="mb-6">
<CardHeader>
<CardTitle>Check Payments Online</CardTitle>
<CardDescription>
Select a date range and check MH payment status online
</CardDescription>
</CardHeader>
<CardContent>
<div className="flex flex-wrap items-center gap-4">
{/* From date picker */}
<div className="flex items-center gap-2">
<span className="text-sm font-medium text-gray-600 whitespace-nowrap">From:</span>
<Popover open={fromCalendarOpen} onOpenChange={setFromCalendarOpen}>
<PopoverTrigger asChild>
<Button
variant="outline"
className="w-[160px] justify-start text-left font-normal"
>
<CalendarIcon className="mr-2 h-4 w-4 text-gray-400" />
{mhFromDate
? mhFromDate.toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" })
: <span className="text-muted-foreground">Pick a date</span>}
</Button>
</PopoverTrigger>
<PopoverContent className="w-auto p-0" align="start">
<Calendar
mode="single"
selected={mhFromDate}
onSelect={(d) => {
setMhFromDate(d);
setFromCalendarOpen(false);
}}
onClose={() => setFromCalendarOpen(false)}
/>
</PopoverContent>
</Popover>
</div>
{/* To date picker */}
<div className="flex items-center gap-2">
<span className="text-sm font-medium text-gray-600 whitespace-nowrap">To:</span>
<Popover open={toCalendarOpen} onOpenChange={setToCalendarOpen}>
<PopoverTrigger asChild>
<Button
variant="outline"
className="w-[160px] justify-start text-left font-normal"
>
<CalendarIcon className="mr-2 h-4 w-4 text-gray-400" />
{mhToDate
? mhToDate.toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" })
: <span className="text-muted-foreground">Pick a date</span>}
</Button>
</PopoverTrigger>
<PopoverContent className="w-auto p-0" align="start">
<Calendar
mode="single"
selected={mhToDate}
onSelect={(d) => {
setMhToDate(d);
setToCalendarOpen(false);
}}
onClose={() => setToCalendarOpen(false)}
/>
</PopoverContent>
</Popover>
</div>
{/* Check All MH Payment button */}
<Button
variant="default"
onClick={() => {
// Logic to be defined later
}}
>
Check All MH Payment
</Button>
</div>
</CardContent>
</Card>
{/* Recent Payments table */} {/* Recent Payments table */}
<Card> <Card>
<CardHeader> <CardHeader>
@@ -221,7 +315,7 @@ export default function PaymentsPage() {
</CardDescription> </CardDescription>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<PaymentsRecentTable allowEdit allowDelete /> <PaymentsRecentTable allowEdit allowDelete allowCheckbox />
</CardContent> </CardContent>
</Card> </Card>