172 lines
5.0 KiB
TypeScript
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";
|
|
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();
|