From 800253699cb7fad85ce12a7594d98edcad76398b Mon Sep 17 00:00:00 2001 From: Jepp9350 <2jepp9350@gmail.com> Date: Tue, 25 Feb 2025 17:57:08 +0100 Subject: [PATCH] Add related items support and improve POS UI/UX Introduced support for related items in orders by enabling nested relationships through the `related_item_id` parameter. Enhanced the POS interface with better UI elements, such as reusable components (`ProductBox`, `WhiteBox`) and dynamic labels for buttons. Debugging tools and query parameter support for `order_id`, `step`, and `customer_id` were also added to streamline the process flow. --- src/assets/main.css | 10 ++ src/components/displays/PopperDefault.vue | 17 ++ src/components/displays/boxes/ProductBox.vue | 136 ++++++++++++++ .../department/pos/PosDepartmentMVP.vue | 31 +++- .../department/pos/PosOrderItemsCurrent.vue | 167 ++++++++++++++++-- .../department/pos/PosSelectedCustomer.vue | 9 +- .../pos/steps/PosDepartmentStep2.vue | 21 ++- .../pos/steps/PosDepartmentStep3.vue | 43 +++-- .../department/pos/SelectProductsFormPOS.vue | 128 +++++++++++--- .../forms/department/pos/buttons/NextStep.vue | 6 +- src/components/menus/MenuDefault.vue | 2 +- src/components/shop/OrdersItems.vue | 5 +- src/components/shop/POSDepartmentProcess.vue | 13 ++ 13 files changed, 529 insertions(+), 59 deletions(-) create mode 100644 src/components/displays/boxes/ProductBox.vue diff --git a/src/assets/main.css b/src/assets/main.css index f835f25b..e37b8572 100644 --- a/src/assets/main.css +++ b/src/assets/main.css @@ -3,4 +3,14 @@ } textarea.has-sharp-edges { border-radius: 0; +} + +.pos-addon-popper { + padding: 0.5rem; + background-color: #fbfcfe; + opacity: 0.9; + border: 3px solid #68696b; + min-width: 250px; + min-height: 100px; + text-align: center; } \ No newline at end of file diff --git a/src/components/displays/PopperDefault.vue b/src/components/displays/PopperDefault.vue index 8885b686..3209b097 100644 --- a/src/components/displays/PopperDefault.vue +++ b/src/components/displays/PopperDefault.vue @@ -66,6 +66,23 @@ export const popperBox = (title, content, data_key = null) => { `; }; +export const popperBoxProductAddonHtml = (id, name, price, description, classes = '') => { + + let html = `
`; + html += `

${name}  /  ${price} Kr.

`; + html += ``; + // If the description is set (and isn't empty), show it + if (description && description !== '' && description !== ' ') { + html += `

${description}

`; + } + // Otherwise, show a placeholder + else { + html += `

No description available

`; + } + html += `
`; + return html; +}; + export const showPopperWithContent = (title, content) => { showPopper(popperBox(title, content)); }; diff --git a/src/components/displays/boxes/ProductBox.vue b/src/components/displays/boxes/ProductBox.vue new file mode 100644 index 00000000..2126e612 --- /dev/null +++ b/src/components/displays/boxes/ProductBox.vue @@ -0,0 +1,136 @@ + + + + + \ No newline at end of file diff --git a/src/components/displays/department/pos/PosDepartmentMVP.vue b/src/components/displays/department/pos/PosDepartmentMVP.vue index 3596da29..63c6038d 100644 --- a/src/components/displays/department/pos/PosDepartmentMVP.vue +++ b/src/components/displays/department/pos/PosDepartmentMVP.vue @@ -1,12 +1,41 @@ diff --git a/src/components/displays/department/pos/PosOrderItemsCurrent.vue b/src/components/displays/department/pos/PosOrderItemsCurrent.vue index 226c63e0..96c072be 100644 --- a/src/components/displays/department/pos/PosOrderItemsCurrent.vue +++ b/src/components/displays/department/pos/PosOrderItemsCurrent.vue @@ -8,6 +8,8 @@ import {ref, watch, defineProps, defineSlots, useSlots, renderSlot } from 'vue'; import { createPopper } from '@popperjs/core'; import { showPopper, popper, showPopperWithContent, removePopperIfOpen, popperBox} from "@/components/displays/PopperDefault.vue"; import { SessionUser } from "@/components/session/token/SessionUser.vue"; +import ActionSettingsWheelButton from "@/components/displays/buttons/ActionSettingsWheelButton.vue"; +import ActionSettingsWheelItem from "@/components/displays/buttons/ActionSettingsWheelItem.vue"; // Tabs @@ -47,13 +49,28 @@ const hideOrderItemWhileRemoving = (id) => { document.getElementById('order-item-' + id).style.display = 'none'; }; +const getItemRelatedItems = (orderItems, orderItemId) => { + // Return all items with the item.related_item_id equal to the orderItemId + return orderItems.filter((orderItem) => orderItem.related_item_id === orderItemId); +}; + +const isLastRelatedItem = (orderItems, orderItem) => { + // Get all related items + const relatedItems = getItemRelatedItems(orderItems, orderItem.related_item_id); + // If there are no related items, return true + if (relatedItems.length === 0) { + return true; + } + // If the last related item is the current item, return true + return relatedItems[relatedItems.length - 1].id === orderItem.id; +}; \ No newline at end of file diff --git a/src/components/displays/department/pos/PosSelectedCustomer.vue b/src/components/displays/department/pos/PosSelectedCustomer.vue index f72016d6..4d837d06 100644 --- a/src/components/displays/department/pos/PosSelectedCustomer.vue +++ b/src/components/displays/department/pos/PosSelectedCustomer.vue @@ -145,11 +145,12 @@ loadCustomerAttributes();
-
- {{ customer_name }} +
+ {{ customer_name }}
-
-
+
+
  • diff --git a/src/components/displays/department/pos/steps/PosDepartmentStep2.vue b/src/components/displays/department/pos/steps/PosDepartmentStep2.vue index 1adf610d..bf70a1b8 100644 --- a/src/components/displays/department/pos/steps/PosDepartmentStep2.vue +++ b/src/components/displays/department/pos/steps/PosDepartmentStep2.vue @@ -10,6 +10,7 @@ import PosOrderItemsCurrent from "@/components/displays/department/pos/PosOrderI import { order_items, order_id, loadOrderItems, setProductsCategory, notes } from "@/components/shop/POSDepartmentProcess.vue"; import Cancel from "@/components/forms/department/pos/buttons/Cancel.vue"; import PosNotes from "@/components/displays/department/pos/PosNotes.vue"; +import WhiteBox from "@/components/displays/boxes/WhiteBox.vue"; // Set the category to products setProductsCategory(null); @@ -19,18 +20,24 @@ setProductsCategory(null);
    -
    -

    Choose products

    +
    - -
    - - + +
    + + + +
    - +
    diff --git a/src/components/displays/department/pos/steps/PosDepartmentStep3.vue b/src/components/displays/department/pos/steps/PosDepartmentStep3.vue index 831f5339..6ed3ed2d 100644 --- a/src/components/displays/department/pos/steps/PosDepartmentStep3.vue +++ b/src/components/displays/department/pos/steps/PosDepartmentStep3.vue @@ -5,10 +5,12 @@ import PosLastScannedLicensePlates from "@/components/displays/department/pos/Po import NextStep from "@/components/forms/department/pos/buttons/NextStep.vue"; import SelectProductsFormPOS from "@/components/forms/department/pos/SelectProductsFormPOS.vue"; import NextStepError from "@/components/forms/department/pos/error/NextStepError.vue"; -import { order_items, order_id, loadOrderItems } from "@/components/shop/POSDepartmentProcess.vue"; +import { order_items, order_id, loadOrderItems, isCustomerSelected } from "@/components/shop/POSDepartmentProcess.vue"; import PosOrderItemsCurrent from "@/components/displays/department/pos/PosOrderItemsCurrent.vue"; import PosSelectedCustomer from "@/components/displays/department/pos/PosSelectedCustomer.vue"; import Cancel from "@/components/forms/department/pos/buttons/Cancel.vue"; +import WhiteBox from "@/components/displays/boxes/WhiteBox.vue"; +import ButtonsBox from "@/components/displays/boxes/ButtonsBox.vue"; @@ -16,19 +18,36 @@ import Cancel from "@/components/forms/department/pos/buttons/Cancel.vue";
    -
    -

    Choose add-ons

    - +
    + + + + + +
    + + +
    +
    -
    - - - -
    - - -
    +
    + + +
    diff --git a/src/components/forms/department/pos/SelectProductsFormPOS.vue b/src/components/forms/department/pos/SelectProductsFormPOS.vue index 48019ed2..f7fd4fd9 100644 --- a/src/components/forms/department/pos/SelectProductsFormPOS.vue +++ b/src/components/forms/department/pos/SelectProductsFormPOS.vue @@ -30,6 +30,8 @@ import {SessionUser} from "@/components/session/token/SessionUser.vue"; import { useRoute } from 'vue-router'; import Swal from "sweetalert2"; import ConfigurationCategory from "@/components/displays/superuser/configuration/ConfigurationCategory.vue"; +import WhiteBox from "@/components/displays/boxes/WhiteBox.vue"; +import ProductBox from "@/components/displays/boxes/ProductBox.vue"; // Define the products const products = ref([]); @@ -196,19 +198,40 @@ const setProductAddon = (productId, addonId, status) => { } }; -const addAddonsToOrderMiddleware = async (product_id, quantity = 1) => { +// Toggle product addon +const toggleProductAddon = (productId, addonId) => { + console.log(productId, addonId); + setProductAddon(productId, addonId, !isProductAddonTrue(productId, addonId)); +}; + +const addAddonsToOrderMiddleware = async (product_id, quantity = 1, related_item_id = null) => { // If the product addons are set, add them to the order (If the product is added) var product_addons = productAddons.value.filter((productAddon) => productAddon.product_id === product_id && productAddon.status === true); for (let i = 0; i < product_addons.length; i++) { // Get the products addon, and show the fake create order item - showFakeCreateOrderItem(product_addons[i].addon_id, quantity, 0); + showFakeCreateOrderItem(product_addons[i].addon_id, quantity, 0, related_item_id); } // Add the addons to the order for (let i = 0; i < product_addons.length; i++) { - await createOrderItem(getOrderId(), product_addons[i].addon_id, quantity); + await createOrderItem(getOrderId(), product_addons[i].addon_id, quantity, related_item_id); } }; +const addProductWithAddonsToOrder = async (product_id) => { + console.log(product_id); + let quantity = 1; + // Show the fake create order item + showFakeCreateOrderItem(product_id, quantity, 0); + // Create the order item + await createOrderItem(getOrderId(), product_id, quantity).then(async (result) => { + let order_item_id = result.data.data.id; + // Add the addons to the order + await addAddonsToOrderMiddleware(product_id, quantity, order_item_id).then(() => { + loadOrderItems(); + }); + }); +}; + const recommended_product_ids = ref([]); const recommended_last_orders = ref([]); @@ -246,32 +269,66 @@ const showRecommendedProducts = () => { const hideRecommendedProducts = () => { isShowingRecommended.value = false; }; + +const selectedProduct = ref(null); + +const isProductSelected = (productId) => { + return selectedProduct.valueOf() === parseInt(productId); +}; + +const selectProduct = (productId) => { + if (selectedProduct.valueOf() === parseInt(productId)) { + selectedProduct.value = null; + } else { + selectedProduct.value = parseInt(productId); + } +}; + +const getAddonPrice = (productId, addonId) => { + return products.value.find((product) => product.id === productId).addons.find((addon) => addon.option_id === addonId).price; +};