import React, { useEffect, useRef, useState } from 'react'; import { X, Camera, Trash2, Check, Loader2, Save, ClipboardList, Image as ImageIcon } from 'lucide-react'; import { HybridBackend } from '../services/backend.ts'; import { useToast } from '../contexts/ToastContext.tsx'; // ── Odontograma FDI (dentição permanente) ─────────────────────────────────── const UP_RIGHT = ['18', '17', '16', '15', '14', '13', '12', '11']; const UP_LEFT = ['21', '22', '23', '24', '25', '26', '27', '28']; const LO_RIGHT = ['48', '47', '46', '45', '44', '43', '42', '41']; const LO_LEFT = ['31', '32', '33', '34', '35', '36', '37', '38']; type StatusKey = 'higido' | 'carie' | 'restaurado' | 'ausente' | 'coroa' | 'tratamento' | 'implante' | 'fratura'; const STATUS: Record = { higido: { label: 'HÍGIDO', cor: '#94a3b8', bg: '#f8fafc' }, carie: { label: 'CÁRIE', cor: '#dc2626', bg: '#fef2f2' }, restaurado: { label: 'RESTAURADO', cor: '#2563eb', bg: '#eff6ff' }, ausente: { label: 'AUSENTE', cor: '#1f2937', bg: '#f3f4f6' }, coroa: { label: 'COROA', cor: '#d97706', bg: '#fffbeb' }, tratamento: { label: 'TRAT. CANAL',cor: '#7c3aed', bg: '#f5f3ff' }, implante: { label: 'IMPLANTE', cor: '#0d9488', bg: '#f0fdfa' }, fratura: { label: 'FRATURA', cor: '#0d9488', bg: '#fff7ed' }, }; const STATUS_ORDER: StatusKey[] = ['higido', 'carie', 'restaurado', 'tratamento', 'coroa', 'implante', 'fratura', 'ausente']; interface ToothState { status?: StatusKey; nota?: string; } type Odontograma = Record; const Tooth: React.FC<{ num: string; st?: ToothState; selected: boolean; onClick: () => void }> = ({ num, st, selected, onClick }) => { const cfg = st?.status ? STATUS[st.status] : null; return ( ); }; interface Props { agendamentoId?: string; avaliacaoId?: string; pacienteNome?: string; onClose: () => void; onSaved?: () => void; } export const AvaliacaoEditor: React.FC = ({ agendamentoId, avaliacaoId, pacienteNome, onClose, onSaved }) => { const toast = useToast(); const [loading, setLoading] = useState(true); const [saving, setSaving] = useState(false); const [aval, setAval] = useState(null); const [odonto, setOdonto] = useState({}); const [obs, setObs] = useState(''); const [sel, setSel] = useState(null); const [fotos, setFotos] = useState([]); const [uploading, setUploading] = useState(false); const fileRef = useRef(null); useEffect(() => { (async () => { setLoading(true); let a: any = null; if (avaliacaoId) a = await HybridBackend.getAvaliacao(avaliacaoId); else if (agendamentoId) a = await HybridBackend.getAvaliacaoDoAgendamento(agendamentoId); setAval(a); setOdonto((a?.odontograma && typeof a.odontograma === 'object') ? a.odontograma : {}); setObs(a?.observacoes || ''); setFotos(a?.fotos || []); setLoading(false); })(); }, [agendamentoId, avaliacaoId]); const setToothStatus = (status: StatusKey) => { if (!sel) return; setOdonto(prev => ({ ...prev, [sel]: { ...prev[sel], status: prev[sel]?.status === status ? undefined : status } })); }; const setToothNota = (nota: string) => { if (!sel) return; setOdonto(prev => ({ ...prev, [sel]: { ...prev[sel], nota } })); }; const persist = async (status?: string) => { if (!aval?.id) { toast.error('AVALIAÇÃO NÃO ENCONTRADA.'); return; } setSaving(true); const r = await HybridBackend.salvarAvaliacao(aval.id, { odontograma: odonto, observacoes: obs, ...(status ? { status } : {}) }); setSaving(false); if (r?.success) { toast.success(status === 'concluida' ? 'AVALIAÇÃO CONCLUÍDA.' : 'AVALIAÇÃO SALVA.'); onSaved?.(); if (status === 'concluida') onClose(); } else { toast.error('ERRO AO SALVAR AVALIAÇÃO.'); } }; const onPick = async (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (!file || !aval?.id) return; setUploading(true); const r = await HybridBackend.uploadAvaliacaoFoto(aval.id, file, sel || undefined); setUploading(false); if (fileRef.current) fileRef.current.value = ''; if (r?.success && r.foto) { setFotos(prev => [...prev, r.foto]); toast.success('FOTO ENVIADA.'); } else toast.error('ERRO AO ENVIAR FOTO.'); }; const delFoto = async (id: string) => { const r = await HybridBackend.deleteAvaliacaoFoto(id); if (r?.success) setFotos(prev => prev.filter(f => f.id !== id)); }; const selSt = sel ? odonto[sel] : undefined; const totalMarcados = Object.values(odonto).filter(t => t?.status).length; return (
e.stopPropagation()}> {/* header */}

Avaliação clínica

{pacienteNome || aval?.paciente_nome || '—'}

{loading ? (
) : !aval ? (

Nenhuma avaliação vinculada a este agendamento.

) : (
{/* Odontograma */}

Odontograma · {totalMarcados} marcados

{UP_RIGHT.map(n => setSel(n)} />)}
{UP_LEFT.map(n => setSel(n)} />)}
{LO_RIGHT.map(n => setSel(n)} />)}
{LO_LEFT.map(n => setSel(n)} />)}
{/* Painel do dente selecionado */} {sel ? (

Dente {sel}

{STATUS_ORDER.map(k => { const active = selSt?.status === k; return ( ); })}
setToothNota(e.target.value)} placeholder="Observação do dente (opcional)" className="w-full px-3 py-2 rounded-lg border border-gray-200 text-xs focus:outline-none focus:border-teal-400" />
) : (

Toque em um dente para marcar o estado.

)} {/* Observações gerais */}

Observações gerais