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();