Add focus handling and improve registration input behavior
Added focus tracking and dropdown activation logic to enhance user input interactions in LicensePlateInput. Fixed logic to handle empty registration numbers, updated search behavior, and integrated visual cues for registration validation.
This commit is contained in:
@@ -41,7 +41,6 @@ const vehicleObject = ref(null);
|
||||
// debug end
|
||||
|
||||
const setVehicleObject = (emittedVehicleObject) => {
|
||||
//console.log(vehicleObject, 'Emitted vehicleObject');
|
||||
// Set the vehicle object to the ref
|
||||
vehicleObject.value = emittedVehicleObject;
|
||||
// Console log the reference
|
||||
@@ -146,6 +145,8 @@ const customerHasAttribute = (attribute) => {
|
||||
v-model:inputModel="reg_2"
|
||||
:customer_number="parseInt(customer_id) || 0"
|
||||
v-bind:actual-value="reg_2"
|
||||
v-bind:reg_1="reg_1"
|
||||
v-bind:customer_id="parseInt(customer_id) || 0"
|
||||
/>
|
||||
</div>
|
||||
<!-- License plate 3 -->
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup>
|
||||
import {defineEmits, defineProps, ref, watch} from 'vue';
|
||||
import {defineEmits, defineProps, ref, watch, computed} from 'vue';
|
||||
import {SessionUser} from "@/components/session/token/SessionUser.vue";
|
||||
import {getError, parseError} from "@/components/request/HandleGlobalError.vue";
|
||||
|
||||
@@ -24,6 +24,14 @@ const props = defineProps(
|
||||
customer_number: {
|
||||
type: Number,
|
||||
required: false
|
||||
},
|
||||
reg_1: {
|
||||
type: String,
|
||||
required: false
|
||||
},
|
||||
customer_id: {
|
||||
type: Number,
|
||||
required: false,
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -97,6 +105,7 @@ const setLoading = (value) => {
|
||||
};
|
||||
|
||||
const runParserFocusOut = async () => {
|
||||
justLostFocus();
|
||||
// Push the vehicle data to the parent
|
||||
emits('update:inputModel', event.target.value);
|
||||
vehicleData.value = {};
|
||||
@@ -122,8 +131,8 @@ const runParserInput = async () => {
|
||||
searchCustomerRegistrationNumbers(event.target.value);
|
||||
};
|
||||
|
||||
const searchCustomerRegistrationNumbers = (licensePlate) => {
|
||||
if (props.customer_number === 0 || props.customer_number === undefined || licensePlate.length < 1) {
|
||||
const searchCustomerRegistrationNumbers = (licensePlate, allowEmpty = false) => {
|
||||
if (props.customer_number === 0 || props.customer_number === undefined || (licensePlate.length < 1 && !allowEmpty)) {
|
||||
customerRegistrationNumbers.value = [];
|
||||
return;
|
||||
}
|
||||
@@ -133,7 +142,13 @@ const searchCustomerRegistrationNumbers = (licensePlate) => {
|
||||
return;
|
||||
}
|
||||
// Remove duplicates
|
||||
customerRegistrationNumbers.value = response.filter((v, i, a) => a.findIndex(t => (t.registration_number === v.registration_number)) === i);
|
||||
let uniqueRegistrationNumbers = response.filter((v, i, a) => a.findIndex(t => (t.registration_number === v.registration_number)) === i);
|
||||
// Remove those entries with registration_number = ""
|
||||
uniqueRegistrationNumbers = uniqueRegistrationNumbers.filter((object) => {
|
||||
return object.registration_number !== '';
|
||||
})
|
||||
|
||||
customerRegistrationNumbers.value = uniqueRegistrationNumbers
|
||||
}).catch((error) => {
|
||||
parseError(error, 'license_plate_input_' + uniqueFieldId);
|
||||
});
|
||||
@@ -167,9 +182,36 @@ const lookupVehicleData = () => {
|
||||
});
|
||||
};
|
||||
|
||||
const isUserFocused = () => {
|
||||
return document.activeElement === document.getElementById(props.inputId);
|
||||
};
|
||||
const lostFocusAt = ref(null)
|
||||
|
||||
const justLostFocus = () => {
|
||||
lostFocusAt.value = Date.now();
|
||||
}
|
||||
|
||||
const wasUserJustFocused = () => {
|
||||
if (lostFocusAt.value === null) {
|
||||
return false;
|
||||
}
|
||||
const now = Date.now();
|
||||
const timeDiff = now - lostFocusAt.value;
|
||||
lostFocusAt.value = now;
|
||||
return timeDiff < 200;
|
||||
}
|
||||
|
||||
const isDropdownOpen = () => {
|
||||
let tmp_result = false;
|
||||
// Check if the user is focused right now
|
||||
if (isUserFocused.value) { tmp_result = true; }
|
||||
// Check if the user was focused in the last 200 ms
|
||||
if (wasUserJustFocused()) { tmp_result = true; }
|
||||
return tmp_result;
|
||||
}
|
||||
|
||||
//const isUserFocused = ref((document.activeElement === document.getElementById(props.inputId)));
|
||||
|
||||
const isUserFocused = computed(() => {
|
||||
return hasFocus.value;
|
||||
})
|
||||
|
||||
const selectedDropdownItem = ref(-1);
|
||||
|
||||
@@ -195,12 +237,16 @@ const pressedKey = (event) => {
|
||||
// Select the license plate
|
||||
setInputValue(customerRegistrationNumbers.value[selectedDropdownItem.value]);
|
||||
selectedDropdownItem.value = -1;
|
||||
// Set the active input to the reference field
|
||||
// Set the active input to the reference field (If it exists)
|
||||
if (document.getElementById('reference')) {
|
||||
document.getElementById('reference').focus();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const setInputValue = (licensePlate) => {
|
||||
inputField.value.value = licensePlate.registration_number;
|
||||
currentFieldVal.value = licensePlate.registration_number;
|
||||
event.target.value = licensePlate.registration_number;
|
||||
};
|
||||
|
||||
@@ -214,23 +260,82 @@ watch(() => props.actualValue, (newValue) => {
|
||||
if (newValue === currentFieldVal.value) {
|
||||
return;
|
||||
}
|
||||
// Set the value of the input field
|
||||
inputField.value.value = newValue;
|
||||
currentFieldVal.value = newValue;
|
||||
checkLicensePlateValid(newValue);
|
||||
searchCustomerRegistrationNumbers(newValue);
|
||||
});
|
||||
|
||||
const onChangeReg1 = () => {
|
||||
// If the field is empty, search all
|
||||
console.log('onBecomeActive');
|
||||
// Check if reg_1 is set
|
||||
if (props.reg_1) {
|
||||
console.log('reg_1 is set, searching matching plates');
|
||||
searchCustomerRegistrationNumbers('', true);
|
||||
}
|
||||
}
|
||||
|
||||
watch(() => props.reg_1, (newValue) => {
|
||||
onChangeReg1();
|
||||
})
|
||||
|
||||
watch(() => props.customer_id, (newValue) => {
|
||||
onChangeReg1();
|
||||
})
|
||||
|
||||
const onClickDropdownItem = (newRegistrationNumber) => {
|
||||
// Get the inputField
|
||||
if (inputField.value) {
|
||||
currentFieldVal.value = newRegistrationNumber;
|
||||
// Update the input value
|
||||
inputField.value.value = newRegistrationNumber;
|
||||
// Get the inputField
|
||||
emits('update:inputModel', newRegistrationNumber);
|
||||
}
|
||||
}
|
||||
|
||||
const hasFocus = ref(false);
|
||||
|
||||
const isRegistrationNumberKnown = (registrationNumber) => {
|
||||
return customerRegistrationNumbers.value.some((customer) => {
|
||||
return customer.registration_number === registrationNumber;
|
||||
});
|
||||
}
|
||||
|
||||
const getRegistrationNumberColor = (registrationNumber) => {
|
||||
let tmp_result = 'has-text-grey';
|
||||
function isRegistrationNumberVerified() {
|
||||
// If the registration number exists in the results
|
||||
return isRegistrationNumberKnown(registrationNumber);
|
||||
}
|
||||
if (isRegistrationNumberVerified()) {
|
||||
tmp_result = 'has-text-success';
|
||||
}
|
||||
return tmp_result;
|
||||
}
|
||||
|
||||
const getCurrentIconColor = () => {
|
||||
let tmp_result = 'has-text-grey';
|
||||
if (isRegistrationNumberKnown(currentFieldVal.value)) {
|
||||
tmp_result = 'has-text-success';
|
||||
}
|
||||
return tmp_result;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div
|
||||
class="control has-icons-right"
|
||||
class="control has-icons-left has-icons-right"
|
||||
:class="{'is-loading': isLoading}">
|
||||
<span class="icon is-small is-left">
|
||||
<!-- Circle icon -->
|
||||
<i class="fas fa-circle" :class="getCurrentIconColor()"></i>
|
||||
</span>
|
||||
<input class="input reg-input has-sharp-edges"
|
||||
type="text"
|
||||
@focusout="runParserFocusOut"
|
||||
@focusout="runParserFocusOut; hasFocus = false"
|
||||
@focusin="hasFocus = true"
|
||||
@input="runParserInput"
|
||||
:id="props.inputId"
|
||||
:tabindex="props.tabIndex"
|
||||
@@ -265,13 +370,23 @@ watch(() => props.actualValue, (newValue) => {
|
||||
<!-- Search results -->
|
||||
<div
|
||||
class="dropdown"
|
||||
:class="{'is-active': customerRegistrationNumbers.length > 0 && !isLoading && !isEmpty() && isUserFocused()}"
|
||||
v-if="customerRegistrationNumbers.length > 0 && !isLoading && !isEmpty() && isUserFocused()"
|
||||
:class="{'is-active': customerRegistrationNumbers.length > 0 && !isLoading && !isEmpty() && isUserFocused}"
|
||||
v-if="customerRegistrationNumbers.length > 0 && !isLoading && !isEmpty() && isUserFocused"
|
||||
>
|
||||
<div class="dropdown-menu">
|
||||
<div class="dropdown-content">
|
||||
<a class="dropdown-item" v-for="registrationNumber in customerRegistrationNumbers" :key="registrationNumber.registration_number" @click="setInputValue(registrationNumber.registration_number)" :class="{'is-active': isDropdownItemSelected(registrationNumber)}">
|
||||
{{ registrationNumber.registration_number }}
|
||||
<a class="dropdown-item"
|
||||
v-for="registrationNumberObj in customerRegistrationNumbers"
|
||||
:key="registrationNumberObj.registration_number"
|
||||
:class="{'is-active': isDropdownItemSelected(registrationNumberObj)}"
|
||||
@mousedown="onClickDropdownItem(registrationNumberObj.registration_number)"
|
||||
>
|
||||
<span class="icon">
|
||||
<!-- Circle icon -->
|
||||
<i class="fas fa-circle" :class="getRegistrationNumberColor(registrationNumberObj.registration_number)"></i>
|
||||
</span>
|
||||
|
||||
{{ registrationNumberObj.registration_number }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -106,7 +106,7 @@ const searchVehicle = async ( inputValue, search_id ) => {
|
||||
}
|
||||
).then(response => {
|
||||
if (!is_latest_search(search_id)) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
let result = response?.data?.data;
|
||||
if (result && result.length > 0) {
|
||||
@@ -168,8 +168,7 @@ const getUnregisteredVehicleObjects = (inputValue, search_id) => {
|
||||
// Set the vehicles_matching array to include both registered and unregistered vehicles
|
||||
vehicles_matching.value = [...vehicles_matching.value, ...unregistered_vehicles];
|
||||
} else {
|
||||
// If no results, clear the vehicles_matching array
|
||||
vehicles_matching.value = [];
|
||||
// Do nothing
|
||||
}
|
||||
}).catch(error => {
|
||||
console.error("Error:", error, search_id);
|
||||
@@ -222,12 +221,12 @@ const selectVehicle = (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;
|
||||
getCustomerName(vehicle.customer_id);
|
||||
if (vehicle.customer_id) {
|
||||
// If the vehicle has a customer ID, search and select the customer
|
||||
// This is to prevent the unregistered vehicle from trying to select an unknown customer
|
||||
// Optionally, you can also set other properties like customer_id, etc.
|
||||
customer_id.value = vehicle.customer_id;
|
||||
searchAndSelectCustomer(vehicle.customer_id);
|
||||
}
|
||||
// Emit the vehicle object to the parent component
|
||||
|
||||
Reference in New Issue
Block a user