From a5844ab0884ad91b2fcc8811eafc8074b199bd06 Mon Sep 17 00:00:00 2001 From: Potenz Date: Fri, 25 Jul 2025 19:17:20 +0530 Subject: [PATCH] claim page table logic done, ui to be fixed --- apps/Backend/src/routes/claims.ts | 33 +++++ apps/Backend/src/storage/index.ts | 31 ++++- .../claims/claims-for-patient-table.tsx | 125 ------------------ .../claims/claims-of-patient-table.tsx | 77 +++++++++++ .../claims/claims-patient-search-table.tsx | 69 ---------- .../components/claims/claims-recent-table.tsx | 38 +++--- apps/Frontend/src/pages/claims-page.tsx | 4 + 7 files changed, 158 insertions(+), 219 deletions(-) delete mode 100644 apps/Frontend/src/components/claims/claims-for-patient-table.tsx create mode 100644 apps/Frontend/src/components/claims/claims-of-patient-table.tsx delete mode 100644 apps/Frontend/src/components/claims/claims-patient-search-table.tsx diff --git a/apps/Backend/src/routes/claims.ts b/apps/Backend/src/routes/claims.ts index 5479c3f..6da7b91 100644 --- a/apps/Backend/src/routes/claims.ts +++ b/apps/Backend/src/routes/claims.ts @@ -202,6 +202,39 @@ router.get("/recent", async (req: Request, res: Response) => { } }); +// GET /api/claims/patient/:patientId +router.get( + "/patient/:patientId", + async (req: Request, res: Response): Promise => { + try { + const patientIdParam = req.params.patientId; + if (!patientIdParam) { + return res.status(400).json({ message: "Missing patientId" }); + } + const patientId = parseInt(patientIdParam); + if (isNaN(patientId)) { + return res.status(400).json({ message: "Invalid patientId" }); + } + const limit = parseInt(req.query.limit as string) || 10; + const offset = parseInt(req.query.offset as string) || 0; + + if (isNaN(patientId)) { + return res.status(400).json({ message: "Invalid patient ID" }); + } + + const [claims, totalCount] = await Promise.all([ + storage.getRecentClaimsByPatientId(patientId, limit, offset), + storage.getTotalClaimCountByPatient(patientId), + ]); + + res.json({ claims, totalCount }); + } catch (error) { + console.error("Failed to retrieve claims for patient:", error); + res.status(500).json({ message: "Failed to retrieve patient claims" }); + } + } +); + // Get all claims for the logged-in user router.get("/all", async (req: Request, res: Response) => { try { diff --git a/apps/Backend/src/storage/index.ts b/apps/Backend/src/storage/index.ts index 6b93541..a649e58 100644 --- a/apps/Backend/src/storage/index.ts +++ b/apps/Backend/src/storage/index.ts @@ -219,7 +219,13 @@ export interface IStorage { // Claim methods getClaim(id: number): Promise; - getClaimsByPatientId(patientId: number): Promise; + getRecentClaimsByPatientId( + patientId: number, + limit: number, + offset: number + ): Promise; + + getTotalClaimCountByPatient(patientId: number): Promise; getClaimsByAppointmentId(appointmentId: number): Promise; getRecentClaimsByUser( userId: number, @@ -521,8 +527,27 @@ export const storage: IStorage = { return claim ?? undefined; }, - async getClaimsByPatientId(patientId: number): Promise { - return await db.claim.findMany({ where: { patientId } }); + async getRecentClaimsByPatientId( + patientId: number, + limit: number, + offset: number + ): Promise { + return db.claim.findMany({ + where: { patientId }, + orderBy: { createdAt: "desc" }, + skip: offset, + take: limit, + include: { + serviceLines: true, + staff: true, + }, + }); + }, + + async getTotalClaimCountByPatient(patientId: number): Promise { + return db.claim.count({ + where: { patientId }, + }); }, async getClaimsByAppointmentId(appointmentId: number): Promise { diff --git a/apps/Frontend/src/components/claims/claims-for-patient-table.tsx b/apps/Frontend/src/components/claims/claims-for-patient-table.tsx deleted file mode 100644 index 3f062f5..0000000 --- a/apps/Frontend/src/components/claims/claims-for-patient-table.tsx +++ /dev/null @@ -1,125 +0,0 @@ -// components/claims/ClaimsForPatientTable.tsx - -import { useEffect, useMemo, useState } from "react"; -import { useQuery, useMutation } from "@tanstack/react-query"; -import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; -import { Button } from "@/components/ui/button"; -import { Edit, Eye, Trash2 } from "lucide-react"; -import { apiRequest, queryClient } from "@/lib/queryClient"; -import { format } from "date-fns"; -import { useToast } from "@/hooks/use-toast"; -import { Pagination, PaginationContent, PaginationItem, PaginationLink } from "@/components/ui/pagination"; -import { DeleteConfirmationDialog } from "@/components/modals/DeleteConfirmationDialog"; -import ClaimViewModal from "@/components/modals/ClaimViewModal"; - -interface Claim { - id: number; - patientId: number; - patientName: string; - serviceDate: string; - insuranceProvider: string; - status: string; - remarks: string; - createdAt: string; -} - -interface Props { - patientId: number; -} - -export default function ClaimsForPatientTable({ patientId }: Props) { - const { toast } = useToast(); - const [currentPage, setCurrentPage] = useState(1); - const [viewClaim, setViewClaim] = useState(null); - const [deleteClaim, setDeleteClaim] = useState(null); - - const limit = 5; - const offset = (currentPage - 1) * limit; - - const { data, isLoading } = useQuery({ - queryKey: ["claims-by-patient", patientId, currentPage], - queryFn: async () => { - const res = await apiRequest("GET", `/api/claims/by-patient/${patientId}?limit=${limit}&offset=${offset}`); - if (!res.ok) throw new Error("Failed to load claims for patient"); - return res.json(); - }, - enabled: !!patientId, - placeholderData: { data: [], total: 0 }, - }); - - const deleteMutation = useMutation({ - mutationFn: async (id: number) => { - const res = await apiRequest("DELETE", `/api/claims/${id}`); - if (!res.ok) throw new Error("Failed to delete claim"); - }, - onSuccess: () => { - toast({ title: "Deleted", description: "Claim removed." }); - queryClient.invalidateQueries({ queryKey: ["claims-by-patient"] }); - }, - }); - - const handleDelete = () => { - if (deleteClaim) deleteMutation.mutate(deleteClaim.id); - setDeleteClaim(null); - }; - - const totalPages = useMemo(() => Math.ceil((data?.total || 0) / limit), [data]); - - return ( -
-

