import { useState, useEffect, useRef, useCallback, memo, useMemo } from "react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs"; import { Label } from "@/components/ui/label"; import { X, Calendar as CalendarIcon, HelpCircle, Trash2 } from "lucide-react"; import { useToast } from "@/hooks/use-toast"; import { Calendar } from "@/components/ui/calendar"; import { Popover, PopoverContent, PopoverTrigger, } from "@/components/ui/popover"; import { useQuery } from "@tanstack/react-query"; import { apiRequest } from "@/lib/queryClient"; import { MultipleFileUploadZone, MultipleFileUploadZoneHandle, } from "../file-upload/multiple-file-upload-zone"; import { useAuth } from "@/hooks/use-auth"; import { Tooltip, TooltipContent, TooltipTrigger, } from "@/components/ui/tooltip"; import { formatLocalDate, parseLocalDate } from "@/utils/dateUtils"; import { Claim, ClaimFileMeta, ClaimFormData, ClaimPreAuthData, InputServiceLine, InsertAppointment, MissingTeethStatus, NpiProvider, Patient, Staff, UpdateAppointment, UpdatePatient, } from "@repo/db/types"; import { Decimal } from "decimal.js"; import { mapPricesForForm, applyComboToForm, getDescriptionForCode, findPriceMismatches, type PriceMismatch, } from "@/utils/procedureCombosMapping"; import { PROCEDURE_COMBOS } from "@/utils/procedureCombos"; import { DateInput } from "../ui/dateInput"; import { MissingTeethSimple, type MissingMapStrict } from "./tooth-ui"; import { RemarksField } from "./claims-ui"; import { DirectComboButtons, RegularComboButtons, } from "@/components/procedure/procedure-combo-buttons"; import { Switch } from "@/components/ui/switch"; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from "@/components/ui/alert-dialog"; interface ClaimFormProps { patientId: number; appointmentId?: number; autoSubmit?: boolean; /** When true: form saves to AppointmentProcedure (Select Procedures flow), shows only Save button */ proceduresOnly?: boolean; onSubmit: (data: ClaimFormData) => Promise; onHandleAppointmentSubmit: ( appointmentData: InsertAppointment | UpdateAppointment, ) => Promise; onHandleUpdatePatient: (patient: UpdatePatient & { id: number }) => void; onHandleForMHSeleniumClaim: (data: ClaimFormData) => void; onHandleForMHSeleniumClaimPreAuth: (data: ClaimPreAuthData) => void; onHandleForCCASeleniumClaim: (data: ClaimFormData) => void; onHandleForCCASeleniumPreAuth: (data: ClaimPreAuthData) => void; onHandleForDDMASeleniumClaim: (data: ClaimFormData) => void; onHandleForUnitedDHSeleniumClaim: (data: ClaimFormData) => void; onHandleForTuftsSCOSeleniumClaim: (data: ClaimFormData) => void; onClose: () => void; isLicensed?: boolean; } export function ClaimForm({ patientId, appointmentId, autoSubmit, proceduresOnly = false, onHandleAppointmentSubmit, onHandleUpdatePatient, onHandleForMHSeleniumClaim, onHandleForMHSeleniumClaimPreAuth, onHandleForCCASeleniumClaim, onHandleForCCASeleniumPreAuth, onHandleForDDMASeleniumClaim, onHandleForUnitedDHSeleniumClaim, onHandleForTuftsSCOSeleniumClaim, onSubmit, onClose, isLicensed = false, }: ClaimFormProps) { const { toast } = useToast(); const { user } = useAuth(); const [prefillDone, setPrefillDone] = useState(false); const autoSubmittedRef = useRef(false); // When an existing claim is loaded for the appointment, store its ID so // the form submits an update instead of creating a new claim. const [existingClaimId, setExistingClaimId] = useState(null); const [directSubmitEnabled, setDirectSubmitEnabled] = useState(false); const [patient, setPatient] = useState(null); // staffId from the appointment column — used for claim creation, not shown in UI const [appointmentStaffId, setAppointmentStaffId] = useState(null); // npiProviderId loaded from AppointmentProcedure (2b) — restored to form when npiProviders load const [savedProcNpiId, setSavedProcNpiId] = useState(null); // Query patient based on given patient id const { data: fetchedPatient, isLoading, error, } = useQuery({ queryKey: ["/api/patients/", patientId], queryFn: async () => { const res = await apiRequest("GET", `/api/patients/${patientId}`); if (!res.ok) throw new Error("Failed to fetch patient"); return res.json(); }, enabled: !!patientId, }); // Sync fetched patient when available useEffect(() => { if (fetchedPatient) { setPatient(fetchedPatient); } }, [fetchedPatient]); //Fetching staff memebers const [staff, setStaff] = useState(null); const { data: staffMembersRaw = [] as Staff[], isLoading: isLoadingStaff } = useQuery({ queryKey: ["/api/staffs/"], queryFn: async () => { const res = await apiRequest("GET", "/api/staffs/"); return res.json(); }, }); useEffect(() => { if (staffMembersRaw.length > 0 && !staff) { const kaiGao = staffMembersRaw.find( (member) => member.name === "Kai Gao", ); const defaultStaff = kaiGao || staffMembersRaw[0]; if (defaultStaff) setStaff(defaultStaff); } }, [staffMembersRaw, staff]); // fetching npi providers const { data: npiProviders = [] } = useQuery({ queryKey: ["/api/npiProviders/"], queryFn: async () => { const res = await apiRequest("GET", "/api/npiProviders/"); if (!res.ok) throw new Error("Failed to fetch NPI providers"); return res.json(); }, }); useEffect(() => { if (!npiProviders.length) return; // do not override if user already selected if (form.npiProvider?.npiNumber) return; const maryScannell = npiProviders.find( (p) => p.providerName.toLowerCase() === "mary scannell", ); const fallback = maryScannell || npiProviders[0]; if (fallback) { setForm((prev) => ({ ...prev, npiProvider: { npiNumber: fallback.npiNumber, providerName: fallback.providerName, }, })); } }, [npiProviders]); // Service date state const [serviceDateValue, setServiceDateValue] = useState(new Date()); const [serviceDate, setServiceDate] = useState( formatLocalDate(new Date()), ); const [serviceDateOpen, setServiceDateOpen] = useState(false); const [openProcedureDateIndex, setOpenProcedureDateIndex] = useState< number | null >(null); //incase when appointmentId is given - directly from appoinmentpage - claimpage - to here. // then, update the service date as per the appointment date. useEffect(() => { if (!appointmentId) return; if (!Number.isFinite(appointmentId) || appointmentId <= 0) return; let cancelled = false; (async () => { try { const res = await apiRequest( "GET", `/api/appointments/${appointmentId}`, ); if (!res.ok) { let body: any = null; try { body = await res.json(); } catch {} if (!cancelled) { toast({ title: "Failed to load appointment", description: body?.message ?? body?.error ?? `Could not fetch appointment ${appointmentId}.`, variant: "destructive", }); } return; } const appointment = await res.json(); // Capture the column staffId from the appointment if (!cancelled && appointment?.staffId) { setAppointmentStaffId(Number(appointment.staffId)); } // appointment.date is expected to be either "YYYY-MM-DD" or an ISO string. const rawDate = appointment?.date ?? appointment?.day ?? ""; if (!rawDate) return; // Use your parseLocalDate to create a local-midnight Date (avoid TZ shifts) let dateVal: Date; try { dateVal = parseLocalDate(String(rawDate)); } catch (e) { // Fallback - try constructing Date and then normalize const maybe = new Date(rawDate); if (isNaN(maybe.getTime())) { console.error("Could not parse appointment date:", rawDate); return; } dateVal = new Date( maybe.getFullYear(), maybe.getMonth(), maybe.getDate(), ); } if (!cancelled) { setServiceDateValue(dateVal); setServiceDate(formatLocalDate(dateVal)); } } catch (err: any) { if (!cancelled) { console.error("Error fetching appointment:", err); toast({ title: "Error", description: err?.message ?? "Failed to fetch Appointment.", variant: "destructive", }); } } })(); return () => { cancelled = true; }; }, [appointmentId]); // // 2a. Load existing saved claim for this appointment (if any). // Skipped in proceduresOnly mode — that mode always reads from AppointmentProcedure. useEffect(() => { if (!appointmentId) return; if (proceduresOnly) return; let cancelled = false; (async () => { try { const res = await apiRequest( "GET", `/api/claims/by-appointment/${appointmentId}`, ); if (!res.ok) return; // 404 = no existing claim, that's fine const claim = await res.json(); if (cancelled || !claim?.id) return; setExistingClaimId(claim.id); // Restore service date const rawDate = claim.serviceDate ?? ""; const claimDate = rawDate ? String(rawDate).split("T")[0] ?? "" : ""; if (claimDate) { try { setServiceDateValue(parseLocalDate(claimDate)); setServiceDate(claimDate); } catch {} } // Prefer AppointmentProcedure records for service lines — they reflect any // updates the user made in the Select Procedure dialog after the claim was saved. let serviceLines: InputServiceLine[] = []; try { const procRes = await apiRequest( "GET", `/api/appointment-procedures/prefill-from-appointment/${appointmentId}`, ); if (procRes.ok) { const procData = await procRes.json(); if ((procData.procedures || []).length > 0) { serviceLines = (procData.procedures as any[]).map((p) => ({ procedureCode: p.procedureCode ?? "", procedureDate: claimDate || serviceDate, quad: p.quad || "", arch: p.arch || "", toothNumber: p.toothNumber || "", toothSurface: p.toothSurface || "", totalBilled: new Decimal(Number(p.fee ?? 0)), totalAdjusted: new Decimal(0), totalPaid: new Decimal(0), })); } } } catch {} // Fall back to the claim's own service lines if no AppointmentProcedure records exist if (serviceLines.length === 0) { serviceLines = (claim.serviceLines ?? []).map((sl: any) => ({ procedureCode: sl.procedureCode ?? "", procedureDate: sl.procedureDate ? String(sl.procedureDate).split("T")[0] : claimDate, quad: sl.quad ?? "", arch: sl.arch ?? "", toothNumber: sl.toothNumber ?? "", toothSurface: sl.toothSurface ?? "", totalBilled: new Decimal(Number(sl.totalBilled ?? 0)), totalAdjusted: new Decimal(Number(sl.totalAdjusted ?? 0)), totalPaid: new Decimal(Number(sl.totalPaid ?? 0)), })); } setForm((prev) => ({ ...prev, claimId: claim.id, serviceDate: claimDate || prev.serviceDate, serviceLines: serviceLines.length > 0 ? serviceLines : prev.serviceLines, remarks: claim.remarks ?? "", missingTeethStatus: (claim.missingTeethStatus as MissingTeethStatus) ?? "No_missing", missingTeeth: (claim.missingTeeth as Record) ?? {}, insuranceProvider: claim.insuranceProvider ?? "", insuranceSiteKey: claim.insuranceSiteKey || deriveInsuranceSiteKey(claim.insuranceProvider), ...(claim.staffId ? { staffId: claim.staffId } : {}), claimFiles: claim.claimFiles ?? [], })); // Restore staff selection if (claim.staffId && staffMembersRaw.length > 0) { const matchedStaff = staffMembersRaw.find( (s) => Number(s.id) === Number(claim.staffId), ); if (matchedStaff) setStaff(matchedStaff); } // Restore NPI provider selection if ((claim as any).npiProviderId && npiProviders.length > 0) { const matchedNpi = npiProviders.find( (p) => Number(p.id) === Number((claim as any).npiProviderId), ); if (matchedNpi) { setForm((prev) => ({ ...prev, npiProvider: { npiNumber: matchedNpi.npiNumber, providerName: matchedNpi.providerName, }, })); } } setPrefillDone(true); } catch (err) { // no existing claim — silently continue } })(); return () => { cancelled = true; }; }, [appointmentId]); // 2b. Prefill procedures from AppointmentProcedure records. // Skipped when an existing claim was already loaded above. useEffect(() => { if (!appointmentId) return; if (existingClaimId) return; // existing claim takes priority let cancelled = false; (async () => { try { const res = await apiRequest( "GET", `/api/appointment-procedures/prefill-from-appointment/${appointmentId}`, ); if (!res.ok) return; const data = await res.json(); if (cancelled) return; const mappedLines = (data.procedures || []).map((p: any) => ({ procedureCode: p.procedureCode, procedureDate: serviceDate, quad: (p as any).quad || "", arch: (p as any).arch || "", toothNumber: p.toothNumber || "", toothSurface: p.toothSurface || "", totalBilled: new Decimal(p.fee || 0), totalAdjusted: new Decimal(0), totalPaid: new Decimal(0), })); setForm((prev) => ({ ...prev, serviceLines: mappedLines, ...(data.appointmentFiles?.length ? { claimFiles: data.appointmentFiles } : {}), })); // Restore NPI provider from saved procedures if (data.npiProviderId) { const npiId = Number(data.npiProviderId); setSavedProcNpiId(npiId); // Apply immediately if providers are already loaded const matched = npiProviders.find((p) => p.id === npiId); if (matched) { setForm((prev) => ({ ...prev, npiProvider: { npiNumber: matched.npiNumber, providerName: matched.providerName }, })); } } setPrefillDone(true); } catch (err) { console.error("Failed to prefill procedures:", err); } })(); return () => { cancelled = true; }; }, [appointmentId, serviceDate, existingClaimId]); // Restore NPI provider from saved procedures when npiProviders list loads after 2b useEffect(() => { if (!savedProcNpiId || !npiProviders.length) return; if (form.npiProvider?.npiNumber) return; // already set const matched = npiProviders.find((p) => p.id === savedProcNpiId); if (matched) { setForm((prev) => ({ ...prev, npiProvider: { npiNumber: matched.npiNumber, providerName: matched.providerName }, })); } }, [savedProcNpiId, npiProviders]); // Update service date when calendar date changes const onServiceDateChange = (date: Date | undefined) => { if (date) { const formattedDate = formatLocalDate(date); setServiceDateValue(date); setServiceDate(formattedDate); setForm((prev) => ({ ...prev, serviceDate: formattedDate })); } }; // when service date is chenged, it will change the each service lines procedure date in sync as well. useEffect(() => { setForm((prevForm) => { const updatedLines = prevForm.serviceLines.map((line) => ({ ...line, procedureDate: serviceDate, // set all to current serviceDate string })); return { ...prevForm, serviceLines: updatedLines, serviceDate, // keep form.serviceDate in sync as well }; }); }, [serviceDate]); // Determine patient date of birth format - required as date extracted from pdfs has different format. // Replace previous implementation with this type-safe normalizer. // Always returns canonical YYYY-MM-DD or "" if it cannot parse. function normalizeToIsoDateString(dob: string | Date | undefined): string { if (!dob) return ""; // Date object -> canonicalize if (dob instanceof Date) { if (isNaN(dob.getTime())) return ""; return formatLocalDate(dob); } const raw = String(dob).trim(); if (!raw) return ""; // 1) If already date-only ISO (yyyy-mm-dd) if (/^\d{4}-\d{2}-\d{2}$/.test(raw)) { try { parseLocalDate(raw); // validate return raw; } catch { return ""; } } // 2) Try parseLocalDate for ISO-like inputs (will throw if not suitable) try { const parsed = parseLocalDate(raw); return formatLocalDate(parsed); } catch { // continue to other fallbacks } // 3) MM/DD/YYYY or M/D/YYYY -> convert to ISO const m1 = raw.match(/^(\d{1,2})\/(\d{1,2})\/(\d{4})$/); if (m1) { const mm = m1[1] ?? ""; const dd = m1[2] ?? ""; const yyyy = m1[3] ?? ""; if (mm && dd && yyyy) { const iso = `${yyyy}-${mm.padStart(2, "0")}-${dd.padStart(2, "0")}`; try { parseLocalDate(iso); return iso; } catch { return ""; } } } // 4) OCR-ish short form: MMDDYY (exactly 6 digits) -> guess century const m2 = raw.match(/^(\d{6})$/); if (m2) { const s = m2[1]; if (s && s.length === 6) { const mm = s.slice(0, 2); const dd = s.slice(2, 4); const yy = s.slice(4, 6); const year = Number(yy) < 50 ? 2000 + Number(yy) : 1900 + Number(yy); const iso = `${year}-${mm.padStart(2, "0")}-${dd.padStart(2, "0")}`; try { parseLocalDate(iso); return iso; } catch { return ""; } } } // 5) Last resort: naive Date parse -> normalize to local calendar fields try { const maybe = new Date(raw); if (!isNaN(maybe.getTime())) { return formatLocalDate(maybe); } } catch { /* ignore */ } return ""; } // assumes input is either "" or "YYYY-MM-DD" const toMMDDYYYY = (iso: string): string => { if (!iso) return ""; const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(iso); return m ? `${m[2]}-${m[3]}-${m[1]}` : ""; }; // MAIN FORM INITIAL STATE const [form, setForm] = useState({ patientId: patientId || 0, appointmentId: 0, userId: Number(user?.id), staffId: appointmentStaffId ?? Number(staff?.id), patientName: `${patient?.firstName} ${patient?.lastName}`.trim(), memberId: patient?.insuranceId ?? "", dateOfBirth: normalizeToIsoDateString(patient?.dateOfBirth), remarks: "", missingTeethStatus: "No_missing", missingTeeth: {} as MissingMapStrict, serviceDate: serviceDate, insuranceProvider: "", insuranceSiteKey: "", status: "PENDING", serviceLines: Array.from({ length: 10 }, () => ({ procedureCode: "", procedureDate: serviceDate, quad: "", arch: "", toothNumber: "", toothSurface: "", totalBilled: new Decimal(0), totalAdjusted: new Decimal(0), totalPaid: new Decimal(0), })), uploadedFiles: [], }); // Map patient.insuranceProvider (free-text from eligibility) → insuranceSiteKey const deriveInsuranceSiteKey = (provider: string | null | undefined): string => { const p = (provider || "").toLowerCase().trim(); if (!p) return ""; if (p.includes("masshealth") || p === "mh" || p === "mass health") return "MH"; if (p.includes("commonwealth care alliance") || p === "cca") return "CCA"; if (p.includes("ddma") || p.includes("delta dental ma")) return "DDMA"; if (p.includes("delta ins") || p === "deltains") return "DeltaIns"; if (p.includes("tufts") || p.includes("dentaquest") || p === "tuftssco") return "TuftsSCO"; if ((p.includes("united") && p.includes("sco")) || p === "unitedsco") return "UnitedSCO"; if (p.includes("cmsp")) return "CMSP"; if (p.includes("bcbs") || p.includes("blue cross")) return "BCBS"; if (p.includes("united aapr") || p === "unitedaapr") return "UnitedAAPR"; if (p.includes("aetna")) return "Aetna"; if (p.includes("altus")) return "Altus"; if (p.includes("metlife")) return "MetlifeDental"; if (p.includes("cigna")) return "Cigna"; if (p.includes("delta wa") || p === "deltawa") return "DeltaWA"; if (p.includes("delta il") || p === "deltail") return "DeltaIL"; return ""; }; // Sync patient data to form when patient updates useEffect(() => { if (patient) { const fullName = `${patient.firstName || ""} ${patient.lastName || ""}`.trim(); const siteKey = deriveInsuranceSiteKey(patient.insuranceProvider); setForm((prev) => ({ ...prev, patientId: Number(patient.id), patientName: fullName, dateOfBirth: normalizeToIsoDateString(patient.dateOfBirth), memberId: patient.insuranceId || "", ...(siteKey ? { insuranceSiteKey: siteKey } : {}), })); } }, [patient]); // Handle patient field changes (to make inputs controlled and editable) const updatePatientField = (field: keyof Patient, value: any) => { setPatient((prev) => (prev ? { ...prev, [field]: value } : null)); }; const updateServiceLine = ( index: number, field: keyof InputServiceLine, value: any, ) => { const updatedLines = [...form.serviceLines]; if (updatedLines[index]) { if (field === "totalBilled") { const num = typeof value === "string" ? parseFloat(value) : value; const rounded = Math.round((isNaN(num) ? 0 : num) * 100) / 100; updatedLines[index][field] = new Decimal(rounded); } else { updatedLines[index][field] = value; } } setForm({ ...form, serviceLines: updatedLines }); }; const updateProcedureDate = (index: number, date: Date | undefined) => { if (!date) return; const formattedDate = formatLocalDate(date); const updatedLines = [...form.serviceLines]; if (updatedLines[index]) { updatedLines[index].procedureDate = formattedDate; } setForm({ ...form, serviceLines: updatedLines }); }; // normalize for display while typing: uppercase and allow letters, commas and spaces function normalizeToothSurface(raw?: string): string { if (!raw) return ""; // Uppercase and remove characters that are not A-Z, comma or space return raw.toUpperCase().replace(/[^A-Z,\s]/g, ""); } // Missing teeth section const setMissingTeethStatus = (status: MissingTeethStatus) => { setForm((prev) => { if (prev.missingTeethStatus === status) return prev; // no-op return { ...prev, missingTeethStatus: status, missingTeeth: status === "Yes_missing" ? prev.missingTeeth : {}, }; }); }; const clearAllToothSelections = () => setForm((prev) => ({ ...prev, missingTeeth: {} as MissingMapStrict })); // for serviceLine rows, to auto scroll when it got updated by combo buttons and all. const rowRefs = useRef<(HTMLDivElement | null)[]>([]); const scrollToLine = (index: number) => { const el = rowRefs.current[index]; if (el) { el.scrollIntoView({ behavior: "smooth", block: "start" }); } }; // Map Price function — uses the fee schedule for the selected insurance type const onMapPrice = () => { setForm((prev) => mapPricesForForm({ form: prev, patientDOB: patient?.dateOfBirth ?? "", insuranceSiteKey: prev.insuranceSiteKey, }), ); }; // FILE UPLOAD ZONE const uploadZoneRef = useRef(null); const [isUploading, setIsUploading] = useState(false); const [priceMismatches, setPriceMismatches] = useState([]); const pendingClaimAction = useRef<(() => void) | null>(null); // NO validation here — the upload zone handles validation, toasts, max files, sizes, etc. const handleFilesChange = useCallback((files: File[]) => { setForm((prev) => ({ ...prev, uploadedFiles: files })); }, []); // 1st Button workflow - Mass Health Button Handler const handleMHSubmit = async ( formToUse?: ClaimFormData & { uploadedFiles?: File[] }, ) => { // Use the passed form, or fallback to current state const f = formToUse ?? form; // 0. Validate required fields const missingFields: string[] = []; if (!f.memberId?.trim()) missingFields.push("Member ID"); if (!f.dateOfBirth?.trim()) missingFields.push("Date of Birth"); if (!patient?.firstName?.trim()) missingFields.push("First Name"); if (missingFields.length > 0) { toast({ title: "Missing Required Fields", description: `Please fill out the following field(s): ${missingFields.join(", ")}`, variant: "destructive", }); return; } // require at least one procedure code before proceeding const filteredServiceLines = (f.serviceLines || []).filter( (line) => (line.procedureCode ?? "").trim() !== "", ); if (filteredServiceLines.length === 0) { toast({ title: "No procedure codes", description: "Please add at least one procedure code before submitting the claim.", variant: "destructive", }); return; } if (!f.npiProvider?.npiNumber) { toast({ title: "NPI Provider Required", description: "Please select a NPI Provider.", variant: "destructive", }); return; } // 1. Create or update appointment let appointmentIdToUse = appointmentId; if (appointmentIdToUse == null) { const appointmentData = { patientId: patientId, date: serviceDate, staffId: appointmentStaffId ?? staff?.id, }; const created = await onHandleAppointmentSubmit(appointmentData); if (typeof created === "number" && created > 0) { appointmentIdToUse = created; } else if (created && typeof (created as any).id === "number") { appointmentIdToUse = (created as any).id; } } // 3. Create Claim(if not) // Filter out empty service lines (empty procedureCode) const { uploadedFiles, insuranceSiteKey, npiProvider, ...formToCreateClaim } = f; // build claimFiles metadata from uploadedFiles (only filename + mimeType) const claimFilesMeta: ClaimFileMeta[] = (uploadedFiles || []).map((f) => ({ filename: f.name, mimeType: f.type, })); const selectedNpiProviderId = npiProvider?.npiNumber ? npiProviders.find((p) => p.npiNumber === npiProvider.npiNumber)?.id ?? null : null; const createdClaim = await onSubmit({ ...formToCreateClaim, serviceLines: filteredServiceLines, staffId: appointmentStaffId ?? Number(staff?.id), patientId: patientId, insuranceProvider: "MassHealth", appointmentId: appointmentIdToUse!, claimFiles: claimFilesMeta, ...(selectedNpiProviderId ? { npiProviderId: selectedNpiProviderId } : {}), }); // 4. sending form data to selenium service onHandleForMHSeleniumClaim({ ...f, dateOfBirth: toMMDDYYYY(f.dateOfBirth), serviceLines: filteredServiceLines, staffId: appointmentStaffId ?? Number(staff?.id), npiProvider: f.npiProvider, patientId: patientId, insuranceProvider: "Mass Health", appointmentId: appointmentIdToUse!, insuranceSiteKey: "MH", claimId: createdClaim.id, }); // 5. Close form onClose(); }; // 2st Button workflow - Mass Health Pre Auth Button Handler const handleMHPreAuth = async ( formToUse?: ClaimFormData & { uploadedFiles?: File[] }, ) => { // Use the passed form, or fallback to current state const f = formToUse ?? form; // 0. Validate required fields const missingFields: string[] = []; if (!f.memberId?.trim()) missingFields.push("Member ID"); if (!f.dateOfBirth?.trim()) missingFields.push("Date of Birth"); if (!patient?.firstName?.trim()) missingFields.push("First Name"); if (missingFields.length > 0) { toast({ title: "Missing Required Fields", description: `Please fill out the following field(s): ${missingFields.join(", ")}`, variant: "destructive", }); return; } // require at least one procedure code before proceeding const filteredServiceLines = (f.serviceLines || []).filter( (line) => (line.procedureCode ?? "").trim() !== "", ); if (filteredServiceLines.length === 0) { toast({ title: "No procedure codes", description: "Please add at least one procedure code before submitting the claim preAuth.", variant: "destructive", }); return; } if (!f.npiProvider?.npiNumber) { toast({ title: "NPI Provider Required", description: "Please select a NPI Provider.", variant: "destructive", }); return; } // sending form data to selenium service onHandleForMHSeleniumClaimPreAuth({ ...f, dateOfBirth: toMMDDYYYY(f.dateOfBirth), serviceLines: filteredServiceLines, staffId: appointmentStaffId ?? Number(staff?.id), npiProvider: f.npiProvider, patientId: patientId, insuranceProvider: "Mass Health", insuranceSiteKey: "MH", }); // 5. Close form onClose(); }; // 3rd Button workflow — CCA Claim: saves to DB then submits via Selenium const handleCCAClaim = async () => { const missingFields: string[] = []; if (!form.memberId?.trim()) missingFields.push("Member ID"); if (!form.dateOfBirth?.trim()) missingFields.push("Date of Birth"); if (!patient?.firstName?.trim()) missingFields.push("First Name"); if (missingFields.length > 0) { toast({ title: "Missing Required Fields", description: `Please fill out the following field(s): ${missingFields.join(", ")}`, variant: "destructive", }); return; } const filteredServiceLines = (form.serviceLines || []).filter( (line) => (line.procedureCode ?? "").trim() !== "", ); if (filteredServiceLines.length === 0) { toast({ title: "No procedure codes", description: "Please add at least one procedure code before submitting the claim.", variant: "destructive", }); return; } // Create appointment if needed let appointmentIdToUse = appointmentId; if (appointmentIdToUse == null) { const created = await onHandleAppointmentSubmit({ patientId, date: serviceDate, staffId: appointmentStaffId ?? staff?.id, }); if (typeof created === "number" && created > 0) { appointmentIdToUse = created; } else if (created && typeof (created as any).id === "number") { appointmentIdToUse = (created as any).id; } } const { uploadedFiles, insuranceSiteKey, npiProvider, ...formToCreateClaim } = form; const claimFilesMeta: ClaimFileMeta[] = (uploadedFiles || []).map((f) => ({ filename: f.name, mimeType: f.type, })); const selectedNpiProviderId = npiProvider?.npiNumber ? npiProviders.find((p) => p.npiNumber === npiProvider.npiNumber)?.id ?? null : null; // Save claim to DB const createdClaim = await onSubmit({ ...formToCreateClaim, serviceLines: filteredServiceLines, staffId: appointmentStaffId ?? Number(staff?.id), patientId, insuranceProvider: "CCA", appointmentId: appointmentIdToUse!, claimFiles: claimFilesMeta, ...(selectedNpiProviderId ? { npiProviderId: selectedNpiProviderId } : {}), }); // Send to CCA Selenium — send raw YYYY-MM-DD so Python _format_dob converts correctly onHandleForCCASeleniumClaim({ ...form, serviceLines: filteredServiceLines, staffId: appointmentStaffId ?? Number(staff?.id), patientId, insuranceProvider: "CCA", appointmentId: appointmentIdToUse!, insuranceSiteKey: "CCA", claimId: createdClaim.id, }); onClose(); }; // Delta MA Claim: saves to DB then submits via Selenium const handleDDMAClaim = async () => { const missingFields: string[] = []; if (!form.memberId?.trim()) missingFields.push("Member ID"); if (!form.dateOfBirth?.trim()) missingFields.push("Date of Birth"); if (!patient?.firstName?.trim()) missingFields.push("First Name"); if (missingFields.length > 0) { toast({ title: "Missing Required Fields", description: `Please fill out the following field(s): ${missingFields.join(", ")}`, variant: "destructive", }); return; } const filteredServiceLines = (form.serviceLines || []).filter( (line) => (line.procedureCode ?? "").trim() !== "", ); if (filteredServiceLines.length === 0) { toast({ title: "No procedure codes", description: "Please add at least one procedure code before submitting the claim.", variant: "destructive", }); return; } let appointmentIdToUse = appointmentId; if (appointmentIdToUse == null) { const created = await onHandleAppointmentSubmit({ patientId, date: serviceDate, staffId: appointmentStaffId ?? staff?.id, }); if (typeof created === "number" && created > 0) { appointmentIdToUse = created; } else if (created && typeof (created as any).id === "number") { appointmentIdToUse = (created as any).id; } } const { uploadedFiles, insuranceSiteKey, npiProvider, ...formToCreateClaim } = form; // Upload files to server so we get local filePaths for Selenium const claimFilesMeta: ClaimFileMeta[] = uploadedFiles?.length ? await uploadAttachmentsToLocalFolder(uploadedFiles) : []; const selectedNpiProviderId = npiProvider?.npiNumber ? npiProviders.find((p) => p.npiNumber === npiProvider.npiNumber)?.id ?? null : null; const createdClaim = await onSubmit({ ...formToCreateClaim, serviceLines: filteredServiceLines, staffId: appointmentStaffId ?? Number(staff?.id), patientId, insuranceProvider: "Delta Dental MA", appointmentId: appointmentIdToUse!, claimFiles: claimFilesMeta, ...(selectedNpiProviderId ? { npiProviderId: selectedNpiProviderId } : {}), }); onHandleForDDMASeleniumClaim({ ...form, serviceLines: filteredServiceLines, staffId: appointmentStaffId ?? Number(staff?.id), patientId, insuranceProvider: "Delta Dental MA", appointmentId: appointmentIdToUse!, insuranceSiteKey: "DDMA", claimId: createdClaim.id, claimFiles: claimFilesMeta, }); onClose(); }; // United/DentalHub Claim: saves to DB then submits via Selenium const handleUnitedDHClaim = async () => { const missingFields: string[] = []; if (!form.memberId?.trim()) missingFields.push("Member ID"); if (!form.dateOfBirth?.trim()) missingFields.push("Date of Birth"); if (!patient?.firstName?.trim()) missingFields.push("First Name"); if (missingFields.length > 0) { toast({ title: "Missing Required Fields", description: `Please fill out the following field(s): ${missingFields.join(", ")}`, variant: "destructive", }); return; } const filteredServiceLines = (form.serviceLines || []).filter( (line) => (line.procedureCode ?? "").trim() !== "", ); if (filteredServiceLines.length === 0) { toast({ title: "No procedure codes", description: "Please add at least one procedure code before submitting the claim.", variant: "destructive", }); return; } let appointmentIdToUse = appointmentId; if (appointmentIdToUse == null) { const created = await onHandleAppointmentSubmit({ patientId, date: serviceDate, staffId: appointmentStaffId ?? staff?.id, }); if (typeof created === "number" && created > 0) { appointmentIdToUse = created; } else if (created && typeof (created as any).id === "number") { appointmentIdToUse = (created as any).id; } } const { uploadedFiles, insuranceSiteKey, npiProvider, ...formToCreateClaim } = form; const claimFilesMeta: ClaimFileMeta[] = uploadedFiles?.length ? await uploadAttachmentsToLocalFolder(uploadedFiles) : []; const selectedNpiProviderId = npiProvider?.npiNumber ? npiProviders.find((p) => p.npiNumber === npiProvider.npiNumber)?.id ?? null : null; const createdClaim = await onSubmit({ ...formToCreateClaim, serviceLines: filteredServiceLines, staffId: appointmentStaffId ?? Number(staff?.id), patientId, insuranceProvider: "United/DentalHub", appointmentId: appointmentIdToUse!, claimFiles: claimFilesMeta, ...(selectedNpiProviderId ? { npiProviderId: selectedNpiProviderId } : {}), }); onHandleForUnitedDHSeleniumClaim({ ...form, serviceLines: filteredServiceLines, staffId: appointmentStaffId ?? Number(staff?.id), patientId, insuranceProvider: "United/DentalHub", appointmentId: appointmentIdToUse!, insuranceSiteKey: "UNITED_SCO", claimId: createdClaim.id, claimFiles: claimFilesMeta, }); onClose(); }; // Tufts SCO Claim: saves to DB then submits via Selenium const handleTuftsSCOClaim = async () => { const missingFields: string[] = []; if (!form.memberId?.trim()) missingFields.push("Member ID"); if (!form.dateOfBirth?.trim()) missingFields.push("Date of Birth"); if (!patient?.firstName?.trim()) missingFields.push("First Name"); if (missingFields.length > 0) { toast({ title: "Missing Required Fields", description: `Please fill out the following field(s): ${missingFields.join(", ")}`, variant: "destructive", }); return; } const filteredServiceLines = (form.serviceLines || []).filter( (line) => (line.procedureCode ?? "").trim() !== "", ); if (filteredServiceLines.length === 0) { toast({ title: "No procedure codes", description: "Please add at least one procedure code before submitting the claim.", variant: "destructive", }); return; } let appointmentIdToUse = appointmentId; if (appointmentIdToUse == null) { const created = await onHandleAppointmentSubmit({ patientId, date: serviceDate, staffId: appointmentStaffId ?? staff?.id, }); if (typeof created === "number" && created > 0) { appointmentIdToUse = created; } else if (created && typeof (created as any).id === "number") { appointmentIdToUse = (created as any).id; } } const { uploadedFiles, insuranceSiteKey, npiProvider, ...formToCreateClaim } = form; const claimFilesMeta: ClaimFileMeta[] = uploadedFiles?.length ? await uploadAttachmentsToLocalFolder(uploadedFiles) : []; const selectedNpiProviderId = npiProvider?.npiNumber ? npiProviders.find((p) => p.npiNumber === npiProvider.npiNumber)?.id ?? null : null; let createdClaim: any; try { createdClaim = await onSubmit({ ...formToCreateClaim, serviceLines: filteredServiceLines, staffId: appointmentStaffId ?? Number(staff?.id), patientId, insuranceProvider: "Tufts SCO", appointmentId: appointmentIdToUse!, claimFiles: claimFilesMeta, ...(selectedNpiProviderId ? { npiProviderId: selectedNpiProviderId } : {}), }); } catch (err: any) { toast({ title: "Failed to save claim", description: err?.message || "An error occurred saving the claim.", variant: "destructive", }); return; } onHandleForTuftsSCOSeleniumClaim({ ...form, serviceLines: filteredServiceLines, staffId: appointmentStaffId ?? Number(staff?.id), patientId, insuranceProvider: "Tufts SCO", appointmentId: appointmentIdToUse!, insuranceSiteKey: "TuftsSCO", claimId: createdClaim.id, claimFiles: claimFilesMeta, }); onClose(); }; const handleClaimAll = () => { const siteKey = (form.insuranceSiteKey || deriveInsuranceSiteKey(form.insuranceProvider || "")).toUpperCase(); if (siteKey === "MH" || siteKey === "MASSHEALTH") { runWithPriceCheck(() => handleMHSubmit()); } else if (siteKey === "CCA") { runWithPriceCheck(handleCCAClaim); } else if (siteKey === "DDMA") { runWithPriceCheck(handleDDMAClaim); } else if (siteKey === "TUFTSSCO" || siteKey === "TUFTS_SCO") { runWithPriceCheck(handleTuftsSCOClaim); } else if (siteKey === "UNITEDSCO" || siteKey === "UNITEDDH" || siteKey === "UNITED_SCO") { runWithPriceCheck(handleUnitedDHClaim); } else { toast({ title: "No automated claim for this insurance", description: `Insurance type "${form.insuranceSiteKey || "unknown"}" does not have an automated claim. Please use one of the buttons below.`, variant: "destructive", }); } }; const handleCCAPreAuth = async () => { const missingFields: string[] = []; if (!form.memberId?.trim()) missingFields.push("Member ID"); if (!form.dateOfBirth?.trim()) missingFields.push("Date of Birth"); if (!patient?.firstName?.trim()) missingFields.push("First Name"); if (missingFields.length > 0) { toast({ title: "Missing Required Fields", description: `Please fill out the following field(s): ${missingFields.join(", ")}`, variant: "destructive", }); return; } const filteredServiceLines = (form.serviceLines || []).filter( (line) => (line.procedureCode ?? "").trim() !== "", ); if (filteredServiceLines.length === 0) { toast({ title: "No procedure codes", description: "Please add at least one procedure code before submitting the pre-authorization.", variant: "destructive", }); return; } onHandleForCCASeleniumPreAuth({ ...form, serviceLines: filteredServiceLines, staffId: appointmentStaffId ?? Number(staff?.id), patientId, insuranceProvider: "CCA", insuranceSiteKey: "CCA", }); onClose(); }; /** Check prices against the fee schedule. If mismatches exist, show dialog and * store the action to run after the user responds. Otherwise run immediately. */ const runWithPriceCheck = (action: () => void) => { const siteKey = form.insuranceSiteKey || deriveInsuranceSiteKey(form.insuranceProvider || ""); const mismatches = findPriceMismatches( (form.serviceLines || []).filter(l => (l.procedureCode || "").trim()), siteKey, patient?.dateOfBirth || "", form.serviceDate || serviceDate, ); if (mismatches.length === 0) { action(); } else { pendingClaimAction.current = action; setPriceMismatches(mismatches); } }; const savePricesToSchedule = async (mismatches: PriceMismatch[]) => { const siteKey = form.insuranceSiteKey || deriveInsuranceSiteKey(form.insuranceProvider || ""); await Promise.all( mismatches.map(m => apiRequest("POST", "/api/fee-schedule/update-price", { siteKey, procedureCode: m.procedureCode, price: m.enteredPrice, }) ) ); }; const uploadAttachmentsToLocalFolder = async (files: File[]): Promise => { if (!files.length) return []; const patientName = patient?.firstName && patient?.lastName ? `${patient.firstName} ${patient.lastName}` : patient?.firstName ?? `patient-${patientId}`; const formData = new FormData(); formData.append("patientName", patientName); files.forEach((f) => formData.append("files", f)); const res = await apiRequest("POST", "/api/claims/upload-attachments", formData); const data = await res.json(); return (data.data ?? []) as ClaimFileMeta[]; }; const handleSave = async () => { const filteredServiceLines = form.serviceLines.filter( (line) => (line.procedureCode ?? "").trim() !== "", ); if (filteredServiceLines.length === 0) { toast({ title: "No procedure codes", description: "Please add at least one procedure code before saving.", variant: "destructive", }); return; } const missingFields: string[] = []; if (!form.memberId?.trim()) missingFields.push("Member ID"); if (!form.dateOfBirth?.trim()) missingFields.push("Date of Birth"); if (missingFields.length > 0) { toast({ title: "Missing Required Fields", description: `Please fill out: ${missingFields.join(", ")}`, variant: "destructive", }); return; } let appointmentIdToUse = appointmentId; if (appointmentIdToUse == null) { const appointmentData = { patientId: patientId, date: serviceDate, staffId: appointmentStaffId ?? staff?.id, }; const created = await onHandleAppointmentSubmit(appointmentData); if (typeof created === "number" && created > 0) { appointmentIdToUse = created; } else if (created && typeof (created as any).id === "number") { appointmentIdToUse = (created as any).id; } } const { uploadedFiles, insuranceSiteKey, npiProvider, ...formToSave } = form; const claimFilesMeta: ClaimFileMeta[] = uploadedFiles?.length ? await uploadAttachmentsToLocalFolder(uploadedFiles) : []; // Find the npiProviderId matching the currently selected NPI provider const selectedNpiProviderId = npiProvider?.npiNumber ? npiProviders.find((p) => p.npiNumber === npiProvider.npiNumber)?.id ?? null : null; try { await onSubmit({ ...formToSave, serviceLines: filteredServiceLines, staffId: appointmentStaffId ?? Number(staff?.id), patientId: patientId, insuranceProvider: "MassHealth", appointmentId: appointmentIdToUse!, claimFiles: claimFilesMeta, ...(selectedNpiProviderId ? { npiProviderId: selectedNpiProviderId } : {}), isDraft: true, }); toast({ title: "Saved", description: "Claim saved successfully." }); } catch (err: any) { toast({ title: "Save failed", description: err?.message ?? "Failed to save claim.", variant: "destructive", }); } }; // Saves CDT codes + NPI provider to AppointmentProcedure (proceduresOnly mode) const handleProceduresSave = async () => { if (!appointmentId || !patientId) { toast({ title: "Missing appointment", description: "Cannot save without an appointment.", variant: "destructive" }); return; } const filteredServiceLines = form.serviceLines.filter( (line) => (line.procedureCode ?? "").trim() !== "", ); if (filteredServiceLines.length === 0) { toast({ title: "No procedure codes", description: "Please add at least one procedure code.", variant: "destructive" }); return; } const selectedNpiProviderId = form.npiProvider?.npiNumber ? (npiProviders.find((p) => p.npiNumber === form.npiProvider!.npiNumber)?.id ?? null) : null; try { const attachments = form.uploadedFiles?.length ? await uploadAttachmentsToLocalFolder(form.uploadedFiles) : []; const res = await apiRequest("POST", "/api/appointment-procedures/save-for-appointment", { appointmentId, patientId, npiProviderId: selectedNpiProviderId, procedures: filteredServiceLines.map((l) => ({ procedureCode: l.procedureCode, fee: Number(l.totalBilled) || null, toothNumber: l.toothNumber || null, toothSurface: l.toothSurface || null, })), attachments, }); const data = await res.json(); if (!data.success) throw new Error("Failed to save procedures"); const attachMsg = attachments.length ? ` and ${attachments.length} attachment(s)` : ""; toast({ title: "Procedures saved", description: `${data.count} procedure(s)${attachMsg} saved.` }); onClose(); } catch (err: any) { toast({ title: "Save failed", description: err?.message ?? "Failed to save procedures.", variant: "destructive" }); } }; // Same as handleProceduresSave but also resets any existing submitted claim so // batch-column will treat this appointment as needing a new submission. const handleProceduresUpdate = async () => { if (!appointmentId) return; try { await apiRequest("POST", "/api/claims/reset-for-resubmit", { appointmentId }); } catch { // Non-fatal: if reset fails we still save procedures } await handleProceduresSave(); }; // Saves claim to DB with all info (member ID, DOB, service date, provider, CDT codes, NPI). // No Selenium action — works like a real MassHealth claim for payment/report tracking. const handleClaimSaved = async () => { const missingFields: string[] = []; if (!form.memberId?.trim()) missingFields.push("Member ID"); if (!form.dateOfBirth?.trim()) missingFields.push("Date of Birth"); if (!patient?.firstName?.trim()) missingFields.push("First Name"); if (missingFields.length > 0) { toast({ title: "Missing Required Fields", description: `Please fill out: ${missingFields.join(", ")}`, variant: "destructive", }); return; } const filteredServiceLines = form.serviceLines.filter( (line) => (line.procedureCode ?? "").trim() !== "", ); if (filteredServiceLines.length === 0) { toast({ title: "No procedure codes", description: "Please add at least one procedure code before saving.", variant: "destructive", }); return; } let appointmentIdToUse = appointmentId; if (appointmentIdToUse == null) { const created = await onHandleAppointmentSubmit({ patientId: patientId, date: serviceDate, staffId: appointmentStaffId ?? staff?.id, }); if (typeof created === "number" && created > 0) { appointmentIdToUse = created; } else if (created && typeof (created as any).id === "number") { appointmentIdToUse = (created as any).id; } } const { uploadedFiles, insuranceSiteKey, npiProvider, ...formToCreateClaim } = form; const claimFilesMeta: ClaimFileMeta[] = uploadedFiles?.length ? await uploadAttachmentsToLocalFolder(uploadedFiles) : []; const selectedNpiProviderId = npiProvider?.npiNumber ? npiProviders.find((p) => p.npiNumber === npiProvider.npiNumber)?.id ?? null : null; try { await onSubmit({ ...formToCreateClaim, serviceLines: filteredServiceLines, staffId: appointmentStaffId ?? Number(staff?.id), patientId: patientId, insuranceProvider: patient?.insuranceProvider || "MassHealth", appointmentId: appointmentIdToUse!, claimFiles: claimFilesMeta, ...(selectedNpiProviderId ? { npiProviderId: selectedNpiProviderId } : {}), }); toast({ title: "Claim Saved", description: "Claim saved to database successfully." }); onClose(); } catch (err: any) { toast({ title: "Save failed", description: err?.message ?? "Failed to save claim.", variant: "destructive", }); } }; // Marks the claim for this appointment as VOID so batch-column will always skip it. const handleProceduresVoid = async () => { if (!appointmentId) return; try { await apiRequest("POST", "/api/claims/void-for-appointment", { appointmentId }); toast({ title: "Claim voided", description: "This appointment will be skipped when claiming for the column." }); onClose(); } catch (err: any) { toast({ title: "Void failed", description: err?.message ?? "Failed to void claim.", variant: "destructive" }); } }; // for direct combo button. const applyComboAndThenMH = async ( comboId: keyof typeof PROCEDURE_COMBOS, ) => { const nextForm = applyComboToForm( form, comboId, patient?.dateOfBirth ?? "", { replaceAll: false, lineDate: form.serviceDate }, form.insuranceSiteKey, ); setForm(nextForm); setTimeout(() => scrollToLine(0), 0); await handleMHSubmit(nextForm); }; const isFormReady = useMemo(() => { return ( !!patient && !!form.memberId?.trim() && !!form.dateOfBirth?.trim() && !!form.patientName?.trim() && Array.isArray(form.serviceLines) && form.serviceLines.some( (l) => l.procedureCode && l.procedureCode.trim() !== "", ) ); }, [ patient, form.memberId, form.dateOfBirth, form.patientName, form.serviceLines, ]); // when autoSubmit mode is given, it will then submit the claims. useEffect(() => { if (!autoSubmit) return; if (!prefillDone) return; if (!isFormReady) return; if (autoSubmittedRef.current) return; autoSubmittedRef.current = true; handleMHSubmit(); }, [autoSubmit, prefillDone, isFormReady]); // overlay click handler (close when clicking backdrop) const onOverlayMouseDown = (e: React.MouseEvent) => { // only close if clicked the backdrop itself (not inner modal) if (e.target === e.currentTarget) { onClose(); } }; useEffect(() => { return () => { // reset when ClaimForm unmounts (modal closes) autoSubmittedRef.current = false; setPrefillDone(false); }; }, []); return (
Insurance Forms
Insurance Claim PreAuth
{/* ── Insurance Claim Tab ── */}
{/* Patient Information */}
{ setForm({ ...form, memberId: e.target.value }); updatePatientField("insuranceId", e.target.value); }} />
{ const formatted = date ? formatLocalDate(date) : ""; setForm((prev) => ({ ...prev, dateOfBirth: formatted })); updatePatientField("dateOfBirth", formatted); }} disableFuture />
{ updatePatientField("firstName", e.target.value); setForm((prev) => ({ ...prev, patientName: `${e.target.value} ${patient?.lastName || ""}`.trim(), })); }} disabled={isLoading} />
{ updatePatientField("lastName", e.target.value); setForm((prev) => ({ ...prev, patientName: `${patient?.firstName || ""} ${e.target.value}`.trim(), })); }} disabled={isLoading} />
{/* Service Lines */}

