Files
scoreodonto.com/frontend/views/ComissoesView.tsx
T
VPS 4 Builder 3e64514b08 feat(financeiro+salas): motor financeiro V1/V2, glosas, GTO→financeiro, contratos de convênio e redesenho de Salas
Financeiro V1 (Épicos 1–4):
- FKs de origem no lançamento (paciente/profissional/procedimento/agendamento/contrato/realizado), soft delete e cron "Atrasado"
- prontuário de procedimentos realizados (fotos antes/depois) + regra sem-comissão (garantia/reabertura do mesmo profissional)
- normalizeFinanceiro: corrige CHECK capitalizado vs enforceUppercase (lançamentos não salvavam pela UI)
- baixa de pagamento (pago_em), despesas (fornecedor/centro de custo/recorrente), competência por data de conclusão
- relatório financeiro (período, paciente, profissional, forma, convênio, glosa)
- orçamento Assinado -> contas a receber; contrato vigente -> cobrança recorrente; renovação automática de contrato

Financeiro V2 (Comissão/Repasse):
- regras de % (padrão/procedimento/override por profissional) + custo de laboratório
- base selecionável (recebido/produção), fechamento persistido, pagar (gera despesa) + comprovante + estorno

Glosas de convênio: por item de GTO, recurso/protocolo/prazo, recuperar/estornar, impacto no relatório

GTO -> Financeiro: finalizar gera RECEITA (convênio a receber); LancarGTO passa a persistir; convênios reais (planos)

Contratos: modelo "Atendimento a Convênios / Repasse (Dentista Credenciado)" + variáveis de convênio

Salas (redesenho): perfil "Dono de Sala"; locação sempre ativa; menus MINHAS/ALUGAR SALAS;
sala como workspace isolado (seletor agrupado Clínicas x Salas, tenantGuard por dono); financeiro de
locação (reserva confirmada -> recebível); Painel da Sala (KPIs, agenda visual, recebíveis, relatório por sala)

Docs: BACKLOG-FINANCEIRO-V1, AUDITORIA-FUNCIONAL-SCOREODONTO, CHECKLIST-DEPLOY-PRODUCAO

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 00:28:07 +02:00

