/** * @ai-summary Modal to review VIN OCR results with editable cascade dropdowns * @ai-context Shows extracted VIN with OCR confidence, editable vehicle dropdowns, accept/retake options */ import React, { useState, useEffect } from 'react'; import { Dialog, DialogTitle, DialogContent, DialogActions, Button, Box, Typography, Alert, useTheme, useMediaQuery, Drawer, LinearProgress, } from '@mui/material'; import CheckCircleIcon from '@mui/icons-material/CheckCircle'; import WarningIcon from '@mui/icons-material/Warning'; import ErrorIcon from '@mui/icons-material/Error'; import CameraAltIcon from '@mui/icons-material/CameraAlt'; import { VinCaptureResult } from '../hooks/useVinOcr'; import { VinReviewSelections } from '../types/vehicles.types'; import { vehiclesApi } from '../api/vehicles.api'; interface VinOcrReviewModalProps { open: boolean; result: VinCaptureResult | null; onAccept: (selections: VinReviewSelections) => void; onRetake: () => void; onClose: () => void; } /** Get confidence level from OCR percentage */ function getConfidenceLevel(confidence: number): 'high' | 'medium' | 'low' { if (confidence >= 0.9) return 'high'; if (confidence >= 0.7) return 'medium'; return 'low'; } /** VIN OCR confidence indicator with percentage */ const ConfidenceIndicator: React.FC<{ level: 'high' | 'medium' | 'low'; percentage: number }> = ({ level, percentage, }) => { const configs = { high: { color: 'success.main', icon: CheckCircleIcon, label: 'High' }, medium: { color: 'warning.main', icon: WarningIcon, label: 'Medium' }, low: { color: 'error.light', icon: ErrorIcon, label: 'Low' }, }; const config = configs[level]; const Icon = config.icon; return ( {config.label} ({Math.round(percentage * 100)}%) ); }; /** Shared select classes matching VehicleForm styling */ const selectClasses = 'w-full px-3 py-2 border rounded-md min-h-[44px] bg-white text-gray-900 border-gray-300 focus:outline-none focus:ring-2 focus:ring-primary-500 dark:bg-scuro dark:text-avus dark:border-silverstone dark:focus:ring-abudhabi dark:focus:border-abudhabi'; /** Main modal content with cascade dropdowns */ const ReviewContent: React.FC<{ result: VinCaptureResult; onAccept: (selections: VinReviewSelections) => void; onRetake: () => void; }> = ({ result, onAccept, onRetake }) => { const { ocrResult, decodedVehicle, decodeError } = result; const vinConfidenceLevel = getConfidenceLevel(ocrResult.confidence); // Dropdown option arrays const [years, setYears] = useState([]); const [makes, setMakes] = useState([]); const [models, setModels] = useState([]); const [trims, setTrims] = useState([]); const [engines, setEngines] = useState([]); const [transmissions, setTransmissions] = useState([]); const [loadingDropdowns, setLoadingDropdowns] = useState(false); // Selected values const [selectedYear, setSelectedYear] = useState(undefined); const [selectedMake, setSelectedMake] = useState(''); const [selectedModel, setSelectedModel] = useState(''); const [selectedTrim, setSelectedTrim] = useState(''); const [selectedEngine, setSelectedEngine] = useState(''); const [selectedTransmission, setSelectedTransmission] = useState(''); // Source reference values for unmatched fields const [sourceRefs, setSourceRefs] = useState>({}); // Initialize dropdown options and pre-select decoded values useEffect(() => { const initialize = async () => { setLoadingDropdowns(true); try { const yearsData = await vehiclesApi.getYears(); setYears(yearsData); if (!decodedVehicle) return; // Store source reference values for unmatched fields setSourceRefs({ make: decodedVehicle.make.confidence === 'none' ? decodedVehicle.make.sourceValue : null, model: decodedVehicle.model.confidence === 'none' ? decodedVehicle.model.sourceValue : null, trim: decodedVehicle.trimLevel.confidence === 'none' ? decodedVehicle.trimLevel.sourceValue : null, engine: decodedVehicle.engine.confidence === 'none' ? decodedVehicle.engine.sourceValue : null, transmission: decodedVehicle.transmission.confidence === 'none' ? decodedVehicle.transmission.sourceValue : null, }); const yearValue = decodedVehicle.year.value; if (!yearValue) return; setSelectedYear(yearValue); const makesData = await vehiclesApi.getMakes(yearValue); setMakes(makesData); const makeValue = decodedVehicle.make.value; if (!makeValue) return; setSelectedMake(makeValue); const modelsData = await vehiclesApi.getModels(yearValue, makeValue); setModels(modelsData); const modelValue = decodedVehicle.model.value; if (!modelValue) return; setSelectedModel(modelValue); const trimsData = await vehiclesApi.getTrims(yearValue, makeValue, modelValue); setTrims(trimsData); const trimValue = decodedVehicle.trimLevel.value; if (!trimValue) return; setSelectedTrim(trimValue); const [enginesData, transmissionsData] = await Promise.all([ vehiclesApi.getEngines(yearValue, makeValue, modelValue, trimValue), vehiclesApi.getTransmissions(yearValue, makeValue, modelValue, trimValue), ]); setEngines(enginesData); setTransmissions(transmissionsData); if (decodedVehicle.engine.value) setSelectedEngine(decodedVehicle.engine.value); if (decodedVehicle.transmission.value) setSelectedTransmission(decodedVehicle.transmission.value); } catch (error) { console.error('Failed to initialize review modal dropdowns:', error); } finally { setLoadingDropdowns(false); } }; initialize(); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); // Cascade handlers const handleYearChange = async (year: number | undefined) => { setSelectedYear(year); setSelectedMake(''); setSelectedModel(''); setSelectedTrim(''); setSelectedEngine(''); setSelectedTransmission(''); setModels([]); setTrims([]); setEngines([]); setTransmissions([]); if (year) { setLoadingDropdowns(true); try { const makesData = await vehiclesApi.getMakes(year); setMakes(makesData); } catch { setMakes([]); } finally { setLoadingDropdowns(false); } } else { setMakes([]); } }; const handleMakeChange = async (make: string) => { setSelectedMake(make); setSelectedModel(''); setSelectedTrim(''); setSelectedEngine(''); setSelectedTransmission(''); setTrims([]); setEngines([]); setTransmissions([]); if (make && selectedYear) { setLoadingDropdowns(true); try { const modelsData = await vehiclesApi.getModels(selectedYear, make); setModels(modelsData); } catch { setModels([]); } finally { setLoadingDropdowns(false); } } else { setModels([]); } }; const handleModelChange = async (model: string) => { setSelectedModel(model); setSelectedTrim(''); setSelectedEngine(''); setSelectedTransmission(''); setEngines([]); setTransmissions([]); if (model && selectedYear && selectedMake) { setLoadingDropdowns(true); try { const trimsData = await vehiclesApi.getTrims(selectedYear, selectedMake, model); setTrims(trimsData); } catch { setTrims([]); } finally { setLoadingDropdowns(false); } } else { setTrims([]); } }; const handleTrimChange = async (trim: string) => { setSelectedTrim(trim); setSelectedEngine(''); setSelectedTransmission(''); if (trim && selectedYear && selectedMake && selectedModel) { setLoadingDropdowns(true); try { const [enginesData, transmissionsData] = await Promise.all([ vehiclesApi.getEngines(selectedYear, selectedMake, selectedModel, trim), vehiclesApi.getTransmissions(selectedYear, selectedMake, selectedModel, trim), ]); setEngines(enginesData); setTransmissions(transmissionsData); } catch { setEngines([]); setTransmissions([]); } finally { setLoadingDropdowns(false); } } else { setEngines([]); setTransmissions([]); } }; const handleAccept = () => { onAccept({ vin: ocrResult.vin, year: selectedYear, make: selectedMake || undefined, model: selectedModel || undefined, trimLevel: selectedTrim || undefined, engine: selectedEngine || undefined, transmission: selectedTransmission || undefined, }); }; /** Show source reference when field had no dropdown match */ const sourceHint = (field: string) => { const ref = sourceRefs[field]; if (!ref) return null; // Only show hint when no value is currently selected const selected: Record = { make: selectedMake, model: selectedModel, trim: selectedTrim, engine: selectedEngine, transmission: selectedTransmission, }; if (selected[field]) return null; return (

Decoded value: {ref}

); }; return ( <> {/* VIN Section - OCR confidence only */} Detected VIN {ocrResult.vin} {vinConfidenceLevel !== 'high' && ( {vinConfidenceLevel === 'low' ? 'Low confidence detection - please verify the VIN is correct' : 'Medium confidence - please verify the VIN is correct'} )} {/* Decode Error */} {decodeError && ( {decodeError} )} {/* Loading indicator */} {loadingDropdowns && } {/* Vehicle Details - Editable Cascade Dropdowns */} Vehicle Details Review and adjust the vehicle details below.
{/* Year */}
{/* Make */}
{sourceHint('make')}
{/* Model */}
{sourceHint('model')}
{/* Trim */}
{sourceHint('trim')}
{/* Engine */}
{sourceHint('engine')}
{/* Transmission */}
{sourceHint('transmission')}
{/* No decoded data - VIN only mode */} {!decodedVehicle && !decodeError && ( VIN extracted successfully. Select vehicle details above or enter them after accepting. )} {/* Action Buttons */} ); }; export const VinOcrReviewModal: React.FC = ({ open, result, onAccept, onRetake, onClose, }) => { const theme = useTheme(); const isMobile = useMediaQuery(theme.breakpoints.down('sm')); if (!result) return null; // Use bottom sheet on mobile, dialog on desktop if (isMobile) { return ( {/* Drag handle */} VIN Detected ); } // Desktop dialog return ( VIN Detected {/* Actions are in ReviewContent */} ); };