fix: Vehicle summary screen does not display maintenance records (#239) #240

Merged
egullickson merged 2 commits from issue-239-vehicle-summary-maintenance into main 2026-05-16 01:56:11 +00:00
2 changed files with 58 additions and 8 deletions
Showing only changes of commit a49f419eab - Show all commits

View File

@@ -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<VehicleDetailMobileProps> = ({
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<FuelLogResponse | null>(null);
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));
}, [fuelLogs]);
}, [fuelLogs, maintenanceRecords]);
const filteredRecords = useMemo(() => {
if (recordFilter === 'All') return records;
@@ -243,7 +273,7 @@ export const VehicleDetailMobile: React.FC<VehicleDetailMobileProps> = ({
<Section title="Vehicle Records">
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 1 }}>
<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>
<FormControl size="small" sx={{ minWidth: 180 }}>
<InputLabel id="vehicle-records-filter">Filter</InputLabel>
@@ -262,7 +292,7 @@ export const VehicleDetailMobile: React.FC<VehicleDetailMobileProps> = ({
</Box>
<Card>
<CardContent sx={{ p: 0 }}>
{isFuelLoading ? (
{(isFuelLoading || isMaintenanceLoading) ? (
<Box sx={{ p: 2 }}>
<Typography color="text.secondary" variant="body2">Loading records</Typography>
</Box>

View File

@@ -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 = () => {
</TableRow>
</TableHead>
<TableBody>
{(isFuelLoading || isDocumentsLoading) && (
{(isFuelLoading || isDocumentsLoading || isMaintenanceLoading) && (
<TableRow>
<TableCell colSpan={5}>
<Typography color="text.secondary">Loading records</Typography>
</TableCell>
</TableRow>
)}
{!isFuelLoading && !isDocumentsLoading && filteredRecords.length === 0 && (
{!isFuelLoading && !isDocumentsLoading && !isMaintenanceLoading && filteredRecords.length === 0 && (
<TableRow>
<TableCell colSpan={5}>
<Typography color="text.secondary">No records found for this filter.</Typography>
</TableCell>
</TableRow>
)}
{!isFuelLoading && !isDocumentsLoading && filteredRecords.map((rec) => (
<TableRow key={rec.id} hover sx={{ cursor: rec.type === 'Documents' ? 'default' : 'pointer' }} onClick={() => handleRowClick(rec.id, rec.type)}>
{!isFuelLoading && !isDocumentsLoading && !isMaintenanceLoading && filteredRecords.map((rec) => (
<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>{rec.type}</TableCell>
<TableCell>{rec.summary}</TableCell>