Service Lines

{ onServiceDateChange(date); }} onClose={() => setServiceDateOpen(false)} />
{ if (directSubmitEnabled) { applyComboAndThenMH(comboKey as any); } else { setForm((prev) => { const next = applyComboToForm( prev, comboKey as any, patient?.dateOfBirth ?? "", { replaceAll: false, lineDate: prev.serviceDate }, prev.insuranceSiteKey, ); setTimeout(() => scrollToLine(0), 0); return next; }); } }} />
{/* Header */}
Procedure Code
Info Procedure Date Tooth Number Tooth Surface Quad Arch Qty Auth No. Billed Amount
{/* Dynamic Rows */} {form.serviceLines.map((line, i) => { const raw = line.procedureCode || ""; const code = raw.trim(); const desc = code ? getDescriptionForCode(code) || "No description available" : "Enter a procedure code"; return (
{ rowRefs.current[i] = el; if (!el) rowRefs.current.splice(i, 1); }} className="scroll-mt-28 grid grid-cols-[1.5fr,0.5fr,1fr,1fr,1fr,1fr,1fr,0.5fr,1fr,1fr] gap-1 mb-2 items-center" >
updateServiceLine( i, "procedureCode", e.target.value.toUpperCase(), ) } />
{desc}
setOpenProcedureDateIndex(open ? i : null) } > updateProcedureDate(i, date)} onClose={() => setOpenProcedureDateIndex(null)} /> updateServiceLine(i, "toothNumber", e.target.value) } /> { const typed = normalizeToothSurface(e.target.value); updateServiceLine(i, "toothSurface", typed); }} /> updateServiceLine(i, "quantity", e.target.value) } /> updateServiceLine(i, "quad", e.target.value) } /> { updateServiceLine(i, "totalBilled", e.target.value); }} onBlur={(e) => { const val = parseFloat(e.target.value); const rounded = Math.round(val * 100) / 100; updateServiceLine( i, "totalBilled", isNaN(rounded) ? 0 : rounded, ); }} />
); })} { setForm((prev) => { const next = applyComboToForm( prev, comboKey as any, patient?.dateOfBirth ?? "", { replaceAll: false, lineDate: prev.serviceDate }, ); setTimeout(() => scrollToLine(0), 0); return next; }); }} />
{/* File Upload Section */}

