Simplify client invoice list: remove summary stats, strip card to essentials

Shows invoice number, issue/due date, overdue flag, item count, status, download PDF.
Removes outstanding/paid/overdue summary grid and total amount display.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Krao Hasanee
2026-05-13 12:10:28 -04:00
parent 54ceb69dd0
commit 19e7ae48af
+18 -31
View File
@@ -1,5 +1,6 @@
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import Layout from '../../components/Layout'; import Layout from '../../components/Layout';
import LoadingButton from '../../components/LoadingButton';
import { supabase } from '../../lib/supabase'; import { supabase } from '../../lib/supabase';
import { generateInvoicePDF } from '../../lib/invoice'; import { generateInvoicePDF } from '../../lib/invoice';
@@ -8,6 +9,7 @@ const statusColor = { draft: 'not_started', sent: 'in_progress', paid: 'client_a
export default function MyInvoices() { export default function MyInvoices() {
const [invoices, setInvoices] = useState([]); const [invoices, setInvoices] = useState([]);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [generatingInvoiceId, setGeneratingInvoiceId] = useState('');
useEffect(() => { useEffect(() => {
async function load() { async function load() {
@@ -22,12 +24,15 @@ export default function MyInvoices() {
}, []); }, []);
const handleDownload = async (invoice) => { const handleDownload = async (invoice) => {
if (generatingInvoiceId) return;
setGeneratingInvoiceId(invoice.id);
try {
await generateInvoicePDF(invoice, invoice.company, invoice.items || []); await generateInvoicePDF(invoice, invoice.company, invoice.items || []);
} finally {
setGeneratingInvoiceId('');
}
}; };
const outstanding = invoices.filter(i => i.status === 'sent').reduce((s, i) => s + Number(i.total), 0);
const paid = invoices.filter(i => i.status === 'paid').reduce((s, i) => s + Number(i.total), 0);
return ( return (
<Layout> <Layout>
<div className="page-header"> <div className="page-header">
@@ -37,17 +42,6 @@ export default function MyInvoices() {
</div> </div>
</div> </div>
<div className="stats-grid" style={{ marginBottom: 24 }}>
<div className="stat-card">
<div className="stat-value" style={{ fontSize: 22 }}>${outstanding.toFixed(2)}</div>
<div className="stat-label">Outstanding</div>
</div>
<div className="stat-card">
<div className="stat-value" style={{ fontSize: 22 }}>${paid.toFixed(2)}</div>
<div className="stat-label">Paid</div>
</div>
</div>
{loading ? ( {loading ? (
<p style={{ color: 'var(--text-muted)' }}>Loading...</p> <p style={{ color: 'var(--text-muted)' }}>Loading...</p>
) : invoices.length === 0 ? ( ) : invoices.length === 0 ? (
@@ -56,35 +50,28 @@ export default function MyInvoices() {
<p>Your invoices will appear here once they are sent.</p> <p>Your invoices will appear here once they are sent.</p>
</div> </div>
) : ( ) : (
<div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}> <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
{invoices.map(inv => { {invoices.map(inv => {
const isOverdue = inv.status !== 'paid' && new Date(inv.due_date) < new Date(); const isOverdue = inv.status !== 'paid' && new Date(inv.due_date) < new Date();
return ( return (
<div key={inv.id} className="request-card"> <div key={inv.id} className="interactive-surface" style={{ border: '1px solid var(--border)', borderRadius: 8, background: 'var(--card-bg)', padding: '14px 18px', display: 'flex', alignItems: 'center', gap: 16, flexWrap: 'wrap' }}>
<div className="request-card-header"> <div style={{ flex: 1, minWidth: 0 }}>
<div> <div style={{ fontWeight: 600, fontSize: 14, color: 'var(--text-primary)', marginBottom: 4 }}>{inv.invoice_number}</div>
<div className="request-card-title">{inv.invoice_number}</div> <div style={{ fontSize: 12, color: 'var(--text-muted)' }}>
<div className="request-card-meta">
Issued {new Date(inv.invoice_date).toLocaleDateString()} · Due{' '} Issued {new Date(inv.invoice_date).toLocaleDateString()} · Due{' '}
<span style={{ color: isOverdue ? 'var(--danger)' : 'inherit' }}> <span style={{ color: isOverdue ? 'var(--danger)' : 'inherit' }}>
{new Date(inv.due_date).toLocaleDateString()} {new Date(inv.due_date).toLocaleDateString()}
</span> </span>
{isOverdue && ' · Overdue'} {isOverdue && ' · Overdue'}
{inv.items?.length > 0 && ` · ${inv.items.length} item${inv.items.length !== 1 ? 's' : ''}`}
</div> </div>
</div> </div>
<div style={{ display: 'flex', alignItems: 'center', gap: 10 }}> <div style={{ display: 'flex', alignItems: 'center', gap: 10, flexShrink: 0 }}>
<span className={`badge badge-${statusColor[inv.status]}`} style={{ textTransform: 'capitalize' }}>{inv.status}</span> <span className={`badge badge-${statusColor[inv.status]}`} style={{ textTransform: 'capitalize' }}>{inv.status}</span>
<div style={{ fontSize: 18, fontWeight: 700, color: 'var(--accent)' }}>${Number(inv.total).toFixed(2)}</div> <LoadingButton className="btn btn-outline btn-sm" loading={generatingInvoiceId === inv.id} disabled={Boolean(generatingInvoiceId)} loadingText="Generating..." onClick={() => handleDownload(inv)}>
</div>
</div>
{inv.items && inv.items.length > 0 && (
<div style={{ fontSize: 12, color: 'var(--text-muted)', marginBottom: 10 }}>
{inv.items.map(i => i.description).join(' · ')}
</div>
)}
<button className="btn btn-outline btn-sm" onClick={() => handleDownload(inv)}>
Download PDF Download PDF
</button> </LoadingButton>
</div>
</div> </div>
); );
})} })}