import { useState, useEffect } from 'react'; import { useParams, useNavigate } from 'react-router-dom'; import Layout from '../../components/Layout'; import PageLoader from '../../components/PageLoader'; import LoadingButton from '../../components/LoadingButton'; import { supabase } from '../../lib/supabase'; import { generateSubcontractorPOPDF } from '../../lib/invoice'; import { blobToEmailAttachment, sendEmail } from '../../lib/email'; import { useSortable } from '../../hooks/useSortable'; import SubcontractorInvoiceDetailView from '../../components/SubcontractorInvoiceDetailView'; export default function SubInvoiceDetail() { const { id } = useParams(); const navigate = useNavigate(); const [invoice, setInvoice] = useState(null); const [loading, setLoading] = useState(true); const [saving, setSaving] = useState(false); const [generating, setGenerating] = useState(false); const [error, setError] = useState(''); const { sortKey, sortDir, toggle, sort } = useSortable('description'); useEffect(() => { supabase .from('subcontractor_invoices') .select('*, profile:profiles!subcontractor_invoices_profile_id_fkey(id, name, email), items:subcontractor_invoice_items(*)') .eq('id', id) .single() .then(({ data, error: err }) => { if (err || !data) setError('Invoice not found.'); setInvoice(data || null); setLoading(false); }); }, [id]); const total = (invoice?.items || []).reduce((sum, item) => sum + Number(item.unit_price || 0) * Number(item.quantity || 1), 0); const sortedItems = [...(invoice?.items || [])].sort((a, b) => (a.sort_order ?? 0) - (b.sort_order ?? 0)); const tableItems = sort(sortedItems, (item, key) => { const typeSort = /\bR(\d{2})\b/i.test(String(item.description || '')) ? Number(String(item.description).match(/\bR(\d{2})\b/i)?.[1] || 0) : item.task_id ? 0 : 999; if (key === 'type') return typeSort; if (key === 'description') return item.description || ''; if (key === 'quantity') return Number(item.quantity || 0); if (key === 'unit_price') return Number(item.unit_price || 0); if (key === 'line_total') return Number(item.unit_price || 0) * Number(item.quantity || 1); return ''; }); const buildPDFArgs = (inv) => ({ po_number: inv.invoice_number, status: 'paid', profile: inv.profile, project: { name: 'Subcontractor Invoice', company: { name: 'Fourge Branding' } }, date: inv.created_at?.split('T')[0], due_date: (inv.paid_at || new Date().toISOString()).split('T')[0], amount: total, description: 'Payment for completed subcontractor work.', notes: inv.notes, items: (inv.items || []).map((item, idx) => ({ description: item.description, amount: Number(item.unit_price) * Number(item.quantity || 1), sort_order: item.sort_order ?? idx, })), }); const handleMarkPaid = async () => { if (!invoice) return; setSaving(true); try { const paidAt = new Date().toISOString(); const { error: updateError } = await supabase.from('subcontractor_invoices').update({ status: 'paid', paid_at: paidAt }).eq('id', id); if (updateError) throw updateError; const updated = { ...invoice, status: 'paid', paid_at: paidAt }; setInvoice(updated); let pdfBlob = null; try { pdfBlob = await generateSubcontractorPOPDF(buildPDFArgs(updated), { output: 'blob' }); } catch { /* PDF generation failed — continue without attachment */ } if (invoice.profile?.email) { try { const attachments = pdfBlob ? [await blobToEmailAttachment(pdfBlob, `${invoice.invoice_number}-receipt.pdf`)] : []; await sendEmail('receipt_sent', invoice.profile.email, { invoiceNumber: invoice.invoice_number, billTo: invoice.profile.name || invoice.profile.email, total: total.toFixed(2), paidDate: new Date(paidAt).toLocaleDateString('en-US', { month: 'long', day: 'numeric', year: 'numeric' }), }, attachments); } catch (emailErr) { setError(`Marked paid, but email failed: ${emailErr.message || 'unknown error'}`); } } } catch (err) { setError(`Failed to mark as paid: ${err.message}`); } setSaving(false); }; const handleReceipt = async () => { if (!invoice) return; setGenerating(true); try { await generateSubcontractorPOPDF(buildPDFArgs(invoice)); } catch (err) { setError(err.message); } setGenerating(false); }; const handleDelete = async () => { if (!invoice) return; if (!window.confirm(`Delete invoice ${invoice.invoice_number}? This cannot be undone.`)) return; setSaving(true); const { error: deleteError } = await supabase.from('subcontractor_invoices').delete().eq('id', id); if (deleteError) { setError(`Failed to delete: ${deleteError.message}`); setSaving(false); return; } navigate('/finances', { state: { tab: 'sub-invoices' } }); }; if (loading) return ; if (!invoice) return

{error || 'Invoice not found.'}

; const headerActions = ( <> {invoice.status === 'submitted' ? ( Mark as Paid ) : null} {invoice.status === 'paid' ? ( Download Receipt ) : null} ); return (
{invoice.profile?.name || 'External'}
{invoice.profile?.email || '—'}
)} rightCardTitle="Invoice Details" rightCardBody={(

{invoice.invoice_number}

{invoice.status ? invoice.status.charAt(0).toUpperCase() + invoice.status.slice(1) : '—'}

{invoice.submitted_at ? new Date(invoice.submitted_at).toLocaleDateString() : '—'}

{invoice.paid_at ?

{new Date(invoice.paid_at).toLocaleDateString()}

: null}

{sortedItems.length}

${total.toFixed(2)}

)} sortKey={sortKey} sortDir={sortDir} onSort={toggle} items={tableItems} notes={invoice.notes} total={total} />
); }