Permitir selecionar transações

Adiciona opção de marcar quais transações importar na tela de preview, incluindo:
- checkboxes por transação não duplicada
- botões "Selecionar Todas" e "Desmarcar Todas"
- estado de seleção no hook de importação
- envio apenas das transações selecionadas na importação
- atualização de tipos para incluir campo selecionada
- ajustes no PreviewTransacoes e useImportacaoExtrato para suportar a seleção.

X-Lovable-Edit-ID: edt-1b002e2b-ba8b-45c1-a2b5-a8c21512e68f
This commit is contained in:
gpt-engineer-app[bot] 2025-11-25 23:23:04 +00:00
commit 32f9571d3a
4 changed files with 110 additions and 10 deletions

View File

@ -3,12 +3,17 @@ import { Card } from '@/components/ui/card';
import { Badge } from '@/components/ui/badge';
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { Checkbox } from '@/components/ui/checkbox';
import { Button } from '@/components/ui/button';
import { AlertCircle, TrendingUp, TrendingDown, Copy } from 'lucide-react';
import { Alert, AlertDescription } from '@/components/ui/alert';
interface PreviewTransacoesProps {
transacoes: TransacaoImportada[];
onCategoriaChange: (hash: string, categoria: string) => void;
onSelectionChange: (hash: string, selected: boolean) => void;
onSelectAll: () => void;
onDeselectAll: () => void;
}
const categorias = [
@ -26,11 +31,19 @@ const categorias = [
'Outros'
];
export const PreviewTransacoes = ({ transacoes, onCategoriaChange }: PreviewTransacoesProps) => {
export const PreviewTransacoes = ({
transacoes,
onCategoriaChange,
onSelectionChange,
onSelectAll,
onDeselectAll
}: PreviewTransacoesProps) => {
const novas = transacoes.filter(t => !t.isDuplicada);
const duplicadas = transacoes.filter(t => t.isDuplicada);
const totalEntradas = novas.filter(t => t.tipo === 'entrada').reduce((sum, t) => sum + t.valor, 0);
const totalSaidas = novas.filter(t => t.tipo === 'saida').reduce((sum, t) => sum + t.valor, 0);
const selecionadas = novas.filter(t => t.selecionada);
const totalEntradas = selecionadas.filter(t => t.tipo === 'entrada').reduce((sum, t) => sum + t.valor, 0);
const totalSaidas = selecionadas.filter(t => t.tipo === 'saida').reduce((sum, t) => sum + t.valor, 0);
const todasSelecionadas = novas.length > 0 && novas.every(t => t.selecionada);
const formatCurrency = (value: number) => {
return new Intl.NumberFormat('pt-BR', {
@ -99,11 +112,38 @@ export const PreviewTransacoes = ({ transacoes, onCategoriaChange }: PreviewTran
{/* Tabela de transações */}
<Card className="p-6">
<h3 className="text-lg font-semibold mb-4">Transações para Importar</h3>
<div className="flex items-center justify-between mb-4">
<h3 className="text-lg font-semibold">Transações para Importar</h3>
<div className="flex gap-2">
<Button
variant="outline"
size="sm"
onClick={onSelectAll}
>
Selecionar Todas
</Button>
<Button
variant="outline"
size="sm"
onClick={onDeselectAll}
>
Desmarcar Todas
</Button>
</div>
</div>
<div className="max-h-96 overflow-y-auto">
<Table>
<TableHeader>
<TableRow>
<TableHead className="w-12">
<Checkbox
checked={todasSelecionadas}
onCheckedChange={(checked) => {
if (checked) onSelectAll();
else onDeselectAll();
}}
/>
</TableHead>
<TableHead>Data</TableHead>
<TableHead>Descrição</TableHead>
<TableHead>Categoria</TableHead>
@ -118,6 +158,15 @@ export const PreviewTransacoes = ({ transacoes, onCategoriaChange }: PreviewTran
key={transacao.hash_unico}
className={transacao.isDuplicada ? 'opacity-50' : ''}
>
<TableCell>
<Checkbox
checked={transacao.selecionada ?? false}
disabled={transacao.isDuplicada}
onCheckedChange={(checked) =>
onSelectionChange(transacao.hash_unico, checked as boolean)
}
/>
</TableCell>
<TableCell className="whitespace-nowrap">
{formatDate(transacao.data)}
</TableCell>

View File

@ -49,7 +49,14 @@ export const useImportacaoExtrato = () => {
// Verificar duplicatas
const transacoesComDuplicatas = await verificarDuplicatas(transacoesParseadas);
setTransacoes(transacoesComDuplicatas);
// Marcar todas as não duplicadas como selecionadas por padrão
const transacoesComSelecao = transacoesComDuplicatas.map(t => ({
...t,
selecionada: !t.isDuplicada
}));
setTransacoes(transacoesComSelecao);
const duplicadas = transacoesComDuplicatas.filter(t => t.isDuplicada).length;
const novas = transacoesComDuplicatas.length - duplicadas;
@ -117,6 +124,24 @@ export const useImportacaoExtrato = () => {
));
};
const atualizarSelecao = (hash: string, selecionada: boolean) => {
setTransacoes(transacoes.map(t =>
t.hash_unico === hash ? { ...t, selecionada } : t
));
};
const selecionarTodas = () => {
setTransacoes(transacoes.map(t =>
t.isDuplicada ? t : { ...t, selecionada: true }
));
};
const desselecionarTodas = () => {
setTransacoes(transacoes.map(t =>
t.isDuplicada ? t : { ...t, selecionada: false }
));
};
return {
transacoes,
isProcessing,
@ -124,6 +149,9 @@ export const useImportacaoExtrato = () => {
processarArquivo,
importar,
limpar,
atualizarCategoria
atualizarCategoria,
atualizarSelecao,
selecionarTodas,
desselecionarTodas
};
};

View File

@ -22,7 +22,18 @@ const ImportarExtrato = () => {
const [resultadoDialog, setResultadoDialog] = useState(false);
const [logImportacao, setLogImportacao] = useState<LogImportacao | null>(null);
const { transacoes, isProcessing, isImporting, processarArquivo, importar, limpar, atualizarCategoria } = useImportacaoExtrato();
const {
transacoes,
isProcessing,
isImporting,
processarArquivo,
importar,
limpar,
atualizarCategoria,
atualizarSelecao,
selecionarTodas,
desselecionarTodas
} = useImportacaoExtrato();
const { contas, isLoading, criar, recarregar } = useContasBancarias();
const handleFileSelect = async (file: File) => {
@ -55,8 +66,15 @@ const ImportarExtrato = () => {
const tipoArquivo = detectarTipoArquivo(arquivo.name);
if (!tipoArquivo) return;
// Filtrar apenas transações selecionadas
const transacoesSelecionadas = transacoes.filter(t => t.selecionada && !t.isDuplicada);
if (transacoesSelecionadas.length === 0) {
return;
}
try {
const log = await importar(transacoes, contaSelecionada, arquivo.name, tipoArquivo);
const log = await importar(transacoesSelecionadas, contaSelecionada, arquivo.name, tipoArquivo);
setLogImportacao(log);
setResultadoDialog(true);
@ -80,6 +98,7 @@ const ImportarExtrato = () => {
};
const novasTransacoes = transacoes.filter(t => !t.isDuplicada);
const transacoesSelecionadas = transacoes.filter(t => t.selecionada && !t.isDuplicada);
return (
<div className="min-h-screen bg-gradient-to-br from-background via-background to-muted/20 p-6">
@ -154,6 +173,9 @@ const ImportarExtrato = () => {
<PreviewTransacoes
transacoes={transacoes}
onCategoriaChange={atualizarCategoria}
onSelectionChange={atualizarSelecao}
onSelectAll={selecionarTodas}
onDeselectAll={desselecionarTodas}
/>
<Card className="p-6">
@ -163,11 +185,11 @@ const ImportarExtrato = () => {
</Button>
<Button
onClick={handleImportar}
disabled={isImporting || novasTransacoes.length === 0}
disabled={isImporting || transacoesSelecionadas.length === 0}
size="lg"
>
<FileCheck className="w-5 h-5 mr-2" />
{isImporting ? 'Importando...' : `Importar ${novasTransacoes.length} Transações`}
{isImporting ? 'Importando...' : `Importar ${transacoesSelecionadas.length} Transações`}
</Button>
</div>
</Card>

View File

@ -19,6 +19,7 @@ export interface TransacaoImportada {
categoria?: string;
hash_unico: string;
isDuplicada?: boolean;
selecionada?: boolean;
}
export interface LogImportacao {