Modernization Project Complete. Updated to latest versions of frameworks.
This commit is contained in:
@@ -1,37 +1,75 @@
|
||||
/**
|
||||
* @ai-summary Main vehicles page with Material Design 3
|
||||
* @ai-summary Main vehicles page with React 19 advanced features
|
||||
* @ai-context Enhanced with Suspense, useOptimistic, and useTransition
|
||||
*/
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import { Box, Typography, Grid, Button as MuiButton } from '@mui/material';
|
||||
import React, { useState, useEffect, useTransition } from 'react';
|
||||
import { Box, Typography, Grid, Button as MuiButton, TextField, IconButton } from '@mui/material';
|
||||
import AddIcon from '@mui/icons-material/Add';
|
||||
import { useVehicles, useCreateVehicle, useDeleteVehicle } from '../hooks/useVehicles';
|
||||
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 { VehicleCard } from '../components/VehicleCard';
|
||||
import { VehicleForm } from '../components/VehicleForm';
|
||||
import { Card } from '../../../shared-minimal/components/Card';
|
||||
import { VehicleListSuspense, FormSuspense } from '../../../components/SuspenseWrappers';
|
||||
import { useAppStore } from '../../../core/store';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
export const VehiclesPage: React.FC = () => {
|
||||
const navigate = useNavigate();
|
||||
const { data: vehicles, isLoading } = useVehicles();
|
||||
const createVehicle = useCreateVehicle();
|
||||
const deleteVehicle = useDeleteVehicle();
|
||||
const setSelectedVehicle = useAppStore(state => state.setSelectedVehicle);
|
||||
|
||||
// React 19 optimistic updates and transitions
|
||||
const {
|
||||
optimisticVehicles,
|
||||
isPending: isOptimisticPending,
|
||||
optimisticCreateVehicle,
|
||||
optimisticDeleteVehicle
|
||||
} = useOptimisticVehicles(vehicles || []);
|
||||
|
||||
const {
|
||||
searchQuery,
|
||||
filteredVehicles,
|
||||
isPending: isSearchPending,
|
||||
handleSearch,
|
||||
clearSearch,
|
||||
updateVehicles
|
||||
} = useVehicleSearch(optimisticVehicles);
|
||||
|
||||
const [isPending, startTransition] = useTransition();
|
||||
const [showForm, setShowForm] = useState(false);
|
||||
|
||||
// Update search vehicles when optimistic vehicles change
|
||||
useEffect(() => {
|
||||
updateVehicles(optimisticVehicles);
|
||||
}, [optimisticVehicles, updateVehicles]);
|
||||
|
||||
const handleSelectVehicle = (id: string) => {
|
||||
setSelectedVehicle(id);
|
||||
navigate(`/vehicles/${id}`);
|
||||
// Use transition for navigation to avoid blocking UI
|
||||
startTransition(() => {
|
||||
setSelectedVehicle(id);
|
||||
navigate(`/vehicles/${id}`);
|
||||
});
|
||||
};
|
||||
|
||||
const handleDelete = async (id: string) => {
|
||||
if (confirm('Are you sure you want to delete this vehicle?')) {
|
||||
await deleteVehicle.mutateAsync(id);
|
||||
await optimisticDeleteVehicle(id);
|
||||
}
|
||||
};
|
||||
|
||||
const handleCreateVehicle = async (data: any) => {
|
||||
await optimisticCreateVehicle(data);
|
||||
// Use transition for UI state change
|
||||
startTransition(() => {
|
||||
setShowForm(false);
|
||||
});
|
||||
};
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<Box sx={{
|
||||
@@ -46,45 +84,77 @@ export const VehiclesPage: React.FC = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<Box sx={{ py: 2 }}>
|
||||
<Box sx={{
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
mb: 4
|
||||
}}>
|
||||
<Typography variant="h4" sx={{ fontWeight: 700, color: 'text.primary' }}>
|
||||
My Vehicles
|
||||
</Typography>
|
||||
{!showForm && (
|
||||
<MuiButton
|
||||
variant="contained"
|
||||
startIcon={<AddIcon />}
|
||||
onClick={() => setShowForm(true)}
|
||||
sx={{ borderRadius: '999px' }}
|
||||
>
|
||||
Add Vehicle
|
||||
</MuiButton>
|
||||
<VehicleListSuspense>
|
||||
<Box sx={{ py: 2 }}>
|
||||
<Box sx={{
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
mb: 4
|
||||
}}>
|
||||
<Typography variant="h4" sx={{ fontWeight: 700, color: 'text.primary' }}>
|
||||
My Vehicles
|
||||
</Typography>
|
||||
{!showForm && (
|
||||
<MuiButton
|
||||
variant="contained"
|
||||
startIcon={<AddIcon />}
|
||||
onClick={() => startTransition(() => setShowForm(true))}
|
||||
sx={{ borderRadius: '999px' }}
|
||||
disabled={isPending || isOptimisticPending}
|
||||
>
|
||||
Add Vehicle
|
||||
</MuiButton>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{/* Search functionality */}
|
||||
{vehicles && vehicles.length > 0 && (
|
||||
<Box sx={{ mb: 3 }}>
|
||||
<TextField
|
||||
fullWidth
|
||||
placeholder="Search vehicles by name, make, model, or VIN..."
|
||||
value={searchQuery}
|
||||
onChange={(e) => handleSearch(e.target.value)}
|
||||
InputProps={{
|
||||
startAdornment: <SearchIcon sx={{ mr: 1, color: 'text.secondary' }} />,
|
||||
endAdornment: searchQuery && (
|
||||
<IconButton onClick={clearSearch} size="small">
|
||||
<ClearIcon />
|
||||
</IconButton>
|
||||
),
|
||||
}}
|
||||
sx={{
|
||||
'& .MuiOutlinedInput-root': {
|
||||
borderRadius: '999px',
|
||||
backgroundColor: isSearchPending ? 'action.hover' : 'background.paper'
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{searchQuery && (
|
||||
<Typography variant="body2" sx={{ mt: 1, color: 'text.secondary' }}>
|
||||
{isSearchPending ? 'Searching...' : `Found ${filteredVehicles.length} vehicle(s)`}
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{showForm && (
|
||||
<Card className="mb-6">
|
||||
<Typography variant="h6" sx={{ fontWeight: 600, mb: 2 }}>
|
||||
Add New Vehicle
|
||||
</Typography>
|
||||
<VehicleForm
|
||||
onSubmit={async (data) => {
|
||||
await createVehicle.mutateAsync(data);
|
||||
setShowForm(false);
|
||||
}}
|
||||
onCancel={() => setShowForm(false)}
|
||||
loading={createVehicle.isPending}
|
||||
/>
|
||||
</Card>
|
||||
<FormSuspense>
|
||||
<Card className="mb-6">
|
||||
<Typography variant="h6" sx={{ fontWeight: 600, mb: 2 }}>
|
||||
Add New Vehicle
|
||||
</Typography>
|
||||
<VehicleForm
|
||||
onSubmit={handleCreateVehicle}
|
||||
onCancel={() => startTransition(() => setShowForm(false))}
|
||||
loading={isOptimisticPending}
|
||||
/>
|
||||
</Card>
|
||||
</FormSuspense>
|
||||
)}
|
||||
|
||||
{vehicles?.length === 0 ? (
|
||||
{optimisticVehicles.length === 0 ? (
|
||||
<Card>
|
||||
<Box sx={{ textAlign: 'center', py: 8 }}>
|
||||
<Typography color="text.secondary" sx={{ mb: 3 }}>
|
||||
@@ -94,8 +164,9 @@ export const VehiclesPage: React.FC = () => {
|
||||
<MuiButton
|
||||
variant="contained"
|
||||
startIcon={<AddIcon />}
|
||||
onClick={() => setShowForm(true)}
|
||||
onClick={() => startTransition(() => setShowForm(true))}
|
||||
sx={{ borderRadius: '999px' }}
|
||||
disabled={isPending || isOptimisticPending}
|
||||
>
|
||||
Add Your First Vehicle
|
||||
</MuiButton>
|
||||
@@ -104,7 +175,7 @@ export const VehiclesPage: React.FC = () => {
|
||||
</Card>
|
||||
) : (
|
||||
<Grid container spacing={3}>
|
||||
{vehicles?.map((vehicle) => (
|
||||
{filteredVehicles.map((vehicle) => (
|
||||
<Grid item xs={12} md={6} lg={4} key={vehicle.id}>
|
||||
<VehicleCard
|
||||
vehicle={vehicle}
|
||||
@@ -116,6 +187,7 @@ export const VehiclesPage: React.FC = () => {
|
||||
))}
|
||||
</Grid>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
</VehicleListSuspense>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user