claim page table logic done, ui to be fixed
This commit is contained in:
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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",
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user