192 lines
10 KiB
JavaScript
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>
|
|
);
|
|
};
|