diff --git a/inventory/static/js/csv.js b/inventory/static/js/csv.js index 9ccb765..3b22e12 100644 --- a/inventory/static/js/csv.js +++ b/inventory/static/js/csv.js @@ -25,9 +25,9 @@ async function export_csv(ids, csv_route, filename=`${csv_route}_export.csv`) { URL.revokeObjectURL(url); } else { - renderToast({ message: `Export failed: ${result.error}`, type: 'danger' }); + Toast.renderToast({ message: `Export failed: ${result.error}`, type: 'danger' }); } } catch (err) { - renderToast({ message: `Export failed: ${err}`, type: 'danger' }); + Toast.renderToast({ message: `Export failed: ${err}`, type: 'danger' }); } } \ No newline at end of file diff --git a/inventory/static/js/image.js b/inventory/static/js/image.js new file mode 100644 index 0000000..57653cc --- /dev/null +++ b/inventory/static/js/image.js @@ -0,0 +1,52 @@ +const ImageWidget = (() => { + function submitImageUpload(id) { + const form = document.getElementById(`image-upload-form-${id}`); + const formData = new FormData(form); + + fetch("/api/images", { + method: "POST", + body: formData + }).then(async response => { + if (!response.ok) { + // Try to parse JSON, fallback to text + const contentType = response.headers.get("Content-Type") || ""; + let errorDetails; + if (contentType.includes("application/json")) { + errorDetails = await response.json(); + } else { + errorDetails = { error: await response.text() }; + } + throw errorDetails; + } + return response.json(); + }).then(data => { + Toast.renderToast({ message: `Image uploaded.`, type: "success" }); + location.reload(); + }).catch(err => { + const msg = typeof err === "object" && err.error ? err.error : err.toString(); + Toast.renderToast({ message: `Upload failed: ${msg}`, type: "danger" }); + }); + } + + function deleteImage(inventoryId, imageId) { + if (!confirm("Are you sure you want to delete this image?")) return; + + fetch(`/api/images/${imageId}`, { + method: "DELETE" + }).then(response => response.json()).then(data => { + if (data.success) { + Toast.renderToast({ message: "Image deleted.", type: "success" }); + location.reload(); // Update view + } else { + Toast.renderToast({ message: `Failed to delete: ${data.error}`, type: "danger" }); + } + }).catch(err => { + Toast.renderToast({ message: `Error deleting image: ${err}`, type: "danger" }); + }); + } + + return { + submitImageUpload, + deleteImage + } +})(); \ No newline at end of file diff --git a/inventory/static/js/toast.js b/inventory/static/js/toast.js new file mode 100644 index 0000000..c6797d6 --- /dev/null +++ b/inventory/static/js/toast.js @@ -0,0 +1,70 @@ +document.addEventListener("DOMContentLoaded", () => { + const toastData = localStorage.getItem("toastMessage"); + if (toastData) { + const { message, type } = JSON.parse(toastData); + Toast.renderToast({ message, type }); + localStorage.removeItem("toastMessage"); + } +}); + +const Toast = (() => { + const ToastConfig = { + containerId: 'toast-container', + positionClasses: 'toast-container position-fixed bottom-0 end-0 p-3', + defaultType: 'info', + defaultTimeout: 3000 + }; + + function updateToastConfig(overrides = {}) { + Object.assign(ToastConfig, overrides); + } + + function renderToast({ message, type = ToastConfig.defaultType, timeout = ToastConfig.defaultTimeout }) { + if (!message) { + console.warn('renderToast was called without a message.'); + return; + } + + // Auto-create the toast container if it doesn't exist + let container = document.getElementById(ToastConfig.containerId); + if (!container) { + container = document.createElement('div'); + container.id = ToastConfig.containerId; + container.className = ToastConfig.positionClasses; + document.body.appendChild(container); + } + + const toastId = `toast-${Date.now()}`; + const wrapper = document.createElement('div'); + wrapper.innerHTML = ` +