/** * @ai-summary Vehicle detail page matching VehicleForm styling */ import React, { useMemo, useState, useEffect } from 'react'; import { useParams, useNavigate, useSearchParams } from 'react-router-dom'; import { Box, Typography, Button as MuiButton, Divider, FormControl, InputLabel, Select, MenuItem, Table, TableHead, TableRow, TableCell, TableBody, Dialog, DialogTitle, DialogContent, useMediaQuery, IconButton } from '@mui/material'; import { useQueryClient } from '@tanstack/react-query'; import ArrowBackIcon from '@mui/icons-material/ArrowBack'; import EditIcon from '@mui/icons-material/Edit'; import LocalGasStationIcon from '@mui/icons-material/LocalGasStation'; import BuildIcon from '@mui/icons-material/Build'; import DeleteIcon from '@mui/icons-material/Delete'; import { Vehicle } from '../types/vehicles.types'; import { getVehicleLabel, getVehicleSubtitle } from '@/core/utils/vehicleDisplay'; import { vehiclesApi } from '../api/vehicles.api'; import { Card } from '../../../shared-minimal/components/Card'; import { VehicleForm } from '../components/VehicleForm'; import { VehicleImage } from '../components/VehicleImage'; import { useFuelLogs } from '../../fuel-logs/hooks/useFuelLogs'; import { FuelLogResponse, UpdateFuelLogRequest } from '../../fuel-logs/types/fuel-logs.types'; import { FuelLogEditDialog } from '../../fuel-logs/components/FuelLogEditDialog'; import { FuelLogForm } from '../../fuel-logs/components/FuelLogForm'; // Unit conversions now handled by backend import { fuelLogsApi } from '../../fuel-logs/api/fuel-logs.api'; import { OwnershipCostsList } from '../../ownership-costs'; import { useDocumentsByVehicle, useDeleteDocument, useRemoveVehicleFromDocument } from '../../documents/hooks/useDocuments'; import { DeleteDocumentConfirmDialog } from '../../documents/components/DeleteDocumentConfirmDialog'; import type { DocumentRecord } from '../../documents/types/documents.types'; const DetailField: React.FC<{ label: string; value?: string | number; isRequired?: boolean; className?: string; }> = ({ label, value, isRequired, className = "" }) => (
{value || Not provided}
); export const VehicleDetailPage: React.FC = () => { const { id } = useParams<{ id: string }>(); const navigate = useNavigate(); const [searchParams, setSearchParams] = useSearchParams(); const [vehicle, setVehicle] = useState(null); const [isLoading, setIsLoading] = useState(true); const [isEditing, setIsEditing] = useState(() => searchParams.get('edit') === 'true'); const [error, setError] = useState(null); const [recordFilter, setRecordFilter] = useState<'All' | 'Fuel Logs' | 'Maintenance' | 'Documents'>('All'); const { fuelLogs, isLoading: isFuelLoading } = useFuelLogs(id); const { data: documents, isLoading: isDocumentsLoading } = useDocumentsByVehicle(id); const { mutateAsync: deleteDocument } = useDeleteDocument(); const { mutateAsync: removeVehicleFromDocument } = useRemoveVehicleFromDocument(); const queryClient = useQueryClient(); const [editingLog, setEditingLog] = useState(null); const [showAddDialog, setShowAddDialog] = useState(false); const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); const [documentToDelete, setDocumentToDelete] = useState(null); const isSmallScreen = useMediaQuery('(max-width:600px)'); // Unit conversions now handled by backend // Define records list hooks BEFORE any early returns to keep hooks order stable type VehicleRecord = { id: string; type: 'Fuel Logs' | 'Maintenance' | 'Documents'; date: string; // ISO summary: string; amount?: string; // formatted }; const records: VehicleRecord[] = useMemo(() => { const list: VehicleRecord[] = []; if (fuelLogs && Array.isArray(fuelLogs)) { // Build a map of prior odometer readings to compute trip distance when missing const logsAsc = [...(fuelLogs as FuelLogResponse[])].sort( (a, b) => new Date(a.dateTime).getTime() - new Date(b.dateTime).getTime() ); const prevOdoById = new Map(); let lastOdo: number | undefined = undefined; for (const l of logsAsc) { prevOdoById.set(l.id, lastOdo); if (typeof l.odometerReading === 'number' && !isNaN(l.odometerReading)) { lastOdo = l.odometerReading; } } for (const log of fuelLogs as FuelLogResponse[]) { const parts: string[] = []; // Efficiency: Use backend calculation (primary display) if (typeof log.efficiency === 'number' && log.efficiency > 0) { parts.push(`${log.efficiencyLabel || 'MPG'}: ${log.efficiency.toFixed(3)}`); } // Grade label (secondary display) if (log.fuelGrade) { parts.push(`Grade: ${log.fuelGrade}`); } else if (log.fuelType) { const ft = String(log.fuelType); parts.push(ft.charAt(0).toUpperCase() + ft.slice(1)); } const summary = parts.join(' • '); const amount = (typeof log.totalCost === 'number') ? `$${log.totalCost.toFixed(2)}` : undefined; list.push({ id: log.id, type: 'Fuel Logs', date: log.dateTime, summary, amount }); } } // Add documents to records if (documents && Array.isArray(documents)) { for (const doc of documents) { const parts: string[] = []; parts.push(doc.title); parts.push(doc.documentType.charAt(0).toUpperCase() + doc.documentType.slice(1)); if (doc.expirationDate) { parts.push(`Expires: ${new Date(doc.expirationDate).toLocaleDateString()}`); } const summary = parts.join(' • '); const date = doc.issuedDate || doc.createdAt; list.push({ id: doc.id, type: 'Documents', date, summary, amount: undefined }); } } return list.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()); }, [fuelLogs, documents]); const filteredRecords = useMemo(() => { if (recordFilter === 'All') return records; return records.filter(r => r.type === recordFilter); }, [records, recordFilter]); useEffect(() => { const loadVehicle = async () => { if (!id) return; try { setIsLoading(true); const vehicleData = await vehiclesApi.getById(id); setVehicle(vehicleData); } catch (err) { setError('Failed to load vehicle details'); console.error('Error loading vehicle:', err); } finally { setIsLoading(false); } }; loadVehicle(); }, [id]); const handleBack = () => { navigate('/garage/vehicles'); }; const handleEdit = () => { setIsEditing(true); }; const handleUpdateVehicle = async (data: any) => { if (!vehicle) return; try { const updatedVehicle = await vehiclesApi.update(vehicle.id, data); setVehicle(updatedVehicle); setIsEditing(false); // Clear the edit query param from URL if (searchParams.has('edit')) { searchParams.delete('edit'); setSearchParams(searchParams, { replace: true }); } } catch (err) { console.error('Error updating vehicle:', err); } }; const handleCancelEdit = () => { setIsEditing(false); // Clear the edit query param from URL if (searchParams.has('edit')) { searchParams.delete('edit'); setSearchParams(searchParams, { replace: true }); } }; if (isLoading) { return ( Loading vehicle details... ); } if (error || !vehicle) { return ( {error || 'Vehicle not found'} } > Back to Vehicles ); } const displayName = getVehicleLabel(vehicle); const handleRowClick = (recId: string, type: VehicleRecord['type']) => { if (type === 'Fuel Logs') { const log = (fuelLogs as FuelLogResponse[] | undefined)?.find(l => l.id === recId) || null; setEditingLog(log); } // Documents are handled via delete button, not row click }; const handleDeleteDocumentClick = (docId: string, event: React.MouseEvent) => { event.stopPropagation(); // Prevent row click const doc = documents?.find(d => d.id === docId) || null; if (doc) { setDocumentToDelete(doc); setDeleteDialogOpen(true); } }; const handleDeleteConfirm = async (fullDelete: boolean) => { if (!documentToDelete || !id) return; try { if (fullDelete) { // Full delete await deleteDocument(documentToDelete.id); } else { // Remove vehicle association only await removeVehicleFromDocument({ docId: documentToDelete.id, vehicleId: id }); } // Invalidate queries to refresh data queryClient.invalidateQueries({ queryKey: ['documents-by-vehicle', id] }); queryClient.invalidateQueries({ queryKey: ['documents'] }); setDeleteDialogOpen(false); setDocumentToDelete(null); } catch (err) { console.error('Error deleting/removing document:', err); } }; const handleDeleteCancel = () => { setDeleteDialogOpen(false); setDocumentToDelete(null); }; const handleCloseEdit = () => setEditingLog(null); const handleSaveEdit = async (id: string, data: UpdateFuelLogRequest) => { await fuelLogsApi.update(id, data); await queryClient.invalidateQueries({ queryKey: ['fuelLogs', id] }); await queryClient.invalidateQueries({ queryKey: ['fuelLogs', vehicle?.id] }); await queryClient.invalidateQueries({ queryKey: ['fuelLogs'] }); setEditingLog(null); }; if (isEditing) { return ( } onClick={handleCancelEdit} sx={{ mr: 2 }} > Cancel Edit {displayName} setVehicle(updated)} /> ); } return ( } onClick={handleBack} sx={{ mr: 2 }} > Back {displayName} } onClick={handleEdit} sx={{ borderRadius: '999px' }} > Edit Vehicle } sx={{ borderRadius: '999px' }} onClick={() => setShowAddDialog(true)} > Add Fuel Log } sx={{ borderRadius: '999px' }} > Schedule Maintenance Vehicle Details {getVehicleSubtitle(vehicle) || 'Unknown Vehicle'} {vehicle.vin && ( VIN: {vehicle.vin} )}
{/* Vehicle Specification Section */}
{/* Purchase Information Section */}