File Upload

You can upload up to 10 files. Allowed types: PDF, JPG, PNG, WEBP.

{form.uploadedFiles.length > 0 && (
    {form.uploadedFiles.map((file, index) => (
  • {file.name}
  • ))}
)}
{/* Missing Teeth */}

Missing Teeth

{form.missingTeethStatus === "Yes_missing" && ( )}
{form.missingTeethStatus === "Yes_missing" && ( setForm((prev) => ({ ...prev, missingTeeth: next })) } /> )}
{/* Remarks */}

Remarks

setForm((prev) => ({ ...prev, remarks: next })) } />
{/* Insurance Carriers */}

{proceduresOnly ? "Save Procedures" : "Insurance Carriers"}

{proceduresOnly ? (
) : ( <>
)}
{/* ── PreAuth Tab ── */}
{/* Patient Information */}
{ setForm({ ...form, memberId: e.target.value }); updatePatientField("insuranceId", e.target.value); }} />
{ const formatted = date ? formatLocalDate(date) : ""; setForm((prev) => ({ ...prev, dateOfBirth: formatted })); updatePatientField("dateOfBirth", formatted); }} disableFuture />
{ updatePatientField("firstName", e.target.value); setForm((prev) => ({ ...prev, patientName: `${e.target.value} ${patient?.lastName || ""}`.trim(), })); }} disabled={isLoading} />
{ updatePatientField("lastName", e.target.value); setForm((prev) => ({ ...prev, patientName: `${patient?.firstName || ""} ${e.target.value}`.trim(), })); }} disabled={isLoading} />
{/* Service Lines */}

