feat: add new frontend components, MH batch worker, and gitignore rules

- Add all new Frontend source files (pages, components, hooks, utils)
- Add selenium_MHBatchPaymentCheckWorker.py and MHSinglePaymentCheckWorker.py
- Add install-steps-5-13.sh setup script
- Update .gitignore to exclude runtime/sensitive data (backups, uploads,
  chat-history, keys, downloads, generated .d.ts files) while keeping folders
- Add .gitkeep to preserve empty runtime folders in git

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-26 00:23:43 -04:00
co-authored by Claude Sonnet 4.6
parent b7e06adf2f
commit 1edf73fdc8
173 changed files with 33469 additions and 0 deletions
@@ -0,0 +1,97 @@
export const APPOINTMENT_TYPES = [
{ value: "recall", label: "Recalls & New Patients" },
{ value: "filling", label: "Filling (Composite)" },
{ value: "pedo", label: "Pedo" },
{ value: "dentures", label: "Dentures / Partials" },
{ value: "implant", label: "Implant" },
{ value: "endo", label: "Endo (Root Canal)" },
{ value: "crown", label: "Crown / Prosthodontics" },
{ value: "perio", label: "Periodontics" },
{ value: "extraction", label: "Extraction" },
{ value: "ortho", label: "Orthodontics" },
{ value: "consultation", label: "Consultation" },
{ value: "emergency", label: "Emergency" },
{ value: "other", label: "Other" },
];
const LEGACY_LABELS = {
checkup: "Checkup",
cleaning: "Cleaning",
"root-canal": "Root Canal",
};
export function getAppointmentTypeLabel(type) {
if (!type)
return "";
if (type.startsWith("other:"))
return type.slice(6);
const found = APPOINTMENT_TYPES.find((t) => t.value === type);
if (found)
return found.label;
return LEGACY_LABELS[type] ?? type;
}
function codeToType(code) {
const c = code.replace(/\s/g, "").toUpperCase();
// Special cases (pedo-specific codes that share ranges with other categories)
if (c === "D1351" || c === "D2930" || c === "D3220")
return "pedo";
// Emergency / consultation
if (c === "D9110")
return "emergency";
if (c === "D9310")
return "consultation";
// Endo: D3xxx (root canals, pulp therapy)
if (/^D3/.test(c))
return "endo";
// Implants: D6xxx
if (/^D6/.test(c))
return "implant";
// Crown / Prosthodontics: D27xx, D28xx (fixed partials, crowns)
if (/^D2[78]/.test(c))
return "crown";
// Dentures / Partials: D51xx–D58xx (complete/partial dentures, relines, repairs, adjustments)
if (/^D5[1-8]/.test(c))
return "dentures";
// Extractions: D71xx
if (/^D71/.test(c))
return "extraction";
// Periodontics: D43xx–D49xx
if (/^D4[3-9]/.test(c))
return "perio";
// Fillings / Restorations: remaining D2xxx
if (/^D2/.test(c))
return "filling";
// Orthodontics: D8xxx
if (/^D8/.test(c))
return "ortho";
// Recalls & New Patients: D0xxx (exams, x-rays), D1xxx (preventive)
if (/^D[01]/.test(c))
return "recall";
return null;
}
export function inferTypeFromProcedureCodes(codes) {
if (!codes.length)
return null;
const scores = {};
for (const code of codes) {
const t = codeToType(code);
if (t)
scores[t] = (scores[t] ?? 0) + 1;
}
if (!Object.keys(scores).length)
return null;
// Priority order: most specialized/dominant type wins on ties
const priority = [
"endo", "implant", "crown", "pedo", "dentures",
"extraction", "perio", "filling", "ortho",
"recall", "consultation", "emergency",
];
let best = null;
let bestCount = 0;
for (const type of priority) {
const count = scores[type] ?? 0;
if (count > bestCount) {
best = type;
bestCount = count;
}
}
return best;
}