Spaces:
Sleeping
Sleeping
Commit ·
76265b8
1
Parent(s): 03a23e0
✨ Implement Interactive Dental Odontogram FDI Chart
Browse files
toothmap_frontend/src/App.jsx
CHANGED
|
@@ -16,6 +16,9 @@ const MODES = [
|
|
| 16 |
// Distinct colours for FDI quadrants 1-4
|
| 17 |
const QUADRANT_COLOURS = ['#3b82f6', '#22c55e', '#f59e0b', '#ef4444'];
|
| 18 |
|
|
|
|
|
|
|
|
|
|
| 19 |
function getColour(fdi) {
|
| 20 |
if (!fdi || fdi < 0) return '#a855f7';
|
| 21 |
const quad = Math.floor(fdi / 10) - 1;
|
|
@@ -230,6 +233,51 @@ export default function App() {
|
|
| 230 |
)}
|
| 231 |
</div>
|
| 232 |
)}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 233 |
</aside>
|
| 234 |
|
| 235 |
{/* Canvas */}
|
|
|
|
| 16 |
// Distinct colours for FDI quadrants 1-4
|
| 17 |
const QUADRANT_COLOURS = ['#3b82f6', '#22c55e', '#f59e0b', '#ef4444'];
|
| 18 |
|
| 19 |
+
const UPPER_TEETH = [18, 17, 16, 15, 14, 13, 12, 11, 21, 22, 23, 24, 25, 26, 27, 28];
|
| 20 |
+
const LOWER_TEETH = [48, 47, 46, 45, 44, 43, 42, 41, 31, 32, 33, 34, 35, 36, 37, 38];
|
| 21 |
+
|
| 22 |
function getColour(fdi) {
|
| 23 |
if (!fdi || fdi < 0) return '#a855f7';
|
| 24 |
const quad = Math.floor(fdi / 10) - 1;
|
|
|
|
| 233 |
)}
|
| 234 |
</div>
|
| 235 |
)}
|
| 236 |
+
|
| 237 |
+
{/* Dental FDI Chart (Odontogram) */}
|
| 238 |
+
{(pipelineResults && mode.includes('pipeline')) && (
|
| 239 |
+
<div className="bg-zinc-900/60 rounded-xl p-3 border border-zinc-800/80 shadow-inner">
|
| 240 |
+
<p className="text-[10px] font-bold text-slate-400 uppercase tracking-widest mb-3 text-center">FDI Dental Chart</p>
|
| 241 |
+
<div className="flex flex-col gap-1.5">
|
| 242 |
+
{/* Upper Row */}
|
| 243 |
+
<div className="flex w-full justify-between gap-0.5">
|
| 244 |
+
{UPPER_TEETH.map((t, idx) => {
|
| 245 |
+
const isLeftQuad = idx < 8; // patient's right is visually on the left
|
| 246 |
+
return (
|
| 247 |
+
<div key={t}
|
| 248 |
+
className={`flex-1 aspect-[2/3] rounded-[2px] flex items-center justify-center text-[9px] font-bold transition-all ${
|
| 249 |
+
pipelineResults.some(r => r.fdi === t)
|
| 250 |
+
? 'bg-green-500/20 text-green-400 border border-green-500/40 shadow-[0_0_8px_rgba(34,197,94,0.15)]'
|
| 251 |
+
: 'bg-zinc-800 text-zinc-600 border border-zinc-700/40 opacity-70'
|
| 252 |
+
} ${isLeftQuad && idx === 7 ? 'mr-1.5' : ''}`}>
|
| 253 |
+
{t}
|
| 254 |
+
</div>
|
| 255 |
+
)})}
|
| 256 |
+
</div>
|
| 257 |
+
{/* Divider */}
|
| 258 |
+
<div className="h-px w-full bg-zinc-800/80" />
|
| 259 |
+
{/* Lower Row */}
|
| 260 |
+
<div className="flex w-full justify-between gap-0.5">
|
| 261 |
+
{LOWER_TEETH.map((t, idx) => {
|
| 262 |
+
const isLeftQuad = idx < 8;
|
| 263 |
+
return (
|
| 264 |
+
<div key={t}
|
| 265 |
+
className={`flex-1 aspect-[2/3] rounded-[2px] flex items-center justify-center text-[9px] font-bold transition-all ${
|
| 266 |
+
pipelineResults.some(r => r.fdi === t)
|
| 267 |
+
? 'bg-green-500/20 text-green-400 border border-green-500/40 shadow-[0_0_8px_rgba(34,197,94,0.15)]'
|
| 268 |
+
: 'bg-zinc-800 text-zinc-600 border border-zinc-700/40 opacity-70'
|
| 269 |
+
} ${isLeftQuad && idx === 7 ? 'mr-1.5' : ''}`}>
|
| 270 |
+
{t}
|
| 271 |
+
</div>
|
| 272 |
+
)})}
|
| 273 |
+
</div>
|
| 274 |
+
</div>
|
| 275 |
+
<div className="flex justify-center gap-4 mt-3 text-[10px] text-zinc-500">
|
| 276 |
+
<div className="flex items-center gap-1.5"><span className="w-2.5 h-2.5 rounded-sm bg-green-500/20 border border-green-500/40"></span> Present</div>
|
| 277 |
+
<div className="flex items-center gap-1.5"><span className="w-2.5 h-2.5 rounded-sm bg-zinc-800 border border-zinc-700/40"></span> Missing</div>
|
| 278 |
+
</div>
|
| 279 |
+
</div>
|
| 280 |
+
)}
|
| 281 |
</aside>
|
| 282 |
|
| 283 |
{/* Canvas */}
|