aboutsummaryrefslogtreecommitdiffstats
path: root/src/wwwroot/scripts/back
diff options
context:
space:
mode:
Diffstat (limited to 'src/wwwroot/scripts/back')
-rw-r--r--src/wwwroot/scripts/back/bestillinger.js321
-rw-r--r--src/wwwroot/scripts/back/dokumenter.js102
-rw-r--r--src/wwwroot/scripts/back/index.js51
-rw-r--r--src/wwwroot/scripts/back/produkter.js671
4 files changed, 1145 insertions, 0 deletions
diff --git a/src/wwwroot/scripts/back/bestillinger.js b/src/wwwroot/scripts/back/bestillinger.js
new file mode 100644
index 0000000..17b56d5
--- /dev/null
+++ b/src/wwwroot/scripts/back/bestillinger.js
@@ -0,0 +1,321 @@
+import Grid from "../grid";
+import {strings} from "../i10n";
+import {$, doc, pageInit, toaster} from "../base";
+import {eyeIcon} from "../icons";
+import {utilites} from "../utilities";
+import {messages} from "../messages";
+import {cancelOrder, captureVippsOrder, getOrderDetails, getOrders, refundOrder} from "../api/order-api";
+import Modal from "bootstrap/js/src/modal";
+
+if (location.pathname.startsWith("/kontoret/bestillinger")) {
+ const ordersLoader = $("#orders-loader");
+ const ordersWrapper = $("#orders-wrapper");
+ const orderInfoModalEl = $("#order-info-modal");
+ const orderInfoModal = new Modal(orderInfoModalEl);
+
+ const grid = new Grid({
+ search: {
+ dataIds: ["orderReference", ["contactInfo", "name"], ["contactInfo", "emailAddress"], ["contactInfo", "phoneNumber"]],
+ },
+ strings: {
+ search: strings.languageSpesific.search,
+ nextPage: strings.languageSpesific.next_page,
+ previousPage: strings.languageSpesific.previous_page,
+ },
+ debug: location.href.includes("localhost"),
+ classes: {
+ table: "table table-bordered mt-3",
+ thead: "table-primary",
+ },
+ columns: [
+ {
+ dataId: "orderReference",
+ minWidth: "150px",
+ columnName: "Referanse",
+ className: "btn-link cursor-pointer",
+ click: (row) => openViewOrderModal(row),
+ },
+ {
+ columnName: "Navn",
+ dataId: ["contactInfo", "name"],
+ minWidth: "200px",
+ },
+ {
+ columnName: "E-postadresse",
+ dataId: ["contactInfo", "emailAddress"],
+ minWidth: "200px",
+ },
+ {
+ columnName: "Telefonnummer",
+ dataId: ["contactInfo", "phoneNumber"],
+ minWidth: "150px",
+ },
+ {
+ dataId: "created",
+ minWidth: "175px",
+ cellValue: (row) => utilites.toReadableDateString(new Date(row.created)),
+ columnName: "Dato",
+ },
+ {
+ minWidth: "150px",
+ columnName: "Status",
+ cellValue: (row) => {
+ const status = doc.createElement("span");
+ switch (row.status) {
+ case 0: {
+ status.innerText = "Pågående";
+ break;
+ }
+ case 1: {
+ status.innerText = "Kansellert";
+ break;
+ }
+ case 2: {
+ status.innerText = "Feilet";
+ status.classList.add("text-danger");
+ break;
+ }
+ case 3: {
+ status.innerText = "Fullført";
+ status.classList.add("text-success");
+ break;
+ }
+ case 4: {
+ status.innerText = "Venter på faktura";
+ break;
+ }
+ case 5: {
+ status.innerText = "Venter på vipps";
+ break;
+ }
+ }
+ return status;
+ },
+ },
+ {
+ columnName: "",
+ width: "40px",
+ minWidth: "40px",
+ cellValue: (row) => {
+ const viewOrder = doc.createElement("button");
+ viewOrder.className = "btn btn-link text-info shadow-none";
+ viewOrder.title = strings.languageSpesific.view + ` "${row.orderReference}"`;
+ viewOrder.innerHTML = eyeIcon();
+ viewOrder.onclick = () => openViewOrderModal(row);
+ return viewOrder;
+ },
+ },
+ ],
+ });
+
+ if (location.pathname.startsWith("/kontoret/bestillinger")) {
+ pageInit(() => {
+ renderProductsView();
+ });
+ }
+
+ function openViewOrderModal({id, orderReference}) {
+ orderInfoModalEl.querySelector(".modal-title").innerText = orderReference;
+ $("#loader").style.display = "";
+ $("#loaded").style.display = "none";
+
+ orderInfoModal.show();
+
+ getOrderDetails(id).then(res => {
+ if (res.ok) {
+ res.json().then(order => {
+ order = utilites.resolveReferences(order);
+ $("#contact-info-name").innerText = order.contactInformation.name;
+ $("#contact-info-emailAddress").innerHTML = `<a href='mailto:${order.contactInformation.emailAddress}'>${order.contactInformation.emailAddress}</a>`;
+ $("#contact-info-phoneNumber").innerHTML = `<a href='tel:${order.contactInformation.phoneNumber}'>${order.contactInformation.phoneNumber}</a>`;
+ $("#order-reference").innerText = order.orderReference;
+ $("#order-date").innerText = new Date(order.orderDate).toLocaleDateString();
+ $("#order-payment-type").innerText = utilites.getOrderPaymentName(order.paymentType);
+ $("#order-status").innerText = utilites.getOrderStatusName(order.status);
+ if (order.comment && order.comment !== "") {
+ $("#order-comment").innerText = order.comment;
+ $("#order-comment").style.display = "inline-block";
+ } else {
+ $("#order-comment").style.display = "none";
+ }
+
+ if (order.paymentType === 0) {
+ if ([
+ "SALE",
+ "RESERVED",
+ ].includes(order.vippsTransactionStatus)) {
+ $("#vipps-order-refund").classList.remove("d-none");
+ $("#vipps-order-refund").onclick = () => {
+ if (confirm("Er du sikker på at du vil refundere ordren " + order.orderReference)) {
+ $("#vipps-order-refund").classList.add("loading");
+ refundOrder(order.id).then(res => {
+ if (res.ok) {
+ toaster.success("Refundert", "Ordren er refundert, og vipps har startet refundering");
+ } else {
+ console.error("Received non-successful status code when submitting refund order command");
+ toaster.error("En feil oppstod", "Prøv å refundere i vipps portalen");
+ }
+ $("#vipps-order-refund").classList.remove("loading");
+ }).catch(err => {
+ $("#vipps-order-refund").classList.remove("loading");
+
+ console.error("refund order NetworkRequest failed");
+ toaster.error("En uventet feil oppstod");
+ console.error(err);
+ });
+ }
+ };
+ }
+
+ if ([
+ "SALE",
+ "RESERVED",
+ ].includes(order.vippsTransactionStatus)) {
+ $("#vipps-order-capture").classList.remove("d-none");
+ $("#vipps-order-capture").onclick = () => {
+ if (confirm("Er du sikker på at du vil fullføre ordren " + order.orderReference)) {
+ $("#vipps-order-capture").classList.add("loading");
+ captureVippsOrder(order.id).then(res => {
+ if (res.ok) {
+ toaster.success("Fullført", "Ordren er fullført, og vipps har registrert transaksjonen");
+ } else {
+ console.error("Received non-successful status code when submitting fulfill order command");
+ toaster.error("En feil oppstod", "Prøv å fullføre i vipps portalen");
+ }
+ $("#vipps-order-capture").classList.remove("loading");
+ }).catch(err => {
+ $("#vipps-order-capture").classList.remove("loading");
+ console.error("fulfill order NetworkRequest failed");
+ toaster.error("En uventet feil oppstod");
+ console.error(err);
+ });
+ }
+ };
+ }
+
+ if ([
+ "SALE",
+ "RESERVED",
+ ].includes(order.vippsTransactionStatus)) {
+ $("#vipps-order-cancel").classList.remove("d-none");
+ $("#vipps-order-cancel").onclick = () => {
+ if (confirm("Er du sikker på at du vil kansellere ordren " + order.orderReference)) {
+ $("#vipps-order-cancel").classList.add("loading");
+ cancelOrder(order.id).then(res => {
+ if (res.ok) {
+ toaster.success("Kansellert", "Ordren er kansellert");
+ } else {
+ console.error("Received non-successful status code when submitting cancel order command");
+ toaster.error("En feil oppstod", "Prøv å kansellere i vipps portalen");
+ }
+ $("#vipps-order-cancel").classList.remove("loading");
+ }).catch(err => {
+ $("#vipps-order-cancel").classList.remove("loading");
+ console.error("cancel order NetworkRequest failed");
+ toaster.error("En uventet feil oppstod");
+ console.error(err);
+ });
+ }
+ };
+ }
+
+
+ switch (order.vippsTransactionStatus) {
+ case "SALE":
+ $("#vipps-status").innerText = "Salg (fullført og overført)";
+ break;
+ case "RESERVED":
+ $("#vipps-status").innerText = "Reservert";
+ break;
+ case "CANCELLED":
+ $("#vipps-status").innerText = "Kansellert";
+ break;
+ case "REJECTED":
+ $("#vipps-status").innerText = "Avslått";
+ break;
+ case "SALE_FAILED":
+ $("#vipps-status").innerText = "Salg feilet";
+ break;
+ case "AUTO_CANCEL":
+ $("#vipps-status").innerText = "Automatisk kansellering";
+ break;
+ case "RESERVE_FAILED":
+ $("#vipps-status").innerText = "Reservering feilet";
+ break;
+ }
+ $("#vipps-link").innerHTML = `<a href='https://portal.vipps.no/61861/transactions/${order.vippsId}' target="_blank">Åpne bestillingen i Vipps-portalen</a>`;
+ $("#vipps-section").classList.remove("d-none");
+ } else {
+ $("#vipps-section").classList.add("d-none");
+ }
+
+ orderInfoModalEl.querySelector(".modal-body").style.display = "";
+
+ const productsBody = orderInfoModalEl.querySelector("tbody");
+ productsBody.innerHTML = "";
+ let productIndex = 1;
+ let totalPrice = 0;
+ for (const product of order.products) {
+ const rowItem = doc.createElement("tr");
+ const nrCell = doc.createElement("td");
+ nrCell.innerText = productIndex.toString();
+ const nameCell = doc.createElement("td");
+ nameCell.innerText = product.name;
+ const countCell = doc.createElement("td");
+ countCell.innerText = product.count;
+ const priceCell = doc.createElement("td");
+ const totalSum = product.payedPrice * product.count;
+ priceCell.innerText = `${product.payedPrice} (totalt: ${totalSum})`;
+
+ rowItem.appendChild(nrCell);
+ rowItem.appendChild(nameCell);
+ rowItem.appendChild(countCell);
+ rowItem.appendChild(priceCell);
+ productsBody.appendChild(rowItem);
+ productIndex++;
+ totalPrice += totalSum;
+ }
+ $("#order-total").innerText = totalPrice;
+ $("#loader").style.display = "none";
+ $("#loaded").style.display = "";
+ orderInfoModal.show();
+ });
+ } else {
+ toaster.error("En feil oppstod", "Kunne ikke hente ordren, prøv igjen snart!");
+ }
+ }).catch(error => {
+ console.error(error);
+ });
+ }
+
+ function renderProductsView() {
+ ordersWrapper.innerHTML = "";
+ ordersWrapper.classList.add("d-none");
+ ordersLoader.classList.remove("d-none");
+
+ getOrders("not-cancelled").then(res => {
+ if (res.ok) {
+ res.json().then(products => {
+ grid.render(ordersWrapper);
+ products = utilites.resolveReferences(products);
+ grid.refresh(products);
+ const queryOrderRef = new URL(location.href).searchParams.get("order");
+ if (queryOrderRef) {
+ const product = products.find(c => c.orderReference === queryOrderRef);
+ openViewOrderModal(product);
+ }
+ });
+ ordersWrapper.classList.remove("d-none");
+ ordersLoader.classList.add("d-none");
+ } else {
+ utilites.handleError(res, {
+ title: strings.languageSpesific.could_not_retrieve_orders,
+ message: strings.languageSpesific.try_again_soon,
+ });
+ }
+ }).catch(error => {
+ console.error(error);
+ toaster.errorObj(messages.networkRequestFailed);
+ });
+ }
+}
diff --git a/src/wwwroot/scripts/back/dokumenter.js b/src/wwwroot/scripts/back/dokumenter.js
new file mode 100644
index 0000000..c92bdbf
--- /dev/null
+++ b/src/wwwroot/scripts/back/dokumenter.js
@@ -0,0 +1,102 @@
+import {$, pageInit, toaster} from "../base";
+import Quill from "../vendor/quill";
+import {uploadDocumentImages, getDocument, setDocument} from "../api/documents-api";
+import {configuration} from "../configuration";
+import {utilites} from "../utilities";
+import {strings} from "../i10n";
+
+if (location.pathname.startsWith("/kontoret/dokumenter")) {
+ const documentSelect = $("#document-selector");
+ const publishButton = $("#publish-button");
+ const toolbarOptions = [["link", "image"],
+ ["bold", "italic", "underline", "strike"],
+ ["blockquote", "code-block"],
+ [{"header": 1}, {"header": 2}],
+ [{"list": "ordered"}, {"list": "bullet"}],
+ [{"script": "sub"}, {"script": "super"}],
+ [{"indent": "-1"}, {"indent": "+1"}],
+ [{"direction": "rtl"}],
+ [{"size": ["small", false, "large", "huge"]}],
+ [{"header": [1, 2, 3, 4, 5, 6, false]}],
+ [{"color": []}, {"background": []}],
+ [{"font": []}],
+ [{"align": []}],
+ ["clean"]];
+
+
+ pageInit(() => {
+ documentSelect.onchange = () => setDocumentContent();
+ publishButton.onclick = () => publishDocument();
+ const editor = new Quill("#editor", {
+ theme: "snow",
+ modules: {
+ toolbar: toolbarOptions,
+ blotFormatter: true,
+ imageDrop: true,
+ imageUploader: {
+ upload: (file) => new Promise(((resolve, reject) => {
+ uploadDocumentImages([file]).then(res => {
+ if (res.ok) {
+ res.json().then(fileNames => {
+ fileNames = utilites.resolveReferences(fileNames);
+ resolve(configuration.paths.documents + fileNames[0]);
+ });
+ } else {
+ reject(res);
+ }
+ }).catch(error => {
+ reject(error);
+ });
+ })),
+ },
+ },
+ });
+
+ function publishDocument() {
+ const html = editor.container.querySelector(".ql-editor").innerHTML;
+ if (!html || html === "<p><br></p>") return;
+ setDocument(documentSelect.value, html).then(res => {
+ if (res.ok) {
+ toaster.success("Dokumentet er publisert");
+ } else {
+ utilites.handleError(res, {
+ title: "Kunne ikke publisere dokumentet",
+ message: "Prøv igjen senere",
+ });
+ }
+ }).catch(error => {
+ console.error(error);
+ toaster.errorObj({
+ title: strings.languageSpesific.could_not_reach_server,
+ message: strings.languageSpesific.try_again_soon,
+ });
+ });
+ }
+
+ function setDocumentContent() {
+ getDocument(documentSelect.value).then(res => {
+ if (res.ok) {
+ res.text().then(content => {
+ editor.setText("");
+ if (content) {
+ editor.clipboard.dangerouslyPasteHTML(0, content);
+ }
+ });
+ } else {
+ utilites.handleError(res, {
+ title: strings.languageSpesific.an_error_occured,
+ message: strings.languageSpesific.try_again_soon,
+ });
+ }
+ }).catch(error => {
+ console.error(error);
+ toaster.errorObj({
+ title: strings.languageSpesific.could_not_reach_server,
+ message: strings.languageSpesific.try_again_soon,
+ });
+ });
+ }
+
+ setDocumentContent();
+ });
+} \ No newline at end of file
diff --git a/src/wwwroot/scripts/back/index.js b/src/wwwroot/scripts/back/index.js
new file mode 100644
index 0000000..5b71a8d
--- /dev/null
+++ b/src/wwwroot/scripts/back/index.js
@@ -0,0 +1,51 @@
+import {$, toaster} from "../base";
+import {logout, updatePassword} from "../api/account-api";
+import {Modal} from "bootstrap";
+import {utilites} from "../utilities";
+import {messages} from "../messages";
+import {strings} from "../i10n.ts";
+
+const updatePasswordModalElement = $("#update-password-modal");
+const updatePasswordModal = new Modal(updatePasswordModalElement);
+const logoutButton = $(".logout-btn");
+const newPasswordInput = $("#input-new-password");
+const submitNewPasswordFormButton = $("#submit-new-password-form");
+
+logoutButton.addEventListener("click", (e) => {
+ e.preventDefault();
+ if (confirm(strings.languageSpesific.are_you_sure)) {
+ logout().then((res) => {
+ if (res.status === 200) {
+ setTimeout(function () {
+ location.href = "/";
+ }, 500);
+ }
+ });
+ }
+});
+
+function initSetpasswordModal() {
+ newPasswordInput.value = "";
+ submitNewPasswordFormButton.addEventListener("click", () => {
+ if (newPasswordInput.value.length < 6) return;
+ submitNewPasswordFormButton.querySelector(".spinner-border").classList.remove("d-none");
+ submitNewPasswordFormButton.classList.add("disabled");
+ updatePassword(newPasswordInput.value).then(res => {
+ if (res.ok) {
+ toaster.success(strings.languageSpesific.new_password_is_applied);
+ newPasswordInput.value = "";
+ updatePasswordModal.hide();
+ } else {
+ utilites.handleError(res, messages.unknownError);
+ }
+ submitNewPasswordFormButton.querySelector(".spinner-border").classList.add("d-none");
+ submitNewPasswordFormButton.classList.remove("disabled");
+ });
+ });
+}
+
+$(".open-update-password-modal").addEventListener("click", (e) => {
+ e.preventDefault();
+ initSetpasswordModal();
+ updatePasswordModal.show();
+}); \ No newline at end of file
diff --git a/src/wwwroot/scripts/back/produkter.js b/src/wwwroot/scripts/back/produkter.js
new file mode 100644
index 0000000..ab8ecf6
--- /dev/null
+++ b/src/wwwroot/scripts/back/produkter.js
@@ -0,0 +1,671 @@
+import {$, $$, doc, toaster} from "../base";
+import {configuration} from "../configuration";
+import {utilites} from "../utilities";
+import {createCategory, deleteCategory, disableCategory, enableCategory, getCategories} from "../api/categories-api";
+import {createProduct, deleteProduct, getProducts, updateProduct, uploadProductImages} from "../api/products-api";
+import Modal from "bootstrap/js/dist/modal";
+import Grid from "../grid";
+import {messages} from "../messages";
+import {eyeIcon, pencilSquareIcon, plusIcon, threeDotsVerticalIcon, trashIcon} from "../icons";
+import {strings} from "../i10n.ts";
+
+let shouldReloadProductsView;
+const isProductsPage = location.pathname.startsWith("/kontoret/produkter");
+const inputImages = $("#input-images-row");
+const productsWrapper = $("#products");
+const productsLoader = $("#products-loader");
+const productForm = $("#product-form");
+const productCategoriesPickerElement = $("#product-category-picker-wrapper #picker");
+const productCategoriesPickerLoadingElement = $("#product-category-picker-wrapper #loader");
+const productModalElement = $("#product-modal");
+const submitProductFormButton = $("#submit-product-form");
+const submitProductFormButtonSpinner = $("#submit-product-form .spinner-border");
+const openProductModalButton = $("#open-product-modal");
+const productModalTitle = $("#product-modal-title");
+const productModal = isProductsPage ? new Modal(productModalElement, {
+ backdrop: "static",
+}) : undefined;
+
+const newCategoryForm = $("#new-category-form");
+const newCategoryName = $("#new-category-name");
+const categoryModalElement = $("#categories-modal");
+const categoryModal = isProductsPage ? new Modal(categoryModalElement) : undefined;
+const categoryListElement = $("#categories-modal #list-wrapper");
+const openCategoriesModalButton = $("#open-categories-modal");
+const categoryListLoadingElement = $("#categories-modal #loading-wrapper");
+
+if (isProductsPage) {
+ renderProductsView();
+ sessionStorage.removeItem(configuration.storageKeys.productForm.imageUrls);
+ openProductModalButton.addEventListener("click", () => openProductModal(undefined, undefined));
+ openCategoriesModalButton.addEventListener("click", openCategoriesModal);
+}
+
+/*
+ PRODUCTS
+*/
+
+const grid = new Grid({
+ search: {
+ dataIds: ["name", ["category", "name"]],
+ },
+ strings: {
+ search: strings.languageSpesific.search,
+ nextPage: strings.languageSpesific.next_page,
+ previousPage: strings.languageSpesific.previous_page,
+ },
+ classes: {
+ table: "table table-bordered mt-3",
+ thead: "table-primary",
+ },
+ columns: [
+ {
+ dataId: "name",
+ minWidth: "250px",
+ columnName: "Navn",
+ },
+ {
+ columnName: "Beskrivelse",
+ dataId: "description",
+ maxWidth: "250px",
+ minWidth: "250px",
+ width: "250px",
+ },
+ {
+ minWidth: "250px",
+ dataId: ["category", "name"],
+ columnName: "Kategori",
+ },
+ {
+ minWidth: "100px",
+ columnName: "Pris",
+ cellValue: (row) => row.price + row.readablePriceSuffix,
+ },
+ {
+ columnName: "",
+ width: "80px",
+ minWidth: "80px",
+ cellValue: (row) => {
+ const group = doc.createElement("div");
+ group.className = "btn-group";
+ const deleteProductButton = doc.createElement("button");
+ deleteProductButton.className = "btn btn-link text-danger shadow-none p-0 ps-3";
+ deleteProductButton.title = strings.languageSpesific.delete + ` "${row.name}"`;
+ deleteProductButton.innerHTML = trashIcon("22", "22");
+ deleteProductButton.onclick = () => removeProduct(row.id, row.name);
+ const editProductButton = doc.createElement("button");
+ editProductButton.className = "btn btn-link text-info shadow-none p-0";
+ editProductButton.title = strings.languageSpesific.edit + ` "${row.name}"`;
+ editProductButton.innerHTML = pencilSquareIcon();
+ editProductButton.onclick = () => openProductModal(row);
+ group.appendChild(editProductButton);
+ group.appendChild(deleteProductButton);
+ return group;
+ },
+ },
+ ],
+});
+
+function renderProductsView() {
+ productsWrapper.innerHTML = "";
+ productsWrapper.classList.add("d-none");
+ productsLoader.classList.remove("d-none");
+
+ getProducts().then(res => {
+ if (res.ok) {
+ res.json().then(products => {
+ grid.render(productsWrapper);
+ products = utilites.resolveReferences(products);
+ grid.refresh(products);
+ });
+ productsWrapper.classList.remove("d-none");
+ productsLoader.classList.add("d-none");
+ } else {
+ utilites.handleError(res, {
+ title: strings.languageSpesific.could_not_retrieve_products,
+ message: strings.languageSpesific.try_again_soon,
+ });
+ }
+ }).catch(error => {
+ console.error(error);
+ toaster.errorObj(messages.networkRequestFailed);
+ });
+}
+
+function removeProduct(id, name) {
+ if (!name || !id) return;
+ if (confirm(`${strings.languageSpesific.are_you_sure_you_want_to_delete} ${name}?`)) {
+ deleteProduct(id).then(res => {
+ if (res.ok) {
+ toaster.success(`${name} ${strings.languageSpesific.is_deleted_LC}`);
+ grid.removeByID(id);
+ } else {
+ utilites.handleError(res, {
+ title: strings.languageSpesific.could_not_delete_product,
+ message: strings.languageSpesific.try_again_soon,
+ });
+ }
+ }).catch(error => {
+ console.error(error);
+ toaster.errorObj(messages.networkRequestFailed);
+ });
+ }
+}
+
+
+function uploadFiles() {
+ const input = doc.createElement("input");
+ const maxFiles = 5;
+ const maxFileSize = 2 * 1024 * 1024;
+ input.type = "file";
+ input.multiple = true;
+ input.accept = "image/png,image/jpeg";
+ input.onchange = (e) => {
+ productForm.classList.add("loading");
+ const files = e.target.files;
+
+ if (files.length > maxFiles) {
+ toaster.error(strings.languageSpesific.too_many_files, strings.languageSpesific.max_five_files_at_a_time);
+ productForm.classList.remove("loading");
+ return;
+ }
+
+ for (const file of files) {
+ if (!input.accept.split(",").includes(file.type)) {
+ toaster.error(strings.languageSpesific.invalid_file, file.name + " " + strings.languageSpesific.has_invalid_file_format_LC);
+ productForm.classList.remove("loading");
+ return;
+ }
+ if (file.size > maxFileSize) {
+ toaster.error(strings.languageSpesific.too_big_file, file.name + " " + strings.languageSpesific.is_too_big_LC);
+ productForm.classList.remove("loading");
+ return;
+ }
+ }
+
+ uploadProductImages(files).then(res => {
+ if (res.ok) {
+ res.json().then(fileNameArray => {
+ fileNameArray = utilites.resolveReferences(fileNameArray);
+ const sessionStorageImages = utilites.getSessionStorageJSON(configuration.storageKeys.productForm.imageUrls) ?? [];
+ const newImages = [...sessionStorageImages];
+ for (let i = 0; i < fileNameArray.length; i++) {
+ newImages.push({
+ order: i,
+ fileName: fileNameArray[i],
+ });
+ }
+ utilites.setSessionStorageJSON(configuration.storageKeys.productForm.imageUrls, newImages);
+ renderProductFormImages(() => {
+ productForm.classList.remove("loading");
+ if (inputImages.childNodes.length > 3) {
+ inputImages.scrollLeft = inputImages.scrollWidth;
+ }
+ });
+ });
+ } else {
+ utilites.handleError(res, {
+ title: strings.languageSpesific.could_not_upload + " " + (files.length === 1
+ ? strings.languageSpesific.the_image.toLocaleLowerCase()
+ : strings.languageSpesific.the_images.toLocaleLowerCase()),
+ message: strings.languageSpesific.try_again_soon,
+ });
+ productForm.classList.remove("loading");
+ }
+ }).catch(error => {
+ console.error(error);
+ toaster.errorObj(messages.networkRequestFailed);
+ productForm.classList.remove("loading");
+ });
+ };
+ input.click();
+}
+
+function renderProductFormImages(cb) {
+ inputImages.innerHTML = "";
+ inputImages.appendChild(getAddImageCardButton());
+ let images = utilites.getSessionStorageJSON(configuration.storageKeys.productForm.imageUrls);
+ if (utilites.array.isEmpty(images)) return;
+ images = images.sort(img => img.order);
+ for (const image of images) {
+ inputImages.appendChild(generateProductFormImageColumn(image));
+ }
+ // drag to scroll
+ let pos = {top: 0, left: 0, x: 0, y: 0};
+ let mouseIsDown;
+ inputImages.addEventListener("mousemove", (e) => {
+ if (mouseIsDown) {
+ const dx = e.clientX - pos.x;
+ // scroll
+ inputImages.scrollLeft = pos.left - dx;
+ }
+ });
+ inputImages.addEventListener("mousedown", (e) => {
+ mouseIsDown = true;
+ inputImages.style.cursor = "grabbing";
+ inputImages.style.userSelect = "none";
+ pos = {
+ // current scroll
+ left: inputImages.scrollLeft,
+ top: inputImages.scrollTop,
+ // current mouse position
+ x: e.clientX,
+ y: e.clientY,
+ };
+ });
+ inputImages.addEventListener("mouseup", () => {
+ mouseIsDown = false;
+ inputImages.style.removeProperty("cursor");
+ inputImages.style.removeProperty("user-select");
+ });
+
+ if (typeof cb === "function") cb();
+}
+
+
+function getAddImageCardButton() {
+ const wrapper = doc.createElement("div");
+ wrapper.className = "col mw-100px";
+ const button = doc.createElement("div");
+ button.type = "button";
+ button.onclick = () => uploadFiles();
+ button.className = "btn btn-light p-3 d-flex align-items-center justify-content-center h-100 border-primary text-primary";
+ button.innerHTML = plusIcon("32", "32");
+ wrapper.appendChild(button);
+ return wrapper;
+}
+
+function deleteImageFromSessionStorage(image) {
+ const oldImages = utilites.getSessionStorageJSON(configuration.storageKeys.productForm.imageUrls);
+ const newImages = [];
+ for (const oldImage of oldImages)
+ if (oldImage.fileName !== image.fileName)
+ newImages.push(oldImage);
+ utilites.setSessionStorageJSON(configuration.storageKeys.productForm.imageUrls, newImages);
+ renderProductFormImages();
+}
+
+function generateProductFormImageColumn(image) {
+ const wrapper = doc.createElement("div");
+ wrapper.className = "col-3 product-form-image-thumbnail";
+
+ const card = doc.createElement("div");
+ card.className = "card";
+
+ const img = doc.createElement("img");
+ img.src = configuration.paths.products + image.fileName;
+ img.className = "card-img";
+ img.alt = strings.languageSpesific.picture_of_the_product;
+
+ const cardOverlay = doc.createElement("div");
+ cardOverlay.className = "card-img-overlay";
+
+ const dropdownWrapper = doc.createElement("div");
+ dropdownWrapper.className = "dropleft";
+
+ const contextMenuButton = doc.createElement("div");
+ contextMenuButton.className = "btn btn-light float-end context-menu-button";
+ contextMenuButton.setAttribute("data-bs-toggle", "dropdown");
+ contextMenuButton.setAttribute("aria-expanded", "false");
+ contextMenuButton.type = "button";
+ contextMenuButton.innerHTML = threeDotsVerticalIcon();
+
+ const dropdownMenu = doc.createElement("ul");
+ dropdownMenu.className = "dropdown-menu";
+
+ const deleteMenuItem = doc.createElement("li");
+ deleteMenuItem.innerText = `${strings.languageSpesific.delete} ${strings.languageSpesific.the_image.toLocaleLowerCase()}`;
+ deleteMenuItem.onclick = () => deleteImageFromSessionStorage(image);
+ deleteMenuItem.className = "text-danger dropdown-item";
+
+ dropdownMenu.appendChild(deleteMenuItem);
+ dropdownWrapper.appendChild(contextMenuButton);
+ dropdownWrapper.appendChild(dropdownMenu);
+ cardOverlay.appendChild(dropdownWrapper);
+ card.appendChild(img);
+ card.appendChild(cardOverlay);
+ wrapper.appendChild(card);
+ return wrapper;
+}
+
+function generateCategoryPickerItem(category, initialCategoryId = undefined) {
+ const wrapper = doc.createElement("div");
+ wrapper.className = "form-check form-check-inline";
+ const radio = doc.createElement("input");
+ radio.type = "radio";
+ radio.name = "category";
+ radio.className = "form-check-input";
+ radio.value = category.id;
+ radio.id = category.id;
+ if (initialCategoryId !== undefined && category.id === initialCategoryId) radio.checked = true;
+ const label = doc.createElement("label");
+ label.className = "form-check-label cursor-pointer";
+ label.innerHTML = category.disabled ? ("<span class='text-decoration-line-through'>" + category.name + "</span>") : category.name;
+ label.htmlFor = category.id;
+ wrapper.appendChild(radio);
+ wrapper.appendChild(label);
+ return wrapper;
+}
+
+function renderCategoriesPicker(initialCategoryId = undefined) {
+ productCategoriesPickerElement.innerHTML = "";
+ getCategories().then(res => {
+ if (res.ok) {
+ res.json().then(data => {
+ let i = 0;
+ data = utilites.resolveReferences(data);
+ data.forEach(category => {
+ i++;
+ if (i % 3 === 1) productCategoriesPickerElement.appendChild(doc.createElement("br"));
+ if (initialCategoryId !== undefined)
+ productCategoriesPickerElement.appendChild(generateCategoryPickerItem(category, initialCategoryId));
+ else
+ productCategoriesPickerElement.appendChild(generateCategoryPickerItem(category));
+ });
+ productCategoriesPickerLoadingElement.classList.add("d-none");
+ productCategoriesPickerElement.classList.remove("d-none");
+ });
+ } else {
+ utilites.handleError(res, {
+ title: strings.languageSpesific.could_not_retrieve_categories,
+ message: strings.languageSpesific.try_again_soon,
+ });
+ }
+ }).catch((error) => {
+ console.error(error);
+ toaster.errorObj(messages.networkRequestFailed);
+ });
+}
+
+function submitProductForm() {
+ const id = productModalElement.dataset.id;
+ const isEditing = id !== "" && productModalElement.dataset.isEditing === "true";
+ const payload = {
+ name: $("#input-name").value,
+ price: $("#input-price").value,
+ priceSuffix: parseInt($("#input-price-suffix").value),
+ description: $("#input-description").value,
+ count: parseInt($("#input-count").value),
+ showOnFrontpage: $("input[name='show-on-frontpage']:checked")?.checked ?? false,
+ category: {
+ id: $("input[name='category']:checked").value,
+ },
+ images: [],
+ };
+
+ if (!payload.count && payload.count !== 0) payload.count = -1;
+
+ const images = utilites.getSessionStorageJSON(configuration.storageKeys.productForm.imageUrls);
+ if (images !== undefined) {
+ if (typeof images[0] !== "object") {
+ for (let i = 0; i < images.length; i++) {
+ payload.images.push({
+ order: i,
+ fileName: images[i],
+ });
+ }
+ } else {
+ payload.images = images;
+ }
+ }
+
+ if (isEditing) payload.id = id;
+ if (!payload.name || !payload.price || !payload.category.id) {
+ toaster.error(strings.languageSpesific.invalid_form);
+ return;
+ }
+
+ if (payload.price.substring(payload.price.lastIndexOf(".")).length > 3 || payload.price.split(".").length > 2) {
+ toaster.error(strings.languageSpesific.invalid_form);
+ return;
+ }
+
+ payload.price = parseFloat(payload.price);
+
+ const action = isEditing ? updateProduct(payload) : createProduct(payload);
+ productForm.classList.add("loading");
+ submitProductFormButton.classList.add("disabled");
+ submitProductFormButtonSpinner.classList.remove("d-none");
+ action.then((res) => {
+ if (res.ok) {
+ shouldReloadProductsView = true;
+ productModal.hide();
+ sessionStorage.removeItem(configuration.storageKeys.productForm.imageUrls);
+ productForm.classList.remove("loading");
+ submitProductFormButton.classList.remove("disabled");
+ submitProductFormButtonSpinner.classList.add("d-none");
+ } else {
+ utilites.handleError(res, {
+ title: isEditing ? strings.languageSpesific.could_not_update_the_product : strings.languageSpesific.could_not_add_the_product,
+ message: "Prøv igjen snart",
+ });
+ productForm.classList.remove("loading");
+ submitProductFormButton.classList.remove("disabled");
+ submitProductFormButtonSpinner.classList.add("d-none");
+ }
+ }).catch((error) => {
+ console.error(error);
+ toaster.errorObj(messages.networkRequestFailed);
+ productForm.classList.remove("loading");
+ submitProductFormButton.classList.remove("disabled");
+ submitProductFormButtonSpinner.classList.add("d-none");
+ });
+}
+
+function disposeNewProductModal() {
+ if (shouldReloadProductsView) {
+ renderProductsView();
+ shouldReloadProductsView = false;
+ }
+ sessionStorage.removeItem(configuration.storageKeys.productForm.imageUrls);
+ productModalElement.dataset.isEditing = "false";
+ productForm.reset();
+ productForm.classList.remove("loading");
+ submitProductFormButton.removeEventListener("click", submitProductForm);
+ console.log("disposed newProductModal");
+}
+
+function openProductModal(edit = undefined, initalCategoryId = undefined) {
+ productForm.reset();
+ productModalElement.addEventListener("hidden.bs.modal", disposeNewProductModal, {once: true});
+ submitProductFormButton.addEventListener("click", submitProductForm);
+ utilites.dom.restrictInputToNumbers($("#input-price"), ["."], true);
+ utilites.dom.restrictInputToNumbers($("#input-count"), ["+", "-"], true);
+
+ if (edit !== undefined) {
+ if (!utilites.array.isEmpty(edit.images)) {
+ utilites.setSessionStorageJSON(configuration.storageKeys.productForm.imageUrls, edit.images);
+ }
+ renderCategoriesPicker(edit.category.id);
+ productModalElement.dataset.isEditing = "true";
+ productModalElement.dataset.id = edit.id;
+ $("#input-name").value = edit.name;
+ $("#input-price").value = edit.price;
+ $("#input-price-suffix").value = edit.priceSuffix;
+ $("#input-count").value = edit.count;
+ $("#show-on-frontpage").checked = edit.showOnFrontpage;
+ $("#input-description").value = edit.description;
+ productModalTitle.innerText = strings.languageSpesific.edit + " " + edit.name;
+ $("#submit-product-form .text").innerText = strings.languageSpesific.save;
+ } else {
+ sessionStorage.removeItem(configuration.storageKeys.productForm.imageUrls);
+ productModalTitle.innerText = strings.languageSpesific.new_product;
+ $("#submit-product-form .text").innerText = strings.languageSpesific.create;
+ productModalElement.dataset.isEditing = "false";
+ productModalElement.dataset.id = "";
+ if (initalCategoryId !== undefined)
+ renderCategoriesPicker(initalCategoryId);
+ else
+ renderCategoriesPicker();
+ }
+ renderProductFormImages();
+ productModal.show();
+}
+
+
+/*
+ CATEGORIES
+*/
+
+function removeCategory(id, name) {
+ if (!id || !name) return;
+ if (confirm(`${strings.languageSpesific.are_you_sure_you_want_to_delete} ${name}?`)) {
+ deleteCategory(id).then(res => {
+ if (res.ok) {
+ shouldReloadProductsView = true;
+ renderCategoriesList();
+ } else {
+ utilites.handleError(res, {
+ title: strings.languageSpesific.could_not_delete_category,
+ message: strings.languageSpesific.try_again_soon,
+ });
+ }
+ }).catch(err => {
+ console.error(err);
+ toaster.errorObj(messages.networkRequestFailed);
+ });
+ }
+}
+
+function setCategoryState(id, name, disabled) {
+ if (typeof disabled !== "boolean" || !id || !name) return;
+ const actionName = disabled ? ` ${strings.languageSpesific.hide_V.toLocaleLowerCase()} ` : ` ${strings.languageSpesific.show_V.toLocaleLowerCase()} `;
+ const confirmText = `${strings.languageSpesific.are_you_sure_you_want_to} ${actionName} ${name} ${strings.languageSpesific.in_the_store.toLocaleLowerCase()}?`;
+ if (confirm(confirmText)) {
+ const action = disabled ? disableCategory(id) : enableCategory(id);
+ action.then(res => {
+ if (res.ok) {
+ shouldReloadProductsView = true;
+ renderCategoriesList();
+ } else {
+ utilites.handleError(res, {
+ title: `${strings.languageSpesific.could_not} ${actionName} ${name}`,
+ message: strings.languageSpesific.try_again_soon,
+ });
+ }
+ }).catch(err => {
+ console.error(err);
+ toaster.errorObj(messages.networkRequestFailed);
+ });
+ }
+}
+
+function generateCategoryListItem(category) {
+ const nameEl = doc.createElement("div");
+ nameEl.className = "flex-grow-1";
+ nameEl.innerText = category.name;
+
+ const disabledCheckEl = doc.createElement("div");
+ const disabledCheckInputEl = doc.createElement("input");
+ disabledCheckEl.className = "form-check form-switch";
+
+ if (category.disabled === true) {
+ disabledCheckEl.title = `${strings.languageSpesific.show} ${category.name} ${strings.languageSpesific.in_the_store}`;
+ disabledCheckInputEl.checked = false;
+ disabledCheckInputEl.onclick = (e) => {
+ e.preventDefault();
+ disabledCheckInputEl.checked = false;
+ setCategoryState(category.id, category.name, false);
+ };
+ } else if (category.disabled === false) {
+ disabledCheckEl.title = `${strings.languageSpesific.hide} ${category.name} ${strings.languageSpesific.in_the_store}`;
+ disabledCheckInputEl.checked = true;
+ disabledCheckInputEl.onclick = (e) => {
+ e.preventDefault();
+ disabledCheckInputEl.checked = true;
+ setCategoryState(category.id, category.name, true);
+ };
+ }
+ disabledCheckInputEl.type = "checkbox";
+ disabledCheckInputEl.className = "form-check-input";
+ disabledCheckEl.appendChild(disabledCheckInputEl);
+
+ const deleteButtonEl = doc.createElement("button");
+ deleteButtonEl.className = "btn btn-link text-danger";
+ deleteButtonEl.title = "Slett \"" + category.name + "\" for godt";
+ deleteButtonEl.innerHTML = trashIcon("22", "22");
+ deleteButtonEl.onclick = () => removeCategory(category.id, category.name);
+
+ const secondParent = doc.createElement("div");
+ secondParent.className = "d-flex align-items-center";
+ secondParent.appendChild(nameEl);
+ secondParent.appendChild(disabledCheckEl);
+ secondParent.appendChild(deleteButtonEl);
+
+ const parent = doc.createElement("div");
+ parent.className = "list-group-item";
+ parent.dataset.id = category.id;
+ parent.appendChild(secondParent);
+
+ return parent;
+}
+
+function renderCategoriesList() {
+ categoryListElement.innerHTML = "";
+ getCategories().then(res => {
+ if (res.ok) {
+ res.json().then(data => {
+ data = utilites.resolveReferences(data);
+ data.forEach(category => {
+ categoryListElement.appendChild(generateCategoryListItem(category));
+ });
+ categoryListLoadingElement.classList.add("d-none");
+ categoryListElement.classList.remove("d-none");
+ });
+ } else {
+ utilites.handleError(res, {
+ title: strings.languageSpesific.could_not_retrieve_categories,
+ message: strings.languageSpesific.try_again_soon,
+ });
+ }
+ }).catch(err => {
+ console.error(err);
+ toaster.errorObj(messages.networkRequestFailed);
+ });
+}
+
+function handleKeyUpWhenCategoriesModalIsVisible(e) {
+ if (utilites.dom.elementHasFocus(newCategoryName) && e.key === "Enter") {
+ e.preventDefault();
+ if (newCategoryName.value) {
+ newCategoryForm.classList.add("loading");
+ createCategory(newCategoryName.value).then(res => {
+ if (res.ok) {
+ shouldReloadProductsView = true;
+ renderCategoriesList();
+ } else {
+ utilites.handleError(res, {
+ title: strings.languageSpesific.could_not_add_the_category,
+ message: strings.languageSpesific.try_again_soon,
+ });
+ }
+ newCategoryForm.classList.remove("loading");
+ }).catch(err => {
+ console.error(err);
+ toaster.errorObj(messages.networkRequestFailed);
+ });
+ } else {
+ toaster.error(strings.languageSpesific.name + " " + strings.languageSpesific.is_required_LC);
+ }
+ }
+}
+
+function disposeCategoriesModal() {
+ if (shouldReloadProductsView) {
+ renderProductsView();
+ shouldReloadProductsView = false;
+ }
+ newCategoryForm.reset();
+ doc.removeEventListener("keyup", handleKeyUpWhenCategoriesModalIsVisible);
+ console.log("disposed categoriesModal");
+}
+
+function openCategoriesModal() {
+ newCategoryForm.reset();
+ doc.addEventListener("keyup", handleKeyUpWhenCategoriesModalIsVisible);
+ categoryModalElement.addEventListener("hidden.bs.modal", disposeCategoriesModal, {
+ once: true,
+ });
+ renderCategoriesList();
+ categoryModal.show();
+}