Files
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

471 lines
34 KiB
TypeScript

import React, { useState, useEffect, useCallback, useRef } from 'react';
import { TrendingUp, Users, Calendar, DollarSign, ArrowUpRight, ArrowDownRight, Printer, Filter, Loader2, Download, BarChart3, PieChart, Activity, Wallet, Clock, AlertTriangle, Search, X, Stethoscope, CreditCard } from 'lucide-react';
import { HybridBackend } from '../services/backend.ts';
import { useHybridBackend } from '../hooks/useHybridBackend.ts';
import { PageHeader } from '../components/PageHeader.tsx';
const fmtBRL = (v: number) => 'R$ ' + Number(v || 0).toLocaleString('pt-BR', { minimumFractionDigits: 2 });
// ─── Relatório Financeiro (H3.1 período + H3.2 paciente/profissional) ─────────
const RelatorioFinanceiro: React.FC<{ inicio: string; fim: string }> = ({ inicio, fim }) => {
const [dados, setDados] = useState<any | null>(null);
const [loading, setLoading] = useState(false);
const [dentistas, setDentistas] = useState<any[]>([]);
const [profissionalId, setProfissionalId] = useState('');
const [paciente, setPaciente] = useState<{ id: string; nome: string } | null>(null);
const [busca, setBusca] = useState('');
const [resultados, setResultados] = useState<any[]>([]);
const [buscaAberta, setBuscaAberta] = useState(false);
const buscaRef = useRef<any>(null);
useEffect(() => { HybridBackend.getDentistas().then(d => setDentistas(Array.isArray(d) ? d : [])); }, []);
// busca de paciente (debounce simples)
useEffect(() => {
if (!busca) { setResultados([]); return; }
const t = setTimeout(() => {
HybridBackend.getPacientes(busca).then(r => setResultados(r.data || []));
}, 350);
return () => clearTimeout(t);
}, [busca]);
const carregar = useCallback(() => {
setLoading(true);
HybridBackend.getFinanceiroRelatorio({ inicio, fim, profissionalId: profissionalId || undefined, pacienteId: paciente?.id })
.then(setDados).catch(() => setDados(null)).finally(() => setLoading(false));
}, [inicio, fim, profissionalId, paciente?.id]);
useEffect(() => { carregar(); }, [carregar]);
const r = dados?.resumo;
const maxMes = Math.max(1, ...(dados?.porMes || []).map((m: any) => Number(m.recebido) + Number(m.a_receber)));
const exportCSV = () => {
const linhas = dados?.lancamentos || [];
let csv = 'Data;Descricao;Paciente;Tipo;Status;Forma;Valor;Origem;SemComissao\n';
linhas.forEach((l: any) => {
csv += `${(l.datavencimento || '').slice(0, 10)};${(l.descricao || '').replace(/;/g, ',')};${(l.pacientenome || '').replace(/;/g, ',')};${l.tipo};${l.status};${l.formapagamento || ''};${Number(l.valor).toFixed(2)};${l.origem || ''};${l.sem_comissao ? 'sim' : 'nao'}\n`;
});
const url = window.URL.createObjectURL(new Blob([csv], { type: 'text/csv' }));
const a = document.createElement('a'); a.href = url; a.download = `relatorio_financeiro_${inicio}_${fim}.csv`; a.click();
};
const KPI: React.FC<{ titulo: string; valor: number; cor: string; Icon: any; sub?: string }> = ({ titulo, valor, cor, Icon, sub }) => (
<div className="bg-white p-5 rounded-2xl border border-gray-100 shadow-sm relative overflow-hidden">
<div className="absolute top-0 right-0 p-3 opacity-10"><Icon size={56} className={cor} /></div>
<h4 className="text-[10px] font-black text-gray-400 uppercase tracking-widest">{titulo}</h4>
<div className="mt-2 text-xl font-black text-gray-900">{fmtBRL(valor)}</div>
{sub && <p className="mt-2 text-[10px] font-bold text-gray-400 uppercase">{sub}</p>}
</div>
);
return (
<div className="bg-white rounded-3xl border border-gray-100 shadow-xl overflow-hidden">
<div className="px-6 sm:px-8 py-6 bg-gradient-to-r from-gray-50 to-white border-b border-gray-100 flex flex-wrap justify-between items-center gap-4">
<div className="flex items-center gap-3">
<div className="w-10 h-10 bg-green-600 rounded-xl flex items-center justify-center text-white shadow-lg shadow-green-200"><Wallet size={20} /></div>
<div>
<h3 className="text-lg font-black text-gray-900 uppercase">RELATÓRIO FINANCEIRO</h3>
<p className="text-[10px] text-gray-400 font-bold uppercase tracking-widest">A receber / recebido · por paciente e profissional</p>
</div>
</div>
<button onClick={exportCSV} disabled={!dados?.lancamentos?.length} className="bg-white border border-gray-200 text-gray-600 px-4 py-2 rounded-lg hover:bg-gray-50 flex items-center gap-2 text-[10px] font-black uppercase transition-all shadow-sm disabled:opacity-50">
<Download size={14} /> Exportar
</button>
</div>
{/* Filtros H3.2 */}
<div className="px-6 sm:px-8 py-4 border-b border-gray-100 bg-gray-50/40 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 flex items-center gap-1.5"><Stethoscope size={12} /> Profissional</label>
<select value={profissionalId} onChange={e => setProfissionalId(e.target.value)} className="bg-white border border-gray-200 rounded-xl px-3 py-2.5 text-sm font-bold text-gray-700 outline-none focus:border-teal-400 min-w-[200px]">
<option value="">Todos</option>
{dentistas.map(d => <option key={d.id} value={d.id}>{d.nome}</option>)}
</select>
</div>
<div className="space-y-1.5 relative">
<label className="text-[10px] font-black text-gray-400 uppercase tracking-widest flex items-center gap-1.5"><Users size={12} /> Paciente</label>
{paciente ? (
<div className="flex items-center gap-2 bg-teal-50 border border-teal-200 rounded-xl px-3 py-2.5 min-w-[200px]">
<span className="text-sm font-bold text-teal-700 uppercase truncate flex-1">{paciente.nome}</span>
<button onClick={() => setPaciente(null)} className="text-teal-400 hover:text-teal-600"><X size={16} /></button>
</div>
) : (
<div className="relative">
<Search size={14} className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-300" />
<input ref={buscaRef} value={busca} onChange={e => setBusca(e.target.value)} onFocus={() => setBuscaAberta(true)} onBlur={() => setTimeout(() => setBuscaAberta(false), 200)}
placeholder="Buscar paciente..." className="bg-white border border-gray-200 rounded-xl pl-9 pr-3 py-2.5 text-sm font-bold text-gray-700 outline-none focus:border-teal-400 min-w-[200px]" />
{buscaAberta && busca && (
<ul className="absolute z-20 w-full bg-white border border-gray-200 rounded-xl mt-1 max-h-48 overflow-y-auto shadow-lg">
{resultados.map(p => (
<li key={p.id} onMouseDown={() => { setPaciente({ id: p.id, nome: p.nome }); setBusca(''); }} className="px-3 py-2 hover:bg-gray-50 cursor-pointer text-sm font-semibold text-gray-700 uppercase">{p.nome}</li>
))}
{resultados.length === 0 && <li className="px-3 py-2 text-sm text-gray-400">Nenhum paciente.</li>}
</ul>
)}
</div>
)}
</div>
<p className="text-[10px] font-bold text-gray-400 uppercase ml-auto self-center">Período herda os filtros de data acima</p>
</div>
{loading ? (
<div className="flex justify-center py-16"><Loader2 className="animate-spin text-green-600" size={32} /></div>
) : (
<div className="p-6 sm:p-8 space-y-8">
{/* KPIs */}
<div className="grid grid-cols-2 lg:grid-cols-4 gap-4">
<KPI titulo="Recebido" valor={r?.recebido || 0} cor="text-green-600" Icon={DollarSign} sub="Receitas pagas" />
<KPI titulo="A receber" valor={r?.a_receber || 0} cor="text-amber-500" Icon={Clock} sub="Pendentes" />
<KPI titulo="Atrasado" valor={r?.atrasado || 0} cor="text-red-500" Icon={AlertTriangle} sub="Vencidos" />
{Number(r?.glosa) > 0
? <KPI titulo="Glosa" valor={r?.glosa || 0} cor="text-red-600" Icon={AlertTriangle} sub={`Líquido a receber ${fmtBRL(r?.a_receber_liquido || 0)}`} />
: <KPI titulo="Despesa paga" valor={r?.despesa_paga || 0} cor="text-gray-500" Icon={TrendingUp} sub={`${r?.total_lancamentos || 0} lançamentos`} />}
</div>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
{/* Série por mês */}
<div>
<h4 className="text-[10px] font-black text-gray-400 uppercase tracking-widest mb-4 flex items-center gap-2"><BarChart3 size={14} /> Por competência (mês)</h4>
{(dados?.porMes || []).length === 0 ? <p className="text-sm text-gray-400">Sem dados no período.</p> : (
<div className="space-y-3">
{dados.porMes.map((m: any) => {
const total = Number(m.recebido) + Number(m.a_receber);
return (
<div key={m.mes}>
<div className="flex justify-between text-[10px] font-black text-gray-500 mb-1"><span>{m.mes}</span><span>{fmtBRL(total)}</span></div>
<div className="flex h-3 rounded-full overflow-hidden bg-gray-100">
<div className="bg-green-500" style={{ width: `${(Number(m.recebido) / maxMes) * 100}%` }} title={`Recebido ${fmtBRL(m.recebido)}`} />
<div className="bg-amber-400" style={{ width: `${(Number(m.a_receber) / maxMes) * 100}%` }} title={`A receber ${fmtBRL(m.a_receber)}`} />
</div>
</div>
);
})}
<div className="flex gap-4 pt-2 text-[10px] font-bold text-gray-400">
<span className="flex items-center gap-1.5"><span className="w-3 h-3 rounded bg-green-500" /> Recebido</span>
<span className="flex items-center gap-1.5"><span className="w-3 h-3 rounded bg-amber-400" /> A receber</span>
</div>
</div>
)}
</div>
{/* Por forma de pagamento */}
<div>
<h4 className="text-[10px] font-black text-gray-400 uppercase tracking-widest mb-4 flex items-center gap-2"><CreditCard size={14} /> Recebido por forma de pagamento</h4>
{(dados?.porForma || []).length === 0 ? <p className="text-sm text-gray-400">Nenhum recebimento no período.</p> : (
<div className="space-y-2">
{dados.porForma.map((f: any) => (
<div key={f.forma} className="flex items-center justify-between bg-gray-50 rounded-xl px-4 py-2.5">
<span className="text-xs font-bold text-gray-700 uppercase">{f.forma} <span className="text-gray-400">· {f.qtd}x</span></span>
<span className="text-sm font-black text-green-600">{fmtBRL(f.total)}</span>
</div>
))}
</div>
)}
</div>
</div>
{/* Receitas por convênio */}
{(dados?.porConvenio || []).length > 0 && (
<div>
<h4 className="text-[10px] font-black text-gray-400 uppercase tracking-widest mb-4 flex items-center gap-2"><Users size={14} /> Receitas por convênio</h4>
<div className="overflow-x-auto rounded-2xl border border-gray-100">
<table className="w-full text-left text-xs">
<thead className="bg-gray-50/60">
<tr>{['Convênio', 'Lanç.', 'Recebido', 'A receber', 'Glosa', 'Total'].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">
{dados.porConvenio.map((c: any) => (
<tr key={c.convenio} className="hover:bg-gray-50/50">
<td className="px-4 py-3 font-black text-gray-700 uppercase">{c.convenio}</td>
<td className="px-4 py-3 text-gray-500">{c.qtd}</td>
<td className="px-4 py-3 font-bold text-green-600">{fmtBRL(c.recebido)}</td>
<td className="px-4 py-3 font-bold text-amber-600">{fmtBRL(c.a_receber)}</td>
<td className="px-4 py-3 font-bold text-red-600">{Number(c.glosa) > 0 ? fmtBRL(c.glosa) : '—'}</td>
<td className="px-4 py-3 font-black text-gray-800">{fmtBRL(c.total)}</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
)}
{/* Despesas por centro de custo (H3.3) */}
{(dados?.porCategoria || []).length > 0 && (
<div>
<h4 className="text-[10px] font-black text-gray-400 uppercase tracking-widest mb-4 flex items-center gap-2"><TrendingUp size={14} /> Despesas por centro de custo</h4>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-2">
{dados.porCategoria.map((c: any) => (
<div key={c.centro_custo} className="flex items-center justify-between bg-gray-50 rounded-xl px-4 py-2.5">
<span className="text-xs font-bold text-gray-700 uppercase">{c.centro_custo} <span className="text-gray-400">· {c.qtd}x</span></span>
<span className="text-sm font-black text-red-500">{fmtBRL(c.total)}</span>
</div>
))}
</div>
</div>
)}
{/* Lançamentos */}
<div>
<h4 className="text-[10px] font-black text-gray-400 uppercase tracking-widest mb-3 flex items-center gap-2"><PieChart size={14} /> Lançamentos ({(dados?.lancamentos || []).length})</h4>
<div className="overflow-x-auto rounded-2xl border border-gray-100">
<table className="w-full text-left text-xs">
<thead className="bg-gray-50/60">
<tr>
{['Venc.', 'Descrição', 'Paciente', 'Status', 'Forma', 'Valor'].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">
{(dados?.lancamentos || []).slice(0, 100).map((l: any) => (
<tr key={l.id} className="hover:bg-gray-50/50">
<td className="px-4 py-3 font-bold text-gray-500 whitespace-nowrap">{(l.datavencimento || '').slice(0, 10).split('-').reverse().join('/')}</td>
<td className="px-4 py-3 font-bold text-gray-700">{l.descricao}{l.sem_comissao && <span className="ml-2 text-[9px] font-black px-1.5 py-0.5 rounded bg-gray-100 text-gray-500 uppercase">Sem comissão</span>}</td>
<td className="px-4 py-3 text-gray-500 uppercase">{l.pacientenome || '—'}</td>
<td className="px-4 py-3">
<span className={`px-2 py-0.5 rounded-full text-[9px] font-black uppercase ${l.status === 'Pago' ? 'bg-green-100 text-green-700' : l.status === 'Atrasado' ? 'bg-red-100 text-red-600' : 'bg-amber-100 text-amber-700'}`}>{l.status}</span>
</td>
<td className="px-4 py-3 text-gray-500 uppercase">{l.formapagamento || '—'}</td>
<td className={`px-4 py-3 font-black ${l.tipo === 'DESPESA' ? 'text-red-500' : 'text-gray-800'}`}>{l.tipo === 'DESPESA' ? '-' : ''}{fmtBRL(l.valor)}</td>
</tr>
))}
{(dados?.lancamentos || []).length === 0 && (
<tr><td colSpan={6} className="px-4 py-8 text-center text-gray-400 text-sm">Nenhum lançamento no período/filtro.</td></tr>
)}
</tbody>
</table>
</div>
</div>
</div>
)}
</div>
);
};
interface ReportData {
financeiro: {
receita: number;
despesa: number;
totalTransacoes: number;
};
producao: {
dentista: string;
totalAgendamentos: number;
concluidos: number;
faltas: number;
}[];
}
export const ReportsView: React.FC = () => {
const activeWorkspace = HybridBackend.getActiveWorkspace();
const [startDate, setStartDate] = useState(new Date(new Date().getFullYear(), new Date().getMonth(), 1).toISOString().split('T')[0]);
const [endDate, setEndDate] = useState(new Date().toISOString().split('T')[0]);
const { data, isLoading, error, refresh } = useHybridBackend<ReportData>(() =>
activeWorkspace ? HybridBackend.getProductivityReport(activeWorkspace.id, startDate, endDate) : Promise.resolve(null),
[activeWorkspace?.id, startDate, endDate]
);
const receita = data?.financeiro?.receita || 0;
const despesa = data?.financeiro?.despesa || 0;
const saldo = receita - despesa;
const exportToCSV = () => {
if (!data) return;
let csv = "Dentista,Total,Concluidos,Faltas,Taxa Ocupacao\n";
data.producao.forEach(p => {
const taxa = p.totalAgendamentos > 0 ? ((p.concluidos / p.totalAgendamentos) * 100).toFixed(1) : 0;
csv += `${p.dentista},${p.totalAgendamentos},${p.concluidos},${p.faltas},${taxa}%\n`;
});
const blob = new Blob([csv], { type: 'text/csv' });
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `relatorio_producao_${startDate}_${endDate}.csv`;
a.click();
};
return (
<div className="space-y-8 animate-in fade-in duration-500 pb-10">
<PageHeader title="RELATÓRIOS & PERFORMANCE">
<div className="flex items-center gap-3">
<button onClick={() => window.print()} className="bg-white border border-gray-200 text-gray-600 p-2 rounded-lg hover:bg-gray-50 flex items-center gap-2 text-xs font-bold transition-all shadow-sm">
<Printer size={16} /> <span className="hidden sm:inline">IMPRIMIR</span>
</button>
<button onClick={exportToCSV} className="bg-white border border-gray-200 text-gray-600 p-2 rounded-lg hover:bg-gray-50 flex items-center gap-2 text-xs font-bold transition-all shadow-sm">
<Download size={16} /> <span className="hidden sm:inline">EXPORTAR</span>
</button>
</div>
</PageHeader>
{/* Filters */}
<div className="bg-white p-6 rounded-2xl shadow-sm border border-gray-100 flex flex-wrap items-end gap-6">
<div className="space-y-2">
<label className="text-[10px] font-black text-gray-400 uppercase tracking-widest flex items-center gap-2">
<Calendar size={12} /> INÍCIO DO PERÍODO
</label>
<input
type="date"
value={startDate}
onChange={(e) => setStartDate(e.target.value)}
className="bg-gray-50 border border-gray-200 rounded-xl px-4 py-2.5 text-sm font-bold text-gray-700 focus:ring-2 focus:ring-teal-100 outline-none transition-all"
/>
</div>
<div className="space-y-2">
<label className="text-[10px] font-black text-gray-400 uppercase tracking-widest flex items-center gap-2">
<Calendar size={12} /> FIM DO PERÍODO
</label>
<input
type="date"
value={endDate}
onChange={(e) => setEndDate(e.target.value)}
className="bg-gray-50 border border-gray-200 rounded-xl px-4 py-2.5 text-sm font-bold text-gray-700 focus:ring-2 focus:ring-teal-100 outline-none transition-all"
/>
</div>
<button onClick={refresh} className="bg-teal-600 text-white px-6 py-3 rounded-xl font-black text-xs uppercase hover:bg-teal-700 transition-all shadow-lg shadow-teal-200 flex items-center gap-2">
<Filter size={16} /> FILTRAR
</button>
</div>
{isLoading ? (
<div className="flex flex-col items-center justify-center py-20 gap-4">
<Loader2 className="animate-spin text-teal-600" size={40} />
<p className="text-xs font-black text-gray-400 uppercase tracking-widest">Processando dados clínicos...</p>
</div>
) : (
<div className="space-y-8">
{/* Financial Summary */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
<div className="bg-white p-6 rounded-2xl border border-gray-100 shadow-sm overflow-hidden relative group">
<div className="absolute top-0 right-0 p-4 opacity-10 group-hover:scale-110 transition-transform">
<DollarSign size={80} className="text-green-600" />
</div>
<h4 className="text-[10px] font-black text-gray-400 uppercase tracking-widest">RECEITA NO PERÍODO</h4>
<div className="mt-2 flex items-baseline gap-2">
<span className="text-2xl font-black text-gray-900">R$ {receita.toLocaleString('pt-BR', { minimumFractionDigits: 2 })}</span>
<span className="text-green-500 text-[10px] font-black flex items-center"><ArrowUpRight size={12} /> +2.4%</span>
</div>
<div className="mt-4 w-full bg-gray-50 h-1.5 rounded-full overflow-hidden">
<div className="bg-green-500 h-full w-[70%]"></div>
</div>
</div>
<div className="bg-white p-6 rounded-2xl border border-gray-100 shadow-sm overflow-hidden relative group">
<div className="absolute top-0 right-0 p-4 opacity-10 group-hover:scale-110 transition-transform">
<TrendingUp size={80} className="text-red-600" />
</div>
<h4 className="text-[10px] font-black text-gray-400 uppercase tracking-widest">DESPESA NO PERÍODO</h4>
<div className="mt-2 flex items-baseline gap-2">
<span className="text-2xl font-black text-gray-900">R$ {despesa.toLocaleString('pt-BR', { minimumFractionDigits: 2 })}</span>
<span className="text-red-500 text-[10px] font-black flex items-center"><ArrowDownRight size={12} /> -1.2%</span>
</div>
<div className="mt-4 w-full bg-gray-50 h-1.5 rounded-full overflow-hidden">
<div className="bg-red-500 h-full w-[30%]"></div>
</div>
</div>
<div className="bg-white p-6 rounded-2xl border border-gray-100 shadow-sm overflow-hidden relative group">
<div className="absolute top-0 right-0 p-4 opacity-10 group-hover:scale-110 transition-transform">
<Activity size={80} className="text-teal-600" />
</div>
<h4 className="text-[10px] font-black text-gray-400 uppercase tracking-widest">SALDO OPERACIONAL</h4>
<div className="mt-2">
<span className={`text-2xl font-black ${saldo >= 0 ? 'text-blue-600' : 'text-red-600'}`}>
R$ {saldo.toLocaleString('pt-BR', { minimumFractionDigits: 2 })}
</span>
</div>
<p className="mt-4 text-[10px] font-bold text-gray-400">LIQUIDEZ ATUAL DA UNIDADE</p>
</div>
<div className="bg-white p-6 rounded-2xl border border-gray-100 shadow-sm overflow-hidden relative group">
<div className="absolute top-0 right-0 p-4 opacity-10 group-hover:scale-110 transition-transform">
<Users size={80} className="text-purple-600" />
</div>
<h4 className="text-[10px] font-black text-gray-400 uppercase tracking-widest">TRANSAÇÕES</h4>
<div className="mt-2">
<span className="text-2xl font-black text-gray-900">{data?.financeiro?.totalTransacoes || 0}</span>
</div>
<p className="mt-4 text-[10px] font-bold text-gray-400">MOVIMENTAÇÕES FINANCEIRAS</p>
</div>
</div>
{/* Productivity Table */}
<div className="bg-white rounded-3xl border border-gray-100 shadow-xl overflow-hidden">
<div className="px-8 py-6 bg-gradient-to-r from-gray-50 to-white border-b border-gray-100 flex justify-between items-center">
<div className="flex items-center gap-3">
<div className="w-10 h-10 bg-teal-600 rounded-xl flex items-center justify-center text-white shadow-lg shadow-teal-200">
<BarChart3 size={20} />
</div>
<div>
<h3 className="text-lg font-black text-gray-900 uppercase">PRODUTIVIDADE POR DENTISTA</h3>
<p className="text-[10px] text-gray-400 font-bold uppercase tracking-widest">DESEMPENHO CLÍNICO NO PERÍODO</p>
</div>
</div>
<div className="hidden sm:flex items-center gap-2 px-4 py-2 bg-teal-50 rounded-full">
<PieChart size={16} className="text-teal-600" />
<span className="text-[10px] font-black text-teal-600 uppercase">Visão por Especialista</span>
</div>
</div>
<div className="overflow-x-auto">
<table className="w-full text-left">
<thead className="bg-gray-50/50">
<tr>
<th className="px-8 py-5 text-[10px] font-black text-gray-400 uppercase tracking-widest">ESPECIALISTA</th>
<th className="px-8 py-5 text-[10px] font-black text-gray-400 uppercase tracking-widest">TOTAL APPTS</th>
<th className="px-8 py-5 text-[10px] font-black text-gray-400 uppercase tracking-widest">EFETIVADOS</th>
<th className="px-8 py-5 text-[10px] font-black text-gray-400 uppercase tracking-widest">FALTAS</th>
<th className="px-8 py-5 text-[10px] font-black text-gray-400 uppercase tracking-widest">TAXA OCUPAÇÃO</th>
<th className="px-8 py-5 text-[10px] font-black text-gray-400 uppercase tracking-widest">STATUS</th>
</tr>
</thead>
<tbody className="divide-y divide-gray-100">
{(data?.producao || []).map((p, idx) => {
const occupancy = p.totalAgendamentos > 0 ? (p.concluidos / p.totalAgendamentos) * 100 : 0;
return (
<tr key={idx} className="hover:bg-teal-50/30 transition-colors group">
<td className="px-8 py-5">
<div className="flex items-center gap-3">
<div className="w-8 h-8 rounded-lg bg-gray-100 flex items-center justify-center text-gray-400 font-black text-xs group-hover:bg-teal-600 group-hover:text-white transition-all">
{p.dentista.substring(0, 2).toUpperCase()}
</div>
<span className="font-bold text-gray-900 uppercase text-xs">{p.dentista}</span>
</div>
</td>
<td className="px-8 py-5 font-bold text-gray-600 text-xs">{p.totalAgendamentos}</td>
<td className="px-8 py-5 font-bold text-green-600 text-xs">{p.concluidos}</td>
<td className="px-8 py-5 font-bold text-red-500 text-xs">{p.faltas}</td>
<td className="px-8 py-5">
<div className="flex items-center gap-3 min-w-[120px]">
<div className="flex-1 h-2 bg-gray-100 rounded-full overflow-hidden shadow-inner">
<div
className={`h-full transition-all duration-1000 ${occupancy > 70 ? 'bg-green-500' : occupancy > 40 ? 'bg-teal-500' : 'bg-teal-400'}`}
style={{ width: `${occupancy}%` }}
></div>
</div>
<span className="text-[10px] font-black text-gray-400">{occupancy.toFixed(0)}%</span>
</div>
</td>
<td className="px-8 py-5">
{occupancy > 75 ? (
<span className="px-2 py-1 bg-green-50 text-green-600 text-[9px] font-black rounded-lg uppercase border border-green-100">Alta Performance</span>
) : occupancy > 30 ? (
<span className="px-2 py-1 bg-blue-50 text-blue-600 text-[9px] font-black rounded-lg uppercase border border-blue-100">Estável</span>
) : (
<span className="px-2 py-1 bg-gray-50 text-gray-400 text-[9px] font-black rounded-lg uppercase border border-gray-100">Baixa Ocupação</span>
)}
</td>
</tr>
);
})}
</tbody>
</table>
</div>
</div>
{/* Relatório financeiro (H3.1 + H3.2) */}
<RelatorioFinanceiro inicio={startDate} fim={endDate} />
</div>
)}
</div>
);
};