From c3d1d86b07bcc4b741beacf6b33116245732f605 Mon Sep 17 00:00:00 2001 From: renee-png Date: Wed, 3 Jun 2026 00:47:11 -0400 Subject: [PATCH] Reports: account drill-down to GL; bids/quotes PDF attachments - P&L and Balance Sheet account rows are now clickable and open the General Ledger filtered to that account (its transaction list for the COA). Adds StructuredRow.accountId, threaded from the report builders, with a clickable row in StructuredTable and an initialAccountId prop on GeneralLedgerReport. - Bids/Quotes: PDF upload on the New Bid/Quote dialog (bid-attachments bucket + document_url/document_name columns), shown as a link on the bid Details dialog. Migration applied: bids_quotes_pdf_attachments. Co-Authored-By: Claude Opus 4.8 --- src/components/BidQuoteDetailsDialog.jsx | 8 +++ src/components/BidQuoteDialog.jsx | 55 ++++++++++++++++++- .../accounting/AccountingReportsPage.tsx | 27 ++++++--- .../components/GeneralLedgerReport.tsx | 11 +++- src/pages/accounting/lib/reportPdf.ts | 2 + 5 files changed, 90 insertions(+), 13 deletions(-) diff --git a/src/components/BidQuoteDetailsDialog.jsx b/src/components/BidQuoteDetailsDialog.jsx index 6fb9e66..9a0121c 100644 --- a/src/components/BidQuoteDetailsDialog.jsx +++ b/src/components/BidQuoteDetailsDialog.jsx @@ -94,6 +94,14 @@ export function BidQuoteDetailsDialog({ open, onOpenChange, bid, onRefresh }) {
Status: {bid.status}
{bid.received_date &&
Received: {format(new Date(bid.received_date), 'MMM d, yyyy')}
} + {bid.document_url && ( +
+ + đź“„ {bid.document_name || 'View attached PDF'} + +
+ )} diff --git a/src/components/BidQuoteDialog.jsx b/src/components/BidQuoteDialog.jsx index c9e10ce..1069988 100644 --- a/src/components/BidQuoteDialog.jsx +++ b/src/components/BidQuoteDialog.jsx @@ -38,6 +38,9 @@ export function BidQuoteDialog({ open, onOpenChange, onSuccess }) { const [associations, setAssociations] = useState([]); const [selectedAssociations, setSelectedAssociations] = useState([]); const [uploading, setUploading] = useState(false); + const [fileUploading, setFileUploading] = useState(false); + const [documentUrl, setDocumentUrl] = useState(''); + const [documentName, setDocumentName] = useState(''); const { user } = useAuth(); const { toast } = useToast(); @@ -55,9 +58,34 @@ export function BidQuoteDialog({ open, onOpenChange, onSuccess }) { fetchAssociations(); form.reset(); setSelectedAssociations([]); + setDocumentUrl(''); + setDocumentName(''); } }, [open, form]); + const handleFileUpload = async (e) => { + const f = e.target.files?.[0]; + if (!f) return; + if (f.type !== 'application/pdf') { + toast({ variant: 'destructive', title: 'PDF only', description: 'Please upload a PDF file.' }); + return; + } + setFileUploading(true); + try { + const path = `${user?.id || 'anon'}/${Date.now()}-${f.name.replace(/[^a-zA-Z0-9._-]/g, '_')}`; + const { error } = await supabase.storage.from('bid-attachments').upload(path, f, { upsert: true }); + if (error) throw error; + const { data } = supabase.storage.from('bid-attachments').getPublicUrl(path); + setDocumentUrl(data.publicUrl); + setDocumentName(f.name); + toast({ title: 'PDF attached', description: f.name }); + } catch (err) { + toast({ variant: 'destructive', title: 'Upload failed', description: err.message }); + } finally { + setFileUploading(false); + } + }; + const fetchAssociations = async () => { try { const { data, error } = await supabase @@ -91,7 +119,9 @@ export function BidQuoteDialog({ open, onOpenChange, onSuccess }) { amount: values.amount || 0, association_id: assocId, created_by: user?.id, - status: 'pending' + status: 'pending', + document_url: documentUrl || null, + document_name: documentName || null, })); const { error } = await supabase.from('bids_quotes').insert(inserts); @@ -191,6 +221,27 @@ export function BidQuoteDialog({ open, onOpenChange, onSuccess }) { )} /> +
+ Attachment (PDF) + {documentName ? ( +
+ + {documentName} + + +
+ ) : ( + + )} +
+
Assign to Associations @@ -232,7 +283,7 @@ export function BidQuoteDialog({ open, onOpenChange, onSuccess }) { -