Files
fourge-portal/src/pages/external/MyInvoices.jsx
T

94 lines
3.7 KiB
React

import { useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import Layout from '../../components/Layout';
import StatusBadge from '../../components/StatusBadge';
import { supabase } from '../../lib/supabase';
import { useAuth } from '../../context/AuthContext';
import { readPageCache, writePageCache } from '../../lib/pageCache';
const STATUS_BADGE = { draft: 'not_started', submitted: 'in_progress', paid: 'client_approved' };
const STATUS_LABEL = { draft: 'Draft', submitted: 'Submitted', paid: 'Paid' };
function fmt(val) {
return `$${Number(val || 0).toFixed(2)}`;
}
function invoiceTotal(items) {
return (items || []).reduce((s, i) => s + Number(i.unit_price || 0) * Number(i.quantity || 1), 0);
}
export default function MyInvoices() {
const { currentUser } = useAuth();
const navigate = useNavigate();
const cacheKey = `ext-invoices:${currentUser?.id}`;
const cached = readPageCache(cacheKey, 3 * 60_000);
const [invoices, setInvoices] = useState(() => cached || []);
const [loading, setLoading] = useState(() => !cached);
const [error, setError] = useState('');
useEffect(() => {
if (!currentUser?.id) { setLoading(false); return; }
supabase
.from('subcontractor_invoices')
.select('*, items:subcontractor_invoice_items(*)')
.order('created_at', { ascending: false })
.then(({ data, error: err }) => {
if (err) setError(err.message);
else { setInvoices(data || []); writePageCache(cacheKey, data || []); }
setLoading(false);
});
}, [currentUser?.id]); // eslint-disable-line react-hooks/exhaustive-deps
return (
<Layout>
<div className="page-header">
<div>
<div className="page-title">Invoices</div>
<div className="page-subtitle">Submit invoices to Fourge Branding for your completed work.</div>
<div style={{ fontSize: 12, color: 'var(--text-muted)', marginTop: 4 }}>Payment terms: NET 30 from the date Fourge receives payment from the client.</div>
</div>
<button className="btn btn-primary" onClick={() => navigate('/my-invoices-sub/new')} disabled={loading}>
+ New Invoice
</button>
</div>
{error && <div className="notification notification-info" style={{ marginBottom: 16 }}>{error}</div>}
{loading ? (
<div className="empty-state">Loading invoices...</div>
) : invoices.length === 0 ? (
<div className="empty-state">
<h3>No invoices yet</h3>
<p>Create your first invoice to get paid for your completed work.</p>
</div>
) : (
<div className="table-wrapper">
<table>
<thead>
<tr>
<th>Invoice #</th>
<th>Submitted</th>
<th>Status</th>
<th style={{ textAlign: 'right' }}>Total</th>
</tr>
</thead>
<tbody>
{invoices.map(inv => {
const total = invoiceTotal(inv.items);
return (
<tr key={inv.id} style={{ cursor: 'pointer' }} onClick={() => navigate(`/my-invoices-sub/${inv.id}`)}>
<td style={{ fontWeight: 700 }}>{inv.invoice_number}</td>
<td>{inv.submitted_at ? new Date(inv.submitted_at).toLocaleDateString() : <span style={{ color: 'var(--text-muted)' }}></span>}</td>
<td><StatusBadge status={STATUS_BADGE[inv.status]} label={STATUS_LABEL[inv.status]} /></td>
<td style={{ textAlign: 'right', fontWeight: 700 }}>{fmt(total)}</td>
</tr>
);
})}
</tbody>
</table>
</div>
)}
</Layout>
);
}