import React from 'react'; import { Button } from '../../../shared-minimal/components/Button'; import { UpgradeRequiredDialog } from '../../../shared-minimal/components/UpgradeRequiredDialog'; 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 { Checkbox, FormControlLabel, LinearProgress } from '@mui/material'; import LockOutlinedIcon from '@mui/icons-material/LockOutlined'; import dayjs from 'dayjs'; import { useCreateDocument, useUpdateDocument, useAddSharedVehicle, useRemoveVehicleFromDocument } from '../hooks/useDocuments'; import { documentsApi } from '../api/documents.api'; import type { DocumentType, DocumentRecord } from '../types/documents.types'; import { useVehicles } from '../../vehicles/hooks/useVehicles'; import type { Vehicle } from '../../vehicles/types/vehicles.types'; import { useTierAccess } from '../../../core/hooks/useTierAccess'; import { useManualExtraction } from '../hooks/useManualExtraction'; import { MaintenanceScheduleReviewScreen } from '../../maintenance/components/MaintenanceScheduleReviewScreen'; interface DocumentFormProps { mode?: 'create' | 'edit'; initialValues?: Partial; documentId?: string; onSuccess?: () => void; onCancel?: () => void; } export const DocumentForm: React.FC = ({ mode = 'create', initialValues, documentId, onSuccess, onCancel }) => { const [documentType, setDocumentType] = React.useState( initialValues?.documentType || '' ); const [vehicleID, setVehicleID] = React.useState(initialValues?.vehicleId || ''); const [title, setTitle] = React.useState(initialValues?.title || ''); const [notes, setNotes] = React.useState(initialValues?.notes || ''); // Insurance fields const [insuranceCompany, setInsuranceCompany] = React.useState( initialValues?.details?.insuranceCompany || '' ); const [policyNumber, setPolicyNumber] = React.useState( initialValues?.details?.policyNumber || '' ); const [effectiveDate, setEffectiveDate] = React.useState( initialValues?.issuedDate || '' ); const [expirationDate, setExpirationDate] = React.useState( initialValues?.expirationDate || '' ); const [bodilyInjuryPerson, setBodilyInjuryPerson] = React.useState( initialValues?.details?.bodilyInjuryPerson || '' ); const [bodilyInjuryIncident, setBodilyInjuryIncident] = React.useState( initialValues?.details?.bodilyInjuryIncident || '' ); const [propertyDamage, setPropertyDamage] = React.useState( initialValues?.details?.propertyDamage || '' ); const [premium, setPremium] = React.useState( initialValues?.details?.premium ? String(initialValues.details.premium) : '' ); // Registration fields const [licensePlate, setLicensePlate] = React.useState( initialValues?.details?.licensePlate || '' ); const [registrationExpirationDate, setRegistrationExpirationDate] = React.useState( initialValues?.expirationDate || '' ); const [registrationCost, setRegistrationCost] = React.useState( initialValues?.details?.cost ? String(initialValues.details.cost) : '' ); // Manual fields const [scanForMaintenance, setScanForMaintenance] = React.useState( initialValues?.scanForMaintenance || false ); // Shared vehicles for edit mode const [selectedSharedVehicles, setSelectedSharedVehicles] = React.useState( initialValues?.sharedVehicleIds || [] ); const [file, setFile] = React.useState(null); const [uploadProgress, setUploadProgress] = React.useState(0); const [error, setError] = React.useState(null); const [upgradeDialogOpen, setUpgradeDialogOpen] = React.useState(false); const { data: vehicles } = useVehicles(); const create = useCreateDocument(); const update = useUpdateDocument(documentId || ''); const addSharedVehicle = useAddSharedVehicle(); const removeSharedVehicle = useRemoveVehicleFromDocument(); const { hasAccess } = useTierAccess(); const canScanMaintenance = hasAccess('document.scanMaintenanceSchedule'); const extraction = useManualExtraction(); const [reviewDialogOpen, setReviewDialogOpen] = React.useState(false); // Open review dialog when extraction completes React.useEffect(() => { if (extraction.status === 'completed' && extraction.result) { setReviewDialogOpen(true); } }, [extraction.status, extraction.result]); const isExtracting = extraction.status === 'pending' || extraction.status === 'processing'; const handleReviewClose = () => { setReviewDialogOpen(false); extraction.reset(); resetForm(); onSuccess?.(); }; const handleSchedulesCreated = (_count: number) => { setReviewDialogOpen(false); extraction.reset(); resetForm(); onSuccess?.(); }; const resetForm = () => { setTitle(''); setNotes(''); setInsuranceCompany(''); setPolicyNumber(''); setEffectiveDate(''); setExpirationDate(''); setBodilyInjuryPerson(''); setBodilyInjuryIncident(''); setPropertyDamage(''); setPremium(''); setLicensePlate(''); setRegistrationExpirationDate(''); setRegistrationCost(''); setScanForMaintenance(false); setSelectedSharedVehicles([]); setFile(null); setUploadProgress(0); setError(null); }; const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); setError(null); if (!vehicleID) { setError('Please select a vehicle.'); return; } if (!documentType) { setError('Please select a document type.'); return; } if (!title.trim()) { setError('Please enter a title.'); return; } try { const details: Record = {}; let issued_date: string | undefined; let expiration_date: string | undefined; if (documentType === 'insurance') { details.insuranceCompany = insuranceCompany || undefined; details.policyNumber = policyNumber || undefined; details.bodilyInjuryPerson = bodilyInjuryPerson || undefined; details.bodilyInjuryIncident = bodilyInjuryIncident || undefined; details.propertyDamage = propertyDamage || undefined; details.premium = premium ? parseFloat(premium) : undefined; issued_date = effectiveDate || undefined; expiration_date = expirationDate || undefined; } else if (documentType === 'registration') { details.licensePlate = licensePlate || undefined; details.cost = registrationCost ? parseFloat(registrationCost) : undefined; expiration_date = registrationExpirationDate || undefined; } // Manual type: no details or dates, just scanForMaintenance flag if (mode === 'edit' && documentId) { // Update existing document await update.mutateAsync({ title: title.trim(), notes: notes.trim() || null, details: Object.keys(details).length > 0 ? details : undefined, issuedDate: issued_date || null, expirationDate: expiration_date || null, scanForMaintenance: documentType === 'manual' ? scanForMaintenance : undefined, }); // Handle shared vehicles only for insurance documents if (documentType === 'insurance') { const currentSharedVehicleIds = initialValues?.sharedVehicleIds || []; // Add new shared vehicles const vehiclesToAdd = selectedSharedVehicles.filter( id => !currentSharedVehicleIds.includes(id) ); for (const vehicleId of vehiclesToAdd) { await addSharedVehicle.mutateAsync({ docId: documentId, vehicleId }); } // Remove unselected shared vehicles const vehiclesToRemove = currentSharedVehicleIds.filter( id => !selectedSharedVehicles.includes(id) ); for (const vehicleId of vehiclesToRemove) { await removeSharedVehicle.mutateAsync({ docId: documentId, vehicleId }); } } // Handle file upload if a new file was selected if (file) { const allowed = new Set(['application/pdf', 'image/jpeg', 'image/png']); if (!file.type || !allowed.has(file.type)) { setError('Unsupported file type. Allowed: PDF, JPG/JPEG, PNG.'); return; } try { await documentsApi.uploadWithProgress(documentId, file, (pct) => setUploadProgress(pct)); } catch (uploadErr: any) { const status = uploadErr?.response?.status; if (status === 415) { setError('Unsupported file type. Allowed: PDF, JPG/JPEG, PNG.'); return; } setError(uploadErr?.message || 'Failed to upload file'); return; } } onSuccess?.(); } else { // Create new document const created = await create.mutateAsync({ vehicleId: vehicleID, documentType: documentType, title: title.trim(), notes: notes.trim() || undefined, details: Object.keys(details).length > 0 ? details : undefined, issuedDate: issued_date, expirationDate: expiration_date, scanForMaintenance: documentType === 'manual' ? scanForMaintenance : undefined, }); if (file) { const allowed = new Set(['application/pdf', 'image/jpeg', 'image/png']); if (!file.type || !allowed.has(file.type)) { setError('Unsupported file type. Allowed: PDF, JPG/JPEG, PNG.'); return; } try { await documentsApi.uploadWithProgress(created.id, file, (pct) => setUploadProgress(pct)); } catch (uploadErr: any) { const status = uploadErr?.response?.status; if (status === 415) { setError('Unsupported file type. Allowed: PDF, JPG/JPEG, PNG.'); return; } setError(uploadErr?.message || 'Failed to upload file'); return; } // Trigger manual extraction if scan checkbox was checked if (scanForMaintenance && documentType === 'manual' && file.type === 'application/pdf') { try { await extraction.submit(file, vehicleID); // Don't call onSuccess yet - wait for extraction and review return; } catch (extractionErr: any) { setError(extractionErr?.message || 'Failed to start maintenance extraction'); return; } } } resetForm(); onSuccess?.(); } } catch (err: any) { const status = err?.response?.status; if (status === 415) { setError('Unsupported file type. Allowed: PDF, JPG/JPEG, PNG.'); } else { setError(err?.message || `Failed to ${mode === 'edit' ? 'update' : 'create'} document`); } } finally { setUploadProgress(0); } }; const vehicleLabel = (v: Vehicle) => { if (v.nickname && v.nickname.trim().length > 0) return v.nickname.trim(); const parts = [v.year, v.make, v.model, v.trimLevel].filter(Boolean); const primary = parts.join(' ').trim(); if (primary.length > 0) return primary; if (v.vin && v.vin.length > 0) return v.vin; return v.id.slice(0, 8) + '...'; }; // Filter out the primary vehicle from shared vehicle options const sharedVehicleOptions = (vehicles || []).filter(v => v.id !== vehicleID); const handleSharedVehicleToggle = (vehicleId: string) => { setSelectedSharedVehicles(prev => prev.includes(vehicleId) ? prev.filter(id => id !== vehicleId) : [...prev, vehicleId] ); }; return (
setTitle(e.target.value)} required />
{documentType === 'insurance' && ( <>
setInsuranceCompany(e.target.value)} />
setPolicyNumber(e.target.value)} />
setEffectiveDate(newValue?.format('YYYY-MM-DD') || '')} format="MM/DD/YYYY" slotProps={{ textField: { fullWidth: true, sx: { '& .MuiOutlinedInput-root': { minHeight: 44, }, }, }, }} />
setExpirationDate(newValue?.format('YYYY-MM-DD') || '')} format="MM/DD/YYYY" slotProps={{ textField: { fullWidth: true, sx: { '& .MuiOutlinedInput-root': { minHeight: 44, }, }, }, }} />
setBodilyInjuryPerson(e.target.value)} />
setBodilyInjuryIncident(e.target.value)} />
setPropertyDamage(e.target.value)} />
setPremium(e.target.value)} />
{mode === 'edit' && sharedVehicleOptions.length > 0 && (
{sharedVehicleOptions.map((v) => ( handleSharedVehicleToggle(v.id)} color="primary" sx={{ '& .MuiSvgIcon-root': { fontSize: 24 } }} /> } label={vehicleLabel(v)} sx={{ width: '100%', minHeight: 44, mx: 0, px: 1, borderRadius: 1, '&:hover': { bgcolor: 'action.hover', }, '& .MuiFormControlLabel-label': { fontSize: '0.875rem', color: 'text.primary', }, }} /> ))}
)} )} {documentType === 'registration' && ( <>
setLicensePlate(e.target.value)} />
setRegistrationExpirationDate(newValue?.format('YYYY-MM-DD') || '')} format="MM/DD/YYYY" slotProps={{ textField: { fullWidth: true, sx: { '& .MuiOutlinedInput-root': { minHeight: 44, }, }, }, }} />
setRegistrationCost(e.target.value)} />
)} {documentType === 'manual' && (
canScanMaintenance && setScanForMaintenance(e.target.checked)} disabled={!canScanMaintenance} color="primary" sx={{ '& .MuiSvgIcon-root': { fontSize: 24 } }} /> } label="Scan for Maintenance Schedule" sx={{ opacity: canScanMaintenance ? 1 : 0.6, cursor: canScanMaintenance ? 'pointer' : 'not-allowed', '& .MuiFormControlLabel-label': { fontSize: '0.875rem', fontWeight: 500, color: 'text.primary', }, }} /> {!canScanMaintenance && ( )} {canScanMaintenance && scanForMaintenance && ( PDF will be scanned after upload )}
)}