Files
scoreodonto.com/frontend/views/TutoriaSection.tsx
T
VPS 4 Builder ba062e92d0 fix(login): render standalone views edge-to-edge e esconde scrollbar do carrossel de onboarding
- App.tsx: wrappers de padding (p-4/md:p-8) e max-w-7xl/mx-auto agora só se aplicam quando não é tela standalone; login/landing/public renderizam full-bleed
- OnboardingChat.tsx: classe ob-noscroll esconde a barra de rolagem do slide intro (mantém scroll), substituindo a custom-scrollbar indefinida no fluxo de login

Inclui também demais alterações pendentes do working tree (snapshot do estado atual de dev).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 13:23:19 +02:00

570 lines
26 KiB
TypeScript

import React, { useState, useEffect, useCallback, useRef } from 'react';
import {
GraduationCap, Plus, X, Send, Clock, CheckCircle2,
MessageSquare, ChevronDown, ChevronUp, Loader2, User, Camera,
ShieldAlert, DollarSign, Info
} from 'lucide-react';
import { HybridBackend } from '../services/backend.ts';
import { useToast } from '../contexts/ToastContext.tsx';
// ─── Tipos ────────────────────────────────────────────────────────
interface Tutor {
id: string;
nome: string;
especialidade?: string;
bio?: string;
preco_base?: number;
sla_horas?: number;
}
interface Mensagem {
id: string;
request_id: string;
autor_tipo: 'CLINICA' | 'TUTOR';
autor_nome?: string;
conteudo: string;
created_at: string;
}
interface Solicitacao {
id: string;
tratamento_id: string;
tutor_id: string;
tutor_nome?: string;
tutor_especialidade?: string;
tutor_bio?: string;
sla_horas?: number;
tipo: string;
descricao?: string;
paciente_nome?: string;
status: string;
created_at: string;
mensagens?: Mensagem[];
fotos?: { id: string; url: string; slot: string }[];
}
// ─── Helpers ──────────────────────────────────────────────────────
const TIPO_LABELS: Record<string, string> = {
SEGUNDA_OPINIAO: 'SEGUNDA OPINIÃO',
PLANEJAMENTO_COMPLETO: 'PLANEJAMENTO COMPLETO',
DISCUSSAO_CASO: 'DISCUSSÃO DE CASO',
ALINHADORES: 'ALINHADORES',
ORTO_CIRURGICO: 'ORTO-CIRÚRGICO',
ENCAMINHAMENTO: 'ENCAMINHAMENTO',
};
const STATUS_STYLE: Record<string, { cls: string; label: string }> = {
AGUARDANDO: { cls: 'bg-amber-100 text-amber-700', label: 'AGUARDANDO' },
EM_ANALISE: { cls: 'bg-blue-100 text-blue-700', label: 'EM ANÁLISE' },
RESPONDIDO: { cls: 'bg-green-100 text-green-700', label: 'RESPONDIDO' },
FECHADO: { cls: 'bg-gray-100 text-gray-500', label: 'FECHADO' },
};
// ─── Chat inline ──────────────────────────────────────────────────
const TutorChat: React.FC<{
solicitacao: Solicitacao;
autorNome: string;
onUpdated: () => void;
}> = ({ solicitacao, autorNome, onUpdated }) => {
const toast = useToast();
const [msgs, setMsgs] = useState<Mensagem[]>(solicitacao.mensagens || []);
const [texto, setTexto] = useState('');
const [enviando, setEnviando] = useState(false);
const bottomRef = useRef<HTMLDivElement>(null);
useEffect(() => { bottomRef.current?.scrollIntoView({ behavior: 'smooth' }); }, [msgs]);
const send = async () => {
const txt = texto.trim();
if (!txt) return;
setEnviando(true);
try {
const res = await HybridBackend.sendMensagemTutoria(solicitacao.id, txt, 'CLINICA', autorNome);
if (!res.success) { toast.error('ERRO AO ENVIAR.'); return; }
setMsgs(prev => [...prev, res.mensagem]);
setTexto('');
onUpdated();
} catch { toast.error('ERRO AO ENVIAR.'); }
finally { setEnviando(false); }
};
return (
<div className="flex flex-col gap-2">
<div className="max-h-52 overflow-y-auto space-y-2 pr-1">
{msgs.length === 0 ? (
<p className="text-[10px] text-gray-400 font-bold uppercase text-center py-4">NENHUMA MENSAGEM AINDA INICIE A CONVERSA</p>
) : msgs.map(m => (
<div key={m.id} className={`flex ${m.autor_tipo === 'CLINICA' ? 'justify-end' : 'justify-start'}`}>
<div className={`max-w-[80%] px-3 py-2 rounded-xl text-xs font-medium ${
m.autor_tipo === 'CLINICA'
? 'bg-blue-600 text-white rounded-br-none'
: 'bg-gray-100 text-gray-800 rounded-bl-none'
}`}>
{m.autor_nome && (
<p className={`text-[9px] font-black uppercase mb-0.5 ${m.autor_tipo === 'CLINICA' ? 'text-blue-200' : 'text-gray-400'}`}>
{m.autor_nome}
</p>
)}
<p>{m.conteudo}</p>
<p className={`text-[9px] mt-1 ${m.autor_tipo === 'CLINICA' ? 'text-blue-200' : 'text-gray-400'}`}>
{new Date(m.created_at).toLocaleTimeString('pt-BR', { hour: '2-digit', minute: '2-digit' })}
</p>
</div>
</div>
))}
<div ref={bottomRef} />
</div>
{solicitacao.status !== 'FECHADO' && (
<div className="flex gap-2">
<input
type="text"
value={texto}
onChange={e => setTexto(e.target.value)}
onKeyDown={e => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); send(); } }}
placeholder="MENSAGEM PARA O TUTOR..."
className="flex-1 border border-gray-200 rounded-xl px-3 py-2 text-xs font-medium focus:outline-none focus:border-blue-400 uppercase placeholder:normal-case"
/>
<button
onClick={send}
disabled={enviando || !texto.trim()}
className="px-3 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-xl disabled:opacity-40 transition-colors flex items-center gap-1"
>
{enviando ? <Loader2 size={14} className="animate-spin" /> : <Send size={14} />}
</button>
</div>
)}
</div>
);
};
// ─── Card de solicitação ──────────────────────────────────────────
const SolicitacaoCard: React.FC<{
sol: Solicitacao;
autorNome: string;
onUpdated: () => void;
}> = ({ sol, autorNome, onUpdated }) => {
const [open, setOpen] = useState(false);
const [detail, setDetail] = useState<Solicitacao | null>(null);
const [loadingDetail, setLoadingDetail] = useState(false);
const st = STATUS_STYLE[sol.status] || STATUS_STYLE.AGUARDANDO;
const loadDetail = useCallback(async () => {
if (detail) return;
setLoadingDetail(true);
try {
const res = await HybridBackend.getSolicitacaoTutoria(sol.id);
if (res.success) setDetail(res.solicitacao);
} catch {}
finally { setLoadingDetail(false); }
}, [sol.id, detail]);
const toggle = () => {
if (!open) loadDetail();
setOpen(!open);
};
const refreshDetail = async () => {
const res = await HybridBackend.getSolicitacaoTutoria(sol.id);
if (res.success) setDetail(res.solicitacao);
onUpdated();
};
return (
<div className="border border-gray-200 rounded-xl overflow-hidden bg-white">
{/* Header clicável */}
<button onClick={toggle} className="w-full flex items-center justify-between px-4 py-3 hover:bg-gray-50 transition-colors text-left gap-3">
<div className="flex items-center gap-3 min-w-0 flex-1">
<div className="w-9 h-9 bg-indigo-100 rounded-xl flex items-center justify-center flex-shrink-0">
<GraduationCap size={16} className="text-indigo-600" />
</div>
<div className="min-w-0 flex-1">
<p className="font-black text-sm text-gray-800 truncate">{sol.tutor_nome || 'TUTOR'}</p>
<p className="text-[10px] text-gray-500 font-bold uppercase">{TIPO_LABELS[sol.tipo] || sol.tipo}</p>
</div>
</div>
<div className="flex items-center gap-2 flex-shrink-0">
<span className={`text-[9px] font-black px-2 py-0.5 rounded-full uppercase ${st.cls}`}>{st.label}</span>
{open ? <ChevronUp size={16} className="text-gray-400" /> : <ChevronDown size={16} className="text-gray-400" />}
</div>
</button>
{/* Expandido */}
{open && (
<div className="px-4 pb-4 border-t border-gray-100 space-y-3 pt-3">
{/* Info do tutor */}
<div className="grid grid-cols-2 gap-2 text-xs">
{sol.tutor_especialidade && (
<div><p className="text-[9px] font-black text-gray-400 uppercase">ESPECIALIDADE</p><p className="font-bold text-gray-700">{sol.tutor_especialidade}</p></div>
)}
{sol.sla_horas && (
<div><p className="text-[9px] font-black text-gray-400 uppercase">SLA</p><p className="font-bold text-gray-700">ATÉ {sol.sla_horas}H</p></div>
)}
{sol.descricao && (
<div className="col-span-2"><p className="text-[9px] font-black text-gray-400 uppercase">DESCRIÇÃO</p><p className="font-medium text-gray-700">{sol.descricao}</p></div>
)}
</div>
{/* Fotos do pacote */}
{loadingDetail ? (
<div className="flex items-center justify-center py-4"><Loader2 size={20} className="animate-spin text-gray-300" /></div>
) : detail?.fotos && detail.fotos.length > 0 && (
<div>
<p className="text-[9px] font-black text-gray-400 uppercase mb-2">FOTOS INCLUÍDAS ({detail.fotos.length})</p>
<div className="flex gap-1.5 flex-wrap">
{detail.fotos.slice(0, 8).map(f => (
<img key={f.id} src={f.url} alt={f.slot} className="w-10 h-10 object-cover rounded-lg border border-gray-200" />
))}
{detail.fotos.length > 8 && (
<div className="w-10 h-10 bg-gray-100 rounded-lg border border-gray-200 flex items-center justify-center">
<span className="text-[9px] font-black text-gray-500">+{detail.fotos.length - 8}</span>
</div>
)}
</div>
</div>
)}
{/* Chat */}
{detail && (
<TutorChat
solicitacao={detail}
autorNome={autorNome}
onUpdated={refreshDetail}
/>
)}
</div>
)}
</div>
);
};
// ─── Modal de aceite de responsabilidade ─────────────────────────
const TEXTO_RESPONSABILIDADE_DEFAULT = `Ao solicitar esta tutoria, o profissional solicitante declara expressamente que:
• É o RESPONSÁVEL LEGAL pelo tratamento do paciente e por todas as decisões clínicas relacionadas.
• O parecer emitido pelo tutor tem caráter estritamente CONSULTIVO e NÃO VINCULANTE.
• A decisão de executar ou não as recomendações do tutor compete EXCLUSIVAMENTE ao profissional tratante, que deve avaliá-las com base em seu conhecimento clínico e nas condições específicas do paciente.
• O tutor NÃO ASSUME qualquer responsabilidade sobre a execução do tratamento, os resultados obtidos ou quaisquer complicações decorrentes da aplicação ou não das recomendações emitidas.
• Este termo é um registro formal do entendimento mútuo sobre os limites e a natureza consultiva do serviço de tutoria.`;
const AceiteModal: React.FC<{
pacienteNome: string;
autorNome: string;
textoCustom?: string;
onAceitar: (nome: string) => void;
onCancelar: () => void;
}> = ({ pacienteNome, autorNome, textoCustom, onAceitar, onCancelar }) => {
const [nome, setNome] = useState(autorNome);
const [aceito, setAceito] = useState(false);
const texto = textoCustom || TEXTO_RESPONSABILIDADE_DEFAULT;
return (
<div className="fixed inset-0 bg-black/70 z-[60] flex items-center justify-center p-4 backdrop-blur-sm">
<div className="bg-white rounded-2xl shadow-2xl w-full max-w-lg max-h-[90vh] flex flex-col">
<div className="flex items-center gap-3 px-6 py-4 border-b border-gray-100 flex-shrink-0">
<div className="w-9 h-9 bg-amber-100 rounded-xl flex items-center justify-center flex-shrink-0">
<ShieldAlert size={18} className="text-amber-600" />
</div>
<div>
<h3 className="font-black text-sm uppercase text-gray-800">TERMO DE RESPONSABILIDADE CLÍNICA</h3>
<p className="text-[10px] text-gray-400 font-bold uppercase">PACIENTE: {pacienteNome}</p>
</div>
</div>
<div className="flex-1 overflow-y-auto p-6 space-y-4 min-h-0">
<div className="bg-amber-50 border border-amber-200 rounded-xl p-4">
<p className="text-[11px] text-amber-800 font-medium leading-relaxed whitespace-pre-line">{texto}</p>
</div>
<div>
<label className="text-[10px] font-black text-gray-400 uppercase block mb-1">SEU NOME COMPLETO (PARA REGISTRO) *</label>
<input type="text" value={nome} onChange={e => setNome(e.target.value.toUpperCase())}
className="w-full border border-gray-200 rounded-xl px-3 py-2.5 text-sm font-bold focus:outline-none focus:border-amber-400 uppercase" />
</div>
<label className="flex items-start gap-3 p-3 bg-gray-50 border-2 border-gray-200 rounded-xl cursor-pointer hover:border-amber-300 transition-colors"
onClick={() => setAceito(!aceito)}>
<div className={`w-5 h-5 rounded border-2 flex items-center justify-center flex-shrink-0 mt-0.5 transition-all ${aceito ? 'bg-amber-500 border-amber-500' : 'border-gray-300'}`}>
{aceito && <CheckCircle2 size={12} className="text-white" />}
</div>
<p className="text-[11px] font-black text-gray-700 uppercase leading-relaxed">
LI, COMPREENDI E CONCORDO COM OS TERMOS ACIMA. ESTOU CIENTE DE QUE SOU O RESPONSÁVEL PELO TRATAMENTO E PELAS DECISÕES CLÍNICAS DO PACIENTE.
</p>
</label>
</div>
<div className="px-6 pb-6 flex gap-3 flex-shrink-0 border-t border-gray-100 pt-4">
<button onClick={onCancelar} className="flex-1 py-2.5 border border-gray-200 rounded-xl text-[11px] font-black uppercase hover:bg-gray-50 transition-colors">
CANCELAR
</button>
<button onClick={() => onAceitar(nome)} disabled={!aceito || !nome.trim()}
className="flex-1 py-2.5 bg-amber-600 text-white rounded-xl text-[11px] font-black uppercase hover:bg-amber-700 transition-colors disabled:opacity-40 flex items-center justify-center gap-2">
<CheckCircle2 size={13} /> CONFIRMAR E PROSSEGUIR
</button>
</div>
</div>
</div>
);
};
// ─── Modal de nova solicitação ────────────────────────────────────
const NovoModal: React.FC<{
tratamentoId: string;
clinicaId?: string;
pacienteNome: string;
autorNome: string;
onClose: () => void;
onCreated: () => void;
}> = ({ tratamentoId, clinicaId, pacienteNome, autorNome, onClose, onCreated }) => {
const toast = useToast();
const [tutores, setTutores] = useState<Tutor[]>([]);
const [tutorId, setTutorId] = useState('');
const [tipo, setTipo] = useState('SEGUNDA_OPINIAO');
const [descricao, setDescricao] = useState('');
const [loading, setLoading] = useState(true);
const [salvando, setSalvando] = useState(false);
const [showAceite, setShowAceite] = useState(false);
const [cfgTexto, setCfgTexto] = useState('');
useEffect(() => {
Promise.all([
HybridBackend.getTutores().then(r => { if (r.success) setTutores(r.tutores || []); }),
HybridBackend.getAdminTutorConfig().then(r => { if (r.success) setCfgTexto(r.config?.texto_responsabilidade || ''); }),
]).finally(() => setLoading(false));
}, []);
const tutorSelecionado = tutores.find(t => t.id === tutorId);
const handleSubmitComAceite = async (nomeAceite: string) => {
setShowAceite(false);
setSalvando(true);
try {
const res = await HybridBackend.createSolicitacaoTutoria({
tratamento_id: tratamentoId,
clinica_id: clinicaId,
tutor_id: tutorId,
tipo,
descricao: descricao.toUpperCase() || undefined,
paciente_nome: pacienteNome,
aceite_responsabilidade: true,
aceite_nome: nomeAceite,
aceite_texto_versao: 'v1',
});
if (!res.success) { toast.error(res.message || 'ERRO.'); return; }
toast.success(`TUTORIA SOLICITADA! ${res.fotos_incluidas} FOTOS INCLUÍDAS NO PACOTE.`);
onCreated();
} catch { toast.error('ERRO AO SOLICITAR TUTORIA.'); }
finally { setSalvando(false); }
};
return (
<>
<div className="fixed inset-0 bg-black/60 z-50 flex items-center justify-center p-4 backdrop-blur-sm">
<div className="bg-white rounded-2xl shadow-2xl w-full max-w-lg max-h-[90vh] flex flex-col">
<div className="flex items-center justify-between px-6 py-4 border-b border-gray-100 flex-shrink-0">
<div className="flex items-center gap-2">
<GraduationCap size={18} className="text-indigo-500" />
<h3 className="font-black text-sm uppercase text-gray-800">SOLICITAR TUTORIA</h3>
</div>
<button onClick={onClose} className="p-1.5 hover:bg-gray-100 rounded-lg"><X size={18} /></button>
</div>
<div className="flex-1 overflow-y-auto p-6 space-y-4 min-h-0">
{/* Paciente */}
<div className="bg-indigo-50 border border-indigo-100 rounded-xl p-3 flex items-center gap-2">
<User size={14} className="text-indigo-500" />
<span className="text-[11px] font-black text-indigo-700 uppercase">PACIENTE: {pacienteNome}</span>
</div>
{/* Tutor */}
<div>
<label className="text-[10px] font-black text-gray-400 uppercase block mb-1">TUTOR ORTODÔNTICO *</label>
{loading ? (
<div className="flex items-center gap-2 py-2 text-gray-400 text-xs"><Loader2 size={14} className="animate-spin" /> CARREGANDO TUTORES...</div>
) : tutores.length === 0 ? (
<p className="text-xs text-red-500 font-bold uppercase">NENHUM TUTOR DISPONÍVEL CADASTRADO.</p>
) : (
<div className="space-y-2">
{tutores.map(t => (
<label key={t.id} className={`flex items-start gap-3 p-3 rounded-xl border-2 cursor-pointer transition-all ${tutorId === t.id ? 'border-indigo-500 bg-indigo-50' : 'border-gray-200 hover:border-indigo-200'}`}>
<input type="radio" name="tutor" value={t.id} checked={tutorId === t.id} onChange={() => setTutorId(t.id)} className="mt-0.5" />
<div className="flex-1">
<p className="font-black text-sm text-gray-800">{t.nome}</p>
{t.especialidade && <p className="text-[10px] text-gray-500 font-bold uppercase">{t.especialidade}</p>}
<div className="flex gap-3 mt-1 text-[10px] font-bold text-gray-400 uppercase flex-wrap">
{t.sla_horas && <span className="flex items-center gap-1"><Clock size={10} /> RESP. EM {t.sla_horas}H</span>}
{t.preco_base && (
<span className="flex items-center gap-1 text-indigo-600">
<DollarSign size={10} /> R$ {Number(t.preco_base).toLocaleString('pt-BR', { minimumFractionDigits: 2 })} / CONSULTA
</span>
)}
</div>
{t.bio && <p className="text-[10px] text-gray-500 mt-1 italic">{t.bio}</p>}
</div>
</label>
))}
</div>
)}
</div>
{/* Preço estimado */}
{tutorSelecionado?.preco_base && (
<div className="bg-green-50 border border-green-100 rounded-xl p-3 flex items-center gap-2">
<DollarSign size={14} className="text-green-600 flex-shrink-0" />
<div>
<p className="text-[11px] font-black text-green-700 uppercase">
VALOR DA CONSULTORIA: R$ {Number(tutorSelecionado.preco_base).toLocaleString('pt-BR', { minimumFractionDigits: 2 })}
</p>
<p className="text-[9px] text-green-600 font-bold uppercase">PAGAMENTO COMBINADO DIRETAMENTE COM O TUTOR APÓS RESPOSTA</p>
</div>
</div>
)}
{/* Tipo */}
<div>
<label className="text-[10px] font-black text-gray-400 uppercase block mb-1">TIPO DE TUTORIA *</label>
<select value={tipo} onChange={e => setTipo(e.target.value)}
className="w-full border border-gray-200 rounded-xl px-3 py-2 text-sm font-bold focus:outline-none focus:border-indigo-400 uppercase">
{Object.entries(TIPO_LABELS).map(([k, v]) => <option key={k} value={k}>{v}</option>)}
</select>
</div>
{/* Descrição */}
<div>
<label className="text-[10px] font-black text-gray-400 uppercase block mb-1">DESCRIÇÃO DO CASO</label>
<textarea value={descricao} onChange={e => setDescricao(e.target.value.toUpperCase())}
placeholder="DESCREVA SUAS DÚVIDAS OU O QUE PRECISA DE APOIO..."
className="w-full border border-gray-200 rounded-xl px-3 py-2 text-xs font-medium h-20 resize-none focus:outline-none focus:border-indigo-400 uppercase placeholder:normal-case" />
</div>
{/* Aviso pacote */}
<div className="bg-amber-50 border border-amber-100 rounded-xl p-3 flex items-start gap-2">
<Camera size={14} className="text-amber-600 flex-shrink-0 mt-0.5" />
<p className="text-[10px] text-amber-700 font-bold uppercase leading-relaxed">
O SISTEMA VAI INCLUIR AUTOMATICAMENTE TODAS AS FOTOS DO TRATAMENTO NO PACOTE ENVIADO AO TUTOR.
</p>
</div>
{/* Aviso responsabilidade */}
<div className="bg-red-50 border border-red-100 rounded-xl p-3 flex items-start gap-2">
<ShieldAlert size={14} className="text-red-500 flex-shrink-0 mt-0.5" />
<p className="text-[10px] text-red-600 font-bold uppercase leading-relaxed">
AO PROSSEGUIR, VOCÊ DEVERÁ ASSINAR UM TERMO DE RESPONSABILIDADE CONFIRMANDO QUE É O RESPONSÁVEL PELO TRATAMENTO E PELA EXECUÇÃO OU NÃO DAS RECOMENDAÇÕES DO TUTOR.
</p>
</div>
</div>
<div className="px-6 pb-6 flex gap-3 flex-shrink-0 border-t border-gray-100 pt-4">
<button onClick={onClose} className="flex-1 py-2.5 border border-gray-200 rounded-xl text-[11px] font-black uppercase hover:bg-gray-50 transition-colors">
CANCELAR
</button>
<button onClick={() => setShowAceite(true)} disabled={salvando || !tutorId}
className="flex-1 py-2.5 bg-indigo-600 text-white rounded-xl text-[11px] font-black uppercase hover:bg-indigo-700 transition-colors disabled:opacity-40 flex items-center justify-center gap-2">
{salvando ? <><Loader2 size={13} className="animate-spin" /> ENVIANDO...</> : <><ShieldAlert size={13} /> PROSSEGUIR E ASSINAR TERMO</>}
</button>
</div>
</div>
</div>
{showAceite && (
<AceiteModal
pacienteNome={pacienteNome}
autorNome={autorNome}
textoCustom={cfgTexto}
onAceitar={handleSubmitComAceite}
onCancelar={() => setShowAceite(false)}
/>
)}
</>
);
};
// ─── Componente principal ─────────────────────────────────────────
interface Props {
tratamentoId: string;
clinicaId?: string;
pacienteNome: string;
}
export const TutoriaSection: React.FC<Props> = ({ tratamentoId, clinicaId, pacienteNome }) => {
const [solicitacoes, setSolicitacoes] = useState<Solicitacao[]>([]);
const [loading, setLoading] = useState(true);
const [showModal, setShowModal] = useState(false);
const autorNome = HybridBackend.getCurrentUserName() || 'CLÍNICA';
const load = useCallback(async () => {
try {
const res = await HybridBackend.getSolicitacoesTutoria(clinicaId, tratamentoId);
if (res.success) setSolicitacoes(res.solicitacoes || []);
} catch {}
finally { setLoading(false); }
}, [clinicaId, tratamentoId]);
useEffect(() => { load(); }, [load]);
return (
<div className="bg-white border border-gray-200 rounded-xl shadow-sm overflow-hidden">
{/* Header */}
<div className="px-5 py-4 border-b border-gray-100 bg-gray-50 flex items-center justify-between gap-3">
<div className="flex items-center gap-2">
<GraduationCap size={16} className="text-indigo-500" />
<h3 className="font-black text-gray-700 uppercase text-sm">TUTORIA ORTODÔNTICA</h3>
{solicitacoes.length > 0 && (
<span className="text-[10px] font-black px-2 py-0.5 bg-indigo-100 text-indigo-600 rounded-full">{solicitacoes.length}</span>
)}
</div>
<button
onClick={() => setShowModal(true)}
className="flex items-center gap-1.5 px-3 py-1.5 bg-indigo-600 hover:bg-indigo-700 text-white rounded-xl text-[10px] font-black uppercase transition-colors"
>
<Plus size={13} /> SOLICITAR TUTORIA
</button>
</div>
{/* Conteúdo */}
<div className="p-5">
{loading ? (
<div className="flex items-center justify-center h-20"><Loader2 size={22} className="animate-spin text-gray-300" /></div>
) : solicitacoes.length === 0 ? (
<div className="flex flex-col items-center justify-center py-6 text-gray-400 gap-2">
<GraduationCap size={28} />
<p className="text-xs font-bold uppercase">NENHUMA TUTORIA SOLICITADA</p>
<p className="text-[10px] text-gray-400 text-center max-w-xs">
SOLICITE UMA SEGUNDA OPINIÃO OU MENTORIA DE UM ORTODONTISTA ESPECIALISTA.
</p>
</div>
) : (
<div className="space-y-3">
{solicitacoes.map(s => (
<SolicitacaoCard key={s.id} sol={s} autorNome={autorNome} onUpdated={load} />
))}
</div>
)}
</div>
{/* Modal */}
{showModal && (
<NovoModal
tratamentoId={tratamentoId}
clinicaId={clinicaId}
pacienteNome={pacienteNome}
autorNome={autorNome}
onClose={() => setShowModal(false)}
onCreated={() => { setShowModal(false); load(); }}
/>
)}
</div>
);
};