Service Lines

{ onServiceDateChange(date); }} onClose={() => setServiceDateOpen(false)} />
{/* Header */}
Procedure Code
Info Procedure Date Tooth Number Tooth Surface Quad Arch Qty Auth No. Billed Amount
{/* Dynamic Rows */} {form.serviceLines.map((line, i) => { const raw = line.procedureCode || ""; const code = raw.trim(); const desc = code ? getDescriptionForCode(code) || "No description available" : "Enter a procedure code"; return (
{ rowRefs.current[i] = el; if (!el) rowRefs.current.splice(i, 1); }} className="scroll-mt-28 grid grid-cols-[1.5fr,0.5fr,1fr,1fr,1fr,1fr,1fr,0.5fr,1fr,1fr] gap-1 mb-2 items-center" >
updateServiceLine( i, "procedureCode", e.target.value.toUpperCase(), ) } />
{desc}
setOpenProcedureDateIndex(open ? i : null) } > updateProcedureDate(i, date)} onClose={() => setOpenProcedureDateIndex(null)} /> updateServiceLine(i, "toothNumber", e.target.value) } /> { const typed = normalizeToothSurface(e.target.value); updateServiceLine(i, "toothSurface", typed); }} /> updateServiceLine(i, "quantity", e.target.value) } /> updateServiceLine(i, "quad", e.target.value) } /> { updateServiceLine(i, "totalBilled", e.target.value); }} onBlur={(e) => { const val = parseFloat(e.target.value); const rounded = Math.round(val * 100) / 100; updateServiceLine( i, "totalBilled", isNaN(rounded) ? 0 : rounded, ); }} />
); })} { setForm((prev) => { const next = applyComboToForm( prev, comboKey as any, patient?.dateOfBirth ?? "", { replaceAll: false, lineDate: prev.serviceDate }, ); setTimeout(() => scrollToLine(0), 0); return next; }); }} />
{/* File Upload Section */}