Claims for Selected Patient

- - - - Claim ID - Service Date - Insurance - Status - Actions - - - - {data?.data?.map((claim: Claim) => ( - - CLM-{claim.id.toString().padStart(4, "0")} - {format(new Date(claim.serviceDate), "MMM dd, yyyy")} - {claim.insuranceProvider} - {claim.status} - - - - - - - ))} - -
- - - - {Array.from({ length: totalPages }).map((_, i) => ( - - setCurrentPage(i + 1)}> - {i + 1} - - - ))} - - - - setViewClaim(null)} /> - setDeleteClaim(null)} - entityName="claim" - /> -
- ); -} diff --git a/apps/Frontend/src/components/claims/claims-of-patient-table.tsx b/apps/Frontend/src/components/claims/claims-of-patient-table.tsx new file mode 100644 index 0000000..5337004 --- /dev/null +++ b/apps/Frontend/src/components/claims/claims-of-patient-table.tsx @@ -0,0 +1,77 @@ +import { useState } from "react"; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; +import ClaimsRecentTable from "./claims-recent-table"; +import { PatientTable } from "../patients/patient-table"; +import { PatientUncheckedCreateInputObjectSchema } from "@repo/db/usedSchemas"; +import { z } from "zod"; +import { Card, CardDescription, CardHeader, CardTitle } from "../ui/card"; + +const PatientSchema = ( + PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject +).omit({ + appointments: true, +}); +type Patient = z.infer; + +export default function ClaimsOfPatientModal() { + const [selectedPatient, setSelectedPatient] = useState(null); + const [isModalOpen, setIsModalOpen] = useState(false); + const [claimsPage, setClaimsPage] = useState(1); + + const handleSelectPatient = (patient: Patient | null) => { + if (patient) { + setSelectedPatient(patient); + setClaimsPage(1); + setIsModalOpen(true); + } + }; + + return ( +
+ {/* Claims Section */} + {selectedPatient && ( + + + + Claims for {selectedPatient.firstName} {selectedPatient.lastName} + + + Displaying recent claims for the selected patient. + + +
+ +
+
+ )} + + {/* Patients Section */} + + + Patients + + Select a patient to view their recent claims. + + +
+ +
+
+
+ ); +} diff --git a/apps/Frontend/src/components/claims/claims-patient-search-table.tsx b/apps/Frontend/src/components/claims/claims-patient-search-table.tsx deleted file mode 100644 index da5fac2..0000000 --- a/apps/Frontend/src/components/claims/claims-patient-search-table.tsx +++ /dev/null @@ -1,69 +0,0 @@ -// components/patients/PatientSearchTable.tsx - -import { useEffect, useState } from "react"; -import { Input } from "@/components/ui/input"; -import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; -import { apiRequest } from "@/lib/queryClient"; -import { useQuery } from "@tanstack/react-query"; - -interface Patient { - id: number; - name: string; - gender: string; - dob: string; - memberId: string; -} - -interface Props { - onSelectPatient: (patient: Patient) => void; -} - -export default function PatientSearchTable({ onSelectPatient }: Props) { - const [term, setTerm] = useState(""); - const [visible, setVisible] = useState(false); - - const { data, isLoading } = useQuery({ - queryKey: ["patients", term], - queryFn: async () => { - const res = await apiRequest("GET", `/api/patients/search?term=${term}`); - if (!res.ok) throw new Error("Failed to load patients"); - return res.json(); - }, - enabled: !!term, - }); - - useEffect(() => { - if (term.length > 0) setVisible(true); - }, [term]); - - return ( -
- setTerm(e.target.value)} /> - - {visible && data?.length > 0 && ( -
- - - - Name - Gender - DOB - Member ID - - - - {data.map((patient: Patient) => ( - onSelectPatient(patient)} className="cursor-pointer hover:bg-muted"> - {patient.name} - {patient.gender} - {patient.dob} - {patient.memberId} - - ))} - -
-
- )} -
- ); -} diff --git a/apps/Frontend/src/components/claims/claims-recent-table.tsx b/apps/Frontend/src/components/claims/claims-recent-table.tsx index e428ee7..0cb430d 100644 --- a/apps/Frontend/src/components/claims/claims-recent-table.tsx +++ b/apps/Frontend/src/components/claims/claims-recent-table.tsx @@ -83,6 +83,7 @@ interface ClaimsRecentTableProps { allowCheckbox?: boolean; onSelectClaim?: (claim: Claim | null) => void; onPageChange?: (page: number) => void; + patientId?: number; } export default function ClaimsRecentTable({ @@ -92,6 +93,7 @@ export default function ClaimsRecentTable({ allowCheckbox, onSelectClaim, onPageChange, + patientId, }: ClaimsRecentTableProps) { const { toast } = useToast(); const { user } = useAuth(); @@ -119,22 +121,24 @@ export default function ClaimsRecentTable({ } }; + const getClaimsQueryKey = () => + patientId + ? ["claims-recent", "patient", patientId, currentPage] + : ["claims-recent", "global", currentPage]; + const { data: claimsData, isLoading, isError, } = useQuery({ - queryKey: [ - "claims-recent", - { - page: currentPage, - }, - ], + queryKey: getClaimsQueryKey(), + queryFn: async () => { - const res = await apiRequest( - "GET", - `/api/claims/recent?limit=${claimsPerPage}&offset=${offset}` - ); + const endpoint = patientId + ? `/api/claims/patient/${patientId}?limit=${claimsPerPage}&offset=${offset}` + : `/api/claims/recent?limit=${claimsPerPage}&offset=${offset}`; + + const res = await apiRequest("GET", endpoint); if (!res.ok) { const errorData = await res.json(); throw new Error(errorData.message || "Search failed"); @@ -163,12 +167,7 @@ export default function ClaimsRecentTable({ variant: "default", }); queryClient.invalidateQueries({ - queryKey: [ - "claims-recent", - { - page: currentPage, - }, - ], + queryKey: getClaimsQueryKey(), }); }, onError: (error) => { @@ -188,12 +187,7 @@ export default function ClaimsRecentTable({ onSuccess: () => { setIsDeleteClaimOpen(false); queryClient.invalidateQueries({ - queryKey: [ - "claims-recent", - { - page: currentPage, - }, - ], + queryKey: getClaimsQueryKey(), }); toast({ title: "Success", diff --git a/apps/Frontend/src/pages/claims-page.tsx b/apps/Frontend/src/pages/claims-page.tsx index fcdc059..16dcf8f 100644 --- a/apps/Frontend/src/pages/claims-page.tsx +++ b/apps/Frontend/src/pages/claims-page.tsx @@ -24,6 +24,7 @@ import { import { SeleniumTaskBanner } from "@/components/ui/selenium-task-banner"; import { formatLocalDate, parseLocalDate } from "@/utils/dateUtils"; import ClaimsRecentTable from "@/components/claims/claims-recent-table"; +import ClaimsOfPatientModal from "@/components/claims/claims-of-patient-table"; //creating types out of schema auto generated. type Appointment = z.infer; @@ -708,6 +709,9 @@ export default function ClaimsPage() { allowView={true} allowDelete={true} /> + + {/* Recent Claims by Patients */} +