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:
Jepp9350
2025-05-08 10:51:39 +02:00
parent 0d46f5b7a3
commit 23e718ca48
3 changed files with 137 additions and 22 deletions
@@ -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