import React, { useState, lazy, Suspense } from 'react'; import Papa from 'papaparse'; import FileUpload from './components/FileUpload'; import { Layers, AlertCircle, X } from 'lucide-react'; // Lazy load Dashboard since it's not needed on initial load const Dashboard = lazy(() => import('./components/Dashboard')); function App() { const [view, setView] = useState('upload'); // 'upload' | 'dashboard' const [rawData, setRawData] = useState([]); const [processedData, setProcessedData] = useState({}); const [stats, setStats] = useState({ total: 0, noWebsite: 0, niches: 0 }); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [showExportModal, setShowExportModal] = useState(false); const [selectedNiches, setSelectedNiches] = useState([]); const handleFileUpload = (files) => { // Reset states setError(null); setLoading(true); // Validate files const MAX_FILE_SIZE = 50 * 1024 * 1024; // 50MB for (const file of files) { if (!file.name.endsWith('.csv')) { setError('Por favor, selecione apenas arquivos CSV.'); setLoading(false); return; } if (file.size > MAX_FILE_SIZE) { setError(`O arquivo ${file.name} é muito grande. Tamanho máximo: 50MB.`); setLoading(false); return; } } const parsePromises = files.map(file => { return new Promise((resolve, reject) => { Papa.parse(file, { header: true, skipEmptyLines: true, // REMOVED worker: true - causes issues in production/Hugging Face complete: (results) => { if (results.errors && results.errors.length > 0) { console.warn('CSV parsing warnings:', results.errors); } resolve(results.data); }, error: (error) => { console.error("Error parsing CSV:", error); reject(error); } }); }); }); Promise.all(parsePromises) .then(resultsArray => { // Flatten array of arrays const allData = resultsArray.flat(); if (allData.length === 0) { setError('Nenhum dado encontrado nos arquivos CSV.'); setLoading(false); return; } processData(allData); setLoading(false); }) .catch(error => { console.error("Error processing files:", error); setError(`Erro ao processar arquivos: ${error.message || 'Erro desconhecido'}`); setLoading(false); }); }; const processData = (data) => { setRawData(data); // Filter: Keep only those WITHOUT a website AND WITH a phone number const filteredData = data.filter(item => { const hasNoWebsite = !item.website || item.website.trim() === ''; const hasPhone = item.phone && item.phone.trim() !== ''; return hasNoWebsite && hasPhone; }).map((item, index) => ({ ...item, // Generate a unique ID based on content or fallback to index if needed id: `${item.title || 'unknown'}-${item.phone || 'no-phone'}-${index}` })); // Group by Niche (categoryName) const grouped = {}; filteredData.forEach(item => { // Normalize category name const niche = item.categoryName ? item.categoryName.trim() : 'Outros'; if (!grouped[niche]) { grouped[niche] = []; } grouped[niche].push(item); }); setProcessedData(grouped); setStats({ total: data.length, noWebsite: filteredData.length, niches: Object.keys(grouped).length }); setView('dashboard'); }; const openExportModal = () => { // Initialize with all niches selected setSelectedNiches(Object.keys(processedData)); setShowExportModal(true); }; const toggleNicheSelection = (niche) => { setSelectedNiches(prev => { if (prev.includes(niche)) { return prev.filter(n => n !== niche); } else { return [...prev, niche]; } }); }; const toggleSelectAll = () => { if (selectedNiches.length === Object.keys(processedData).length) { setSelectedNiches([]); } else { setSelectedNiches(Object.keys(processedData)); } }; const handleExport = () => { if (selectedNiches.length === 0) { setError('Por favor, selecione pelo menos um nicho para exportar.'); return; } // Flatten the grouped data back to a list for export // Only include selected niches const exportList = []; selectedNiches.forEach(niche => { const items = processedData[niche] || []; items.forEach(item => { exportList.push({ Name: item.title, Phone: item.phone, Address: `${item.street || ''} ${item.city || ''} ${item.state || ''}`.trim(), Niche: niche }); }); }); const csv = Papa.unparse(exportList); const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' }); const url = URL.createObjectURL(blob); const link = document.createElement('a'); link.href = url; // Generate filename with selected niches info const filename = selectedNiches.length === Object.keys(processedData).length ? 'leads_todos_nichos.csv' : selectedNiches.length === 1 ? `leads_${selectedNiches[0].toLowerCase().replace(/[^a-z0-9]/g, '_')}.csv` : `leads_${selectedNiches.length}_nichos.csv`; link.setAttribute('download', filename); document.body.appendChild(link); link.click(); document.body.removeChild(link); setShowExportModal(false); }; return (
{/* Header */}

LeadGen Pro

{view === 'dashboard' && ( )}
{/* Error Banner */} {error && (
{error}
)} {view === 'upload' ? (

Transforme dados brutos em
Oportunidades de Venda

Filtre automaticamente negócios sem website, organize por nicho e exporte para prospecção em massa.

) : (

Carregando dashboard...

}> )} {/* Export Modal */} {showExportModal && (
setShowExportModal(false)} >
e.stopPropagation()} > {/* Modal Header */}

Exportar Leads

Selecione os nichos que deseja exportar

{/* Modal Body */}
{/* Select All Toggle */}
{/* Niches List */}
{Object.entries(processedData).sort(([a], [b]) => a.localeCompare(b)).map(([niche, items]) => { const isSelected = selectedNiches.includes(niche); return (
toggleNicheSelection(niche)} onMouseEnter={(e) => { if (!isSelected) e.currentTarget.style.background = 'var(--surface-hover)'; }} onMouseLeave={(e) => { if (!isSelected) e.currentTarget.style.background = 'var(--surface-color)'; }} >
); })}
{/* Modal Footer */}
{selectedNiches.length} {selectedNiches.length === 1 ? 'nicho selecionado' : 'nichos selecionados'} {selectedNiches.length > 0 && ( ({selectedNiches.reduce((sum, niche) => sum + (processedData[niche]?.length || 0), 0)} leads) )}
)} ); } export default App;