document.addEventListener("DOMContentLoaded", () => { const moduleEl = document.querySelector("[data-import-module]"); if (!moduleEl) return; const dropzone = moduleEl.querySelector("[data-import-dropzone]"); const input = dropzone?.querySelector("input[type='file']"); const uploadButton = moduleEl.querySelector("[data-import-upload-trigger]"); const resetButton = moduleEl.querySelector("[data-import-reset]"); const feedbackEl = moduleEl.querySelector("#import-upload-feedback"); const previewBody = moduleEl.querySelector("[data-import-preview-body]"); const previewContainer = moduleEl.querySelector("#import-preview-container"); const actionsEl = moduleEl.querySelector("[data-import-actions]"); const commitButton = moduleEl.querySelector("[data-import-commit]"); const cancelButton = moduleEl.querySelector("[data-import-cancel]"); let stageToken = null; function showFeedback(message, type = "info") { if (!feedbackEl) return; feedbackEl.textContent = message; feedbackEl.classList.remove("hidden", "success", "error", "info"); feedbackEl.classList.add(type); } function hideFeedback() { if (!feedbackEl) return; feedbackEl.textContent = ""; feedbackEl.classList.add("hidden"); } function clearPreview() { if (previewBody) { previewBody.innerHTML = ""; } previewContainer?.classList.add("hidden"); actionsEl?.classList.add("hidden"); commitButton?.setAttribute("disabled", "disabled"); stageToken = null; } function enableUpload() { uploadButton?.removeAttribute("disabled"); resetButton?.classList.remove("hidden"); } function disableUpload() { uploadButton?.setAttribute("disabled", "disabled"); uploadButton?.classList.remove("loading"); resetButton?.classList.add("hidden"); } dropzone?.addEventListener("dragover", (event) => { event.preventDefault(); dropzone.classList.add("dragover"); }); dropzone?.addEventListener("dragleave", () => { dropzone.classList.remove("dragover"); }); dropzone?.addEventListener("drop", (event) => { event.preventDefault(); dropzone.classList.remove("dragover"); if (!event.dataTransfer?.files?.length || !input) { return; } input.files = event.dataTransfer.files; enableUpload(); hideFeedback(); }); input?.addEventListener("change", () => { if (input.files?.length) { enableUpload(); hideFeedback(); } else { disableUpload(); } }); resetButton?.addEventListener("click", () => { if (input) { input.value = ""; } disableUpload(); hideFeedback(); clearPreview(); }); async function uploadAndPreview() { if (!input?.files?.length) { showFeedback( "Please select a CSV or XLSX file before uploading.", "error" ); return; } const file = input.files[0]; showFeedback("Uploading…", "info"); uploadButton?.classList.add("loading"); uploadButton?.setAttribute("disabled", "disabled"); const formData = new FormData(); formData.append("file", file); let response; try { response = await fetch("/imports/projects/preview", { method: "POST", body: formData, }); } catch (error) { console.error(error); NotificationCenter?.show({ message: "Network error during upload.", level: "error", }); showFeedback("Network error during upload.", "error"); uploadButton?.classList.remove("loading"); uploadButton?.removeAttribute("disabled"); return; } if (!response.ok) { const detail = await response.json().catch(() => ({})); const message = detail?.detail || "Upload failed. Please check the file."; NotificationCenter?.show({ message, level: "error" }); showFeedback(message, "error"); uploadButton?.classList.remove("loading"); uploadButton?.removeAttribute("disabled"); return; } const payload = await response.json(); hideFeedback(); renderPreview(payload); uploadButton?.classList.remove("loading"); uploadButton?.removeAttribute("disabled"); NotificationCenter?.show({ message: `Preview ready: ${payload.summary.accepted} row(s) accepted`, level: "success", }); } function renderPreview(payload) { const rows = payload.rows || []; const issues = payload.row_issues || []; stageToken = payload.stage_token || null; if (!previewBody) return; previewBody.innerHTML = ""; const issueMap = new Map(); issues.forEach((issue) => { issueMap.set(issue.row_number, issue.issues); }); rows.forEach((row) => { const tr = document.createElement("tr"); const rowIssues = issueMap.get(row.row_number) || []; const issuesText = [ ...row.issues, ...rowIssues.map((i) => i.message), ].join(", "); tr.innerHTML = ` ${row.row_number} ${row.state} ${issuesText || "—"} ${Object.values(row.data) .map((value) => `${value ?? ""}`) .join("")} `; previewBody.appendChild(tr); }); previewContainer?.classList.remove("hidden"); if (stageToken && payload.summary.accepted > 0) { actionsEl?.classList.remove("hidden"); commitButton?.removeAttribute("disabled"); } else { actionsEl?.classList.add("hidden"); commitButton?.setAttribute("disabled", "disabled"); } } uploadButton?.addEventListener("click", uploadAndPreview); commitButton?.addEventListener("click", async () => { if (!stageToken) return; commitButton.classList.add("loading"); commitButton.setAttribute("disabled", "disabled"); let response; try { response = await fetch("/imports/projects/commit", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ token: stageToken }), }); } catch (error) { console.error(error); NotificationCenter?.show({ message: "Network error during commit.", level: "error", }); commitButton.classList.remove("loading"); commitButton.removeAttribute("disabled"); return; } if (!response.ok) { const detail = await response.json().catch(() => ({})); const message = detail?.detail || "Commit failed. Please review the import data."; NotificationCenter?.show({ message, level: "error" }); commitButton.classList.remove("loading"); commitButton.removeAttribute("disabled"); return; } const result = await response.json(); NotificationCenter?.show({ message: `Import committed. Created: ${result.summary.created}, Updated: ${result.summary.updated}`, level: "success", }); clearPreview(); if (input) { input.value = ""; } disableUpload(); }); cancelButton?.addEventListener("click", () => { clearPreview(); NotificationCenter?.show({ message: "Import canceled.", level: "info" }); }); });