claim page table logic done, ui to be fixed

This commit is contained in:
2025-07-25 19:17:20 +05:30
parent 0f54bc6121
commit a5844ab088
7 changed files with 158 additions and 219 deletions

View File

@@ -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<Claim | null>(null);
const [deleteClaim, setDeleteClaim] = useState<Claim | null>(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 (
<div className="bg-white rounded shadow p-4 mt-4">
<h2 className="text-xl font-bold mb-4">Claims for Selected Patient</h2>
<Table>
<TableHeader>
<TableRow>
<TableHead>Claim ID</TableHead>
<TableHead>Service Date</TableHead>
<TableHead>Insurance</TableHead>
<TableHead>Status</TableHead>
<TableHead className="text-right">Actions</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{data?.data?.map((claim: Claim) => (
<TableRow key={claim.id}>
<TableCell>CLM-{claim.id.toString().padStart(4, "0")}</TableCell>
<TableCell>{format(new Date(claim.serviceDate), "MMM dd, yyyy")}</TableCell>
<TableCell>{claim.insuranceProvider}</TableCell>
<TableCell>{claim.status}</TableCell>
<TableCell className="text-right space-x-2">
<Button size="icon" variant="ghost" onClick={() => setViewClaim(claim)}>
<Eye className="h-4 w-4" />
</Button>
<Button size="icon" variant="ghost" onClick={() => {/* handle edit */}}>
<Edit className="h-4 w-4" />
</Button>
<Button size="icon" variant="ghost" onClick={() => setDeleteClaim(claim)}>
<Trash2 className="h-4 w-4 text-red-500" />
</Button>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
<Pagination>
<PaginationContent>
{Array.from({ length: totalPages }).map((_, i) => (
<PaginationItem key={i}>
<PaginationLink isActive={i + 1 === currentPage} onClick={() => setCurrentPage(i + 1)}>
{i + 1}
</PaginationLink>
</PaginationItem>
))}
</PaginationContent>
</Pagination>
<ClaimViewModal claim={viewClaim} onClose={() => setViewClaim(null)} />
<DeleteConfirmationDialog
isOpen={!!deleteClaim}
onConfirm={handleDelete}
onCancel={() => setDeleteClaim(null)}
entityName="claim"
/>
</div>
);
}

View File

@@ -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<any>
).omit({
appointments: true,
});
type Patient = z.infer<typeof PatientSchema>;
export default function ClaimsOfPatientModal() {
const [selectedPatient, setSelectedPatient] = useState<Patient | null>(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 (
<div className="space-y-8 py-8">
{/* Claims Section */}
{selectedPatient && (
<Card>
<CardHeader className="pb-4">
<CardTitle>
Claims for {selectedPatient.firstName} {selectedPatient.lastName}
</CardTitle>
<CardDescription>
Displaying recent claims for the selected patient.
</CardDescription>
</CardHeader>
<div className="p-4">
<ClaimsRecentTable
patientId={selectedPatient.id}
allowView
allowEdit
allowDelete
onPageChange={setClaimsPage}
/>
</div>
</Card>
)}
{/* Patients Section */}
<Card>
<CardHeader className="pb-4">
<CardTitle>Patients</CardTitle>
<CardDescription>
Select a patient to view their recent claims.
</CardDescription>
</CardHeader>
<div className="p-4">
<PatientTable
allowView
allowCheckbox
onSelectPatient={handleSelectPatient}
/>
</div>
</Card>
</div>
);
}

View File

@@ -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 (
<div className="space-y-2">
<Input placeholder="Search patients..." value={term} onChange={(e) => setTerm(e.target.value)} />
{visible && data?.length > 0 && (
<div className="border rounded overflow-hidden">
<Table>
<TableHeader>
<TableRow>
<TableHead>Name</TableHead>
<TableHead>Gender</TableHead>
<TableHead>DOB</TableHead>
<TableHead>Member ID</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{data.map((patient: Patient) => (
<TableRow key={patient.id} onClick={() => onSelectPatient(patient)} className="cursor-pointer hover:bg-muted">
<TableCell>{patient.name}</TableCell>
<TableCell>{patient.gender}</TableCell>
<TableCell>{patient.dob}</TableCell>
<TableCell>{patient.memberId}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
)}
</div>
);
}

View File

@@ -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<ClaimApiResponse, Error>({
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",

View File

@@ -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<typeof AppointmentUncheckedCreateInputObjectSchema>;
@@ -708,6 +709,9 @@ export default function ClaimsPage() {
allowView={true}
allowDelete={true}
/>
{/* Recent Claims by Patients */}
<ClaimsOfPatientModal />
</main>
</div>