recent patients done, actions are done, now have to add search criteria

This commit is contained in:
2025-07-05 22:03:39 +05:30
parent e620e9db1c
commit 015d677c7e
9 changed files with 510 additions and 914 deletions

View File

@@ -4,7 +4,7 @@ import { storage } from "../storage";
import { z } from "zod"; import { z } from "zod";
import { ClaimUncheckedCreateInputObjectSchema } from "@repo/db/usedSchemas"; import { ClaimUncheckedCreateInputObjectSchema } from "@repo/db/usedSchemas";
import multer from "multer"; import multer from "multer";
import { forwardToSeleniumAgent } from "../services/seleniumClient"; import { forwardToSeleniumAgent } from "../services/seleniumClaimClient";
import path from "path"; import path from "path";
import axios from "axios"; import axios from "axios";
import fs from "fs"; import fs from "fs";

View File

@@ -68,6 +68,24 @@ router.get("/", async (req, res) => {
} }
}); });
// Get recent patients (paginated)
router.get("/recent", async (req: Request, res: Response) => {
try {
const limit = parseInt(req.query.limit as string) || 10;
const offset = parseInt(req.query.offset as string) || 0;
const [patients, totalCount] = await Promise.all([
storage.getRecentPatients(limit, offset),
storage.getTotalPatientCount(),
]);
res.json({ patients, totalCount });
} catch (error) {
console.error("Failed to retrieve recent patients:", error);
res.status(500).json({ message: "Failed to retrieve recent patients" });
}
});
// Get a single patient by ID // Get a single patient by ID
router.get( router.get(
"/:id", "/:id",
@@ -101,19 +119,6 @@ router.get(
} }
); );
// Get recent patients (paginated)
router.get("/recent", async (req: Request, res: Response) => {
try {
const limit = parseInt(req.query.limit as string) || 10;
const offset = parseInt(req.query.offset as string) || 0;
const recentPatients = await storage.getRecentPatients(limit, offset);
res.json(recentPatients);
} catch (error) {
console.error("Failed to retrieve recent patients:", error);
res.status(500).json({ message: "Failed to retrieve recent patients" });
}
});
// Create a new patient // Create a new patient
router.post("/", async (req: Request, res: Response): Promise<any> => { router.post("/", async (req: Request, res: Response): Promise<any> => {

View File

@@ -167,6 +167,7 @@ export interface IStorage {
getPatient(id: number): Promise<Patient | undefined>; getPatient(id: number): Promise<Patient | undefined>;
getPatientsByUserId(userId: number): Promise<Patient[]>; getPatientsByUserId(userId: number): Promise<Patient[]>;
getRecentPatients(limit: number, offset: number): Promise<Patient[]>; getRecentPatients(limit: number, offset: number): Promise<Patient[]>;
getTotalPatientCount(): Promise<number>;
createPatient(patient: InsertPatient): Promise<Patient>; createPatient(patient: InsertPatient): Promise<Patient>;
updatePatient(id: number, patient: UpdatePatient): Promise<Patient>; updatePatient(id: number, patient: UpdatePatient): Promise<Patient>;
deletePatient(id: number): Promise<void>; deletePatient(id: number): Promise<void>;
@@ -304,6 +305,10 @@ export const storage: IStorage = {
}); });
}, },
async getTotalPatientCount(): Promise<number> {
return db.patient.count();
},
async createPatient(patient: InsertPatient): Promise<Patient> { async createPatient(patient: InsertPatient): Promise<Patient> {
return await db.patient.create({ data: patient as Patient }); return await db.patient.create({ data: patient as Patient });
}, },
@@ -371,7 +376,7 @@ export const storage: IStorage = {
return db.appointment.findMany({ return db.appointment.findMany({
skip: offset, skip: offset,
take: limit, take: limit,
orderBy: { date: "desc" } orderBy: { date: "desc" },
}); });
}, },
@@ -589,7 +594,7 @@ export const storage: IStorage = {
id: true, id: true,
filename: true, filename: true,
uploadedAt: true, uploadedAt: true,
patient:true, patient: true,
}, },
}); });
}, },

View File

