Update vehicle and customer selection workflows in POS form

Refactored `SelectVehicleFormPOS` and related components to improve vehicle and customer selection, including new UI elements for license plate input, dropdown suggestions, and customer search. Introduced reusable `ExpandableContentBox` and optimized handling of vehicle objects and focus states.
This commit is contained in:
Jepp9350
2025-04-29 15:50:06 +02:00
parent b1eab6f251
commit 6c5818615b
13 changed files with 1313 additions and 71 deletions
@@ -1,5 +1,5 @@
<script setup>
import {defineProps, defineSlots, onMounted, ref, watch} from 'vue';
import {defineProps, defineSlots, onMounted, ref, watch, defineEmits} from 'vue';
import { useRouter } from 'vue-router';
const props = defineProps({
@@ -32,10 +32,20 @@ const props = defineProps({
allowCompactWhenOneTab: {
type: Boolean,
default: false
}
},
/**
* Forcefully set the active tab
* @default null
* @type String
*/
forceActiveTab: {
type: String,
default: null
},
});
const slots = defineSlots();
const emit = defineEmits(['update:activeTab']);
const visibleTabs = ref([]);
/**
* The active tab
@@ -113,6 +123,30 @@ const isTabVisible = (tab) => {
return tmp_visible;
};
const onClickTab = (tab) => {
// If the tab is not visible, do nothing
if (!isTabVisible(tab)) {
return;
}
// Set the active tab to the clicked tab
if (props.forceActiveTab === null || props.forceActiveTab === undefined) {
activeTab.value = tab;
}
// Emit the active tab
emit('update:activeTab', tab);
};
// Watch the forceActiveTab prop and set the active tab to the forceActiveTab value
watch(() => props.forceActiveTab, (newValue) => {
if (newValue !== null && newValue !== undefined) {
activeTab.value = newValue;
}
});
if (props.forceActiveTab !== null && props.forceActiveTab !== undefined) {
activeTab.value = props.forceActiveTab;
}
</script>
<template>
<div>
@@ -131,7 +165,7 @@ const isTabVisible = (tab) => {
:class="{ 'is-active': tab.slot === activeTab }"
v-if="isTabVisible(tab)"
>
<a @click="activeTab = tab.slot">
<a @click="onClickTab(tab.slot)">
<!-- Display the tab icon (if any) -->
<span v-if="tab.icon" class="icon is-small">
<i :class="tab.icon"></i>
@@ -0,0 +1,44 @@
<script setup>
import { ref, defineProps, defineEmits } from 'vue';
import { SessionUser } from "@/components/session/token/SessionUser.vue";
const props = defineProps({
expanded: {
type: Boolean,
default: false,
},
})
const emit = defineEmits(['update:expanded']);
const expandIcon = ref(null);
const toggleIsSelected = () => {
emit('update:expanded', !props.expanded);
};
</script>
<template>
<div v-if="props.expanded">
<slot name="expandedContent"></slot>
</div>
<!-- Expander -->
<div class="column is-12-desktop">
<p
class="has-text-centered"
@click="toggleIsSelected"
@mouseover="expandIcon.classList.add('has-text-link')"
@mouseleave="expandIcon.classList.remove('has-text-link')"
>
<span class="icon is-small" style="color: #c7c7c7;">
<i
class="fas fa-angle-down fa-lg"
:class="{ 'fa-rotate-180': props.expanded }"
aria-hidden="true"
ref="expandIcon"
></i>
</span>
</p>
</div>
</template>
<style scoped>
</style>
@@ -0,0 +1,442 @@
<script setup>
import { getDepartment, selectScan } from "@/components/shop/POSDepartmentProcess.vue";
import {
isLoaded,
isLoading,
list,
loadList,
metaCurrentPage,
metaItemsPerPage,
metaTotalItems,
setEndpoint,
setMetaItemsPerPage,
setPage,
search,
setFilter,
setOrder,
metaSearch,
} from "@/components/pagination/paginatedList.vue";
import {useElementVisibility} from "@vueuse/core";
import {SessionUser} from "@/components/session/token/SessionUser.vue";
import {ref, watch, onMounted, useTemplateRef} from 'vue';
import {popperBox, showPopper, showPopperWithContent, removePopperIfOpen} from "@/components/displays/PopperDefault.vue";
import { Colors } from "@/ThemeConfig.vue";
const activeTab = ref(null);
const panel_tabs = ref([
{
name: 'Seneste scanninger',
active: true
},
{
name: 'Søg efter nummerplade',
active: false
}
]);
const scansInfinityScroll = ref(null);
const highestPage = ref(0); // The highest page that has been loaded (This is used to prevent loading the same page multiple times)
const limitReached = ref(false);
/** Fetch the scans when the component is mounted */
onMounted(() => {
limitReached.value = false;
setEndpoint("/numberplatescans", false);
setFilter('department_id', parseInt(getDepartment()), false);
setOrder('created_at', 'desc', false);
setMetaItemsPerPage(10, false);
});
/** Whenever the search changes, reset the infinite scroll */
const onChanged = () => {
scansInfinityScroll.value = null;
highestPage.value = 0;
console.log('Changed', 'total items: ' + metaTotalItems.value, 'items per page: ' + metaItemsPerPage.value, 'current page: ' + metaCurrentPage.value, 'search: ' + metaSearch.value, 'limit reached: ' + limitReached.value);
};
/** Whenever the tab changes, update the items per page, and reset the search */
const changeTab = (tabIndex) => {
// Reset the infinite scroll
scansInfinityScroll.value = null;
// Set the highest page to 0
highestPage.value = 0;
search('', false);
// Set the page to 1
setPage(1);
// Load the list
loadList();
};
/** Whenever the search changes, reset the infinite scroll */
watch(metaSearch, () => {
onChanged();
});
// Watch the list, and add the items to the infinite scroll list
watch(list, (newList) => {
if (scansInfinityScroll.value === null) {
scansInfinityScroll.value = newList;
} else {
// Check if the list already contains the ids from the new list (To prevent duplicates)
const ids = scansInfinityScroll.value.map((item) => item.id);
newList = newList.filter((item) => !ids.includes(item.id));
// If the new list is empty, then return (There's no new items)
scansInfinityScroll.value = scansInfinityScroll.value.concat(newList);
}
});
const activePopperPlate = ref({
plate: '',
isLoaded: false
});
const tmp = {
"registration_number": "BA95215",
"status": "Registreret",
"status_date": "2016-04-04T14:12:14.000+02:00",
"type": "Stor personbil",
"use": "Busku00f8rsel",
"first_registration": "2016-04-04+02:00",
"vin": "YV3T2U829G1177642",
"own_weight": 15900,
"cerb_weight": 16500,
"total_weight": 24350,
"axels": 3,
"pulling_axels": 1,
"seats": 54,
"coupling": true,
"trailer_maxweight_nobrakes": 0,
"trailer_maxweight_withbrakes": 2800,
"doors": null,
"make": "VOLVO",
"model": "9700",
"variant": "9700HD (9711R 13,83)",
"model_type": "B6SC",
"model_year": 0,
"color": null,
"chassis_type": "",
"engine_cylinders": 6,
"engine_volume": 10837,
"engine_power": 345,
"fuel_type": "Diesel",
"is_hybrid": false,
"hybrid_type": "mild",
"registration_zipcode": "",
"vehicle_id": 9000000001742548,
"mot_info": {
"type": "PeriodiskSyn",
"date": "2024-02-22",
"result": "Godkendt",
"status": "Aktiv",
"status_date": "2024-02-22",
"mileage": 546000
},
"is_leasing": false,
"leasing_from": null,
"leasing_to": null
};
const decodeMotorAPIString = (string) => {
// Check if the string contains u00, if so add a backslash
if (string.includes('u00')) {
string = string.replace(/u00/g, '\\u00');
}
// unicode decode
return string.replace(/\\u[\dA-F]{4}/gi,
(match) => String.fromCharCode(parseInt(match.replace(/\\u/g, ''), 16)));
};
const generatePopperPlate = (scan, motorapi_data) => {
const data = {
make: motorapi_data.make,
model: motorapi_data.model,
variant: motorapi_data.variant,
type: motorapi_data.type,
use: decodeMotorAPIString(motorapi_data.use),
time: '<strong>'
+ new Date(scan.created_at).toLocaleTimeString(
'da-DK',
{
timeZone: 'Europe/Copenhagen',
hour: '2-digit',
minute: '2-digit',
hour12: false,
})
+ '</strong>' +
' d. ' +
'<strong>' +
new Date(scan.created_at).toLocaleDateString(
'da-DK',
{
timeZone: 'Europe/Copenhagen'
}
)
+ '</strong>'
}
return `
<div class="numberplate-popper" data-plate="${scan.plate}">
<p><strong>Mærke:</strong> ${data.make}</p>
<p><strong>Model:</strong> ${data.model}</p>
<p><strong>Variant:</strong> ${data.variant}</p>
<p><strong>Type:</strong> ${data.type}</p>
<p><strong>Use:</strong> ${data.use}</p>
<p><strong>Scannet:</strong> kl. ${data.time}</p>
</div>
`;
};
const isPlatePopperLoading = ref(false);
const shownNumberPlatePoppers = ref([]);
const removePlatePoppersIfOpen = () => {
shownNumberPlatePoppers.value.forEach((popperId) => {
removePopperIfOpen(popperId);
});
};
const showPopperPlate = (scan, element) => {
// If the plate is loading, then return
if (isPlatePopperLoading.value) {
return;
}
if (scan === null) {
return;
}
// Set the plate as loading
isPlatePopperLoading.value = true;
// Get the plate registration
SessionUser.superUser.modules.motorapi.functions.lookup(scan.plate).then((response) => {
const motorapi_data = response.data.data;
let content = generatePopperPlate(scan, motorapi_data);
const uniqueId = "plate-popper-" + scan.plate;
showPopper(popperBox(
'Nummerplade: ' + scan.plate,
content,
uniqueId
), element);
shownNumberPlatePoppers.value.push(uniqueId);
isPlatePopperLoading.value = false;
}).catch((error) => {
isPlatePopperLoading.value = false;
console.error(error);
});
}
const plate_scanners = ref([]);
const getPlateScanners = async () => {
await SessionUser.request('/department/numberplatescanners?id=' + getDepartment(),
'GET')
.then((response) => {
plate_scanners.value = response.data.data;
console.log(plate_scanners.value);
}
);
}
getPlateScanners();
const getPlateScannerName = (id) => {
for (let i = 0; i < plate_scanners.value.length; i++) {
if (plate_scanners.value[i].id === id) {
return plate_scanners.value[i].name;
}
}
return 'N/A';
};
/**
* End of list reference
*/
const endOfList = useTemplateRef('endOfList');
/**
* If the end of the list is visible, then load more items
*/
const endOfListVisibility = useElementVisibility(endOfList);
watch(endOfListVisibility, (isVisible) => {
console.log('Checking visibility');
console.log(isVisible);
if (isVisible) {
// If the current page is the highest page, then return
console.log('Current page: ' + metaCurrentPage.value);
console.log('Highest page: ' + highestPage.value);
console.log('Total items: ' + metaTotalItems.value);
console.log('Items per page: ' + metaItemsPerPage.value);
// Calculate the total pages
const totalPages = Math.ceil(metaTotalItems.value / metaItemsPerPage.value);
console.log('Total pages: ' + totalPages);
// If the current page is the highest page, then return
if (metaCurrentPage.value === totalPages) {
limitReached.value = true;
return;
} else {
limitReached.value = false;
}
// Load more items
setPage(metaCurrentPage.value + 1);
// If the current page is 1, then we don't need to load the list again
if (metaCurrentPage.value !== 1) {
loadList();
}
}
});
const isLimitReached = () => {
return limitReached.value;
};
// Wait 1s before loading the list
setTimeout(() => {
loadList();
}, 1000);
</script>
<template>
<div @mouseleave="removePlatePoppersIfOpen()">
<article class="" id="scans-panel">
<p
class="is-size-4 title"
style="font-weight: 400;"
>
{{ SessionUser.objects.orders.columns.reg_1.label }}
</p>
<p
class="is-size-6 subtitle mb-1"
>
Seneste scanninger
</p>
<p class="control mb-2">
<input
@input="onChanged(); search($event.target.value);"
class="input mt-2 has-sharp-edges has-placeholder-italic"
type="text"
placeholder="Søg efter nummerplade"
style="border-color: transparent;"
:style="{
'background-color': Colors.inputs.default.backgroundColor,
'color': Colors.inputs.default.textColor
}"
v-model="metaSearch"
/>
</p>
<div id="scans-list" class="scans-list">
<table class="table is-fullwidth is-hoverable">
<thead>
<tr>
<th>{{ SessionUser.objects.orders.columns.reg_1.label }}</th>
<th class="is-narrow">Scanner</th>
<th class="is-narrow">{{ SessionUser.objects.global.language.time.at_hour }}</th>
</tr>
</thead>
<tbody>
<template v-for="scan in scansInfinityScroll" :key="scan.id">
<tr class="plate-scan-entry" @click="selectScan(scan)"
@mouseenter="showPopperPlate(scan, $event.target)"
@mouseleave="removePlatePoppersIfOpen()">
<!--<span class="panel-icon">
<i class="fas fa-car" aria-hidden="true"></i>
</span> -->
<td style="width: 50%;">{{ scan.plate }}</td>
<td class="" style="width: 15%;"
v-if="plate_scanners.length > 0"
>
{{ getPlateScannerName(parseInt(scan.plate_scanner_id)) }}
</td>
<!-- Time, and date HH:MM DD/MM (European format - 13:00 01/01) -->
<td
class="is-narrow"
style="width: 50%;"
>
{{ SessionUser.functions.date.toLocalTimeHours(scan.created_at) }}
</td>
</tr>
</template>
<!-- If the infinity scroll is null, show a loading spinner -->
<tr v-if="scansInfinityScroll === null">
<td colspan="3">
<div class="has-text-centered">
<i class="fas fa-spinner fa-spin"></i>
</div>
</td>
</tr>
<!-- If the infinity scroll is empty, show a message -->
<tr v-if="scansInfinityScroll !== null && scansInfinityScroll.length === 0">
<td colspan="3">
<div class="has-text-centered">
Ingen flere nummerplader
</div>
</td>
</tr>
<!-- Element to detect the end of the list -->
<tr ref="endOfList" class="end-of-list">
<td colspan="3">
<div class="has-text-centered" v-if="!isLimitReached()">
<i class="fas fa-spinner fa-spin"></i>
</div>
<div class="has-text-centered" v-else>
<span class="icon">
<i class="fas fa-check"></i>
</span>
<span>{{ SessionUser.objects.global.language.nothing_left_to_show }}</span>
</div>
</td>
</tr>
</tbody>
</table>
</div>
<!-- If there's no scans, and the infinity scroll is also empty or null -->
<div class="panel-block" v-if="list.length === 0 && getDepartment() !== '' && (scansInfinityScroll === null || scansInfinityScroll.length === 0)">
<p>
Ingen nummerplader blev fundet
</p>
</div>
<!-- Reload button -->
<div class="panel-block" v-if="list.length === 0 && getDepartment() !== '' && scansInfinityScroll === null">
<button class="button is-dark is-fullwidth" @click="loadList">
<span class="icon">
<i class="fas fa-redo"></i>
</span>
<span>Prøv igen</span>
</button>
</div>
</article>
<div>
</div>
</div>
</template>
<style scoped>
.scans-list {
overflow-y: auto;
max-height: 300px;
}
.scans-list::-webkit-scrollbar {
width: 5px;
}
.scans-list::-webkit-scrollbar-thumb {
background-color: #4a4a4a;
border-radius: 5px;
}
.scans-list::-webkit-scrollbar-track {
background-color: transparent;
}
.input.has-sharp-edges {
border-radius: 0;
}
.input.has-placeholder-italic::placeholder {
font-style: italic;
}
</style>
@@ -166,13 +166,13 @@ loadCustomerAttributes();
<div v-if="customer_name">
<article class="">
<div class="panel-block">
<div class="columns is-vcentered">
<div class="column is-4">
<div class="columns is-vcentered is-multiline">
<div class="column is-12">
<span class="is-size-6 title"
>{{ customer_name }}</span>
</div>
<div class="column">
<div class="tabs is-right is-small is-float-right">
<div class="tabs is-small is-centered">
<ul>
<li v-for="(tab, index) in panel_tabs" :key="index" :class="{'is-active': tab.active}" @click="panel_tabs.forEach(tab => tab.active = false); tab.active = true">
<RequiresPermission :permission="tab.permission" v-if="tab.permission">
@@ -46,7 +46,14 @@ const isReaderConnected = (reader) => {
return reader.status === 'online';
};
const isReaderAvailable = (reader) => {
return (reader.action === null || reader.action === undefined || reader.action === "") && isReaderConnected(reader);
if (!isReaderConnected(reader)) {
return false;
}
// If the action status is available, the reader is available
if (getReaderStatus(reader) === 'Available') {
return true;
}
return (reader.action === null || reader.action === undefined || reader.action === "")
};
const getAvailableReaders = () => {
return readers.value.filter(isReaderAvailable);
@@ -15,7 +15,8 @@ import {onMounted, ref} from "vue";
import ElementTabsBox from "@/components/displays/boxes/ElementTabsBox.vue";
import {POS_STEP_1_VERSION} from "@/main.js";
import SelectVehicleFormPOS from "@/components/forms/department/pos/SelectVehicleFormPOS.vue";
import PosLastScannedLicensePlatesV2 from "@/components/displays/department/pos/PosLastScannedLicensePlatesV2.vue";
import {watch} from "vue";
// Clear cache
clearCache();
@@ -42,6 +43,23 @@ const right_tabs = ref([
}
]);
const forceActiveTab = ref('license_plates'); // Force the active tab to be license plates
const setTab = (tab) => {
console.log("Tab changed to: " + tab);
forceActiveTab.value = tab;
};
// Watch for changes in the isCustomerSelected variable
watch(() => isCustomerSelected(), (newValue) => {
// If the customer is selected, set the active tab to customer
if (newValue) {
forceActiveTab.value = 'customer';
} else {
forceActiveTab.value = 'license_plates';
}
});
</script>
<template>
@@ -121,12 +139,14 @@ const right_tabs = ref([
default-active-tab="license_plates"
class="is-boxed"
:allowCompactWhenOneTab="true"
@update:activeTab="setTab"
v-bind:force-active-tab="forceActiveTab"
>
<template #customer>
<PosSelectedCustomer />
</template>
<template #license_plates>
<PosLastScannedLicensePlates class="my-3"/>
<PosLastScannedLicensePlatesV2 class="my-3"/>
</template>
</ElementTabsBox>
</template>
@@ -1,5 +1,5 @@
<script setup>
import { defineProps, ref } from "vue";
import {defineProps, ref, watch} from "vue";
import { SessionUser } from "@/components/session/token/SessionUser.vue";
const props = defineProps({
@@ -7,6 +7,26 @@ const props = defineProps({
type: Number,
required: true,
},
displayPrice: {
type: Boolean,
default: true,
},
displayReference: {
type: Boolean,
default: true,
},
displayNotes: {
type: Boolean,
default: true,
},
useLocalOrderItems: {
type: Boolean,
default: false,
},
localOrderItems: {
type: Array,
default: () => [],
},
})
const order_item_default = {
@@ -37,6 +57,10 @@ const order_item_default = {
const orderItems = ref([]);
const loadOrderItems = async () => {
if (props.useLocalOrderItems) {
orderItems.value = props.localOrderItems;
return;
}
await SessionUser.request(
'/order/items',
'GET',
@@ -66,6 +90,12 @@ const ucFirst = (str) => {
}
loadOrderItems();
watch(() => props.orderId, (newValue, oldValue) => {
if (newValue !== oldValue) {
loadOrderItems();
}
}, { immediate: true });
</script>
<template>
@@ -74,10 +104,10 @@ loadOrderItems();
<thead>
<tr>
<th>{{ucFirst(SessionUser.objects.products.meta.labels.single)}}</th>
<th>{{ucFirst(SessionUser.objects.orders.columns.notes.label)}}</th>
<th>{{ucFirst(SessionUser.objects.orders.columns.reference.label)}}</th>
<th v-if="props.displayNotes">{{ucFirst(SessionUser.objects.orders.columns.notes.label)}}</th>
<th v-if="props.displayReference">{{ucFirst(SessionUser.objects.orders.columns.reference.label)}}</th>
<th>{{ucFirst(SessionUser.objects.global.language.quantity)}}</th>
<th>{{ucFirst(SessionUser.objects.products.columns.price.label)}}</th>
<th v-if="props.displayPrice">{{ucFirst(SessionUser.objects.products.columns.price.label)}}</th>
</tr>
</thead>
<tbody>
@@ -87,18 +117,18 @@ loadOrderItems();
<!-- If the item is included in the invoice, show it normally -->
<tr v-if="item.include_in_invoice">
<td>{{ item.product.name }}</td>
<td>{{ item.reference }}</td>
<td>{{ item.notes }}</td>
<td v-if="props.displayReference">{{ item.reference }}</td>
<td v-if="props.displayNotes">{{ item.notes }}</td>
<td>{{ item.quantity }}</td>
<td>{{ SessionUser.functions.currency.toLocal(item.price) }}</td>
<td v-if="props.displayPrice">{{ SessionUser.functions.currency.toLocal(item.price) }}</td>
</tr>
<!-- If the item is not included in the invoice, show it with a strikethrough -->
<tr v-else class="has-background-warning-light">
<td><span style="text-decoration: line-through;">{{ item.product.name }}</span> ( {{SessionUser.objects.vehicles.columns.wash_subscription.label }} )</td>
<td>{{ item.reference }}</td>
<td>{{ item.notes }}</td>
<td v-if="props.displayReference">{{ item.reference }}</td>
<td v-if="props.displayNotes">{{ item.notes }}</td>
<td>{{ item.quantity }}</td>
<td style="text-decoration: line-through;">{{ SessionUser.functions.currency.toLocal(item.price) }}</td>
<td v-if="props.displayPrice" style="text-decoration: line-through;">{{ SessionUser.functions.currency.toLocal(item.price) }}</td>
</tr>
</template>
<!-- Show the add-on items -->
@@ -106,18 +136,18 @@ loadOrderItems();
<!-- If the add-on item is included in the invoice, show it normally -->
<tr v-if="addon_item.include_in_invoice">
<td>+ {{ addon_item.product.name }}</td>
<td>{{ addon_item.reference }}</td>
<td>{{ addon_item.notes }}</td>
<td v-if="props.displayReference">{{ addon_item.reference }}</td>
<td v-if="props.displayNotes">{{ addon_item.notes }}</td>
<td>{{ addon_item.quantity }}</td>
<td>{{ SessionUser.functions.currency.toLocal(addon_item.price) }}</td>
<td v-if="props.displayPrice">{{ SessionUser.functions.currency.toLocal(addon_item.price) }}</td>
</tr>
<!-- If the add-on item is not included in the invoice, show it with a strikethrough -->
<tr v-else class="has-background-warning">
<td>+ <span style="text-decoration: line-through;">{{ addon_item.product.name }}</span> ( {{SessionUser.objects.vehicles.columns.wash_subscription.label }} )</td>
<td>{{ addon_item.reference }}</td>
<td>{{ addon_item.notes }}</td>
<td v-if="props.displayReference">{{ addon_item.reference }}</td>
<td v-if="props.displayNotes">{{ addon_item.notes }}</td>
<td>{{ addon_item.quantity }}</td>
<td style="text-decoration: line-through;">{{ SessionUser.functions.currency.toLocal(addon_item.price) }}</td>
<td v-if="props.displayPrice" style="text-decoration: line-through;">{{ SessionUser.functions.currency.toLocal(addon_item.price) }}</td>
</tr>
</template>
</template>
@@ -8,22 +8,263 @@ import { isSearching } from "@/components/search/economic/customerSearch.vue";
import { SessionUser } from "@/components/session/token/SessionUser.vue";
import LicensePlateInput from "@/components/forms/department/pos/input/LicensePlateInput.vue";
import LicensePlateReg1Input from "@/components/forms/department/pos/input/LicensePlateReg1Input.vue";
import CustomerSearchFieldPos from "@/components/forms/department/pos/input/customerSearchFieldPos.vue";
import ExpandableContentBox from "@/components/displays/boxes/ExpandableContentBox.vue";
import WhiteBox from "@/components/displays/boxes/WhiteBox.vue";
import OrderContentTable from "@/components/displays/superuser/tables/OrderContentTable.vue";
import OrderItemsTable from "@/components/displays/department/pos/order/orderItemsTable.vue";
const isRegistrationNumbersExpanded = ref(false);
const isOtherExpanded = ref(false);
const keyDownNextInput = (event, field_id) => {
if (event.key === 'Enter') {
event.preventDefault();
document.getElementById(field_id).focus();
}
};
const keyDownNextTabIndexButton = (event, tabIndex) => {
if (event.key === 'Enter') {
event.preventDefault();
// First unfocus the input field. This is to ensure the sync between the input field and the POSDepartmentProcess.vue data.
event.target.blur();
document.querySelector(`button[tabindex="${tabIndex}"]`).click();
}
};
const vehicleObject = ref(null);
// debug:
//vehicleObject.value = { "id": 324, "user_id": 1952, "customer_id": 12345679, "type": 1, "reg": "EC21233", "wash_subscription": true, "addons": { "enabled": 1, "available": 2, "list": [ { "id": 287, "vehicle_id": 324, "addon_id": 14, "amount": 1, "product": { "id": 24, "name": "Spot Free- Lastbil", "description": " ", "price": 39, "subscription_allowed": 1, "category": "4", "piktogram": "", "economic_product_id": "33", "apply_category_discount": false, "requires_note": false, "created_at": "2024-12-09 14:31:49", "updated_at": "2025-04-08 14:07:41" } } ] }, "last_order_id": 6249 }
// debug end
const setVehicleObject = (emittedVehicleObject) => {
//console.log(vehicleObject, 'Emitted vehicleObject');
// Set the vehicle object to the ref
vehicleObject.value = emittedVehicleObject;
};
const ucFirst = (str) => {
return str.charAt(0).toUpperCase() + str.slice(1);
};
const isUserCurrentlyInFocusReg1 = ref(false);
const setFocusStateReg1 = (isFocused) => {
isUserCurrentlyInFocusReg1.value = isFocused;
};
const customerHasAttribute = (attribute) => {
// Check if the user has the attribute
if (customer_attributes.value === null) {
return false;
}
if (customer_attributes.value.length === 0) {
return false;
}
// Check if the attribute is in the list
for (let i = 0; i < customer_attributes.value.length; i++) {
if (customer_attributes.value[i].attribute === attribute) {
return true;
}
}
};
</script>
<template>
<div>
<!-- License plate input fields -->
<!-- License plate 1 -->
<div class="field">
<label class="label">{{SessionUser.objects.orders.columns.reg_1.label}}</label>
<div class="control">
<LicensePlateReg1Input
:tabindex="0"
@update:vehicleObject="setVehicleObject"
@update:focus="setFocusStateReg1"
/>
</div>
</div>
<!-- License plate 2 -->
<div class="field">
<div class="is-pulled-right">
<div class="tag is-small is-clickable" @click="isRegistrationNumbersExpanded = !isRegistrationNumbersExpanded">
<span>
{{ (isRegistrationNumbersExpanded ? (SessionUser.objects.global.language.hide) : (SessionUser.objects.global.language.show)) + ' ' + SessionUser.objects.orders.columns.reg_3.label}}
</span>
</div>
</div>
<label class="label">{{ SessionUser.objects.orders.columns.reg_2.label }}</label>
<LicensePlateInput :tab-index="2"
@keydown="keyDownNextInput($event, (isRegistrationNumbersExpanded ? 'reg_3' : 'reference'))"
input-id="reg_2"
v-model:inputModel="reg_2"
:customer_number="parseInt(customer_id) || 0"
v-bind:actual-value="reg_2"
/>
</div>
<!-- License plate 3 -->
<div class="field" v-show="isRegistrationNumbersExpanded">
<label class="label">{{ SessionUser.objects.orders.columns.reg_3.label }}</label>
<LicensePlateInput :tab-index="3"
@keydown="keyDownNextInput($event, 'reference')"
input-id="reg_3"
v-model:inputModel="reg_3"
:customer_number="parseInt(customer_id) || 0"
v-bind:actual-value="reg_3"
/>
</div>
<!-- Reference field -->
<div class="field" v-if="customerHasAttribute('requiresReferenceNumber')">
<label class="label">{{SessionUser.objects.orders.columns.reference.label}}</label>
<div class="control">
<input
type="text"
class="input has-sharp-edges"
id="reference"
:tabindex="4"
v-model="reference"
@keydown="keyDownNextInput($event, 'customer_id')"
/>
</div>
</div>
<!-- Notes -->
<PosNotes :notes="notes" :is-loading="false" :isAddFormVisible="false" :isOldNotesVisible="true" class="mb-3"/>
<!-- If the vehicle is registered to a customer -->
<template v-if="vehicleObject">
<!-- Divider -->
<div class="divider">{{ SessionUser.objects.vehicles.meta.labels.single.toUpperCase() }}</div>
<!-- Vehicle information -->
<div class="card">
<div class="card-header">
<!-- Icon -->
<div class="card-header-icon">
<span class="icon is-small">
<i class="fas fa-car" aria-hidden="true"></i>
</span>
</div>
<!-- Vehicle registration number -->
<div class="card-header-title">{{ SessionUser.objects.products.functions.getProductName(parseInt(vehicleObject.type)) }}</div>
<!-- Subscription -->
<div class="card-header-icon">
<span
class="tag is-light"
:class="{
'is-success': vehicleObject.wash_subscription,
'is-info': !vehicleObject.wash_subscription
}"
>
{{vehicleObject.wash_subscription
? SessionUser.objects.global.language.have
: SessionUser.objects.global.language.have_not
}} {{ SessionUser.objects.vehicles.columns.wash_subscription.label.toLowerCase() }}</span>
</div>
</div>
<!-- Vehicle information -->
<div class="card-content">
<div class="content">
<!-- Suggest order items -->
<div class="columns">
<!-- Last time, the vehicle was washed -->
<div class="column is-6">
<div class="divider">{{ SessionUser.objects.global.language.last_wash }}</div>
<!-- Content of the last wash -->
<order-content-table v-bind:order-id="vehicleObject.last_order_id" :displayPrice="false" :displayReference="false" :display-notes="false"/>
<!-- Copy last wash content -->
<div class="control mt-3">
<button
class="button is-small is-light is-fullwidth"
>
<span class="icon is-small">
<i class="fas fa-copy" aria-hidden="true"></i>
</span>
<span>{{ SessionUser.objects.global.language.copy }} {{ SessionUser.objects.global.language.last_wash.toLowerCase() }}</span>
</button>
</div>
</div>
<!-- Wash subscription -->
<div class="column is-6">
<div class="divider is-success">{{ SessionUser.objects.vehicles.columns.wash_subscription.label }}</div>
<!-- Content of the wash subscription -->
<table class="table is-fullwidth">
<thead>
<tr>
<th>{{ucFirst(SessionUser.objects.products.meta.labels.single)}}</th>
<th>{{ucFirst(SessionUser.objects.global.language.quantity)}}</th>
</tr>
</thead>
<tbody>
<tr>
<td>{{ SessionUser.objects.products.functions.getProductName(parseInt(vehicleObject.type)) }}</td>
<td>1</td>
</tr>
<template v-for="item in vehicleObject.addons.list" :key="item.id">
<!-- If the item is a primary item, show it -->
<tr>
<!-- If the item is included in the invoice, show it normally -->
<td>+ {{ item.product.name }}</td>
<td>{{ item.amount }}</td>
</tr>
</template>
</tbody>
</table>
<!-- Copy last wash subscription content -->
<div class="control mt-3">
<button
class="button is-small is-light is-fullwidth is-success"
>
<span class="icon is-small">
<i class="fas fa-copy" aria-hidden="true"></i>
</span>
<span>{{ SessionUser.objects.global.language.copy }} {{ SessionUser.objects.vehicles.columns.wash_subscription.label.toLowerCase() }}</span>
</button>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Vehicle object -->
<!--<pre>{{vehicleObject}}</pre>-->
</template>
<!-- If the vehicle is not registered to a customer -->
<template v-else-if="!vehicleObject && reg_1.length > 0 && !isUserCurrentlyInFocusReg1">
<div class="divider">{{ SessionUser.objects.global.language.customer }}</div>
<customerSearchFieldPos />
</template>
<!-- Expandable content box for rarely used inputs -->
<ExpandableContentBox
v-bind:expanded="isOtherExpanded"
@update:expanded="isOtherExpanded = $event"
>
<template #expandedContent>
<!-- Divider -->
<div class="divider">{{ SessionUser.objects.global.language.other }}</div>
<!-- Customer search field (Located discreetly here, if the vehicle is registered to a customer) -->
<CustomerSearchFieldPos v-if="vehicleObject" />
<!-- Reference field -->
<div class="field" v-if="!customerHasAttribute('requiresReferenceNumber')">
<label class="label">{{SessionUser.objects.orders.columns.reference.label}}</label>
<div class="control">
<input
type="text"
class="input has-sharp-edges"
id="reference"
:tabindex="4"
v-model="reference"
@keydown="keyDownNextInput($event, 'customer_id')"
/>
</div>
</div>
<!-- Add notes -->
<PosNotes :notes="notes" :is-loading="false" :isAddFormVisible="SessionUser.adminUser" :isOldNotesVisible="false" :is-label-visible="true" class="mt-3" v-if="isCustomerSelected()" />
</template>
</ExpandableContentBox>
</div>
</template>
<style scoped>
.input.has-sharp-edges {
@@ -1,53 +1,325 @@
<script setup>
import { SessionUser } from "@/components/session/token/SessionUser.vue";
import { ref, watch } from "vue";
import { reg_1, department_id, getCustomerName, searchAndSelectCustomer } from "@/components/shop/POSDepartmentProcess.vue";
import { ref, watch, defineEmits } from "vue";
import { reg_1, department_id, getCustomerName, searchAndSelectCustomer, customer_id, clearCache, clearCustomerSelection } from "@/components/shop/POSDepartmentProcess.vue"
// Define the matching customer default object
const matching_customer_default = {
id: null,
number: null,
name: null,
const emit = defineEmits(['update:vehicleObject', 'update:focus']);
// Define the vehicle response object (Example)
const vehicle_response_object = {
id: 43, // Vehicle ID
user_id: 1820, // User ID
customer_id: 25266211, // Economic customer ID
customer_name: "", // Customer name (From E-conomic)
type: 5, // Vehicle primary product id
reg: "EC25573", // License plate
wash_subscription: true, // Subscription status
addons: {
enabled: 1, // How many addons are enabled
available: 2, // How many addons are available
list: [
// List of enabled addons
{
id: 289, // Vehicle addon ID (Unique, not the same as the product ID or option ID)
vehicle_id: 43, // Vehicle ID
addon_id: 64, // Product option ID (Unique, not the same as the product ID or vehicle ID)
amount: 1, // Amount of this addon
product: {
id: 24, // Product ID (Unique, not the same as the vehicle ID or option ID)
name: "Spot Free- Lastbil", // Product name
description: " ", // Product description
price: 39, // Product (default) price
subscription_allowed: 1, // True if subscription is allowed (This should be true for all products listed here.)
category: "4", // Product category ID
piktogram: "", // Product image URL, can be empty
economic_product_id: "33", // Economic product ID
apply_category_discount: false, // True if the product allows category, or E-conomic customer discount rules.
requires_note: false, // True if the product requires a note when added to a vehicle
created_at: "2024-12-09 14:31:49", // Product created date
updated_at: "2025-04-08 14:07:41" // Product updated date
}
}
]
}
}
// To prevent the searches from overriding more recent searches
const current_search_id = ref(null);
const register_new_search = () => {
let search_id = Date.now();
current_search_id.value = search_id;
return search_id;
};
const is_latest_search = (search_id) => {
return search_id === current_search_id.value;
};
// Define reactive properties
const customers_matching = ref([]);
const vehicles_matching = ref([]);
const onInputChange = (event) => {
const inputValue = event.target.value;
// Perform any necessary validation or processing on the input value
console.log("Input value changed:", inputValue);
};
// Function to search for vehicles
const searchVehicle = async ( inputValue, search_id ) => {
// Check if the search ID is the latest
if (!is_latest_search(search_id)) {
console.log("Search ID is not the latest. Ignoring this search.");
return;
}
// Set the isSearching flag to true
isSearching.value = true;
// Perform the search
SessionUser.request(
SessionUser.objects.vehicles.meta.endpoint,
'GET',
{
search: inputValue,
page: 1,
limit: 10,
}
).then(response => {
if (!is_latest_search(search_id)) {
return
}
let result = response?.data?.data;
if (result && result.length > 0) {
// If there are results, set the vehicles_matching array
vehicles_matching.value = result;
} else {
// If no results, clear the vehicles_matching array
vehicles_matching.value = [];
}
}).catch(error => {
console.error("Error:", error, search_id);
}).finally(() => {
console.log("Finish search", search_id);
if (is_latest_search(search_id)) {
// Set the isSearching flag to false
isSearching.value = false;
}
});
};
const getVehicleObjectFromId = (id) => {
// Find the vehicle object by ID
return vehicles_matching.value.find(vehicle => vehicle.id === id);
};
// Watch for changes in reg_1
watch(reg_1, (newValue) => {
// Define the search ID for this input change
const search_id = register_new_search();
console.log("reg_1 changed:", newValue);
// Perform any necessary actions when reg_1 changes
unselectCustomerOnChange();
emitVehicleObject(null);
// Check if the new value is empty, if so, clear the vehicles_matching array
if (!newValue) {
vehicles_matching.value = [];
// Set the isSearching flag to false, as no search is performed
if (is_latest_search(search_id)) {
// Set the isSearching flag to false
isSearching.value = false;
}
return;
}
// Perform the search
searchVehicle(
newValue,
search_id
);
});
const unselectCustomerOnChange = () => {
clearCustomerSelection();
};
const selectVehicle = (vehicle_id) => {
// Find the vehicle object by ID
const vehicle = getVehicleObjectFromId(vehicle_id);
if (vehicle) {
// If a vehicle is found, set the reg_1 value to the vehicle's reg
reg_1.value = vehicle.reg;
// Optionally, you can also set other properties like customer_id, etc.
customer_id.value = vehicle.customer_id;
department_id.value = vehicle.type;
getCustomerName(vehicle.customer_id);
searchAndSelectCustomer(vehicle.customer_id);
// Emit the vehicle object to the parent component
emitVehicleObject(vehicle);
}
};
const getSearchIndexByVehicleId = (vehicle_id) => {
// Find the index of the vehicle in the vehicles_matching array
return vehicles_matching.value.findIndex(vehicle => vehicle.id === vehicle_id);
};
const selectDropdownItem = (index) => {
// Select a vehicle from the dropdown
if (vehicles_matching.value[index]) {
selectVehicle(vehicles_matching.value[index].id);
} else {
emitVehicleObject(null);
}
// Go to the next input field
focusNextField();
};
const keyDownNextTabIndexButton = (event, tabIndex) => {
// Handle keydown event to navigate to the next button
if (event.key === "Enter") {
event.preventDefault();
const button = document.querySelector(`button[tabindex="${tabIndex}"]`);
if (button) {
button.click();
}
}
};
const arrowKeyHandler = (event) => {
// Handle arrow key navigation in the dropdown
if (event.key === 'ArrowDown') {
event.preventDefault();
if (selectedDropdownItem.value < vehicles_matching.value.length - 1) {
selectedDropdownItem.value++;
}
}
if (event.key === 'ArrowUp') {
event.preventDefault();
if (selectedDropdownItem.value > -1) {
selectedDropdownItem.value--;
}
}
if (event.key === 'Enter') {
event.preventDefault();
selectDropdownItem(selectedDropdownItem.value);
selectedDropdownItem.value = -1;
// Imitate the tab key press to move to the next input
focusNextField();
}
if (event.key === 'Tab') {
event.preventDefault();
selectDropdownItem(selectedDropdownItem.value);
selectedDropdownItem.value = -1;
// Set the active input to the next field
focusNextField();
}
};
const selectedDropdownItem = ref(-1);
const showSelector = ref(false);
const lostfocus = () => {
emitFocus(false);
// Delay to prevent the dropdown from closing immediately
setTimeout(() => {
showSelector.value = false;
}, 200);
// Check if the current input value is in the vehicles_matching array
const currentValue = reg_1.value;
const vehicle = vehicles_matching.value.find(vehicle => vehicle.reg === currentValue);
if (vehicle) {
// If the vehicle is found, set the reg_1 value to the vehicle's reg
reg_1.value = vehicle.reg;
customer_id.value = vehicle.customer_id;
department_id.value = vehicle.type;
getCustomerName(vehicle.customer_id);
searchAndSelectCustomer(vehicle.customer_id);
// Emit the vehicle object to the parent component
emitVehicleObject(vehicle);
} else {
// If not found, clear the vehicles_matching array
vehicles_matching.value = [];
emitVehicleObject(null);
}
};
const isSearching = ref(false);
// Watch for changes in the vehicles_matching array, to check if the current index is valid
watch(vehicles_matching, (newValue) => {
// Check if the input directly matches an item in the vehicles_matching array (Then we can automatically select it)
const currentValue = reg_1.value;
const vehicle = newValue.find(vehicle => vehicle.reg === currentValue);
if (vehicle) {
// If the vehicle is found, set the reg_1 value to the vehicle's reg
reg_1.value = vehicle.reg;
customer_id.value = vehicle.customer_id;
department_id.value = vehicle.type;
getCustomerName(vehicle.customer_id);
searchAndSelectCustomer(vehicle.customer_id);
// Emit the vehicle object to the parent component
emitVehicleObject(vehicle);
}
// Check if the selectedDropdownItem index is valid
if (selectedDropdownItem.value >= newValue.length) {
// Set the selectedDropdownItem to the last index if it exceeds the new length
selectedDropdownItem.value = newValue.length - 1;
}
});
const focusNextField = () => {
// Focus on the next input field
const nextField = document.querySelector('input[tabindex="2"]');
if (nextField) {
nextField.focus();
}
};
// Function to emit the vehicle object to the parent component
const emitVehicleObject = (vehicle) => {
// Emit the vehicle object
emit('update:vehicleObject', vehicle);
};
const emitFocus = (isFocused) => {
// Emit the focus event
emit('update:focus', isFocused);
};
</script>
<template>
<div>
<!-- License Plate Registration 1 Input -->
<div class="field">
<div class="control" :class="{'is-loading': isSearching}">
<input
type="text"
class="input has-sharp-edges"
class="input has-sharp-edges is-loading"
v-model="reg_1"
@focus="showSelector = true; emitFocus(true)"
@blur="lostfocus"
@keydown="arrowKeyHandler"
id="reg_1"
autocomplete="off"
/>
<!-- Matching Customers, if multiple -->
<template v-if="1 === 1">
<!-- Divider -->
<div class="divider">{{SessionUser.objects.global.language.customer}}</div>
<!-- Matching Customers -->
<div class="columns is-multiline">
<template v-for="matching_customer in customers_matching">
<div class="column is-4">
{{ matching_customer }}
</div>
</template>
</div>
</template>
<!-- Dropdown for license plate suggestions -->
<div
class="dropdown"
:class="{'is-active': showSelector}"
v-show="showSelector && vehicles_matching.length > 0"
>
<div class="dropdown-menu">
<div class="dropdown-content">
<a
class="dropdown-item is-clickable"
v-for="result in vehicles_matching"
:key="result.id"
@click="selectVehicle(result.id)"
:class="{
'is-active': getSearchIndexByVehicleId(result.id) === selectedDropdownItem,
'is-drop-down-selected': getSearchIndexByVehicleId(result.customerNumber) === selectedDropdownItem,
}">
{{ result.reg }} - {{ result.customer_name }} {{ (result.wash_subscription ? ' ( VA )' : '') }}
</a>
</div>
</div>
</div>
</div>
</template>
@@ -0,0 +1,45 @@
<script setup>
import { SessionUser } from "@/components/session/token/SessionUser.vue";
import { ref, watch, defineEmits } from "vue";
import { reg_1, department_id, getCustomerName, searchAndSelectCustomer, customer_id, clearCache, clearCustomerSelection } from "@/components/shop/POSDepartmentProcess.vue"
const props = defineProps({
showSuggestions: {
type: Boolean,
default: false,
},
});
const suggestions = ref([]);
const onChange = () => {
if (props.showSuggestions) {
// Show suggestions
console.log("Showing suggestions");
}
}
const onClick = (suggestion) => {
// Handle click on suggestion
console.log("Clicked suggestion:", suggestion);
}
</script>
<template>
<div>
<div class="tags">
<span
v-for="(suggestion, index) in suggestions"
:key="index"
class="tag is-link is-light"
@click="onClick(suggestion)"
>
{{ suggestion }}
</span>
</div>
</div>
</template>
<style scoped>
</style>
@@ -0,0 +1,106 @@
<script setup>
import { ref } from 'vue';
import { customer_id, isCustomerBarred, customer_name, selectCustomer, isCustomerSelected } from "@/components/shop/POSDepartmentProcess.vue";
import CustomerSearchField from "@/components/search/economic/customerSearchField.vue";
import { searchCustomerResults } from "@/components/search/economic/customerSearch.vue";
import { isSearching } from "@/components/search/economic/customerSearch.vue";
import { SessionUser } from "@/components/session/token/SessionUser.vue";
const selectedDropdownItem = ref(-1);
const showSelector = ref(false);
const arrowKeyHandler = (event) => {
if (event.key === 'ArrowDown') {
event.preventDefault();
if (selectedDropdownItem.value < searchCustomerResults.value.length - 1) {
selectedDropdownItem.value++;
}
}
if (event.key === 'ArrowUp') {
event.preventDefault();
if (selectedDropdownItem.value > -1) {
selectedDropdownItem.value--;
}
}
if (event.key === 'Enter') {
event.preventDefault();
selectCustomer(searchCustomerResults.value[selectedDropdownItem.value]);
selectedDropdownItem.value = -1;
// Set the active input to the reference field
document.getElementById('reference').focus();
}
};
const getSearchIndexByCustomerNumber = (customerNumber) => {
return searchCustomerResults.value.findIndex((result) => result.customerNumber === customerNumber);
};
// This delay is needed to prevent the dropdown from closing when clicking on a dropdown item (Making the dropdown item clickable)
const lostfocus = () => {
setTimeout(() => {
showSelector.value = false;
}, 200);
};
const keyDownNextInput = (event, field_id) => {
if (event.key === 'Enter') {
event.preventDefault();
document.getElementById(field_id).focus();
}
};
const keyDownNextTabIndexButton = (event, tabIndex) => {
if (event.key === 'Enter') {
event.preventDefault();
// First unfocus the input field. This is to ensure the sync between the input field and the POSDepartmentProcess.vue data.
event.target.blur();
document.querySelector(`button[tabindex="${tabIndex}"]`).click();
}
};
</script>
<template>
<div class="field" :class="{'is-hidden': isCustomerSelected()}">
<label class="label">{{SessionUser.objects.global.language.customer}}</label>
<div class="control" :class="{'is-loading': isSearching}">
<CustomerSearchField
:v-model="customer_id"
class="input has-sharp-edges"
type="text"
:disabled="isCustomerSelected()"
@keydown="arrowKeyHandler"
@focusout="selectedDropdownItem = -1; lostfocus()"
@focusin="showSelector = true"
:tabindex="isCustomerSelected() ? -1 : 0"
autocomplete="off"
id="pos_select_customer_input"
/>
</div>
<div class="dropdown" :class="{'is-active': showSelector && searchCustomerResults.length > 0}">
<div class="dropdown-menu">
<div class="dropdown-content">
<a class="dropdown-item customer-drop-down-select" v-for="result in searchCustomerResults" :key="result.id" @click="selectCustomer(result)" :class="{'is-active': getSearchIndexByCustomerNumber(result.customerNumber) === selectedDropdownItem, 'is-drop-down-selected': getSearchIndexByCustomerNumber(result.customerNumber) === selectedDropdownItem, 'has-text-warning': isCustomerBarred(result)}">
{{ result.name }} - {{ result.customerNumber }} {{ (isCustomerBarred(result) ? ' (Spærret)' : '') }}
</a>
</div>
</div>
</div>
<!-- Stripe payment button -->
<button class="button is-text mt-1" @click="selectCustomer({name: 'Stripe Payment', customerNumber: 999})" :tabindex="isCustomerSelected() ? 1 : -1" :class="{'is-hidden': isCustomerSelected()}" style="text-decoration-line: none;">
Vælg direkte betaling med betalingskort
</button>
</div>
<div class="field has-addons-right has-addons mt-1" :class="{'is-hidden': !isCustomerSelected()}">
<p class="control is-expanded">
<input class="input has-sharp-edges" type="text" v-model="customer_name" disabled />
</p>
<p class="control">
<button class="button is-danger" @click="selectCustomer(null)" :tabindex="isCustomerSelected() ? 1 : -1">
Clear
</button>
</p>
</div>
</template>
<style scoped>
</style>
@@ -19,9 +19,14 @@ export const ObjectsGlobal = {
split: "Opdel",
save: "Gem",
invalid: "Ugyldig",
last_wash: "Sidste vask",
have_not: "Har ikke",
have: "Har",
hide: "Skjul",
cancel: "Annuller",
select: "Vælg",
month: "Måned",
copy: "Kopier",
yes: "Ja",
no: "Nej",
email: "Email",
+15 -19
View File
@@ -441,21 +441,11 @@ export const selectScan = (scan) => {
showSelectedScanDialog(scan);
console.log(scan);
// Check which registration number is empty
if (!Object.keys(reg_1.value).length > 0 || !reg_1.value) {
//if (!Object.keys(reg_1.value).length > 0 || !reg_1.value) {
// Set the registration number 1
reg_1.value = scan.plate;
return;
}
if (!reg_2.value) {
// Set the registration number 2
reg_2.value = scan.plate;
return;
}
if (!reg_3.value) {
// Set the registration number 3
reg_3.value = scan.plate;
return;
}
//reg_1.value = scan.plate;
//return;
//}
// Set the registration number 1
reg_1.value = scan.plate;
};
@@ -489,6 +479,7 @@ export const searchAndSelectCustomer = async (customerNumber) => {
/** Show selected scan dialog */
export const showSelectedScanDialog = (scan) => {
return;
// Show a loading swal
Swal.fire({
title: 'Henter nummerplade',
@@ -688,9 +679,7 @@ export const clearCache = () => {
// Reset the query parameters
window.history.pushState({}, '', `?step=1`);
// Clear the customer data
customer_id.value = '';
customer_name.value = '';
customer_data.value = [];
clearCustomerSelection();
// Clear the order data
order_id.value = null;
order_items.value = [];
@@ -698,13 +687,20 @@ export const clearCache = () => {
reg_1.value = '';
reg_2.value = '';
reg_3.value = '';
// Clear the scans
scans.value = [];
};
export const clearCustomerSelection = () => {
// Clear the customer data
customer_id.value = '';
customer_name.value = '';
customer_data.value = [];
// Clear the notes
notes.value = [];
order_notes.value = '';
// Clear the attributes
customer_attributes.value = [];
// Clear the scans
scans.value = [];
};
/** Show delete order dialog */