feat(financeiro): modal de lançamento de procedimento completo

- Catálogo de procedimentos por especialidade com busca e filtros
- Preço automático pelo convênio/plano do paciente (valoresPlanos)
- Campos de região: dente (texto livre), arcada (Superior/Inferior/Ambas),
  face (V/L/M/D/O multi-select) conforme tipo_regiao e exige_face
- Quantidade por item com +/-
- Painel financeiro: desconto (R$ ou %), entrada (auto-marcada Pago),
  parcelamento até 48x com vencimentos mensais automáticos
- Ao salvar: gera uma entrada de financeiro por parcela + entrada separada
- Integrado no botão LANÇAR do menu financeiro dos pacientes

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
VPS 4 Builder
2026-05-14 18:29:10 +02:00
parent 6cb8169d62
commit d6d2bc1e78
2 changed files with 454 additions and 1 deletions
+447
View File
@@ -0,0 +1,447 @@
import React, { useState, useMemo } from 'react';
import {
X, Search, Plus, Minus, Trash2, DollarSign, Check,
ChevronDown, Loader2, Tag, Percent, Banknote
} from 'lucide-react';
import { HybridBackend } from '../services/backend.ts';
import { Paciente, Procedimento, Especialidade, Plano } from '../types.ts';
import { useHybridBackend } from '../hooks/useHybridBackend.ts';
import { useToast } from '../contexts/ToastContext.tsx';
// ─── Tipos internos ────────────────────────────────────────────────────────────
interface LancItem {
uid: string;
proc: Procedimento;
quantidade: number;
dente: string;
face: string;
arco: string;
observacao: string;
valorUnit: number;
}
const FACES = ['V', 'L', 'M', 'D', 'O'];
const ARCOS = ['Superior', 'Inferior', 'Ambas'];
const FORMAS_PAG = ['Pix', 'Cartão', 'Dinheiro', 'Boleto'] as const;
const STATUS_LIST = ['Pendente', 'Pago', 'Atrasado'] as const;
const fmt = (v: number) => v.toLocaleString('pt-BR', { style: 'currency', currency: 'BRL' });
function addMonths(date: string, n: number): string {
const d = new Date(date + 'T12:00:00');
d.setMonth(d.getMonth() + n);
return d.toISOString().slice(0, 10);
}
// ─── Chip de procedimento selecionado ─────────────────────────────────────────
const ItemCard: React.FC<{
item: LancItem;
onChange: (patch: Partial<LancItem>) => void;
onRemove: () => void;
}> = ({ item, onChange, onRemove }) => {
const [open, setOpen] = useState(item.proc.tipo_regiao !== 'GERAL');
const inp = "border border-gray-200 rounded-lg px-2 py-1.5 text-xs focus:ring-2 focus:ring-blue-100 outline-none w-full";
return (
<div className="border border-gray-200 rounded-xl bg-white overflow-hidden">
<div className="flex items-center gap-2 px-3 py-2.5">
<div className="flex-1 min-w-0">
<p className="text-xs font-black text-gray-800 uppercase truncate">{item.proc.nome}</p>
<div className="flex items-center gap-2 mt-0.5">
{item.proc.especialidadeNome && (
<span className="text-[9px] bg-blue-50 text-blue-600 px-1.5 py-0.5 rounded-full font-bold uppercase">{item.proc.especialidadeNome}</span>
)}
<span className="text-[10px] font-bold text-green-600">{fmt(item.valorUnit * item.quantidade)}</span>
{item.quantidade > 1 && <span className="text-[9px] text-gray-400">{item.quantidade}× {fmt(item.valorUnit)}</span>}
</div>
</div>
<div className="flex items-center gap-1 flex-shrink-0">
<button onClick={() => onChange({ quantidade: Math.max(1, item.quantidade - 1) })} className="w-6 h-6 rounded-full border border-gray-200 flex items-center justify-center hover:bg-gray-100"><Minus size={10} /></button>
<span className="w-5 text-center text-xs font-bold">{item.quantidade}</span>
<button onClick={() => onChange({ quantidade: item.quantidade + 1 })} className="w-6 h-6 rounded-full border border-gray-200 flex items-center justify-center hover:bg-gray-100"><Plus size={10} /></button>
</div>
<button onClick={() => setOpen(o => !o)} className="text-gray-300 hover:text-gray-600 flex-shrink-0">
<ChevronDown size={14} className={`transition-transform ${open ? 'rotate-180' : ''}`} />
</button>
<button onClick={onRemove} className="text-gray-300 hover:text-red-500 flex-shrink-0"><Trash2 size={13} /></button>
</div>
{open && (
<div className="border-t border-gray-100 bg-gray-50/50 px-3 py-2.5 grid grid-cols-2 gap-2">
{item.proc.tipo_regiao === 'DENTE' && (
<div>
<label className="text-[9px] font-black text-gray-400 uppercase block mb-1">Dente(s)</label>
<input value={item.dente} onChange={e => onChange({ dente: e.target.value })} className={inp} placeholder="ex: 16, 21-22" />
</div>
)}
{item.proc.tipo_regiao === 'ARCO' && (
<div>
<label className="text-[9px] font-black text-gray-400 uppercase block mb-1">Arcada</label>
<select value={item.arco} onChange={e => onChange({ arco: e.target.value })} className={inp}>
<option value=""> Selecione </option>
{ARCOS.map(a => <option key={a}>{a}</option>)}
</select>
</div>
)}
{item.proc.exige_face && (
<div>
<label className="text-[9px] font-black text-gray-400 uppercase block mb-1">Face(s)</label>
<div className="flex gap-1 flex-wrap">
{FACES.map(f => (
<button key={f} type="button"
onClick={() => {
const faces = item.face.split('').filter(Boolean);
const next = faces.includes(f) ? faces.filter(x => x !== f) : [...faces, f];
onChange({ face: next.join('') });
}}
className={`w-7 h-7 rounded-lg text-[10px] font-black border transition-colors ${item.face.includes(f) ? 'bg-blue-600 text-white border-blue-600' : 'bg-white text-gray-500 border-gray-200 hover:border-blue-400'}`}>
{f}
</button>
))}
</div>
</div>
)}
<div className={item.proc.tipo_regiao !== 'GERAL' || item.proc.exige_face ? '' : 'col-span-2'}>
<label className="text-[9px] font-black text-gray-400 uppercase block mb-1">Obs</label>
<input value={item.observacao} onChange={e => onChange({ observacao: e.target.value })} className={inp} placeholder="observação opcional" />
</div>
</div>
)}
</div>
);
};
// ─── Modal principal ──────────────────────────────────────────────────────────
export const LancarProcedimentoModal: React.FC<{
patient: Paciente;
onClose: () => void;
onSaved: () => void;
}> = ({ patient, onClose, onSaved }) => {
const toast = useToast();
const { data: procsRaw } = useHybridBackend<Procedimento[]>(HybridBackend.getProcedimentos);
const { data: espsRaw } = useHybridBackend<Especialidade[]>(HybridBackend.getEspecialidades);
const { data: planosRaw } = useHybridBackend<Plano[]>(HybridBackend.getPlanos);
const procs = procsRaw ?? [];
const esps = espsRaw ?? [];
const planos = planosRaw ?? [];
const [busca, setBusca] = useState('');
const [espFiltro, setEspFiltro] = useState('');
const [items, setItems] = useState<LancItem[]>([]);
const [saving, setSaving] = useState(false);
// Pagamento
const [descTipo, setDescTipo] = useState<'R$' | '%'>('R$');
const [descValor, setDescValor] = useState('');
const [entrada, setEntrada] = useState('');
const [parcelas, setParcelas] = useState(1);
const [primVenc, setPrimVenc] = useState(new Date().toISOString().slice(0, 10));
const [formaPag, setFormaPag] = useState<typeof FORMAS_PAG[number]>('Pix');
const [statusPag, setStatusPag] = useState<typeof STATUS_LIST[number]>('Pendente');
// Plano vigente do paciente
const planoVigente = useMemo(() => {
if (!patient.convenio) return null;
return planos.find(p => p.nome === patient.convenio) ?? null;
}, [patient.convenio, planos]);
const getValorProc = (proc: Procedimento): number => {
if (planoVigente) {
const vp = proc.valoresPlanos?.find(v => v.planoId === planoVigente.id);
if (vp) return vp.valor;
}
return proc.valorParticular ?? 0;
};
const addProc = (proc: Procedimento) => {
if (items.some(i => i.proc.id === proc.id)) {
setItems(prev => prev.map(i => i.proc.id === proc.id ? { ...i, quantidade: i.quantidade + 1 } : i));
return;
}
setItems(prev => [...prev, {
uid: `${proc.id}_${Date.now()}`,
proc, quantidade: 1,
dente: '', face: '', arco: '', observacao: '',
valorUnit: getValorProc(proc),
}]);
};
const updateItem = (uid: string, patch: Partial<LancItem>) =>
setItems(prev => prev.map(i => i.uid === uid ? { ...i, ...patch } : i));
const removeItem = (uid: string) => setItems(prev => prev.filter(i => i.uid !== uid));
const subtotal = items.reduce((s, i) => s + i.valorUnit * i.quantidade, 0);
const descNum = parseFloat(descValor) || 0;
const descReais = descTipo === '%' ? subtotal * descNum / 100 : descNum;
const entradaNum = parseFloat(entrada) || 0;
const restante = Math.max(0, subtotal - descReais - entradaNum);
const valorParcela = parcelas > 0 ? restante / parcelas : 0;
const procsFiltrados = useMemo(() => {
return procs.filter(p => {
if (espFiltro && p.especialidadeId !== espFiltro) return false;
if (busca && !p.nome.toLowerCase().includes(busca.toLowerCase())) return false;
return true;
});
}, [procs, espFiltro, busca]);
const procsPorEsp = useMemo(() => {
const grupos: Record<string, { esp: Especialidade | null; procs: Procedimento[] }> = {};
procsFiltrados.forEach(p => {
const key = p.especialidadeId || '__geral';
if (!grupos[key]) {
grupos[key] = { esp: esps.find(e => e.id === p.especialidadeId) ?? null, procs: [] };
}
grupos[key].procs.push(p);
});
return Object.values(grupos).sort((a, b) => (a.esp?.ordem ?? 99) - (b.esp?.ordem ?? 99));
}, [procsFiltrados, esps]);
const handleSave = async () => {
if (items.length === 0) { toast.error('SELECIONE AO MENOS UM PROCEDIMENTO'); return; }
setSaving(true);
try {
const entriesToCreate: any[] = [];
const baseDesc = items.map(i => {
const region = [i.dente, i.arco, i.face].filter(Boolean).join('/');
return i.proc.nome + (region ? ` (${region})` : '') + (i.quantidade > 1 ? ` x${i.quantidade}` : '');
}).join(', ');
if (entradaNum > 0) {
entriesToCreate.push({
id: crypto.randomUUID(),
pacienteNome: patient.nome,
descricao: `ENTRADA — ${baseDesc}`,
valor: entradaNum,
dataVencimento: primVenc,
status: 'Pago',
formaPagamento: formaPag,
});
}
for (let i = 0; i < parcelas; i++) {
const label = parcelas > 1 ? `${i + 1}/${parcelas}` : '';
entriesToCreate.push({
id: crypto.randomUUID(),
pacienteNome: patient.nome,
descricao: baseDesc + label,
valor: parseFloat(valorParcela.toFixed(2)),
dataVencimento: entradaNum > 0 ? addMonths(primVenc, i + 1) : addMonths(primVenc, i),
status: i === 0 && parcelas === 1 ? statusPag : (i === 0 ? statusPag : 'Pendente'),
formaPagamento: formaPag,
});
}
for (const entry of entriesToCreate) {
await HybridBackend.saveFinanceiro(entry);
}
toast.success(`${entriesToCreate.length} LANÇAMENTO(S) GERADO(S) COM SUCESSO!`);
onSaved();
onClose();
} catch { toast.error('ERRO AO SALVAR LANÇAMENTOS'); } finally { setSaving(false); }
};
const inp = "border border-gray-200 rounded-lg px-2.5 py-2 text-sm focus:ring-2 focus:ring-blue-100 outline-none w-full bg-white";
const jaAdicionado = (id: string) => items.some(i => i.proc.id === id);
return (
<div className="fixed inset-0 bg-black/50 z-50 flex items-center justify-center backdrop-blur-sm p-0">
<div className="bg-white shadow-2xl w-full h-dvh md:w-[96vw] md:h-[98vh] rounded-none md:rounded-2xl flex flex-col overflow-hidden animate-in fade-in zoom-in-95 duration-200">
{/* Header */}
<div className="flex-shrink-0 px-5 py-4 border-b border-gray-100 flex items-center justify-between bg-gradient-to-r from-emerald-50 to-white">
<div className="flex items-center gap-3">
<div className="w-9 h-9 bg-emerald-100 rounded-xl flex items-center justify-center">
<DollarSign size={18} className="text-emerald-600" />
</div>
<div>
<h2 className="text-sm font-black text-gray-800 uppercase">Lançar Procedimento</h2>
<p className="text-[10px] text-gray-400 uppercase font-medium">
{patient.nome}
{planoVigente && <span className="ml-2 bg-emerald-100 text-emerald-700 px-1.5 py-0.5 rounded-full font-bold">{planoVigente.nome}</span>}
</p>
</div>
</div>
<button onClick={onClose} className="text-gray-400 hover:text-gray-600 p-1.5 hover:bg-gray-100 rounded-lg transition-colors"><X size={18} /></button>
</div>
{/* Body */}
<div className="flex flex-1 overflow-hidden">
{/* ── Catálogo (esquerda) ── */}
<div className="w-72 flex-shrink-0 border-r border-gray-100 flex flex-col bg-gray-50/30">
{/* Busca + filtro especialidade */}
<div className="p-3 space-y-2 border-b border-gray-100 flex-shrink-0">
<div className="relative">
<Search size={13} className="absolute left-2.5 top-1/2 -translate-y-1/2 text-gray-400" />
<input value={busca} onChange={e => setBusca(e.target.value)} placeholder="Buscar procedimento..."
className="w-full pl-8 pr-3 py-2 border border-gray-200 rounded-lg text-xs focus:ring-2 focus:ring-blue-100 outline-none bg-white" />
</div>
<div className="flex gap-1 flex-wrap">
<button onClick={() => setEspFiltro('')}
className={`text-[10px] font-bold px-2 py-0.5 rounded-full border transition-colors ${!espFiltro ? 'bg-gray-800 text-white border-gray-800' : 'text-gray-500 border-gray-200 hover:bg-gray-100'}`}>
Todas
</button>
{esps.map(e => (
<button key={e.id} onClick={() => setEspFiltro(f => f === e.id ? '' : e.id)}
className={`text-[10px] font-bold px-2 py-0.5 rounded-full border transition-colors ${espFiltro === e.id ? 'bg-blue-600 text-white border-blue-600' : 'text-gray-500 border-gray-200 hover:bg-gray-100'}`}>
{e.nome}
</button>
))}
</div>
</div>
{/* Lista procedimentos */}
<div className="flex-1 overflow-y-auto p-2 space-y-3">
{!procsRaw && <div className="flex justify-center pt-8"><Loader2 size={18} className="animate-spin text-gray-300" /></div>}
{procsPorEsp.map((grupo, gi) => (
<div key={gi}>
{grupo.esp && (
<p className="text-[9px] font-black text-gray-400 uppercase tracking-widest px-2 mb-1">{grupo.esp.nome}</p>
)}
<div className="space-y-0.5">
{grupo.procs.map(p => {
const val = getValorProc(p);
const adicionado = jaAdicionado(p.id);
return (
<button key={p.id} onClick={() => addProc(p)}
className={`w-full text-left px-2.5 py-2 rounded-lg transition-all flex items-center justify-between gap-2 group ${adicionado ? 'bg-emerald-50 border border-emerald-200' : 'border border-transparent hover:bg-white hover:border-gray-200 hover:shadow-sm'}`}>
<div className="min-w-0 flex-1">
<p className={`text-xs font-bold truncate ${adicionado ? 'text-emerald-700' : 'text-gray-700'}`}>{p.nome}</p>
{p.codigo && <p className="text-[9px] text-gray-400">{p.codigo}</p>}
</div>
<div className="flex items-center gap-1.5 flex-shrink-0">
<span className={`text-[10px] font-black ${adicionado ? 'text-emerald-600' : 'text-green-600'}`}>{fmt(val)}</span>
{adicionado
? <Check size={13} className="text-emerald-500" />
: <Plus size={13} className="text-gray-300 group-hover:text-blue-500" />}
</div>
</button>
);
})}
</div>
</div>
))}
{procsRaw && procsFiltrados.length === 0 && (
<p className="text-xs text-gray-400 text-center pt-6">Nenhum procedimento encontrado</p>
)}
</div>
</div>
{/* ── Selecionados + pagamento (direita) ── */}
<div className="flex-1 flex flex-col overflow-hidden">
{/* Items selecionados */}
<div className="flex-1 overflow-y-auto p-4 space-y-2">
{items.length === 0 ? (
<div className="flex flex-col items-center justify-center h-full text-center">
<div className="w-16 h-16 bg-gray-100 rounded-2xl flex items-center justify-center mb-3">
<Tag size={24} className="text-gray-300" />
</div>
<p className="text-sm font-bold text-gray-400">Nenhum procedimento selecionado</p>
<p className="text-xs text-gray-300 mt-1">Clique nos procedimentos ao lado para adicionar</p>
</div>
) : (
items.map(item => (
<ItemCard key={item.uid} item={item}
onChange={patch => updateItem(item.uid, patch)}
onRemove={() => removeItem(item.uid)} />
))
)}
</div>
{/* ── Painel financeiro ── */}
<div className="flex-shrink-0 border-t border-gray-100 bg-gray-50/50 p-4 space-y-3">
{/* Subtotal */}
<div className="flex items-center justify-between text-sm">
<span className="font-bold text-gray-500 uppercase text-xs">Subtotal</span>
<span className="font-black text-gray-800 text-base">{fmt(subtotal)}</span>
</div>
{/* Desconto + entrada */}
<div className="grid grid-cols-2 gap-3">
<div>
<label className="text-[9px] font-black text-gray-400 uppercase block mb-1">Desconto</label>
<div className="flex gap-1">
<button onClick={() => setDescTipo(t => t === 'R$' ? '%' : 'R$')}
className="px-2 py-2 border border-gray-200 rounded-lg text-[10px] font-black text-gray-500 hover:bg-gray-100 flex-shrink-0 bg-white">
{descTipo === 'R$' ? <Banknote size={12} /> : <Percent size={12} />}
</button>
<input type="number" step="0.01" value={descValor} onChange={e => setDescValor(e.target.value)}
placeholder="0" className={inp + " text-sm"} />
</div>
{descReais > 0 && <p className="text-[10px] text-red-500 mt-0.5"> {fmt(descReais)}</p>}
</div>
<div>
<label className="text-[9px] font-black text-gray-400 uppercase block mb-1">Entrada (R$)</label>
<input type="number" step="0.01" value={entrada} onChange={e => setEntrada(e.target.value)}
placeholder="0" className={inp} />
{entradaNum > 0 && <p className="text-[10px] text-blue-500 mt-0.5"> {fmt(entradaNum)} (marca como Pago)</p>}
</div>
</div>
{/* Restante + parcelas */}
<div className="bg-white rounded-xl border border-gray-200 p-3 space-y-2.5">
<div className="flex items-center justify-between">
<span className="text-xs font-black text-gray-500 uppercase">Restante a parcelar</span>
<span className="text-lg font-black text-emerald-600">{fmt(restante)}</span>
</div>
<div className="grid grid-cols-3 gap-2">
<div>
<label className="text-[9px] font-black text-gray-400 uppercase block mb-1">Parcelas</label>
<select value={parcelas} onChange={e => setParcelas(Number(e.target.value))} className={inp + " text-xs"}>
{Array.from({ length: 48 }, (_, i) => i + 1).map(n => (
<option key={n} value={n}>{n}x {fmt(restante / n)}</option>
))}
</select>
</div>
<div>
<label className="text-[9px] font-black text-gray-400 uppercase block mb-1">1ª Parcela</label>
<input type="date" value={primVenc} onChange={e => setPrimVenc(e.target.value)} className={inp + " text-xs"} />
</div>
<div>
<label className="text-[9px] font-black text-gray-400 uppercase block mb-1">Forma</label>
<select value={formaPag} onChange={e => setFormaPag(e.target.value as any)} className={inp + " text-xs"}>
{FORMAS_PAG.map(f => <option key={f}>{f}</option>)}
</select>
</div>
</div>
{parcelas > 1 && (
<div className="flex items-center justify-between bg-emerald-50 rounded-lg px-3 py-2">
<span className="text-xs font-bold text-emerald-700">{parcelas}x de</span>
<span className="text-sm font-black text-emerald-700">{fmt(valorParcela)}</span>
</div>
)}
<div className="grid grid-cols-2 gap-2">
<div>
<label className="text-[9px] font-black text-gray-400 uppercase block mb-1">Status 1ª parcela</label>
<select value={statusPag} onChange={e => setStatusPag(e.target.value as any)} className={inp + " text-xs"}>
{STATUS_LIST.map(s => <option key={s}>{s}</option>)}
</select>
</div>
<div className="flex items-end">
<div className="w-full bg-gray-50 border border-gray-100 rounded-lg p-2 text-center">
<p className="text-[9px] font-black text-gray-400 uppercase">Total geral</p>
<p className="text-sm font-black text-gray-800">{fmt(entradaNum + restante)}</p>
</div>
</div>
</div>
</div>
{/* Ações */}
<div className="flex gap-2 pt-1">
<button onClick={onClose} className="px-5 py-2.5 bg-gray-100 hover:bg-gray-200 text-gray-700 font-bold rounded-lg text-xs uppercase flex-shrink-0">Cancelar</button>
<button onClick={handleSave} disabled={items.length === 0 || saving}
className="flex-1 py-2.5 bg-emerald-600 hover:bg-emerald-700 text-white font-black rounded-lg text-xs uppercase disabled:opacity-40 flex items-center justify-center gap-2 transition-colors">
{saving ? <Loader2 size={14} className="animate-spin" /> : <Check size={14} />}
{saving ? 'Salvando...' : `Confirmar ${items.length > 0 ? `${fmt(entradaNum + restante)}` : ''}`}
</button>
</div>
</div>
</div>
</div>
</div>
</div>
);
};
+7 -1
View File
@@ -751,6 +751,7 @@ import { PageHeader } from '../components/PageHeader.tsx';
import { ReceitaModal } from './ReceitaModal.tsx';
import { PedidoExameModal } from './PedidoExameModal.tsx';
import { useGTOStore } from './LancarGTO.tsx';
import { LancarProcedimentoModal } from './LancarProcedimentoModal.tsx';
// ----------- TRATAMENTOS MODAL -----------
const TratamentosModal: React.FC<{ patient: Paciente; onClose: () => void }> = ({ patient, onClose }) => {
@@ -1207,6 +1208,11 @@ export const PatientsView: React.FC = () => {
<RelatorioPacienteModal patient={modal.patient} onClose={closeModal} />
)}
{/* LANÇAR PROCEDIMENTO MODAL */}
{modal.type === 'lancar' && modal.patient && (
<LancarProcedimentoModal patient={modal.patient} onClose={closeModal} onSaved={refresh} />
)}
{/* MODAL MENU FINANCEIRO */}
{modal.type === 'financial' && modal.patient && (
<div className="fixed inset-0 bg-black bg-opacity-40 z-50 flex items-center justify-center backdrop-blur-sm p-0">
@@ -1218,7 +1224,7 @@ export const PatientsView: React.FC = () => {
<p className="text-gray-500 text-sm mb-8 font-bold uppercase">{modal.patient.nome}</p>
<div className="grid grid-cols-3 gap-4">
<button onClick={() => toast.info?.('Em breve: Lançar Procedimento')} className="flex flex-col items-center justify-center p-6 bg-white border border-gray-200 rounded-xl hover:border-green-400 hover:bg-green-50/50 transition-all group shadow-sm"><PlusCircle size={32} className="text-green-500 mb-2 group-hover:scale-110 transition-transform" /><span className="font-bold text-gray-700 text-sm">LANÇAR</span></button>
<button onClick={() => setModal({ type: 'lancar', patient: modal.patient })} className="flex flex-col items-center justify-center p-6 bg-white border border-gray-200 rounded-xl hover:border-green-400 hover:bg-green-50/50 transition-all group shadow-sm"><PlusCircle size={32} className="text-green-500 mb-2 group-hover:scale-110 transition-transform" /><span className="font-bold text-gray-700 text-sm">LANÇAR</span></button>
<button onClick={() => setModal({ type: 'asaas', patient: modal.patient })} className="flex flex-col items-center justify-center p-6 bg-white border border-gray-200 rounded-xl hover:border-blue-400 hover:bg-blue-50/50 transition-all group shadow-sm"><Banknote size={32} className="text-blue-600 mb-2 group-hover:scale-110 transition-transform" /><span className="font-bold text-gray-700 text-sm">ASAAS</span></button>
<button onClick={() => setModal({ type: 'historico', patient: modal.patient })} className="flex flex-col items-center justify-center p-6 bg-white border border-gray-200 rounded-xl hover:border-gray-400 hover:bg-gray-50/50 transition-all group shadow-sm"><History size={32} className="text-gray-500 mb-2 group-hover:scale-110 transition-transform" /><span className="font-bold text-gray-700 text-sm">HISTÓRICO</span></button>
</div>