From a49f419eab302a9b7cac5866fa498099b00cc499 Mon Sep 17 00:00:00 2001 From: Eric Gullickson <16152721+ericgullickson@users.noreply.github.com> Date: Fri, 15 May 2026 20:45:42 -0500 Subject: [PATCH] fix: show maintenance records on vehicle summary screen (refs #239) The Vehicle Records section on /garage/vehicles/:id never called useMaintenanceRecords, so maintenance rows always rendered empty even when records existed for the vehicle. Wire the existing hook into both the desktop VehicleDetailPage and mobile VehicleDetailMobile, merge records into the unified list with category + subtypes + shop name, and include the maintenance loading state in the section's loading and empty-state guards. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../vehicles/mobile/VehicleDetailMobile.tsx | 36 +++++++++++++++++-- .../vehicles/pages/VehicleDetailPage.tsx | 30 +++++++++++++--- 2 files changed, 58 insertions(+), 8 deletions(-) diff --git a/frontend/src/features/vehicles/mobile/VehicleDetailMobile.tsx b/frontend/src/features/vehicles/mobile/VehicleDetailMobile.tsx index 7f9d16f..bef18a7 100644 --- a/frontend/src/features/vehicles/mobile/VehicleDetailMobile.tsx +++ b/frontend/src/features/vehicles/mobile/VehicleDetailMobile.tsx @@ -12,6 +12,9 @@ import { FuelLogResponse, UpdateFuelLogRequest } from '../../fuel-logs/types/fue import { FuelLogEditDialog } from '../../fuel-logs/components/FuelLogEditDialog'; import { fuelLogsApi } from '../../fuel-logs/api/fuel-logs.api'; import { MaintenanceRecordForm } from '../../maintenance/components/MaintenanceRecordForm'; +import { useMaintenanceRecords } from '../../maintenance/hooks/useMaintenanceRecords'; +import { getCategoryDisplayName } from '../../maintenance/types/maintenance.types'; +import type { MaintenanceRecordResponse } from '../../maintenance/types/maintenance.types'; import { VehicleImage } from '../components/VehicleImage'; import { OwnershipCostsList } from '../../ownership-costs'; import { getVehicleLabel } from '@/core/utils/vehicleDisplay'; @@ -46,6 +49,7 @@ export const VehicleDetailMobile: React.FC = ({ const [recordFilter, setRecordFilter] = useState<'All' | 'Fuel Logs' | 'Maintenance' | 'Documents'>('All'); const { fuelLogs, isLoading: isFuelLoading } = useFuelLogs(vehicle.id); + const { records: maintenanceRecords, isRecordsLoading: isMaintenanceLoading } = useMaintenanceRecords(vehicle.id); const queryClient = useQueryClient(); const [editingLog, setEditingLog] = useState(null); const [showMaintenanceDialog, setShowMaintenanceDialog] = useState(false); @@ -124,8 +128,34 @@ export const VehicleDetailMobile: React.FC = ({ }); } } + + if (maintenanceRecords && Array.isArray(maintenanceRecords)) { + for (const rec of maintenanceRecords as MaintenanceRecordResponse[]) { + const catLabel = getCategoryDisplayName(rec.category); + const subtypes = Array.isArray(rec.subtypes) ? rec.subtypes : []; + const subtypeText = subtypes.length + ? `${subtypes.slice(0, 3).join(', ')}${subtypes.length > 3 ? ` +${subtypes.length - 3}` : ''}` + : ''; + const summary = subtypeText ? `${catLabel} — ${subtypeText}` : catLabel; + const secondaryParts: string[] = []; + if (rec.shopName) secondaryParts.push(rec.shopName); + secondaryParts.push(new Date(rec.date).toLocaleDateString()); + secondaryParts.push('Maintenance'); + const secondary = secondaryParts.join(' • '); + const amount = typeof rec.cost === 'number' ? `$${rec.cost.toFixed(2)}` : undefined; + list.push({ + id: rec.id, + type: 'Maintenance', + date: rec.date, + summary, + amount, + secondary + }); + } + } + return list.sort((a, b) => b.date.localeCompare(a.date)); - }, [fuelLogs]); + }, [fuelLogs, maintenanceRecords]); const filteredRecords = useMemo(() => { if (recordFilter === 'All') return records; @@ -243,7 +273,7 @@ export const VehicleDetailMobile: React.FC = ({
- {isFuelLoading ? 'Loading…' : `${filteredRecords.length} record${filteredRecords.length === 1 ? '' : 's'}`} + {(isFuelLoading || isMaintenanceLoading) ? 'Loading…' : `${filteredRecords.length} record${filteredRecords.length === 1 ? '' : 's'}`} Filter @@ -262,7 +292,7 @@ export const VehicleDetailMobile: React.FC = ({ - {isFuelLoading ? ( + {(isFuelLoading || isMaintenanceLoading) ? ( Loading records… diff --git a/frontend/src/features/vehicles/pages/VehicleDetailPage.tsx b/frontend/src/features/vehicles/pages/VehicleDetailPage.tsx index 179aef4..65e7e0f 100644 --- a/frontend/src/features/vehicles/pages/VehicleDetailPage.tsx +++ b/frontend/src/features/vehicles/pages/VehicleDetailPage.tsx @@ -23,6 +23,9 @@ import { FuelLogResponse, UpdateFuelLogRequest } from '../../fuel-logs/types/fue import { FuelLogEditDialog } from '../../fuel-logs/components/FuelLogEditDialog'; import { FuelLogForm } from '../../fuel-logs/components/FuelLogForm'; import { MaintenanceRecordForm } from '../../maintenance/components/MaintenanceRecordForm'; +import { useMaintenanceRecords } from '../../maintenance/hooks/useMaintenanceRecords'; +import { getCategoryDisplayName } from '../../maintenance/types/maintenance.types'; +import type { MaintenanceRecordResponse } from '../../maintenance/types/maintenance.types'; // Unit conversions now handled by backend import { fuelLogsApi } from '../../fuel-logs/api/fuel-logs.api'; import { OwnershipCostsList } from '../../ownership-costs'; @@ -60,6 +63,7 @@ export const VehicleDetailPage: React.FC = () => { const { fuelLogs, isLoading: isFuelLoading } = useFuelLogs(id); const { data: documents, isLoading: isDocumentsLoading } = useDocumentsByVehicle(id); + const { records: maintenanceRecords, isRecordsLoading: isMaintenanceLoading } = useMaintenanceRecords(id); const { mutateAsync: deleteDocument } = useDeleteDocument(); const { mutateAsync: removeVehicleFromDocument } = useRemoveVehicleFromDocument(); const queryClient = useQueryClient(); @@ -133,8 +137,24 @@ export const VehicleDetailPage: React.FC = () => { } } + if (maintenanceRecords && Array.isArray(maintenanceRecords)) { + for (const rec of maintenanceRecords as MaintenanceRecordResponse[]) { + const catLabel = getCategoryDisplayName(rec.category); + const subtypes = Array.isArray(rec.subtypes) ? rec.subtypes : []; + const subtypeText = subtypes.length + ? `${subtypes.slice(0, 3).join(', ')}${subtypes.length > 3 ? ` +${subtypes.length - 3}` : ''}` + : ''; + const parts: string[] = [catLabel]; + if (subtypeText) parts.push(subtypeText); + if (rec.shopName) parts.push(rec.shopName); + const summary = parts.join(' • '); + const amount = typeof rec.cost === 'number' ? `$${rec.cost.toFixed(2)}` : undefined; + list.push({ id: rec.id, type: 'Maintenance', date: rec.date, summary, amount }); + } + } + return list.sort((a, b) => b.date.localeCompare(a.date)); - }, [fuelLogs, documents]); + }, [fuelLogs, documents, maintenanceRecords]); const filteredRecords = useMemo(() => { if (recordFilter === 'All') return records; @@ -475,22 +495,22 @@ export const VehicleDetailPage: React.FC = () => { - {(isFuelLoading || isDocumentsLoading) && ( + {(isFuelLoading || isDocumentsLoading || isMaintenanceLoading) && ( Loading records… )} - {!isFuelLoading && !isDocumentsLoading && filteredRecords.length === 0 && ( + {!isFuelLoading && !isDocumentsLoading && !isMaintenanceLoading && filteredRecords.length === 0 && ( No records found for this filter. )} - {!isFuelLoading && !isDocumentsLoading && filteredRecords.map((rec) => ( - handleRowClick(rec.id, rec.type)}> + {!isFuelLoading && !isDocumentsLoading && !isMaintenanceLoading && filteredRecords.map((rec) => ( + handleRowClick(rec.id, rec.type)}> {dayjs(rec.date).format('M/D/YYYY')} {rec.type} {rec.summary}