Purchase Information

{/* Recurring Ownership Costs */} Vehicle Records Filter Date Type Summary Amount Actions {(isFuelLoading || isDocumentsLoading) && ( Loading records… )} {!isFuelLoading && !isDocumentsLoading && filteredRecords.length === 0 && ( No records found for this filter. )} {!isFuelLoading && !isDocumentsLoading && filteredRecords.map((rec) => ( handleRowClick(rec.id, rec.type)}> {new Date(rec.date).toLocaleDateString()} {rec.type} {rec.summary} {rec.amount || '—'} {rec.type === 'Documents' && ( handleDeleteDocumentClick(rec.id, e)} sx={{ minWidth: 44, minHeight: 44 }} aria-label="Delete document" > )} ))}
{/* Edit Dialog for Fuel Logs */} {/* Add Fuel Log Dialog */} setShowAddDialog(false)} maxWidth="md" fullWidth fullScreen={isSmallScreen} PaperProps={{ sx: { maxHeight: '90vh' } }} > Add Fuel Log { setShowAddDialog(false); // Refresh fuel logs data queryClient.invalidateQueries({ queryKey: ['fuelLogs', vehicle?.id] }); queryClient.invalidateQueries({ queryKey: ['fuelLogs'] }); }} /> {/* Delete Document Confirmation Dialog */}
); };