Bug Fixes

This commit is contained in:
Eric Gullickson
2025-11-09 10:37:27 -06:00
parent 408a0736c0
commit b1755d415c
2 changed files with 145 additions and 34 deletions

View File

@@ -2,7 +2,7 @@
* @ai-summary Vehicle form component for create/edit with dropdown cascades
*/
import React, { useState, useEffect } from 'react';
import React, { useState, useEffect, useRef } from 'react';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
@@ -67,15 +67,6 @@ export const VehicleForm: React.FC<VehicleFormProps> = ({
initialData,
loading,
}) => {
const formatVehicleLabel = (value?: string): string => {
if (!value) return '';
return value
.split(' ')
.filter(Boolean)
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
.join(' ');
};
const [years, setYears] = useState<number[]>([]);
const [makes, setMakes] = useState<DropdownOption[]>([]);
const [models, setModels] = useState<DropdownOption[]>([]);
@@ -89,6 +80,9 @@ export const VehicleForm: React.FC<VehicleFormProps> = ({
const [selectedTrim, setSelectedTrim] = useState<DropdownOption | undefined>();
const [decodingVIN, setDecodingVIN] = useState(false);
const [decodeSuccess, setDecodeSuccess] = useState(false);
const hasInitialized = useRef(false);
const isInitializing = useRef(false);
const [dropdownsReady, setDropdownsReady] = useState(false);
const {
register,
@@ -96,6 +90,7 @@ export const VehicleForm: React.FC<VehicleFormProps> = ({
formState: { errors },
watch,
setValue,
reset,
} = useForm<CreateVehicleRequest>({
resolver: zodResolver(vehicleSchema),
defaultValues: initialData,
@@ -151,8 +146,113 @@ export const VehicleForm: React.FC<VehicleFormProps> = ({
loadYears();
}, []);
// Initialize dropdowns when editing existing vehicle
useEffect(() => {
const initializeEditMode = async () => {
// Only run once and only if we have initialData
if (hasInitialized.current || !initialData || !initialData.year) return;
hasInitialized.current = true;
isInitializing.current = true;
try {
setLoadingDropdowns(true);
// Set year and load makes
setSelectedYear(initialData.year);
const makesData = await vehiclesApi.getMakes(initialData.year);
setMakes(makesData);
if (initialData.make) {
const makeOption = makesData.find(m => m.name === initialData.make);
if (makeOption) {
setSelectedMake(makeOption);
// Load models
const modelsData = await vehiclesApi.getModels(initialData.year, makeOption.id);
setModels(modelsData);
if (initialData.model) {
const modelOption = modelsData.find(m => m.name === initialData.model);
if (modelOption) {
setSelectedModel(modelOption);
// Load trims and transmissions in parallel
const [trimsData, transmissionsData] = await Promise.all([
vehiclesApi.getTrims(initialData.year, makeOption.id, modelOption.id),
vehiclesApi.getTransmissions(initialData.year, makeOption.id, modelOption.id)
]);
setTrims(trimsData);
setTransmissions(transmissionsData);
if (initialData.trimLevel) {
const trimOption = trimsData.find(t => t.name === initialData.trimLevel);
if (trimOption) {
setSelectedTrim(trimOption);
// Load engines
const enginesData = await vehiclesApi.getEngines(
initialData.year,
makeOption.id,
modelOption.id,
trimOption.id
);
setEngines(enginesData);
}
}
}
}
}
}
// Signal that dropdowns are ready
setDropdownsReady(true);
} catch (error) {
console.error('Failed to initialize edit mode:', error);
} finally {
setLoadingDropdowns(false);
}
};
initializeEditMode();
}, [initialData]); // Run when initialData is available
// Reset form values after dropdowns are loaded and rendered
useEffect(() => {
if (!dropdownsReady || !initialData) return;
let timer2: NodeJS.Timeout;
// Use setTimeout to ensure React has rendered the dropdown options
const timer1 = setTimeout(() => {
// Normalize the data to match dropdown option values (lowercase)
const normalizedData = {
...initialData,
make: initialData.make?.toLowerCase(),
model: initialData.model?.toLowerCase(),
trimLevel: initialData.trimLevel,
transmission: initialData.transmission,
engine: initialData.engine
};
reset(normalizedData);
// Mark initialization complete after a delay to allow effects to process
timer2 = setTimeout(() => {
isInitializing.current = false;
}, 100);
}, 50);
return () => {
clearTimeout(timer1);
if (timer2) clearTimeout(timer2);
};
}, [dropdownsReady, initialData, reset]);
// Load makes when year changes
useEffect(() => {
// Skip during initialization
if (isInitializing.current) return;
if (watchedYear && watchedYear !== selectedYear) {
const loadMakes = async () => {
setLoadingDropdowns(true);
@@ -160,7 +260,7 @@ export const VehicleForm: React.FC<VehicleFormProps> = ({
const makesData = await vehiclesApi.getMakes(watchedYear);
setMakes(makesData);
setSelectedYear(watchedYear);
// Clear dependent selections
setModels([]);
setEngines([]);
@@ -187,6 +287,9 @@ export const VehicleForm: React.FC<VehicleFormProps> = ({
// Load models when make changes
useEffect(() => {
// Skip during initialization
if (isInitializing.current) return;
if (watchedMake && watchedYear && watchedMake !== selectedMake?.name) {
const makeOption = makes.find(make => make.name === watchedMake);
if (makeOption) {
@@ -196,7 +299,7 @@ export const VehicleForm: React.FC<VehicleFormProps> = ({
const modelsData = await vehiclesApi.getModels(watchedYear, makeOption.id);
setModels(modelsData);
setSelectedMake(makeOption);
// Clear dependent selections
setEngines([]);
setTrims([]);
@@ -221,6 +324,9 @@ export const VehicleForm: React.FC<VehicleFormProps> = ({
// Load trims when model changes
useEffect(() => {
// Skip during initialization
if (isInitializing.current) return;
if (watchedModel && watchedYear && selectedMake && watchedModel !== selectedModel?.name) {
const modelOption = models.find(model => model.name === watchedModel);
if (modelOption) {
@@ -257,6 +363,9 @@ export const VehicleForm: React.FC<VehicleFormProps> = ({
// Load engines when trim changes
useEffect(() => {
// Skip during initialization
if (isInitializing.current) return;
const trimName = watch('trimLevel');
if (trimName && watchedYear && selectedMake && selectedModel) {
const trimOption = trims.find(t => t.name === trimName);
@@ -347,7 +456,7 @@ export const VehicleForm: React.FC<VehicleFormProps> = ({
<option value="">Select Make</option>
{makes.map((make) => (
<option key={make.id} value={make.name}>
{formatVehicleLabel(make.name)}
{make.name}
</option>
))}
</select>
@@ -366,7 +475,7 @@ export const VehicleForm: React.FC<VehicleFormProps> = ({
<option value="">Select Model</option>
{models.map((model) => (
<option key={model.id} value={model.name}>
{formatVehicleLabel(model.name)}
{model.name}
</option>
))}
</select>