import React, { useState } from 'react'; import * as XLSX from 'xlsx'; import { useAuth } from '../contexts/AuthContext'; import { Button } from '../components/Button'; import { Upload, FileText, CheckCircle, AlertTriangle, Calendar, Database } from 'lucide-react'; const API_BASE_URL = import.meta.env.VITE_API_URL || "http://localhost:8080"; type ImportType = 'fot' | 'agenda'; interface ImportFotInput { fot: string; empresa_nome: string; curso_nome: string; ano_formatura_label: string; instituicao: string; cidade: string; estado: string; observacoes: string; gastos_captacao: number; pre_venda: boolean; } interface ImportAgendaInput { fot: string; data: string; tipo_evento: string; observacoes: string; local: string; endereco: string; horario: string; qtd_formandos: number; qtd_fotografos: number; qtd_cinegrafistas: number; qtd_recepcionistas: number; qtd_estudios: number; qtd_ponto_foto: number; qtd_ponto_id: number; qtd_ponto_decorado: number; qtd_pontos_led: number; qtd_plataforma_360: number; foto_faltante: number; recep_faltante: number; cine_faltante: number; logistica_observacoes: string; pre_venda: boolean; } export const ImportData: React.FC = () => { const { token } = useAuth(); const [activeTab, setActiveTab] = useState('fot'); // Generic data state (can be Fot or Agenda) const [data, setData] = useState([]); const [filename, setFilename] = useState(""); const [isLoading, setIsLoading] = useState(false); const [result, setResult] = useState<{ success: number; errors: string[] } | null>(null); // Clear data when switching tabs const handleTabChange = (tab: ImportType) => { if (tab !== activeTab) { setActiveTab(tab); setData([]); setFilename(""); setResult(null); } }; const handleFileUpload = (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (!file) return; setFilename(file.name); const reader = new FileReader(); reader.onload = (evt) => { const bstr = evt.target?.result; const wb = XLSX.read(bstr, { type: 'binary' }); const wsname = wb.SheetNames[0]; const ws = wb.Sheets[wsname]; const jsonData = XLSX.utils.sheet_to_json(ws, { header: 1 }) as any[][]; let mappedData: any[] = []; // Start from row 1 (skip header) for (let i = 1; i < jsonData.length; i++) { const row = jsonData[i]; if (!row || row.length === 0) continue; // Helper to get string const getStr = (idx: number) => row[idx] ? String(row[idx]).trim() : ""; // Helper to get int const getInt = (idx: number) => { const val = row[idx]; if (!val) return 0; if (typeof val === 'number') return Math.floor(val); const parsed = parseInt(String(val).replace(/\D/g, ''), 10); return isNaN(parsed) ? 0 : parsed; }; const fot = getStr(0); if (!fot) continue; if (activeTab === 'fot') { // Parse Gastos let gastosStr = getStr(8); gastosStr = gastosStr.replace(/[R$\s.]/g, '').replace(',', '.'); const gastos = parseFloat(gastosStr) || 0; const item: ImportFotInput = { fot: fot, empresa_nome: getStr(1), curso_nome: getStr(2), observacoes: getStr(3), instituicao: getStr(4), ano_formatura_label: getStr(5), cidade: getStr(6), estado: getStr(7), gastos_captacao: gastos, pre_venda: getStr(9).toLowerCase().includes('sim'), }; mappedData.push(item); } else if (activeTab === 'agenda') { // Agenda Parsing // A: FOT (0) // B: Data (1) - Excel often stores dates as numbers. Need formatting helper? // If cell.t is 'n', use XLSX.SSF? Or XLSX.utils.sheet_to_json with raw: false might help but header:1 is safer. // If using header:1, date might be number (days since 1900) or string. // Let's assume text for simplicity or basic number check. let dateStr = getStr(1); if (typeof row[1] === 'number') { // Approximate JS Date const dateObj = new Date(Math.round((row[1] - 25569)*86400*1000)); // Convert to DD/MM/YYYY dateStr = dateObj.toLocaleDateString('pt-BR'); } const item: ImportAgendaInput = { fot: fot, data: dateStr, tipo_evento: getStr(7), // H observacoes: getStr(8), // I local: getStr(9), // J endereco: getStr(10), // K horario: getStr(11), // L qtd_formandos: getInt(12), // M qtd_fotografos: getInt(13), // N qtd_cinegrafistas: getInt(14), // O qtd_estudios: getInt(15), // P (Assumed Estufio?) Screenshot check: Col P header unreadable? "estúdio"? qtd_recepcionistas: getInt(22) > 0 ? getInt(22) : 0, // Wait, where is recep? // Look at screenshot headers: // M: Formandos // N: fotografo // O: cinegrafista / cinegrafista // P: estúdio // Q: ponto de foto // R: ponto de ID // S: Ponto // T: pontos Led // U: plataforma 360 // W: Profissionais Ok? // Recp missing? Maybe column V? // Or maybe Recepcionistas are implied in "Profissionais"? // Let's assume 0 for now unless we find columns. // Wait, screenshot shows icons. // X: Camera icon (Falta Foto) // Y: Woman icon (Falta Recep) -> so Recep info exists? // Maybe "Recepcionistas" is column ? // Let's stick to what we see. qtd_ponto_foto: getInt(16), // Q qtd_ponto_id: getInt(17), // R qtd_ponto_decorado: getInt(18), // S qtd_pontos_led: getInt(19), // T qtd_plataforma_360: getInt(20), // U // Falta foto_faltante: parseInt(row[23]) || 0, // X (Allow negative) recep_faltante: parseInt(row[24]) || 0, // Y cine_faltante: parseInt(row[25]) || 0, // Z logistica_observacoes: getStr(26), // AA pre_venda: getStr(27).toLowerCase().includes('sim'), // AB }; mappedData.push(item); } } setData(mappedData); setResult(null); }; reader.readAsBinaryString(file); }; const handleImport = async () => { if (!token) return; setIsLoading(true); try { const endpoint = activeTab === 'fot' ? '/api/import/fot' : '/api/import/agenda'; const response = await fetch(`${API_BASE_URL}${endpoint}`, { method: "POST", headers: { "Content-Type": "application/json", "Authorization": `Bearer ${token}` }, body: JSON.stringify(data) }); if (!response.ok) { throw new Error(`Erro na importação: ${response.statusText}`); } const resData = await response.json(); // Agenda response might be different? // Fot response: {SuccessCount, Errors}. // Agenda response: {message}. I should unifiy or handle both. if (resData.message) { setResult({ success: data.length, errors: [] }); // Assume all success if message only } else { setResult({ success: resData.SuccessCount, errors: resData.Errors || [] }); } } catch (error) { console.error("Import error:", error); alert("Erro ao importar dados. Verifique o console."); } finally { setIsLoading(false); } }; // Drag scroll logic const tableContainerRef = React.useRef(null); const [isDragging, setIsDragging] = useState(false); const [startX, setStartX] = useState(0); const [scrollLeft, setScrollLeft] = useState(0); const handleMouseDown = (e: React.MouseEvent) => { if (!tableContainerRef.current) return; setIsDragging(true); setStartX(e.pageX - tableContainerRef.current.offsetLeft); setScrollLeft(tableContainerRef.current.scrollLeft); }; const handleMouseLeave = () => { setIsDragging(false); }; const handleMouseUp = () => { setIsDragging(false); }; const handleMouseMove = (e: React.MouseEvent) => { if (!isDragging || !tableContainerRef.current) return; e.preventDefault(); const x = e.pageX - tableContainerRef.current.offsetLeft; const walk = (x - startX) * 2; tableContainerRef.current.scrollLeft = scrollLeft - walk; }; return (

Importação de Dados

Carregue planilhas do Excel para alimentar o sistema.

{/* Tabs */}
{/* Info Box */}
{activeTab === 'fot' ? ( Colunas Esperadas (A-J): ) : ( Colunas Esperadas (A-AB): )}   {activeTab === 'fot' ? "FOT, Empresa, Curso, Observações, Instituição, Ano Formatura, Cidade, Estado, Gastos Captação, Pré Venda." : "FOT, Data, ..., Tipo Evento, Obs, Local, Endereço, Horário, Qtds (Formandos, Foto, Cine...), Faltantes, Logística." }
{filename && (
Arquivo selecionado: {filename} ({data.length} registros)
)}
{data.length > 0 && !result && (

Pré-visualização ({activeTab === 'fot' ? 'FOT' : 'Agenda'})

Total: {data.length}
{activeTab === 'fot' ? ( <> ) : ( <> )} {data.map((row, idx) => ( {activeTab === 'fot' ? ( <> ) : ( <> )} ))}
FOT Empresa Curso Instituição Ano GastosFOT Data Evento Local Horário Formandos Logística
{row.fot} {row.empresa_nome} {row.curso_nome} {row.instituicao} {row.ano_formatura_label} R$ {row.gastos_captacao?.toLocaleString('pt-BR', { minimumFractionDigits: 2 })}{row.fot} {row.data} {row.tipo_evento} {row.local} {row.horario} {row.qtd_formandos} {row.logistica_observacoes}
Total de Registros: {data.length}
)} {result && (
0 ? 'bg-yellow-50' : 'bg-green-50'}`}>
{result.errors.length === 0 ? ( ) : ( )}

Resultado da Importação

Sucesso: {result.success} registros importados/atualizados.

{result.errors.length > 0 && (

Erros ({result.errors.length}):

    {result.errors.map((err, idx) => (
  • {err}
  • ))}
)}
)}
); };