const fileInput = document.getElementById("file-input"); const fileNameEl = document.getElementById("file-name"); const importBtn = document.getElementById("import-btn"); const dropZone = document.getElementById("drop-zone"); const progress = document.getElementById("progress"); const alertSection = document.getElementById("alert-section"); const alertMessage = document.getElementById("alert-message"); const summarySection = document.getElementById("summary-section"); const summaryGrid = document.getElementById("summary-grid"); const mappingSection = document.getElementById("mapping-section"); const mappingList = document.getElementById("mapping-list"); const previewSection = document.getElementById("preview-section"); const previewTable = document.getElementById("preview-table"); const resultsSection = document.getElementById("results-section"); const resultsTable = document.getElementById("results-table"); const healthBanner = document.getElementById("health-banner"); const healthMessage = document.getElementById("health-message"); let selectedFile = null; function showAlert(text, type = "error") { alertSection.hidden = false; alertMessage.textContent = text; alertMessage.className = `alert ${type}`; } function hideAlert() { alertSection.hidden = true; } function setFile(file) { selectedFile = file; fileNameEl.textContent = file ? file.name : "Nenhum arquivo selecionado"; importBtn.disabled = !file; } fileInput.addEventListener("change", () => { const file = fileInput.files?.[0]; if (file) setFile(file); }); dropZone.addEventListener("dragover", (e) => { e.preventDefault(); dropZone.classList.add("dragover"); }); dropZone.addEventListener("dragleave", () => { dropZone.classList.remove("dragover"); }); dropZone.addEventListener("drop", (e) => { e.preventDefault(); dropZone.classList.remove("dragover"); const file = e.dataTransfer?.files?.[0]; if (file) { setFile(file); const dt = new DataTransfer(); dt.items.add(file); fileInput.files = dt.files; } }); function renderPreview(preview) { if (!preview?.headers?.length) return; previewSection.hidden = false; const thead = previewTable.querySelector("thead"); const tbody = previewTable.querySelector("tbody"); thead.innerHTML = `${preview.headers.map((h) => `${escapeHtml(h)}`).join("")}`; tbody.innerHTML = (preview.rows ?? []) .map( (row) => `${row.map((cell) => `${escapeHtml(formatCell(cell))}`).join("")}`, ) .join(""); } function renderHeaderMapping(headerMapping) { const entries = Object.entries(headerMapping ?? {}); if (!entries.length) return; mappingSection.hidden = false; mappingList.innerHTML = entries .map( ([excel, api]) => `
  • ${escapeHtml(excel)}${escapeHtml(api)}
  • `, ) .join(""); } function renderSummary(summary) { summarySection.hidden = false; const items = [ { label: "Total", value: summary.total }, { label: "Criados", value: summary.created }, { label: "Atualizados", value: summary.updated }, { label: "Erros", value: summary.errors }, ]; summaryGrid.innerHTML = items .map( (item) => `
  • ${item.value}${item.label}
  • `, ) .join(""); } function statusLabel(status) { const map = { created: "Criado", updated: "Atualizado", error: "Erro" }; return map[status] ?? status; } function renderResults(results) { if (!results?.length) return; resultsSection.hidden = false; const tbody = resultsTable.querySelector("tbody"); tbody.innerHTML = results .map((r) => { const idPart = [r.id, r.externalId].filter(Boolean).join(" / ") || "—"; return ` ${r.row} ${statusLabel(r.status)} ${escapeHtml(idPart)} ${escapeHtml(r.message)} `; }) .join(""); } function escapeHtml(str) { return String(str) .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """); } function formatCell(cell) { if (cell === null || cell === undefined) return ""; return String(cell); } importBtn.addEventListener("click", async () => { if (!selectedFile) return; hideAlert(); previewSection.hidden = true; summarySection.hidden = true; mappingSection.hidden = true; resultsSection.hidden = true; progress.hidden = false; importBtn.disabled = true; const formData = new FormData(); formData.append("file", selectedFile); try { const res = await fetch("/api/import", { method: "POST", body: formData, }); const data = await res.json().catch(() => ({})); if (!res.ok) { showAlert(data.error ?? `Erro ${res.status} ao importar`); return; } renderSummary(data.summary); renderHeaderMapping(data.headerMapping); renderPreview(data.preview); renderResults(data.results); const { errors, total } = data.summary ?? {}; if (errors === 0) { showAlert(`Importação concluída: ${total} linha(s) processada(s).`, "success"); } else { showAlert( `Importação concluída com ${errors} erro(s) em ${total} linha(s).`, "error", ); } } catch (err) { showAlert(err instanceof Error ? err.message : "Falha de rede ao importar"); } finally { progress.hidden = true; importBtn.disabled = !selectedFile; } }); async function checkHealth() { try { const res = await fetch("/api/health"); const data = await res.json(); healthBanner.hidden = false; if (data.ok) { healthBanner.className = "card banner-ok"; healthMessage.textContent = `Conectado ao Clarizen (usuário: ${data.userId})`; } else { healthBanner.className = "card banner-warn"; healthMessage.textContent = data.error ?? "Não foi possível autenticar no Clarizen"; } } catch { healthBanner.hidden = false; healthBanner.className = "card banner-warn"; healthMessage.textContent = "Servidor indisponível. Inicie com npm start."; } } checkHealth();