diff --git a/apps/Frontend/src/App.tsx b/apps/Frontend/src/App.tsx index b390590..36122bb 100644 --- a/apps/Frontend/src/App.tsx +++ b/apps/Frontend/src/App.tsx @@ -16,10 +16,8 @@ const AppointmentsPage = lazy(() => import("./pages/appointments-page")); const PatientsPage = lazy(() => import("./pages/patients-page")); const SettingsPage = lazy(() => import("./pages/settings-page")); const ClaimsPage = lazy(() => import("./pages/claims-page")); -const PreAuthorizationsPage = lazy( - () => import("./pages/preauthorizations-page") -); const PaymentsPage = lazy(() => import("./pages/payments-page")); +const InsuranceEligibilityPage = lazy(()=> import("./pages/insurance-eligibility-page")) const DocumentPage = lazy(() => import("./pages/documents-page")); const NotFound = lazy(() => import("./pages/not-found")); @@ -34,10 +32,7 @@ function Router() { } /> } /> } /> - } - /> + }/> } /> } /> } /> diff --git a/apps/Frontend/src/components/insurance/credentials-modal.tsx b/apps/Frontend/src/components/insurance/credentials-modal.tsx new file mode 100644 index 0000000..3dd6465 --- /dev/null +++ b/apps/Frontend/src/components/insurance/credentials-modal.tsx @@ -0,0 +1,121 @@ +import { useState } from "react"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, + DialogFooter, +} from "@/components/ui/dialog"; +import { Eye, EyeOff } from "lucide-react"; + +interface CredentialsModalProps { + isOpen: boolean; + onClose: () => void; + onSubmit: (credentials: { username: string; password: string }) => void; + providerName: string; + isLoading?: boolean; +} + +export function CredentialsModal({ + isOpen, + onClose, + onSubmit, + providerName, + isLoading = false +}: CredentialsModalProps) { + const [username, setUsername] = useState(""); + const [password, setPassword] = useState(""); + const [showPassword, setShowPassword] = useState(false); + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + if (username && password) { + onSubmit({ username, password }); + } + }; + + const handleClose = () => { + setUsername(""); + setPassword(""); + setShowPassword(false); + onClose(); + }; + + return ( + + + + Insurance Portal Login + + Enter your credentials for {providerName} insurance portal to check patient eligibility automatically. + + + +
+
+ + setUsername(e.target.value)} + placeholder="Enter your username" + required + disabled={isLoading} + /> +
+ +
+ +
+ setPassword(e.target.value)} + placeholder="Enter your password" + required + disabled={isLoading} + /> + +
+
+ + + + + +
+
+
+ ); +} \ No newline at end of file diff --git a/apps/Frontend/src/components/layout/sidebar.tsx b/apps/Frontend/src/components/layout/sidebar.tsx index 390c7d1..3a75c1f 100644 --- a/apps/Frontend/src/components/layout/sidebar.tsx +++ b/apps/Frontend/src/components/layout/sidebar.tsx @@ -1,5 +1,5 @@ import { Link, useLocation } from "wouter"; -import { LayoutDashboard, Users, Calendar, Settings, FileCheck, ClipboardCheck, CreditCard, FolderOpen } from "lucide-react"; +import { LayoutDashboard, Users, Calendar, Settings, FileCheck, Shield, CreditCard, FolderOpen } from "lucide-react"; import { cn } from "@/lib/utils"; @@ -27,16 +27,16 @@ export function Sidebar({ isMobileOpen, setIsMobileOpen }: SidebarProps) { path: "/patients", icon: , }, + { + name: "Insurance Eligibility", + path: "/insurance-eligibility", + icon: , + }, { name: "Claims", path: "/claims", icon: , }, - { - name: "Pre-authorizations", - path: "/preauthorizations", - icon: , - }, { name: "Payments", path: "/payments", diff --git a/apps/Frontend/src/pages/insurance-eligibility-page.tsx b/apps/Frontend/src/pages/insurance-eligibility-page.tsx new file mode 100644 index 0000000..23b6872 --- /dev/null +++ b/apps/Frontend/src/pages/insurance-eligibility-page.tsx @@ -0,0 +1,615 @@ +import { useState } from "react"; +import { useQuery, useMutation } from "@tanstack/react-query"; +import { TopAppBar } from "@/components/layout/top-app-bar"; +import { Sidebar } from "@/components/layout/sidebar"; +import { Input } from "@/components/ui/input"; +import { Button } from "@/components/ui/button"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { Label } from "@/components/ui/label"; +import { Checkbox } from "@/components/ui/checkbox"; +import { + Search, + Edit, + Eye, + ChevronLeft, + ChevronRight, + Settings, + CheckCircle +} from "lucide-react"; +import { PatientUncheckedCreateInputObjectSchema } from "@repo/db/usedSchemas"; +import { useAuth } from "@/hooks/use-auth"; +import { useToast } from "@/hooks/use-toast"; +import { CredentialsModal } from "@/components/insurance/credentials-modal"; +import { apiRequest } from "@/lib/queryClient"; +import { cn } from "@/lib/utils"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import {z} from 'zod'; + +const PatientSchema = ( + PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject +).omit({ + appointments: true, +}); +type Patient = z.infer; + +export default function InsuranceEligibilityPage() { + const { user } = useAuth(); + const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false); + const [searchTerm, setSearchTerm] = useState(""); + const [searchField, setSearchField] = useState("all"); + const [currentPage, setCurrentPage] = useState(1); + const itemsPerPage = 5; + + // Insurance eligibility check form fields + const [memberId, setMemberId] = useState(""); + const [dateOfBirth, setDateOfBirth] = useState(""); + const [firstName, setFirstName] = useState(""); + const [lastName, setLastName] = useState(""); + + // Selected patient for insurance check + const [selectedPatientId, setSelectedPatientId] = useState(null); + + // Insurance automation states + const [isCredentialsModalOpen, setIsCredentialsModalOpen] = useState(false); + const [selectedProvider, setSelectedProvider] = useState(""); + const { toast } = useToast(); + + // Insurance eligibility check mutation + const checkInsuranceMutation = useMutation({ + mutationFn: async ({ provider, patientId, credentials }: { + provider: string; + patientId: number; + credentials: { username: string; password: string }; + }) => { + const response = await fetch('/api/insurance/check', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ provider, patientId, credentials }), + }); + + if (!response.ok) { + throw new Error('Failed to check insurance'); + } + + return response.json(); + }, + onSuccess: (result) => { + toast({ + title: "Insurance Check Complete", + description: result.isEligible ? + `Patient is eligible. Plan: ${result.planName}` : + "Patient eligibility could not be verified", + }); + }, + onError: (error: any) => { + toast({ + title: "Insurance Check Failed", + description: error.message || "Unable to verify insurance eligibility", + variant: "destructive", + }); + }, + }); + + // Fetch patients + const { + data: patients = [], + isLoading: isLoadingPatients, + } = useQuery({ + queryKey: ["/api/patients"], + enabled: !!user, + }); + + // Filter patients based on search + const filteredPatients = patients.filter(patient => { + if (!searchTerm) return true; + + const searchLower = searchTerm.toLowerCase(); + const fullName = `${patient.firstName} ${patient.lastName}`.toLowerCase(); + const patientId = `PID-${patient.id.toString().padStart(4, '0')}`; + + switch (searchField) { + case "name": + return fullName.includes(searchLower); + case "id": + return patientId.toLowerCase().includes(searchLower); + case "phone": + return patient.phone?.toLowerCase().includes(searchLower) || false; + case "all": + default: + return ( + fullName.includes(searchLower) || + patientId.toLowerCase().includes(searchLower) || + patient.phone?.toLowerCase().includes(searchLower) || + patient.email?.toLowerCase().includes(searchLower) || + false + ); + } + }); + + // Pagination + const totalPages = Math.ceil(filteredPatients.length / itemsPerPage); + const startIndex = (currentPage - 1) * itemsPerPage; + const endIndex = startIndex + itemsPerPage; + const currentPatients = filteredPatients.slice(startIndex, endIndex); + + const toggleMobileMenu = () => { + setIsMobileMenuOpen(!isMobileMenuOpen); + }; + + const formatDate = (dateString: string) => { + const date = new Date(dateString); + return date.toLocaleDateString('en-US', { + month: 'short', + day: 'numeric', + year: 'numeric' + }); + }; + + const getPatientInitials = (firstName: string, lastName: string) => { + return `${firstName.charAt(0)}${lastName.charAt(0)}`.toUpperCase(); + }; + + // Toggle patient selection + const togglePatientSelection = (patientId: number) => { + setSelectedPatientId(selectedPatientId === patientId ? null : patientId); + }; + + // Handle insurance provider button clicks + const handleProviderClick = (providerName: string) => { + if (!selectedPatientId) { + toast({ + title: "No Patient Selected", + description: "Please select a patient first by checking the box next to their name.", + variant: "destructive", + }); + return; + } + + setSelectedProvider(providerName); + setIsCredentialsModalOpen(true); + }; + + // Handle credentials submission and start automation + const handleCredentialsSubmit = (credentials: { username: string; password: string }) => { + if (selectedPatientId && selectedProvider) { + // Use the provided MH credentials for MH provider + const finalCredentials = selectedProvider.toLowerCase() === 'mh' ? { + username: 'kqkgaox@yahoo.com', + password: 'Lex123456' + } : credentials; + + checkInsuranceMutation.mutate({ + provider: selectedProvider, + patientId: selectedPatientId, + credentials: finalCredentials, + }); + } + setIsCredentialsModalOpen(false); + }; + + const handleEligibilityCheck = () => { + // TODO: Implement insurance eligibility check + console.log("Checking MH eligibility:", { memberId, dateOfBirth, firstName, lastName }); + }; + + const handleDeltaMACheck = () => { + // TODO: Implement Delta MA eligibility check + console.log("Checking Delta MA eligibility:", { memberId, dateOfBirth, firstName, lastName }); + }; + + const handleMetlifeDentalCheck = () => { + // TODO: Implement Metlife Dental eligibility check + console.log("Checking Metlife Dental eligibility:", { memberId, dateOfBirth, firstName, lastName }); + }; + + const handlePatientSelect = (patientId: number) => { + const patient = patients.find(p => p.id === patientId); + if (patient) { + setSelectedPatientId(patientId); + // Auto-fill form fields with selected patient data + setMemberId(patient.insuranceId || ""); + setDateOfBirth(patient.dateOfBirth || ""); + setFirstName(patient.firstName || ""); + setLastName(patient.lastName || ""); + } else { + setSelectedPatientId(null); + // Clear form fields + setMemberId(""); + setDateOfBirth(""); + setFirstName(""); + setLastName(""); + } + }; + + return ( +
+ + +
+ + +
+
+ {/* Header */} +
+

Insurance Eligibility

+

Check insurance eligibility and view patient information

+
+ + {/* Insurance Eligibility Check Form */} + + + Check Insurance Eligibility + + +
+
+ + setMemberId(e.target.value)} + /> +
+
+ + setDateOfBirth(e.target.value)} + /> +
+
+ + setFirstName(e.target.value)} + /> +
+
+ + setLastName(e.target.value)} + /> +
+
+
+
+
+ +
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+ +
+
+
+
+
+ + {/* Search and Filters */} + + +
+
+ + setSearchTerm(e.target.value)} + className="pl-10" + /> +
+
+ + +
+
+
+
+ + {/* Patient List */} + + + {isLoadingPatients ? ( +
Loading patients...
+ ) : ( + <> + {/* Table Header */} +
+
Select
+
Patient
+
DOB / Gender
+
Contact
+
Insurance
+
Status
+
Actions
+
+ + {/* Table Rows */} + {currentPatients.length === 0 ? ( +
+ {searchTerm ? "No patients found matching your search." : "No patients available."} +
+ ) : ( + currentPatients.map((patient) => ( +
+ {/* Select Checkbox */} +
+ { + if (checked) { + handlePatientSelect(patient.id); + } else { + handlePatientSelect(0); // This will clear the selection + } + }} + /> +
+ + {/* Patient Info */} +
+
+ {getPatientInitials(patient.firstName, patient.lastName)} +
+
+
+ {patient.firstName} {patient.lastName} +
+
+ PID-{patient.id.toString().padStart(4, '0')} +
+
+
+ + {/* DOB / Gender */} +
+
+ {formatDate(patient.dateOfBirth)} +
+
+ {patient.gender} +
+
+ + {/* Contact */} +
+
+ {patient.phone || 'Not provided'} +
+
+ {patient.email || 'No email'} +
+
+ + {/* Insurance */} +
+
+ {patient.insuranceProvider ? + `${patient.insuranceProvider.charAt(0).toUpperCase()}${patient.insuranceProvider.slice(1)}` : + 'Not specified' + } +
+
+ ID: {patient.insuranceId || 'N/A'} +
+
+ + {/* Status */} +
+ + {patient.status === 'active' ? 'Active' : 'Inactive'} + +
+ + {/* Actions */} +
+
+ + +
+
+
+ )) + )} + + {/* Pagination */} + {totalPages > 1 && ( +
+
+ Showing {startIndex + 1} to {Math.min(endIndex, filteredPatients.length)} of {filteredPatients.length} results +
+
+ + + {/* Page Numbers */} + {Array.from({ length: totalPages }, (_, i) => i + 1).map((page) => ( + + ))} + + +
+
+ )} + + )} +
+
+
+
+
+ + {/* Credentials Modal */} + setIsCredentialsModalOpen(false)} + onSubmit={handleCredentialsSubmit} + providerName={selectedProvider} + isLoading={checkInsuranceMutation.isPending} + /> +
+ ); +} \ No newline at end of file