feat(payment redirect aptmpt page) - added button

This commit is contained in:
2025-09-24 03:37:54 +05:30
parent aea7554396
commit 643cd718d0
3 changed files with 199 additions and 52 deletions

View File

@@ -1,4 +1,4 @@
import { useState } from "react";
import { forwardRef, useEffect, useRef, useState } from "react";
import { PatientTable } from "../patients/patient-table";
import {
Card,
@@ -10,11 +10,63 @@ import {
import { Patient } from "@repo/db/types";
import PaymentsRecentTable from "./payments-recent-table";
export default function PaymentsOfPatientModal() {
const [selectedPatient, setSelectedPatient] = useState<Patient | null>(null);
type Props = {
initialPatient?: Patient | null;
openInitially?: boolean;
onClose?: () => void;
};
const PaymentsOfPatientModal = forwardRef<HTMLDivElement, Props>(
({ initialPatient = null, openInitially = false, onClose }: Props, ref) => {
const [selectedPatient, setSelectedPatient] = useState<Patient | null>(
null
);
const [isModalOpen, setIsModalOpen] = useState(false);
const [paymentsPage, setPaymentsPage] = useState(1);
// minimal, local scroll + cleanup — put inside PaymentsOfPatientModal
useEffect(() => {
if (!selectedPatient) return;
const raf = requestAnimationFrame(() => {
const card = document.getElementById("payments-for-patient-card");
const main = document.querySelector("main"); // your app's scroll container
if (card && main instanceof HTMLElement) {
const parentRect = main.getBoundingClientRect();
const cardRect = card.getBoundingClientRect();
const relativeTop = cardRect.top - parentRect.top + main.scrollTop;
const offset = 8;
main.scrollTo({
top: Math.max(0, relativeTop - offset),
behavior: "smooth",
});
}
});
// cleanup: when selectedPatient changes (ddmodal closes) or component unmounts,
// reset the main scroll to top so other pages are not left scrolled.
return () => {
cancelAnimationFrame(raf);
const main = document.querySelector("main");
if (main instanceof HTMLElement) {
// immediate reset (no animation) so navigation to other pages starts at top
main.scrollTo({ top: 0, behavior: "auto" });
}
};
}, [selectedPatient]);
// when parent provides an initialPatient and openInitially flag, apply it
useEffect(() => {
if (initialPatient) {
setSelectedPatient(initialPatient);
setPaymentsPage(1);
}
if (openInitially) {
setIsModalOpen(true);
}
}, [initialPatient, openInitially]);
const handleSelectPatient = (patient: Patient | null) => {
if (patient) {
setSelectedPatient(patient);
@@ -30,7 +82,7 @@ export default function PaymentsOfPatientModal() {
<div className="space-y-8 py-8">
{/* Payments Section */}
{selectedPatient && (
<Card>
<Card id="payments-for-patient-card">
<CardHeader>
<CardTitle>
Payments for {selectedPatient.firstName}{" "}
@@ -69,4 +121,7 @@ export default function PaymentsOfPatientModal() {
</Card>
</div>
);
}
}
);
export default PaymentsOfPatientModal;

View File

@@ -669,7 +669,7 @@ export default function AppointmentsPage() {
};
const handlePayments = (appointmentId: number) => {
console.log(`Processing payments for appointment: ${appointmentId}`);
setLocation(`/payments?appointmentId=${appointmentId}`);
};
const handleChartPlan = (appointmentId: number) => {

View File

@@ -1,4 +1,4 @@
import { useState } from "react";
import { useEffect, useRef, useState } from "react";
import {
Card,
CardHeader,
@@ -17,10 +17,94 @@ import {
import PaymentsRecentTable from "@/components/payments/payments-recent-table";
import PaymentsOfPatientModal from "@/components/payments/payments-of-patient-table";
import PaymentOCRBlock from "@/components/payments/payment-ocr-block";
import { useLocation } from "wouter";
import { Patient } from "@repo/db/types";
import { apiRequest } from "@/lib/queryClient";
import { toast } from "@/hooks/use-toast";
export default function PaymentsPage() {
const [paymentPeriod, setPaymentPeriod] = useState<string>("all-time");
// for auto-open from appointment redirect
const [location] = useLocation();
const [initialPatientForModal, setInitialPatientForModal] =
useState<Patient | null>(null);
const [openPatientModalFromAppointment, setOpenPatientModalFromAppointment] =
useState(false);
// small helper: remove query params silently
const clearUrlParams = (params: string[]) => {
try {
const url = new URL(window.location.href);
let changed = false;
for (const p of params) {
if (url.searchParams.has(p)) {
url.searchParams.delete(p);
changed = true;
}
}
if (changed)
window.history.replaceState({}, document.title, url.toString());
} catch (e) {
// ignore
}
};
// case1: If payments page is opened via appointment-page, /payments?appointmentId=123 -> fetch patient and open modal
useEffect(() => {
const params = new URLSearchParams(window.location.search);
const appointmentIdParam = params.get("appointmentId");
if (!appointmentIdParam) return;
const appointmentId = Number(appointmentIdParam);
if (!Number.isFinite(appointmentId) || appointmentId <= 0) return;
let cancelled = false;
(async () => {
try {
const res = await apiRequest(
"GET",
`/api/appointments/${appointmentId}/patient`
);
if (!res.ok) {
let body: any = null;
try {
body = await res.json();
} catch {}
if (!cancelled) {
toast({
title: "Failed to load patient",
description:
body?.message ??
body?.error ??
`Could not fetch patient for appointment ${appointmentId}.`,
variant: "destructive",
});
}
return;
}
const data = await res.json();
const patient = data?.patient ?? data;
if (!cancelled && patient && patient.id) {
setInitialPatientForModal(patient as Patient);
setOpenPatientModalFromAppointment(true);
// remove query param so reload won't re-open
clearUrlParams(["appointmentId"]);
}
} catch (err) {
if (!cancelled) {
console.error("Error fetching patient for appointment:", err);
}
}
})();
return () => {
cancelled = true;
};
}, [location]);
return (
<div>
{/* Header */}
@@ -122,7 +206,15 @@ export default function PaymentsPage() {
</Card>
{/* Recent Payments by Patients*/}
<PaymentsOfPatientModal />
<PaymentsOfPatientModal
initialPatient={initialPatientForModal}
openInitially={openPatientModalFromAppointment}
onClose={() => {
// reset the local flags when modal is closed
setOpenPatientModalFromAppointment(false);
setInitialPatientForModal(null);
}}
/>
</div>
);
}