After clicking the member ID link, print the member details page via CDP before navigating to service history. Adds member details as a panel in the side-by-side PDF viewer: MH History shows 3 panels (eligibility, member details, service history); CMSP shows 4 panels (eligibility, member details, service history, accumulator). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1245 lines
45 KiB
TypeScript
Executable File
1245 lines
45 KiB
TypeScript
Executable File
import { useEffect, useState } from "react";
|
|
import { useMutation, useQuery } from "@tanstack/react-query";
|
|
import { Input } from "@/components/ui/input";
|
|
import { Button } from "@/components/ui/button";
|
|
import {
|
|
Card,
|
|
CardContent,
|
|
CardDescription,
|
|
CardHeader,
|
|
CardTitle,
|
|
} from "@/components/ui/card";
|
|
import { Label } from "@/components/ui/label";
|
|
import {
|
|
Select,
|
|
SelectContent,
|
|
SelectItem,
|
|
SelectTrigger,
|
|
SelectValue,
|
|
} from "@/components/ui/select";
|
|
import { CheckCircle, LoaderCircleIcon, Bot, PhoneCall, ChevronDown, ChevronUp } from "lucide-react";
|
|
import { useAuth } from "@/hooks/use-auth";
|
|
import { useToast } from "@/hooks/use-toast";
|
|
import { PatientTable } from "@/components/patients/patient-table";
|
|
import { apiRequest, queryClient } from "@/lib/queryClient";
|
|
import { useAppDispatch, useAppSelector } from "@/redux/hooks";
|
|
import {
|
|
setTaskStatus,
|
|
clearTaskStatus,
|
|
} from "@/redux/slices/seleniumTaskSlice";
|
|
import { SeleniumTaskBanner } from "@/components/ui/selenium-task-banner";
|
|
import { formatLocalDate, parseLocalDate } from "@/utils/dateUtils";
|
|
import { socket } from "@/lib/socket";
|
|
import { InsertPatient, Patient } from "@repo/db/types";
|
|
import { DateInput } from "@/components/ui/dateInput";
|
|
import { QK_PATIENTS_BASE } from "@/components/patients/patient-table";
|
|
import { PdfPreviewModal } from "@/components/insurance-status/pdf-preview-modal";
|
|
import { DualPdfPreviewModal } from "@/components/insurance-status/dual-pdf-preview-modal";
|
|
import { useLocation } from "wouter";
|
|
import { DdmaEligibilityButton } from "@/components/insurance-status/ddma-buton-modal";
|
|
import { DeltaInsEligibilityButton } from "@/components/insurance-status/deltains-button-modal";
|
|
import { TuftsSCOEligibilityButton } from "@/components/insurance-status/tufts-sco-button-modal";
|
|
import { UnitedSCOEligibilityButton } from "@/components/insurance-status/united-sco-button-modal";
|
|
import { CCAEligibilityButton } from "@/components/insurance-status/cca-button-modal";
|
|
|
|
export default function InsuranceStatusPage() {
|
|
const { user } = useAuth();
|
|
const { toast } = useToast();
|
|
const dispatch = useAppDispatch();
|
|
const { status, message, show } = useAppSelector(
|
|
(state) => state.seleniumTasks.eligibilityCheck,
|
|
);
|
|
const [selectedPatient, setSelectedPatient] = useState<Patient | null>(null);
|
|
const [location, setLocation] = useLocation();
|
|
|
|
// Insurance eligibility and claim check form fields
|
|
const [memberId, setMemberId] = useState("");
|
|
const [dateOfBirth, setDateOfBirth] = useState<Date | null>(null);
|
|
const [firstName, setFirstName] = useState("");
|
|
const [lastName, setLastName] = useState("");
|
|
const isFormIncomplete = !memberId || !dateOfBirth;
|
|
const [isCheckingEligibilityStatus, setIsCheckingEligibilityStatus] =
|
|
useState(false);
|
|
const [isCheckingEligibilityAppointment, setIsCheckingEligibilityAppointment] =
|
|
useState(false);
|
|
const [isCheckingEligibilityClaimsPreAuth, setIsCheckingEligibilityClaimsPreAuth] =
|
|
useState(false);
|
|
const [isCheckingEligibilityHistory, setIsCheckingEligibilityHistory] =
|
|
useState(false);
|
|
const [isCheckingCMSP, setIsCheckingCMSP] = useState(false);
|
|
|
|
// AI Call Insurance section
|
|
const [aiCallOpen, setAiCallOpen] = useState(false);
|
|
const [aiSelectedContactId, setAiSelectedContactId] = useState<string>("");
|
|
const [aiCallNotes, setAiCallNotes] = useState("");
|
|
|
|
type InsuranceContact = { id: number; name: string; phoneNumber?: string | null };
|
|
const { data: insuranceContacts = [] } = useQuery<InsuranceContact[]>({
|
|
queryKey: ["/api/insurance-contacts"],
|
|
queryFn: async () => {
|
|
const res = await apiRequest("GET", "/api/insurance-contacts");
|
|
if (!res.ok) return [];
|
|
return res.json();
|
|
},
|
|
});
|
|
|
|
const selectedInsuranceContact = insuranceContacts.find(
|
|
(c) => String(c.id) === aiSelectedContactId
|
|
) ?? null;
|
|
|
|
// PDF preview modal state
|
|
const [previewOpen, setPreviewOpen] = useState(false);
|
|
const [previewPdfId, setPreviewPdfId] = useState<number | null>(null);
|
|
const [previewFallbackFilename, setPreviewFallbackFilename] = useState<
|
|
string | null
|
|
>(null);
|
|
|
|
// Dual PDF modal state (used by MH Eligibility & History)
|
|
const [dualPreviewOpen, setDualPreviewOpen] = useState(false);
|
|
const [dualEligibilityPdfId, setDualEligibilityPdfId] = useState<number | null>(null);
|
|
const [dualEligibilityFilename, setDualEligibilityFilename] = useState<string | null>(null);
|
|
const [dualMemberDetailsPdfId, setDualMemberDetailsPdfId] = useState<number | null>(null);
|
|
const [dualMemberDetailsFilename, setDualMemberDetailsFilename] = useState<string | null>(null);
|
|
const [dualHistoryPdfId, setDualHistoryPdfId] = useState<number | null>(null);
|
|
const [dualHistoryFilename, setDualHistoryFilename] = useState<string | null>(null);
|
|
|
|
// CMSP PDF modal state (used by CMSP Eligibility & History & Remaining)
|
|
const [cmspPreviewOpen, setCmspPreviewOpen] = useState(false);
|
|
const [cmspEligibilityPdfId, setCmspEligibilityPdfId] = useState<number | null>(null);
|
|
const [cmspEligibilityFilename, setCmspEligibilityFilename] = useState<string | null>(null);
|
|
const [cmspMemberDetailsPdfId, setCmspMemberDetailsPdfId] = useState<number | null>(null);
|
|
const [cmspMemberDetailsFilename, setCmspMemberDetailsFilename] = useState<string | null>(null);
|
|
const [cmspHistoryPdfId, setCmspHistoryPdfId] = useState<number | null>(null);
|
|
const [cmspHistoryFilename, setCmspHistoryFilename] = useState<string | null>(null);
|
|
const [cmspAccumulatorPdfId, setCmspAccumulatorPdfId] = useState<number | null>(null);
|
|
const [cmspAccumulatorFilename, setCmspAccumulatorFilename] = useState<string | null>(null);
|
|
|
|
// Populate fields from selected patient
|
|
useEffect(() => {
|
|
if (selectedPatient) {
|
|
setMemberId(selectedPatient.insuranceId ?? "");
|
|
setFirstName(selectedPatient.firstName ?? "");
|
|
setLastName(selectedPatient.lastName ?? "");
|
|
|
|
const dob =
|
|
typeof selectedPatient.dateOfBirth === "string"
|
|
? parseLocalDate(selectedPatient.dateOfBirth)
|
|
: selectedPatient.dateOfBirth;
|
|
setDateOfBirth(dob);
|
|
} else {
|
|
setMemberId("");
|
|
setFirstName("");
|
|
setLastName("");
|
|
setDateOfBirth(null);
|
|
}
|
|
}, [selectedPatient]);
|
|
|
|
// Auto-lookup patient by member ID when typed manually
|
|
useEffect(() => {
|
|
if (selectedPatient && memberId === (selectedPatient.insuranceId ?? "")) return;
|
|
if (!memberId) return;
|
|
|
|
const timer = setTimeout(async () => {
|
|
try {
|
|
const res = await apiRequest("GET", `/api/patients/by-insurance-id?insuranceId=${encodeURIComponent(memberId)}`);
|
|
if (!res.ok) return;
|
|
const patient: Patient | null = await res.json();
|
|
if (patient) {
|
|
setFirstName(patient.firstName ?? "");
|
|
setLastName(patient.lastName ?? "");
|
|
const dob =
|
|
typeof patient.dateOfBirth === "string"
|
|
? parseLocalDate(patient.dateOfBirth)
|
|
: patient.dateOfBirth ?? null;
|
|
setDateOfBirth(dob);
|
|
}
|
|
} catch {
|
|
// silently ignore lookup errors
|
|
}
|
|
}, 500);
|
|
|
|
return () => clearTimeout(timer);
|
|
}, [memberId, selectedPatient]);
|
|
|
|
// Add patient mutation
|
|
const addPatientMutation = useMutation({
|
|
mutationFn: async (patient: InsertPatient) => {
|
|
const res = await apiRequest("POST", "/api/patients/", patient);
|
|
return res.json();
|
|
},
|
|
onSuccess: () => {
|
|
queryClient.invalidateQueries({ queryKey: QK_PATIENTS_BASE });
|
|
toast({
|
|
title: "Success",
|
|
description: "Patient added successfully!",
|
|
variant: "default",
|
|
});
|
|
},
|
|
onError: (error: any) => {
|
|
const msg = error.message;
|
|
|
|
if (msg === "A patient with this insurance ID already exists.") {
|
|
toast({
|
|
title: "Patient already exists",
|
|
description: msg,
|
|
variant: "destructive",
|
|
});
|
|
} else {
|
|
toast({
|
|
title: "Error",
|
|
description: `Failed to add patient: ${msg}`,
|
|
variant: "destructive",
|
|
});
|
|
}
|
|
},
|
|
});
|
|
|
|
// Shared: run MH eligibility selenium job, return jobResult or throw
|
|
const runMHEligibilitySelenium = async (): Promise<any> => {
|
|
const formattedDob = dateOfBirth ? formatLocalDate(dateOfBirth) : "";
|
|
|
|
const data = {
|
|
memberId,
|
|
dateOfBirth: formattedDob,
|
|
insuranceSiteKey: "MH",
|
|
firstName: firstName || undefined,
|
|
lastName: lastName || undefined,
|
|
};
|
|
|
|
dispatch(
|
|
setTaskStatus({
|
|
key: "eligibilityCheck",
|
|
status: "pending",
|
|
message: "Sending Data to Selenium...",
|
|
}),
|
|
);
|
|
|
|
const response = await apiRequest(
|
|
"POST",
|
|
"/api/insurance-status/eligibility-check",
|
|
{ data: data, socketId: socket.id },
|
|
);
|
|
const enqueueResult = await response.json();
|
|
if (enqueueResult.error) throw new Error(enqueueResult.error);
|
|
|
|
const jobId = enqueueResult.jobId;
|
|
if (!jobId) throw new Error("No jobId returned from server");
|
|
|
|
dispatch(
|
|
setTaskStatus({
|
|
key: "eligibilityCheck",
|
|
status: "pending",
|
|
message: "Selenium browser starting...",
|
|
}),
|
|
);
|
|
|
|
return new Promise<any>((resolve, reject) => {
|
|
const handler = (payload: any) => {
|
|
if (String(payload.jobId) !== String(jobId)) return;
|
|
if (payload.status === "active") {
|
|
dispatch(
|
|
setTaskStatus({
|
|
key: "eligibilityCheck",
|
|
status: "pending",
|
|
message: payload.message ?? "Selenium running...",
|
|
}),
|
|
);
|
|
} else if (payload.status === "completed") {
|
|
socket.off("job:update", handler);
|
|
resolve(payload.result ?? {});
|
|
} else if (payload.status === "failed") {
|
|
socket.off("job:update", handler);
|
|
reject(new Error(payload.error ?? "Selenium job failed"));
|
|
}
|
|
};
|
|
socket.on("job:update", handler);
|
|
});
|
|
};
|
|
|
|
const handleAddPatient = async () => {
|
|
const newPatient: InsertPatient = {
|
|
firstName,
|
|
lastName,
|
|
dateOfBirth: dateOfBirth,
|
|
gender: "",
|
|
phone: "",
|
|
userId: user?.id ?? 1,
|
|
insuranceId: memberId,
|
|
};
|
|
await addPatientMutation.mutateAsync(newPatient);
|
|
};
|
|
|
|
// MH Eligibility — check eligibility, save to DB, open PDF preview
|
|
const handleMHEligibilityButton = async () => {
|
|
if (!memberId || !dateOfBirth) {
|
|
toast({
|
|
title: "Missing Fields",
|
|
description: "Please fill in all the required fields: Member ID, Date of Birth.",
|
|
variant: "destructive",
|
|
});
|
|
return;
|
|
}
|
|
|
|
setIsCheckingEligibilityStatus(true);
|
|
try {
|
|
const jobResult = await runMHEligibilitySelenium();
|
|
|
|
dispatch(
|
|
setTaskStatus({
|
|
key: "eligibilityCheck",
|
|
status: "success",
|
|
message: "Patient status is updated, and its eligibility pdf is uploaded at Document Page.",
|
|
}),
|
|
);
|
|
toast({
|
|
title: "Selenium service done.",
|
|
description: "Your Patient Eligibility is fetched and updated, Kindly search through the patient.",
|
|
variant: "default",
|
|
});
|
|
|
|
setSelectedPatient(null);
|
|
await queryClient.invalidateQueries({ queryKey: QK_PATIENTS_BASE });
|
|
|
|
if (jobResult.pdfFileId) {
|
|
setPreviewPdfId(Number(jobResult.pdfFileId));
|
|
setPreviewFallbackFilename(jobResult.pdfFilename ?? `eligibility_${memberId}.pdf`);
|
|
setPreviewOpen(true);
|
|
}
|
|
} catch (error: any) {
|
|
dispatch(setTaskStatus({ key: "eligibilityCheck", status: "error", message: error.message || "Selenium submission failed" }));
|
|
toast({ title: "Selenium service error", description: error.message || "An error occurred.", variant: "destructive" });
|
|
} finally {
|
|
setIsCheckingEligibilityStatus(false);
|
|
}
|
|
};
|
|
|
|
// MH Eligibility & Appointment — check eligibility, save to DB, navigate to appointments
|
|
const handleMHEligibilityAppointmentButton = async () => {
|
|
if (!memberId || !dateOfBirth) {
|
|
toast({
|
|
title: "Missing Fields",
|
|
description: "Please fill in all the required fields: Member ID, Date of Birth.",
|
|
variant: "destructive",
|
|
});
|
|
return;
|
|
}
|
|
|
|
setIsCheckingEligibilityAppointment(true);
|
|
try {
|
|
const jobResult = await runMHEligibilitySelenium();
|
|
|
|
const isInactive = jobResult?.patientUpdateStatus?.includes("INACTIVE");
|
|
|
|
if (isInactive) {
|
|
dispatch(
|
|
setTaskStatus({
|
|
key: "eligibilityCheck",
|
|
status: "error",
|
|
message: "Insurance is inactive. Staying on Eligibility page.",
|
|
}),
|
|
);
|
|
toast({
|
|
title: "Insurance Inactive",
|
|
description: "Patient insurance is inactive. Staying on Eligibility page.",
|
|
variant: "destructive",
|
|
});
|
|
await queryClient.invalidateQueries({ queryKey: QK_PATIENTS_BASE });
|
|
return;
|
|
}
|
|
|
|
dispatch(
|
|
setTaskStatus({
|
|
key: "eligibilityCheck",
|
|
status: "success",
|
|
message: "Eligibility checked and saved. Redirecting to Appointments...",
|
|
}),
|
|
);
|
|
toast({
|
|
title: "Eligibility checked.",
|
|
description: "Patient eligibility saved. Redirecting to Appointments.",
|
|
variant: "default",
|
|
});
|
|
|
|
setSelectedPatient(null);
|
|
await queryClient.invalidateQueries({ queryKey: QK_PATIENTS_BASE });
|
|
setLocation("/appointments");
|
|
} catch (error: any) {
|
|
dispatch(setTaskStatus({ key: "eligibilityCheck", status: "error", message: error.message || "Selenium submission failed" }));
|
|
toast({ title: "Selenium service error", description: error.message || "An error occurred.", variant: "destructive" });
|
|
} finally {
|
|
setIsCheckingEligibilityAppointment(false);
|
|
}
|
|
};
|
|
|
|
// MH Eligibility & Claims/PreAuth — check eligibility, save to DB, navigate to claims
|
|
const handleMHEligibilityClaimsPreAuthButton = async () => {
|
|
if (!memberId || !dateOfBirth) {
|
|
toast({
|
|
title: "Missing Fields",
|
|
description: "Please fill in all the required fields: Member ID, Date of Birth.",
|
|
variant: "destructive",
|
|
});
|
|
return;
|
|
}
|
|
|
|
setIsCheckingEligibilityClaimsPreAuth(true);
|
|
try {
|
|
await runMHEligibilitySelenium();
|
|
|
|
dispatch(
|
|
setTaskStatus({
|
|
key: "eligibilityCheck",
|
|
status: "success",
|
|
message: "Eligibility checked and saved. Redirecting to Claims/PreAuth...",
|
|
}),
|
|
);
|
|
toast({
|
|
title: "Eligibility checked.",
|
|
description: "Patient eligibility saved. Redirecting to Claims/PreAuth.",
|
|
variant: "default",
|
|
});
|
|
|
|
setSelectedPatient(null);
|
|
await queryClient.invalidateQueries({ queryKey: QK_PATIENTS_BASE });
|
|
setLocation("/claims");
|
|
} catch (error: any) {
|
|
dispatch(setTaskStatus({ key: "eligibilityCheck", status: "error", message: error.message || "Selenium submission failed" }));
|
|
toast({ title: "Selenium service error", description: error.message || "An error occurred.", variant: "destructive" });
|
|
} finally {
|
|
setIsCheckingEligibilityClaimsPreAuth(false);
|
|
}
|
|
};
|
|
|
|
// MH Eligibility & History — eligibility check + service history, saves both PDFs, auto-downloads eligibility PDF only
|
|
const handleMHEligibilityHistoryButton = async () => {
|
|
if (!memberId || !dateOfBirth) {
|
|
toast({
|
|
title: "Missing Fields",
|
|
description: "Please fill in all the required fields: Member ID, Date of Birth.",
|
|
variant: "destructive",
|
|
});
|
|
return;
|
|
}
|
|
|
|
setIsCheckingEligibilityHistory(true);
|
|
try {
|
|
const formattedDob = dateOfBirth ? formatLocalDate(dateOfBirth) : "";
|
|
const data = {
|
|
memberId,
|
|
dateOfBirth: formattedDob,
|
|
insuranceSiteKey: "MH",
|
|
firstName: firstName || undefined,
|
|
lastName: lastName || undefined,
|
|
};
|
|
|
|
dispatch(setTaskStatus({ key: "eligibilityCheck", status: "pending", message: "Sending Data to Selenium..." }));
|
|
|
|
const response = await apiRequest(
|
|
"POST",
|
|
"/api/insurance-status/eligibility-history-check",
|
|
{ data, socketId: socket.id },
|
|
);
|
|
const enqueueResult = await response.json();
|
|
if (enqueueResult.error) throw new Error(enqueueResult.error);
|
|
|
|
const jobId = enqueueResult.jobId;
|
|
if (!jobId) throw new Error("No jobId returned from server");
|
|
|
|
dispatch(setTaskStatus({ key: "eligibilityCheck", status: "pending", message: "Selenium browser starting..." }));
|
|
|
|
const jobResult = await new Promise<any>((resolve, reject) => {
|
|
const handler = (payload: any) => {
|
|
if (String(payload.jobId) !== String(jobId)) return;
|
|
if (payload.status === "active") {
|
|
dispatch(setTaskStatus({ key: "eligibilityCheck", status: "pending", message: payload.message ?? "Selenium running..." }));
|
|
} else if (payload.status === "completed") {
|
|
socket.off("job:update", handler);
|
|
resolve(payload.result ?? {});
|
|
} else if (payload.status === "failed") {
|
|
socket.off("job:update", handler);
|
|
reject(new Error(payload.error ?? "Selenium job failed"));
|
|
}
|
|
};
|
|
socket.on("job:update", handler);
|
|
});
|
|
|
|
dispatch(setTaskStatus({ key: "eligibilityCheck", status: "success", message: "Eligibility and service history PDFs saved to Documents." }));
|
|
toast({
|
|
title: "Done",
|
|
description: "Eligibility and service history PDFs saved. Eligibility PDF downloading now.",
|
|
variant: "default",
|
|
});
|
|
|
|
setSelectedPatient(null);
|
|
await queryClient.invalidateQueries({ queryKey: QK_PATIENTS_BASE });
|
|
|
|
// Open all PDFs side by side in the modal
|
|
if (jobResult.pdfFileId || jobResult.memberDetailsPdfFileId || jobResult.historyPdfFileId) {
|
|
setDualEligibilityPdfId(jobResult.pdfFileId ? Number(jobResult.pdfFileId) : null);
|
|
setDualEligibilityFilename(jobResult.pdfFilename ?? `eligibility_${memberId}.pdf`);
|
|
setDualMemberDetailsPdfId(jobResult.memberDetailsPdfFileId ? Number(jobResult.memberDetailsPdfFileId) : null);
|
|
setDualMemberDetailsFilename(jobResult.memberDetailsPdfFilename ?? `eligibility_member_details_${memberId}.pdf`);
|
|
setDualHistoryPdfId(jobResult.historyPdfFileId ? Number(jobResult.historyPdfFileId) : null);
|
|
setDualHistoryFilename(jobResult.historyPdfFilename ?? `eligibility_history_${memberId}.pdf`);
|
|
setDualPreviewOpen(true);
|
|
}
|
|
} catch (error: any) {
|
|
dispatch(setTaskStatus({ key: "eligibilityCheck", status: "error", message: error.message || "Selenium submission failed" }));
|
|
toast({ title: "Selenium service error", description: error.message || "An error occurred.", variant: "destructive" });
|
|
} finally {
|
|
setIsCheckingEligibilityHistory(false);
|
|
}
|
|
};
|
|
|
|
// CMSP Eligibility & History & Remaining — eligibility + service history + accumulator PDFs
|
|
const handleCMSPButton = async () => {
|
|
if (!memberId || !dateOfBirth) {
|
|
toast({
|
|
title: "Missing Fields",
|
|
description: "Please fill in Member ID and Date of Birth.",
|
|
variant: "destructive",
|
|
});
|
|
return;
|
|
}
|
|
|
|
setIsCheckingCMSP(true);
|
|
try {
|
|
const formattedDob = dateOfBirth ? formatLocalDate(dateOfBirth) : "";
|
|
const data = {
|
|
memberId,
|
|
dateOfBirth: formattedDob,
|
|
insuranceSiteKey: "MH",
|
|
firstName: firstName || undefined,
|
|
lastName: lastName || undefined,
|
|
};
|
|
|
|
dispatch(setTaskStatus({ key: "eligibilityCheck", status: "pending", message: "Sending Data to Selenium..." }));
|
|
|
|
const response = await apiRequest(
|
|
"POST",
|
|
"/api/insurance-status/cmsp-eligibility-history-remaining-check",
|
|
{ data, socketId: socket.id },
|
|
);
|
|
const enqueueResult = await response.json();
|
|
if (enqueueResult.error) throw new Error(enqueueResult.error);
|
|
|
|
const jobId = enqueueResult.jobId;
|
|
if (!jobId) throw new Error("No jobId returned from server");
|
|
|
|
dispatch(setTaskStatus({ key: "eligibilityCheck", status: "pending", message: "Selenium browser starting..." }));
|
|
|
|
const jobResult = await new Promise<any>((resolve, reject) => {
|
|
const handler = (payload: any) => {
|
|
if (String(payload.jobId) !== String(jobId)) return;
|
|
if (payload.status === "active") {
|
|
dispatch(setTaskStatus({ key: "eligibilityCheck", status: "pending", message: payload.message ?? "Selenium running..." }));
|
|
} else if (payload.status === "completed") {
|
|
socket.off("job:update", handler);
|
|
resolve(payload.result ?? {});
|
|
} else if (payload.status === "failed") {
|
|
socket.off("job:update", handler);
|
|
reject(new Error(payload.error ?? "Selenium job failed"));
|
|
}
|
|
};
|
|
socket.on("job:update", handler);
|
|
});
|
|
|
|
dispatch(setTaskStatus({ key: "eligibilityCheck", status: "success", message: "CMSP PDFs saved to Documents." }));
|
|
toast({
|
|
title: "Done",
|
|
description: "Eligibility, history, and accumulator PDFs saved.",
|
|
variant: "default",
|
|
});
|
|
|
|
setSelectedPatient(null);
|
|
await queryClient.invalidateQueries({ queryKey: QK_PATIENTS_BASE });
|
|
|
|
// Open 4-panel modal
|
|
if (jobResult.pdfFileId || jobResult.memberDetailsPdfFileId || jobResult.historyPdfFileId || jobResult.accumulatorPdfFileId) {
|
|
setCmspEligibilityPdfId(jobResult.pdfFileId ? Number(jobResult.pdfFileId) : null);
|
|
setCmspEligibilityFilename(jobResult.pdfFilename ?? `cmsp_eligibility_${memberId}.pdf`);
|
|
setCmspMemberDetailsPdfId(jobResult.memberDetailsPdfFileId ? Number(jobResult.memberDetailsPdfFileId) : null);
|
|
setCmspMemberDetailsFilename(jobResult.memberDetailsPdfFilename ?? `cmsp_member_details_${memberId}.pdf`);
|
|
setCmspHistoryPdfId(jobResult.historyPdfFileId ? Number(jobResult.historyPdfFileId) : null);
|
|
setCmspHistoryFilename(jobResult.historyPdfFilename ?? `cmsp_history_${memberId}.pdf`);
|
|
setCmspAccumulatorPdfId(jobResult.accumulatorPdfFileId ? Number(jobResult.accumulatorPdfFileId) : null);
|
|
setCmspAccumulatorFilename(jobResult.accumulatorPdfFilename ?? `cmsp_accumulator_${memberId}.pdf`);
|
|
setCmspPreviewOpen(true);
|
|
}
|
|
} catch (error: any) {
|
|
dispatch(setTaskStatus({ key: "eligibilityCheck", status: "error", message: error.message || "Selenium submission failed" }));
|
|
toast({ title: "Selenium service error", description: error.message || "An error occurred.", variant: "destructive" });
|
|
} finally {
|
|
setIsCheckingCMSP(false);
|
|
}
|
|
};
|
|
|
|
// small helper: remove given query params from the current URL (silent, no reload)
|
|
const clearUrlParams = (params: string[]) => {
|
|
try {
|
|
const url = new URL(window.location.href);
|
|
let changed = false;
|
|
for (const p of params) {
|
|
if (url.searchParams.has(p)) {
|
|
url.searchParams.delete(p);
|
|
changed = true;
|
|
}
|
|
}
|
|
if (changed) {
|
|
window.history.replaceState({}, document.title, url.toString());
|
|
}
|
|
} catch (e) {
|
|
// ignore
|
|
}
|
|
};
|
|
|
|
// handling case-1, when redirect happens from appointment page:
|
|
useEffect(() => {
|
|
const params = new URLSearchParams(window.location.search);
|
|
const appointmentId = params.get("appointmentId");
|
|
const action = params.get("action"); // 'eligibility' | 'claim'
|
|
if (!appointmentId) return;
|
|
const id = Number(appointmentId);
|
|
if (Number.isNaN(id) || id <= 0) return;
|
|
if (!action || (action !== "eligibility" && action !== "claim")) return;
|
|
|
|
let cancelled = false;
|
|
|
|
(async () => {
|
|
try {
|
|
const res = await apiRequest("GET", `/api/appointments/${id}/patient`);
|
|
if (!res.ok) {
|
|
let body: any = null;
|
|
try {
|
|
body = await res.json();
|
|
} catch {}
|
|
if (!cancelled) {
|
|
toast({
|
|
title: "Failed to load patient",
|
|
description:
|
|
body?.message ??
|
|
body?.error ??
|
|
`Could not fetch patient for appointment ${id}.`,
|
|
variant: "destructive",
|
|
});
|
|
}
|
|
return;
|
|
}
|
|
|
|
const data = await res.json();
|
|
const patient = data?.patient ?? data;
|
|
if (!cancelled && patient) {
|
|
// set selectedPatient as before
|
|
setSelectedPatient(patient as Patient);
|
|
|
|
clearUrlParams(["appointmentId", "action"]);
|
|
}
|
|
} catch (err: any) {
|
|
if (!cancelled) {
|
|
console.error("Error fetching patient for appointment:", err);
|
|
toast({
|
|
title: "Error",
|
|
description:
|
|
err?.message ?? "An error occurred while fetching patient.",
|
|
variant: "destructive",
|
|
});
|
|
}
|
|
}
|
|
})();
|
|
|
|
return () => {
|
|
cancelled = true;
|
|
};
|
|
}, [location]);
|
|
|
|
// handling case-1, when redirect happens from appointment page:
|
|
useEffect(() => {
|
|
const params = new URLSearchParams(window.location.search);
|
|
const appointmentId = params.get("appointmentId");
|
|
if (!appointmentId) return;
|
|
|
|
const id = Number(appointmentId);
|
|
if (Number.isNaN(id) || id <= 0) return;
|
|
|
|
let cancelled = false;
|
|
|
|
(async () => {
|
|
try {
|
|
const res = await apiRequest("GET", `/api/appointments/${id}/patient`);
|
|
if (!res.ok) return;
|
|
|
|
const data = await res.json();
|
|
const patient = data?.patient ?? data;
|
|
|
|
if (!cancelled && patient) {
|
|
// ✅ ONLY prefill patient
|
|
setSelectedPatient(patient as Patient);
|
|
|
|
// ✅ clean URL (no auto selenium)
|
|
clearUrlParams(["appointmentId", "action"]);
|
|
}
|
|
} catch (err) {
|
|
console.error("Failed to fetch patient from appointment", err);
|
|
}
|
|
})();
|
|
|
|
return () => {
|
|
cancelled = true;
|
|
};
|
|
}, [location]);
|
|
|
|
return (
|
|
<div>
|
|
<SeleniumTaskBanner
|
|
status={status}
|
|
message={message}
|
|
show={show}
|
|
onClear={() => dispatch(clearTaskStatus("eligibilityCheck"))}
|
|
/>
|
|
|
|
<div className="container mx-auto space-y-6">
|
|
<div className="flex justify-between items-center">
|
|
<div>
|
|
<h1 className="text-3xl font-bold tracking-tight">
|
|
Insurance Eligibility
|
|
</h1>
|
|
<p className="text-muted-foreground">
|
|
Check insurance eligibility.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Insurance Eligibility Check Form */}
|
|
<Card className="mb-6">
|
|
<CardContent>
|
|
<div className="grid grid-cols-4 md:grid-cols-4 gap-4 mb-4">
|
|
<div className="space-y-2">
|
|
<Label htmlFor="memberId">Member ID</Label>
|
|
<Input
|
|
id="memberId"
|
|
placeholder="Enter member ID"
|
|
value={memberId}
|
|
onChange={(e) => setMemberId(e.target.value)}
|
|
/>
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
<DateInput
|
|
label="Date of Birth"
|
|
value={dateOfBirth}
|
|
onChange={setDateOfBirth}
|
|
disableFuture
|
|
/>
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
<Label htmlFor="firstName">First Name <span className="text-muted-foreground font-normal">(Optional)</span></Label>
|
|
<Input
|
|
id="firstName"
|
|
placeholder="Enter first name"
|
|
value={firstName}
|
|
onChange={(e) => setFirstName(e.target.value)}
|
|
/>
|
|
</div>
|
|
<div className="space-y-2">
|
|
<Label htmlFor="lastName">Last Name <span className="text-muted-foreground font-normal">(Optional)</span></Label>
|
|
<Input
|
|
id="lastName"
|
|
placeholder="Enter last name"
|
|
value={lastName}
|
|
onChange={(e) => setLastName(e.target.value)}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex flex-col-2 gap-4">
|
|
<Button
|
|
onClick={() => handleMHEligibilityButton()}
|
|
className="w-full"
|
|
disabled={isCheckingEligibilityStatus}
|
|
>
|
|
{isCheckingEligibilityStatus ? (
|
|
<>
|
|
<LoaderCircleIcon className="h-4 w-4 mr-2 animate-spin" />
|
|
Processing...
|
|
</>
|
|
) : (
|
|
<>
|
|
<CheckCircle className="h-4 w-4 mr-2" />
|
|
MH Eligibility
|
|
</>
|
|
)}
|
|
</Button>
|
|
|
|
<Button
|
|
onClick={() => handleMHEligibilityAppointmentButton()}
|
|
className="w-full"
|
|
disabled={isCheckingEligibilityAppointment}
|
|
>
|
|
{isCheckingEligibilityAppointment ? (
|
|
<>
|
|
<LoaderCircleIcon className="h-4 w-4 mr-2 animate-spin" />
|
|
Processing...
|
|
</>
|
|
) : (
|
|
<>
|
|
<CheckCircle className="h-4 w-4 mr-2" />
|
|
MH Eligibility&Appointment
|
|
</>
|
|
)}
|
|
</Button>
|
|
|
|
<Button
|
|
onClick={() => handleMHEligibilityClaimsPreAuthButton()}
|
|
className="w-full"
|
|
disabled={isCheckingEligibilityClaimsPreAuth}
|
|
>
|
|
{isCheckingEligibilityClaimsPreAuth ? (
|
|
<>
|
|
<LoaderCircleIcon className="h-4 w-4 mr-2 animate-spin" />
|
|
Processing...
|
|
</>
|
|
) : (
|
|
<>
|
|
<CheckCircle className="h-4 w-4 mr-2" />
|
|
MH Eligibility&Claims/PreAuth
|
|
</>
|
|
)}
|
|
</Button>
|
|
</div>
|
|
|
|
<div className="flex flex-col-2 gap-4 mt-4">
|
|
<Button
|
|
className="w-full"
|
|
disabled={isCheckingEligibilityHistory}
|
|
onClick={() => handleMHEligibilityHistoryButton()}
|
|
>
|
|
{isCheckingEligibilityHistory ? (
|
|
<>
|
|
<LoaderCircleIcon className="h-4 w-4 mr-2 animate-spin" />
|
|
Processing...
|
|
</>
|
|
) : (
|
|
<>
|
|
<CheckCircle className="h-4 w-4 mr-2" />
|
|
MH Eligibility & History
|
|
</>
|
|
)}
|
|
</Button>
|
|
|
|
<Button
|
|
className="w-full"
|
|
disabled={isCheckingCMSP}
|
|
onClick={() => handleCMSPButton()}
|
|
>
|
|
{isCheckingCMSP ? (
|
|
<>
|
|
<LoaderCircleIcon className="h-4 w-4 mr-2 animate-spin" />
|
|
Processing...
|
|
</>
|
|
) : (
|
|
<>
|
|
<CheckCircle className="h-4 w-4 mr-2" />
|
|
CMSP Eligibility & History & Remaining
|
|
</>
|
|
)}
|
|
</Button>
|
|
</div>
|
|
|
|
{/* TEMP PROVIDER BUTTONS */}
|
|
<div className="space-y-4 mt-6">
|
|
<h3 className="text-sm font-medium text-muted-foreground">
|
|
Other provider checks
|
|
</h3>
|
|
|
|
{/* Row 1 */}
|
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
|
<DdmaEligibilityButton
|
|
memberId={memberId}
|
|
dateOfBirth={dateOfBirth}
|
|
firstName={firstName}
|
|
lastName={lastName}
|
|
isFormIncomplete={isFormIncomplete}
|
|
onPdfReady={(pdfId, fallbackFilename) => {
|
|
setPreviewPdfId(pdfId);
|
|
setPreviewFallbackFilename(
|
|
fallbackFilename ?? `eligibility_ddma_${memberId}.pdf`,
|
|
);
|
|
setPreviewOpen(true);
|
|
}}
|
|
/>
|
|
|
|
<DeltaInsEligibilityButton
|
|
memberId={memberId}
|
|
dateOfBirth={dateOfBirth}
|
|
firstName={firstName}
|
|
lastName={lastName}
|
|
isFormIncomplete={isFormIncomplete}
|
|
onPdfReady={(pdfId, fallbackFilename) => {
|
|
setPreviewPdfId(pdfId);
|
|
setPreviewFallbackFilename(
|
|
fallbackFilename ?? `eligibility_deltains_${memberId}.pdf`,
|
|
);
|
|
setPreviewOpen(true);
|
|
}}
|
|
/>
|
|
|
|
<Button
|
|
className="w-full"
|
|
variant="outline"
|
|
disabled={isFormIncomplete}
|
|
>
|
|
<CheckCircle className="h-4 w-4 mr-2" />
|
|
BCBS
|
|
</Button>
|
|
</div>
|
|
|
|
{/* Row 2 */}
|
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
|
<TuftsSCOEligibilityButton
|
|
memberId={memberId}
|
|
dateOfBirth={dateOfBirth}
|
|
firstName={firstName}
|
|
lastName={lastName}
|
|
isFormIncomplete={isFormIncomplete}
|
|
onPdfReady={(pdfId, fallbackFilename) => {
|
|
setPreviewPdfId(pdfId);
|
|
setPreviewFallbackFilename(
|
|
fallbackFilename ?? `eligibility_unitedsco_${memberId}.pdf`,
|
|
);
|
|
setPreviewOpen(true);
|
|
}}
|
|
/>
|
|
|
|
<UnitedSCOEligibilityButton
|
|
memberId={memberId}
|
|
dateOfBirth={dateOfBirth}
|
|
firstName={firstName}
|
|
lastName={lastName}
|
|
isFormIncomplete={isFormIncomplete}
|
|
onPdfReady={(pdfId, fallbackFilename) => {
|
|
setPreviewPdfId(pdfId);
|
|
setPreviewFallbackFilename(
|
|
fallbackFilename ?? `eligibility_unitedsco_${memberId}.pdf`,
|
|
);
|
|
setPreviewOpen(true);
|
|
}}
|
|
/>
|
|
|
|
<CCAEligibilityButton
|
|
memberId={memberId}
|
|
dateOfBirth={dateOfBirth}
|
|
firstName={firstName}
|
|
lastName={lastName}
|
|
isFormIncomplete={isFormIncomplete}
|
|
onPdfReady={(pdfId, fallbackFilename) => {
|
|
setPreviewPdfId(pdfId);
|
|
setPreviewFallbackFilename(
|
|
fallbackFilename ?? `eligibility_cca_${memberId}.pdf`,
|
|
);
|
|
setPreviewOpen(true);
|
|
}}
|
|
/>
|
|
</div>
|
|
|
|
{/* Row 3 */}
|
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
|
<Button
|
|
className="w-full"
|
|
variant="outline"
|
|
disabled={isFormIncomplete}
|
|
>
|
|
<CheckCircle className="h-4 w-4 mr-2" />
|
|
United AAPR
|
|
</Button>
|
|
|
|
<Button
|
|
className="w-full"
|
|
variant="outline"
|
|
disabled={isFormIncomplete}
|
|
>
|
|
<CheckCircle className="h-4 w-4 mr-2" />
|
|
Aetna
|
|
</Button>
|
|
<Button
|
|
className="w-full"
|
|
variant="outline"
|
|
disabled={isFormIncomplete}
|
|
>
|
|
<CheckCircle className="h-4 w-4 mr-2" />
|
|
Altus
|
|
</Button>
|
|
<Button
|
|
className="w-full"
|
|
variant="outline"
|
|
disabled={isFormIncomplete}
|
|
>
|
|
<CheckCircle className="h-4 w-4 mr-2" />
|
|
Metlife Dental
|
|
</Button>
|
|
|
|
<Button
|
|
className="w-full"
|
|
variant="outline"
|
|
disabled={isFormIncomplete}
|
|
>
|
|
<CheckCircle className="h-4 w-4 mr-2" />
|
|
Cigna
|
|
</Button>
|
|
|
|
<Button
|
|
className="w-full"
|
|
variant="outline"
|
|
disabled={isFormIncomplete}
|
|
>
|
|
<CheckCircle className="h-4 w-4 mr-2" />
|
|
Delta WA
|
|
</Button>
|
|
</div>
|
|
|
|
{/* Row 5 */}
|
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
|
<Button
|
|
className="w-full"
|
|
variant="outline"
|
|
disabled={isFormIncomplete}
|
|
>
|
|
<CheckCircle className="h-4 w-4 mr-2" />
|
|
Delta IL
|
|
</Button>
|
|
|
|
<Button
|
|
className="w-full"
|
|
variant="outline"
|
|
disabled={isFormIncomplete}
|
|
>
|
|
<CheckCircle className="h-4 w-4 mr-2" />
|
|
Others
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* AI Call Insurance */}
|
|
<Card>
|
|
<CardHeader
|
|
className="cursor-pointer select-none"
|
|
onClick={() => setAiCallOpen((o) => !o)}
|
|
>
|
|
<div className="flex items-center justify-between">
|
|
<div className="flex items-center gap-3">
|
|
<div className="h-9 w-9 rounded-lg bg-violet-100 flex items-center justify-center flex-shrink-0">
|
|
<Bot className="h-5 w-5 text-violet-600" />
|
|
</div>
|
|
<div>
|
|
<CardTitle className="text-base">AI Call Insurance/Transportation</CardTitle>
|
|
<CardDescription className="mt-0.5">
|
|
Use AI to call the insurance company and check eligibility automatically
|
|
</CardDescription>
|
|
</div>
|
|
</div>
|
|
{aiCallOpen
|
|
? <ChevronUp className="h-4 w-4 text-muted-foreground" />
|
|
: <ChevronDown className="h-4 w-4 text-muted-foreground" />}
|
|
</div>
|
|
</CardHeader>
|
|
|
|
{aiCallOpen && (
|
|
<CardContent className="space-y-5 pt-0">
|
|
{/* Patient context banner */}
|
|
{selectedPatient && (
|
|
<div className="flex items-center gap-2 text-sm bg-violet-50 border border-violet-200 rounded-md px-3 py-2">
|
|
<Bot className="h-4 w-4 text-violet-500 flex-shrink-0" />
|
|
<span>
|
|
Selected patient:{" "}
|
|
<span className="font-medium">
|
|
{selectedPatient.firstName} {selectedPatient.lastName}
|
|
</span>
|
|
{selectedPatient.insuranceId && (
|
|
<> — Member ID: <span className="font-medium">{selectedPatient.insuranceId}</span></>
|
|
)}
|
|
</span>
|
|
</div>
|
|
)}
|
|
|
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
|
<div className="space-y-1.5">
|
|
<Label>Insurance Company</Label>
|
|
{insuranceContacts.length === 0 ? (
|
|
<p className="text-sm text-muted-foreground italic py-2">
|
|
No insurance contacts saved.{" "}
|
|
<a href="/settings/insurancecontact" className="underline text-violet-600">
|
|
Add one in Settings → Insurance Contact
|
|
</a>
|
|
</p>
|
|
) : (
|
|
<Select
|
|
value={aiSelectedContactId}
|
|
onValueChange={setAiSelectedContactId}
|
|
>
|
|
<SelectTrigger>
|
|
<SelectValue placeholder="Select insurance company…" />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
{insuranceContacts.map((c) => (
|
|
<SelectItem key={c.id} value={String(c.id)}>
|
|
{c.name}
|
|
</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
)}
|
|
</div>
|
|
|
|
<div className="space-y-1.5">
|
|
<Label>Phone Number</Label>
|
|
<Input
|
|
readOnly
|
|
value={selectedInsuranceContact?.phoneNumber ?? ""}
|
|
placeholder="Auto-filled from insurance contact"
|
|
className="bg-gray-50 text-gray-600 cursor-default"
|
|
/>
|
|
</div>
|
|
|
|
<div className="space-y-1.5 sm:col-span-2">
|
|
<Label htmlFor="ai-call-notes">
|
|
Additional Notes for AI{" "}
|
|
<span className="text-muted-foreground font-normal">(Optional)</span>
|
|
</Label>
|
|
<Input
|
|
id="ai-call-notes"
|
|
placeholder="e.g. Ask about dental benefits and annual maximum"
|
|
value={aiCallNotes}
|
|
onChange={(e) => setAiCallNotes(e.target.value)}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex items-center gap-3">
|
|
<Button
|
|
disabled
|
|
className="gap-2 bg-violet-600 hover:bg-violet-700 text-white opacity-60 cursor-not-allowed"
|
|
>
|
|
<PhoneCall className="h-4 w-4" />
|
|
Start AI Call
|
|
</Button>
|
|
<p className="text-xs text-muted-foreground italic">
|
|
{selectedInsuranceContact
|
|
? `Will call ${selectedInsuranceContact.name}${selectedInsuranceContact.phoneNumber ? ` at ${selectedInsuranceContact.phoneNumber}` : ""} — AI calling logic coming soon`
|
|
: "Select an insurance company to begin — AI calling logic coming soon"}
|
|
</p>
|
|
</div>
|
|
</CardContent>
|
|
)}
|
|
</Card>
|
|
|
|
{/* Patients Table */}
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Patient Records</CardTitle>
|
|
<CardDescription>
|
|
Select Patients and Check Their Eligibility
|
|
</CardDescription>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<PatientTable
|
|
allowView={true}
|
|
allowDelete={true}
|
|
allowCheckbox={true}
|
|
allowEdit={true}
|
|
onSelectPatient={setSelectedPatient}
|
|
/>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
|
|
{/* Pdf preview modal */}
|
|
<PdfPreviewModal
|
|
open={previewOpen}
|
|
onClose={() => {
|
|
setPreviewOpen(false);
|
|
setPreviewPdfId(null);
|
|
setPreviewFallbackFilename(null);
|
|
}}
|
|
pdfId={previewPdfId ?? undefined}
|
|
fallbackFilename={previewFallbackFilename ?? undefined}
|
|
autoDownload
|
|
/>
|
|
|
|
{/* 4-panel modal for CMSP — eligibility, member details, history, accumulator */}
|
|
<DualPdfPreviewModal
|
|
open={cmspPreviewOpen}
|
|
onClose={() => {
|
|
setCmspPreviewOpen(false);
|
|
setCmspEligibilityPdfId(null);
|
|
setCmspEligibilityFilename(null);
|
|
setCmspMemberDetailsPdfId(null);
|
|
setCmspMemberDetailsFilename(null);
|
|
setCmspHistoryPdfId(null);
|
|
setCmspHistoryFilename(null);
|
|
setCmspAccumulatorPdfId(null);
|
|
setCmspAccumulatorFilename(null);
|
|
}}
|
|
title="CMSP Eligibility, History & Remaining"
|
|
panels={[
|
|
{
|
|
pdfId: cmspEligibilityPdfId,
|
|
fallbackFilename: cmspEligibilityFilename,
|
|
label: "Eligibility",
|
|
autoDownload: true,
|
|
},
|
|
{
|
|
pdfId: cmspMemberDetailsPdfId,
|
|
fallbackFilename: cmspMemberDetailsFilename,
|
|
label: "Member Details",
|
|
},
|
|
{
|
|
pdfId: cmspHistoryPdfId,
|
|
fallbackFilename: cmspHistoryFilename,
|
|
label: "Service History",
|
|
},
|
|
{
|
|
pdfId: cmspAccumulatorPdfId,
|
|
fallbackFilename: cmspAccumulatorFilename,
|
|
label: "Accumulator (Remaining)",
|
|
},
|
|
]}
|
|
/>
|
|
|
|
{/* 3-panel modal for MH Eligibility & History */}
|
|
<DualPdfPreviewModal
|
|
open={dualPreviewOpen}
|
|
onClose={() => {
|
|
setDualPreviewOpen(false);
|
|
setDualEligibilityPdfId(null);
|
|
setDualEligibilityFilename(null);
|
|
setDualMemberDetailsPdfId(null);
|
|
setDualMemberDetailsFilename(null);
|
|
setDualHistoryPdfId(null);
|
|
setDualHistoryFilename(null);
|
|
}}
|
|
title="MH Eligibility & Service History"
|
|
panels={[
|
|
{
|
|
pdfId: dualEligibilityPdfId,
|
|
fallbackFilename: dualEligibilityFilename,
|
|
label: "Eligibility",
|
|
autoDownload: true,
|
|
},
|
|
{
|
|
pdfId: dualMemberDetailsPdfId,
|
|
fallbackFilename: dualMemberDetailsFilename,
|
|
label: "Member Details",
|
|
},
|
|
{
|
|
pdfId: dualHistoryPdfId,
|
|
fallbackFilename: dualHistoryFilename,
|
|
label: "Service History",
|
|
},
|
|
]}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|