Ajusta importação CSV

Melhora parser CSV para extrair dados corretamente, incluindo detecção de cabeçalho, mapeamento de colunas (data, descrição, valor), suporte a formato brasileiro e prevenção de duplicatas. AdicionaLoading/ animação de carregamento durante o processamento.

X-Lovable-Edit-ID: edt-8e8b5436-baeb-463b-815d-f542f108e1c0
This commit is contained in:
gpt-engineer-app[bot] 2025-11-25 23:10:46 +00:00
commit 72ffa913ba

View File

@ -16,15 +16,34 @@ export async function parseCSV(file: File, contaBancariaId?: string): Promise<Tr
complete: (results) => { complete: (results) => {
try { try {
const transacoes: TransacaoImportada[] = []; const transacoes: TransacaoImportada[] = [];
const data = results.data as any[]; const linhas = results.data as any[][]; // matriz de linhas
if (data.length === 0) { if (!linhas || linhas.length === 0) {
throw new Error('Arquivo CSV vazio'); throw new Error('Arquivo CSV vazio');
} }
// Tentar detectar colunas automaticamente // Encontrar linha de cabeçalho real (que contém "Data" e "Valor" etc.)
const primeiraLinha = data[0]; let headerIndex = -1;
const colunas = Object.keys(primeiraLinha); for (let i = 0; i < linhas.length; i++) {
const row = linhas[i];
if (!row || row.length === 0) continue;
const primeiraColuna = String(row[0] ?? '').toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g, '');
const segundaColuna = String(row[1] ?? '').toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g, '');
// Ex: "Data Lançamento" | "Histórico" | "Descrição" | "Valor" | "Saldo"
if (primeiraColuna.includes('data') && (segundaColuna.includes('historico') || segundaColuna.includes('descricao'))) {
headerIndex = i;
break;
}
}
if (headerIndex === -1) {
throw new Error('Não foi possível identificar o cabeçalho do arquivo (linha com Data/Histórico/Valor)');
}
const headerRow = linhas[headerIndex];
const colunas = headerRow.map((c) => String(c ?? ''));
// Encontrar colunas relevantes (suporta nomes com acentos e espaços) // Encontrar colunas relevantes (suporta nomes com acentos e espaços)
const colunaData = detectarColuna(colunas, ['data', 'date', 'quando', 'dt', 'lancamento']); const colunaData = detectarColuna(colunas, ['data', 'date', 'quando', 'dt', 'lancamento']);
@ -36,10 +55,19 @@ export async function parseCSV(file: File, contaBancariaId?: string): Promise<Tr
throw new Error('Não foi possível identificar as colunas necessárias (Data, Descrição, Valor)'); throw new Error('Não foi possível identificar as colunas necessárias (Data, Descrição, Valor)');
} }
for (const linha of data) { // Processar linhas de dados após o cabeçalho
const dataStr = linha[colunaData]; for (let i = headerIndex + 1; i < linhas.length; i++) {
const descricao = limparDescricao(String(linha[colunaDescricao] || '')); const linha = linhas[i];
const valorStr = String(linha[colunaValor] || '0'); if (!linha || linha.length === 0) continue;
const rowObj: Record<string, any> = {};
colunas.forEach((col, idx) => {
rowObj[col] = linha[idx];
});
const dataStr = rowObj[colunaData];
const descricao = limparDescricao(String(rowObj[colunaDescricao] || ''));
const valorStr = String(rowObj[colunaValor] || '0');
// Parse de data (tenta vários formatos) // Parse de data (tenta vários formatos)
const data = parseData(dataStr); const data = parseData(dataStr);
@ -51,9 +79,11 @@ export async function parseCSV(file: File, contaBancariaId?: string): Promise<Tr
// Determinar tipo // Determinar tipo
let tipo: 'entrada' | 'saida'; let tipo: 'entrada' | 'saida';
if (colunaTipo) { if (colunaTipo && rowObj[colunaTipo] !== undefined && rowObj[colunaTipo] !== null) {
const tipoStr = String(linha[colunaTipo]).toLowerCase(); const tipoStr = String(rowObj[colunaTipo]).toLowerCase();
tipo = tipoStr.includes('credit') || tipoStr.includes('entrada') || tipoStr.includes('receita') ? 'entrada' : 'saida'; tipo = tipoStr.includes('credit') || tipoStr.includes('entrada') || tipoStr.includes('recebido') || tipoStr.includes('receita')
? 'entrada'
: 'saida';
} else { } else {
tipo = valor >= 0 ? 'entrada' : 'saida'; tipo = valor >= 0 ? 'entrada' : 'saida';
} }