import React, { useState } from 'react'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Upload, FileText, Download, X, Check, Eye, Info } from 'lucide-react'; import { toast } from 'sonner'; import { usePrompts } from '@/hooks/usePrompts'; import * as XLSX from 'xlsx'; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from '@/components/ui/dialog'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select'; interface ProcessedData { headers: string[]; rows: any[][]; totalRows: number; totalColumns: number; rawData: any[]; // Dados originais para processamento } const FileImporter: React.FC = () => { const { prompts, loading: promptsLoading } = usePrompts(); const [file, setFile] = useState(null); const [processing, setProcessing] = useState(false); const [processingPP, setProcessingPP] = useState(false); const [processedData, setProcessedData] = useState(null); const [showPreview, setShowPreview] = useState(false); const [selectedPrompt, setSelectedPrompt] = useState(''); const [generating, setGenerating] = useState(false); const handleFileSelect = (event: React.ChangeEvent) => { const selectedFile = event.target.files?.[0]; if (selectedFile) { const validTypes = [ 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', // .xlsx 'application/vnd.ms-excel', // .xls 'text/csv', // .csv 'application/pdf' // .pdf ]; if (!validTypes.includes(selectedFile.type) && !selectedFile.name.toLowerCase().endsWith('.csv')) { toast.error('Formato de arquivo inválido. Selecione um arquivo .xlsx, .xls, .csv ou .pdf'); return; } setFile(selectedFile); setProcessedData(null); } }; const processFile = async (onlyPP = false) => { if (!file) return; if (onlyPP) { setProcessingPP(true); } else { setProcessing(true); } try { let processedResult: ProcessedData; if (file.type === 'application/pdf') { // Para PDF, ainda simulamos o processamento await new Promise(resolve => setTimeout(resolve, 2000)); processedResult = { headers: ['Marca', 'Perfil', 'Quantidade', 'Peso Unit.', 'Peso Total', 'Material'], rows: [ ['P1', 'VS 200x30x5,0', '2', '15,8', '31,6', 'ASTM A36'], ['P2', 'VS 150x25x4,0', '4', '12,3', '49,2', 'ASTM A36'], ['P3', 'L 50x50x5,0', '8', '3,77', '30,16', 'ASTM A36'], ['P4', 'CH 100x50x17,0', '6', '17,0', '102,0', 'ASTM A36'], ['P5', 'FL 200x8,0', '3', '12,56', '37,68', 'ASTM A36'] ], totalRows: 5, totalColumns: 6, rawData: [] }; } else { // Processar arquivos Excel/CSV usando xlsx const buffer = await file.arrayBuffer(); const workbook = XLSX.read(buffer, { type: 'array' }); // Pegar a primeira planilha const firstSheetName = workbook.SheetNames[0]; const worksheet = workbook.Sheets[firstSheetName]; // Converter para JSON com chaves como nomes das colunas const jsonDataWithHeaders = XLSX.utils.sheet_to_json(worksheet) as any[]; // Converter para formato de array para visualização const jsonArrayData = XLSX.utils.sheet_to_json(worksheet, { header: 1, defval: '', raw: false }) as any[][]; if (jsonDataWithHeaders.length === 0) { throw new Error('Nenhum dado encontrado no arquivo'); } console.log('Dados JSON com headers:', jsonDataWithHeaders); console.log('Dados em array:', jsonArrayData); // Se for processamento PP, filtrar apenas linhas onde a coluna "Pos." está vazia let filteredJsonData = jsonDataWithHeaders; let filteredArrayData = jsonArrayData; if (onlyPP) { // Filtrar dados JSON com headers filteredJsonData = jsonDataWithHeaders.filter(row => { const posValue = extrairValor(row, ['Pos.', 'Pos', 'pos', 'POS']); return !posValue || posValue.toString().trim() === ''; }); // Filtrar dados em array (manter header + linhas filtradas) const headerRow = jsonArrayData[0]; const dataRows = jsonArrayData.slice(1); // Encontrar índice da coluna Pos. const posColumnIndex = headerRow.findIndex(header => ['Pos.', 'Pos', 'pos', 'POS'].includes(header?.toString() || '') ); const filteredDataRows = dataRows.filter(row => { if (posColumnIndex === -1) return true; // Se não encontrar coluna Pos., incluir todas const posValue = row[posColumnIndex]; return !posValue || posValue.toString().trim() === ''; }); filteredArrayData = [headerRow, ...filteredDataRows]; } // Determinar o número máximo de colunas const maxColumns = Math.max(...filteredArrayData.map(row => row.length)); // Padronizar todas as linhas para ter o mesmo número de colunas const normalizedData = filteredArrayData.map(row => { const normalizedRow = [...row]; while (normalizedRow.length < maxColumns) { normalizedRow.push(''); } return normalizedRow; }); // Primeira linha como headers const headers = normalizedData[0].map((header, index) => header || `Coluna ${index + 1}` ); // Resto como dados para visualização const rows = normalizedData.slice(1); processedResult = { headers, rows, totalRows: rows.length, totalColumns: headers.length, rawData: filteredJsonData // Dados originais filtrados para processamento }; console.log('Dados processados:', { totalRows: processedResult.totalRows, totalColumns: processedResult.totalColumns, headers: processedResult.headers, rawDataCount: processedResult.rawData.length, onlyPP: onlyPP }); } setProcessedData(processedResult); setShowPreview(true); const processType = onlyPP ? ' (apenas peças principais)' : ''; toast.success(`Arquivo processado com sucesso${processType}! ${processedResult.totalRows} linhas e ${processedResult.totalColumns} colunas coletadas.`); } catch (error) { console.error('Erro ao processar arquivo:', error); toast.error('Erro ao processar arquivo. Verifique se o arquivo não está corrompido.'); } finally { if (onlyPP) { setProcessingPP(false); } else { setProcessing(false); } } }; const generateCSV = async () => { if (!processedData || !selectedPrompt || !file) return; setGenerating(true); try { const selectedPromptData = prompts.find(p => p.id === selectedPrompt); console.log('Processando com prompt:', selectedPromptData?.name); console.log('Dados originais para processar:', processedData.rawData); // Processar os dados usando a lógica do prompt const csvContent = processSpreadsheetToCsv(file.name, processedData.rawData); // Gerar e baixar CSV const BOM = '\uFEFF'; const blob = new Blob([BOM + csvContent], { type: 'text/csv;charset=utf-8;' }); const link = document.createElement('a'); const csvFileName = file.name.replace(/\.(xlsx|xls|pdf)$/i, '_processado.csv'); link.href = URL.createObjectURL(blob); link.download = csvFileName; link.click(); URL.revokeObjectURL(link.href); toast.success(`CSV gerado com sucesso usando o prompt: ${selectedPromptData?.name}`); setShowPreview(false); setFile(null); setProcessedData(null); setSelectedPrompt(''); } catch (error) { console.error('Erro ao gerar CSV:', error); toast.error('Erro ao gerar CSV. Verifique as instruções do prompt e os dados.'); } finally { setGenerating(false); } }; const processSpreadsheetToCsv = (fileName: string, data: any[]): string => { console.log('Processando arquivo:', fileName); console.log('Total de linhas:', data.length); // Extrair metadados do nome do arquivo const fileNameParts = fileName.replace(/\.(xlsx|xls|pdf)$/i, '').split('-'); const ofNumber = fileNameParts[0] || ''; const etapaFase = fileNameParts[1] || ''; console.log('OF Number:', ofNumber); console.log('Etapa/Fase:', etapaFase); const csvHeader = 'of_number,etapa_fase,marca,descricao,quantidade,peso_unitario,peso_total,tratamento_superficial,material,perfil_principal,tem_componentes,marca_componente,descricao_componente,perfil_componente,peso_unitario_componente,quantidade_por_peca'; const csvRows: string[] = [csvHeader]; let currentPecaMae: any = null; const componentes: any[] = []; // Log das colunas disponíveis if (data.length > 0) { console.log('Colunas disponíveis:', Object.keys(data[0])); } // Processar dados linha por linha for (let i = 0; i < data.length; i++) { const row = data[i]; console.log(`Processando linha ${i + 1}:`, row); // Verificar se é uma peça-mãe (tem valor na coluna Marca) const marcaValue = extrairValor(row, ['Marca', 'marca', 'MARCA']); if (marcaValue && marcaValue.toString().trim() !== '') { console.log('Encontrou peça-mãe:', marcaValue); // Processar peça-mãe anterior se existir if (currentPecaMae) { processarPecaMae(currentPecaMae, componentes, csvRows, ofNumber, etapaFase); } // Nova peça-mãe currentPecaMae = row; componentes.length = 0; // Limpar componentes } else { // Verificar se é um componente através da coluna Pos. const posValue = extrairValor(row, ['Pos.', 'Pos', 'pos', 'POS']); if (posValue && posValue.toString().trim() !== '') { const posString = posValue.toString(); console.log('Verificando Pos.:', posString); // Extrair o último número após o último hífen const lastNumber = posString.split('-').pop(); if (lastNumber && parseInt(lastNumber) >= 1000 && parseInt(lastNumber) <= 9999) { console.log('Componente encontrado:', posString); componentes.push(row); } } } } // Processar última peça-mãe if (currentPecaMae) { processarPecaMae(currentPecaMae, componentes, csvRows, ofNumber, etapaFase); } console.log('Total de linhas CSV geradas:', csvRows.length - 1); return csvRows.join('\n'); }; const processarPecaMae = (pecaMae: any, componentes: any[], csvRows: string[], ofNumber: string, etapaFase: string) => { console.log('Processando peça-mãe:', pecaMae); console.log('Com componentes:', componentes); // Extrair dados da peça-mãe com diferentes variações de nomes de colunas const marcaCompleta = extrairValor(pecaMae, ['Marca', 'marca', 'MARCA'])?.toString() || ''; const marca = marcaCompleta.split('-').pop() || marcaCompleta; const descricao = extrairValor(pecaMae, ['Descrição', 'Descricao', 'descrição', 'descricao', 'DESCRIÇÃO', 'DESCRICAO']) || ''; const quantidade = extrairValor(pecaMae, ['Qtde', 'Quantidade', 'qtde', 'quantidade', 'QTDE', 'QUANTIDADE']) || ''; const pesoUnitario = formatarNumero(extrairValor(pecaMae, ['P.Un.', 'PesoUnitario', 'Peso Unitário', 'peso_unitario', 'PESO_UNITARIO'])); const pesoTotal = formatarNumero(extrairValor(pecaMae, ['P.Tot.', 'PesoTotal', 'Peso Total', 'peso_total', 'PESO_TOTAL'])); const material = extrairValor(pecaMae, ['Mat', 'Material', 'mat', 'material', 'MAT', 'MATERIAL']) || ''; const perfilPrincipal = material; console.log('Dados extraídos - Marca:', marca, 'Descrição:', descricao, 'Qtd:', quantidade); if (componentes.length === 0) { // Peça-mãe sem componentes const csvRow = [ escapeCSV(ofNumber), escapeCSV(etapaFase), escapeCSV(marca), escapeCSV(descricao), escapeCSV(quantidade), escapeCSV(pesoUnitario), escapeCSV(pesoTotal), escapeCSV('-'), // tratamento_superficial escapeCSV(material), escapeCSV(perfilPrincipal), 'false', // tem_componentes '', // marca_componente '', // descricao_componente '', // perfil_componente '', // peso_unitario_componente '' // quantidade_por_peca ].join(','); csvRows.push(csvRow); } else { // Peça-mãe com componentes componentes.forEach(componente => { const posValue = extrairValor(componente, ['Pos.', 'Pos', 'pos', 'POS'])?.toString() || ''; const marcaComponente = posValue.split('-').pop() || ''; const descricaoComponente = extrairValor(componente, ['Descrição', 'Descricao', 'descrição', 'descricao', 'DESCRIÇÃO', 'DESCRICAO']) || ''; const perfilComponente = extrairValor(componente, ['Mat', 'Material', 'mat', 'material', 'MAT', 'MATERIAL']) || ''; const pesoUnitarioComponente = formatarNumero(extrairValor(componente, ['P.Un.', 'PesoUnitario', 'Peso Unitário', 'peso_unitario', 'PESO_UNITARIO'])); const quantidadePorPeca = extrairValor(componente, ['Qtde', 'Quantidade', 'qtde', 'quantidade', 'QTDE', 'QUANTIDADE']) || ''; const csvRow = [ escapeCSV(ofNumber), escapeCSV(etapaFase), escapeCSV(marca), escapeCSV(descricao), escapeCSV(quantidade), escapeCSV(pesoUnitario), escapeCSV(pesoTotal), escapeCSV('-'), // tratamento_superficial escapeCSV(material), escapeCSV(perfilPrincipal), 'true', // tem_componentes escapeCSV(marcaComponente), escapeCSV(descricaoComponente), escapeCSV(perfilComponente), escapeCSV(pesoUnitarioComponente), escapeCSV(quantidadePorPeca) ].join(','); csvRows.push(csvRow); }); } }; const extrairValor = (objeto: any, nomes: string[]): any => { for (const nome of nomes) { if (objeto[nome] !== undefined && objeto[nome] !== null && objeto[nome] !== '') { return objeto[nome]; } } return ''; }; const escapeCSV = (valor: any): string => { if (valor === null || valor === undefined) return ''; const str = valor.toString(); if (str.includes(',') || str.includes('"') || str.includes('\n')) { return `"${str.replace(/"/g, '""')}"`; } return str; }; const formatarNumero = (valor: any): string => { if (!valor) return ''; const numeroStr = valor.toString().replace(',', '.'); const numero = parseFloat(numeroStr); return isNaN(numero) ? '' : numero.toFixed(1); }; const handleCancel = () => { setShowPreview(false); setProcessedData(null); }; return ( <> Importar Arquivo

Formatos suportados: .xlsx, .xls, .csv, .pdf

{file && (
{file.name} ({(file.size / 1024 / 1024).toFixed(2)} MB)
)}

Como funciona:

  • 1. Selecione um arquivo (.xlsx, .xls, .csv ou .pdf)
  • 2. O sistema extrairá todas as tabelas e dados
  • 3. Visualize os dados processados
  • 4. Escolha um prompt para guiar a conversão
  • 5. Gere e baixe o arquivo CSV final

Processar PP:

Processa apenas linhas onde a coluna "Pos." está vazia (peças principais).

Dados Processados Visualize os dados extraídos do arquivo e selecione um prompt para gerar o CSV
{prompts.length === 0 && (

Nenhum prompt encontrado. Crie um prompt primeiro.

)}
{processedData && ( <>
Dados coletados: {processedData.totalRows} linhas × {processedData.totalColumns} colunas
{processedData.headers.map((header, index) => ( ))} {processedData.rows.map((row, rowIndex) => ( {row.map((cell, cellIndex) => ( ))} ))}
{header}
{cell}
)}
); }; export default FileImporter;