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>
This commit is contained in:
VPS 4 Builder
2026-06-13 13:23:19 +02:00
parent ae16a9b333
commit ba062e92d0
65 changed files with 20893 additions and 1974 deletions
+507
View File
@@ -0,0 +1,507 @@
import React, { useState, useEffect, useCallback } from 'react';
import {
GraduationCap, CheckCircle2, XCircle, Clock, Settings,
DollarSign, Users, RefreshCw, Loader2, Eye, CreditCard,
AlertCircle, ChevronDown, ChevronUp, Banknote, Key, Percent, FileText
} from 'lucide-react';
import { HybridBackend } from '../services/backend.ts';
import { useToast } from '../contexts/ToastContext.tsx';
import { PageHeader } from '../components/PageHeader.tsx';
// ─── Status ───────────────────────────────────────────────────────
const STATUS_STYLE: Record<string, { cls: string; label: string; icon: React.ReactNode }> = {
PENDENTE: { cls: 'bg-amber-100 text-amber-700', label: 'PENDENTE', icon: <Clock size={12} /> },
AGUARDANDO_PAGAMENTO:{ cls: 'bg-blue-100 text-blue-700', label: 'AGUARD. PAGAMENTO', icon: <CreditCard size={12} /> },
ATIVO: { cls: 'bg-green-100 text-green-700', label: 'ATIVO', icon: <CheckCircle2 size={12} /> },
REJEITADO: { cls: 'bg-red-100 text-red-700', label: 'REJEITADO', icon: <XCircle size={12} /> },
};
const TITULACAO_BADGE: Record<string, string> = {
ESPECIALISTA: 'bg-blue-100 text-blue-700',
MESTRE: 'bg-purple-100 text-purple-700',
DOUTOR: 'bg-indigo-100 text-indigo-700',
PHD: 'bg-violet-100 text-violet-700',
};
// ─── Card de candidatura ──────────────────────────────────────────
const CandidaturaCard: React.FC<{ c: any; onAction: () => void }> = ({ c, onAction }) => {
const toast = useToast();
const [open, setOpen] = useState(false);
const [obs, setObs] = useState('');
const [loading, setLoading] = useState(false);
const st = STATUS_STYLE[c.status] || STATUS_STYLE.PENDENTE;
const action = async (status: string) => {
if (status === 'REJEITADO' && !obs.trim()) {
toast.error('INFORME O MOTIVO DA REJEIÇÃO.');
return;
}
setLoading(true);
try {
const res = await HybridBackend.updateStatusCandidatura(c.id, status, obs || undefined);
if (!res.success) { toast.error(res.message || 'ERRO.'); return; }
toast.success(`CANDIDATURA ${status === 'REJEITADO' ? 'REJEITADA' : 'APROVADA'}!`);
onAction();
} catch { toast.error('ERRO.'); }
finally { setLoading(false); }
};
const confirmarPagamento = async () => {
setLoading(true);
try {
const res = await HybridBackend.confirmarPagamentoCandidatura(c.id);
if (!res.success) { toast.error(res.message || 'ERRO.'); return; }
toast.success('PAGAMENTO CONFIRMADO — TUTOR ATIVADO!');
onAction();
} catch { toast.error('ERRO.'); }
finally { setLoading(false); }
};
return (
<div className="bg-white border border-gray-200 rounded-xl overflow-hidden">
<button onClick={() => setOpen(!open)}
className="w-full flex items-center gap-4 p-4 hover:bg-gray-50 transition-colors text-left">
<div className="w-10 h-10 bg-indigo-100 rounded-full flex items-center justify-center font-black text-indigo-600 flex-shrink-0">
{c.nome?.charAt(0)}
</div>
<div className="flex-1 min-w-0">
<p className="font-black text-sm text-gray-800 truncate uppercase">{c.nome}</p>
<div className="flex items-center gap-2 mt-0.5 flex-wrap">
<p className="text-[10px] text-gray-500 font-bold uppercase">{c.especialidade_principal} CRO {c.cro_uf}/{c.cro}</p>
{c.titulacao && (
<span className={`text-[9px] font-black px-1.5 py-0.5 rounded-full uppercase ${TITULACAO_BADGE[c.titulacao] || 'bg-gray-100 text-gray-600'}`}>{c.titulacao}</span>
)}
</div>
</div>
<div className="flex items-center gap-2 flex-shrink-0">
<span className={`flex items-center gap-1 text-[9px] font-black px-2 py-0.5 rounded-full uppercase ${st.cls}`}>
{st.icon} {st.label}
</span>
<p className="text-[10px] text-gray-400 font-bold hidden sm:block">{new Date(c.created_at).toLocaleDateString()}</p>
{open ? <ChevronUp size={16} className="text-gray-400" /> : <ChevronDown size={16} className="text-gray-400" />}
</div>
</button>
{open && (
<div className="px-4 pb-4 border-t border-gray-100 pt-4 space-y-4">
{/* Dados */}
<div className="grid grid-cols-2 md:grid-cols-3 gap-3 text-xs">
<div><p className="text-[9px] font-black text-gray-400 uppercase">E-MAIL</p><p className="font-bold text-gray-700">{c.email || '—'}</p></div>
<div><p className="text-[9px] font-black text-gray-400 uppercase">TELEFONE</p><p className="font-bold text-gray-700">{c.telefone || '—'}</p></div>
<div><p className="text-[9px] font-black text-gray-400 uppercase">EXPERIÊNCIA</p><p className="font-bold text-gray-700">{c.anos_experiencia ? `${c.anos_experiencia} ANOS` : '—'}</p></div>
<div><p className="text-[9px] font-black text-gray-400 uppercase">INSTITUIÇÃO</p><p className="font-bold text-gray-700">{c.instituicao || '—'}</p></div>
<div><p className="text-[9px] font-black text-gray-400 uppercase">ANO CONCLUSÃO</p><p className="font-bold text-gray-700">{c.ano_conclusao || '—'}</p></div>
<div>
<p className="text-[9px] font-black text-gray-400 uppercase">PREÇO / CONSULTA</p>
<p className="font-black text-indigo-600">{c.preco_por_consulta ? `R$ ${Number(c.preco_por_consulta).toLocaleString('pt-BR', { minimumFractionDigits: 2 })}` : '—'}</p>
</div>
</div>
{c.bio && (
<div className="bg-gray-50 border border-gray-100 rounded-lg p-3">
<p className="text-[9px] font-black text-gray-400 uppercase mb-1">BIO</p>
<p className="text-xs text-gray-600">{c.bio}</p>
</div>
)}
{c.portfolio_url && (
<a href={c.portfolio_url} target="_blank" rel="noopener noreferrer" className="text-[10px] font-bold text-blue-600 underline">PORTFÓLIO </a>
)}
{/* Ações */}
{c.status === 'PENDENTE' && (
<div className="space-y-3 pt-2 border-t border-gray-100">
<textarea value={obs} onChange={e => setObs(e.target.value)}
placeholder="Observação (obrigatória se rejeitar)..."
className="w-full border border-gray-200 rounded-xl px-3 py-2 text-xs font-medium resize-none h-16 focus:outline-none focus:border-indigo-400" />
<div className="flex gap-2">
<button onClick={() => action('AGUARDANDO_PAGAMENTO')} disabled={loading}
className="flex-1 py-2 bg-green-600 hover:bg-green-700 text-white rounded-xl text-[10px] font-black uppercase transition-colors disabled:opacity-40 flex items-center justify-center gap-1">
{loading ? <Loader2 size={12} className="animate-spin" /> : <CheckCircle2 size={12} />} APROVAR
</button>
<button onClick={() => action('REJEITADO')} disabled={loading}
className="flex-1 py-2 bg-red-600 hover:bg-red-700 text-white rounded-xl text-[10px] font-black uppercase transition-colors disabled:opacity-40 flex items-center justify-center gap-1">
{loading ? <Loader2 size={12} className="animate-spin" /> : <XCircle size={12} />} REJEITAR
</button>
</div>
</div>
)}
{c.status === 'AGUARDANDO_PAGAMENTO' && (
<div className="pt-2 border-t border-gray-100">
<p className="text-[10px] text-gray-400 font-bold uppercase mb-2">CONFIRMAR PAGAMENTO RECEBIDO MANUALMENTE</p>
<button onClick={confirmarPagamento} disabled={loading}
className="flex items-center gap-2 px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-xl text-[10px] font-black uppercase transition-colors disabled:opacity-40">
{loading ? <Loader2 size={12} className="animate-spin" /> : <Banknote size={12} />} CONFIRMAR PAGAMENTO
</button>
</div>
)}
{c.observacao_admin && (
<div className="bg-amber-50 border border-amber-100 rounded-lg p-3">
<p className="text-[9px] font-black text-amber-600 uppercase mb-1">OBSERVAÇÃO DO ADMIN</p>
<p className="text-xs text-amber-700">{c.observacao_admin}</p>
</div>
)}
</div>
)}
</div>
);
};
// ─── Aba configurações ────────────────────────────────────────────
const ConfigTab: React.FC = () => {
const toast = useToast();
const [cfg, setCfg] = useState({ taxa_inscricao: '', chave_pix: '', instrucoes_pagamento: '', mp_access_token: '', mp_public_key: '', comissao_pct: '20', texto_responsabilidade: '' });
const [loading, setLoading] = useState(true);
const [salvando, setSalvando] = useState(false);
const [showToken, setShowToken] = useState(false);
useEffect(() => {
HybridBackend.getAdminTutorConfig().then(r => {
if (r.success && r.config) {
setCfg({ taxa_inscricao: r.config.taxa_inscricao || '', chave_pix: r.config.chave_pix || '', instrucoes_pagamento: r.config.instrucoes_pagamento || '', mp_access_token: r.config.mp_access_token || '', mp_public_key: r.config.mp_public_key || '', comissao_pct: String(r.config.comissao_pct ?? '20'), texto_responsabilidade: r.config.texto_responsabilidade || '' });
}
}).finally(() => setLoading(false));
}, []);
const save = async () => {
setSalvando(true);
try {
const res = await HybridBackend.updateAdminTutorConfig({
...cfg,
taxa_inscricao: parseFloat(cfg.taxa_inscricao) || 0,
comissao_pct: parseFloat(cfg.comissao_pct) || 20,
});
if (!res.success) { toast.error('ERRO AO SALVAR.'); return; }
toast.success('CONFIGURAÇÕES SALVAS!');
} catch { toast.error('ERRO AO SALVAR.'); }
finally { setSalvando(false); }
};
if (loading) return <div className="flex items-center justify-center h-32"><Loader2 size={22} className="animate-spin text-gray-300" /></div>;
return (
<div className="space-y-5">
{/* Comissão da plataforma */}
<div className="bg-white border border-gray-200 rounded-xl p-5 space-y-4">
<h4 className="font-black text-gray-700 uppercase text-xs flex items-center gap-2"><Percent size={14} className="text-indigo-500" /> SPLIT DE PAGAMENTO</h4>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="text-[10px] font-black text-gray-400 uppercase block mb-1">COMISSÃO DA PLATAFORMA (%)</label>
<input type="number" min={0} max={100} step={1} value={cfg.comissao_pct} onChange={e => setCfg(p => ({ ...p, comissao_pct: e.target.value }))}
className="w-full border border-gray-200 rounded-xl px-3 py-2.5 text-sm font-bold focus:outline-none focus:border-indigo-400" placeholder="20" />
<p className="text-[10px] text-gray-400 mt-1">EX: 20% TUTOR RECEBE 80% DO VALOR DE CADA CONSULTORIA</p>
</div>
<div className="bg-indigo-50 border border-indigo-100 rounded-xl p-3 flex items-start gap-2">
<AlertCircle size={14} className="text-indigo-500 flex-shrink-0 mt-0.5" />
<p className="text-[10px] text-indigo-700 font-bold uppercase leading-relaxed">
O TUTOR DEVE ACEITAR ESTE % EXPLICITAMENTE AO CADASTRAR SUAS CREDENCIAIS DE PAGAMENTO.
</p>
</div>
</div>
</div>
{/* Texto do termo de responsabilidade */}
<div className="bg-white border border-gray-200 rounded-xl p-5 space-y-4">
<h4 className="font-black text-gray-700 uppercase text-xs flex items-center gap-2"><FileText size={14} className="text-amber-500" /> TEXTO DO TERMO DE RESPONSABILIDADE</h4>
<div>
<label className="text-[10px] font-black text-gray-400 uppercase block mb-1">TEXTO EXIBIDO AO DENTISTA AO SOLICITAR TUTORIA</label>
<textarea value={cfg.texto_responsabilidade} onChange={e => setCfg(p => ({ ...p, texto_responsabilidade: e.target.value }))}
className="w-full border border-gray-200 rounded-xl px-3 py-2.5 text-xs font-medium h-40 resize-none focus:outline-none focus:border-amber-400"
placeholder="Deixe em branco para usar o texto padrão do sistema..." />
<p className="text-[10px] text-gray-400 mt-1">SE VAZIO, O SISTEMA USA O TEXTO PADRÃO JURIDICAMENTE REDIGIDO.</p>
</div>
</div>
{/* Taxa */}
<div className="bg-white border border-gray-200 rounded-xl p-5 space-y-4">
<h4 className="font-black text-gray-700 uppercase text-xs flex items-center gap-2"><DollarSign size={14} className="text-green-500" /> TAXA DE ATIVAÇÃO</h4>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="text-[10px] font-black text-gray-400 uppercase block mb-1">VALOR DA TAXA (R$) *</label>
<input type="number" min={0} step={10} value={cfg.taxa_inscricao} onChange={e => setCfg(p => ({ ...p, taxa_inscricao: e.target.value }))}
className="w-full border border-gray-200 rounded-xl px-3 py-2.5 text-sm font-bold focus:outline-none focus:border-indigo-400" placeholder="299,90" />
<p className="text-[10px] text-gray-400 mt-1">COBRADO UMA VEZ PARA ATIVAÇÃO DO PERFIL DE TUTOR</p>
</div>
<div>
<label className="text-[10px] font-black text-gray-400 uppercase block mb-1">CHAVE PIX (FALLBACK MANUAL)</label>
<input type="text" value={cfg.chave_pix} onChange={e => setCfg(p => ({ ...p, chave_pix: e.target.value }))}
className="w-full border border-gray-200 rounded-xl px-3 py-2.5 text-sm font-medium focus:outline-none focus:border-indigo-400" placeholder="email@scoreodonto.com" />
</div>
<div className="md:col-span-2">
<label className="text-[10px] font-black text-gray-400 uppercase block mb-1">INSTRUÇÕES DE PAGAMENTO (EXIBIDAS AO CANDIDATO)</label>
<textarea value={cfg.instrucoes_pagamento} onChange={e => setCfg(p => ({ ...p, instrucoes_pagamento: e.target.value }))}
className="w-full border border-gray-200 rounded-xl px-3 py-2.5 text-xs font-medium h-20 resize-none focus:outline-none focus:border-indigo-400"
placeholder="Ex: Após o pagamento, seu perfil será ativado em até 24h. Em caso de dúvidas, contate suporte@scoreodonto.com" />
</div>
</div>
</div>
{/* MercadoPago */}
<div className="bg-white border border-gray-200 rounded-xl p-5 space-y-4">
<h4 className="font-black text-gray-700 uppercase text-xs flex items-center gap-2"><Key size={14} className="text-blue-500" /> MERCADOPAGO CREDENCIAIS</h4>
<div className="bg-blue-50 border border-blue-100 rounded-lg p-3 text-[10px] text-blue-700 font-bold uppercase">
<AlertCircle size={12} className="inline mr-1" />
CONFIGURE COM AS CREDENCIAIS DA SUA CONTA MERCADOPAGO. O WEBHOOK SERÁ: {'/api/candidatura-tutor/webhook'}
</div>
<div className="grid grid-cols-1 gap-4">
<div>
<label className="text-[10px] font-black text-gray-400 uppercase block mb-1">ACCESS TOKEN (PRODUÇÃO)</label>
<div className="relative">
<input type={showToken ? 'text' : 'password'} value={cfg.mp_access_token} onChange={e => setCfg(p => ({ ...p, mp_access_token: e.target.value }))}
className="w-full border border-gray-200 rounded-xl px-3 py-2.5 text-sm font-mono focus:outline-none focus:border-indigo-400 pr-20" placeholder="APP_USR-..." />
<button onClick={() => setShowToken(!showToken)} className="absolute right-3 top-1/2 -translate-y-1/2 text-[10px] font-black text-gray-400 hover:text-gray-600 uppercase">
{showToken ? 'OCULTAR' : 'VER'}
</button>
</div>
<p className="text-[10px] text-gray-400 mt-1">OBTENHA EM MERCADOPAGO.COM SUAS APLICAÇÕES CREDENCIAIS</p>
</div>
<div>
<label className="text-[10px] font-black text-gray-400 uppercase block mb-1">PUBLIC KEY</label>
<input type="text" value={cfg.mp_public_key} onChange={e => setCfg(p => ({ ...p, mp_public_key: e.target.value }))}
className="w-full border border-gray-200 rounded-xl px-3 py-2.5 text-sm font-mono focus:outline-none focus:border-indigo-400" placeholder="APP_USR-..." />
</div>
</div>
</div>
<button onClick={save} disabled={salvando}
className="flex items-center gap-2 px-5 py-2.5 bg-indigo-600 hover:bg-indigo-700 text-white rounded-xl text-[11px] font-black uppercase transition-colors disabled:opacity-40 shadow-sm">
{salvando ? <Loader2 size={13} className="animate-spin" /> : <CheckCircle2 size={13} />} SALVAR CONFIGURAÇÕES
</button>
</div>
);
};
// ─── View principal ───────────────────────────────────────────────
// ─── Aba Financeiro / Repasses ────────────────────────────────────
const FinanceiroTab: React.FC = () => {
const toast = useToast();
const [repasses, setRepasses] = useState<any[]>([]);
const [loading, setLoading] = useState(true);
const load = async () => {
setLoading(true);
try {
const res = await HybridBackend.getRepassesPendentes();
if (res.success) setRepasses(res.repasses || []);
} catch {}
finally { setLoading(false); }
};
useEffect(() => { load(); }, []);
const confirmar = async (id: string, tutor: string, valor: number) => {
if (!window.confirm(`CONFIRMAR REPASSE DE R$ ${valor.toLocaleString('pt-BR', { minimumFractionDigits: 2 })} PARA ${tutor}?`)) return;
const res = await HybridBackend.confirmarRepasse(id);
if (!res.success) { toast.error('ERRO.'); return; }
toast.success('REPASSE CONFIRMADO!');
load();
};
const pendentes = repasses.filter(r => r.status_repasse !== 'PAGO');
const pagos = repasses.filter(r => r.status_repasse === 'PAGO');
const totalPendente = pendentes.reduce((acc, r) => acc + (parseFloat(r.valor_tutor) || 0), 0);
return (
<div className="space-y-4">
{/* Resumo */}
<div className="grid grid-cols-3 gap-2 sm:gap-3">
{[
{ l: 'PENDENTES', v: pendentes.length, color: 'text-amber-600' },
{ l: 'A REPASSAR', v: `R$ ${totalPendente.toLocaleString('pt-BR', { minimumFractionDigits: 2 })}`, color: 'text-indigo-600' },
{ l: 'REPASSADOS', v: pagos.length, color: 'text-green-600' },
].map(s => (
<div key={s.l} className="bg-white border border-gray-200 rounded-xl p-3 sm:p-4 text-center">
<p className={`text-base sm:text-xl font-black ${s.color} truncate`}>{s.v}</p>
<p className="text-[8px] sm:text-[9px] font-black text-gray-400 uppercase mt-1 leading-tight">{s.l}</p>
</div>
))}
</div>
{loading ? (
<div className="flex items-center justify-center h-32"><Loader2 size={24} className="animate-spin text-gray-300" /></div>
) : repasses.length === 0 ? (
<div className="flex flex-col items-center justify-center py-12 text-gray-400 gap-2">
<Banknote size={28} />
<p className="text-sm font-black uppercase">NENHUM REPASSE REGISTRADO</p>
</div>
) : (
<div className="space-y-2">
{repasses.map(r => (
<div key={r.id} className="bg-white border border-gray-200 rounded-xl p-4 space-y-3">
<div className="flex items-start gap-3">
<div className="flex-1 min-w-0">
<p className="font-black text-sm text-gray-800 uppercase truncate">{r.paciente_nome || '—'}</p>
<p className="text-[10px] text-gray-500 font-bold uppercase">{r.tutor_nome} {r.tipo}</p>
<p className="text-[10px] text-gray-400 mt-0.5">{new Date(r.created_at).toLocaleDateString('pt-BR')}</p>
</div>
<div className="text-right flex-shrink-0 space-y-0.5">
<p className="font-black text-sm text-indigo-600">R$ {Number(r.valor_tutor || 0).toLocaleString('pt-BR', { minimumFractionDigits: 2 })}</p>
<p className="text-[9px] text-gray-400 font-bold">{r.pix_key ? `PIX: ${r.pix_key}` : 'SEM PIX'}</p>
{r.comissao_pct && (
<p className="text-[9px] text-gray-400">PLAT: {r.comissao_pct}% = R$ {Number(r.valor_plataforma || 0).toLocaleString('pt-BR', { minimumFractionDigits: 2 })}</p>
)}
</div>
</div>
<div className="border-t border-gray-100 pt-2 flex justify-end">
{r.status_repasse === 'PAGO' ? (
<span className="flex items-center gap-1 text-[9px] font-black px-2 py-1 rounded-full bg-green-100 text-green-700 uppercase">
<CheckCircle2 size={10} /> PAGO {r.repasse_at ? new Date(r.repasse_at).toLocaleDateString('pt-BR') : ''}
</span>
) : (
<button
onClick={() => confirmar(r.id, r.tutor_nome, parseFloat(r.valor_tutor) || 0)}
className="flex items-center gap-1.5 px-4 py-2 bg-green-600 hover:bg-green-700 text-white rounded-xl text-[10px] font-black uppercase transition-colors">
<Banknote size={12} /> CONFIRMAR REPASSE
</button>
)}
</div>
</div>
))}
</div>
)}
</div>
);
};
// ─── View principal ───────────────────────────────────────────────
export const AdminGestaoTutoresView: React.FC = () => {
const [tab, setTab] = useState<'candidaturas' | 'ativos' | 'config' | 'financeiro'>('candidaturas');
const [candidaturas, setCandidaturas] = useState<any[]>([]);
const [loading, setLoading] = useState(true);
const [filtroStatus, setFiltroStatus] = useState('');
const load = useCallback(async () => {
setLoading(true);
try {
const res = await HybridBackend.getAdminCandidaturas(filtroStatus || undefined);
if (res.success) setCandidaturas(res.candidaturas || []);
} catch {}
finally { setLoading(false); }
}, [filtroStatus]);
useEffect(() => { if (tab !== 'config') load(); }, [tab, load]);
const pendentes = candidaturas.filter(c => c.status === 'PENDENTE').length;
const aguardandoPgto = candidaturas.filter(c => c.status === 'AGUARDANDO_PAGAMENTO').length;
const ativos = candidaturas.filter(c => c.status === 'ATIVO');
const tabs = [
{ id: 'candidaturas' as const, label: 'CANDIDATURAS', badge: pendentes + aguardandoPgto },
{ id: 'ativos' as const, label: `TUTORES ATIVOS (${ativos.length})`, badge: 0 },
{ id: 'financeiro' as const, label: 'FINANCEIRO', badge: 0 },
{ id: 'config' as const, label: 'CONFIGURAÇÕES', badge: 0 },
];
return (
<div className="space-y-6">
<div className="mb-4 md:mb-6">
<div className="flex items-center gap-1.5">
<h2 className="text-lg md:text-2xl font-bold text-gray-900 uppercase leading-tight">GESTÃO DE TUTORES ORTODÔNTICOS</h2>
<button onClick={load} className="p-1.5 hover:bg-gray-100 rounded-lg transition-colors text-gray-400 flex-shrink-0">
<RefreshCw size={14} />
</button>
</div>
</div>
{/* Stats */}
<div className="grid grid-cols-2 sm:grid-cols-4 gap-3">
{[
{ label: 'CANDIDATURAS', value: candidaturas.length, color: 'text-gray-700' },
{ label: 'PENDENTES', value: pendentes, color: 'text-amber-600' },
{ label: 'AGUARD. PGTO', value: aguardandoPgto, color: 'text-blue-600' },
{ label: 'TUTORES ATIVOS', value: ativos.length, color: 'text-green-600' },
].map(s => (
<div key={s.label} className="bg-white border border-gray-200 rounded-xl p-4 text-center">
<p className={`text-2xl font-black ${s.color}`}>{s.value}</p>
<p className="text-[9px] font-black text-gray-400 uppercase mt-1">{s.label}</p>
</div>
))}
</div>
{/* Tabs */}
<div className="flex gap-1 p-1 bg-gray-100 rounded-xl overflow-x-auto">
{tabs.map(t => (
<button key={t.id} onClick={() => setTab(t.id)}
className={`flex items-center gap-1.5 px-3 md:px-4 py-2 rounded-xl text-[10px] font-black uppercase transition-all whitespace-nowrap flex-shrink-0 ${tab === t.id ? 'bg-white text-gray-800 shadow-sm' : 'text-gray-500 hover:text-gray-700'}`}>
{t.label}
{t.badge > 0 && (
<span className={`text-[8px] font-black px-1.5 py-0.5 rounded-full ${tab === t.id ? 'bg-red-500 text-white' : 'bg-red-100 text-red-600'}`}>{t.badge}</span>
)}
</button>
))}
</div>
{/* Conteúdo */}
{tab === 'config' ? (
<ConfigTab />
) : tab === 'financeiro' ? (
<FinanceiroTab />
) : loading ? (
<div className="flex items-center justify-center h-40"><Loader2 size={28} className="animate-spin text-gray-300" /></div>
) : tab === 'candidaturas' ? (
<div className="space-y-4">
{/* Filtro */}
<div className="flex gap-2 flex-wrap">
{['', 'PENDENTE', 'AGUARDANDO_PAGAMENTO', 'APROVADO', 'REJEITADO', 'ATIVO'].map(s => (
<button key={s} onClick={() => setFiltroStatus(s)}
className={`px-3 py-1.5 rounded-full text-[10px] font-black uppercase border transition-all ${filtroStatus === s ? 'bg-indigo-600 text-white border-indigo-600' : 'bg-white text-gray-500 border-gray-200 hover:border-indigo-300'}`}>
{s || 'TODOS'}
</button>
))}
</div>
{candidaturas.length === 0 ? (
<div className="flex flex-col items-center justify-center py-16 text-gray-400 gap-2">
<GraduationCap size={32} />
<p className="text-sm font-black uppercase">NENHUMA CANDIDATURA</p>
</div>
) : (
<div className="space-y-3">
{candidaturas.map(c => <CandidaturaCard key={c.id} c={c} onAction={load} />)}
</div>
)}
</div>
) : (
// Tutores ativos
<div className="space-y-3">
{ativos.length === 0 ? (
<div className="flex flex-col items-center justify-center py-16 text-gray-400 gap-2">
<Users size={32} />
<p className="text-sm font-black uppercase">NENHUM TUTOR ATIVO AINDA</p>
</div>
) : ativos.map(c => (
<div key={c.id} className="bg-white border border-gray-200 rounded-xl p-4">
<div className="flex items-center gap-3">
<div className="w-10 h-10 bg-green-100 rounded-full flex items-center justify-center font-black text-green-600 flex-shrink-0">
{c.nome?.charAt(0)}
</div>
<div className="flex-1 min-w-0">
<p className="font-black text-sm text-gray-800 uppercase truncate">{c.nome}</p>
<p className="text-[10px] text-gray-500 font-bold uppercase truncate">CRO {c.cro_uf}/{c.cro} {c.titulacao}</p>
</div>
<span className="flex items-center gap-1 text-[9px] font-black px-2 py-0.5 rounded-full bg-green-100 text-green-700 uppercase flex-shrink-0">
<CheckCircle2 size={10} /> ATIVO
</span>
</div>
<div className="mt-2 flex items-center justify-between pl-[52px]">
<p className="text-[10px] text-gray-500 font-bold uppercase truncate">{c.especialidade_principal}</p>
<p className="font-black text-indigo-600 text-sm flex-shrink-0 ml-2">
{c.preco_por_consulta ? `R$ ${Number(c.preco_por_consulta).toLocaleString('pt-BR', { minimumFractionDigits: 2 })}` : '—'}
<span className="text-[9px] text-gray-400 font-normal ml-1">/ consulta</span>
</p>
</div>
</div>
))}
</div>
)}
</div>
);
};