Files
DentalManagementMH05/apps/Frontend/src/components/claims/claim-form.tsx

1819 lines
61 KiB
TypeScript
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 { 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,
} 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";
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<Claim>;
onHandleAppointmentSubmit: (
appointmentData: InsertAppointment | UpdateAppointment,
) => Promise<number | { id: number }>;
onHandleUpdatePatient: (patient: UpdatePatient & { id: number }) => void;
onHandleForMHSeleniumClaim: (data: ClaimFormData) => void;
onHandleForMHSeleniumClaimPreAuth: (data: ClaimPreAuthData) => void;
onClose: () => void;
}
export function ClaimForm({
patientId,
appointmentId,
autoSubmit,
proceduresOnly = false,
onHandleAppointmentSubmit,
onHandleUpdatePatient,
onHandleForMHSeleniumClaim,
onHandleForMHSeleniumClaimPreAuth,
onSubmit,
onClose,
}: 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<number | null>(null);
const [directSubmitEnabled, setDirectSubmitEnabled] = useState(false);
const [patient, setPatient] = useState<Patient | null>(null);
// staffId from the appointment column — used for claim creation, not shown in UI
const [appointmentStaffId, setAppointmentStaffId] = useState<number | null>(null);
// npiProviderId loaded from AppointmentProcedure (2b) — restored to form when npiProviders load
const [savedProcNpiId, setSavedProcNpiId] = useState<number | null>(null);
// Query patient based on given patient id
const {
data: fetchedPatient,
isLoading,
error,
} = useQuery<Patient>({
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<Staff | null>(null);
const { data: staffMembersRaw = [] as Staff[], isLoading: isLoadingStaff } =
useQuery<Staff[]>({
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<NpiProvider[]>({
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<Date>(new Date());
const [serviceDate, setServiceDate] = useState<string>(
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 {}
}
// Restore service lines
const mappedLines = (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: mappedLines.length > 0 ? mappedLines : prev.serviceLines,
remarks: claim.remarks ?? "",
missingTeethStatus: (claim.missingTeethStatus as MissingTeethStatus) ?? "No_missing",
missingTeeth: (claim.missingTeeth as Record<string, "X" | "O">) ?? {},
insuranceProvider: 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<ClaimFormData & { uploadedFiles: File[] }>({
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: [],
});
// Sync patient data to form when patient updates
useEffect(() => {
if (patient) {
const fullName =
`${patient.firstName || ""} ${patient.lastName || ""}`.trim();
setForm((prev) => ({
...prev,
patientId: Number(patient.id),
patientName: fullName,
dateOfBirth: normalizeToIsoDateString(patient.dateOfBirth),
memberId: patient.insuranceId || "",
}));
}
}, [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
const onMapPrice = () => {
setForm((prev) =>
mapPricesForForm({
form: prev,
patientDOB: patient?.dateOfBirth ?? "",
}),
);
};
// FILE UPLOAD ZONE
const uploadZoneRef = useRef<MultipleFileUploadZoneHandle | null>(null);
const [isUploading, setIsUploading] = useState(false);
// 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;
}
}
// 2. Update patient
if (patient && typeof patient.id === "number") {
const { id, createdAt, userId, ...sanitizedFields } = patient;
const updatedPatientFields = {
id,
...sanitizedFields,
insuranceProvider: "MassHealth",
};
onHandleUpdatePatient(updatedPatientFields);
} else {
toast({
title: "Error",
description: "Cannot update patient: Missing or invalid patient data",
variant: "destructive",
});
}
// 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 createdClaim = await onSubmit({
...formToCreateClaim,
serviceLines: filteredServiceLines,
staffId: appointmentStaffId ?? Number(staff?.id),
patientId: patientId,
insuranceProvider: "MassHealth",
appointmentId: appointmentIdToUse!,
claimFiles: claimFilesMeta,
});
// 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;
}
// 2. Update patient
if (patient && typeof patient.id === "number") {
const { id, createdAt, userId, ...sanitizedFields } = patient;
const updatedPatientFields = {
id,
...sanitizedFields,
insuranceProvider: "MassHealth",
};
onHandleUpdatePatient(updatedPatientFields);
} else {
toast({
title: "Error",
description: "Cannot update patient: Missing or invalid patient data",
variant: "destructive",
});
}
// 4. 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();
};
// 3nd Button workflow - Only Creates Data, patient, appointmetn, claim, payment, not actually submits claim to MH site.
const handleAddService = async () => {
// 0. Validate required fields
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;
}
// require at least one procedure code before proceeding
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;
}
// 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;
}
}
// 2. Update patient
if (patient && typeof patient.id === "number") {
const { id, createdAt, userId, ...sanitizedFields } = patient;
const updatedPatientFields = {
id,
...sanitizedFields,
insuranceProvider: "MassHealth",
};
onHandleUpdatePatient(updatedPatientFields);
} else {
toast({
title: "Error",
description: "Cannot update patient: Missing or invalid patient data",
variant: "destructive",
});
}
// 3. Create Claim(if not)
// Filter out empty service lines (empty procedureCode)
const { uploadedFiles, insuranceSiteKey, npiProvider: _npi, ...formToCreateClaim } = form;
// build claimFiles metadata from uploadedFiles (only filename + mimeType)
const claimFilesMeta: ClaimFileMeta[] = (uploadedFiles || []).map((f) => ({
filename: f.name,
mimeType: f.type,
}));
const createdClaim = await onSubmit({
...formToCreateClaim,
serviceLines: filteredServiceLines,
staffId: appointmentStaffId ?? Number(staff?.id),
patientId: patientId,
insuranceProvider: "MassHealth",
appointmentId: appointmentIdToUse!,
claimFiles: claimFilesMeta,
});
// 4. Close form
onClose();
};
const uploadAttachmentsToLocalFolder = async (files: File[]): Promise<ClaimFileMeta[]> => {
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();
};
// 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 },
);
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<HTMLDivElement>) => {
// 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 (
<div
className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4 overflow-y-auto"
onMouseDown={onOverlayMouseDown}
>
<Card className="w-[90vw] h-[90vh] max-w-none overflow-auto bg-white relative">
<CardHeader className="sticky top-0 z-20 flex flex-row items-center justify-between pb-2 border-b bg-white/95 backdrop-blur-sm">
<CardTitle className="text-xl font-bold">
Insurance Claim Form
</CardTitle>
<Button onClick={onClose}>
{" "}
<X className="w-4 h-4" />
</Button>
</CardHeader>
<CardContent className="pt-4">
<div className="space-y-6">
{/* Patient Information */}
<div className="grid grid-cols-4 gap-4">
<div>
<Label htmlFor="memberId">Member ID</Label>
<Input
id="memberId"
value={form.memberId}
onChange={(e) => {
setForm({ ...form, memberId: e.target.value });
updatePatientField("insuranceId", e.target.value);
}}
/>
</div>
<div>
<Label htmlFor="dateOfBirth">Date Of Birth</Label>
<DateInput
value={
form.dateOfBirth ? parseLocalDate(form.dateOfBirth) : null
}
onChange={(date: Date | null) => {
const formatted = date ? formatLocalDate(date) : "";
setForm((prev) => ({ ...prev, dateOfBirth: formatted }));
updatePatientField("dateOfBirth", formatted);
}}
disableFuture
/>
</div>
<div>
<Label htmlFor="firstName">First Name</Label>
<Input
id="firstName"
value={patient?.firstName || ""}
onChange={(e) => {
updatePatientField("firstName", e.target.value);
setForm((prev) => ({
...prev,
patientName:
`${e.target.value} ${patient?.lastName || ""}`.trim(),
}));
}}
disabled={isLoading}
/>
</div>
<div>
<Label htmlFor="lastName">Last Name</Label>
<Input
id="lastName"
value={patient?.lastName || ""}
onChange={(e) => {
updatePatientField("lastName", e.target.value);
setForm((prev) => ({
...prev,
patientName:
`${patient?.firstName || ""} ${e.target.value}`.trim(),
}));
}}
disabled={isLoading}
/>
</div>
</div>
{/* Service Lines */}
<div>
<h3 className="text-xl font-semibold mb-2 text-center">
Service Lines
</h3>
<div className="flex flex-col gap-2">
<div className="flex justify-end items-center mb-4">
{/* Service Date */}
<div className="flex gap-2">
<Label className="flex items-center">Service Date</Label>
<Popover
open={serviceDateOpen}
onOpenChange={setServiceDateOpen}
>
<PopoverTrigger asChild>
<Button
variant="outline"
className="w-[140px] justify-start text-left font-normal mr-4"
>
<CalendarIcon className="mr-2 h-4 w-4" />
{form.serviceDate}
</Button>
</PopoverTrigger>
<PopoverContent className="w-auto">
<Calendar
mode="single"
selected={serviceDateValue}
onSelect={(date) => {
onServiceDateChange(date);
}}
onClose={() => setServiceDateOpen(false)}
/>
</PopoverContent>
</Popover>
{/* Rendering Npi Provider */}
<Label className="flex items-center ml-2">
Rendering Provider
</Label>
<Select
value={form.npiProvider?.npiNumber || ""}
onValueChange={(npiNumber) => {
const selected = npiProviders.find(
(p) => p.npiNumber === npiNumber,
);
if (!selected) return;
setForm((prev) => ({
...prev,
npiProvider: {
// ✅ CORRECT KEY
npiNumber: selected.npiNumber,
providerName: selected.providerName,
},
}));
}}
>
<SelectTrigger className="w-56">
<SelectValue placeholder="Select NPI Provider" />
</SelectTrigger>
<SelectContent>
{npiProviders.map((p) => (
<SelectItem key={p.id} value={p.npiNumber}>
{p.npiNumber} {p.providerName}
</SelectItem>
))}
</SelectContent>
</Select>
{/* Map Price Button */}
<Button
className="ml-4"
variant="success"
onClick={onMapPrice}
>
Map Price
</Button>
</div>
</div>
<div className="flex items-center gap-3 mb-2">
<Switch
id="direct-submit-toggle"
checked={directSubmitEnabled}
onCheckedChange={setDirectSubmitEnabled}
/>
<Label htmlFor="direct-submit-toggle" className="text-sm cursor-pointer select-none">
Direct Submission {directSubmitEnabled ? <span className="text-green-600 font-semibold">ON</span> : <span className="text-muted-foreground">OFF</span>}
</Label>
</div>
<DirectComboButtons
onDirectCombo={(comboKey) => {
if (directSubmitEnabled) {
applyComboAndThenMH(comboKey as any);
} else {
setForm((prev) => {
const next = applyComboToForm(
prev,
comboKey as any,
patient?.dateOfBirth ?? "",
{ replaceAll: false, lineDate: prev.serviceDate },
);
setTimeout(() => scrollToLine(0), 0);
return next;
});
}
}}
/>
</div>
{/* Header */}
<div className="grid grid-cols-[1.5fr,0.5fr,1fr,1fr,1fr,1fr,1fr,0.5fr,1fr,1fr] gap-1 mb-2 mt-10 font-medium text-sm text-gray-700 items-center">
<div className="grid grid-cols-[auto,1fr] items-center gap-2">
<span />
<span className="pl-8">Procedure Code</span>
</div>
<span className="justify-self-center">Info</span>
<span>Procedure Date</span>
<span>Tooth Number</span>
<span>Tooth Surface</span>
<span>Quad</span>
<span>Arch</span>
<span>Qty</span>
<span>Auth No.</span>
<span>Billed Amount</span>
</div>
{/* 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 (
<div
key={i}
ref={(el) => {
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"
>
<div className="grid grid-cols-[auto,1fr] items-center gap-2">
<button
type="button"
onClick={() =>
setForm((prev) => {
const next = {
...prev,
serviceLines: [...prev.serviceLines],
};
next.serviceLines.splice(i, 1);
return next;
})
}
className="p-1 rounded hover:bg-red-50"
>
<Trash2 className="h-4 w-4 text-red-500 hover:text-red-700" />
</button>
<Input
placeholder="eg. D0120"
value={line.procedureCode}
onChange={(e) =>
updateServiceLine(
i,
"procedureCode",
e.target.value.toUpperCase(),
)
}
/>
</div>
<div className="flex justify-center">
<Tooltip>
<TooltipTrigger asChild>
<HelpCircle className="h-4 w-4 text-gray-400 hover:text-gray-600 cursor-help" />
</TooltipTrigger>
<TooltipContent>
<div className="text-sm">{desc}</div>
</TooltipContent>
</Tooltip>
</div>
{/* Date Picker */}
<Popover
open={openProcedureDateIndex === i}
onOpenChange={(open) =>
setOpenProcedureDateIndex(open ? i : null)
}
>
<PopoverTrigger asChild>
<Button
variant="outline"
className="w-full text-left font-normal"
>
<CalendarIcon className="mr-2 h-4 w-4" />
{line.procedureDate || "Pick Date"}
</Button>
</PopoverTrigger>
<PopoverContent className="w-auto">
<Calendar
mode="single"
selected={
line.procedureDate
? new Date(line.procedureDate)
: undefined
}
onSelect={(date) => updateProcedureDate(i, date)}
onClose={() => setOpenProcedureDateIndex(null)}
/>
</PopoverContent>
</Popover>
<Input
placeholder="eg. 14"
value={line.toothNumber}
onChange={(e) =>
updateServiceLine(i, "toothNumber", e.target.value)
}
/>
<Input
placeholder="eg. B,D,F,I,L,M,O (comma-separated)"
value={line.toothSurface}
onChange={(e) => {
const typed = normalizeToothSurface(e.target.value);
updateServiceLine(i, "toothSurface", typed);
}}
/>
<Select
value={line.quad ?? ""}
onValueChange={(value) =>
updateServiceLine(i, "quad", value)
}
>
<SelectTrigger className="w-full">
<SelectValue placeholder="Quad" />
</SelectTrigger>
<SelectContent>
<SelectItem value="UL">UL</SelectItem>
<SelectItem value="LL">LL</SelectItem>
<SelectItem value="UR">UR</SelectItem>
<SelectItem value="LR">LR</SelectItem>
</SelectContent>
</Select>
<Select
value={line.arch ?? ""}
onValueChange={(value) =>
updateServiceLine(i, "arch", value)
}
>
<SelectTrigger className="w-full">
<SelectValue placeholder="Arch" />
</SelectTrigger>
<SelectContent>
<SelectItem value="Mandible">Mandible</SelectItem>
<SelectItem value="Maxilla">Maxilla</SelectItem>
</SelectContent>
</Select>
<Input
type="number"
placeholder="Qty"
value={line.quantity ?? ""}
onChange={(e) =>
updateServiceLine(i, "quantity", e.target.value)
}
/>
<Input
placeholder="Quad"
value={line.quad}
onChange={(e) =>
updateServiceLine(i, "quad", e.target.value)
}
/>
<Input
type="number"
step="0.01"
placeholder="$0.00"
value={
line.totalBilled?.toNumber() === 0
? ""
: line.totalBilled?.toNumber()
}
onChange={(e) => {
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,
);
}}
/>
</div>
);
})}
<Button
className="mt-2"
variant="outline"
onClick={() =>
setForm((prev) => ({
...prev,
serviceLines: [
...prev.serviceLines,
{
procedureCode: "",
procedureDate: serviceDate,
quad: "",
arch: "",
toothNumber: "",
toothSurface: "",
totalBilled: new Decimal(0),
totalAdjusted: new Decimal(0),
totalPaid: new Decimal(0),
},
],
}))
}
>
+ Add Service Line
</Button>
<RegularComboButtons
onRegularCombo={(comboKey) => {
setForm((prev) => {
const next = applyComboToForm(
prev,
comboKey as any,
patient?.dateOfBirth ?? "",
{
replaceAll: false,
lineDate: prev.serviceDate,
},
);
setTimeout(() => scrollToLine(0), 0);
return next;
});
}}
/>
</div>
{/* File Upload Section */}
<h3 className="text-xl pt-8 font-semibold text-center">
File Upload
</h3>
<div className="mt-4 bg-gray-100 p-4 rounded-md space-y-4">
<p className="text-sm text-gray-500">
You can upload up to 10 files. Allowed types: PDF, JPG, PNG,
WEBP.
</p>
<MultipleFileUploadZone
ref={uploadZoneRef}
onFilesChange={handleFilesChange}
isUploading={isUploading}
acceptedFileTypes="application/pdf,image/jpeg,image/jpg,image/png,image/webp"
maxFiles={10}
/>
{form.uploadedFiles.length > 0 && (
<ul className="text-sm text-gray-700 list-disc ml-6">
{form.uploadedFiles.map((file, index) => (
<li key={index}>{file.name}</li>
))}
</ul>
)}
</div>
{/* Missing Teeth (MassHealth Step 3) */}
<div className="mt-8 pt-8 space-y-4">
<h3 className="text-xl font-semibold text-center">
Missing Teeth
</h3>
{/* Status selector must match Selenium's exact case */}
<div className="flex flex-wrap gap-2 items-center justify-center">
<Label className="mr-2">Status</Label>
<Button
type="button"
variant={
form.missingTeethStatus === "No_missing"
? "default"
: "outline"
}
onClick={() => setMissingTeethStatus("No_missing")}
aria-pressed={form.missingTeethStatus === "No_missing"}
>
No Missing
</Button>
<Button
type="button"
variant={
form.missingTeethStatus === "endentulous"
? "default"
: "outline"
}
onClick={() => setMissingTeethStatus("endentulous")}
aria-pressed={form.missingTeethStatus === "endentulous"}
>
Edentulous
</Button>
<Button
type="button"
variant={
form.missingTeethStatus === "Yes_missing"
? "default"
: "outline"
}
onClick={() => setMissingTeethStatus("Yes_missing")}
aria-pressed={form.missingTeethStatus === "Yes_missing"}
>
Specify Missing
</Button>
{form.missingTeethStatus === "Yes_missing" && (
<Button
type="button"
variant="outline"
onClick={clearAllToothSelections}
>
Clear All
</Button>
)}
</div>
{/* When specifying per-tooth values, show Permanent + Primary grids */}
{form.missingTeethStatus === "Yes_missing" && (
<MissingTeethSimple
value={form.missingTeeth}
onChange={(next) =>
setForm((prev) => ({ ...prev, missingTeeth: next }))
}
/>
)}
</div>
{/* Clinical Notes Entry */}
<div className="mt-8 pt-8 space-y-4">
<h3 className="text-xl font-semibold text-center">Remarks</h3>
<RemarksField
value={form.remarks}
onChange={(next) =>
setForm((prev) => ({ ...prev, remarks: next }))
}
/>
</div>
{/* Insurance Carriers */}
<div className="pt-6">
<h3 className="text-xl font-semibold mb-4 text-center">
{proceduresOnly ? "Save Procedures" : "Insurance Carriers"}
</h3>
{proceduresOnly ? (
/* ── Select Procedures mode ── */
<div className="flex justify-center gap-3">
<Button
className="w-40"
variant="default"
onClick={handleProceduresSave}
>
Save Procedures
</Button>
<Button
className="w-40"
variant="secondary"
onClick={handleProceduresUpdate}
>
Update &amp; Resubmit
</Button>
<Button
className="w-28"
variant="destructive"
onClick={handleProceduresVoid}
>
Void
</Button>
</div>
) : (
/* ── Insurance Claim mode: submit buttons, no Save ── */
<div className="flex justify-between">
<Button
className="w-32"
variant="secondary"
onClick={() => handleMHSubmit()}
>
MH
</Button>
<Button
className="w-32"
variant="secondary"
onClick={() => handleMHPreAuth()}
>
MH PreAuth
</Button>
<Button
className="w-32"
variant="secondary"
onClick={handleAddService}
>
Add Service
</Button>
<Button className="w-32" variant="outline">
Delta MA
</Button>
<Button className="w-32" variant="outline">
Others
</Button>
</div>
)}
</div>
</div>
</CardContent>
</Card>
</div>
);
}