import { useState, useMemo, useRef } from "react"; import { useQuery, useMutation } from "@tanstack/react-query"; import { TopAppBar } from "@/components/layout/top-app-bar"; import { Sidebar } from "@/components/layout/sidebar"; import { PatientTable } from "@/components/patients/patient-table"; import { AddPatientModal } from "@/components/patients/add-patient-modal"; import { PatientSearch, SearchCriteria, } from "@/components/patients/patient-search"; import { FileUploadZone } from "@/components/file-upload/file-upload-zone"; import { Button } from "@/components/ui/button"; import { Plus, RefreshCw, File, FilePlus } from "lucide-react"; import { useToast } from "@/hooks/use-toast"; import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from "@/components/ui/card"; import { PatientUncheckedCreateInputObjectSchema } from "@repo/db/shared/schemas"; // import { Patient, InsertPatient, UpdatePatient } from "@repo/db/shared/schemas"; import { apiRequest, queryClient } from "@/lib/queryClient"; import { useAuth } from "@/hooks/use-auth"; import { z } from "zod"; const PatientSchema = ( PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject ).omit({ appointments: true, }); type Patient = z.infer; const insertPatientSchema = ( PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject ).omit({ id: true, createdAt: true, userId: true, }); type InsertPatient = z.infer; const updatePatientSchema = ( PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject ) .omit({ id: true, createdAt: true, userId: true, }) .partial(); type UpdatePatient = z.infer; // Type for the ref to access modal methods type AddPatientModalRef = { shouldSchedule: boolean; navigateToSchedule: (patientId: number) => void; }; export default function PatientsPage() { const { toast } = useToast(); const { user } = useAuth(); const [isAddPatientOpen, setIsAddPatientOpen] = useState(false); const [isViewPatientOpen, setIsViewPatientOpen] = useState(false); const [currentPatient, setCurrentPatient] = useState( undefined ); const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false); const [searchCriteria, setSearchCriteria] = useState( null ); const addPatientModalRef = useRef(null); // File upload states const [uploadedFile, setUploadedFile] = useState(null); const [isUploading, setIsUploading] = useState(false); const [isExtracting, setIsExtracting] = useState(false); const [extractedInfo, setExtractedInfo] = useState(null); // Fetch patients const { data: patients = [], isLoading: isLoadingPatients, refetch: refetchPatients, } = useQuery({ queryKey: ["/api/patients/"], queryFn: async () => { const res = await apiRequest("GET", "/api/patients/"); return res.json(); }, enabled: !!user, }); // Add patient mutation const addPatientMutation = useMutation({ mutationFn: async (patient: InsertPatient) => { const res = await apiRequest("POST", "/api/patients/", patient); return res.json(); }, onSuccess: (newPatient) => { setIsAddPatientOpen(false); queryClient.invalidateQueries({ queryKey: ["/api/patients/"] }); toast({ title: "Success", description: "Patient added successfully!", variant: "default", }); // If the add patient modal wants to proceed to scheduling, redirect to appointments page if (addPatientModalRef.current?.shouldSchedule) { addPatientModalRef.current.navigateToSchedule(newPatient.id); } }, onError: (error) => { toast({ title: "Error", description: `Failed to add patient: ${error.message}`, variant: "destructive", }); }, }); // 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 toggleMobileMenu = () => { setIsMobileMenuOpen(!isMobileMenuOpen); }; const handleAddPatient = (patient: InsertPatient) => { // Add userId to the patient data if (user) { addPatientMutation.mutate({ ...patient, userId: user.id, }); } }; 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 isLoading = isLoadingPatients || addPatientMutation.isPending || updatePatientMutation.isPending; // Search handling const handleSearch = (criteria: SearchCriteria) => { setSearchCriteria(criteria); }; const handleClearSearch = () => { setSearchCriteria(null); }; // File upload handling const handleFileUpload = (file: File) => { setUploadedFile(file); setIsUploading(false); // In a real implementation, this would be set to true during upload toast({ title: "File Selected", description: `${file.name} is ready for processing.`, variant: "default", }); }; // 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]); return (

Patients

Manage patient records and information

{/* File Upload Zone */}
Upload Patient Document
{/* Patients Table */} Patient Records View and manage all patient information {searchCriteria && (

Found {filteredPatients.length} {filteredPatients.length === 1 ? " patient" : " patients"} {searchCriteria.searchBy !== "all" ? ` with ${searchCriteria.searchBy}` : ""} matching "{searchCriteria.searchTerm}"

)}
{/* Add/Edit Patient Modal */}
); }