/* eslint-disable */
const { useState, useEffect, useMemo, useCallback } = React;
const Recharts = window.Recharts || {};
const { LineChart, Line, BarChart, Bar, XAxis, YAxis, Tooltip, ResponsiveContainer, CartesianGrid, Legend } = Recharts;
const ChartsAvailable = !!(LineChart && BarChart);
const API = ""; // same origin
// ---------- helpers ----------
const fmt$ = (n) => {
if (n == null || isNaN(n)) return "—";
if (n === 0) return "$0";
return "$" + Math.round(n).toLocaleString();
};
const fmt$Compact = (n) => {
if (n == null || isNaN(n)) return "—";
if (Math.abs(n) >= 1_000_000) return "$" + (n / 1_000_000).toFixed(1) + "M";
if (Math.abs(n) >= 1_000) return "$" + (n / 1_000).toFixed(1) + "k";
return "$" + n;
};
const fmtDate = (s) => s ? new Date(s + "T00:00:00").toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" }) : "—";
const fmtDateShort = (s) => s ? new Date(s + "T00:00:00").toLocaleDateString("en-US", { month: "short", day: "numeric" }) : "—";
const todayISO = () => new Date().toISOString().split("T")[0];
const daysFromToday = (iso) => {
if (!iso) return null;
const d = new Date(iso + "T00:00:00");
const t = new Date(); t.setHours(0,0,0,0);
return Math.round((d - t) / (1000 * 60 * 60 * 24));
};
const apiGet = async (path) => {
const r = await fetch(API + path);
if (!r.ok) throw new Error(`${r.status} ${r.statusText}`);
return r.json();
};
const apiSend = async (path, method, body) => {
const r = await fetch(API + path, {
method,
headers: { "Content-Type": "application/json" },
body: body ? JSON.stringify(body) : undefined,
});
if (!r.ok) throw new Error(`${r.status} ${r.statusText}`);
return r.json();
};
// ---------- root app ----------
function App() {
const [page, setPage] = useState("dashboard");
const [dashboard, setDashboard] = useState(null);
const [stages, setStages] = useState([]);
const [coordinators, setCoordinators] = useState([]);
const [toast, setToast] = useState(null);
const [activePatientId, setActivePatientId] = useState(null);
const [showAddPatient, setShowAddPatient] = useState(false);
const loadDashboard = useCallback(() => apiGet("/api/dashboard").then(setDashboard).catch(() => {}), []);
useEffect(() => {
loadDashboard();
apiGet("/api/stages").then(setStages).catch(() => {});
apiGet("/api/lookups/coordinators").then(setCoordinators).catch(() => {});
}, [loadDashboard]);
const showToast = (msg) => {
setToast(msg);
setTimeout(() => setToast(null), 2400);
};
const refreshAll = useCallback(() => {
loadDashboard();
apiGet("/api/lookups/coordinators").then(setCoordinators).catch(() => {});
}, [loadDashboard]);
return (
{page === "dashboard" && (
setShowAddPatient(true)}
refresh={refreshAll}
/>
)}
{page === "follow-ups" && (
)}
{page === "patients" && (
setShowAddPatient(true)}
/>
)}
{page === "kpis" && }
{page === "settings" && apiGet("/api/stages").then(setStages)} showToast={showToast} />}
{activePatientId && (
setActivePatientId(null)}
onUpdate={() => { refreshAll(); }}
showToast={showToast}
/>
)}
{showAddPatient && (
setShowAddPatient(false)}
onSaved={() => { setShowAddPatient(false); refreshAll(); showToast("Patient added"); }}
/>
)}
{toast && {toast}
}
);
}
// ---------- sidebar ----------
function Sidebar({ page, setPage, overdueCount, dueTodayCount }) {
const items = [
{ id: "dashboard", label: "Dashboard", section: "Today" },
{ id: "follow-ups", label: "Follow-ups", badge: overdueCount + dueTodayCount, section: "Today" },
{ id: "patients", label: "All Consultations", section: "Pipeline" },
{ id: "kpis", label: "Performance", section: "Pipeline" },
{ id: "settings", label: "Settings", section: "Configure" },
];
let lastSection = null;
return (
The Implant Center
Treatment Tracker
{items.map((item) => {
const sectionHeader = item.section !== lastSection;
lastSection = item.section;
return (
{sectionHeader && {item.section}
}
setPage(item.id)}>
{item.label}
{item.badge > 0 && {item.badge} }
);
})}
Built for the TC team — focus on the follow-ups, the production follows.
);
}
// ---------- Dashboard ----------
function Dashboard({ dashboard, stages, onPatientClick, onAddPatient, refresh }) {
const [period, setPeriod] = useState("month");
const [kpi, setKpi] = useState(null);
useEffect(() => {
apiGet("/api/kpis?period=" + period).then(setKpi).catch(() => {});
}, [period]);
if (!dashboard) return Loading dashboard…
;
return (
<>
Today, {fmtDate(dashboard.today)}
Today's work
+ New Consultation
{/* Period selector + KPIs */}
Performance — {period}
{["day", "week", "month", "quarter", "year"].map((p) => (
setPeriod(p)}>{p}
))}
{/* Follow-up panels */}
{dashboard.upcoming_7_days.length} patients
>
);
}
function KPICards({ kpi }) {
if (!kpi) return null;
return (
Consultations
{kpi.consults}
{kpi.accepted_count} accepted
Treatment Presented
$ {Math.round(kpi.presented).toLocaleString()}
avg case {fmt$Compact(kpi.average_case_size_presented)}
Treatment Accepted
$ {Math.round(kpi.accepted_dollars).toLocaleString()}
avg accepted {fmt$Compact(kpi.average_case_size_accepted)}
Case Acceptance
{kpi.case_acceptance_rate.toFixed(0)}%
${(kpi.dollar_acceptance_rate).toFixed(0)}% by dollars
Collected Today
$ {Math.round(kpi.paid_collected).toLocaleString()}
deposits + payments
);
}
function FollowUpPanel({ title, subtitle, patients, chip, empty, onPatientClick }) {
return (
{patients.length === 0 ? (
) : (
{patients.slice(0, 12).map((p) => (
onPatientClick(p.id)}
style={{
padding: "12px 0",
borderBottom: "1px solid var(--line-soft)",
cursor: "pointer",
display: "flex",
justifyContent: "space-between",
alignItems: "center",
}}
>
{p.name}
{p.stage || "—"}·
{fmt$Compact(p.treatment_presented)} presented
{p.treatment_coordinator && <>· {p.treatment_coordinator}>}
{chip === "overdue" ? `${Math.abs(daysFromToday(p.next_follow_up_date))}d late` : "today"}
))}
)}
);
}
function StageBreakdown({ breakdown, stages }) {
const stageMap = Object.fromEntries(stages.map(s => [s.stage_name, s]));
const sorted = [...breakdown].sort((a, b) => b.count - a.count);
const total = sorted.reduce((sum, s) => sum + s.count, 0);
return (
{sorted.map((s) => {
const cfg = stageMap[s.stage];
const pct = total ? (s.count / total) * 100 : 0;
return (
{s.stage}
{cfg?.is_terminal && terminal }
{s.count} · {fmt$Compact(s.presented)}
);
})}
);
}
// ---------- Follow-ups page ----------
function FollowUps({ stages, coordinators, onPatientClick, refresh }) {
const [filter, setFilter] = useState("due");
const [patients, setPatients] = useState([]);
const [tcFilter, setTcFilter] = useState("");
const load = useCallback(() => {
let url = "/api/patients?";
if (filter === "overdue") url += "overdue=true";
if (filter === "due") url += "follow_up_due=true";
if (filter === "upcoming") url += `start_date=${todayISO()}`;
if (tcFilter) url += "&tc=" + encodeURIComponent(tcFilter);
apiGet(url).then(setPatients).catch(() => {});
}, [filter, tcFilter]);
useEffect(() => { load(); }, [load]);
return (
<>
Follow-up queue
Patients waiting to hear from you
setFilter("due")}>Due now
setFilter("overdue")}>Overdue
setFilter("upcoming")}>Upcoming
setTcFilter(e.target.value)}>
All coordinators
{coordinators.map(c => {c} )}
>
);
}
// ---------- Patients list ----------
function PatientsList({ stages, coordinators, onPatientClick, onAddPatient }) {
const [patients, setPatients] = useState([]);
const [search, setSearch] = useState("");
const [stage, setStage] = useState("");
const [tc, setTc] = useState("");
const [accepted, setAccepted] = useState("");
const load = useCallback(() => {
const params = new URLSearchParams();
if (search) params.set("search", search);
if (stage) params.set("stage", stage);
if (tc) params.set("tc", tc);
if (accepted === "yes") params.set("accepted", "true");
if (accepted === "no") params.set("accepted", "false");
params.set("limit", "1000");
apiGet("/api/patients?" + params.toString()).then(setPatients).catch(() => {});
}, [search, stage, tc, accepted]);
useEffect(() => {
const t = setTimeout(load, 200);
return () => clearTimeout(t);
}, [load]);
return (
<>
{patients.length} records
All consultations
setSearch(e.target.value)} style={{ minWidth: 220 }} />
setStage(e.target.value)}>
All stages
{stages.map(s => {s.stage_name} )}
setTc(e.target.value)}>
All TCs
{coordinators.map(c => {c} )}
setAccepted(e.target.value)}>
All outcomes
Accepted
Not accepted
>
);
}
// ---------- Reusable patient table ----------
function PatientTable({ patients, onPatientClick, showFollowUp, empty }) {
if (!patients || patients.length === 0) {
return ∅
{empty || "No patients."}
;
}
return (
Consult
Patient
Coordinator
Stage
Presented
Accepted
{showFollowUp && Next Follow-up }
{patients.map((p) => {
const fuDays = daysFromToday(p.next_follow_up_date);
let chipClass = "chip-neutral";
if (p.is_accepted) chipClass = "chip-accepted";
else if (p.closed_lost) chipClass = "chip-firm";
else if (fuDays != null && fuDays < 0) chipClass = "chip-overdue";
else if (fuDays === 0) chipClass = "chip-due";
else if (fuDays != null && fuDays > 0) chipClass = "chip-upcoming";
return (
onPatientClick(p.id)}>
{fmtDateShort(p.consult_date)}
{p.name}
{p.referral_source && {p.referral_source}
}
{p.treatment_coordinator || "—"}
{p.stage ? {p.stage} : — }
{fmt$Compact(p.treatment_presented)}
{p.is_accepted ? fmt$Compact(p.treatment_accepted) : "—"}
{showFollowUp && (
{p.next_follow_up_date ? (
{fmtDateShort(p.next_follow_up_date)}
{fuDays != null && (
{fuDays < 0 ? `${Math.abs(fuDays)}d overdue` : fuDays === 0 ? "today" : `in ${fuDays}d`}
)}
) : — }
)}
);
})}
);
}
// ---------- Patient detail modal ----------
function PatientDetailModal({ patientId, stages, onClose, onUpdate, showToast }) {
const [patient, setPatient] = useState(null);
const [editing, setEditing] = useState(false);
const [logging, setLogging] = useState(false);
const load = useCallback(() => {
apiGet(`/api/patients/${patientId}`).then(setPatient).catch(() => {});
}, [patientId]);
useEffect(() => { load(); }, [load]);
if (!patient) return null;
const fuDays = daysFromToday(patient.next_follow_up_date);
return (
e.stopPropagation()}>
{fmtDate(patient.consult_date)} consult
{patient.name}
✕
{patient.stage && {patient.stage} }
{patient.referral_source && via {patient.referral_source} }
{patient.new_patient && New patient }
setLogging(true)}>+ Log contact
setEditing(!editing)}>
{editing ? "Cancel edit" : "Edit details"}
{editing ? (
{ setEditing(false); load(); onUpdate(); showToast("Patient updated"); }} />
) : (
<>
{patient.notes && (
<>
Consultation Notes
{patient.notes}
>
)}
Contact History ({patient.contacts.length})
{patient.contacts.length === 0 ? (
No contacts logged yet.
) : patient.contacts.map((c) => (
{c.contact_type}
{c.outcome && · {c.outcome} }
{c.contact_date ? new Date(c.contact_date).toLocaleString("en-US", { month: "short", day: "numeric", year: "numeric", hour: "numeric", minute: "2-digit" }) : "—"}
{c.note &&
{c.note}
}
{c.new_stage &&
→ Stage: {c.new_stage}
}
{c.by_user &&
by {c.by_user}
}
))}
>
)}
Snapshot
Treatment Presented {fmt$(patient.treatment_presented)}
Treatment Accepted {patient.is_accepted ? fmt$(patient.treatment_accepted) : "—"}
Paid Today {fmt$(patient.amount_paid_today)}
Records Date {fmtDateShort(patient.records_date)}
Coordinator {patient.treatment_coordinator || "—"}
Assistant {patient.assistant || "—"}
Follow-up
Next Follow-up
{patient.next_follow_up_date ? (
<>
{fmtDateShort(patient.next_follow_up_date)}
{fuDays != null &&
{fuDays < 0 ? `${Math.abs(fuDays)}d overdue` : fuDays === 0 ? "today" : `in ${fuDays}d`}
}
>
) : "—"}
Last Contact {fmtDateShort(patient.last_contact_date)}
{logging && (
setLogging(false)}
onSaved={() => { setLogging(false); load(); onUpdate(); showToast("Contact logged"); }}
/>
)}
);
}
function PatientEditForm({ patient, stages, onSaved }) {
const [form, setForm] = useState({
consult_date: patient.consult_date || "",
name: patient.name || "",
referral_source: patient.referral_source || "",
treatment_coordinator: patient.treatment_coordinator || "",
assistant: patient.assistant || "",
treatment_presented: patient.treatment_presented || 0,
records_date: patient.records_date || "",
amount_paid_today: patient.amount_paid_today || 0,
treatment_accepted: patient.treatment_accepted || 0,
stage: patient.stage || "",
next_follow_up_date: patient.next_follow_up_date || "",
phone: patient.phone || "",
email: patient.email || "",
notes: patient.notes || "",
});
const set = (k, v) => setForm({ ...form, [k]: v });
const save = async () => {
const body = { ...form };
body.treatment_presented = parseFloat(body.treatment_presented) || 0;
body.treatment_accepted = parseFloat(body.treatment_accepted) || 0;
body.amount_paid_today = parseFloat(body.amount_paid_today) || 0;
if (!body.records_date) body.records_date = null;
if (!body.next_follow_up_date) body.next_follow_up_date = null;
await apiSend(`/api/patients/${patient.id}`, "PATCH", body);
onSaved();
};
return (
Consult Date set("consult_date", e.target.value)} />
Name set("name", e.target.value)} />
Referral Source set("referral_source", e.target.value)} />
Coordinator set("treatment_coordinator", e.target.value)} />
Assistant set("assistant", e.target.value)} />
Stage
set("stage", e.target.value)}>
— none —
{stages.map(s => {s.stage_name} )}
Treatment Presented ($) set("treatment_presented", e.target.value)} />
Treatment Accepted ($) set("treatment_accepted", e.target.value)} />
Records Date set("records_date", e.target.value)} />
Paid Today ($) set("amount_paid_today", e.target.value)} />
Next Follow-up set("next_follow_up_date", e.target.value)} />
Phone set("phone", e.target.value)} />
Email set("email", e.target.value)} />
Notes
Save changes
);
}
function LogContactModal({ patient, stages, onClose, onSaved }) {
const [form, setForm] = useState({
contact_type: "call",
outcome: "",
note: "",
new_stage: patient.stage || "",
next_follow_up_date: "",
by_user: "",
});
const set = (k, v) => setForm({ ...form, [k]: v });
// When stage changes, suggest a follow-up date
useEffect(() => {
const cfg = stages.find(s => s.stage_name === form.new_stage);
if (cfg && !cfg.is_terminal && cfg.follow_up_days > 0) {
const d = new Date();
d.setDate(d.getDate() + cfg.follow_up_days);
set("next_follow_up_date", d.toISOString().split("T")[0]);
} else if (cfg?.is_terminal) {
set("next_follow_up_date", "");
}
// eslint-disable-next-line
}, [form.new_stage]);
const save = async () => {
const body = { ...form };
if (!body.next_follow_up_date) body.next_follow_up_date = null;
if (!body.new_stage) body.new_stage = null;
await apiSend(`/api/patients/${patient.id}/contacts`, "POST", body);
onSaved();
};
return (
e.stopPropagation()}>
Log contact
{patient.name}
✕
Contact Type
set("contact_type", e.target.value)}>
Phone Call
Voicemail
Text Message
Email
In Person
Other
Outcome
set("outcome", e.target.value)}>
—
Answered / connected
Left voicemail
No answer
Said they'd call back
Needs more time
Declined
Scheduled appointment
Accepted treatment
Move to Stage
set("new_stage", e.target.value)}>
— keep current —
{stages.map(s => {s.stage_name} )}
Next Follow-up Date
set("next_follow_up_date", e.target.value)} />
Notes
Logged by (your name)
set("by_user", e.target.value)} />
Cancel
Save contact
);
}
// ---------- Add patient ----------
function AddPatientModal({ stages, coordinators, onClose, onSaved }) {
const [form, setForm] = useState({
consult_date: todayISO(),
name: "",
referral_source: "",
new_patient: true,
treatment_coordinator: "",
assistant: "",
treatment_presented: 0,
records_date: "",
amount_paid_today: 0,
treatment_accepted: 0,
stage: "First Follow Up",
phone: "",
email: "",
notes: "",
});
const set = (k, v) => setForm({ ...form, [k]: v });
const save = async () => {
if (!form.name || !form.consult_date) {
alert("Name and consult date are required.");
return;
}
const body = { ...form };
body.treatment_presented = parseFloat(body.treatment_presented) || 0;
body.treatment_accepted = parseFloat(body.treatment_accepted) || 0;
body.amount_paid_today = parseFloat(body.amount_paid_today) || 0;
if (!body.records_date) body.records_date = null;
await apiSend("/api/patients", "POST", body);
onSaved();
};
return (
e.stopPropagation()}>
New consultation
Add patient
✕
Consult Date * set("consult_date", e.target.value)} />
Patient Name * set("name", e.target.value)} placeholder="J. Smith" />
Referral Source set("referral_source", e.target.value)} placeholder="VDA, Google, Referral…" />
Coordinator
set("treatment_coordinator", e.target.value)} />
{coordinators.map(c => )}
Assistant set("assistant", e.target.value)} />
Initial Stage
set("stage", e.target.value)}>
{stages.map(s => {s.stage_name} )}
Treatment Presented ($) set("treatment_presented", e.target.value)} />
Treatment Accepted ($) set("treatment_accepted", e.target.value)} placeholder="0 if not accepted today" />
Records Date set("records_date", e.target.value)} />
Paid Today ($) set("amount_paid_today", e.target.value)} />
Phone set("phone", e.target.value)} />
Email set("email", e.target.value)} />
Notes
Cancel
Save consultation
);
}
// ---------- KPIs / charts page ----------
function KPIs() {
const [period, setPeriod] = useState("year");
const [kpi, setKpi] = useState(null);
const [series, setSeries] = useState([]);
useEffect(() => {
apiGet("/api/kpis?period=" + period).then(setKpi).catch(() => {});
}, [period]);
useEffect(() => {
apiGet("/api/kpis/timeseries?granularity=month&months_back=12").then(setSeries).catch(() => {});
}, []);
const chartData = series.map(s => ({
period: new Date(s.period + "T00:00:00").toLocaleDateString("en-US", { month: "short", year: "2-digit" }),
Presented: Math.round(s.presented),
Accepted: Math.round(s.accepted),
Consults: s.consults,
AcceptedCount: s.accepted_count,
}));
return (
<>
Performance
By the numbers
{["day", "week", "month", "quarter", "year", "all"].map(p => (
setPeriod(p)}>{p}
))}
{!ChartsAvailable && (
∅
Charts library couldn't load. KPI numbers above are accurate — refresh the page to retry charts.
)}
{ChartsAvailable && (
<>
Last 12 months
Presented vs. Accepted
"$" + (v / 1000).toFixed(0) + "k"} />
"$" + v.toLocaleString()} contentStyle={{ background: "var(--paper)", border: "1px solid var(--line)", borderRadius: 6, fontSize: 12 }} />
Volume
Consults & acceptance count
>
)}
>
);
}
// ---------- Settings ----------
function Settings({ stages, reloadStages, showToast }) {
const [edits, setEdits] = useState({});
useEffect(() => {
setEdits(Object.fromEntries(stages.map(s => [s.id, s.follow_up_days])));
}, [stages]);
const save = async (id) => {
await apiSend(`/api/stages/${id}`, "PATCH", { follow_up_days: parseInt(edits[id]) || 0 });
await reloadStages();
showToast("Cadence updated");
};
return (
<>
Configure
Follow-up cadence
Per-stage rules
When should the TC follow up?
When a patient is moved to a stage, the next follow-up date is automatically set to today + N days based on these values. Terminal stages (accepted, declined, do-not-contact) don't trigger follow-ups.
Coming soon
Open Dental integration
Once Open Dental's API is connected, the system will reconcile accepted treatment plans against actual production, surface drop-offs between consultation and procedure, and pull patient phone/email automatically. Endpoints are stubbed — wire up OPEN_DENTAL_BASE_URL and OPEN_DENTAL_API_KEY when ready.
>
);
}
ReactDOM.createRoot(document.getElementById("root")).render( );