import "@shoelace-style/shoelace/dist/components/button/button.js"; import "@shoelace-style/shoelace/dist/components/details/details.js"; import "@shoelace-style/shoelace/dist/components/icon/icon.js"; import "@shoelace-style/shoelace/dist/components/input/input.js"; import "@shoelace-style/shoelace/dist/components/option/option.js"; import "@shoelace-style/shoelace/dist/components/spinner/spinner.js"; import "@shoelace-style/shoelace/dist/components/select/select.js"; import "@shoelace-style/shoelace/dist/components/textarea/textarea.js"; import "./shoelace.js"; import "./camera.ts"; import CameraInput from "./camera.ts"; import SlButton from "@shoelace-style/shoelace/dist/components/button/button.js"; import SlSelect from "@shoelace-style/shoelace/dist/components/select/select.js"; import { notify, notifyError } from "./alert"; import { getResponseError } from "./ajaxUtil.js"; const form = document.forms[0]; const cameraInput = form.querySelector("camera-input") as CameraInput; const btnSubmit = form.querySelector("sl-button[type='submit']") as SlButton; const btnUpload = form.querySelector( "sl-button[class='choose-file']", ) as SlButton; const inpImage = form.photo as HTMLInputElement; const imgPreview = document.getElementById( "upload-preview", ) as HTMLImageElement; const xactselect = document.getElementById("transactions") as SlSelect; let dirty = false; window.addEventListener("beforeunload", function(evt) { if (dirty) { evt.preventDefault(); } }); form.querySelectorAll("sl-input, sl-textarea, input").forEach((inp) => { let eventName = inp.tagName == "input" ? "cange" : "sl-change"; inp.addEventListener(eventName, (evt) => { if ((evt.target as HTMLInputElement).value) { dirty = true; } }); }); form.addEventListener("submit", async (evt) => { evt.preventDefault(); btnSubmit.loading = true; const data = new FormData(form); if (!inpImage.files?.length) { data.delete("photo"); const blob = await cameraInput.getBlob(); if (blob) { data.append("photo", blob, "photo.jpg"); } } let r: Response; try { r = await fetch("", { method: "POST", body: data, }); } catch (e) { notifyError(`Failed to submit form: ${e}`); return; } finally { btnSubmit.loading = false; } if (r.ok) { notify("Successfully uploaded receipt", undefined, undefined, null); dirty = false; window.location.href = "/receipts"; } else { const err = await getResponseError(r); notifyError(err); } }); form.addEventListener("reset", () => { dirty = false; }); cameraInput.addEventListener("ready", ((evt: CustomEvent) => { btnSubmit.disabled = !evt.detail.hasPhoto; btnUpload.disabled = !!evt.detail.hasPhoto; if (!!evt.detail.hasPhoto) { inpImage.value = ""; imgPreview.src = ""; } }) as EventListener); const cameraDetails = document.querySelector( "sl-details[summary='Take Photo']", )!; let cameraInitialized = false; cameraDetails.addEventListener("sl-show", () => { if (!cameraInitialized) { cameraInitialized = true; cameraInput.startCamera(); } }); btnUpload.addEventListener("click", (evt) => { evt.preventDefault(); form.photo.showPicker(); }); inpImage.addEventListener("change", () => { if (inpImage.files) { const file = inpImage.files[0]; if (file) { btnSubmit.disabled = false; imgPreview.src = URL.createObjectURL(file); } } }); async function fetchTransactions() { let r: Response; try { r = await fetch("/transactions"); } catch (e) { notifyError(e.toString()); return; } finally { xactselect.placeholder = ""; xactselect.querySelectorAll("sl-spinner").forEach((e) => e.remove()); } if (!r.ok) { notifyError(await getResponseError(r), 10000); return; } xactselect.placeholder = "Select existing transaction"; xactselect.disabled = false; for (const xact of await r.json()) { const option = document.createElement("sl-option"); option.value = xact.id; option.textContent = `${xact.description} • ${xact.date} • $${xact.amount}`; option.dataset.amount = xact.amount; option.dataset.date = xact.date.split("T")[0]; option.dataset.vendor = xact.description; xactselect.appendChild(option); } } xactselect.addEventListener("sl-change", () => { const value = xactselect.value; if (!value) { return; } const option: HTMLOptionElement | null = xactselect.querySelector( `[value="${value}"]`, ); if (!option) { return; } form.querySelectorAll("sl-input").forEach((elm) => { if (elm.name && elm.value !== undefined) { const value = option.dataset[elm.name]; if (value) { elm.value = value; } } }); }); fetchTransactions();