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:
@@ -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>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user