feat: improve VIN confidence reporting and editable review dropdowns (refs #125)
All checks were successful
Deploy to Staging / Build Images (pull_request) Successful in 4m37s
Deploy to Staging / Deploy to Staging (pull_request) Successful in 51s
Deploy to Staging / Verify Staging (pull_request) Successful in 9s
Deploy to Staging / Notify Staging Ready (pull_request) Successful in 8s
Deploy to Staging / Notify Staging Failure (pull_request) Has been skipped
All checks were successful
Deploy to Staging / Build Images (pull_request) Successful in 4m37s
Deploy to Staging / Deploy to Staging (pull_request) Successful in 51s
Deploy to Staging / Verify Staging (pull_request) Successful in 9s
Deploy to Staging / Notify Staging Ready (pull_request) Successful in 8s
Deploy to Staging / Notify Staging Failure (pull_request) Has been skipped
VIN OCR confidence now reflects recognition accuracy only (not match quality). Review modal replaces read-only fields with editable cascade dropdowns pre-populated from NHTSA decode, with NHTSA reference hints for unmatched fields. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -7,7 +7,7 @@ import { useForm } from 'react-hook-form';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { z } from 'zod';
|
||||
import { Button } from '../../../shared-minimal/components/Button';
|
||||
import { CreateVehicleRequest, Vehicle } from '../types/vehicles.types';
|
||||
import { CreateVehicleRequest, Vehicle, VinReviewSelections } from '../types/vehicles.types';
|
||||
import { vehiclesApi } from '../api/vehicles.api';
|
||||
import { VehicleImageUpload } from './VehicleImageUpload';
|
||||
import { useTierAccess } from '../../../core/hooks/useTierAccess';
|
||||
@@ -433,52 +433,47 @@ export const VehicleForm: React.FC<VehicleFormProps> = ({
|
||||
const watchedVin = watch('vin');
|
||||
|
||||
/**
|
||||
* Handle accepting VIN OCR result
|
||||
* Populates VIN and decoded fields into the form
|
||||
* Handle accepting VIN OCR result with user-edited selections from review modal
|
||||
* Populates VIN and selected dropdown values into the form
|
||||
*/
|
||||
const handleAcceptVinOcr = async () => {
|
||||
const result = vinOcr.acceptResult();
|
||||
if (!result) return;
|
||||
|
||||
const { ocrResult, decodedVehicle } = result;
|
||||
const handleAcceptVinOcr = async (selections: VinReviewSelections) => {
|
||||
// Clear the OCR result state
|
||||
vinOcr.acceptResult();
|
||||
|
||||
// Set the VIN immediately
|
||||
setValue('vin', ocrResult.vin);
|
||||
setValue('vin', selections.vin);
|
||||
|
||||
// If we have decoded vehicle data, populate the form similar to handleDecodeVin
|
||||
if (decodedVehicle) {
|
||||
// Populate form with user's dropdown selections
|
||||
const hasSelections = selections.year || selections.make || selections.model ||
|
||||
selections.trimLevel || selections.engine || selections.transmission;
|
||||
|
||||
if (hasSelections) {
|
||||
// Prevent cascade useEffects from clearing values
|
||||
isVinDecoding.current = true;
|
||||
setLoadingDropdowns(true);
|
||||
|
||||
try {
|
||||
// Determine final values
|
||||
const yearValue = decodedVehicle.year.value;
|
||||
const makeValue = decodedVehicle.make.value;
|
||||
const modelValue = decodedVehicle.model.value;
|
||||
const trimValue = decodedVehicle.trimLevel.value;
|
||||
|
||||
// Load dropdown options hierarchically
|
||||
if (yearValue) {
|
||||
prevYear.current = yearValue;
|
||||
const makesData = await vehiclesApi.getMakes(yearValue);
|
||||
// Load dropdown options hierarchically for the selected values
|
||||
if (selections.year) {
|
||||
prevYear.current = selections.year;
|
||||
const makesData = await vehiclesApi.getMakes(selections.year);
|
||||
setMakes(makesData);
|
||||
|
||||
if (makeValue) {
|
||||
prevMake.current = makeValue;
|
||||
const modelsData = await vehiclesApi.getModels(yearValue, makeValue);
|
||||
if (selections.make) {
|
||||
prevMake.current = selections.make;
|
||||
const modelsData = await vehiclesApi.getModels(selections.year, selections.make);
|
||||
setModels(modelsData);
|
||||
|
||||
if (modelValue) {
|
||||
prevModel.current = modelValue;
|
||||
const trimsData = await vehiclesApi.getTrims(yearValue, makeValue, modelValue);
|
||||
if (selections.model) {
|
||||
prevModel.current = selections.model;
|
||||
const trimsData = await vehiclesApi.getTrims(selections.year, selections.make, selections.model);
|
||||
setTrims(trimsData);
|
||||
|
||||
if (trimValue) {
|
||||
prevTrim.current = trimValue;
|
||||
if (selections.trimLevel) {
|
||||
prevTrim.current = selections.trimLevel;
|
||||
const [enginesData, transmissionsData] = await Promise.all([
|
||||
vehiclesApi.getEngines(yearValue, makeValue, modelValue, trimValue),
|
||||
vehiclesApi.getTransmissions(yearValue, makeValue, modelValue, trimValue),
|
||||
vehiclesApi.getEngines(selections.year, selections.make, selections.model, selections.trimLevel),
|
||||
vehiclesApi.getTransmissions(selections.year, selections.make, selections.model, selections.trimLevel),
|
||||
]);
|
||||
setEngines(enginesData);
|
||||
setTransmissions(transmissionsData);
|
||||
@@ -488,24 +483,12 @@ export const VehicleForm: React.FC<VehicleFormProps> = ({
|
||||
}
|
||||
|
||||
// Set form values after options are loaded
|
||||
if (decodedVehicle.year.value) {
|
||||
setValue('year', decodedVehicle.year.value);
|
||||
}
|
||||
if (decodedVehicle.make.value) {
|
||||
setValue('make', decodedVehicle.make.value);
|
||||
}
|
||||
if (decodedVehicle.model.value) {
|
||||
setValue('model', decodedVehicle.model.value);
|
||||
}
|
||||
if (decodedVehicle.trimLevel.value) {
|
||||
setValue('trimLevel', decodedVehicle.trimLevel.value);
|
||||
}
|
||||
if (decodedVehicle.engine.value) {
|
||||
setValue('engine', decodedVehicle.engine.value);
|
||||
}
|
||||
if (decodedVehicle.transmission.value) {
|
||||
setValue('transmission', decodedVehicle.transmission.value);
|
||||
}
|
||||
if (selections.year) setValue('year', selections.year);
|
||||
if (selections.make) setValue('make', selections.make);
|
||||
if (selections.model) setValue('model', selections.model);
|
||||
if (selections.trimLevel) setValue('trimLevel', selections.trimLevel);
|
||||
if (selections.engine) setValue('engine', selections.engine);
|
||||
if (selections.transmission) setValue('transmission', selections.transmission);
|
||||
} finally {
|
||||
setLoadingDropdowns(false);
|
||||
isVinDecoding.current = false;
|
||||
@@ -513,17 +496,6 @@ export const VehicleForm: React.FC<VehicleFormProps> = ({
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle editing manually after VIN OCR
|
||||
* Just sets the VIN and closes the modal
|
||||
*/
|
||||
const handleEditVinManually = () => {
|
||||
const result = vinOcr.acceptResult();
|
||||
if (result) {
|
||||
setValue('vin', result.ocrResult.vin);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle retaking VIN photo
|
||||
* Resets and restarts capture
|
||||
@@ -1007,7 +979,6 @@ export const VehicleForm: React.FC<VehicleFormProps> = ({
|
||||
open={!!vinOcr.result}
|
||||
result={vinOcr.result}
|
||||
onAccept={handleAcceptVinOcr}
|
||||
onEdit={handleEditVinManually}
|
||||
onRetake={handleRetakeVinPhoto}
|
||||
onClose={vinOcr.reset}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user