; const borderColor=q.status==='accepted'?'var(--emerald)':q.status==='declined'?'var(--coral)':'var(--amber)'; return(
{showContext&&q.stepLabel&&
{q.stepLabel}{q.agentName&&` — ${q.agentName}`}
}
Quotation{q.date&& • {q.date}}
{q.status==='accepted'?'✅ Accepted':q.status==='declined'?'❌ Declined':'⏳ Pending'}
{showContext&&q.vehicleName&&
🚗 {q.vehicleName}{q.vehicleReg&&` (${q.vehicleReg})`}
} {q.items?.map((it,i)=>
{it.desc} × {it.qty}{sym}{(it.qty*it.price).toFixed(2)}
)}
Total{sym}{q.total?.toFixed(2)}
{q.notes&&
"{q.notes}"
}
{isCustomer&&q.status==='pending'&&<>}
); } function InvoiceCard({inv,onMarkPaid,onPayStripe,isAgent}){ const sym=inv.currency==='GBP'?'£':'$'; const statusColor=inv.status==='paid'?'badge-emerald':inv.status==='overdue'?'badge-coral':inv.status==='awaiting_payment'?'badge-amber':inv.status==='sent'?'badge-ocean':'badge-amber'; const borderColor=inv.status==='paid'?'var(--emerald)':inv.status==='overdue'?'var(--coral)':inv.status==='awaiting_payment'?'var(--amber)':'var(--ocean)'; const statusLabel=inv.status==='awaiting_payment'?'Awaiting Payment':inv.status; return(
{inv.title||'Invoice'}
{inv.ref} • {inv.date}
{isAgent?`To: ${inv.toName}`:`From: ${inv.fromName}`}
{statusLabel}
{inv.status==='awaiting_payment'&&!isAgent&&
⚠️ Payment required before service begins. Due: {inv.dueDate}
} {inv.items&&
{inv.items.map((it,i)=>
{it.desc}{sym}{(it.qty*it.price).toFixed(2)}
)}
}
Total{sym}{inv.total?.toFixed(2)}
{inv.notes&&
{inv.notes}
} {inv.bankDetails&&
🏦 Bank Details
{inv.bankDetails.accountName&&
Account: {inv.bankDetails.accountName}
} {inv.bankDetails.sortCode&&
Sort Code: {inv.bankDetails.sortCode}
} {inv.bankDetails.accountNumber&&
Account No: {inv.bankDetails.accountNumber}
} {inv.bankDetails.iban&&
IBAN: {inv.bankDetails.iban}
}
} {/* Paid confirmation with Stripe reference */} {inv.status==='paid'&&
✅ Paid{inv.paidAt&&on {inv.paidAt}}
{inv.paymentMethod&&Method: {inv.paymentMethod==='card'?'💳 Card':inv.paymentMethod==='balance'?'💰 Balance':inv.paymentMethod==='bank_transfer'?'🏦 Bank':'Manual'}} {inv.stripeRef&&Ref: {inv.stripeRef}} {inv.webhookEvent&&🔄 {inv.webhookEvent}}
}
{inv.status!=='paid'&&inv.status!=='cancelled'&&!isAgent&&<>{onPayStripe&&}{onMarkPaid&&}} {inv.status!=='paid'&&isAgent&&onMarkPaid&&}
); } function DocViewerModal({doc,onClose}){ const isImage=doc.name?.match(/\.(jpg|jpeg|png|gif|webp|svg)$/i)||doc.fileType==='image'; const isPdf=doc.name?.match(/\.pdf$/i)||doc.fileType==='pdf'; const hasPhotos=doc.photos&&doc.photos.length>0; const[viewIdx,setViewIdx]=useState(0); const images=hasPhotos?doc.photos:doc.imageUrl?[doc.imageUrl]:[]; return(
e.stopPropagation()} style={{maxHeight:'95vh'}}>

{isImage||hasPhotos?'🖼️':'📄'} {doc.name}

Uploaded by {doc.uploadedBy} • {doc.date||doc.uploadDate}
{doc.status} {doc.type==='agent'?'Agent':'Customer'}
{/* Image viewer with gallery */} {(isImage||hasPhotos)&&images.length>0?
{images.length>1&&<>
{viewIdx+1}/{images.length}
}
{images.length>1&&
{images.map((img,i)=>setViewIdx(i)} style={{width:64,height:64,objectFit:'cover',borderRadius:'var(--r)',cursor:'pointer',border:viewIdx===i?'2px solid var(--brand)':'2px solid var(--line)',opacity:viewIdx===i?1:.6}}/>)}
} {images[viewIdx]?.label&&
📍 {images[viewIdx].label}{images[viewIdx]?.notes?' — '+images[viewIdx].notes:''}
}
/* PDF viewer placeholder */ :isPdf?
📄 {doc.name}
📄
{doc.name}
PDF document • Click download to view full content
In production, PDF.js would render the document inline here
/* Default document viewer */ :
📄
{doc.name}
{doc.type==='agent'?'Agent Document':'Customer Document'}
} {/* Action bar */}
); } function CreateInvoiceModal({onSubmit,onClose,toName,bankDetails}){ const[items,setItems]=useState([{desc:'',qty:1,price:0}]);const[notes,setNotes]=useState('');const[currency,setCurrency]=useState('GBP');const[dueDate,setDueDate]=useState(''); const addItem=()=>setItems([...items,{desc:'',qty:1,price:0}]); const updateItem=(i,k,v)=>{const n=[...items];n[i][k]=v;setItems(n)}; const removeItem=(i)=>setItems(items.filter((_,idx)=>idx!==i)); const total=items.reduce((s,it)=>s+(it.qty*it.price),0); return(
e.stopPropagation()}>

📋 Create Invoice

To: {toName}
setDueDate(e.target.value)}/>
{items.map((it,i)=>
updateItem(i,'desc',e.target.value)} placeholder="Service..."/>
updateItem(i,'qty',parseInt(e.target.value)||0)} min="1"/>
updateItem(i,'price',parseFloat(e.target.value)||0)}/>
)}
Total{currency==='GBP'?'£':'$'}{total.toFixed(2)}