diff --git a/frontend/src/features/documents/components/DeleteDocumentConfirmDialog.tsx b/frontend/src/features/documents/components/DeleteDocumentConfirmDialog.tsx new file mode 100644 index 0000000..d7959ca --- /dev/null +++ b/frontend/src/features/documents/components/DeleteDocumentConfirmDialog.tsx @@ -0,0 +1,137 @@ +/** + * @ai-summary Context-aware document delete confirmation dialog + * Shows different messages based on whether document is being removed from vehicle or fully deleted + */ + +import React from 'react'; +import { + Dialog, + DialogTitle, + DialogContent, + DialogActions, + Button, + Typography, + Box, + useMediaQuery, + useTheme, +} from '@mui/material'; +import WarningAmberIcon from '@mui/icons-material/WarningAmber'; +import type { DocumentRecord } from '../types/documents.types'; + +export interface DeleteDocumentConfirmDialogProps { + open: boolean; + onClose: () => void; + onConfirm: (fullDelete: boolean) => void; + document: DocumentRecord | null; + vehicleId: string | null; +} + +export const DeleteDocumentConfirmDialog: React.FC = ({ + open, + onClose, + onConfirm, + document, + vehicleId, +}) => { + const theme = useTheme(); + const isSmallScreen = useMediaQuery(theme.breakpoints.down('sm')); + + if (!document || !vehicleId) { + return null; + } + + // Determine delete context + const isPrimaryVehicle = document.vehicleId === vehicleId; + const isSharedVehicle = document.sharedVehicleIds.includes(vehicleId); + const sharedCount = document.sharedVehicleIds.length; + + let title: string; + let message: string; + let fullDelete: boolean; + let actionText: string; + + if (isPrimaryVehicle && sharedCount === 0) { + // Primary vehicle with no shares: Full delete + title = 'Delete Document?'; + message = 'This will permanently delete this document. This action cannot be undone.'; + fullDelete = true; + actionText = 'Delete'; + } else if (isSharedVehicle) { + // Shared vehicle: Remove association only + title = 'Remove Document from Vehicle?'; + message = `This will remove the document from this vehicle. The document will remain shared with ${sharedCount - 1 === 1 ? '1 other vehicle' : `${sharedCount - 1} other vehicles`}.`; + fullDelete = false; + actionText = 'Remove'; + } else if (isPrimaryVehicle && sharedCount > 0) { + // Primary vehicle with shares: Full delete (affects all) + title = 'Delete Document?'; + message = `This document is shared with ${sharedCount === 1 ? '1 other vehicle' : `${sharedCount} other vehicles`}. Deleting it will remove it from all vehicles. This action cannot be undone.`; + fullDelete = true; + actionText = 'Delete'; + } else { + // Fallback case (should not happen) + title = 'Delete Document?'; + message = 'This will delete this document. This action cannot be undone.'; + fullDelete = true; + actionText = 'Delete'; + } + + const handleConfirm = () => { + onConfirm(fullDelete); + }; + + return ( + + + + + + {title} + + + + + + {message} + + + + {document.title} + + + {document.documentType.charAt(0).toUpperCase() + document.documentType.slice(1)} + {document.expirationDate && ` • Expires: ${new Date(document.expirationDate).toLocaleDateString()}`} + + + + + + + + + ); +}; diff --git a/frontend/src/features/vehicles/pages/VehicleDetailPage.tsx b/frontend/src/features/vehicles/pages/VehicleDetailPage.tsx index e5a5894..a2469ba 100644 --- a/frontend/src/features/vehicles/pages/VehicleDetailPage.tsx +++ b/frontend/src/features/vehicles/pages/VehicleDetailPage.tsx @@ -4,12 +4,13 @@ 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 } from '@mui/material'; +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 { vehiclesApi } from '../api/vehicles.api'; import { Card } from '../../../shared-minimal/components/Card'; @@ -23,6 +24,9 @@ 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; @@ -53,9 +57,14 @@ export const VehicleDetailPage: React.FC = () => { 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 @@ -105,8 +114,24 @@ export const VehicleDetailPage: React.FC = () => { 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]); + }, [fuelLogs, documents]); const filteredRecords = useMemo(() => { if (recordFilter === 'All') return records; @@ -208,6 +233,46 @@ export const VehicleDetailPage: React.FC = () => { 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); + console.log('Document deleted permanently'); + } else { + // Remove vehicle association only + await removeVehicleFromDocument({ docId: documentToDelete.id, vehicleId: id }); + console.log('Document removed from vehicle'); + } + + // 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); @@ -398,29 +463,43 @@ export const VehicleDetailPage: React.FC = () => { Type Summary Amount + Actions - {isFuelLoading && ( + {(isFuelLoading || isDocumentsLoading) && ( - + Loading records… )} - {!isFuelLoading && filteredRecords.length === 0 && ( + {!isFuelLoading && !isDocumentsLoading && filteredRecords.length === 0 && ( - + No records found for this filter. )} - {!isFuelLoading && filteredRecords.map((rec) => ( - handleRowClick(rec.id, rec.type)}> + {!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" + > + + + )} + ))} @@ -461,6 +540,15 @@ export const VehicleDetailPage: React.FC = () => { + + {/* Delete Document Confirmation Dialog */} + );