View raw

1 const downloadRangeButtons = Array.from(document.querySelectorAll("[data-download-range]")); 2 const customDateFields = document.querySelector("[data-custom-date-fields]"); 3 const customDateCollapse = customDateFields ? new bootstrap.Collapse(customDateFields, { toggle: false }) : null; 4 const downloadFrom = document.querySelector("#download-from"); 5 const downloadTo = document.querySelector("#download-to"); 6 const errorBanner = document.querySelector("[data-downloads-error]"); 7 const form = document.querySelector("form.downloads-form"); 8 let submittedButton = null; 9 10 downloadRangeButtons.forEach(option => { 11 option.addEventListener("change", () => { 12 const custom = option.value === "custom"; 13 14 if (customDateCollapse) { 15 custom ? customDateCollapse.show() : customDateCollapse.hide(); 16 } 17 18 if (custom) { 19 downloadFrom?.focus(); 20 return; 21 } 22 23 if (downloadFrom) { 24 downloadFrom.value = option.dataset.from; 25 } 26 27 if (downloadTo) { 28 downloadTo.value = option.dataset.to; 29 } 30 }); 31 }); 32 33 function showError(message) { 34 if (!errorBanner) return; 35 errorBanner.textContent = message; 36 errorBanner.classList.remove("d-none"); 37 } 38 39 function clearError() { 40 if (!errorBanner) return; 41 errorBanner.textContent = ""; 42 errorBanner.classList.add("d-none"); 43 } 44 45 function triggerDownload(blob, filename) { 46 const url = URL.createObjectURL(blob); 47 const link = document.createElement("a"); 48 link.href = url; 49 link.download = filename; 50 document.body.appendChild(link); 51 link.click(); 52 link.remove(); 53 URL.revokeObjectURL(url); 54 } 55 56 function filenameFromResponse(response) { 57 const header = response.headers.get("Content-Disposition") || ""; 58 const match = header.match(/filename="([^"]+)"/); 59 return match ? match[1] : "readings.csv"; 60 } 61 62 function setDownloadPending(button, pending) { 63 if (!button) return; 64 65 button.disabled = pending; 66 button.setAttribute("aria-busy", pending ? "true" : "false"); 67 button.querySelector("[data-download-spinner]")?.classList.toggle("d-none", !pending); 68 } 69 70 if (form) { 71 form.querySelectorAll("[data-download-button]").forEach(button => { 72 button.addEventListener("click", () => { 73 submittedButton = button; 74 }); 75 }); 76 77 form.addEventListener("submit", async event => { 78 event.preventDefault(); 79 clearError(); 80 81 const button = event.submitter || submittedButton || form.querySelector("[data-download-button]"); 82 const params = new URLSearchParams(new FormData(form)); 83 const action = button?.getAttribute("formaction") || form.action; 84 const url = new URL(action, window.location.origin); 85 url.search = params; 86 setDownloadPending(button, true); 87 88 try { 89 const response = await fetch(url); 90 91 if (!response.ok) { 92 const text = await response.text(); 93 showError(text || `Download failed (HTTP ${response.status}).`); 94 return; 95 } 96 97 const blob = await response.blob(); 98 triggerDownload(blob, filenameFromResponse(response)); 99 } catch (err) { 100 showError("Could not reach the server. Check your connection and try again."); 101 } finally { 102 setDownloadPending(button, false); 103 submittedButton = null; 104 } 105 }); 106 } 107