testes/src/features/financeiro-v2/views/conciliacao-v2/TransacoesNaoCategorizadasV...

192 lines
10 KiB
JavaScript

import React from 'react';
import {
AlertCircle,
Receipt,
Tag,
Plus,
Search,
X,
Save,
Sparkles,
Zap,
RotateCcw
} from 'lucide-react';
import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge';
import { Input } from '@/components/ui/input';
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from '@/components/ui/dialog';
import { Label } from '@/components/ui/label';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { cn } from '@/lib/utils';
import ExcelTable from '../../components/ExcelTable';
import { useToast } from '../../hooks/useToast';
import { formatDate, formatCurrency } from '../../utils/dateUtils';
import { StatementRow } from '../../components/StatementRow';
import { useStatementRefData } from '../../hooks/useStatementRefData';
import { CategorizacaoDialog } from '../../components/CategorizacaoDialog';
export function TransacoesNaoCategorizadasView({ state, actions }) {
// IMPORTANTE: Todos os hooks devem ser chamados incondicionalmente no topo
// NUNCA colocar hooks após early returns ou condições
// Hooks de estado
const [searchTerm, setSearchTerm] = React.useState('');
const [transacaoSelecionada, setTransacaoSelecionada] = React.useState(null);
const [isDialogOpen, setIsDialogOpen] = React.useState(false);
const [isSubmitting, setIsSubmitting] = React.useState(false);
// Hook customizado
const toast = useToast();
const { getCategoryName, getRuleName } = useStatementRefData();
// Extrair dados do state APÓS todos os hooks
const { transacoesNaoCategorizadas = [], caixas = [], categorias = [], isReconciliando = false, progressoReconciliacao = { processados: 0, total: 0 } } = state || {};
// Logs para debug dos dados recebidos
React.useEffect(() => {
console.log('[TransacoesNaoCategorizadasView] ========== DADOS RECEBIDOS DO STATE ==========');
console.log('[TransacoesNaoCategorizadasView] State completo:', state);
console.log('[TransacoesNaoCategorizadasView] Categorias recebidas:', {
count: categorias?.length || 0,
categorias: categorias,
isArray: Array.isArray(categorias),
firstItem: categorias?.[0]
});
console.log('[TransacoesNaoCategorizadasView] Caixas recebidas:', {
count: caixas?.length || 0,
caixas: caixas,
isArray: Array.isArray(caixas),
firstItem: caixas?.[0]
});
}, [state, categorias, caixas]);
// Logs após todos os hooks - removido dependências de objetos para evitar re-renders infinitos
React.useEffect(() => {
console.log('[TransacoesNaoCategorizadasView] ========== COMPONENTE RENDERIZADO ==========');
console.log('[TransacoesNaoCategorizadasView] Estado:', {
isDialogOpen,
hasTransacaoSelecionada: !!transacaoSelecionada
});
}, [isDialogOpen, transacaoSelecionada]);
const filteredTransacoes = React.useMemo(() => {
if (!transacoesNaoCategorizadas || !Array.isArray(transacoesNaoCategorizadas)) {
return [];
}
return transacoesNaoCategorizadas.filter(t =>
(t.descricao || '').toLowerCase().includes((searchTerm || '').toLowerCase()) ||
(t.id || '').toString().includes(searchTerm || '')
);
}, [transacoesNaoCategorizadas, searchTerm]);
// Filtrar categorias pelo tipo da transação: CRÉDITO → apenas entrada, DÉBITO → apenas saída
const categoriasFiltradasPorTipo = React.useMemo(() => {
if (!categorias || !Array.isArray(categorias)) return [];
const tipoTransacao = transacaoSelecionada?.tipo?.toUpperCase?.();
const tipoCategoriaDesejado = tipoTransacao === 'CREDITO' ? 'entrada' : 'saida';
return categorias.filter(cat => {
const tipoCat = (cat?.tipo ?? cat?.tipoMovimento ?? '').toString().toLowerCase();
return tipoCat === tipoCategoriaDesejado;
});
}, [categorias, transacaoSelecionada?.tipo]);
const handleAbrirDialog = (transacao) => {
setTransacaoSelecionada(transacao);
setIsDialogOpen(true);
};
return (
<div className="animate-in fade-in duration-700 space-y-6">
<Card className="bg-white dark:bg-[#1e293b]/40 backdrop-blur-md border-slate-200 dark:border-slate-800 shadow-xl rounded-lg overflow-hidden">
<CardHeader className="py-4 px-4 sm:px-6 border-b border-slate-200 dark:border-slate-800/50">
<div className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-4">
<div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-xl bg-amber-500/10 dark:bg-amber-500/10 flex items-center justify-center border border-amber-500/20 shrink-0">
<AlertCircle className="w-5 h-5 text-amber-500" />
</div>
<div>
<h3 className="font-bold text-slate-900 dark:text-white flex items-center gap-2 flex-wrap text-base sm:text-lg">
Pendentes
<Badge variant="outline" className="text-[10px] border-slate-300 dark:border-slate-700 text-slate-600 dark:text-slate-400 bg-slate-50 dark:bg-slate-900/50">
{filteredTransacoes.length} TRANSAÇÕES
</Badge>
</h3>
<p className="text-[10px] text-slate-500 dark:text-slate-400 font-medium uppercase tracking-wider hidden sm:block">
Transações que precisam de categorização
</p>
</div>
</div>
<div className="flex items-center gap-2 w-full sm:w-auto flex-wrap">
<div className="relative group flex-1 sm:flex-initial min-w-[300px]">
<Search className="w-3.5 h-3.5 absolute left-3 top-1/2 -translate-y-1/2 text-slate-400 dark:text-slate-500" />
<Input
placeholder="Pesquisar transação..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="bg-white dark:bg-slate-900/80 border-slate-200 dark:border-slate-700 pl-9 h-9 text-xs text-slate-900 dark:text-white placeholder:text-slate-400 dark:placeholder:text-slate-600 focus:border-amber-500/50 rounded-lg w-full sm:w-96"
/>
</div>
<div className="flex items-center gap-2">
{/* <Button
size="sm"
onClick={() => actions?.executarConciliacaoAutomatica?.()}
disabled={isReconciliando}
className="bg-blue-600 hover:bg-blue-700 text-white text-xs sm:text-sm disabled:opacity-50 disabled:cursor-not-allowed"
>
<Zap className="w-3 h-3 sm:w-4 sm:h-4 mr-1 sm:mr-2" />
<span className="hidden sm:inline">
{isReconciliando
? `Conciliação... (${progressoReconciliacao.processados}/${progressoReconciliacao.total})`
: 'Conciliação Automática'
}
</span>
<span className="sm:hidden">
{isReconciliando ? `${progressoReconciliacao.processados}/${progressoReconciliacao.total}` : 'Auto'}
</span>
</Button> */}
</div>
</div>
</div>
</CardHeader>
<CardContent className="p-0">
<div className="h-[600px]">
<div className="divide-y divide-slate-100 dark:divide-slate-800 overflow-auto h-[600px] custom-scrollbar">
{filteredTransacoes.length > 0 ? (
filteredTransacoes.map((row, index) => (
<StatementRow
key={row.id || row.idextrato || index}
transaction={row}
categoryName={getCategoryName(row.categoria)}
ruleName={getRuleName(row.regra)}
onClick={() => handleAbrirDialog(row)}
showCategory={true}
showStatus={false}
showBeneficiary={true}
className="cursor-pointer hover:bg-slate-50 dark:hover:bg-slate-800/10"
/>
))
) : (
<div className="flex flex-col items-center justify-center h-full text-slate-500">
<Receipt className="h-12 w-12 mb-4 opacity-20" />
<p>Nenhuma transação pendente encontrada</p>
</div>
)}
</div>
</div>
</CardContent>
</Card>
<CategorizacaoDialog
transacao={transacaoSelecionada}
isOpen={isDialogOpen}
onOpenChange={setIsDialogOpen}
categorias={categorias}
caixas={caixas}
actions={actions}
/>
</div>
);
};