Add additional items handling and customizable product display in POS Mobile flow
- Introduced `PosDepartmentStepMobile2AdditionalItems.vue` for managing additional items in transactions. - Added `additionalItemSelection` view to `PosDepartmentStepMobileFlow.vue` for enhanced item selection. - Enhanced `PosDepartmentStepMobile2Products.vue` with support for displaying products as addons. - Implemented reusable addon conversion logic for managing products as additional items. - Updated `WhiteBoxCard.vue` to emit toggle events for improved interaction. - Refactored button logic in `PosDepartmentStepMobileButtonNextStep.vue` to handle custom actions and disable states. - Adjusted prop handling and event logic in multiple components for seamless addon integration.
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { defineProps } from 'vue';
|
||||
import { defineProps, defineEmits, onMounted, ref } from 'vue';
|
||||
|
||||
import WhiteBox from "@/components/displays/boxes/WhiteBox.vue";
|
||||
|
||||
const props = defineProps({
|
||||
@@ -20,7 +21,13 @@ const props = defineProps({
|
||||
default: false
|
||||
}
|
||||
});
|
||||
import { onMounted, ref } from 'vue';
|
||||
/**
|
||||
* Emits:
|
||||
* - toggle (boolean): Emitted when the card is toggled open or closed.
|
||||
*/
|
||||
const emit = defineEmits<{
|
||||
(e: 'toggle', isOpen: boolean): void;
|
||||
}>();
|
||||
|
||||
const isOpen = ref(props.defaultOpen);
|
||||
onMounted(() => {
|
||||
@@ -28,6 +35,13 @@ onMounted(() => {
|
||||
isOpen.value = props.defaultOpen;
|
||||
}
|
||||
});
|
||||
|
||||
/** Function to emit toggle event when isOpen changes */
|
||||
const toggle = () => {
|
||||
if (!props.toggleable) return;
|
||||
isOpen.value = !isOpen.value;
|
||||
emit('toggle', isOpen.value);
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -42,7 +56,7 @@ onMounted(() => {
|
||||
</div>
|
||||
</div>
|
||||
<!-- Default slot for header -->
|
||||
<div class="card-header" @click="props.toggleable ? isOpen = !isOpen : null" :class="{ 'is-clickable': props.toggleable }" v-if="$slots.header && !props.hideHeader">
|
||||
<div class="card-header" @click="toggle" :class="{ 'is-clickable': props.toggleable }" v-if="$slots.header && !props.hideHeader">
|
||||
<slot name="header"></slot>
|
||||
</div>
|
||||
<!-- Default slot for any content -->
|
||||
|
||||
@@ -13,7 +13,7 @@ import ControlFieldInputLabel from "@/components/viewport/elements/controls/fiel
|
||||
import ControlField from "@/components/viewport/elements/controls/fields/ControlField.vue";
|
||||
import PosDepartmentStepMobile2RegistrationNumbers
|
||||
from "@/components/displays/department/pos/steps/mobile/elements/PosDepartmentStepMobile2RegistrationNumbers.vue";
|
||||
import { vehicleSelection, transactionItems, notes, reference, vehicles, lastOrders, metadata } from "./objects/PosDepartmentStepMobileFlow.vue";
|
||||
import { vehicleSelection, transactionItems, notes, reference, vehicles, lastOrders, metadata, additionalItemSelection } from "./objects/PosDepartmentStepMobileFlow.vue";
|
||||
import PosDepartmentStep2MobileVehicleSelection
|
||||
from "@/components/displays/department/pos/steps/mobile/views/PosDepartmentStep2MobileVehicleSelection.vue";
|
||||
import PosDepartmentStepMobileButtonNextStep
|
||||
@@ -27,6 +27,8 @@ import PosDepartmentStepMobileButtonClearAll
|
||||
import PosDepartmentStepMobileFixedBottomControl
|
||||
from "@/components/displays/department/pos/steps/mobile/elements/PosDepartmentStepMobileFixedBottomControl.vue";
|
||||
import {PosOrderItem} from "@/components/displays/department/pos/steps/mobile/objects/PosOrderItem.vue";
|
||||
import PosDepartmentStepMobile2AdditionalItems
|
||||
from "@/components/displays/department/pos/steps/mobile/elements/PosDepartmentStepMobile2AdditionalItems.vue";
|
||||
|
||||
onMounted(() => {
|
||||
// Set the header to be transparent
|
||||
@@ -313,6 +315,9 @@ watch(() => vehicles.vehicle_1.value.reference, (newValue, oldValue) => {
|
||||
<template v-if="vehicleSelection">
|
||||
<PosDepartmentStep2MobileVehicleSelection @close="vehicleSelection = false"/>
|
||||
</template>
|
||||
<template v-else-if="additionalItemSelection">
|
||||
<PosDepartmentStepMobile2AdditionalItems/>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="is-flex is-flex-direction-column is-justify-content-space-between is-gap-2">
|
||||
<!-- Registration numbers -->
|
||||
@@ -325,6 +330,8 @@ watch(() => vehicles.vehicle_1.value.reference, (newValue, oldValue) => {
|
||||
:classes="layout.classes"
|
||||
:product="transactionItems.primaryItem.value"
|
||||
/>
|
||||
<!-- Additional items -->
|
||||
<PosDepartmentStepMobile2AdditionalItems/>
|
||||
<!-- Last order details -->
|
||||
<PosDepartmentStepMobile2LastOrder
|
||||
v-show="lastOrders.get(1)"
|
||||
|
||||
+201
@@ -0,0 +1,201 @@
|
||||
<script setup lang="ts">
|
||||
import { defineProps, ref, computed, watch } from "vue";
|
||||
import { PosOrder } from "../objects/PosOrder.vue";
|
||||
import { SessionUser } from "@/components/session/token/SessionUser.vue";
|
||||
import WhiteBoxCard from "@/components/displays/boxes/WhiteBoxCard.vue";
|
||||
import { pos } from "../objects/PosDepartmentStepMobileFlow.vue";
|
||||
import {PosProduct} from "@/components/displays/department/pos/steps/mobile/objects/PosProduct.vue";
|
||||
import PosDepartmentStepMobile2CategoryProduct
|
||||
from "@/components/displays/department/pos/steps/mobile/elements/PosDepartmentStepMobile2CategoryProduct.vue";
|
||||
import PosDepartmentStepMobile2Addons
|
||||
from "@/components/displays/department/pos/steps/mobile/elements/PosDepartmentStepMobile2Addons.vue";
|
||||
import {Addon} from "@/components/displays/department/pos/steps/mobile/objects/PosAddon.vue";
|
||||
import PosDepartmentStepMobileFixedBottomControl
|
||||
from "@/components/displays/department/pos/steps/mobile/elements/PosDepartmentStepMobileFixedBottomControl.vue";
|
||||
import PosDepartmentStepMobileButtonNextStep
|
||||
from "@/components/displays/department/pos/steps/mobile/elements/PosDepartmentStepMobileButtonNextStep.vue";
|
||||
import {
|
||||
transactionItems
|
||||
} from "@/components/displays/department/pos/steps/mobile/objects/PosDepartmentStepMobileFlow.vue";
|
||||
import PosDepartmentStep2MobileVehicleSelection
|
||||
from "@/components/displays/department/pos/steps/mobile/views/PosDepartmentStep2MobileVehicleSelection.vue";
|
||||
|
||||
const props = defineProps({
|
||||
label: {
|
||||
type: String,
|
||||
default: "Additional items",
|
||||
required: true
|
||||
},
|
||||
subtitle: {
|
||||
type: String,
|
||||
default: "Click to modify your additional items",
|
||||
required: true
|
||||
},
|
||||
defaultChecked: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
lastOrder: {
|
||||
type: Object as () => PosOrder | null,
|
||||
default: null,
|
||||
required: false
|
||||
},
|
||||
});
|
||||
|
||||
const checked = ref(props.defaultChecked);
|
||||
// Function to generate a summary from the last order
|
||||
function generateSummary(order: PosOrder): string {
|
||||
if (!order || !order.items || order.items.length === 0) {
|
||||
return "Denne ordre har ingen varer.";
|
||||
}
|
||||
const itemNames = order.items.map(item => item?.product?.name || "Ukendt vare");
|
||||
const uniqueItems = Array.from(new Set(itemNames));
|
||||
return uniqueItems.length > 1
|
||||
? `${uniqueItems.length} varer: ${uniqueItems.slice(0, 2).join(", ")}${uniqueItems.length > 2 ? " og flere" : ""}`
|
||||
: uniqueItems[0];
|
||||
}
|
||||
|
||||
// Functions to determine the display values
|
||||
const displayLabel = computed(() => {
|
||||
return props.lastOrder
|
||||
? `Vasket d. ${SessionUser.functions.date.toLocal(new Date(props?.lastOrder?.created_at))}`
|
||||
: props.label;
|
||||
});
|
||||
const displaySubtitle = computed(() => {
|
||||
return props.lastOrder
|
||||
? generateSummary(props.lastOrder)
|
||||
: props.subtitle;
|
||||
});
|
||||
// Emit event on toggle
|
||||
function onToggle(isOpen: boolean) {
|
||||
checked.value = isOpen;
|
||||
}
|
||||
|
||||
/** Additional item management logic */
|
||||
const exampleProducts = ref<PosProduct[]>([
|
||||
{
|
||||
id: 1,
|
||||
name: "Extra Towel",
|
||||
price: 5.00,
|
||||
description: "A soft extra towel",
|
||||
subscription_allowed: false,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: "Premium Soap",
|
||||
price: 3.50,
|
||||
description: "A premium quality soap",
|
||||
subscription_allowed: true,
|
||||
}
|
||||
]);
|
||||
const convertProductToAddon = (product: PosProduct, options: {quantity?: number, min?: number, max?: number} = {}): Addon => {
|
||||
console.warn("Converting product to addon:", product, options);
|
||||
return {
|
||||
id: product.id,
|
||||
name: product.name,
|
||||
price: product.price,
|
||||
product: product,
|
||||
quantity: options.quantity || 0,
|
||||
min: options.min || -1,
|
||||
max: options.max || -1,
|
||||
}
|
||||
}
|
||||
const getAvailableAdditionalItems = () => {
|
||||
const tmp = transactionItems.additionalItems.value || [];
|
||||
if (!tmp || tmp.length === 0) return [];
|
||||
return <Addon[]>tmp.map(p => convertProductToAddon(p, {
|
||||
quantity: p?.quantity || 0,
|
||||
min: -1,
|
||||
max: -1,
|
||||
}));
|
||||
}
|
||||
const availableAdditionalItems = ref<Addon[]>(getAvailableAdditionalItems());
|
||||
const onClickAddOtherProduct = () => {
|
||||
pos.views.additionalItemSelection.value = !pos.views.additionalItemSelection.value;
|
||||
}
|
||||
|
||||
// Watch for changes in available additional items, to update the pos.transactionItems.additionalItems
|
||||
watch(availableAdditionalItems, (newVal) => {
|
||||
newVal.forEach(addon => {
|
||||
if (addon.quantity && addon.quantity > 0 && addon.product) {
|
||||
addon.product.quantity = addon.quantity; // Ensure product has correct quantity
|
||||
}
|
||||
pos.transactionItems.setAdditionalItems(newVal.filter(a => a.quantity && a.quantity > 0).map(a => a.product!).filter((p): p is PosProduct => !!p) );
|
||||
});
|
||||
}, { deep: true });
|
||||
|
||||
// Watch for changes in pos.transactionItems.additionalItems to remove items with quantity 0
|
||||
watch(() => pos.transactionItems.additionalItems.value, (newVal) => {
|
||||
if (!newVal) return;
|
||||
if (newVal.length === 0) {
|
||||
availableAdditionalItems.value = [];
|
||||
return;
|
||||
}
|
||||
// Prevent recursive loop by checking if availableAdditionalItems already matches newVal
|
||||
const currentProductIds = availableAdditionalItems.value.map(a => a.id);
|
||||
const newProductIds = newVal.map(p => p.id);
|
||||
const isSame = currentProductIds.length === newProductIds.length && currentProductIds.every(id => newProductIds.includes(id));
|
||||
if (isSame) return;
|
||||
availableAdditionalItems.value = getAvailableAdditionalItems();
|
||||
}, { deep: true });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<!-- Minimal view, when not set as fullscreen view -->
|
||||
<WhiteBoxCard :toggleable="true" @toggle="onToggle" v-if="!pos.views.additionalItemSelection.value">
|
||||
<!-- Header -->
|
||||
<template #header>
|
||||
<div class="card-header-title">{{ displayLabel }}</div>
|
||||
<div class="card-header-icon">
|
||||
<!-- Toggle Switch -->
|
||||
<label class="switch">
|
||||
<input type="checkbox" v-model="checked" />
|
||||
<span class="slider round"></span>
|
||||
</label>
|
||||
</div>
|
||||
</template>
|
||||
<!-- Content -->
|
||||
<template #content>
|
||||
<!-- Suggested items -->
|
||||
<div>
|
||||
<PosDepartmentStepMobile2Addons :label="''" :addons="availableAdditionalItems" />
|
||||
</div>
|
||||
</template>
|
||||
<!-- Footer -->
|
||||
<template #footer>
|
||||
<!-- Select other product button -->
|
||||
<a class="card-footer-item" @click="onClickAddOtherProduct">Vælg andre produkter</a>
|
||||
</template>
|
||||
</WhiteBoxCard>
|
||||
<!-- Fullscreen view, when selecting other products -->
|
||||
<template v-else>
|
||||
<!-- Shortcuts / recommendations -->
|
||||
<WhiteBoxCard :toggleable="true">
|
||||
<!-- Header -->
|
||||
<template #header>
|
||||
<div class="card-header-title">{{ SessionUser.objects.global.language.recommended }}</div>
|
||||
<div class="card-header-icon">
|
||||
<span class="icon">
|
||||
<!-- Arrow right icon -->
|
||||
<i class="fas fa-arrow-right"></i>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
</WhiteBoxCard>
|
||||
<!-- Categories of products -->
|
||||
<PosDepartmentStep2MobileVehicleSelection :onAddProduct="pos.transactionItems.addAdditionalItem" :onSearchClick="() => console.warn('AdditionalItem Search Clicked')"/><!-- :asAddons="true" :addons="availableAdditionalItems" @update:addons="availableAdditionalItems = $event"/>-->
|
||||
<!--{{ availableAdditionalItems }}-->
|
||||
<!-- Products in category -->
|
||||
<!-- Back button -->
|
||||
|
||||
<!-- Buttons -->
|
||||
<PosDepartmentStepMobileFixedBottomControl>
|
||||
<!-- Complete button -->
|
||||
<PosDepartmentStepMobileButtonNextStep :customAction="onClickAddOtherProduct"/>
|
||||
</PosDepartmentStepMobileFixedBottomControl>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
<style scoped>
|
||||
</style>
|
||||
+59
-8
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { watch, defineEmits } from "vue";
|
||||
import {watch, defineEmits, defineProps, ref} from "vue";
|
||||
import { pos } from "../objects/PosDepartmentStepMobileFlow.vue";
|
||||
import PosDepartmentStepMobile2CategoryProduct
|
||||
from "@/components/displays/department/pos/steps/mobile/elements/PosDepartmentStepMobile2CategoryProduct.vue";
|
||||
@@ -7,8 +7,25 @@ import {PosCategory} from "@/components/displays/department/pos/steps/mobile/obj
|
||||
import { SessionUser } from "@/components/session/token/SessionUser.vue";
|
||||
import {PosProduct} from "@/components/displays/department/pos/steps/mobile/objects/PosProduct.vue";
|
||||
import { department_id, customer_id } from "@/components/shop/POSDepartmentProcess.vue";
|
||||
import PosDepartmentStepMobile2Addons
|
||||
from "@/components/displays/department/pos/steps/mobile/elements/PosDepartmentStepMobile2Addons.vue";
|
||||
import {Addon} from "@/components/displays/department/pos/steps/mobile/objects/PosAddon.vue";
|
||||
|
||||
const emits = defineEmits(["addProduct"]);
|
||||
const props = defineProps({
|
||||
// To be able to count the products, providing the "addonList" object, paired with the "asAddons" boolean
|
||||
// This should be the list of addons, not the complete product list
|
||||
addons: {
|
||||
type: Object as () => Addon[],
|
||||
required: false,
|
||||
},
|
||||
// Display products as addons (To be able to modify the amount of the products)
|
||||
asAddons: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
|
||||
// Function to fetch categories based on the department ID
|
||||
// This function will be called when the component is mounted
|
||||
@@ -54,17 +71,51 @@ watch(() => pos.categories.selection(), (newCategory) => {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/** As Addons **/
|
||||
const mergedAddons = ref<Addon[]>([]);
|
||||
// Function to merge the count of addons with the products
|
||||
const mergeCountAddons = (products: PosProduct[], addons: Addon[] | undefined) => {
|
||||
const tmpAddons = <Addon[]>[]; // Temporary array to hold the merged addons
|
||||
// Convert all products to addons
|
||||
for (let product of products) {
|
||||
let tmpAddon = pos.transactionItems.convertProductToAddon(product); // Convert product to addon (count = 0)
|
||||
tmpAddons.push(tmpAddon);
|
||||
}
|
||||
// Loop through the addons and update the count if the addon exists in the tmpAddons array
|
||||
if (addons) {
|
||||
for (let addon of addons) {
|
||||
let index = tmpAddons.findIndex(a => a.product.id === addon.product.id);
|
||||
if (index !== -1) {
|
||||
tmpAddons[index].quantity = addon.quantity; // Update the count
|
||||
}
|
||||
}
|
||||
}
|
||||
mergedAddons.value = tmpAddons;
|
||||
return mergedAddons.value;
|
||||
};
|
||||
// Recalculate the merged addons when the props.addons change
|
||||
watch(() => props.addons, (newAddons) => {
|
||||
mergeCountAddons(pos.productList.get(), newAddons);
|
||||
}, { immediate: true });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<template v-if="pos.categories.isSelected()">
|
||||
<template v-for="product in pos.productList.get()" :key="product.id">
|
||||
<PosDepartmentStepMobile2CategoryProduct
|
||||
@addProduct="emits('addProduct', product)"
|
||||
:price="product.price"
|
||||
:label="product.name"
|
||||
:piktogram="product.piktogram"
|
||||
/>
|
||||
<template v-if="!props.asAddons">
|
||||
<!-- Display of products, without a "basket" -->
|
||||
<template v-for="product in pos.productList.get()" :key="product.id">
|
||||
<PosDepartmentStepMobile2CategoryProduct
|
||||
@addProduct="emits('addProduct', product)"
|
||||
:price="product.price"
|
||||
:label="product.name"
|
||||
:piktogram="product.piktogram"
|
||||
/>
|
||||
</template>
|
||||
</template>
|
||||
<template v-else>
|
||||
<!-- Display of products, with a "basket" -->
|
||||
<PosDepartmentStepMobile2Addons :label="''" :addons="mergedAddons"/>
|
||||
</template>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
+38
-1
@@ -10,14 +10,26 @@ import Swal from "sweetalert2";
|
||||
const selectCustomerString = `${SessionUser.objects.global.language.select} ${SessionUser.objects.global.language.customer.toLowerCase()}`;
|
||||
|
||||
const props = defineProps({
|
||||
// If true, the button will have a white background. Default is true.
|
||||
isWhite: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
// Function that returns a Promise, to be executed before proceeding to the next step.
|
||||
onBeforeStep: {
|
||||
type: Function,
|
||||
default: () => Promise.resolve(),
|
||||
},
|
||||
// Custom action will override the default onClick behavior, if provided.
|
||||
customAction: {
|
||||
type: Function,
|
||||
default: null,
|
||||
},
|
||||
// Custom is the button disabled state. If provided, this will override the default disabled logic.
|
||||
customDisabled: {
|
||||
type: Boolean,
|
||||
default: null,
|
||||
},
|
||||
});
|
||||
|
||||
const updateReference = () => {
|
||||
@@ -114,6 +126,9 @@ const step1 = () => {
|
||||
reg_1.value = vehicles.vehicle_1.value?.reg || "";
|
||||
reg_2.value = vehicles.vehicle_2.value?.reg || "";
|
||||
reg_3.value = vehicles.vehicle_3.value?.reg || "";
|
||||
// Merge references and notes
|
||||
mergeReferences();
|
||||
mergeNotes();
|
||||
// We only need to check if the customer requires reference, if the field is empty.
|
||||
if (!isNonEmptyString(reference.value)) {
|
||||
validateReferenceRequirements().then((isValid) => {
|
||||
@@ -129,16 +144,29 @@ const step1 = () => {
|
||||
finalizeStep1();
|
||||
}
|
||||
};
|
||||
const finalizeStep1 = () => {
|
||||
const mergeReferences = () => {
|
||||
if (metadata.getReference() && metadata.getReference().length >= 1) {
|
||||
// If there is no reference, set it to the registration number of vehicle 1
|
||||
reference.value = metadata.getReference();
|
||||
updateReference();
|
||||
} else if (reference.value && reference.value.length >= 1) {
|
||||
// If there is a reference in the field, set it to the metadata
|
||||
metadata.setReference(reference.value);
|
||||
updateReference();
|
||||
}
|
||||
}
|
||||
const mergeNotes = () => {
|
||||
if (metadata.getNotes() && metadata.getNotes().length >= 1) {
|
||||
order_notes.value = metadata.getNotes();
|
||||
updateNotes();
|
||||
} else if (order_notes.value && order_notes.value.length >= 1) {
|
||||
metadata.setNotes(order_notes.value);
|
||||
updateNotes();
|
||||
}
|
||||
}
|
||||
const finalizeStep1 = () => {
|
||||
mergeReferences();
|
||||
mergeNotes();
|
||||
// Upload the attachments if there are any (And the order id has been created)
|
||||
if (attachments.getBase64().length > 0 && order_id.value > 0) {
|
||||
for (const attachment of attachments.getBase64()) {
|
||||
@@ -204,6 +232,11 @@ const step3 = () => {
|
||||
};
|
||||
|
||||
const onClick = () => {
|
||||
// Check if a custom action is provided
|
||||
if (props.customAction) {
|
||||
props.customAction();
|
||||
return;
|
||||
}
|
||||
if (isCustomerSelected() && getCustomerId() > 0) {
|
||||
props.onBeforeStep().then(() => {
|
||||
// Proceed to the next step
|
||||
@@ -257,6 +290,10 @@ const isRegistrationNumberFilled = () => {
|
||||
}
|
||||
// Is the requirements for clicking the button met?
|
||||
const isRequirementsForClickMet = () => {
|
||||
// If a custom disabled state is provided, use that.
|
||||
if (props.customDisabled !== null) {
|
||||
return !props.customDisabled;
|
||||
}
|
||||
/**
|
||||
* States, and their requirements:
|
||||
* If any popup is open, the button cannot be clicked.
|
||||
|
||||
+33
-3
@@ -15,12 +15,14 @@ import type { PosActionButton } from './PosActionButton.vue';
|
||||
const manualInput = ref(false);
|
||||
// Step 2
|
||||
const vehicleSelection = ref(false);
|
||||
const additionalItemSelection = ref(false);
|
||||
|
||||
const views = {
|
||||
// Step 1: Manual Input of registration numbers.
|
||||
manualInput,
|
||||
// Step 2: Vehicle Selection from a list.
|
||||
vehicleSelection,
|
||||
vehicleSelection, // Used to select the "Primary" product for the transaction.
|
||||
additionalItemSelection, // Used to select additional items for the transaction.
|
||||
};
|
||||
/** Sound effects */
|
||||
const soundEffects = ref<PosSoundEffect[]>([]);
|
||||
@@ -388,6 +390,10 @@ const removeAdditionalItem = (item: PosProduct) => {
|
||||
const clearAdditionalItems = () => {
|
||||
additionalItems.value = [];
|
||||
};
|
||||
// Function to set the additional items for the transaction (replaces the current list)
|
||||
const setAdditionalItems = (items: PosProduct[]) => {
|
||||
additionalItems.value = items;
|
||||
};
|
||||
// Function to get the total price of all additional items
|
||||
const getAdditionalItemsTotal = () => {
|
||||
let total = 0;
|
||||
@@ -401,6 +407,18 @@ const getAdditionalItemsTotal = () => {
|
||||
});
|
||||
return total;
|
||||
};
|
||||
// Function to convert a product to an addon:
|
||||
const convertProductToAddon = (product: PosProduct, options: {quantity?: number, min?: number, max?: number} = {}): Addon => {
|
||||
return {
|
||||
id: product.id,
|
||||
name: product.name,
|
||||
price: product.price,
|
||||
product: product,
|
||||
quantity: options.quantity || 0,
|
||||
min: options.min || -1,
|
||||
max: options.max || -1,
|
||||
}
|
||||
}
|
||||
// Function to set the primary item for the transaction
|
||||
const setPrimaryItem = (item: PosProduct | null) => {
|
||||
console.warn('Setting primary item:', item);
|
||||
@@ -494,6 +512,7 @@ const transactionItems = {
|
||||
addAdditionalItem,
|
||||
removeAdditionalItem,
|
||||
clearAdditionalItems,
|
||||
setAdditionalItems,
|
||||
// Primary item related functions
|
||||
primaryItem,
|
||||
setPrimaryItem,
|
||||
@@ -504,6 +523,8 @@ const transactionItems = {
|
||||
getTransactionTotal,
|
||||
// Update product prices if needed (e.g., if an item is already in the cart, update its price)
|
||||
updateTransactionPrices,
|
||||
// Convert a product to an addon
|
||||
convertProductToAddon,
|
||||
};
|
||||
|
||||
/** Categories */
|
||||
@@ -945,6 +966,7 @@ const resetVehicles = () => {
|
||||
const resetViews = () => {
|
||||
views.manualInput.value = false;
|
||||
views.vehicleSelection.value = false;
|
||||
views.additionalItemSelection.value = false;
|
||||
}
|
||||
// Function to reset attachments
|
||||
const resetAttachments = () => {
|
||||
@@ -1111,7 +1133,8 @@ const buildSnapshot = () => ({
|
||||
},
|
||||
views: {
|
||||
manualInput: views.manualInput.value,
|
||||
vehicleSelection: views.vehicleSelection.value
|
||||
vehicleSelection: views.vehicleSelection.value,
|
||||
additionalItemSelection: views.additionalItemSelection.value
|
||||
},
|
||||
transactionItems: {
|
||||
primaryItem: transactionItems.primaryItem.value,
|
||||
@@ -1178,6 +1201,7 @@ const retrievePos = () => {
|
||||
if (parsedData.views) {
|
||||
views.manualInput.value = !!parsedData.views.manualInput;
|
||||
views.vehicleSelection.value = !!parsedData.views.vehicleSelection;
|
||||
views.additionalItemSelection.value = !!parsedData.views.additionalItemSelection;
|
||||
}
|
||||
|
||||
// Restore transaction items
|
||||
@@ -1234,7 +1258,7 @@ if (typeof window !== 'undefined' && typeof localStorage !== 'undefined') {
|
||||
watch(() => [vehicles.vehicle_1.value, vehicles.vehicle_2.value, vehicles.vehicle_3.value, vehicles.activeVehicleIndex.value], savePos);
|
||||
|
||||
// Watch views state
|
||||
watch(() => [views.manualInput.value, views.vehicleSelection.value], savePos);
|
||||
watch(() => [views.manualInput.value, views.vehicleSelection.value, views.additionalItemSelection.value], savePos);
|
||||
|
||||
// Watch transaction items
|
||||
watch(() => [transactionItems.primaryItem.value, transactionItems.additionalItems.value], savePos);
|
||||
@@ -1305,6 +1329,10 @@ export default defineComponent({
|
||||
popups,
|
||||
reset,
|
||||
hasRetrievedPos,
|
||||
attachments,
|
||||
locations,
|
||||
sounds,
|
||||
search,
|
||||
};
|
||||
}
|
||||
});
|
||||
@@ -1420,6 +1448,7 @@ export const Meta = {
|
||||
export const UI = {
|
||||
manualInput,
|
||||
vehicleSelection,
|
||||
additionalItemSelection,
|
||||
};
|
||||
|
||||
export const Locations = {
|
||||
@@ -1587,6 +1616,7 @@ export {
|
||||
// UI State
|
||||
manualInput,
|
||||
vehicleSelection,
|
||||
additionalItemSelection,
|
||||
|
||||
// Search
|
||||
search,
|
||||
|
||||
+39
-2
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import {defineEmits} from "vue";
|
||||
import {defineEmits, defineProps} from "vue";
|
||||
import GenericButton from "@/components/viewport/page/templates/generic/graphics/GenericButton.vue";
|
||||
import { pos } from "../objects/PosDepartmentStepMobileFlow.vue";
|
||||
import GenericLabelSearch from "@/components/viewport/page/templates/generic/graphics/GenericLabelSearch.vue";
|
||||
@@ -10,17 +10,54 @@ import PosDepartmentStepMobile2Products
|
||||
from "@/components/displays/department/pos/steps/mobile/elements/PosDepartmentStepMobile2Products.vue";
|
||||
import PosDepartmentStepMobile2ProductRecommendations
|
||||
from "@/components/displays/department/pos/steps/mobile/elements/PosDepartmentStepMobile2ProductRecommendations.vue";
|
||||
import {Addon} from "@/components/displays/department/pos/steps/mobile/objects/PosAddon.vue";
|
||||
// Define the close event to emit when the component is closed
|
||||
const emit = defineEmits(["close"]);
|
||||
// Define props
|
||||
// Custom onAddProduct function to handle adding a product
|
||||
const props = defineProps({
|
||||
// Set a custom function to handle adding a product, if not provided, default will be the local onAddProduct function
|
||||
onAddProduct: {
|
||||
type: Function,
|
||||
required: false,
|
||||
default: null
|
||||
},
|
||||
// Set a custom onSearchClick function to handle search button click, if not provided, default will be the local onSearchClick function
|
||||
onSearchClick: {
|
||||
type: Function,
|
||||
required: false,
|
||||
default: null
|
||||
},
|
||||
asAddons: { // If true, products will be shown as addons, depends on the props.addons being set
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
addons: {
|
||||
type: Object as () => Addon[],
|
||||
required: false,
|
||||
},
|
||||
});
|
||||
|
||||
// Function to handle adding a product
|
||||
const onAddProduct = (product: any) => {
|
||||
// If a custom onAddProduct function is provided, use it
|
||||
if (props.onAddProduct) {
|
||||
product.quantity = product.quantity || 1; // Ensure quantity is set
|
||||
props.onAddProduct(product); // Call the custom function if provided
|
||||
return;
|
||||
}
|
||||
// Default behavior: add the product to the transaction and close the component
|
||||
pos.transactionItems.setPrimaryItem(product); // Set the primary item in the transaction
|
||||
emit("close"); // Emit the close event to notify the parent component
|
||||
};
|
||||
|
||||
// Function to handle search button click
|
||||
const onSearchClick = () => {
|
||||
if (props.onSearchClick) {
|
||||
props.onSearchClick(); // Call the custom function if provided
|
||||
return;
|
||||
}
|
||||
console.log("Search clicked");
|
||||
};
|
||||
</script>
|
||||
@@ -36,7 +73,7 @@ const onSearchClick = () => {
|
||||
<!-- Product Recommendations -->
|
||||
<!--<PosDepartmentStepMobile2ProductRecommendations/>-->
|
||||
<!-- Products -->
|
||||
<PosDepartmentStepMobile2Products @addProduct="onAddProduct"/>
|
||||
<PosDepartmentStepMobile2Products @addProduct="onAddProduct" :asAddons="props.asAddons" :addons="props.addons"/>
|
||||
<!-- Next button -->
|
||||
<GenericButton class="p-5" @click="emit('close')">
|
||||
Next
|
||||
|
||||
@@ -27,6 +27,7 @@ export const ObjectsGlobal = {
|
||||
showing_of_separator: "af",
|
||||
send_as_is: "Send som den er",
|
||||
ignore_customer_arrangements: "Ignorer kundeaftaler",
|
||||
recommended: "Anbefalet",
|
||||
unlink: "Fjern tilknytning",
|
||||
link: "Tilknyt",
|
||||
include_all_items_on_invoice: "Inkluder alle varer på faktura",
|
||||
|
||||
Reference in New Issue
Block a user