@@ -74,7 +74,6 @@ export const AddPatientModal = forwardRef<
>(function AddPatientModal(props, ref) { >(function AddPatientModal(props, ref) {
const { open, onOpenChange, onSubmit, isLoading, patient, extractedInfo } = const { open, onOpenChange, onSubmit, isLoading, patient, extractedInfo } =
props; props;
const { toast } = useToast();
const [formData, setFormData] = useState< const [formData, setFormData] = useState<
InsertPatient | UpdatePatient | null InsertPatient | UpdatePatient | null
>(null); >(null);

View File

@@ -338,11 +338,12 @@ export const PatientForm = forwardRef<PatientFormRef, PatientFormProps>(
<SelectItem value="placeholder"> <SelectItem value="placeholder">
Select provider Select provider
</SelectItem> </SelectItem>
<SelectItem value="delta">Delta Dental</SelectItem> <SelectItem value="Mass Health">Mass Health</SelectItem>
<SelectItem value="metlife">MetLife</SelectItem> <SelectItem value="Delta MA">Delta MA</SelectItem>
<SelectItem value="cigna">Cigna</SelectItem> <SelectItem value="Metlife">MetLife</SelectItem>
<SelectItem value="aetna">Aetna</SelectItem> <SelectItem value="Cigna">Cigna</SelectItem>
<SelectItem value="other">Other</SelectItem> <SelectItem value="Aetna">Aetna</SelectItem>
<SelectItem value="Other">Other</SelectItem>
<SelectItem value="none">None</SelectItem> <SelectItem value="none">None</SelectItem>
</SelectContent> </SelectContent>
</Select> </Select>

View File

@@ -1,4 +1,4 @@
import { useState } from "react"; import { useMemo, useState } from "react";
import { import {
Table, Table,
TableBody, TableBody,
@@ -7,14 +7,8 @@ import {
TableHeader, TableHeader,
TableRow, TableRow,
} from "@/components/ui/table"; } from "@/components/ui/table";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Delete, Edit, Eye, MoreVertical } from "lucide-react"; import { Delete, Edit, Eye } from "lucide-react";
import { Avatar, AvatarFallback } from "@/components/ui/avatar"; import { Avatar, AvatarFallback } from "@/components/ui/avatar";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
import { import {
@@ -26,56 +20,224 @@ import {
PaginationPrevious, PaginationPrevious,
} from "@/components/ui/pagination"; } from "@/components/ui/pagination";
import { PatientUncheckedCreateInputObjectSchema } from "@repo/db/usedSchemas"; import { PatientUncheckedCreateInputObjectSchema } from "@repo/db/usedSchemas";
import {z} from "zod"; import { z } from "zod";
import { apiRequest, queryClient } from "@/lib/queryClient";
import { useMutation, useQuery } from "@tanstack/react-query";
import LoadingScreen from "../ui/LoadingScreen";
import { useToast } from "@/hooks/use-toast";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { AddPatientModal } from "./add-patient-modal";
import { DeleteConfirmationDialog } from "../ui/deleteDialog";
import { useAuth } from "@/hooks/use-auth";
const PatientSchema = (PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>).omit({ const PatientSchema = (
PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
).omit({
appointments: true, appointments: true,
}); });
type Patient = z.infer<typeof PatientSchema>; type Patient = z.infer<typeof PatientSchema>;
interface PatientTableProps { const updatePatientSchema = (
PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
)
.omit({
id: true,
createdAt: true,
userId: true,
})
.partial();
type UpdatePatient = z.infer<typeof updatePatientSchema>;
interface PatientApiResponse {
patients: Patient[]; patients: Patient[];
onEdit: (patient: Patient) => void; totalCount: number;
onView: (patient: Patient) => void;
onDelete: (patient: Patient) => void;
} }
export function PatientTable({ patients, onEdit, onView, onDelete }: PatientTableProps) { interface PatientTableProps {
allowEdit?: boolean;
allowView?: boolean;
allowDelete?: boolean;
}
export function PatientTable({
allowEdit,
allowView,
allowDelete,
}: PatientTableProps) {
const { toast } = useToast();
const { user } = useAuth();
const [isAddPatientOpen, setIsAddPatientOpen] = useState(false);
const [isViewPatientOpen, setIsViewPatientOpen] = useState(false);
const [isDeletePatientOpen, setIsDeletePatientOpen] = useState(false);
const [currentPatient, setCurrentPatient] = useState<Patient | undefined>(
undefined
);
const [currentPage, setCurrentPage] = useState(1); const [currentPage, setCurrentPage] = useState(1);
const patientsPerPage = 5; const patientsPerPage = 5;
const offset = (currentPage - 1) * patientsPerPage;
// Get current patients const {
const indexOfLastPatient = currentPage * patientsPerPage; data: patientsData,
const indexOfFirstPatient = indexOfLastPatient - patientsPerPage; isLoading,
const currentPatients = patients.slice(indexOfFirstPatient, indexOfLastPatient); isError,
const totalPages = Math.ceil(patients.length / patientsPerPage); } = useQuery<PatientApiResponse>({
queryKey: ["patients", currentPage],
queryFn: async () => {
const res = await apiRequest(
"GET",
`/api/patients/recent?limit=${patientsPerPage}&offset=${offset}`
);
return res.json();
},
placeholderData: {
patients: [],
totalCount: 0,
},
});
// Update patient mutation
const updatePatientMutation = useMutation({
mutationFn: async ({
id,
patient,
}: {
id: number;
patient: UpdatePatient;
}) => {
const res = await apiRequest("PUT", `/api/patients/${id}`, patient);
return res.json();
},
onSuccess: () => {
setIsAddPatientOpen(false);
queryClient.invalidateQueries({ queryKey: ["patients", currentPage] });
toast({
title: "Success",
description: "Patient updated successfully!",
variant: "default",
});
},
onError: (error) => {
toast({
title: "Error",
description: `Failed to update patient: ${error.message}`,
variant: "destructive",
});
},
});
const deletePatientMutation = useMutation({
mutationFn: async (id: number) => {
const res = await apiRequest("DELETE", `/api/patients/${id}`);
return;
},
onSuccess: () => {
setIsDeletePatientOpen(false);
queryClient.invalidateQueries({ queryKey: ["patients", currentPage] });
toast({
title: "Success",
description: "Patient deleted successfully!",
variant: "default",
});
},
onError: (error) => {
console.log(error);
toast({
title: "Error",
description: `Failed to delete patient: ${error.message}`,
variant: "destructive",
});
},
});
const handleUpdatePatient = (patient: UpdatePatient & { id?: number }) => {
if (currentPatient && user) {
const { id, ...sanitizedPatient } = patient;
updatePatientMutation.mutate({
id: currentPatient.id,
patient: sanitizedPatient,
});
} else {
console.error("No current patient or user found for update");
toast({
title: "Error",
description: "Cannot update patient: No patient or user found",
variant: "destructive",
});
}
};
const handleEditPatient = (patient: Patient) => {
setCurrentPatient(patient);
setIsAddPatientOpen(true);
};
const handleViewPatient = (patient: Patient) => {
setCurrentPatient(patient);
setIsViewPatientOpen(true);
};
const handleDeletePatient = (patient: Patient) => {
setCurrentPatient(patient);
setIsDeletePatientOpen(true);
};
const handleConfirmDeletePatient = async () => {
if (currentPatient) {
deletePatientMutation.mutate(currentPatient.id);
} else {
toast({
title: "Error",
description: "No patient selected for deletion.",
variant: "destructive",
});
}
};
const totalPages = useMemo(
() => Math.ceil((patientsData?.totalCount || 0) / patientsPerPage),
[patientsData]
);
const startItem = offset + 1;
const endItem = Math.min(
offset + patientsPerPage,
patientsData?.totalCount || 0
);
const getInitials = (firstName: string, lastName: string) => { const getInitials = (firstName: string, lastName: string) => {
return (firstName.charAt(0) + lastName.charAt(0)).toUpperCase(); return (firstName.charAt(0) + lastName.charAt(0)).toUpperCase();
}; };
const getAvatarColor = (id: number) => { const getAvatarColor = (id: number) => {
const colorClasses = [ const colorClasses = [
"bg-blue-500", "bg-blue-500",
"bg-teal-500", "bg-teal-500",
"bg-amber-500", "bg-amber-500",
"bg-rose-500", "bg-rose-500",
"bg-indigo-500", "bg-indigo-500",
"bg-green-500", "bg-green-500",
"bg-purple-500", "bg-purple-500",
]; ];
return colorClasses[id % colorClasses.length];
// This returns a literal string from above — not a generated string };
return colorClasses[id % colorClasses.length];
};
const formatDate = (dateString: string | Date) => { const formatDate = (dateString: string | Date) => {
const date = new Date(dateString); const date = new Date(dateString);
return new Intl.DateTimeFormat('en-US', { return new Intl.DateTimeFormat("en-US", {
day: '2-digit', day: "2-digit",
month: 'short', month: "short",
year: 'numeric' year: "numeric",
}).format(date); }).format(date);
}; };
@@ -94,18 +256,41 @@ export function PatientTable({ patients, onEdit, onView, onDelete }: PatientTabl
</TableRow> </TableRow>
</TableHeader> </TableHeader>
<TableBody> <TableBody>
{currentPatients.length === 0 ? ( {isLoading ? (
<TableRow> <TableRow>
<TableCell colSpan={6} className="text-center py-8 text-muted-foreground"> <TableCell
No patients found. Add your first patient to get started. colSpan={6}
className="text-center py-8 text-muted-foreground"
>
<LoadingScreen />
</TableCell>
</TableRow>
) : isError ? (
<TableRow>
<TableCell
colSpan={6}
className="text-center py-8 text-red-500"
>
Error loading patients.
</TableCell>
</TableRow>
) : patientsData?.patients.length === 0 ? (
<TableRow>
<TableCell
colSpan={6}
className="text-center py-8 text-muted-foreground"
>
No patients found.
</TableCell> </TableCell>
</TableRow> </TableRow>
) : ( ) : (
currentPatients.map((patient) => ( patientsData?.patients.map((patient) => (
<TableRow key={patient.id} className="hover:bg-gray-50"> <TableRow key={patient.id} className="hover:bg-gray-50">
<TableCell> <TableCell>
<div className="flex items-center"> <div className="flex items-center">
<Avatar className={`h-10 w-10 ${getAvatarColor(patient.id)}`}> <Avatar
className={`h-10 w-10 ${getAvatarColor(patient.id)}`}
>
<AvatarFallback className="text-white"> <AvatarFallback className="text-white">
{getInitials(patient.firstName, patient.lastName)} {getInitials(patient.firstName, patient.lastName)}
</AvatarFallback> </AvatarFallback>
@@ -116,7 +301,7 @@ export function PatientTable({ patients, onEdit, onView, onDelete }: PatientTabl
{patient.firstName} {patient.lastName} {patient.firstName} {patient.lastName}
</div> </div>
<div className="text-sm text-gray-500"> <div className="text-sm text-gray-500">
PID-{patient.id.toString().padStart(4, '0')} PID-{patient.id.toString().padStart(4, "0")}
</div> </div>
</div> </div>
</div> </div>
@@ -135,21 +320,7 @@ export function PatientTable({ patients, onEdit, onView, onDelete }: PatientTabl
</TableCell> </TableCell>
<TableCell> <TableCell>
<div className="text-sm text-gray-900"> <div className="text-sm text-gray-900">
{patient.insuranceProvider ? ( {patient.insuranceProvider ?? "Not specified"}
patient.insuranceProvider === 'delta'
? 'Delta Dental'
: patient.insuranceProvider === 'metlife'
? 'MetLife'
: patient.insuranceProvider === 'cigna'
? 'Cigna'
: patient.insuranceProvider === 'aetna'
? 'Aetna'
: patient.insuranceProvider === 'none'
? 'No Insurance'
: patient.insuranceProvider
) : (
'Not specified'
)}
</div> </div>
{patient.insuranceId && ( {patient.insuranceId && (
<div className="text-sm text-gray-500"> <div className="text-sm text-gray-500">
@@ -159,7 +330,9 @@ export function PatientTable({ patients, onEdit, onView, onDelete }: PatientTabl
</TableCell> </TableCell>
<TableCell> <TableCell>
<Badge <Badge
variant={patient.status === 'active' ? 'success' : 'warning'} variant={
patient.status === "active" ? "success" : "warning"
}
className="capitalize" className="capitalize"
> >
{patient.status} {patient.status}
@@ -167,33 +340,43 @@ export function PatientTable({ patients, onEdit, onView, onDelete }: PatientTabl
</TableCell> </TableCell>
<TableCell className="text-right"> <TableCell className="text-right">
<div className="flex justify-end space-x-2"> <div className="flex justify-end space-x-2">
<Button {allowDelete && (
onClick={() => <Button
onDelete(patient) onClick={() => {
} handleDeletePatient(patient);
className="text-red-600 hover:text-red-900" }}
aria-label="Delete Staff" className="text-red-600 hover:text-red-900"
variant="ghost" aria-label="Delete Staff"
size="icon" variant="ghost"
> size="icon"
<Delete/> >
</Button> <Delete />
<Button </Button>
variant="ghost" )}
size="icon" {allowEdit && (
onClick={() => onEdit(patient)} <Button
className="text-blue-600 hover:text-blue-800 hover:bg-blue-50" variant="ghost"
> size="icon"
<Edit className="h-4 w-4" /> onClick={() => {
</Button> handleEditPatient(patient);
<Button }}
variant="ghost" className="text-blue-600 hover:text-blue-800 hover:bg-blue-50"
size="icon" >
onClick={() => onView(patient)} <Edit className="h-4 w-4" />
className="text-gray-600 hover:text-gray-800 hover:bg-gray-50" </Button>
> )}
<Eye className="h-4 w-4" /> {allowView && (
</Button> <Button
variant="ghost"
size="icon"
onClick={() => {
handleViewPatient(patient);
}}
className="text-gray-600 hover:text-gray-800 hover:bg-gray-50"
>
<Eye className="h-4 w-4" />
</Button>
)}
</div> </div>
</TableCell> </TableCell>
</TableRow> </TableRow>
@@ -203,19 +386,189 @@ export function PatientTable({ patients, onEdit, onView, onDelete }: PatientTabl
</Table> </Table>
</div> </div>
{patients.length > patientsPerPage && ( {/* View Patient Modal */}
<div className="bg-white px-4 py-3 flex items-center justify-between border-t border-gray-200"> <Dialog open={isViewPatientOpen} onOpenChange={setIsViewPatientOpen}>
<div className="hidden sm:flex sm:flex-1 sm:items-center sm:justify-between"> <DialogContent className="sm:max-w-[600px]">
<div> <DialogHeader>
<p className="text-sm text-gray-700"> <DialogTitle>Patient Details</DialogTitle>
Showing <span className="font-medium">{indexOfFirstPatient + 1}</span> to{" "} <DialogDescription>
<span className="font-medium"> Complete information about the patient.
{Math.min(indexOfLastPatient, patients.length)} </DialogDescription>
</span>{" "} </DialogHeader>
of <span className="font-medium">{patients.length}</span> results
</p>
</div>
{currentPatient && (
<div className="space-y-4">
<div className="flex items-center space-x-4">
<div className="h-16 w-16 rounded-full bg-primary text-white flex items-center justify-center text-xl font-medium">
{currentPatient.firstName.charAt(0)}
{currentPatient.lastName.charAt(0)}
</div>
<div>
<h3 className="text-xl font-semibold">
{currentPatient.firstName} {currentPatient.lastName}
</h3>
<p className="text-gray-500">
Patient ID: {currentPatient.id.toString().padStart(4, "0")}
</p>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 pt-4">
<div>
<h4 className="font-medium text-gray-900">
Personal Information
</h4>
<div className="mt-2 space-y-2">
<p>
<span className="text-gray-500">Date of Birth:</span>{" "}
{new Date(
currentPatient.dateOfBirth
).toLocaleDateString()}
</p>
<p>
<span className="text-gray-500">Gender:</span>{" "}
{currentPatient.gender.charAt(0).toUpperCase() +
currentPatient.gender.slice(1)}
</p>
<p>
<span className="text-gray-500">Status:</span>{" "}
<span
className={`${
currentPatient.status === "active"
? "text-green-600"
: "text-amber-600"
} font-medium`}
>
{currentPatient.status.charAt(0).toUpperCase() +
currentPatient.status.slice(1)}
</span>
</p>
</div>
</div>
<div>
<h4 className="font-medium text-gray-900">
Contact Information
</h4>
<div className="mt-2 space-y-2">
<p>
<span className="text-gray-500">Phone:</span>{" "}
{currentPatient.phone}
</p>
<p>
<span className="text-gray-500">Email:</span>{" "}
{currentPatient.email || "N/A"}
</p>
<p>
<span className="text-gray-500">Address:</span>{" "}
{currentPatient.address ? (
<>
{currentPatient.address}
{currentPatient.city && `, ${currentPatient.city}`}
{currentPatient.zipCode &&
` ${currentPatient.zipCode}`}
</>
) : (
"N/A"
)}
</p>
</div>
</div>
<div>
<h4 className="font-medium text-gray-900">Insurance</h4>
<div className="mt-2 space-y-2">
<p>
<span className="text-gray-500">Provider:</span>{" "}
{currentPatient.insuranceProvider
? currentPatient.insuranceProvider === "delta"
? "Delta Dental"
: currentPatient.insuranceProvider === "metlife"
? "MetLife"
: currentPatient.insuranceProvider === "cigna"
? "Cigna"
: currentPatient.insuranceProvider === "aetna"
? "Aetna"
: currentPatient.insuranceProvider
: "N/A"}
</p>
<p>
<span className="text-gray-500">ID:</span>{" "}
{currentPatient.insuranceId || "N/A"}
</p>
<p>
<span className="text-gray-500">Group Number:</span>{" "}
{currentPatient.groupNumber || "N/A"}
</p>
<p>
<span className="text-gray-500">Policy Holder:</span>{" "}
{currentPatient.policyHolder || "Self"}
</p>
</div>
</div>
<div>
<h4 className="font-medium text-gray-900">
Medical Information
</h4>
<div className="mt-2 space-y-2">
<p>
<span className="text-gray-500">Allergies:</span>{" "}
{currentPatient.allergies || "None reported"}
</p>
<p>
<span className="text-gray-500">Medical Conditions:</span>{" "}
{currentPatient.medicalConditions || "None reported"}
</p>
</div>
</div>
</div>
<div className="flex justify-end space-x-2 pt-4">
<Button
variant="outline"
onClick={() => setIsViewPatientOpen(false)}
>
Close
</Button>
<Button
onClick={() => {
setIsViewPatientOpen(false);
handleEditPatient(currentPatient);
}}
>
Edit Patient
</Button>
</div>
</div>
)}
</DialogContent>
</Dialog>
{/* Add/Edit Patient Modal */}
<AddPatientModal
open={isAddPatientOpen}
onOpenChange={setIsAddPatientOpen}
onSubmit={handleUpdatePatient}
isLoading={isLoading}
patient={currentPatient}
/>
<DeleteConfirmationDialog
isOpen={isDeletePatientOpen}
onConfirm={handleConfirmDeletePatient}
onCancel={() => setIsDeletePatientOpen(false)}
entityName={currentPatient?.name}
/>
{/* Pagination */}
{totalPages > 1 && (
<div className="bg-white px-4 py-3 border-t border-gray-200">
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between">
<div className="text-sm text-muted-foreground mb-2 sm:mb-0 whitespace-nowrap">
Showing {startItem}{endItem} of {patientsData?.totalCount || 0}{" "}
results
</div>
<Pagination> <Pagination>
<PaginationContent> <PaginationContent>
<PaginationItem> <PaginationItem>
@@ -225,7 +578,9 @@ export function PatientTable({ patients, onEdit, onView, onDelete }: PatientTabl
e.preventDefault(); e.preventDefault();
if (currentPage > 1) setCurrentPage(currentPage - 1); if (currentPage > 1) setCurrentPage(currentPage - 1);
}} }}
className={currentPage === 1 ? "pointer-events-none opacity-50" : ""} className={
currentPage === 1 ? "pointer-events-none opacity-50" : ""
}
/> />
</PaginationItem> </PaginationItem>
@@ -243,15 +598,19 @@ export function PatientTable({ patients, onEdit, onView, onDelete }: PatientTabl
</PaginationLink> </PaginationLink>
</PaginationItem> </PaginationItem>
))} ))}
<PaginationItem> <PaginationItem>
<PaginationNext <PaginationNext
href="#" href="#"
onClick={(e) => { onClick={(e) => {
e.preventDefault(); e.preventDefault();
if (currentPage < totalPages) setCurrentPage(currentPage + 1); if (currentPage < totalPages)
setCurrentPage(currentPage + 1);
}} }}
className={currentPage === totalPages ? "pointer-events-none opacity-50" : ""} className={
currentPage === totalPages
? "pointer-events-none opacity-50"
: ""
}
/> />
</PaginationItem> </PaginationItem>
</PaginationContent> </PaginationContent>

View File

@@ -16,7 +16,6 @@ import { AppointmentsByDay } from "@/components/analytics/appointments-by-day";
import { NewPatients } from "@/components/analytics/new-patients"; import { NewPatients } from "@/components/analytics/new-patients";
import { AppointmentUncheckedCreateInputObjectSchema } from "@repo/db/usedSchemas"; import { AppointmentUncheckedCreateInputObjectSchema } from "@repo/db/usedSchemas";
import { PatientUncheckedCreateInputObjectSchema } from "@repo/db/usedSchemas"; import { PatientUncheckedCreateInputObjectSchema } from "@repo/db/usedSchemas";
import { import {
Users, Users,
Calendar, Calendar,
@@ -26,15 +25,7 @@ import {
Clock, Clock,
} from "lucide-react"; } from "lucide-react";
import { Link } from "wouter"; import { Link } from "wouter";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { z } from "zod"; import { z } from "zod";
import { DeleteConfirmationDialog } from "@/components/ui/deleteDialog";
//creating types out of schema auto generated. //creating types out of schema auto generated.
type Appointment = z.infer<typeof AppointmentUncheckedCreateInputObjectSchema>; type Appointment = z.infer<typeof AppointmentUncheckedCreateInputObjectSchema>;
@@ -72,17 +63,6 @@ const insertPatientSchema = (
}); });
type InsertPatient = z.infer<typeof insertPatientSchema>; type InsertPatient = z.infer<typeof insertPatientSchema>;
const updatePatientSchema = (
PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
)
.omit({
id: true,
createdAt: true,
userId: true,
})
.partial();
type UpdatePatient = z.infer<typeof updatePatientSchema>;
// Type for the ref to access modal methods // Type for the ref to access modal methods
type AddPatientModalRef = { type AddPatientModalRef = {
@@ -93,8 +73,6 @@ type AddPatientModalRef = {
export default function Dashboard() { export default function Dashboard() {
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false); const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
const [isAddPatientOpen, setIsAddPatientOpen] = useState(false); const [isAddPatientOpen, setIsAddPatientOpen] = useState(false);
const [isViewPatientOpen, setIsViewPatientOpen] = useState(false);
const [isDeletePatientOpen, setIsDeletePatientOpen] = useState(false);
const [isAddAppointmentOpen, setIsAddAppointmentOpen] = useState(false); const [isAddAppointmentOpen, setIsAddAppointmentOpen] = useState(false);
const [currentPatient, setCurrentPatient] = useState<Patient | undefined>( const [currentPatient, setCurrentPatient] = useState<Patient | undefined>(
undefined undefined
@@ -160,60 +138,6 @@ export default function Dashboard() {
}, },
}); });
// Update patient mutation
const updatePatientMutation = useMutation({
mutationFn: async ({
id,
patient,
}: {
id: number;
patient: UpdatePatient;
}) => {
const res = await apiRequest("PUT", `/api/patients/${id}`, patient);
return res.json();
},
onSuccess: () => {
setIsAddPatientOpen(false);
queryClient.invalidateQueries({ queryKey: ["/api/patients/"] });
toast({
title: "Success",
description: "Patient updated successfully!",
variant: "default",
});
},
onError: (error) => {
toast({
title: "Error",
description: `Failed to update patient: ${error.message}`,
variant: "destructive",
});
},
});
const deletePatientMutation = useMutation({
mutationFn: async (id: number) => {
const res = await apiRequest("DELETE", `/api/patients/${id}`);
return;
},
onSuccess: () => {
setIsDeletePatientOpen(false);
queryClient.invalidateQueries({ queryKey: ["/api/patients/"] });
toast({
title: "Success",
description: "Patient deleted successfully!",
variant: "default",
});
},
onError: (error) => {
console.log(error);
toast({
title: "Error",
description: `Failed to delete patient: ${error.message}`,
variant: "destructive",
});
},
});
const toggleMobileMenu = () => { const toggleMobileMenu = () => {
setIsMobileMenuOpen(!isMobileMenuOpen); setIsMobileMenuOpen(!isMobileMenuOpen);
}; };
@@ -227,54 +151,9 @@ export default function Dashboard() {
} }
}; };
const handleUpdatePatient = (patient: UpdatePatient & { id?: number }) => {
if (currentPatient && user) {
const { id, ...sanitizedPatient } = patient;
updatePatientMutation.mutate({
id: currentPatient.id,
patient: sanitizedPatient,
});
} else {
console.error("No current patient or user found for update");
toast({
title: "Error",
description: "Cannot update patient: No patient or user found",
variant: "destructive",
});
}
};
const handleEditPatient = (patient: Patient) => {
setCurrentPatient(patient);
setIsAddPatientOpen(true);
};
const handleViewPatient = (patient: Patient) => {
setCurrentPatient(patient);
setIsViewPatientOpen(true);
};
const handleDeletePatient = (patient: Patient) => {
setCurrentPatient(patient);
setIsDeletePatientOpen(true);
};
const handleConfirmDeletePatient = async () => {
if (currentPatient) {
deletePatientMutation.mutate(currentPatient.id);
} else {
toast({
title: "Error",
description: "No patient selected for deletion.",
variant: "destructive",
});
}
};
const isLoading = const isLoading =
isLoadingPatients || isLoadingPatients ||
addPatientMutation.isPending || addPatientMutation.isPending;
updatePatientMutation.isPending;
// Create appointment mutation // Create appointment mutation
const createAppointmentMutation = useMutation({ const createAppointmentMutation = useMutation({
@@ -570,17 +449,9 @@ export default function Dashboard() {
{/* Patient Table */} {/* Patient Table */}
<PatientTable <PatientTable
patients={filteredPatients} allowDelete={true}
onEdit={handleEditPatient} allowEdit={true}
onView={handleViewPatient} allowView={true}
onDelete={handleDeletePatient}
/>
<DeleteConfirmationDialog
isOpen={isDeletePatientOpen}
onConfirm={handleConfirmDeletePatient}
onCancel={() => setIsDeletePatientOpen(false)}
entityName={currentPatient?.name}
/> />
</div> </div>
</main> </main>
@@ -591,187 +462,11 @@ export default function Dashboard() {
ref={addPatientModalRef} ref={addPatientModalRef}
open={isAddPatientOpen} open={isAddPatientOpen}
onOpenChange={setIsAddPatientOpen} onOpenChange={setIsAddPatientOpen}
onSubmit={currentPatient ? handleUpdatePatient : handleAddPatient} onSubmit={handleAddPatient}
isLoading={isLoading} isLoading={isLoading}
patient={currentPatient} patient={currentPatient}
/> />
{/* View Patient Modal */}
<Dialog open={isViewPatientOpen} onOpenChange={setIsViewPatientOpen}>
<DialogContent className="sm:max-w-[600px]">
<DialogHeader>
<DialogTitle>Patient Details</DialogTitle>
<DialogDescription>
Complete information about the patient.
</DialogDescription>
</DialogHeader>
{currentPatient && (
<div className="space-y-4">
<div className="flex items-center space-x-4">
<div className="h-16 w-16 rounded-full bg-primary text-white flex items-center justify-center text-xl font-medium">
{currentPatient.firstName.charAt(0)}
{currentPatient.lastName.charAt(0)}
</div>
<div>
<h3 className="text-xl font-semibold">
{currentPatient.firstName} {currentPatient.lastName}
</h3>
<p className="text-gray-500">
Patient ID: {currentPatient.id.toString().padStart(4, "0")}
</p>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 pt-4">
<div>
<h4 className="font-medium text-gray-900">
Personal Information
</h4>
<div className="mt-2 space-y-2">
<p>
{currentPatient.dateOfBirth ? (
(() => {
const dobDate = parseISO(currentPatient.dateOfBirth);
return isValid(dobDate) ? (
<span>
<span className="text-gray-500">
Date of Birth:
</span>{" "}
{format(dobDate, "PPP")}
</span>
) : (
<span className="text-gray-500">
Date of Birth: N/A
</span>
);
})()
) : (
<span className="text-gray-500">
Date of Birth: N/A
</span>
)}
</p>
<p>
<span className="text-gray-500">Gender:</span>{" "}
{currentPatient.gender.charAt(0).toUpperCase() +
currentPatient.gender.slice(1)}
</p>
<p>
<span className="text-gray-500">Status:</span>{" "}
<span
className={`${
currentPatient.status === "active"
? "text-green-600"
: "text-amber-600"
} font-medium`}
>
{currentPatient.status.charAt(0).toUpperCase() +
currentPatient.status.slice(1)}
</span>
</p>
</div>
</div>
<div>
<h4 className="font-medium text-gray-900">
Contact Information
</h4>
<div className="mt-2 space-y-2">
<p>
<span className="text-gray-500">Phone:</span>{" "}
{currentPatient.phone}
</p>
<p>
<span className="text-gray-500">Email:</span>{" "}
{currentPatient.email || "N/A"}
</p>
<p>
<span className="text-gray-500">Address:</span>{" "}
{currentPatient.address ? (
<>
{currentPatient.address}
{currentPatient.city && `, ${currentPatient.city}`}
{currentPatient.zipCode &&
` ${currentPatient.zipCode}`}
</>
) : (
"N/A"
)}
</p>
</div>
</div>
<div>
<h4 className="font-medium text-gray-900">Insurance</h4>
<div className="mt-2 space-y-2">
<p>
<span className="text-gray-500">Provider:</span>{" "}
{currentPatient.insuranceProvider
? currentPatient.insuranceProvider === "delta"
? "Delta Dental"
: currentPatient.insuranceProvider === "metlife"
? "MetLife"
: currentPatient.insuranceProvider === "cigna"
? "Cigna"
: currentPatient.insuranceProvider === "aetna"
? "Aetna"
: currentPatient.insuranceProvider
: "N/A"}
</p>
<p>
<span className="text-gray-500">ID:</span>{" "}
{currentPatient.insuranceId || "N/A"}
</p>
<p>
<span className="text-gray-500">Group Number:</span>{" "}
{currentPatient.groupNumber || "N/A"}
</p>
<p>
<span className="text-gray-500">Policy Holder:</span>{" "}
{currentPatient.policyHolder || "Self"}
</p>
</div>
</div>
<div>
<h4 className="font-medium text-gray-900">
Medical Information
</h4>
<div className="mt-2 space-y-2">
<p>
<span className="text-gray-500">Allergies:</span>{" "}
{currentPatient.allergies || "None reported"}
</p>
<p>
<span className="text-gray-500">Medical Conditions:</span>{" "}
{currentPatient.medicalConditions || "None reported"}
</p>
</div>
</div>
</div>
<div className="flex justify-end space-x-2 pt-4">
<Button
variant="outline"
onClick={() => setIsViewPatientOpen(false)}
>
Close
</Button>
<Button
onClick={() => {
setIsViewPatientOpen(false);
handleEditPatient(currentPatient);
}}
>
Edit Patient
</Button>
</div>
</div>
)}
</DialogContent>
</Dialog>
{/* Add/Edit Appointment Modal */} {/* Add/Edit Appointment Modal */}
<AddAppointmentModal <AddAppointmentModal
open={isAddAppointmentOpen} open={isAddAppointmentOpen}

View File

@@ -300,98 +300,6 @@ export default function InsuranceEligibilityPage() {
{checkInsuranceMutation.isPending && selectedProvider === 'MH' ? 'Checking...' : 'MH'} {checkInsuranceMutation.isPending && selectedProvider === 'MH' ? 'Checking...' : 'MH'}
</Button> </Button>
</div> </div>
<div>
<Button
onClick={handleDeltaMACheck}
className="w-full"
variant="outline"
disabled={!memberId || !dateOfBirth || !firstName || !lastName}
>
<CheckCircle className="h-4 w-4 mr-2" />
Delta MA
</Button>
</div>
<div>
<Button
onClick={handleMetlifeDentalCheck}
className="w-full"
variant="outline"
disabled={!memberId || !dateOfBirth || !firstName || !lastName}
>
<CheckCircle className="h-4 w-4 mr-2" />
Metlife Dental
</Button>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<div>
<Button
onClick={() => console.log("Checking Tufts eligibility:", { memberId, dateOfBirth, firstName, lastName })}
className="w-full"
variant="outline"
disabled={!memberId || !dateOfBirth || !firstName || !lastName}
>
<CheckCircle className="h-4 w-4 mr-2" />
Tufts SCO/SWH/Navi/Mass Gen
</Button>
</div>
<div>
<Button
onClick={() => console.log("Checking United SCO eligibility:", { memberId, dateOfBirth, firstName, lastName })}
className="w-full"
variant="outline"
disabled={!memberId || !dateOfBirth || !firstName || !lastName}
>
<CheckCircle className="h-4 w-4 mr-2" />
United SCO
</Button>
</div>
<div>
<Button
onClick={() => console.log("Checking United AAPR eligibility:", { memberId, dateOfBirth, firstName, lastName })}
className="w-full"
variant="outline"
disabled={!memberId || !dateOfBirth || !firstName || !lastName}
>
<CheckCircle className="h-4 w-4 mr-2" />
United AAPR
</Button>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<div>
<Button
onClick={() => console.log("Checking CCA eligibility:", { memberId, dateOfBirth, firstName, lastName })}
className="w-full"
variant="outline"
disabled={!memberId || !dateOfBirth || !firstName || !lastName}
>
<CheckCircle className="h-4 w-4 mr-2" />
CCA
</Button>
</div>
<div>
<Button
onClick={() => console.log("Checking Aetna eligibility:", { memberId, dateOfBirth, firstName, lastName })}
className="w-full"
variant="outline"
disabled={!memberId || !dateOfBirth || !firstName || !lastName}
>
<CheckCircle className="h-4 w-4 mr-2" />
Aetna
</Button>
</div>
<div>
<Button
onClick={() => console.log("Checking Altus eligibility:", { memberId, dateOfBirth, firstName, lastName })}
className="w-full"
variant="outline"
disabled={!memberId || !dateOfBirth || !firstName || !lastName}
>
<CheckCircle className="h-4 w-4 mr-2" />
Altus
</Button>
</div>
</div> </div>
</div> </div>
</CardContent> </CardContent>

View File

@@ -23,14 +23,6 @@ import { PatientUncheckedCreateInputObjectSchema } from "@repo/db/usedSchemas";
import { apiRequest, queryClient } from "@/lib/queryClient"; import { apiRequest, queryClient } from "@/lib/queryClient";
import { useAuth } from "@/hooks/use-auth"; import { useAuth } from "@/hooks/use-auth";
import { z } from "zod"; import { z } from "zod";
import { DeleteConfirmationDialog } from "@/components/ui/deleteDialog";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import useExtractPdfData from "@/hooks/use-extractPdfData"; import useExtractPdfData from "@/hooks/use-extractPdfData";
import { useLocation } from "wouter"; import { useLocation } from "wouter";
@@ -50,18 +42,6 @@ const insertPatientSchema = (
}); });
type InsertPatient = z.infer<typeof insertPatientSchema>; type InsertPatient = z.infer<typeof insertPatientSchema>;
const updatePatientSchema = (
PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
)
.omit({
id: true,
createdAt: true,
userId: true,
})
.partial();
type UpdatePatient = z.infer<typeof updatePatientSchema>;
// Type for the ref to access modal methods // Type for the ref to access modal methods
type AddPatientModalRef = { type AddPatientModalRef = {
shouldSchedule: boolean; shouldSchedule: boolean;
@@ -72,8 +52,6 @@ export default function PatientsPage() {
const { toast } = useToast(); const { toast } = useToast();
const { user } = useAuth(); const { user } = useAuth();
const [isAddPatientOpen, setIsAddPatientOpen] = useState(false); const [isAddPatientOpen, setIsAddPatientOpen] = useState(false);
const [isViewPatientOpen, setIsViewPatientOpen] = useState(false);
const [isDeletePatientOpen, setIsDeletePatientOpen] = useState(false);
const [currentPatient, setCurrentPatient] = useState<Patient | undefined>( const [currentPatient, setCurrentPatient] = useState<Patient | undefined>(
undefined undefined
); );
@@ -90,20 +68,6 @@ export default function PatientsPage() {
const { mutate: extractPdf } = useExtractPdfData(); const { mutate: extractPdf } = useExtractPdfData();
const [location, navigate] = useLocation(); const [location, navigate] = useLocation();
// Fetch patients
const {
data: patients = [],
isLoading: isLoadingPatients,
refetch: refetchPatients,
} = useQuery<Patient[]>({
queryKey: ["/api/patients/"],
queryFn: async () => {
const res = await apiRequest("GET", "/api/patients/");
return res.json();
},
enabled: !!user,
});
// Add patient mutation // Add patient mutation
const addPatientMutation = useMutation({ const addPatientMutation = useMutation({
mutationFn: async (patient: InsertPatient) => { mutationFn: async (patient: InsertPatient) => {
@@ -133,60 +97,6 @@ export default function PatientsPage() {
}, },
}); });
// Update patient mutation
const updatePatientMutation = useMutation({
mutationFn: async ({
id,
patient,
}: {
id: number;
patient: UpdatePatient;
}) => {
const res = await apiRequest("PUT", `/api/patients/${id}`, patient);
return res.json();
},
onSuccess: () => {
setIsAddPatientOpen(false);
queryClient.invalidateQueries({ queryKey: ["/api/patients/"] });
toast({
title: "Success",
description: "Patient updated successfully!",
variant: "default",
});
},
onError: (error) => {
toast({
title: "Error",
description: `Failed to update patient: ${error.message}`,
variant: "destructive",
});
},
});
const deletePatientMutation = useMutation({
mutationFn: async (id: number) => {
const res = await apiRequest("DELETE", `/api/patients/${id}`);
return;
},
onSuccess: () => {
setIsDeletePatientOpen(false);
queryClient.invalidateQueries({ queryKey: ["/api/patients/"] });
toast({
title: "Success",
description: "Patient deleted successfully!",
variant: "default",
});
},
onError: (error) => {
console.log(error);
toast({
title: "Error",
description: `Failed to delete patient: ${error.message}`,
variant: "destructive",
});
},
});
const toggleMobileMenu = () => { const toggleMobileMenu = () => {
setIsMobileMenuOpen(!isMobileMenuOpen); setIsMobileMenuOpen(!isMobileMenuOpen);
}; };
@@ -200,54 +110,7 @@ export default function PatientsPage() {
} }
}; };
const handleUpdatePatient = (patient: UpdatePatient & { id?: number }) => { const isLoading = addPatientMutation.isPending;
if (currentPatient && user) {
const { id, ...sanitizedPatient } = patient;
updatePatientMutation.mutate({
id: currentPatient.id,
patient: sanitizedPatient,
});
} else {
console.error("No current patient or user found for update");
toast({
title: "Error",
description: "Cannot update patient: No patient or user found",
variant: "destructive",
});
}
};
const handleEditPatient = (patient: Patient) => {
setCurrentPatient(patient);
setIsAddPatientOpen(true);
};
const handleViewPatient = (patient: Patient) => {
setCurrentPatient(patient);
setIsViewPatientOpen(true);
};
const handleDeletePatient = (patient: Patient) => {
setCurrentPatient(patient);
setIsDeletePatientOpen(true);
};
const handleConfirmDeletePatient = async () => {
if (currentPatient) {
deletePatientMutation.mutate(currentPatient.id);
} else {
toast({
title: "Error",
description: "No patient selected for deletion.",
variant: "destructive",
});
}
};
const isLoading =
isLoadingPatients ||
addPatientMutation.isPending ||
updatePatientMutation.isPending;
// Search handling // Search handling
const handleSearch = (criteria: SearchCriteria) => { const handleSearch = (criteria: SearchCriteria) => {
@@ -258,42 +121,6 @@ export default function PatientsPage() {
setSearchCriteria(null); setSearchCriteria(null);
}; };
// Filter patients based on search criteria
const filteredPatients = useMemo(() => {
if (!searchCriteria || !searchCriteria.searchTerm) {
return patients;
}
const term = searchCriteria.searchTerm.toLowerCase();
return patients.filter((patient) => {
switch (searchCriteria.searchBy) {
case "name":
return (
patient.firstName.toLowerCase().includes(term) ||
patient.lastName.toLowerCase().includes(term)
);
case "phone":
return patient.phone.toLowerCase().includes(term);
case "insuranceProvider":
return patient.insuranceProvider?.toLowerCase().includes(term);
case "insuranceId":
return patient.insuranceId?.toLowerCase().includes(term);
case "all":
default:
return (
patient.firstName.toLowerCase().includes(term) ||
patient.lastName.toLowerCase().includes(term) ||
patient.phone.toLowerCase().includes(term) ||
patient.email?.toLowerCase().includes(term) ||
patient.address?.toLowerCase().includes(term) ||
patient.city?.toLowerCase().includes(term) ||
patient.insuranceProvider?.toLowerCase().includes(term) ||
patient.insuranceId?.toLowerCase().includes(term)
);
}
});
}, [patients, searchCriteria]);
// File upload handling // File upload handling
const handleFileUpload = (file: File) => { const handleFileUpload = (file: File) => {
setIsUploading(true); setIsUploading(true);
@@ -373,16 +200,6 @@ export default function PatientsPage() {
<Plus className="h-4 w-4" /> <Plus className="h-4 w-4" />
New Patient New Patient
</Button> </Button>
<Button
variant="outline"
size="sm"
className="h-9 gap-1"
onClick={() => refetchPatients()}
disabled={isLoading}
>
<RefreshCw className="h-4 w-4" />
Refresh
</Button>
</div> </div>
</div> </div>
@@ -441,213 +258,20 @@ export default function PatientsPage() {
isSearchActive={!!searchCriteria} isSearchActive={!!searchCriteria}
/> />
{searchCriteria && (
<div className="flex items-center my-4 px-2 py-1 bg-muted rounded-md text-sm">
<p>
Found {filteredPatients.length}
{filteredPatients.length === 1 ? " patient" : " patients"}
{searchCriteria.searchBy !== "all"
? ` with ${searchCriteria.searchBy}`
: ""}
matching "{searchCriteria.searchTerm}"
</p>
</div>
)}
<PatientTable <PatientTable
patients={filteredPatients} allowDelete={true}
onEdit={handleEditPatient} allowEdit={true}
onView={handleViewPatient} allowView={true}
onDelete={handleDeletePatient}
/>
<DeleteConfirmationDialog
isOpen={isDeletePatientOpen}
onConfirm={handleConfirmDeletePatient}
onCancel={() => setIsDeletePatientOpen(false)}
entityName={currentPatient?.name}
/> />
</CardContent> </CardContent>
</Card> </Card>
{/* View Patient Modal */}
<Dialog
open={isViewPatientOpen}
onOpenChange={setIsViewPatientOpen}
>
<DialogContent className="sm:max-w-[600px]">
<DialogHeader>
<DialogTitle>Patient Details</DialogTitle>
<DialogDescription>
Complete information about the patient.
</DialogDescription>
</DialogHeader>
{currentPatient && (
<div className="space-y-4">
<div className="flex items-center space-x-4">
<div className="h-16 w-16 rounded-full bg-primary text-white flex items-center justify-center text-xl font-medium">
{currentPatient.firstName.charAt(0)}
{currentPatient.lastName.charAt(0)}
</div>
<div>
<h3 className="text-xl font-semibold">
{currentPatient.firstName} {currentPatient.lastName}
</h3>
<p className="text-gray-500">
Patient ID:{" "}
{currentPatient.id.toString().padStart(4, "0")}
</p>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 pt-4">
<div>
<h4 className="font-medium text-gray-900">
Personal Information
</h4>
<div className="mt-2 space-y-2">
<p>
<span className="text-gray-500">
Date of Birth:
</span>{" "}
{new Date(
currentPatient.dateOfBirth
).toLocaleDateString()}
</p>
<p>
<span className="text-gray-500">Gender:</span>{" "}
{currentPatient.gender.charAt(0).toUpperCase() +
currentPatient.gender.slice(1)}
</p>
<p>
<span className="text-gray-500">Status:</span>{" "}
<span
className={`${
currentPatient.status === "active"
? "text-green-600"
: "text-amber-600"
} font-medium`}
>
{currentPatient.status.charAt(0).toUpperCase() +
currentPatient.status.slice(1)}
</span>
</p>
</div>
</div>
<div>
<h4 className="font-medium text-gray-900">
Contact Information
</h4>
<div className="mt-2 space-y-2">
<p>
<span className="text-gray-500">Phone:</span>{" "}
{currentPatient.phone}
</p>
<p>
<span className="text-gray-500">Email:</span>{" "}
{currentPatient.email || "N/A"}
</p>
<p>
<span className="text-gray-500">Address:</span>{" "}
{currentPatient.address ? (
<>
{currentPatient.address}
{currentPatient.city &&
`, ${currentPatient.city}`}
{currentPatient.zipCode &&
` ${currentPatient.zipCode}`}
</>
) : (
"N/A"
)}
</p>
</div>
</div>
<div>
<h4 className="font-medium text-gray-900">Insurance</h4>
<div className="mt-2 space-y-2">
<p>
<span className="text-gray-500">Provider:</span>{" "}
{currentPatient.insuranceProvider
? currentPatient.insuranceProvider === "delta"
? "Delta Dental"
: currentPatient.insuranceProvider === "metlife"
? "MetLife"
: currentPatient.insuranceProvider === "cigna"
? "Cigna"
: currentPatient.insuranceProvider ===
"aetna"
? "Aetna"
: currentPatient.insuranceProvider
: "N/A"}
</p>
<p>
<span className="text-gray-500">ID:</span>{" "}
{currentPatient.insuranceId || "N/A"}
</p>
<p>
<span className="text-gray-500">Group Number:</span>{" "}
{currentPatient.groupNumber || "N/A"}
</p>
<p>
<span className="text-gray-500">
Policy Holder:
</span>{" "}
{currentPatient.policyHolder || "Self"}
</p>
</div>
</div>
<div>
<h4 className="font-medium text-gray-900">
Medical Information
</h4>
<div className="mt-2 space-y-2">
<p>
<span className="text-gray-500">Allergies:</span>{" "}
{currentPatient.allergies || "None reported"}
</p>
<p>
<span className="text-gray-500">
Medical Conditions:
</span>{" "}
{currentPatient.medicalConditions ||
"None reported"}
</p>
</div>
</div>
</div>
<div className="flex justify-end space-x-2 pt-4">
<Button
variant="outline"
onClick={() => setIsViewPatientOpen(false)}
>
Close
</Button>
<Button
onClick={() => {
setIsViewPatientOpen(false);
handleEditPatient(currentPatient);
}}
>
Edit Patient
</Button>
</div>
</div>
)}
</DialogContent>
</Dialog>
{/* Add/Edit Patient Modal */} {/* Add/Edit Patient Modal */}
<AddPatientModal <AddPatientModal
ref={addPatientModalRef} ref={addPatientModalRef}
open={isAddPatientOpen} open={isAddPatientOpen}
onOpenChange={setIsAddPatientOpen} onOpenChange={setIsAddPatientOpen}
onSubmit={currentPatient ? handleUpdatePatient : handleAddPatient} onSubmit={handleAddPatient}
isLoading={isLoading} isLoading={isLoading}
patient={currentPatient} patient={currentPatient}
/> />