diff --git a/apps/Frontend/src/components/appointment-procedures/appointment-procedures-dialog.tsx b/apps/Frontend/src/components/appointment-procedures/appointment-procedures-dialog.tsx index 9c65e5e..1f4e2e1 100644 --- a/apps/Frontend/src/components/appointment-procedures/appointment-procedures-dialog.tsx +++ b/apps/Frontend/src/components/appointment-procedures/appointment-procedures-dialog.tsx @@ -12,7 +12,7 @@ import { Label } from "@/components/ui/label"; import { Trash2, Plus, Save, X } from "lucide-react"; import { apiRequest, queryClient } from "@/lib/queryClient"; import { useToast } from "@/hooks/use-toast"; -import { PROCEDURE_COMBOS, COMBO_CATEGORIES } from "@/utils/procedureCombos"; +import { PROCEDURE_COMBOS } from "@/utils/procedureCombos"; import { CODE_MAP, getPriceForCodeWithAgeFromMap, @@ -20,6 +20,10 @@ import { import { Patient, AppointmentProcedure } from "@repo/db/types"; import { useLocation } from "wouter"; import { DeleteConfirmationDialog } from "../ui/deleteDialog"; +import { + DirectComboButtons, + RegularComboButtons, +} from "@/components/procedure/procedure-combo-buttons"; interface Props { open: boolean; @@ -66,13 +70,13 @@ export function AppointmentProceduresDialog({ queryFn: async () => { const res = await apiRequest( "GET", - `/api/appointment-procedures/${appointmentId}` + `/api/appointment-procedures/${appointmentId}`, ); if (!res.ok) throw new Error("Failed to load procedures"); return res.json(); }, enabled: open && !!appointmentId, - } + }, ); // ----------------------------- @@ -94,7 +98,7 @@ export function AppointmentProceduresDialog({ const res = await apiRequest( "POST", "/api/appointment-procedures", - payload + payload, ); if (!res.ok) throw new Error("Failed to add procedure"); return res.json(); @@ -124,7 +128,7 @@ export function AppointmentProceduresDialog({ const res = await apiRequest( "POST", "/api/appointment-procedures/bulk", - rows + rows, ); if (!res.ok) throw new Error("Failed to add combo procedures"); return res.json(); @@ -141,7 +145,7 @@ export function AppointmentProceduresDialog({ mutationFn: async (id: number) => { const res = await apiRequest( "DELETE", - `/api/appointment-procedures/${id}` + `/api/appointment-procedures/${id}`, ); if (!res.ok) throw new Error("Failed to delete"); }, @@ -157,7 +161,7 @@ export function AppointmentProceduresDialog({ mutationFn: async () => { const res = await apiRequest( "DELETE", - `/api/appointment-procedures/clear/${appointmentId}` + `/api/appointment-procedures/clear/${appointmentId}`, ); if (!res.ok) throw new Error("Failed to clear procedures"); }, @@ -183,7 +187,7 @@ export function AppointmentProceduresDialog({ const res = await apiRequest( "PUT", `/api/appointment-procedures/${editingId}`, - editRow + editRow, ); if (!res.ok) throw new Error("Failed to update"); return res.json(); @@ -291,34 +295,18 @@ export function AppointmentProceduresDialog({ {/* ================= COMBOS ================= */} -
-
- Quick Add Combos -
+
+ { + handleAddCombo(comboKey); + }} + /> - {Object.entries(COMBO_CATEGORIES).map(([categoryName, comboKeys]) => ( -
-
{categoryName}
- -
- {comboKeys.map((comboKey) => { - const combo = PROCEDURE_COMBOS[comboKey]; - if (!combo) return null; - - return ( - - ); - })} -
-
- ))} + { + handleAddCombo(comboKey); + }} + />
{/* ================= MANUAL ADD ================= */} diff --git a/apps/Frontend/src/components/claims/claim-form.tsx b/apps/Frontend/src/components/claims/claim-form.tsx index fd9ce57..b4ce5b9 100644 --- a/apps/Frontend/src/components/claims/claim-form.tsx +++ b/apps/Frontend/src/components/claims/claim-form.tsx @@ -50,10 +50,14 @@ import { applyComboToForm, getDescriptionForCode, } from "@/utils/procedureCombosMapping"; -import { COMBO_CATEGORIES, PROCEDURE_COMBOS } from "@/utils/procedureCombos"; +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"; interface ClaimFormProps { patientId: number; @@ -61,7 +65,7 @@ interface ClaimFormProps { autoSubmit?: boolean; onSubmit: (data: ClaimFormData) => Promise; onHandleAppointmentSubmit: ( - appointmentData: InsertAppointment | UpdateAppointment + appointmentData: InsertAppointment | UpdateAppointment, ) => Promise; onHandleUpdatePatient: (patient: UpdatePatient & { id: number }) => void; onHandleForMHSeleniumClaim: (data: ClaimFormData) => void; @@ -123,7 +127,7 @@ export function ClaimForm({ useEffect(() => { if (staffMembersRaw.length > 0 && !staff) { const kaiGao = staffMembersRaw.find( - (member) => member.name === "Kai Gao" + (member) => member.name === "Kai Gao", ); const defaultStaff = kaiGao || staffMembersRaw[0]; if (defaultStaff) setStaff(defaultStaff); @@ -133,7 +137,7 @@ export function ClaimForm({ // Service date state const [serviceDateValue, setServiceDateValue] = useState(new Date()); const [serviceDate, setServiceDate] = useState( - formatLocalDate(new Date()) + formatLocalDate(new Date()), ); const [serviceDateOpen, setServiceDateOpen] = useState(false); const [openProcedureDateIndex, setOpenProcedureDateIndex] = useState< @@ -152,7 +156,7 @@ export function ClaimForm({ try { const res = await apiRequest( "GET", - `/api/appointments/${appointmentId}` + `/api/appointments/${appointmentId}`, ); if (!res.ok) { let body: any = null; @@ -191,7 +195,7 @@ export function ClaimForm({ dateVal = new Date( maybe.getFullYear(), maybe.getMonth(), - maybe.getDate() + maybe.getDate(), ); } @@ -228,7 +232,7 @@ export function ClaimForm({ try { const res = await apiRequest( "GET", - `/api/appointment-procedures/prefill-from-appointment/${appointmentId}` + `/api/appointment-procedures/prefill-from-appointment/${appointmentId}`, ); if (!res.ok) return; @@ -429,7 +433,7 @@ export function ClaimForm({ const updateServiceLine = ( index: number, field: keyof InputServiceLine, - value: any + value: any, ) => { const updatedLines = [...form.serviceLines]; @@ -496,7 +500,7 @@ export function ClaimForm({ mapPricesForForm({ form: prev, patientDOB: patient?.dateOfBirth ?? "", - }) + }), ); }; @@ -511,7 +515,7 @@ export function ClaimForm({ // 1st Button workflow - Mass Health Button Handler const handleMHSubmit = async ( - formToUse?: ClaimFormData & { uploadedFiles?: File[] } + formToUse?: ClaimFormData & { uploadedFiles?: File[] }, ) => { // Use the passed form, or fallback to current state const f = formToUse ?? form; @@ -534,7 +538,7 @@ export function ClaimForm({ // require at least one procedure code before proceeding const filteredServiceLines = (f.serviceLines || []).filter( - (line) => (line.procedureCode ?? "").trim() !== "" + (line) => (line.procedureCode ?? "").trim() !== "", ); if (filteredServiceLines.length === 0) { toast({ @@ -620,7 +624,7 @@ export function ClaimForm({ // 2st Button workflow - Mass Health Pre Auth Button Handler const handleMHPreAuth = async ( - formToUse?: ClaimFormData & { uploadedFiles?: File[] } + formToUse?: ClaimFormData & { uploadedFiles?: File[] }, ) => { // Use the passed form, or fallback to current state const f = formToUse ?? form; @@ -643,7 +647,7 @@ export function ClaimForm({ // require at least one procedure code before proceeding const filteredServiceLines = (f.serviceLines || []).filter( - (line) => (line.procedureCode ?? "").trim() !== "" + (line) => (line.procedureCode ?? "").trim() !== "", ); if (filteredServiceLines.length === 0) { toast({ @@ -707,7 +711,7 @@ export function ClaimForm({ // require at least one procedure code before proceeding const filteredServiceLines = (form.serviceLines || []).filter( - (line) => (line.procedureCode ?? "").trim() !== "" + (line) => (line.procedureCode ?? "").trim() !== "", ); if (filteredServiceLines.length === 0) { toast({ @@ -780,13 +784,13 @@ export function ClaimForm({ // for direct combo button. const applyComboAndThenMH = async ( - comboId: keyof typeof PROCEDURE_COMBOS + comboId: keyof typeof PROCEDURE_COMBOS, ) => { const nextForm = applyComboToForm( form, comboId, patient?.dateOfBirth ?? "", - { replaceAll: false, lineDate: form.serviceDate } + { replaceAll: false, lineDate: form.serviceDate }, ); setForm(nextForm); @@ -803,7 +807,7 @@ export function ClaimForm({ !!form.patientName?.trim() && Array.isArray(form.serviceLines) && form.serviceLines.some( - (l) => l.procedureCode && l.procedureCode.trim() !== "" + (l) => l.procedureCode && l.procedureCode.trim() !== "", ) ); }, [ @@ -963,7 +967,7 @@ export function ClaimForm({ value={staff?.id?.toString() || ""} onValueChange={(id) => { const selected = staffMembersRaw.find( - (member) => member.id?.toString() === id + (member) => member.id?.toString() === id, ); if (selected) { setStaff(selected); @@ -1007,163 +1011,11 @@ export function ClaimForm({
-
- {/* Section Title */} -
- Direct Claim Submission Buttons -
- -
- {/* CHILD RECALL GROUP */} -
-
- Child Recall -
-
- {[ - "childRecallDirect", - "childRecallDirect2BW", - "childRecallDirect4BW", - "childRecallDirect2PA2BW", - "childRecallDirect2PA4BW", - "childRecallDirect3PA2BW", - "childRecallDirect3PA", - "childRecallDirect4PA", - "childRecallDirectPANO", - ].map((comboId) => { - const b = PROCEDURE_COMBOS[comboId]; - if (!b) return null; - const codesWithTooth = b.codes.map((code, idx) => { - const tooth = b.toothNumbers?.[idx]; - return tooth ? `${code} (tooth ${tooth})` : code; - }); - const tooltipText = codesWithTooth.join(", "); - const labelMap: Record = { - childRecallDirect: "Direct", - childRecallDirect2BW: "Direct 2BW", - childRecallDirect4BW: "Direct 4BW", - childRecallDirect2PA2BW: "Direct 2PA 2BW", - childRecallDirect2PA4BW: "Direct 2PA 4BW", - childRecallDirect3PA2BW: "Direct 3PA 2BW", - childRecallDirect3PA: "Direct 3PA", - childRecallDirect4PA: "Direct 4PA", - childRecallDirectPANO: "Direct Pano", - }; - return ( - - - - - -
- {tooltipText} -
-
-
- ); - })} -
-
- - {/* ADULT RECALL GROUP */} -
-
- Adult Recall -
-
- {[ - "adultRecallDirect", - "adultRecallDirect2BW", - "adultRecallDirect4BW", - "adultRecallDirect2PA2BW", - "adultRecallDirect2PA4BW", - "adultRecallDirect4PA", - "adultRecallDirectPano", - ].map((comboId) => { - const b = PROCEDURE_COMBOS[comboId]; - if (!b) return null; - const codesWithTooth = b.codes.map((code, idx) => { - const tooth = b.toothNumbers?.[idx]; - return tooth ? `${code} (tooth ${tooth})` : code; - }); - const tooltipText = codesWithTooth.join(", "); - const labelMap: Record = { - adultRecallDirect: "Direct", - adultRecallDirect2BW: "Direct 2BW", - adultRecallDirect4BW: "Direct 4BW", - adultRecallDirect2PA2BW: "Direct 2PA 2BW", - adultRecallDirect2PA4BW: "Direct 2PA 4BW", - adultRecallDirect4PA: "Direct 4PA", - adultRecallDirectPano: "Direct Pano", - }; - return ( - - - - - -
- {tooltipText} -
-
-
- ); - })} -
-
- - {/* ORTH GROUP */} -
-
Orth
- -
- {[ - "orthPreExamDirect", - "orthRecordDirect", - "orthPerioVisitDirect", - "orthRetentionDirect", - ].map((comboId) => { - const b = PROCEDURE_COMBOS[comboId]; - if (!b) return null; - - const tooltipText = b.codes.join(", "); - - return ( - - - - - - -
- {tooltipText} -
-
-
- ); - })} -
-
-
-
+ + applyComboAndThenMH(comboKey as any) + } + /> {/* Header */} @@ -1221,7 +1073,7 @@ export function ClaimForm({ updateServiceLine( i, "procedureCode", - e.target.value.toUpperCase() + e.target.value.toUpperCase(), ) } /> @@ -1308,7 +1160,7 @@ export function ClaimForm({ updateServiceLine( i, "totalBilled", - isNaN(rounded) ? 0 : rounded + isNaN(rounded) ? 0 : rounded, ); }} /> @@ -1341,66 +1193,24 @@ export function ClaimForm({ + Add Service Line -
- {Object.entries(COMBO_CATEGORIES).map(([section, ids]) => ( -
-
- {section} -
-
- {ids.map((id) => { - const b = PROCEDURE_COMBOS[id]; - if (!b) { - return; - } - // Build a human readable string for the tooltip - const codesWithTooth = b.codes.map((code, idx) => { - const tooth = b.toothNumbers?.[idx]; - return tooth ? `${code} (tooth ${tooth})` : code; - }); - const tooltipText = codesWithTooth.join(", "); + { + setForm((prev) => { + const next = applyComboToForm( + prev, + comboKey as any, + patient?.dateOfBirth ?? "", + { + replaceAll: false, + lineDate: prev.serviceDate, + }, + ); - return ( - - - - - - -
- {tooltipText} -
-
-
- ); - })} -
-
- ))} -
+ setTimeout(() => scrollToLine(0), 0); + return next; + }); + }} + /> {/* File Upload Section */} diff --git a/apps/Frontend/src/components/procedure/procedure-combo-buttons.tsx b/apps/Frontend/src/components/procedure/procedure-combo-buttons.tsx new file mode 100644 index 0000000..659c9c0 --- /dev/null +++ b/apps/Frontend/src/components/procedure/procedure-combo-buttons.tsx @@ -0,0 +1,207 @@ +import { Button } from "@/components/ui/button"; +import { + Tooltip, + TooltipContent, + TooltipTrigger, +} from "@/components/ui/tooltip"; +import { + PROCEDURE_COMBOS, + COMBO_CATEGORIES, +} from "@/utils/procedureCombos"; + +/* ========================================================= + DIRECT COMBO BUTTONS (TOP SECTION) + ========================================================= */ + +export function DirectComboButtons({ + onDirectCombo, +}: { + onDirectCombo: (comboKey: string) => void; +}) { + return ( +
+ {/* Section Title */} +
+ Direct Claim Submission Buttons +
+ +
+ {/* CHILD RECALL */} + + + {/* ADULT RECALL */} + + + {/* ORTH */} + +
+
+ ); +} + +/* ========================================================= + REGULAR COMBO BUTTONS (BOTTOM SECTION) + ========================================================= */ + +export function RegularComboButtons({ + onRegularCombo, +}: { + onRegularCombo: (comboKey: string) => void; +}) { + return ( +
+ {Object.entries(COMBO_CATEGORIES).map(([section, ids]) => ( +
+
+ {section} +
+ +
+ {ids.map((id) => { + const b = PROCEDURE_COMBOS[id]; + if (!b) return null; + + const tooltipText = b.codes + .map((code, idx) => { + const tooth = b.toothNumbers?.[idx]; + return tooth ? `${code} (tooth ${tooth})` : code; + }) + .join(", "); + + return ( + + + + + + +
+ {tooltipText} +
+
+
+ ); + })} +
+
+ ))} +
+ ); +} + +/* ========================================================= + INTERNAL HELPERS + ========================================================= */ + +function DirectGroup({ + title, + combos, + labelMap, + onSelect, +}: { + title: string; + combos: string[]; + labelMap?: Record; + onSelect: (id: string) => void; +}) { + return ( +
+
{title}
+ +
+ {combos.map((id) => { + const b = PROCEDURE_COMBOS[id]; + if (!b) return null; + + const tooltipText = b.codes + .map((code, idx) => { + const tooth = b.toothNumbers?.[idx]; + return tooth ? `${code} (tooth ${tooth})` : code; + }) + .join(", "); + + return ( + + + + + + +
+ {tooltipText} +
+
+
+ ); + })} +
+
+ ); +}