-
- {/* responsive grid that auto-fits cells; no horizontal overflow */}
-
- {toothNames.map((name) => (
-
- ))}
-
-
- );
-}
-
+import { Label } from "recharts";
+import { Input } from "../ui/input";
+import { Button } from "../ui/button";
// ——— Missing Teeth helpers for claim-view and edit modal———
type MissingMap = Record;
@@ -153,3 +55,137 @@ export function ToothChip({ name, v }: { name: string; v: ToothVal }) {
);
}
+
+export type ToothVal = "X" | "O";
+export type MissingMapStrict = Record;
+
+/* ---------- parsing helpers ---------- */
+const PERM_NUMBERS = new Set(
+ Array.from({ length: 32 }, (_, i) => String(i + 1))
+);
+const PRIM_LETTERS = new Set(Array.from("ABCDEFGHIJKLMNOPQRST"));
+
+function normalizeToothToken(token: string): string | null {
+ const t = token.trim().toUpperCase();
+ if (!t) return null;
+ if (PERM_NUMBERS.has(t)) return t; // 1..32
+ if (t.length === 1 && PRIM_LETTERS.has(t)) return t; // A..T
+ return null;
+}
+
+function listToEntries(list: string, val: ToothVal): Array<[string, ToothVal]> {
+ if (!list) return [];
+ const seen = new Set();
+ return list
+ .split(/[,\s]+/g) // commas OR spaces
+ .map(normalizeToothToken) // uppercase + validate
+ .filter((t): t is string => !!t)
+ .filter((t) => {
+ // de-duplicate within field
+ if (seen.has(t)) return false;
+ seen.add(t);
+ return true;
+ })
+ .map((t) => [`T_${t}`, val]);
+}
+
+/** Build map; 'O' overrides 'X' when duplicated across fields. */
+export function mapFromLists(
+ missingList: string,
+ pullList: string
+): MissingMapStrict {
+ const map: MissingMapStrict = {};
+ for (const [k, v] of listToEntries(missingList, "X")) map[k] = v;
+ for (const [k, v] of listToEntries(pullList, "O")) map[k] = v;
+ return map;
+}
+
+/** For initializing the inputs from an existing map (used only on mount or clear). */
+export function listsFromMap(map: Record): {
+ missing: string;
+ toPull: string;
+} {
+ const missing: string[] = [];
+ const toPull: string[] = [];
+ for (const [k, v] of Object.entries(map || {})) {
+ if (v === "X") missing.push(k.replace(/^T_/, ""));
+ else if (v === "O") toPull.push(k.replace(/^T_/, ""));
+ }
+ const sort = (a: string, b: string) => {
+ const na = Number(a),
+ nb = Number(b);
+ const an = !Number.isNaN(na),
+ bn = !Number.isNaN(nb);
+ if (an && bn) return na - nb;
+ if (an) return -1;
+ if (bn) return 1;
+ return a.localeCompare(b);
+ };
+ missing.sort(sort);
+ toPull.sort(sort);
+ return { missing: missing.join(", "), toPull: toPull.join(", ") };
+}
+
+/* ---------- UI ---------- */
+export function MissingTeethSimple({
+ value,
+ onChange,
+}: {
+ /** Must match ClaimFormData.missingTeeth exactly */
+ value: MissingMapStrict;
+ onChange: (next: MissingMapStrict) => void;
+}) {
+ // initialize text inputs from incoming map
+ const init = React.useMemo(() => listsFromMap(value), []); // only on mount
+ const [missingField, setMissingField] = React.useState(init.missing);
+ const [pullField, setPullField] = React.useState(init.toPull);
+
+ // only resync when parent CLEARS everything (so your Clear All works)
+ React.useEffect(() => {
+ if (!value || Object.keys(value).length === 0) {
+ setMissingField("");
+ setPullField("");
+ }
+ }, [value]);
+
+ const recompute = (mStr: string, pStr: string) => {
+ onChange(mapFromLists(mStr, pStr));
+ };
+
+ return (
+
+
+
+ {/* simple text label (no recharts Label) */}
+
Tooth Number - Missing - X
+ {
+ const m = e.target.value.toUpperCase(); // keep uppercase in the field
+ setMissingField(m);
+ recompute(m, pullField);
+ }}
+ aria-label="Tooth Numbers — Missing"
+ />
+
+
+
+
+ Tooth Number - To be pulled - O
+
+ {
+ const p = e.target.value.toUpperCase(); // keep uppercase in the field
+ setPullField(p);
+ recompute(missingField, p);
+ }}
+ aria-label="Tooth Numbers — To be pulled"
+ />
+