From b6b408e41e56be412cab283b1b1a89a9f7ca4d52 Mon Sep 17 00:00:00 2001 From: Admin Date: Fri, 8 May 2026 12:42:01 +0330 Subject: [PATCH] feature: FDI tooth chart UI finalized. --- .../components/ui/treatment/FdiToothChart.tsx | 25 ++---- .../components/ui/treatment/ToothGlyph.tsx | 77 ++++-------------- .../ui/treatment/fdiChartDesigns.ts | 6 -- .../ui/treatment/toothGlyphPaint.ts | 53 ------------- .../ui/treatment/toothGlyphStructural.ts | 15 ---- .../components/ui/treatment/toothPathModel.ts | 2 +- .../ui/treatment/toothPathModelTextbook.ts | 78 ------------------- 7 files changed, 22 insertions(+), 234 deletions(-) delete mode 100644 frontend/src/components/ui/treatment/fdiChartDesigns.ts delete mode 100644 frontend/src/components/ui/treatment/toothGlyphPaint.ts delete mode 100644 frontend/src/components/ui/treatment/toothGlyphStructural.ts delete mode 100644 frontend/src/components/ui/treatment/toothPathModelTextbook.ts diff --git a/frontend/src/components/ui/treatment/FdiToothChart.tsx b/frontend/src/components/ui/treatment/FdiToothChart.tsx index 20fdfae..5857f48 100644 --- a/frontend/src/components/ui/treatment/FdiToothChart.tsx +++ b/frontend/src/components/ui/treatment/FdiToothChart.tsx @@ -1,10 +1,8 @@ 'use client'; -import { useId, useState } from 'react'; +import { useId } from 'react'; import { FDI_LOWER_LEFT_TO_RIGHT, FDI_UPPER_LEFT_TO_RIGHT, getToothShapeKind } from '@/components/treatment/fdiToothMeta'; import { ToothGlyph } from '@/components/ui/treatment/ToothGlyph'; -import { Dropdown } from '@/components/ui/common/Dropdown'; -import { FDI_CHART_DESIGNS, type FdiChartDesignId } from '@/components/ui/treatment/fdiChartDesigns'; import type { FdiToothId } from '@/types/treatment'; const TOOTH_NUMBER_GAP = 'mt-1'; @@ -29,10 +27,9 @@ interface FdiToothChartProps { export function FdiToothChart({ selected, onToggle, disabled }: FdiToothChartProps) { const uid = useId().replace(/:/g, ''); const archPeak = 10; - const [chartDesign, setChartDesign] = useState('clinical'); const TOOTH_TWEAKS: Record = { - '18': { glyph: 'w-[1.9rem] h-[4.5rem]', offset: 7, rotate: -11 }, + '18': { glyph: 'w-[1.98rem] h-[4.65rem]', offset: 7, rotate: -11 }, '17': { glyph: 'w-[2rem] h-[4.75rem]', offset: 8, rotate: -8 }, '16': { glyph: 'w-[2.1rem] h-[4.95rem]', offset: 9, rotate: -6 }, '15': { glyph: 'w-[1.8rem] h-[4.8rem]', offset: 9, rotate: -4 }, @@ -47,9 +44,9 @@ export function FdiToothChart({ selected, onToggle, disabled }: FdiToothChartPro '25': { glyph: 'w-[1.8rem] h-[4.8rem]', offset: 9, rotate: 4 }, '26': { glyph: 'w-[2.1rem] h-[4.95rem]', offset: 9, rotate: 6 }, '27': { glyph: 'w-[2rem] h-[4.75rem]', offset: 8, rotate: 8 }, - '28': { glyph: 'w-[1.9rem] h-[4.5rem]', offset: 7, rotate: 11 }, + '28': { glyph: 'w-[1.98rem] h-[4.65rem]', offset: 7, rotate: 11 }, - '48': { glyph: 'w-[1.9rem] h-[4.55rem]', offset: -7, rotate: -11 }, + '48': { glyph: 'w-[1.98rem] h-[4.7rem]', offset: -7, rotate: -11 }, '47': { glyph: 'w-[2rem] h-[4.8rem]', offset: -8, rotate: -8 }, '46': { glyph: 'w-[2.15rem] h-[5rem]', offset: -9, rotate: -6 }, '45': { glyph: 'w-[1.82rem] h-[4.85rem]', offset: -9, rotate: -4 }, @@ -64,7 +61,7 @@ export function FdiToothChart({ selected, onToggle, disabled }: FdiToothChartPro '35': { glyph: 'w-[1.82rem] h-[4.85rem]', offset: -9, rotate: 4 }, '36': { glyph: 'w-[2.15rem] h-[5rem]', offset: -9, rotate: 6 }, '37': { glyph: 'w-[2rem] h-[4.8rem]', offset: -8, rotate: 8 }, - '38': { glyph: 'w-[1.9rem] h-[4.55rem]', offset: -7, rotate: 11 }, + '38': { glyph: 'w-[1.98rem] h-[4.7rem]', offset: -7, rotate: 11 }, }; const toothSizeClass = (fdi: FdiToothId) => { @@ -133,7 +130,6 @@ export function FdiToothChart({ selected, onToggle, disabled }: FdiToothChartPro aria-label={`FDI tooth ${fdi}${isSel ? ', selected' : ''}`} >
- setChartDesign(e.target.value as FdiChartDesignId)} - > - {FDI_CHART_DESIGNS.map((d) => ( - - ))} -

Selected: {selected.size === 0 ? '—' : [...selected].sort().join(', ')}

diff --git a/frontend/src/components/ui/treatment/ToothGlyph.tsx b/frontend/src/components/ui/treatment/ToothGlyph.tsx index b933ef6..67e081f 100644 --- a/frontend/src/components/ui/treatment/ToothGlyph.tsx +++ b/frontend/src/components/ui/treatment/ToothGlyph.tsx @@ -3,12 +3,7 @@ import { memo, type ReactNode } from 'react'; import type { FdiToothId } from '@/types/treatment'; import type { ToothShapeKind } from '@/components/treatment/fdiToothMeta'; -import type { FdiChartDesignId } from '@/components/ui/treatment/fdiChartDesigns'; -import { getToothGlyphPaint } from '@/components/ui/treatment/toothGlyphPaint'; -import type { ToothGlyphPaint } from '@/components/ui/treatment/toothGlyphPaint'; import { getToothPathModel, type ToothPathModel } from '@/components/ui/treatment/toothPathModel'; -import { getTextbookToothPathModel } from '@/components/ui/treatment/toothPathModelTextbook'; -import { wireframeInnerScale, wireframeOriginY } from '@/components/ui/treatment/toothGlyphStructural'; interface ToothGlyphProps { fdi: FdiToothId; @@ -19,7 +14,6 @@ interface ToothGlyphProps { mirrored?: boolean; upsideDown?: boolean; className?: string; - variant?: FdiChartDesignId; } function filledSilhouette( @@ -45,49 +39,18 @@ function detailOnly(model: ToothPathModel, stroke: string, strokeW: number): Rea return ; } -function renderClinicalFill(model: ToothPathModel, paint: ToothGlyphPaint): ReactNode { - const { crownFill, rootFill, stroke, strokeW } = paint; +function renderClinicalFill(model: ToothPathModel, selected: boolean, gradientId: string): ReactNode { + const stroke = selected ? 'var(--color-primary)' : '#1e293b'; + const strokeW = selected ? 2.4 : 1.35; + const fill = `url(#${gradientId})`; return ( <> - {filledSilhouette(model, crownFill, rootFill, stroke, strokeW)} + {filledSilhouette(model, fill, fill, stroke, strokeW)} {detailOnly(model, stroke, strokeW)} ); } - -function renderWireframe(model: ToothPathModel, paint: ToothGlyphPaint, kind: ToothShapeKind): ReactNode { - const { stroke, strokeW } = paint; - const outer = paint.wireOuter ?? '#f8fafc'; - const innerCol = paint.wireInner ?? '#64748b'; - const s = wireframeInnerScale(kind); - const crownOy = wireframeOriginY('crown', kind); - const rootOy = wireframeOriginY('root', kind); - const rootScale = (i: number) => s - (model.roots.length > 1 ? i * 0.018 : 0); - - return ( - <> - - - - - - - - {model.roots.map((d, i) => ( - - - - - - - - ))} - {detailOnly(model, stroke, strokeW)} - - ); -} - -/** Clinical uses FDI-specific paths; wireframe uses textbook silhouettes + line sculpture (original combo). */ +/** FDI chart tooth: clinical gradient + roots only. */ export const ToothGlyph = memo(function ToothGlyph({ fdi, kind, @@ -97,15 +60,9 @@ export const ToothGlyph = memo(function ToothGlyph({ mirrored, upsideDown, className = 'w-8 h-[4.85rem]', - variant = 'clinical', }: ToothGlyphProps) { - const lastDigit = Number(fdi[1]); - const model: ToothPathModel | null = - variant === 'clinical' - ? getToothPathModel(fdi, kind, upper) - : getTextbookToothPathModel(kind, upper, lastDigit); - const paint = getToothGlyphPaint(variant, gradientId, selected); - const { showClinicalGradient, filter, structuralMode } = paint; + const model: ToothPathModel | null = getToothPathModel(fdi, kind, upper); + const filter = selected ? 'drop-shadow(0 0 6px rgba(9, 169, 188, 0.65))' : undefined; if (!model) { return ( @@ -117,7 +74,7 @@ export const ToothGlyph = memo(function ToothGlyph({ ); } - const body = structuralMode === 'wireframe' ? renderWireframe(model, paint, kind) : renderClinicalFill(model, paint); + const body = renderClinicalFill(model, selected, gradientId); return ( - {showClinicalGradient && ( - - - - - - - - )} + + + + + + + = 6 && lastDigit <= 8; - if (upperMolar) { - return { - crown, - roots: [ - 'M8.5 32.5 L12 32.5 L10.5 54 L9 71 L7 69.5 L8.5 54 Z', - 'M15.5 32.5 L20.5 32.5 L19 56 L18 72 L16.5 72 L15.5 56 Z', - 'M24 32.5 L27.5 32.5 L27.5 54 L29 71 L27 69.5 L26 54 Z', - ], - detailStroke: { - d: 'M11 10.5 Q18 12.5 25 10.5 M10 18 Q18 20 26 18', - widthScale: 0.55, - }, - }; - } - return { - crown, - roots: [ - 'M9 32.5 L15 32.5 L13 56 L10.5 71 L8 69.5 L10 56 Z', - 'M21 32.5 L27 32.5 L26 56 L27.5 71 L25 69.5 L24 56 Z', - ], - detailStroke: { - d: 'M12 11 Q18 13.5 24 11 M11 19 Q18 21.5 25 19 M16 62 Q18 65 20 62', - widthScale: 0.5, - }, - }; - } - default: - return { crown: 'M10 10 L26 10 L26 60 L10 60 Z', roots: [] }; - } -}