import React, { useEffect, useMemo, useState, useRef, memo } from 'react'; import { useForm, Controller } from 'react-hook-form'; import { z } from 'zod'; import { zodResolver } from '@hookform/resolvers/zod'; import { Grid, Card, CardHeader, CardContent, TextField, Box, Button, CircularProgress, ToggleButton, ToggleButtonGroup } from '@mui/material'; import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; import { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFns'; import { DateTimePicker } from '@mui/x-date-pickers/DateTimePicker'; import { VehicleSelector } from './VehicleSelector'; import { DistanceInput } from './DistanceInput'; import { FuelTypeSelector } from './FuelTypeSelector'; import { UnitSystemDisplay } from './UnitSystemDisplay'; import { StationPicker } from './StationPicker'; import { CostCalculator } from './CostCalculator'; import { useFuelLogs } from '../hooks/useFuelLogs'; import { useUserSettings } from '../hooks/useUserSettings'; import { useGeolocation } from '../../stations/hooks/useGeolocation'; import { CreateFuelLogRequest, FuelType } from '../types/fuel-logs.types'; const schema = z.object({ vehicleId: z.string().uuid(), dateTime: z.string().min(1), odometerReading: z.coerce.number().positive().optional(), tripDistance: z.coerce.number().positive().optional(), fuelType: z.nativeEnum(FuelType), fuelGrade: z.union([z.string(), z.null()]).optional(), fuelUnits: z.coerce.number().positive(), costPerUnit: z.coerce.number().positive(), locationData: z.any().optional(), notes: z.string().max(500).optional(), }).refine((d) => (d.odometerReading && d.odometerReading > 0) || (d.tripDistance && d.tripDistance > 0), { message: 'Either odometer reading or trip distance is required' }).refine((d) => !(d.odometerReading && d.tripDistance), { message: 'Cannot specify both odometer reading and trip distance' }); const FuelLogFormComponent: React.FC<{ onSuccess?: () => void; initial?: Partial }> = ({ onSuccess, initial }) => { const { userSettings } = useUserSettings(); const { createFuelLog, isLoading } = useFuelLogs(); const [useOdometer, setUseOdometer] = useState(false); const formInitialized = useRef(false); // Get user location for nearby station search const { coordinates: userLocation } = useGeolocation(); const { control, handleSubmit, watch, setValue, reset, formState: { errors, isValid } } = useForm({ resolver: zodResolver(schema), mode: 'onChange', defaultValues: { dateTime: new Date().toISOString().slice(0, 16), fuelType: FuelType.GASOLINE, ...initial } as any }); // DEBUG: Log component renders and form state console.log('[FuelLogForm] Render', { at: new Date().toISOString(), formInitialized: formInitialized.current, isLoading, }); // Prevent form reset after initial load useEffect(() => { console.log('[FuelLogForm] Mounted'); if (!formInitialized.current) { formInitialized.current = true; console.log('[FuelLogForm] Form initialized'); } return () => { console.log('[FuelLogForm] Unmounted'); }; }, []); // DEBUG: Watch for form value changes const vehicleId = watch('vehicleId'); useEffect(() => { console.log('[FuelLogForm] Vehicle ID changed:', vehicleId); }, [vehicleId]); // DEBUG: Track dirty-state changes to detect resets useEffect(() => { const subscription = watch((_, info) => { if (info.name) { console.log('[FuelLogForm] Field change', { name: info.name, type: info.type }); } }); return () => subscription.unsubscribe(); }, [watch]); const watched = watch(['fuelUnits', 'costPerUnit']); const [fuelUnitsRaw, costPerUnitRaw] = watched as [string | number | undefined, string | number | undefined]; // Convert to numbers for calculation const fuelUnits = typeof fuelUnitsRaw === 'string' ? parseFloat(fuelUnitsRaw) : fuelUnitsRaw; const costPerUnit = typeof costPerUnitRaw === 'string' ? parseFloat(costPerUnitRaw) : costPerUnitRaw; const calculatedCost = useMemo(() => { const units = fuelUnits && !isNaN(fuelUnits) ? fuelUnits : 0; const cost = costPerUnit && !isNaN(costPerUnit) ? costPerUnit : 0; return units > 0 && cost > 0 ? units * cost : 0; }, [fuelUnits, costPerUnit]); // Watch for distance and fuel units to calculate efficiency const watchedDistance = watch(useOdometer ? 'odometerReading' : 'tripDistance'); const distanceValue = typeof watchedDistance === 'string' ? parseFloat(watchedDistance) : watchedDistance; const calculatedEfficiency = useMemo(() => { const distance = distanceValue && !isNaN(distanceValue) ? distanceValue : 0; const units = fuelUnits && !isNaN(fuelUnits) ? fuelUnits : 0; if (distance > 0 && units > 0) { return distance / units; } return 0; }, [distanceValue, fuelUnits]); const onSubmit = async (data: CreateFuelLogRequest) => { const payload: CreateFuelLogRequest = { ...data, odometerReading: useOdometer ? data.odometerReading : undefined, tripDistance: useOdometer ? undefined : data.tripDistance, }; await createFuelLog(payload); // Reset form to initial defaults after successful create reset({ vehicleId: undefined as any, dateTime: new Date().toISOString().slice(0, 16), odometerReading: undefined as any, tripDistance: undefined as any, fuelType: FuelType.GASOLINE, fuelGrade: undefined as any, fuelUnits: undefined as any, costPerUnit: undefined as any, locationData: undefined as any, notes: undefined as any, } as any); onSuccess?.(); }; useEffect(() => { if (useOdometer) setValue('tripDistance', undefined as any); else setValue('odometerReading', undefined as any); }, [useOdometer, setValue]); return ( } />
{/* Row 1: Select Vehicle */} ( )} /> {/* Row 2: Date/Time | MPG/km/L */} ( field.onChange(newValue?.toISOString() || '')} format="MM/dd/yyyy hh:mm a" slotProps={{ textField: { fullWidth: true, error: !!errors.dateTime, helperText: errors.dateTime?.message, sx: { '& .MuiOutlinedInput-root': { minHeight: '56px', } } } }} /> )} /> 0 ? calculatedEfficiency.toFixed(3) : ''} fullWidth InputProps={{ readOnly: true, sx: { backgroundColor: 'grey.50', '& .MuiOutlinedInput-input': { cursor: 'default', }, }, }} helperText="Calculated from distance รท fuel amount" sx={{ '& .MuiOutlinedInput-root': { minHeight: '56px', } }} /> {/* Row 3: Odometer | Distance Input Method */} ( )} /> { if (newValue !== null) { setUseOdometer(newValue === 'odometer'); } }} aria-label="distance input method" fullWidth sx={{ height: '56px', // Match input field height '& .MuiToggleButton-root': { textTransform: 'none', fontWeight: 500, borderRadius: '8px', height: '56px', // Ensure button height matches '&.Mui-selected': { backgroundColor: 'primary.main', color: 'primary.contrastText', '&:hover': { backgroundColor: 'primary.dark', }, }, }, }} > Trip Distance Odometer Reading ( ( )} /> )} /> ( field.onChange(e.target.value)} label={`Fuel Amount (${userSettings?.unitSystem === 'imperial' ? 'gallons' : 'liters'})`} type="number" inputProps={{ step: 0.001, min: 0.001 }} fullWidth error={!!errors.fuelUnits} helperText={errors.fuelUnits?.message} /> )} /> ( field.onChange(e.target.value)} label={`Cost Per ${userSettings?.unitSystem === 'imperial' ? 'Gallon' : 'Liter'}`} type="number" inputProps={{ step: 0.001, min: 0.001 }} fullWidth error={!!errors.costPerUnit} helperText={errors.costPerUnit?.message} /> )} /> ( )} /> ( )} />
); }; export const FuelLogForm = memo(FuelLogFormComponent);