feat: implement export functionality for projects and scenarios with CSV and Excel support
This commit is contained in:
97
static/js/exports.js
Normal file
97
static/js/exports.js
Normal file
@@ -0,0 +1,97 @@
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
const modalContainer = document.createElement("div");
|
||||
modalContainer.id = "export-modal-container";
|
||||
document.body.appendChild(modalContainer);
|
||||
|
||||
async function loadModal(dataset) {
|
||||
const response = await fetch(`/exports/modal/${dataset}`);
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to load export modal (${response.status})`);
|
||||
}
|
||||
const html = await response.text();
|
||||
modalContainer.innerHTML = html;
|
||||
const modal = modalContainer.querySelector(".modal");
|
||||
if (!modal) return;
|
||||
modal.classList.add("is-active");
|
||||
|
||||
const closeButtons = modal.querySelectorAll("[data-dismiss='modal']");
|
||||
closeButtons.forEach((btn) =>
|
||||
btn.addEventListener("click", () => closeModal(modal))
|
||||
);
|
||||
|
||||
const form = modal.querySelector("[data-export-form]");
|
||||
if (form) {
|
||||
form.addEventListener("submit", handleSubmit);
|
||||
}
|
||||
}
|
||||
|
||||
function closeModal(modal) {
|
||||
modal.classList.remove("is-active");
|
||||
setTimeout(() => {
|
||||
modalContainer.innerHTML = "";
|
||||
}, 200);
|
||||
}
|
||||
|
||||
async function handleSubmit(event) {
|
||||
event.preventDefault();
|
||||
const form = event.currentTarget;
|
||||
const submitUrl = form.action;
|
||||
const formData = new FormData(form);
|
||||
const format = formData.get("format") || "csv";
|
||||
|
||||
const response = await fetch(submitUrl, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
format,
|
||||
include_metadata: formData.get("include_metadata") === "true",
|
||||
filters: null,
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
alert("Export failed. Please try again.");
|
||||
return;
|
||||
}
|
||||
|
||||
const blob = await response.blob();
|
||||
const disposition = response.headers.get("Content-Disposition");
|
||||
let filename = "export";
|
||||
if (disposition) {
|
||||
const match = disposition.match(/filename=([^;]+)/i);
|
||||
if (match) {
|
||||
filename = match[1].replace(/"/g, "");
|
||||
}
|
||||
}
|
||||
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const link = document.createElement("a");
|
||||
link.href = url;
|
||||
link.download = filename;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
link.remove();
|
||||
window.URL.revokeObjectURL(url);
|
||||
|
||||
const modal = modalContainer.querySelector(".modal");
|
||||
if (modal) {
|
||||
closeModal(modal);
|
||||
}
|
||||
}
|
||||
|
||||
document.querySelectorAll("[data-export-trigger]").forEach((button) => {
|
||||
button.addEventListener("click", async (event) => {
|
||||
event.preventDefault();
|
||||
const dataset = button.getAttribute("data-export-target");
|
||||
if (!dataset) return;
|
||||
try {
|
||||
await loadModal(dataset);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
alert("Unable to open export dialog.");
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
100
static/js/imports.js
Normal file
100
static/js/imports.js
Normal file
@@ -0,0 +1,100 @@
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
const dropzones = document.querySelectorAll("[data-import-dropzone]");
|
||||
const uploadButtons = document.querySelectorAll(
|
||||
"[data-import-upload-trigger]"
|
||||
);
|
||||
const resetButtons = document.querySelectorAll("[data-import-reset]");
|
||||
const feedbackEl = document.querySelector("#import-upload-feedback");
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
dropzones.forEach((zone) => {
|
||||
const input = zone.querySelector("input[type='file']");
|
||||
const uploadButton = zone
|
||||
.closest("[data-import-upload]")
|
||||
.querySelector("[data-import-upload-trigger]");
|
||||
const resetButton = zone
|
||||
.closest("[data-import-upload]")
|
||||
.querySelector("[data-import-reset]");
|
||||
|
||||
function enableUpload() {
|
||||
if (uploadButton) {
|
||||
uploadButton.disabled = false;
|
||||
}
|
||||
if (resetButton) {
|
||||
resetButton.hidden = false;
|
||||
}
|
||||
}
|
||||
|
||||
function disableUpload() {
|
||||
if (uploadButton) {
|
||||
uploadButton.disabled = true;
|
||||
}
|
||||
if (resetButton) {
|
||||
resetButton.hidden = true;
|
||||
}
|
||||
}
|
||||
|
||||
zone.addEventListener("dragover", (event) => {
|
||||
event.preventDefault();
|
||||
zone.classList.add("dragover");
|
||||
});
|
||||
|
||||
zone.addEventListener("dragleave", () => {
|
||||
zone.classList.remove("dragover");
|
||||
});
|
||||
|
||||
zone.addEventListener("drop", (event) => {
|
||||
event.preventDefault();
|
||||
zone.classList.remove("dragover");
|
||||
if (!event.dataTransfer?.files?.length) {
|
||||
return;
|
||||
}
|
||||
input.files = event.dataTransfer.files;
|
||||
enableUpload();
|
||||
hideFeedback();
|
||||
});
|
||||
|
||||
input.addEventListener("change", () => {
|
||||
if (input.files?.length) {
|
||||
enableUpload();
|
||||
hideFeedback();
|
||||
} else {
|
||||
disableUpload();
|
||||
}
|
||||
});
|
||||
|
||||
resetButton?.addEventListener("click", () => {
|
||||
input.value = "";
|
||||
disableUpload();
|
||||
hideFeedback();
|
||||
});
|
||||
|
||||
uploadButton?.addEventListener("click", () => {
|
||||
if (!input.files?.length) {
|
||||
showFeedback(
|
||||
"Please select a CSV or XLSX file before uploading.",
|
||||
"error"
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
showFeedback("Uploading…", "info");
|
||||
uploadButton.disabled = true;
|
||||
uploadButton.classList.add("loading");
|
||||
|
||||
// Actual upload logic handled separately (e.g., fetch).
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user