File Upload

You can upload up to 10 files. Allowed types: PDF, JPG, PNG, WEBP.

{form.uploadedFiles.length > 0 && (
    {form.uploadedFiles.map((file, index) => (
  • {file.name}
  • ))}
)}
{/* Missing Teeth */}

Missing Teeth

{form.missingTeethStatus === "Yes_missing" && ( )}
{form.missingTeethStatus === "Yes_missing" && ( setForm((prev) => ({ ...prev, missingTeeth: next })) } /> )}
{/* Remarks */}

Remarks

setForm((prev) => ({ ...prev, remarks: next })) } />
{/* PreAuth Buttons */}

PreAuth

{/* Price mismatch dialog */} 0} onOpenChange={open => { if (!open) setPriceMismatches([]); }}> Save new price to the app?

The following procedure prices differ from the fee schedule:

    {priceMismatches.map(m => (
  • {m.procedureCode} Schedule: ${m.schedulePrice.toFixed(2)} Entered: ${m.enteredPrice.toFixed(2)}
  • ))}

Do you want to save the new price(s) to the fee schedule for future use?

{ setPriceMismatches([]); pendingClaimAction.current?.(); pendingClaimAction.current = null; }}> No { await savePricesToSchedule(priceMismatches); setPriceMismatches([]); pendingClaimAction.current?.(); pendingClaimAction.current = null; }}> Yes
); }