fix: show maintenance records on vehicle summary screen (refs #239)
All checks were successful
Deploy to Staging / Build Images (pull_request) Successful in 1m14s
Deploy to Staging / Deploy to Staging (pull_request) Successful in 42s
Deploy to Staging / Verify Staging (pull_request) Successful in 3s
Deploy to Staging / Notify Staging Ready (pull_request) Successful in 4s
Deploy to Staging / Notify Staging Failure (pull_request) Has been skipped
All checks were successful
Deploy to Staging / Build Images (pull_request) Successful in 1m14s
Deploy to Staging / Deploy to Staging (pull_request) Successful in 42s
Deploy to Staging / Verify Staging (pull_request) Successful in 3s
Deploy to Staging / Notify Staging Ready (pull_request) Successful in 4s
Deploy to Staging / Notify Staging Failure (pull_request) Has been skipped
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) <noreply@anthropic.com>
This commit is contained in:
@@ -12,6 +12,9 @@ import { FuelLogResponse, UpdateFuelLogRequest } from '../../fuel-logs/types/fue
|
|||||||
import { FuelLogEditDialog } from '../../fuel-logs/components/FuelLogEditDialog';
|
import { FuelLogEditDialog } from '../../fuel-logs/components/FuelLogEditDialog';
|
||||||
import { fuelLogsApi } from '../../fuel-logs/api/fuel-logs.api';
|
import { fuelLogsApi } from '../../fuel-logs/api/fuel-logs.api';
|
||||||
import { MaintenanceRecordForm } from '../../maintenance/components/MaintenanceRecordForm';
|
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 { VehicleImage } from '../components/VehicleImage';
|
||||||
import { OwnershipCostsList } from '../../ownership-costs';
|
import { OwnershipCostsList } from '../../ownership-costs';
|
||||||
import { getVehicleLabel } from '@/core/utils/vehicleDisplay';
|
import { getVehicleLabel } from '@/core/utils/vehicleDisplay';
|
||||||
@@ -46,6 +49,7 @@ export const VehicleDetailMobile: React.FC<VehicleDetailMobileProps> = ({
|
|||||||
|
|
||||||
const [recordFilter, setRecordFilter] = useState<'All' | 'Fuel Logs' | 'Maintenance' | 'Documents'>('All');
|
const [recordFilter, setRecordFilter] = useState<'All' | 'Fuel Logs' | 'Maintenance' | 'Documents'>('All');
|
||||||
const { fuelLogs, isLoading: isFuelLoading } = useFuelLogs(vehicle.id);
|
const { fuelLogs, isLoading: isFuelLoading } = useFuelLogs(vehicle.id);
|
||||||
|
const { records: maintenanceRecords, isRecordsLoading: isMaintenanceLoading } = useMaintenanceRecords(vehicle.id);
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const [editingLog, setEditingLog] = useState<FuelLogResponse | null>(null);
|
const [editingLog, setEditingLog] = useState<FuelLogResponse | null>(null);
|
||||||
const [showMaintenanceDialog, setShowMaintenanceDialog] = useState(false);
|
const [showMaintenanceDialog, setShowMaintenanceDialog] = useState(false);
|
||||||
@@ -124,8 +128,34 @@ export const VehicleDetailMobile: React.FC<VehicleDetailMobileProps> = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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));
|
return list.sort((a, b) => b.date.localeCompare(a.date));
|
||||||
}, [fuelLogs]);
|
}, [fuelLogs, maintenanceRecords]);
|
||||||
|
|
||||||
const filteredRecords = useMemo(() => {
|
const filteredRecords = useMemo(() => {
|
||||||
if (recordFilter === 'All') return records;
|
if (recordFilter === 'All') return records;
|
||||||
@@ -243,7 +273,7 @@ export const VehicleDetailMobile: React.FC<VehicleDetailMobileProps> = ({
|
|||||||
<Section title="Vehicle Records">
|
<Section title="Vehicle Records">
|
||||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 1 }}>
|
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 1 }}>
|
||||||
<Typography variant="body2" color="text.secondary">
|
<Typography variant="body2" color="text.secondary">
|
||||||
{isFuelLoading ? 'Loading…' : `${filteredRecords.length} record${filteredRecords.length === 1 ? '' : 's'}`}
|
{(isFuelLoading || isMaintenanceLoading) ? 'Loading…' : `${filteredRecords.length} record${filteredRecords.length === 1 ? '' : 's'}`}
|
||||||
</Typography>
|
</Typography>
|
||||||
<FormControl size="small" sx={{ minWidth: 180 }}>
|
<FormControl size="small" sx={{ minWidth: 180 }}>
|
||||||
<InputLabel id="vehicle-records-filter">Filter</InputLabel>
|
<InputLabel id="vehicle-records-filter">Filter</InputLabel>
|
||||||
@@ -262,7 +292,7 @@ export const VehicleDetailMobile: React.FC<VehicleDetailMobileProps> = ({
|
|||||||
</Box>
|
</Box>
|
||||||
<Card>
|
<Card>
|
||||||
<CardContent sx={{ p: 0 }}>
|
<CardContent sx={{ p: 0 }}>
|
||||||
{isFuelLoading ? (
|
{(isFuelLoading || isMaintenanceLoading) ? (
|
||||||
<Box sx={{ p: 2 }}>
|
<Box sx={{ p: 2 }}>
|
||||||
<Typography color="text.secondary" variant="body2">Loading records…</Typography>
|
<Typography color="text.secondary" variant="body2">Loading records…</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -23,6 +23,9 @@ import { FuelLogResponse, UpdateFuelLogRequest } from '../../fuel-logs/types/fue
|
|||||||
import { FuelLogEditDialog } from '../../fuel-logs/components/FuelLogEditDialog';
|
import { FuelLogEditDialog } from '../../fuel-logs/components/FuelLogEditDialog';
|
||||||
import { FuelLogForm } from '../../fuel-logs/components/FuelLogForm';
|
import { FuelLogForm } from '../../fuel-logs/components/FuelLogForm';
|
||||||
import { MaintenanceRecordForm } from '../../maintenance/components/MaintenanceRecordForm';
|
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
|
// Unit conversions now handled by backend
|
||||||
import { fuelLogsApi } from '../../fuel-logs/api/fuel-logs.api';
|
import { fuelLogsApi } from '../../fuel-logs/api/fuel-logs.api';
|
||||||
import { OwnershipCostsList } from '../../ownership-costs';
|
import { OwnershipCostsList } from '../../ownership-costs';
|
||||||
@@ -60,6 +63,7 @@ export const VehicleDetailPage: React.FC = () => {
|
|||||||
|
|
||||||
const { fuelLogs, isLoading: isFuelLoading } = useFuelLogs(id);
|
const { fuelLogs, isLoading: isFuelLoading } = useFuelLogs(id);
|
||||||
const { data: documents, isLoading: isDocumentsLoading } = useDocumentsByVehicle(id);
|
const { data: documents, isLoading: isDocumentsLoading } = useDocumentsByVehicle(id);
|
||||||
|
const { records: maintenanceRecords, isRecordsLoading: isMaintenanceLoading } = useMaintenanceRecords(id);
|
||||||
const { mutateAsync: deleteDocument } = useDeleteDocument();
|
const { mutateAsync: deleteDocument } = useDeleteDocument();
|
||||||
const { mutateAsync: removeVehicleFromDocument } = useRemoveVehicleFromDocument();
|
const { mutateAsync: removeVehicleFromDocument } = useRemoveVehicleFromDocument();
|
||||||
const queryClient = useQueryClient();
|
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));
|
return list.sort((a, b) => b.date.localeCompare(a.date));
|
||||||
}, [fuelLogs, documents]);
|
}, [fuelLogs, documents, maintenanceRecords]);
|
||||||
|
|
||||||
const filteredRecords = useMemo(() => {
|
const filteredRecords = useMemo(() => {
|
||||||
if (recordFilter === 'All') return records;
|
if (recordFilter === 'All') return records;
|
||||||
@@ -475,22 +495,22 @@ export const VehicleDetailPage: React.FC = () => {
|
|||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{(isFuelLoading || isDocumentsLoading) && (
|
{(isFuelLoading || isDocumentsLoading || isMaintenanceLoading) && (
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell colSpan={5}>
|
<TableCell colSpan={5}>
|
||||||
<Typography color="text.secondary">Loading records…</Typography>
|
<Typography color="text.secondary">Loading records…</Typography>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
)}
|
)}
|
||||||
{!isFuelLoading && !isDocumentsLoading && filteredRecords.length === 0 && (
|
{!isFuelLoading && !isDocumentsLoading && !isMaintenanceLoading && filteredRecords.length === 0 && (
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell colSpan={5}>
|
<TableCell colSpan={5}>
|
||||||
<Typography color="text.secondary">No records found for this filter.</Typography>
|
<Typography color="text.secondary">No records found for this filter.</Typography>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
)}
|
)}
|
||||||
{!isFuelLoading && !isDocumentsLoading && filteredRecords.map((rec) => (
|
{!isFuelLoading && !isDocumentsLoading && !isMaintenanceLoading && filteredRecords.map((rec) => (
|
||||||
<TableRow key={rec.id} hover sx={{ cursor: rec.type === 'Documents' ? 'default' : 'pointer' }} onClick={() => handleRowClick(rec.id, rec.type)}>
|
<TableRow key={rec.id} hover sx={{ cursor: rec.type === 'Fuel Logs' ? 'pointer' : 'default' }} onClick={() => handleRowClick(rec.id, rec.type)}>
|
||||||
<TableCell>{dayjs(rec.date).format('M/D/YYYY')}</TableCell>
|
<TableCell>{dayjs(rec.date).format('M/D/YYYY')}</TableCell>
|
||||||
<TableCell>{rec.type}</TableCell>
|
<TableCell>{rec.type}</TableCell>
|
||||||
<TableCell>{rec.summary}</TableCell>
|
<TableCell>{rec.summary}</TableCell>
|
||||||
|
|||||||
Reference in New Issue
Block a user