import { useState, useCallback, useRef, useEffect } from 'react'; import { useNavigate } from 'react-router-dom'; import { useMutation } from '@tanstack/react-query'; import { Box, Typography, Card, CardContent, TextField, Button, Grid, MenuItem, Autocomplete, CircularProgress, } from '@mui/material'; import SendIcon from '@mui/icons-material/Send'; import { toast } from 'react-toastify'; import { proposalsApi, type CreateProposalRequest } from '../../../lib/api/proposals'; import { sitesApi, type Site } from '../../../lib/api/sites'; import { SERVICE_CATEGORIES, PRIORITIES, PRIORITY_LABELS } from '../../../constants'; import { queryClient } from '../../../lib/queryClient'; const CUSTOMERS = ['Amazon Services, LLC'] as const; export default function ProposalFormPage() { const navigate = useNavigate(); const [form, setForm] = useState({ workOrderNumber: '', customerName: CUSTOMERS[0], customerAddress: '', scopeOfWork: '', serviceCategory: '', priority: 'Standard', notes: '', }); const [vendorFile, setVendorFile] = useState(null); const [siteOptions, setSiteOptions] = useState([]); const [siteLoading, setSiteLoading] = useState(false); const [siteInputValue, setSiteInputValue] = useState(''); const [manualEntry, setManualEntry] = useState(false); const [manualSiteCode, setManualSiteCode] = useState(''); const [manualStreet, setManualStreet] = useState(''); const [manualCity, setManualCity] = useState(''); const [manualState, setManualState] = useState(''); const [manualZip, setManualZip] = useState(''); const [customCategory, setCustomCategory] = useState(''); const siteDebounceRef = useRef | null>(null); useEffect(() => { return () => { if (siteDebounceRef.current) clearTimeout(siteDebounceRef.current); }; }, []); useEffect(() => { if (!manualEntry) return; const addressParts = [ manualStreet, manualCity, [manualState, manualZip].filter(Boolean).join(' '), ].filter(Boolean); const address = addressParts.join(', '); const display = manualSiteCode && address ? `${manualSiteCode} — ${address}` : manualSiteCode || address; setForm((prev) => ({ ...prev, customerAddress: display })); }, [manualEntry, manualSiteCode, manualStreet, manualCity, manualState, manualZip]); const handleChange = (field: keyof CreateProposalRequest, value: string) => { setForm((prev) => ({ ...prev, [field]: value })); }; const searchSites = useCallback(async (query: string) => { if (query.length < 2) { setSiteOptions([]); return; } setSiteLoading(true); try { const results = await sitesApi.search(query); setSiteOptions(results); } catch { setSiteOptions([]); } finally { setSiteLoading(false); } }, []); const createMutation = useMutation({ mutationFn: async () => { const submitData = { ...form }; if (form.serviceCategory === 'Other' && customCategory.trim()) { submitData.notes = `[Category: ${customCategory.trim()}]\n${form.notes || ''}`.trim(); } const proposal = await proposalsApi.create(submitData); if (vendorFile) { try { const { uploadUrl, vendorProposalId } = await proposalsApi.uploadAttachment(proposal.id, vendorFile.name); await fetch(uploadUrl, { method: 'PUT', body: vendorFile, headers: { 'Content-Type': vendorFile.type || 'application/pdf' }, }); await proposalsApi.confirmUpload(proposal.id, vendorProposalId); } catch { toast.warning(`Proposal ${proposal.proposalNumber} was created, but vendor PDF upload failed. You can re-upload from the proposal detail page.`); } } return proposal; }, onSuccess: (proposal) => { queryClient.invalidateQueries({ queryKey: ['proposals'] }); toast.success(`Proposal ${proposal.proposalNumber} submitted`); navigate(`/proposals/${proposal.id}`); }, onError: (error: Error) => { toast.error(error.message || 'Failed to submit proposal'); }, }); const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); createMutation.mutate(); }; const isValid = form.workOrderNumber.trim() && form.customerName.trim() && form.customerAddress.trim() && form.scopeOfWork.trim() && form.serviceCategory && (form.serviceCategory !== 'Other' || customCategory.trim()); return ( New Proposal Request handleChange('workOrderNumber', e.target.value)} /> handleChange('poNumber', e.target.value)} /> handleChange('customerName', e.target.value)} > {CUSTOMERS.map((name) => ( {name} ))} {manualEntry ? ( setManualSiteCode(e.target.value)} placeholder="e.g. LGB8" /> setManualStreet(e.target.value)} /> setManualCity(e.target.value)} /> setManualState(e.target.value)} placeholder="e.g. CA" /> setManualZip(e.target.value)} /> ) : ( x} getOptionLabel={(opt) => opt.fullAddress ? `${opt.siteCode} — ${opt.fullAddress}` : opt.siteCode } loading={siteLoading} inputValue={siteInputValue} onInputChange={(_, value, reason) => { if (reason === 'input') { setSiteInputValue(value); if (siteDebounceRef.current) clearTimeout(siteDebounceRef.current); siteDebounceRef.current = setTimeout(() => searchSites(value), 300); } }} onChange={(_, value) => { if (value) { const display = value.fullAddress ? `${value.siteCode} — ${value.fullAddress}` : value.siteCode; handleChange('customerAddress', display); setSiteInputValue(display); } else { handleChange('customerAddress', ''); setSiteInputValue(''); } }} noOptionsText={ siteLoading ? 'Searching...' : siteInputValue.length >= 2 ? `No verified sites found for "${siteInputValue}"` : 'Type a site code to search (e.g. LGB8)' } renderInput={(params) => ( {siteLoading && } {params.InputProps.endAdornment} ), }, }} /> )} /> )} handleChange('serviceCategory', e.target.value)} > {SERVICE_CATEGORIES.map((cat) => ( {cat} ))} {form.serviceCategory === 'Other' ? ( setCustomCategory(e.target.value)} placeholder="e.g. Fire Protection" /> ) : ( )} handleChange('priority', e.target.value)} > {PRIORITIES.map((p) => ( {PRIORITY_LABELS[p] || p} ))} handleChange('scopeOfWork', e.target.value)} placeholder="Describe the work needed..." /> handleChange('notes', e.target.value)} /> ); }