387 lines
28 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import React, { useState, useEffect, useCallback } from 'react';
import { Percent, Wallet, History, Plus, Trash2, Lock, X, RefreshCw, Filter, AlertTriangle, ChevronDown, ChevronRight, CheckCircle, Paperclip, FileText } from 'lucide-react';
import { HybridBackend } from '../services/backend.ts';
import { useToast } from '../contexts/ToastContext.tsx';
import { useConfirm } from '../contexts/ConfirmContext.tsx';
import { PageHeader } from '../components/PageHeader.tsx';
const COLOR = '#0d9488';
const fmtBRL = (v: number) => 'R$ ' + Number(v || 0).toLocaleString('pt-BR', { minimumFractionDigits: 2 });
const hoje = new Date();
const inicioMes = new Date(hoje.getFullYear(), hoje.getMonth(), 1).toISOString().split('T')[0];
const fimHoje = hoje.toISOString().split('T')[0];
const dataBR = (d: string) => (d || '').slice(0, 10).split('-').reverse().join('/');
type Aba = 'relatorio' | 'regras' | 'historico';
export const ComissoesView: React.FC = () => {
const toast = useToast();
const confirm = useConfirm();
const [aba, setAba] = useState<Aba>('relatorio');
return (
<div className="space-y-6 pb-10">
<PageHeader title="COMISSÕES & REPASSE" />
<div className="flex gap-2 border-b border-gray-200">
{([['relatorio', 'Relatório / Fechamento', Wallet], ['regras', 'Regras de %', Percent], ['historico', 'Histórico', History]] as const).map(([id, label, Icon]) => (
<button key={id} onClick={() => setAba(id)}
className={`flex items-center gap-2 px-4 py-2.5 text-xs font-black uppercase tracking-wide border-b-2 -mb-px transition-colors ${aba === id ? 'border-teal-600 text-teal-700' : 'border-transparent text-gray-400 hover:text-gray-600'}`}>
<Icon size={15} /> {label}
</button>
))}
</div>
{aba === 'relatorio' && <AbaRelatorio toast={toast} confirm={confirm} />}
{aba === 'regras' && <AbaRegras toast={toast} confirm={confirm} />}
{aba === 'historico' && <AbaHistorico toast={toast} confirm={confirm} />}
</div>
);
};
// ─── Aba 1: Relatório ao vivo + Fechar repasse ────────────────────────────────
const AbaRelatorio: React.FC<{ toast: any; confirm: any }> = ({ toast, confirm }) => {
const [inicio, setInicio] = useState(inicioMes);
const [fim, setFim] = useState(fimHoje);
const [base, setBase] = useState<'recebido' | 'producao'>('recebido');
const [dados, setDados] = useState<any | null>(null);
const [loading, setLoading] = useState(false);
const [fechando, setFechando] = useState(false);
const [aberto, setAberto] = useState<string | null>(null);
const carregar = useCallback(() => {
setLoading(true);
HybridBackend.getComissoesRelatorio({ inicio, fim, base }).then(setDados).catch(() => setDados(null)).finally(() => setLoading(false));
}, [inicio, fim, base]);
useEffect(() => { carregar(); }, [carregar]);
const fechar = async () => {
if (!dados?.grupos?.length) { toast.error('NADA A FECHAR NO PERÍODO.'); return; }
if (!await confirm(`FECHAR REPASSE de ${dataBR(inicio)} a ${dataBR(fim)} (base ${base.toUpperCase()})? Serão gravados ${dados.grupos.length} fechamento(s) — total ${fmtBRL(dados.total_comissao)}. Esta ação fica registrada para auditoria.`)) return;
setFechando(true);
try {
const r = await HybridBackend.fecharRepasse({ inicio, fim, base });
toast.success(`REPASSE FECHADO: ${r.criados} PROFISSIONAL(IS), TOTAL ${fmtBRL(r.total_comissao)}.`);
} catch (e: any) { toast.error((e.message || 'ERRO').toUpperCase()); }
finally { setFechando(false); }
};
return (
<div className="space-y-6">
{/* Filtros */}
<div className="bg-white p-5 rounded-2xl shadow-sm border border-gray-100 flex flex-wrap items-end gap-4">
<div className="space-y-1.5">
<label className="text-[10px] font-black text-gray-400 uppercase tracking-widest">Início</label>
<input type="date" value={inicio} onChange={e => setInicio(e.target.value)} className="block bg-gray-50 border border-gray-200 rounded-xl px-3 py-2.5 text-sm font-bold text-gray-700 outline-none focus:border-teal-400" />
</div>
<div className="space-y-1.5">
<label className="text-[10px] font-black text-gray-400 uppercase tracking-widest">Fim</label>
<input type="date" value={fim} onChange={e => setFim(e.target.value)} className="block bg-gray-50 border border-gray-200 rounded-xl px-3 py-2.5 text-sm font-bold text-gray-700 outline-none focus:border-teal-400" />
</div>
<div className="space-y-1.5">
<label className="text-[10px] font-black text-gray-400 uppercase tracking-widest">Base de cálculo</label>
<div className="inline-flex rounded-xl border border-gray-200 overflow-hidden">
<button onClick={() => setBase('recebido')} className={`px-4 py-2.5 text-xs font-bold uppercase transition-colors ${base === 'recebido' ? 'bg-teal-600 text-white' : 'bg-white text-gray-500 hover:bg-gray-50'}`}>Recebido</button>
<button onClick={() => setBase('producao')} className={`px-4 py-2.5 text-xs font-bold uppercase transition-colors ${base === 'producao' ? 'bg-teal-600 text-white' : 'bg-white text-gray-500 hover:bg-gray-50'}`}>Produção</button>
</div>
</div>
<button onClick={carregar} className="bg-gray-100 text-gray-600 px-4 py-2.5 rounded-xl font-black text-xs uppercase hover:bg-gray-200 transition-colors flex items-center gap-2"><Filter size={14} /> Atualizar</button>
<button onClick={fechar} disabled={fechando || !dados?.grupos?.length} className="ml-auto text-white px-5 py-2.5 rounded-xl font-black text-xs uppercase transition-colors flex items-center gap-2 disabled:opacity-50" style={{ backgroundColor: COLOR }}>
{fechando ? <RefreshCw size={14} className="animate-spin" /> : <Lock size={14} />} Fechar repasse
</button>
</div>
<p className="text-[11px] text-gray-400 font-bold uppercase flex items-center gap-1.5">
<AlertTriangle size={12} className="text-amber-500" />
{base === 'recebido' ? 'Recebido: só lançamentos PAGOS (caixa).' : 'Produção: tudo lançado no período (competência), pago ou não.'} Garantias/reaberturas sem comissão são ignoradas.
</p>
{loading ? <div className="flex justify-center py-16"><RefreshCw className="animate-spin text-teal-600" size={30} /></div> : (
<div className="bg-white rounded-2xl border border-gray-100 shadow-sm overflow-hidden">
<div className="px-6 py-4 border-b border-gray-100 flex justify-between items-center bg-gray-50/50">
<h3 className="text-sm font-black text-gray-800 uppercase">Comissão por profissional</h3>
<div className="flex items-center gap-6">
{Number(dados?.total_custo_lab) > 0 && (
<div className="text-right">
<p className="text-[10px] font-black text-gray-400 uppercase">Custo lab</p>
<p className="text-sm font-black text-amber-600">{fmtBRL(dados?.total_custo_lab || 0)}</p>
</div>
)}
<div className="text-right">
<p className="text-[10px] font-black text-gray-400 uppercase">Total comissão</p>
<p className="text-lg font-black text-teal-700">{fmtBRL(dados?.total_comissao || 0)}</p>
</div>
</div>
</div>
{(dados?.grupos || []).length === 0 ? (
<div className="py-12 text-center text-gray-400 text-sm font-bold uppercase">Nenhuma comissão no período.</div>
) : (
<div className="divide-y divide-gray-100">
{dados.grupos.map((g: any) => (
<div key={g.profissional_id}>
<button onClick={() => setAberto(aberto === g.profissional_id ? null : g.profissional_id)} className="w-full flex items-center gap-3 px-6 py-4 hover:bg-gray-50/50 transition-colors text-left">
{aberto === g.profissional_id ? <ChevronDown size={16} className="text-gray-400" /> : <ChevronRight size={16} className="text-gray-400" />}
<div className="flex-1 min-w-0">
<p className="font-black text-gray-800 text-sm uppercase truncate">{g.profissional_nome}</p>
<p className="text-[10px] text-gray-400 font-bold uppercase">{g.qtd} lançamento(s) · base {fmtBRL(g.base_valor)} · {g.percentual_medio}% médio</p>
</div>
<span className="text-base font-black text-teal-700 shrink-0">{fmtBRL(g.comissao_valor)}</span>
</button>
{aberto === g.profissional_id && (
<div className="px-6 pb-4 bg-gray-50/40">
<table className="w-full text-xs">
<thead><tr className="text-[10px] font-black text-gray-400 uppercase">
<th className="text-left py-1.5">Descrição</th><th className="text-right">Valor</th><th className="text-right">Lab</th><th className="text-right">%</th><th className="text-right">Comissão</th>
</tr></thead>
<tbody>
{g.detalhe.map((d: any) => (
<tr key={d.id} className="border-t border-gray-100">
<td className="py-1.5 font-bold text-gray-600 uppercase">{d.descricao}</td>
<td className="text-right text-gray-500">{fmtBRL(d.valor)}</td>
<td className="text-right text-amber-600">{Number(d.custo_lab) > 0 ? '-' + fmtBRL(d.custo_lab) : '—'}</td>
<td className="text-right text-gray-500">{d.percentual}%</td>
<td className="text-right font-black text-teal-700">{fmtBRL(d.comissao)}</td>
</tr>
))}
</tbody>
</table>
</div>
)}
</div>
))}
</div>
)}
</div>
)}
</div>
);
};
// ─── Aba 2: Regras de % (padrão + por procedimento + override por profissional) ──
const AbaRegras: React.FC<{ toast: any; confirm: any }> = ({ toast, confirm }) => {
const [regras, setRegras] = useState<any[]>([]);
const [procs, setProcs] = useState<any[]>([]);
const [dentistas, setDentistas] = useState<any[]>([]);
const [loading, setLoading] = useState(false);
const [padrao, setPadrao] = useState('');
const [novoProc, setNovoProc] = useState('');
const [novoPct, setNovoPct] = useState('');
const [novoLab, setNovoLab] = useState('');
const [novoProf, setNovoProf] = useState('');
const [novoProfPct, setNovoProfPct] = useState('');
const carregar = useCallback(async () => {
setLoading(true);
try {
const [r, p, d] = await Promise.all([HybridBackend.getComissaoRegras(), HybridBackend.getProcedimentos(), HybridBackend.getDentistas()]);
setRegras(Array.isArray(r) ? r : []);
setProcs(Array.isArray(p) ? p : []);
setDentistas(Array.isArray(d) ? d : []);
const def = (r || []).find((x: any) => !x.procedimento_id && !x.profissional_id);
setPadrao(def ? String(def.percentual) : '');
} catch { /* silent */ } finally { setLoading(false); }
}, []);
useEffect(() => { carregar(); }, [carregar]);
const salvarPadrao = async () => {
const v = Number(padrao);
if (!(v >= 0 && v <= 100)) { toast.error('PERCENTUAL ENTRE 0 E 100.'); return; }
try { await HybridBackend.saveComissaoRegra({ percentual: v }); toast.success('PERCENTUAL PADRÃO SALVO.'); carregar(); }
catch (e: any) { toast.error((e.message || 'ERRO').toUpperCase()); }
};
const addRegra = async () => {
const v = Number(novoPct);
if (!novoProc) { toast.error('SELECIONE O PROCEDIMENTO.'); return; }
if (!(v >= 0 && v <= 100)) { toast.error('PERCENTUAL ENTRE 0 E 100.'); return; }
try { await HybridBackend.saveComissaoRegra({ procedimento_id: novoProc, percentual: v, custo_lab: Number(novoLab) || 0 }); toast.success('REGRA SALVA.'); setNovoProc(''); setNovoPct(''); setNovoLab(''); carregar(); }
catch (e: any) { toast.error((e.message || 'ERRO').toUpperCase()); }
};
const addOverride = async () => {
const v = Number(novoProfPct);
if (!novoProf) { toast.error('SELECIONE O PROFISSIONAL.'); return; }
if (!(v >= 0 && v <= 100)) { toast.error('PERCENTUAL ENTRE 0 E 100.'); return; }
try { await HybridBackend.saveComissaoRegra({ profissional_id: novoProf, percentual: v }); toast.success('OVERRIDE SALVO.'); setNovoProf(''); setNovoProfPct(''); carregar(); }
catch (e: any) { toast.error((e.message || 'ERRO').toUpperCase()); }
};
const excluir = async (id: string) => {
if (!await confirm('EXCLUIR esta regra de comissão?')) return;
try { await HybridBackend.deleteComissaoRegra(id); toast.success('REGRA EXCLUÍDA.'); carregar(); }
catch (e: any) { toast.error((e.message || 'ERRO').toUpperCase()); }
};
const regrasProc = regras.filter(r => r.procedimento_id);
const regrasProf = regras.filter(r => r.profissional_id);
return (
<div className="space-y-6 max-w-3xl">
{/* Padrão */}
<div className="bg-white p-5 rounded-2xl shadow-sm border border-gray-100">
<h3 className="text-sm font-black text-gray-800 uppercase mb-1">Percentual padrão da clínica</h3>
<p className="text-[11px] text-gray-400 font-bold mb-3">Aplicado quando não override do profissional nem regra do procedimento.</p>
<div className="flex items-center gap-2">
<div className="relative">
<input type="number" min="0" max="100" value={padrao} onChange={e => setPadrao(e.target.value)} placeholder="0"
className="w-28 bg-gray-50 border border-gray-200 rounded-xl pl-3 pr-8 py-2.5 text-sm font-bold text-gray-700 outline-none focus:border-teal-400" />
<Percent size={14} className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-300" />
</div>
<button onClick={salvarPadrao} className="text-white px-4 py-2.5 rounded-xl font-black text-xs uppercase" style={{ backgroundColor: COLOR }}>Salvar</button>
</div>
<p className="text-[10px] text-gray-400 font-bold mt-3 uppercase">Prioridade: override do profissional regra do procedimento padrão.</p>
</div>
{/* Override por profissional */}
<div className="bg-white p-5 rounded-2xl shadow-sm border border-gray-100">
<h3 className="text-sm font-black text-gray-800 uppercase mb-1">Override por profissional</h3>
<p className="text-[11px] text-gray-400 font-bold mb-3">% fixo para um profissional, prevalece sobre as demais regras.</p>
<div className="flex flex-wrap items-end gap-2 mb-4">
<select value={novoProf} onChange={e => setNovoProf(e.target.value)} className="flex-1 min-w-[180px] bg-gray-50 border border-gray-200 rounded-xl px-3 py-2.5 text-sm font-bold text-gray-700 outline-none focus:border-teal-400">
<option value="">Selecione o profissional</option>
{dentistas.map(d => <option key={d.id} value={d.id}>{d.nome}</option>)}
</select>
<div className="relative">
<input type="number" min="0" max="100" value={novoProfPct} onChange={e => setNovoProfPct(e.target.value)} placeholder="0"
className="w-24 bg-gray-50 border border-gray-200 rounded-xl pl-3 pr-8 py-2.5 text-sm font-bold text-gray-700 outline-none focus:border-teal-400" />
<Percent size={14} className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-300" />
</div>
<button onClick={addOverride} className="text-white px-4 py-2.5 rounded-xl font-black text-xs uppercase flex items-center gap-1.5" style={{ backgroundColor: COLOR }}><Plus size={14} /> Adicionar</button>
</div>
{regrasProf.length === 0 ? <p className="text-sm text-gray-400 font-bold uppercase py-2 text-center">Nenhum override.</p> : (
<div className="divide-y divide-gray-100">
{regrasProf.map(r => (
<div key={r.id} className="flex items-center justify-between py-2.5">
<span className="text-sm font-bold text-gray-700 uppercase">{r.profissional_nome || r.profissional_id}</span>
<div className="flex items-center gap-3">
<span className="text-sm font-black text-teal-700">{r.percentual}%</span>
<button onClick={() => excluir(r.id)} className="p-1.5 text-gray-400 hover:text-red-600 hover:bg-red-50 rounded-lg transition-colors"><Trash2 size={15} /></button>
</div>
</div>
))}
</div>
)}
</div>
{/* Por procedimento (+ custo de lab) */}
<div className="bg-white p-5 rounded-2xl shadow-sm border border-gray-100">
<h3 className="text-sm font-black text-gray-800 uppercase mb-1">Percentual por procedimento</h3>
<p className="text-[11px] text-gray-400 font-bold mb-3">Opcional: custo de laboratório deduzido do valor antes da comissão.</p>
<div className="flex flex-wrap items-end gap-2 mb-4">
<select value={novoProc} onChange={e => setNovoProc(e.target.value)} className="flex-1 min-w-[160px] bg-gray-50 border border-gray-200 rounded-xl px-3 py-2.5 text-sm font-bold text-gray-700 outline-none focus:border-teal-400">
<option value="">Selecione o procedimento</option>
{procs.map(p => <option key={p.id} value={p.id}>{p.nome}</option>)}
</select>
<div>
<label className="text-[9px] font-black text-gray-400 uppercase block mb-1">%</label>
<div className="relative">
<input type="number" min="0" max="100" value={novoPct} onChange={e => setNovoPct(e.target.value)} placeholder="0"
className="w-20 bg-gray-50 border border-gray-200 rounded-xl pl-3 pr-7 py-2.5 text-sm font-bold text-gray-700 outline-none focus:border-teal-400" />
<Percent size={13} className="absolute right-2.5 top-1/2 -translate-y-1/2 text-gray-300" />
</div>
</div>
<div>
<label className="text-[9px] font-black text-gray-400 uppercase block mb-1">Custo lab (R$)</label>
<input type="number" min="0" value={novoLab} onChange={e => setNovoLab(e.target.value)} placeholder="0,00"
className="w-28 bg-gray-50 border border-gray-200 rounded-xl px-3 py-2.5 text-sm font-bold text-gray-700 outline-none focus:border-teal-400" />
</div>
<button onClick={addRegra} className="text-white px-4 py-2.5 rounded-xl font-black text-xs uppercase flex items-center gap-1.5" style={{ backgroundColor: COLOR }}><Plus size={14} /> Adicionar</button>
</div>
{loading ? <div className="flex justify-center py-6"><RefreshCw className="animate-spin text-teal-500" size={20} /></div> : regrasProc.length === 0 ? (
<p className="text-sm text-gray-400 font-bold uppercase py-3 text-center">Nenhuma regra específica usando o padrão.</p>
) : (
<div className="divide-y divide-gray-100">
{regrasProc.map(r => (
<div key={r.id} className="flex items-center justify-between py-2.5">
<span className="text-sm font-bold text-gray-700 uppercase">{r.procedimento_nome || r.procedimento_id}</span>
<div className="flex items-center gap-3">
{Number(r.custo_lab) > 0 && <span className="text-[10px] font-black text-amber-600 uppercase">lab {fmtBRL(r.custo_lab)}</span>}
<span className="text-sm font-black text-teal-700">{r.percentual}%</span>
<button onClick={() => excluir(r.id)} className="p-1.5 text-gray-400 hover:text-red-600 hover:bg-red-50 rounded-lg transition-colors"><Trash2 size={15} /></button>
</div>
</div>
))}
</div>
)}
</div>
</div>
);
};
// ─── Aba 3: Histórico de fechamentos (auditoria) ──────────────────────────────
const AbaHistorico: React.FC<{ toast: any; confirm: any }> = ({ toast, confirm }) => {
const [lista, setLista] = useState<any[]>([]);
const [loading, setLoading] = useState(false);
const carregar = useCallback(() => {
setLoading(true);
HybridBackend.getRepasses().then(r => setLista(Array.isArray(r) ? r : [])).catch(() => setLista([])).finally(() => setLoading(false));
}, []);
useEffect(() => { carregar(); }, [carregar]);
const cancelar = async (r: any) => {
const msg = r.status === 'pago'
? `CANCELAR o repasse PAGO de ${r.profissional_nome} (${fmtBRL(r.comissao_valor)})? A despesa gerada no Financeiro será ESTORNADA.`
: `CANCELAR o fechamento de ${r.profissional_nome} (${fmtBRL(r.comissao_valor)})? Fica registrado na auditoria.`;
if (!await confirm(msg)) return;
try { await HybridBackend.cancelarRepasse(r.id); toast.success(r.status === 'pago' ? 'REPASSE CANCELADO — DESPESA ESTORNADA.' : 'FECHAMENTO CANCELADO.'); carregar(); }
catch (e: any) { toast.error((e.message || 'ERRO').toUpperCase()); }
};
const pagar = async (r: any) => {
if (!await confirm(`MARCAR como PAGO o repasse de ${r.profissional_nome} (${fmtBRL(r.comissao_valor)})? Será lançada uma DESPESA no Financeiro (centro de custo COMISSÕES).`)) return;
try { await HybridBackend.pagarRepasse(r.id); toast.success('REPASSE PAGO — DESPESA LANÇADA NO FINANCEIRO.'); carregar(); }
catch (e: any) { toast.error((e.message || 'ERRO').toUpperCase()); }
};
const anexarComprovante = (r: any) => {
const input = document.createElement('input');
input.type = 'file'; input.accept = 'image/*,application/pdf';
input.onchange = async () => {
const f = input.files?.[0]; if (!f) return;
try { await HybridBackend.uploadRepasseComprovante(r.id, f); toast.success('COMPROVANTE ANEXADO.'); carregar(); }
catch (e: any) { toast.error((e.message || 'ERRO').toUpperCase()); }
};
input.click();
};
const badge = (s: string) => s === 'cancelado' ? 'bg-red-100 text-red-600' : s === 'pago' ? 'bg-teal-100 text-teal-700' : 'bg-amber-100 text-amber-700';
if (loading) return <div className="flex justify-center py-16"><RefreshCw className="animate-spin text-teal-600" size={28} /></div>;
if (lista.length === 0) return <div className="py-16 text-center text-gray-400 text-sm font-bold uppercase">Nenhum repasse fechado ainda.</div>;
return (
<div className="bg-white rounded-2xl border border-gray-100 shadow-sm overflow-x-auto">
<table className="w-full text-left text-xs">
<thead className="bg-gray-50/60">
<tr>{['Profissional', 'Período', 'Base', 'Lanç.', 'Comissão', 'Status', 'Por', ''].map(h => <th key={h} className="px-4 py-3 text-[10px] font-black text-gray-400 uppercase tracking-widest">{h}</th>)}</tr>
</thead>
<tbody className="divide-y divide-gray-100">
{lista.map(r => (
<tr key={r.id} className={`hover:bg-gray-50/50 ${r.status === 'cancelado' ? 'opacity-50' : ''}`}>
<td className="px-4 py-3 font-black text-gray-800 uppercase">{r.profissional_nome}</td>
<td className="px-4 py-3 text-gray-500 font-bold whitespace-nowrap">{dataBR(r.inicio)} {dataBR(r.fim)}</td>
<td className="px-4 py-3"><span className="px-2 py-0.5 rounded-full text-[9px] font-black uppercase bg-gray-100 text-gray-600">{r.base}</span></td>
<td className="px-4 py-3 text-gray-500">{r.qtd_lancamentos}</td>
<td className="px-4 py-3 font-black text-teal-700">{fmtBRL(r.comissao_valor)}{Number(r.custo_lab_total) > 0 && <span className="block text-[9px] font-bold text-amber-600">lab {fmtBRL(r.custo_lab_total)}</span>}</td>
<td className="px-4 py-3">
<span className={`px-2 py-0.5 rounded-full text-[9px] font-black uppercase ${badge(r.status)}`}>{r.status}</span>
{r.status === 'pago' && r.pago_em && <span className="block text-[9px] text-gray-400 mt-0.5">{dataBR(r.pago_em)}</span>}
</td>
<td className="px-4 py-3 text-gray-400 uppercase">{r.criado_por_nome || '—'}</td>
<td className="px-4 py-3 text-right whitespace-nowrap">
{r.status === 'fechado' && (
<button onClick={() => pagar(r)} title="MARCAR PAGO" className="p-1.5 text-green-600 hover:bg-green-50 rounded-lg transition-colors"><CheckCircle size={15} /></button>
)}
{r.status === 'pago' && (
r.comprovante_url
? <a href={r.comprovante_url} target="_blank" rel="noopener noreferrer" title="VER COMPROVANTE" className="inline-flex p-1.5 text-teal-600 hover:bg-teal-50 rounded-lg transition-colors"><FileText size={15} /></a>
: <button onClick={() => anexarComprovante(r)} title="ANEXAR COMPROVANTE" className="p-1.5 text-gray-400 hover:text-teal-600 hover:bg-teal-50 rounded-lg transition-colors"><Paperclip size={15} /></button>
)}
{(r.status === 'fechado' || r.status === 'pago') && (
<button onClick={() => cancelar(r)} title={r.status === 'pago' ? 'CANCELAR (ESTORNA DESPESA)' : 'CANCELAR'} className="p-1.5 text-gray-400 hover:text-red-600 hover:bg-red-50 rounded-lg transition-colors"><X size={15} /></button>
)}
</td>
</tr>
))}
</tbody>
</table>
</div>
);
};