import React, { useState, useEffect, useCallback, useRef } from 'react'; import { X, Plus, Stethoscope, ShieldCheck, RotateCcw, Trash2, RefreshCw, Camera, ImagePlus, Save, } from 'lucide-react'; import { HybridBackend } from '../services/backend.ts'; import { useToast } from '../contexts/ToastContext.tsx'; import { useConfirm } from '../contexts/ConfirmContext.tsx'; const COLOR = '#0d9488'; const inputCls = 'w-full px-3 py-2.5 bg-gray-50 border border-gray-200 rounded-xl text-sm font-medium text-gray-700 outline-none focus:border-teal-400 focus:bg-white transition-colors'; const labelCls = 'text-[11px] font-bold text-gray-500 mb-1.5 block'; const TIPO_META: Record = { novo: { label: 'Novo', cls: 'bg-teal-100 text-teal-700' }, reabertura: { label: 'Reabertura', cls: 'bg-amber-100 text-amber-700' }, garantia: { label: 'Garantia', cls: 'bg-violet-100 text-violet-700' }, }; const fmtBRL = (v: number) => Number(v || 0).toLocaleString('pt-BR', { style: 'currency', currency: 'BRL' }); // ─── Slot de foto (antes/depois) ────────────────────────────────────────────── const FotoSlot: React.FC<{ url?: string; label: string; onPick: (f: File) => void; busy?: boolean }> = ({ url, label, onPick, busy }) => { const ref = useRef(null); return (

{label}

{ const f = e.target.files?.[0]; if (f) onPick(f); e.currentTarget.value = ''; }} />
); }; // ─── Card de procedimento realizado (busca o detalhe p/ fotos) ──────────────── const CardProc: React.FC<{ id: string; onRefazer: (r: any) => void; onExcluir: (id: string) => void; reloadKey: number }> = ({ id, onRefazer, onExcluir, reloadKey }) => { const toast = useToast(); const [r, setR] = useState(null); const [busy, setBusy] = useState<'antes' | 'depois' | null>(null); const carregar = useCallback(() => HybridBackend.getProcedimentoRealizado(id).then(setR), [id]); useEffect(() => { carregar(); }, [carregar, reloadKey]); const fotoDe = (m: string) => (r?.fotos || []).find((f: any) => f.momento === m)?.url; const enviar = async (momento: 'antes' | 'depois', file: File) => { setBusy(momento); try { await HybridBackend.uploadProcedimentoFoto(id, momento, file); toast.success('FOTO ENVIADA.'); await carregar(); } catch (e: any) { toast.error((e.message || 'ERRO').toUpperCase()); } finally { setBusy(null); } }; if (!r) return
; const meta = TIPO_META[r.tipo] || TIPO_META.novo; return (

{r.procedimento_nome || 'Procedimento'}

{r.profissional_nome || 'Profissional'} · {new Date(r.created_at).toLocaleDateString('pt-BR')}

{meta.label} {r.sem_comissao ? Sem comissão : Number(r.valor) > 0 && {fmtBRL(r.valor)}}
enviar('antes', f)} busy={busy === 'antes'} /> enviar('depois', f)} busy={busy === 'depois'} />
{r.tipo === 'novo' && ( )}
); }; // ─── Modal principal ────────────────────────────────────────────────────────── export const ProcedimentosProntuario: React.FC<{ patient: { id: string; nome: string }; onClose: () => void }> = ({ patient, onClose }) => { const toast = useToast(); const confirm = useConfirm(); const [lista, setLista] = useState([]); const [dentistas, setDentistas] = useState([]); const [loading, setLoading] = useState(false); const [reloadKey, setReloadKey] = useState(0); const [form, setForm] = useState({ procedimento_nome: '', profissional_id: '', valor: '', data_conclusao: new Date().toISOString().split('T')[0] }); const [saving, setSaving] = useState(false); const carregar = useCallback(async () => { setLoading(true); try { const [ls, ds] = await Promise.all([HybridBackend.getProcedimentosRealizados(patient.id), HybridBackend.getDentistas()]); setLista(ls); setDentistas(Array.isArray(ds) ? ds : []); } catch { /* silent */ } finally { setLoading(false); } }, [patient.id]); useEffect(() => { carregar(); }, [carregar]); const refresh = () => { carregar(); setReloadKey(k => k + 1); }; const registrar = async () => { if (!form.procedimento_nome.trim()) { toast.error('INFORME O PROCEDIMENTO.'); return; } setSaving(true); try { const dent = dentistas.find(d => d.id === form.profissional_id); await HybridBackend.saveProcedimentoRealizado({ paciente_id: patient.id, paciente_nome: patient.nome, profissional_id: form.profissional_id || null, profissional_nome: dent?.nome || null, procedimento_nome: form.procedimento_nome, valor: form.valor === '' ? 0 : Number(form.valor), tipo: 'novo', data_conclusao: form.data_conclusao || null, }); toast.success('PROCEDIMENTO REGISTRADO.'); setForm(f => ({ procedimento_nome: '', profissional_id: '', valor: '', data_conclusao: f.data_conclusao })); refresh(); } catch (e: any) { toast.error((e.message || 'ERRO').toUpperCase()); } finally { setSaving(false); } }; const refazerGarantia = async (r: any) => { if (!await confirm(`REFAZER "${r.procedimento_nome}" na garantia? (sem comissão — registre as fotos antes/depois)`)) return; try { await HybridBackend.saveProcedimentoRealizado({ paciente_id: patient.id, paciente_nome: patient.nome, profissional_id: r.profissional_id, profissional_nome: r.profissional_nome, procedimento_nome: `${r.procedimento_nome} (garantia)`, tipo: 'garantia', origem_id: r.id, valor: 0, }); toast.success('GARANTIA REGISTRADA — ANEXE AS FOTOS ANTES/DEPOIS.'); refresh(); } catch (e: any) { toast.error((e.message || 'ERRO').toUpperCase()); } }; const excluir = async (id: string) => { if (!await confirm('EXCLUIR este procedimento?')) return; try { await HybridBackend.deleteProcedimentoRealizado(id); toast.success('EXCLUÍDO.'); refresh(); } catch (e: any) { toast.error((e.message || 'ERRO').toUpperCase()); } }; return (

Procedimentos · Prontuário

{patient.nome}

{/* Registrar */}
setForm(f => ({ ...f, procedimento_nome: e.target.value }))} placeholder="Ex.: Restauração 26" />
setForm(f => ({ ...f, valor: e.target.value }))} placeholder="0,00" />
setForm(f => ({ ...f, data_conclusao: e.target.value }))} />

Reabertura/garantia pelo mesmo profissional não gera comissão.

{/* Lista */}
{loading ?
: lista.length === 0 ?

Nenhum procedimento registrado

:
{lista.map(r => )}
}
); };