/** * @ai-summary Edit dialog for maintenance records with linked receipt display * @ai-context Mobile-friendly dialog with proper form handling and receipt thumbnail/view */ import React, { useState, useEffect } from 'react'; import { Dialog, DialogTitle, DialogContent, DialogActions, Button, TextField, Box, Grid, FormControl, InputLabel, Select, MenuItem, Typography, useMediaQuery, useTheme, } from '@mui/material'; import ReceiptIcon from '@mui/icons-material/Receipt'; import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; import { DatePicker } from '@mui/x-date-pickers/DatePicker'; import dayjs from 'dayjs'; import { MaintenanceRecordResponse, UpdateMaintenanceRecordRequest, MaintenanceCategory, getCategoryDisplayName, } from '../types/maintenance.types'; import { SubtypeCheckboxGroup } from './SubtypeCheckboxGroup'; import { useVehicles } from '../../vehicles/hooks/useVehicles'; import { documentsApi } from '../../documents/api/documents.api'; import type { Vehicle } from '../../vehicles/types/vehicles.types'; import { getVehicleLabel } from '@/core/utils/vehicleDisplay'; interface MaintenanceRecordEditDialogProps { open: boolean; record: MaintenanceRecordResponse | null; onClose: () => void; onSave: (id: string, data: UpdateMaintenanceRecordRequest) => Promise; } export const MaintenanceRecordEditDialog: React.FC = ({ open, record, onClose, onSave, }) => { const [formData, setFormData] = useState({}); const [isSaving, setIsSaving] = useState(false); const [error, setError] = useState(null); const vehiclesQuery = useVehicles(); const vehicles = vehiclesQuery.data; const theme = useTheme(); const isMobile = useMediaQuery(theme.breakpoints.down('sm')); const isSmallScreen = useMediaQuery('(max-width:600px)'); const [receiptThumbnailUrl, setReceiptThumbnailUrl] = useState(null); // Reset form when record changes useEffect(() => { if (record && record.id) { try { setFormData({ category: record.category, subtypes: record.subtypes, date: record.date, odometerReading: record.odometerReading || undefined, cost: record.cost ? Number(record.cost) : undefined, shopName: record.shopName || undefined, notes: record.notes || undefined, }); setError(null); } catch (err) { console.error('[MaintenanceRecordEditDialog] Error setting form data:', err); setError(err as Error); } } }, [record]); // Load receipt thumbnail when record has a linked receipt document useEffect(() => { if (!record?.receiptDocument?.documentId) { setReceiptThumbnailUrl(null); return; } let revoked = false; documentsApi.download(record.receiptDocument.documentId).then((blob) => { if (!revoked) { const url = URL.createObjectURL(blob); setReceiptThumbnailUrl(url); } }).catch((err) => { console.error('[MaintenanceRecordEditDialog] Failed to load receipt thumbnail:', err); }); return () => { revoked = true; if (receiptThumbnailUrl) { URL.revokeObjectURL(receiptThumbnailUrl); } }; // eslint-disable-next-line react-hooks/exhaustive-deps }, [record?.receiptDocument?.documentId]); const handleViewReceipt = async () => { if (!record?.receiptDocument?.documentId) return; try { const blob = await documentsApi.download(record.receiptDocument.documentId); const url = URL.createObjectURL(blob); window.open(url, '_blank'); // Revoke after a delay to allow the new tab to load setTimeout(() => URL.revokeObjectURL(url), 10000); } catch (err) { console.error('[MaintenanceRecordEditDialog] Failed to open receipt:', err); } }; const handleInputChange = (field: keyof UpdateMaintenanceRecordRequest, value: any) => { setFormData((prev) => ({ ...prev, [field]: value, })); }; const handleSave = async () => { if (!record || !record.id) { console.error('[MaintenanceRecordEditDialog] No valid record to save'); return; } try { setIsSaving(true); // Filter out unchanged fields const changedData: UpdateMaintenanceRecordRequest = {}; Object.entries(formData).forEach(([key, value]) => { const typedKey = key as keyof UpdateMaintenanceRecordRequest; const recordValue = record[typedKey as keyof MaintenanceRecordResponse]; // Special handling for arrays if (Array.isArray(value) && Array.isArray(recordValue)) { if (JSON.stringify(value) !== JSON.stringify(recordValue)) { (changedData as any)[key] = value; } } else if (value !== recordValue) { (changedData as any)[key] = value; } }); // Only send update if there are actual changes if (Object.keys(changedData).length > 0) { await onSave(record.id, changedData); } onClose(); } catch (err) { console.error('[MaintenanceRecordEditDialog] Failed to save record:', err); setError(err as Error); } finally { setIsSaving(false); } }; const handleCancel = () => { onClose(); }; // Early bailout if dialog not open or no record to edit if (!open || !record) return null; // Error state if (error) { return ( Error Loading Maintenance Record Failed to load maintenance record data. Please try again. {error.message} ); } return ( Edit Maintenance Record {/* Vehicle (Read-only display) */} { const vehicle = vehicles?.find((v: Vehicle) => v.id === record.vehicleId); return getVehicleLabel(vehicle); })()} helperText="Vehicle cannot be changed when editing" /> {/* Linked Receipt Display */} {record.receiptDocument && ( {receiptThumbnailUrl ? ( ) : ( )} Linked Receipt {record.receiptDocument.fileName && ( {record.receiptDocument.fileName} )} )} {/* Category */} Category {/* Subtypes */} {formData.category && ( Service Types * handleInputChange('subtypes', subtypes)} /> )} {/* Date */} handleInputChange('date', newValue?.format('YYYY-MM-DD') || '') } format="MM/DD/YYYY" slotProps={{ textField: { fullWidth: true, sx: { '& .MuiOutlinedInput-root': { minHeight: '56px', }, }, }, }} /> {/* Odometer Reading */} handleInputChange( 'odometerReading', e.target.value ? parseInt(e.target.value) : undefined ) } helperText="Current mileage" inputProps={{ min: 0 }} /> {/* Cost */} handleInputChange('cost', e.target.value ? parseFloat(e.target.value) : undefined) } helperText="Total service cost" inputProps={{ step: 0.01, min: 0 }} /> {/* Shop Name */} handleInputChange('shopName', e.target.value || undefined)} helperText="Service location" inputProps={{ maxLength: 200 }} /> {/* Notes */} handleInputChange('notes', e.target.value || undefined)} placeholder="Optional notes about this service..." inputProps={{ maxLength: 10000 }} /> ); };