/**
* @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}
)}
{/* 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 */}
{/* Delete Document Confirmation Dialog */}
);
};