/** * @ai-summary Main vehicles page with React 19 advanced features * @ai-context Enhanced with Suspense, useOptimistic, and useTransition */ import React, { useState, useTransition, useMemo, useEffect } from 'react'; import { Box, Typography, Grid, Button as MuiButton, TextField, IconButton } from '@mui/material'; import AddIcon from '@mui/icons-material/Add'; import LockIcon from '@mui/icons-material/Lock'; import SearchIcon from '@mui/icons-material/Search'; import ClearIcon from '@mui/icons-material/Clear'; import { useVehicles } from '../hooks/useVehicles'; import { useOptimisticVehicles } from '../hooks/useOptimisticVehicles'; import { useVehicleSearch } from '../hooks/useVehicleTransitions'; import { useVehicleLimitCheck } from '../hooks/useVehicleLimitCheck'; import { VehicleCard } from '../components/VehicleCard'; import { VehicleForm } from '../components/VehicleForm'; import { Card } from '../../../shared-minimal/components/Card'; import { VehicleLimitDialog } from '../../../shared-minimal/components'; import { VehicleListSuspense, FormSuspense } from '../../../components/SuspenseWrappers'; import { useAppStore } from '../../../core/store'; import { useNavigate, useLocation } from 'react-router-dom'; import { useQueryClient } from '@tanstack/react-query'; import { vehiclesApi } from '../api/vehicles.api'; export const VehiclesPage: React.FC = () => { const navigate = useNavigate(); const location = useLocation(); const queryClient = useQueryClient(); const { data: vehicles, isLoading } = useVehicles(); const setSelectedVehicle = useAppStore(state => state.setSelectedVehicle); // Stable reference for empty array (prevents infinite loop when vehicles is undefined) const safeVehicles = useMemo( () => (Array.isArray(vehicles) ? vehicles : []), [vehicles] ); // React 19 optimistic updates and transitions const { optimisticVehicles, isPending: isOptimisticPending, optimisticCreateVehicle, optimisticDeleteVehicle } = useOptimisticVehicles(safeVehicles); const { searchQuery, filteredVehicles, isPending: isSearchPending, handleSearch, clearSearch } = useVehicleSearch(optimisticVehicles); const [isPending, startTransition] = useTransition(); const [showForm, setShowForm] = useState(false); const [stagedImageFile, setStagedImageFile] = useState(null); // Vehicle limit check const { isAtLimit, limit, tier, showLimitDialog, setShowLimitDialog, } = useVehicleLimitCheck(safeVehicles.length); // Auto-show form if navigated with showAddForm state (from dashboard) useEffect(() => { const state = location.state as { showAddForm?: boolean } | null; if (state?.showAddForm) { setShowForm(true); // Clear the state to prevent re-triggering on refresh navigate(location.pathname, { replace: true, state: {} }); } }, [location.state, location.pathname, navigate]); const handleSelectVehicle = (id: string) => { // Use transition for navigation to avoid blocking UI startTransition(() => { const vehicle = optimisticVehicles.find(v => v.id === id); setSelectedVehicle(vehicle || null); navigate(`/garage/vehicles/${id}`); }); }; const handleDelete = async (id: string) => { if (confirm('Are you sure you want to delete this vehicle?')) { await optimisticDeleteVehicle(id); } }; const handleCreateVehicle = async (data: any) => { const newVehicle = await optimisticCreateVehicle(data); console.log('[VehiclesPage] Vehicle created:', newVehicle?.id, 'stagedImageFile:', !!stagedImageFile); // Upload staged image if one was selected during creation if (stagedImageFile && newVehicle?.id) { // Don't upload if ID is temporary (optimistic) if (newVehicle.id.startsWith('temp-')) { console.warn('[VehiclesPage] Cannot upload image - vehicle has temporary ID:', newVehicle.id); } else { try { console.log('[VehiclesPage] Uploading image for vehicle:', newVehicle.id); const updatedVehicle = await vehiclesApi.uploadImage(newVehicle.id, stagedImageFile); console.log('[VehiclesPage] Image uploaded, updated vehicle:', updatedVehicle); // Directly update the cache with the vehicle that has imageUrl queryClient.setQueryData(['vehicles'], (old: typeof vehicles) => { if (!old || !Array.isArray(old)) return old; return old.map(v => v.id === updatedVehicle.id ? updatedVehicle : v); }); } catch (err: any) { console.error('[VehiclesPage] Failed to upload vehicle image:', { error: err, message: err?.message, response: err?.response?.data, status: err?.response?.status }); } } } setStagedImageFile(null); // Use transition for UI state change startTransition(() => { setShowForm(false); }); }; if (isLoading) { return ( Loading vehicles... ); } const handleAddVehicleClick = () => { if (isAtLimit) { setShowLimitDialog(true); } else { startTransition(() => setShowForm(true)); } }; return ( My Vehicles {!showForm && ( : } onClick={handleAddVehicleClick} sx={{ borderRadius: '999px' }} disabled={isPending || isOptimisticPending} > Add Vehicle )} {/* Search functionality */} {vehicles && Array.isArray(vehicles) && vehicles.length > 0 && ( handleSearch(e.target.value)} InputProps={{ startAdornment: , endAdornment: searchQuery && ( ), }} sx={{ '& .MuiOutlinedInput-root': { borderRadius: '999px', backgroundColor: isSearchPending ? 'action.hover' : 'background.paper' } }} /> {searchQuery && ( {isSearchPending ? 'Searching...' : `Found ${filteredVehicles.length} vehicle(s)`} )} )} {showForm && ( Add New Vehicle startTransition(() => setShowForm(false))} loading={isOptimisticPending} onStagedImage={setStagedImageFile} /> )} {optimisticVehicles.length === 0 ? ( No vehicles added yet {!showForm && ( : } onClick={handleAddVehicleClick} sx={{ borderRadius: '999px' }} disabled={isPending || isOptimisticPending} > Add Your First Vehicle )} ) : ( {filteredVehicles.map((vehicle) => ( navigate(`/garage/vehicles/${v.id}?edit=true`)} onDelete={handleDelete} onSelect={handleSelectVehicle} /> ))} )} {/* Vehicle Limit Dialog */} setShowLimitDialog(false)} currentCount={safeVehicles.length} limit={limit ?? 0} currentTier={tier} /> ); };