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}
+
+
+
+ );
+ })}
+
+
+ );
+}