receipts/js/receipt-form.ts

172 lines
5.0 KiB
TypeScript

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";
let prev = xactselect.firstChild;
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.insertBefore(option, prev);
}
}
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();