Update deployment health checks. Fix UI bugs.
This commit is contained in:
@@ -135,7 +135,7 @@ verify:
|
|||||||
- |
|
- |
|
||||||
HEALTH_OK=0
|
HEALTH_OK=0
|
||||||
for i in 1 2 3 4 5 6; do
|
for i in 1 2 3 4 5 6; do
|
||||||
if docker compose -f $DOCKER_COMPOSE_FILE exec -T mvp-backend curl -sf http://localhost:3001/health > /dev/null 2>&1; then
|
if node -e "require('http').get('http://localhost:3001/health', (res) => { process.exit(res.statusCode === 200 ? 0 : 1) }).on('error', () => process.exit(1))" > /dev/null 2>&1; then
|
||||||
echo "OK: Backend health check passed"
|
echo "OK: Backend health check passed"
|
||||||
HEALTH_OK=1
|
HEALTH_OK=1
|
||||||
break
|
break
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
"test": "jest",
|
"test": "jest",
|
||||||
"test:watch": "jest --watch",
|
"test:watch": "jest --watch",
|
||||||
"test:feature": "jest --testPathPattern=src/features/${npm_config_feature}",
|
"test:feature": "jest --testPathPattern=src/features/${npm_config_feature}",
|
||||||
|
"migrate": "npm run migrate:all",
|
||||||
"migrate:all": "ts-node src/_system/migrations/run-all.ts",
|
"migrate:all": "ts-node src/_system/migrations/run-all.ts",
|
||||||
"migrate:feature": "ts-node src/_system/migrations/run-feature.ts",
|
"migrate:feature": "ts-node src/_system/migrations/run-feature.ts",
|
||||||
"schema:generate": "ts-node src/_system/schema/generate.ts",
|
"schema:generate": "ts-node src/_system/schema/generate.ts",
|
||||||
|
|||||||
@@ -1,53 +0,0 @@
|
|||||||
acura
|
|
||||||
alfa_romeo
|
|
||||||
aston_martin
|
|
||||||
audi
|
|
||||||
bentley
|
|
||||||
bmw
|
|
||||||
buick
|
|
||||||
cadillac
|
|
||||||
chevrolet
|
|
||||||
chrysler
|
|
||||||
dodge
|
|
||||||
ferrari
|
|
||||||
fiat
|
|
||||||
ford
|
|
||||||
genesis
|
|
||||||
gmc
|
|
||||||
honda
|
|
||||||
hummer
|
|
||||||
hyundai
|
|
||||||
infiniti
|
|
||||||
isuzu
|
|
||||||
jaguar
|
|
||||||
jeep
|
|
||||||
kia
|
|
||||||
lamborghini
|
|
||||||
land_rover
|
|
||||||
lexus
|
|
||||||
lincoln
|
|
||||||
lotus
|
|
||||||
lucid
|
|
||||||
maserati
|
|
||||||
mazda
|
|
||||||
mclaren
|
|
||||||
mercury
|
|
||||||
mini
|
|
||||||
mitsubishi
|
|
||||||
nissan
|
|
||||||
oldsmobile
|
|
||||||
plymouth
|
|
||||||
polestar
|
|
||||||
pontiac
|
|
||||||
porsche
|
|
||||||
ram
|
|
||||||
rivian
|
|
||||||
rolls_royce
|
|
||||||
saab
|
|
||||||
scion
|
|
||||||
smart
|
|
||||||
subaru
|
|
||||||
tesla
|
|
||||||
toyota
|
|
||||||
volkswagen
|
|
||||||
volvo
|
|
||||||
139
docs/PROMPTS.md
139
docs/PROMPTS.md
@@ -20,12 +20,11 @@ Your task is to create a plan that can be dispatched to a seprate set of AI agen
|
|||||||
|
|
||||||
|
|
||||||
*** ROLE ***
|
*** ROLE ***
|
||||||
You are a senior DevOps SRE in charge of improving the deployment of htis app.
|
You are a senior DevOps SRE in charge of MotoVaultPro. A automotive fleet management application.
|
||||||
|
|
||||||
*** ACTION ***
|
*** ACTION ***
|
||||||
- You need to add a deployment step where the vehicle-etl process that's currentl configured to be manual, is executed during the gitlab deployment.
|
- There are errors in deployment then also console errors
|
||||||
- Research the directory @data/vehicle-etl/ to understand the process.
|
- It appears when trying to run the npm migrations there is an error.
|
||||||
- The source database will be @data/vehicle-etl/snapshots/vehicle-drop-down.sqlite
|
|
||||||
- Read README.md CLAUDE.md and AI-INDEX.md to understand this code repository in the context of this change.
|
- Read README.md CLAUDE.md and AI-INDEX.md to understand this code repository in the context of this change.
|
||||||
|
|
||||||
*** CONTEXT ***
|
*** CONTEXT ***
|
||||||
@@ -33,3 +32,135 @@ You are a senior DevOps SRE in charge of improving the deployment of htis app.
|
|||||||
|
|
||||||
*** CHANGES TO IMPLEMENT ***
|
*** CHANGES TO IMPLEMENT ***
|
||||||
- Plan and recommend the best solution for this change
|
- Plan and recommend the best solution for this change
|
||||||
|
|
||||||
|
Here are the relevant logs.
|
||||||
|
egullickson@mvp-runner-1:~/motovaultpro$ docker compose run --rm mvp-backend npm run migrate
|
||||||
|
[+] 3/3t 3/31
|
||||||
|
✔ Network motovaultpro_backend Created 0.1s
|
||||||
|
✔ Container mvp-postgres Running 0.0s
|
||||||
|
✔ Container mvp-redis Running 0.0s
|
||||||
|
Image motovaultpro-mvp-backend Building
|
||||||
|
[+] Building 196.2s (26/26) FINISHED
|
||||||
|
=> [internal] load local bake definitions 0.0s
|
||||||
|
=> => reading from stdin 622B
|
||||||
|
=> => unpacking to docker.io/library/motovaultpro-mvp-backend:latest 9.5s
|
||||||
|
=> resolving provenance for metadata file 0.0s
|
||||||
|
Image motovaultpro-mvp-backend Built
|
||||||
|
Container motovaultpro-mvp-backend-run-9a43629523b7 Creating
|
||||||
|
Container motovaultpro-mvp-backend-run-9a43629523b7 Created
|
||||||
|
npm error Missing script: "migrate"
|
||||||
|
npm error
|
||||||
|
npm error To see a list of scripts, run:
|
||||||
|
npm error npm run
|
||||||
|
npm error A complete log of this run can be found in: /home/nodejs/.npm/_logs/2025-12-20T16_15_34_540Z-debug-0.log
|
||||||
|
index-BZX6X6vG.js:21 [Auth Gate] Module loaded, authInitialized: false
|
||||||
|
index-BZX6X6vG.js:21 [Navigation] State rehydrated successfully
|
||||||
|
index-BZX6X6vG.js:21 [Auth0Provider] Component loaded Object
|
||||||
|
index-BZX6X6vG.js:21 [TokenInjector] Component loaded
|
||||||
|
index-BZX6X6vG.js:27 [DEBUG App] Render check - isLoading: true isAuthenticated: false isAuthGateReady: false
|
||||||
|
index-BZX6X6vG.js:27 MotoVaultPro status: Object
|
||||||
|
index-BZX6X6vG.js:21 [useIsAuthInitialized] Starting poll for auth init
|
||||||
|
index-BZX6X6vG.js:27 DataSync: Skipping prefetch - user not authenticated
|
||||||
|
index-BZX6X6vG.js:27 Window width: 1728 User agent mobile: false Mobile mode: false
|
||||||
|
index-BZX6X6vG.js:21 [Auth Debug] Mobile: false, Loading: true, Authenticated: false, User: null
|
||||||
|
index-BZX6X6vG.js:21 [Auth Debug] State check: Object
|
||||||
|
index-BZX6X6vG.js:21 [DEBUG] setAuthInitialized called with: false (was: false )
|
||||||
|
index-BZX6X6vG.js:21 [IndexedDB] Loaded 0 items into cache
|
||||||
|
index-BZX6X6vG.js:21 [IndexedDB] Storage initialized successfully
|
||||||
|
index-BZX6X6vG.js:21 [useIsAuthInitialized] Poll #1: initialized=false
|
||||||
|
index-BZX6X6vG.js:21 [useIsAuthInitialized] Poll #2: initialized=false
|
||||||
|
index-BZX6X6vG.js:21 [useIsAuthInitialized] Poll #3: initialized=false
|
||||||
|
index-BZX6X6vG.js:21 [Auth0Provider] Redirect callback triggered Object
|
||||||
|
index-BZX6X6vG.js:21 [Auth0Provider] Component loaded Object
|
||||||
|
index-BZX6X6vG.js:21 [TokenInjector] Component loaded
|
||||||
|
index-BZX6X6vG.js:27 [DEBUG App] Render check - isLoading: false isAuthenticated: true isAuthGateReady: false
|
||||||
|
index-BZX6X6vG.js:27 MotoVaultPro status: Object
|
||||||
|
index-BZX6X6vG.js:27 [DEBUG App] Auth gate not ready yet, showing loading state
|
||||||
|
index-BZX6X6vG.js:21 [Auth Debug] Mobile: false, Loading: false, Authenticated: true, User: present
|
||||||
|
index-BZX6X6vG.js:21 [Auth Debug] State check: Object
|
||||||
|
index-BZX6X6vG.js:27 [DEBUG App] Render check - isLoading: false isAuthenticated: true isAuthGateReady: false
|
||||||
|
index-BZX6X6vG.js:27 MotoVaultPro status: Object
|
||||||
|
index-BZX6X6vG.js:27 [DEBUG App] Auth gate not ready yet, showing loading state
|
||||||
|
index-BZX6X6vG.js:21 [Auth] IndexedDB storage is ready
|
||||||
|
index-BZX6X6vG.js:21 [Mobile Auth] Initializing token cache (mobile: false, delay: 100ms)
|
||||||
|
index-BZX6X6vG.js:21 [useIsAuthInitialized] Poll #4: initialized=false
|
||||||
|
index-BZX6X6vG.js:21 [Mobile Auth] Token acquired successfully on attempt 1 Object
|
||||||
|
index-BZX6X6vG.js:21 [Mobile Auth] Token pre-warming successful
|
||||||
|
index-BZX6X6vG.js:21 [DEBUG] setAuthInitialized called with: true (was: false )
|
||||||
|
index-BZX6X6vG.js:21 [DEBUG Auth Gate] Authentication fully initialized
|
||||||
|
index-BZX6X6vG.js:21 [useIsAuthInitialized] Poll #5: initialized=true
|
||||||
|
index-BZX6X6vG.js:21 [useIsAuthInitialized] Auth initialized via poll!
|
||||||
|
index-BZX6X6vG.js:27 [DEBUG App] Render check - isLoading: false isAuthenticated: true isAuthGateReady: true
|
||||||
|
index-BZX6X6vG.js:27 MotoVaultPro status: Object
|
||||||
|
index-BZX6X6vG.js:21 [Auth0Provider] Component loaded Object
|
||||||
|
index-BZX6X6vG.js:21 [TokenInjector] Component loaded
|
||||||
|
index-BZX6X6vG.js:27 [DEBUG App] Render check - isLoading: false isAuthenticated: true isAuthGateReady: true
|
||||||
|
index-BZX6X6vG.js:27 MotoVaultPro status: Object
|
||||||
|
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
|
||||||
|
index-BZX6X6vG.js:27 [useVehicles] Query function called - fetching vehicles
|
||||||
|
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
|
||||||
|
index-BZX6X6vG.js:21 [Mobile Auth] Token acquired successfully on attempt 1 Object
|
||||||
|
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
|
||||||
|
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
|
||||||
|
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
|
||||||
|
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
|
||||||
|
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
|
||||||
|
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
|
||||||
|
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
|
||||||
|
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
|
||||||
|
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
|
||||||
|
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
|
||||||
|
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
|
||||||
|
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
|
||||||
|
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
|
||||||
|
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
|
||||||
|
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
|
||||||
|
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
|
||||||
|
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
|
||||||
|
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
|
||||||
|
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
|
||||||
|
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
|
||||||
|
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
|
||||||
|
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
|
||||||
|
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
|
||||||
|
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
|
||||||
|
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
|
||||||
|
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
|
||||||
|
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
|
||||||
|
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
|
||||||
|
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
|
||||||
|
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
|
||||||
|
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
|
||||||
|
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
|
||||||
|
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
|
||||||
|
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
|
||||||
|
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
|
||||||
|
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
|
||||||
|
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
|
||||||
|
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
|
||||||
|
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
|
||||||
|
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
|
||||||
|
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
|
||||||
|
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
|
||||||
|
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
|
||||||
|
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
|
||||||
|
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
|
||||||
|
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
|
||||||
|
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
|
||||||
|
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
|
||||||
|
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
|
||||||
|
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
|
||||||
|
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
|
||||||
|
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
|
||||||
|
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
|
||||||
|
index-BZX6X6vG.js:21 Uncaught Error: Minified React error #185; visit https://react.dev/errors/185 for the full message or use the non-minified dev environment for full errors and additional helpful warnings.
|
||||||
|
at yi (index-BZX6X6vG.js:21:32113)
|
||||||
|
at hi (index-BZX6X6vG.js:21:31638)
|
||||||
|
at Yc (index-BZX6X6vG.js:21:64112)
|
||||||
|
at Hc (index-BZX6X6vG.js:21:63724)
|
||||||
|
at index-BZX6X6vG.js:27:314956
|
||||||
|
at Zu (index-BZX6X6vG.js:21:97033)
|
||||||
|
at Ad (index-BZX6X6vG.js:21:113396)
|
||||||
|
at Fd (index-BZX6X6vG.js:21:113280)
|
||||||
|
at Ad (index-BZX6X6vG.js:21:113441)
|
||||||
|
at Fd (index-BZX6X6vG.js:21:113280)
|
||||||
|
|||||||
@@ -40,9 +40,11 @@ export function useOptimisticVehicles(vehicles: Vehicle[] = []) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Sync optimistic state with actual vehicles when they change
|
// Sync optimistic state with actual vehicles when they change
|
||||||
|
// Guard: Skip if both are empty arrays (prevents infinite loop on reference changes)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (vehicles.length === 0 && optimisticVehicles.length === 0) return;
|
||||||
setOptimisticVehicles(vehicles);
|
setOptimisticVehicles(vehicles);
|
||||||
}, [vehicles]);
|
}, [vehicles, optimisticVehicles.length]);
|
||||||
|
|
||||||
const optimisticCreateVehicle = async (data: CreateVehicleRequest) => {
|
const optimisticCreateVehicle = async (data: CreateVehicleRequest) => {
|
||||||
// Create optimistic vehicle with temporary ID
|
// Create optimistic vehicle with temporary ID
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
* @ai-context Non-blocking updates for better UI responsiveness
|
* @ai-context Non-blocking updates for better UI responsiveness
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useTransition, useState, useCallback } from 'react';
|
import { useTransition, useState, useCallback, useEffect } from 'react';
|
||||||
import { Vehicle } from '../types/vehicles.types';
|
import { Vehicle } from '../types/vehicles.types';
|
||||||
|
|
||||||
export function useVehicleSearch(vehicles: Vehicle[]) {
|
export function useVehicleSearch(vehicles: Vehicle[]) {
|
||||||
@@ -42,35 +42,32 @@ export function useVehicleSearch(vehicles: Vehicle[]) {
|
|||||||
});
|
});
|
||||||
}, [vehicles]);
|
}, [vehicles]);
|
||||||
|
|
||||||
// Update filtered vehicles when vehicles data changes
|
// Auto-sync filtered vehicles when source vehicles change
|
||||||
const updateVehicles = useCallback((newVehicles: Vehicle[]) => {
|
// Note: Direct state update (no startTransition) to avoid deferred render cascades
|
||||||
startTransition(() => {
|
useEffect(() => {
|
||||||
if (!searchQuery.trim()) {
|
if (!searchQuery.trim()) {
|
||||||
setFilteredVehicles(newVehicles);
|
setFilteredVehicles(vehicles);
|
||||||
} else {
|
} else {
|
||||||
// Re-apply search filter to new data
|
const filtered = vehicles.filter(vehicle => {
|
||||||
const filtered = newVehicles.filter(vehicle => {
|
const searchTerm = searchQuery.toLowerCase();
|
||||||
const searchTerm = searchQuery.toLowerCase();
|
return (
|
||||||
return (
|
vehicle.nickname?.toLowerCase().includes(searchTerm) ||
|
||||||
vehicle.nickname?.toLowerCase().includes(searchTerm) ||
|
vehicle.make?.toLowerCase().includes(searchTerm) ||
|
||||||
vehicle.make?.toLowerCase().includes(searchTerm) ||
|
vehicle.model?.toLowerCase().includes(searchTerm) ||
|
||||||
vehicle.model?.toLowerCase().includes(searchTerm) ||
|
vehicle.vin.toLowerCase().includes(searchTerm) ||
|
||||||
vehicle.vin.toLowerCase().includes(searchTerm) ||
|
vehicle.year?.toString().includes(searchTerm)
|
||||||
vehicle.year?.toString().includes(searchTerm)
|
);
|
||||||
);
|
});
|
||||||
});
|
setFilteredVehicles(filtered);
|
||||||
setFilteredVehicles(filtered);
|
}
|
||||||
}
|
}, [vehicles, searchQuery]);
|
||||||
});
|
|
||||||
}, [searchQuery]);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
searchQuery,
|
searchQuery,
|
||||||
filteredVehicles,
|
filteredVehicles,
|
||||||
isPending,
|
isPending,
|
||||||
handleSearch,
|
handleSearch,
|
||||||
clearSearch,
|
clearSearch
|
||||||
updateVehicles
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
* @ai-context Enhanced with Suspense, optimistic updates, and transitions
|
* @ai-context Enhanced with Suspense, optimistic updates, and transitions
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useTransition, useEffect } from 'react';
|
import React, { useTransition, useMemo } from 'react';
|
||||||
import { Box, Typography, Grid, Button } from '@mui/material';
|
import { Box, Typography, Grid, Button } from '@mui/material';
|
||||||
import AddIcon from '@mui/icons-material/Add';
|
import AddIcon from '@mui/icons-material/Add';
|
||||||
import { useVehicles } from '../hooks/useVehicles';
|
import { useVehicles } from '../hooks/useVehicles';
|
||||||
@@ -40,24 +40,22 @@ export const VehiclesMobileScreen: React.FC<VehiclesMobileScreenProps> = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const { data: vehicles, isLoading } = useVehicles();
|
const { data: vehicles, isLoading } = useVehicles();
|
||||||
const [_isPending, startTransition] = useTransition();
|
const [_isPending, startTransition] = useTransition();
|
||||||
|
|
||||||
|
// Stable reference for empty array (prevents infinite loop when vehicles is undefined)
|
||||||
|
const safeVehicles = useMemo(
|
||||||
|
() => (Array.isArray(vehicles) ? vehicles : []),
|
||||||
|
[vehicles]
|
||||||
|
);
|
||||||
|
|
||||||
// React 19 optimistic updates
|
// React 19 optimistic updates
|
||||||
const {
|
const {
|
||||||
optimisticVehicles,
|
optimisticVehicles,
|
||||||
isPending: isOptimisticPending
|
isPending: isOptimisticPending
|
||||||
} = useOptimisticVehicles(Array.isArray(vehicles) ? vehicles : []);
|
} = useOptimisticVehicles(safeVehicles);
|
||||||
|
|
||||||
// Enhanced search with transitions
|
|
||||||
const {
|
|
||||||
filteredVehicles,
|
|
||||||
updateVehicles
|
|
||||||
} = useVehicleSearch(optimisticVehicles);
|
|
||||||
|
|
||||||
// Update search when optimistic vehicles change
|
|
||||||
useEffect(() => {
|
|
||||||
updateVehicles(optimisticVehicles);
|
|
||||||
}, [optimisticVehicles, updateVehicles]);
|
|
||||||
|
|
||||||
|
// Enhanced search with transitions (auto-syncs when vehicles change)
|
||||||
|
const { filteredVehicles } = useVehicleSearch(optimisticVehicles);
|
||||||
|
|
||||||
const handleVehicleSelect = (vehicle: Vehicle) => {
|
const handleVehicleSelect = (vehicle: Vehicle) => {
|
||||||
// Use transition to avoid blocking UI during navigation
|
// Use transition to avoid blocking UI during navigation
|
||||||
startTransition(() => {
|
startTransition(() => {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
* @ai-context Enhanced with Suspense, useOptimistic, and useTransition
|
* @ai-context Enhanced with Suspense, useOptimistic, and useTransition
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useState, useEffect, useTransition } from 'react';
|
import React, { useState, useTransition, useMemo } from 'react';
|
||||||
import { Box, Typography, Grid, Button as MuiButton, TextField, IconButton } from '@mui/material';
|
import { Box, Typography, Grid, Button as MuiButton, TextField, IconButton } from '@mui/material';
|
||||||
import AddIcon from '@mui/icons-material/Add';
|
import AddIcon from '@mui/icons-material/Add';
|
||||||
import SearchIcon from '@mui/icons-material/Search';
|
import SearchIcon from '@mui/icons-material/Search';
|
||||||
@@ -25,33 +25,33 @@ export const VehiclesPage: React.FC = () => {
|
|||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const { data: vehicles, isLoading } = useVehicles();
|
const { data: vehicles, isLoading } = useVehicles();
|
||||||
const setSelectedVehicle = useAppStore(state => state.setSelectedVehicle);
|
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
|
// React 19 optimistic updates and transitions
|
||||||
const {
|
const {
|
||||||
optimisticVehicles,
|
optimisticVehicles,
|
||||||
isPending: isOptimisticPending,
|
isPending: isOptimisticPending,
|
||||||
optimisticCreateVehicle,
|
optimisticCreateVehicle,
|
||||||
optimisticDeleteVehicle
|
optimisticDeleteVehicle
|
||||||
} = useOptimisticVehicles(Array.isArray(vehicles) ? vehicles : []);
|
} = useOptimisticVehicles(safeVehicles);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
searchQuery,
|
searchQuery,
|
||||||
filteredVehicles,
|
filteredVehicles,
|
||||||
isPending: isSearchPending,
|
isPending: isSearchPending,
|
||||||
handleSearch,
|
handleSearch,
|
||||||
clearSearch,
|
clearSearch
|
||||||
updateVehicles
|
|
||||||
} = useVehicleSearch(optimisticVehicles);
|
} = useVehicleSearch(optimisticVehicles);
|
||||||
|
|
||||||
const [isPending, startTransition] = useTransition();
|
const [isPending, startTransition] = useTransition();
|
||||||
const [showForm, setShowForm] = useState(false);
|
const [showForm, setShowForm] = useState(false);
|
||||||
const [stagedImageFile, setStagedImageFile] = useState<File | null>(null);
|
const [stagedImageFile, setStagedImageFile] = useState<File | null>(null);
|
||||||
|
|
||||||
// Update search vehicles when optimistic vehicles change
|
|
||||||
useEffect(() => {
|
|
||||||
updateVehicles(optimisticVehicles);
|
|
||||||
}, [optimisticVehicles, updateVehicles]);
|
|
||||||
|
|
||||||
const handleSelectVehicle = (id: string) => {
|
const handleSelectVehicle = (id: string) => {
|
||||||
// Use transition for navigation to avoid blocking UI
|
// Use transition for navigation to avoid blocking UI
|
||||||
startTransition(() => {
|
startTransition(() => {
|
||||||
|
|||||||
Reference in New Issue
Block a user