'use client'; import { useId } from 'react'; import { useTranslations } from 'next-intl'; import { FDI_LOWER_LEFT_TO_RIGHT, FDI_UPPER_LEFT_TO_RIGHT, getToothShapeKind } from '@/components/treatment/fdiToothMeta'; import { ToothGlyph } from '@/components/ui/treatment/ToothGlyph'; import type { FdiToothId } from '@/types/treatment'; const TOOTH_NUMBER_GAP = 'mt-1'; /** Six teeth each side of the midline (anteriors): slightly larger glyph + wrapper. */ const MIDLINE_SIX = new Set([ '13', '12', '11', '21', '22', '23', '43', '42', '41', '31', '32', '33', ]); function quadrantMirrored(fdi: FdiToothId): boolean { const q = fdi[0]; return q === '2' || q === '3'; } interface FdiToothChartProps { selected: ReadonlySet; onToggle: (fdi: FdiToothId) => void; disabled?: boolean; } export function FdiToothChart({ selected, onToggle, disabled }: FdiToothChartProps) { const t = useTranslations('treatment'); const uid = useId().replace(/:/g, ''); const archPeak = 10; const TOOTH_TWEAKS: Record = { '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 }, '14': { glyph: 'w-[1.8rem] h-[4.85rem]', offset: 9, rotate: -2 }, '13': { glyph: 'w-[1.9rem] h-[5.28rem]', offset: 8, rotate: -1 }, '12': { glyph: 'w-[1.8rem] h-[5.02rem]', offset: 7, rotate: -1 }, '11': { glyph: 'w-[1.8rem] h-[5.08rem]', offset: 7, rotate: -0.5 }, '21': { glyph: 'w-[1.8rem] h-[5.08rem]', offset: 7, rotate: 0.5 }, '22': { glyph: 'w-[1.8rem] h-[5.02rem]', offset: 7, rotate: 1 }, '23': { glyph: 'w-[1.9rem] h-[5.28rem]', offset: 8, rotate: 1 }, '24': { glyph: 'w-[1.8rem] h-[4.85rem]', offset: 9, rotate: 2 }, '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.98rem] h-[4.65rem]', 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 }, '44': { glyph: 'w-[1.82rem] h-[4.9rem]', offset: -9, rotate: -2 }, '43': { glyph: 'w-[1.88rem] h-[5.34rem]', offset: -8, rotate: -1 }, '42': { glyph: 'w-[1.76rem] h-[5.04rem]', offset: -7, rotate: -1 }, '41': { glyph: 'w-[1.74rem] h-[4.98rem]', offset: -7, rotate: -0.5 }, '31': { glyph: 'w-[1.74rem] h-[4.98rem]', offset: -7, rotate: 0.5 }, '32': { glyph: 'w-[1.76rem] h-[5.04rem]', offset: -7, rotate: 1 }, '33': { glyph: 'w-[1.88rem] h-[5.34rem]', offset: -8, rotate: 1 }, '34': { glyph: 'w-[1.82rem] h-[4.9rem]', offset: -9, rotate: 2 }, '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.98rem] h-[4.7rem]', offset: -7, rotate: 11 }, }; const toothSizeClass = (fdi: FdiToothId) => { const kind = getToothShapeKind(fdi); const mid = MIDLINE_SIX.has(fdi); switch (kind) { case 'incisor': return mid ? { wrapper: 'w-[2.1rem]', glyph: 'w-[1.68rem] h-[4.78rem]' } : { wrapper: 'w-[2rem]', glyph: 'w-[1.6rem] h-[4.6rem]' }; case 'canine': return mid ? { wrapper: 'w-[2.2rem]', glyph: 'w-[1.78rem] h-[5.05rem]' } : { wrapper: 'w-[2.1rem]', glyph: 'w-[1.7rem] h-[4.9rem]' }; case 'premolar': return mid ? { wrapper: 'w-[2.34rem]', glyph: 'w-[1.93rem] h-[5.02rem]' } : { wrapper: 'w-[2.25rem]', glyph: 'w-[1.85rem] h-[4.85rem]' }; case 'molar': return mid ? { wrapper: 'w-[2.52rem]', glyph: 'w-[2.12rem] h-[5.1rem]' } : { wrapper: 'w-[2.45rem]', glyph: 'w-[2.05rem] h-[4.95rem]' }; default: return { wrapper: 'w-[2.25rem]', glyph: 'w-[1.85rem] h-[4.85rem]' }; } }; const archOffset = (index: number, count: number, upper?: boolean) => { const center = (count - 1) / 2; const dist = Math.abs(index - center) / center; const curve = (1 - Math.pow(dist, 1.45)) * archPeak; return upper ? curve : -curve; }; const archRotate = (index: number, count: number) => { const center = (count - 1) / 2; const normalized = (index - center) / center; return normalized * 6; }; const Row = ({ teeth, upper }: { teeth: FdiToothId[]; upper?: boolean }) => (
{teeth.map((fdi, i) => { const kind = getToothShapeKind(fdi); const gid = `${uid}-g-${fdi}-${i}`; const isSel = selected.has(fdi); const size = toothSizeClass(fdi); const tweak = TOOTH_TWEAKS[fdi]; const offsetY = tweak ? tweak.offset : archOffset(i, teeth.length, upper); const rotate = tweak ? tweak.rotate : archRotate(i, teeth.length); const alignItems = upper ? 'items-end' : 'items-start'; return (
); })}
); return (

{t('toothChartTitle')}

{t('toothChartHint')}

{t('selectedLabel')} {selected.size === 0 ? t('selectedEmpty') : [...selected].sort().join(', ')}

{t('upperArch')}

{FDI_UPPER_LEFT_TO_RIGHT.map((fdi) => { const size = toothSizeClass(fdi); const isSel = selected.has(fdi); return ( {fdi} ); })}
{FDI_LOWER_LEFT_TO_RIGHT.map((fdi) => { const size = toothSizeClass(fdi); const isSel = selected.has(fdi); return ( {fdi} ); })}

{t('lowerArch')}

); }