Add company tabs to client invoices page

Tabs match projects page style, sorted alphabetically. Stats (Outstanding,
Paid, Overdue) update to reflect the selected company only.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Krao Hasanee
2026-05-13 12:23:43 -04:00
parent 1003b82944
commit 476aab8ae8
+36 -6
View File
@@ -3,10 +3,15 @@ import Layout from '../../components/Layout';
import LoadingButton from '../../components/LoadingButton'; 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';
import { useAuth } from '../../context/AuthContext';
const statusColor = { draft: 'not_started', sent: 'in_progress', paid: 'client_approved' }; const statusColor = { draft: 'not_started', sent: 'in_progress', paid: 'client_approved' };
export default function MyInvoices() { export default function MyInvoices() {
const { currentUser } = useAuth();
const companies = (currentUser?.companies?.length ? currentUser.companies : (currentUser?.company ? [currentUser.company] : [])).slice().sort((a, b) => a.name.localeCompare(b.name));
const [activeCompanyId, setActiveCompanyId] = useState(companies[0]?.id || null);
const [invoices, setInvoices] = useState([]); const [invoices, setInvoices] = useState([]);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [generatingInvoiceId, setGeneratingInvoiceId] = useState(''); const [generatingInvoiceId, setGeneratingInvoiceId] = useState('');
@@ -33,19 +38,44 @@ export default function MyInvoices() {
} }
}; };
const outstanding = invoices.filter(i => i.status === 'sent').reduce((s, i) => s + Number(i.total), 0); const visible = companies.length > 1 && activeCompanyId
const paid = invoices.filter(i => i.status === 'paid').reduce((s, i) => s + Number(i.total), 0); ? invoices.filter(inv => inv.company_id === activeCompanyId)
const overdueCount = invoices.filter(inv => inv.status !== 'paid' && new Date(inv.due_date) < new Date()).length; : invoices;
const outstanding = visible.filter(i => i.status === 'sent').reduce((s, i) => s + Number(i.total), 0);
const paid = visible.filter(i => i.status === 'paid').reduce((s, i) => s + Number(i.total), 0);
const overdueCount = visible.filter(inv => inv.status !== 'paid' && new Date(inv.due_date) < new Date()).length;
return ( return (
<Layout> <Layout>
<div className="page-header"> <div className="page-header">
<div> <div>
<div className="page-title">Invoices</div> <div className="page-title">Invoices</div>
<div className="page-subtitle">{invoices.length} invoice{invoices.length !== 1 ? 's' : ''}</div> <div className="page-subtitle">{visible.length} invoice{visible.length !== 1 ? 's' : ''}</div>
</div> </div>
</div> </div>
{companies.length > 1 && (
<div style={{ marginBottom: 16, display: 'flex', alignItems: 'center', gap: 10, flexWrap: 'wrap' }} className="card-title">
{companies.map((company, index) => (
<span key={company.id} style={{ display: 'inline-flex', alignItems: 'center', gap: 10 }}>
{index > 0 && <span style={{ color: 'var(--text-muted)' }}>|</span>}
<button
type="button"
onClick={() => setActiveCompanyId(company.id)}
style={{
background: 'none', border: 'none', padding: 0, margin: 0,
cursor: 'pointer', font: 'inherit', textTransform: 'inherit', letterSpacing: 'inherit',
color: activeCompanyId === company.id ? 'var(--text-primary)' : 'var(--text-muted)',
}}
>
{company.name}
</button>
</span>
))}
</div>
)}
<div className="stats-grid" style={{ marginBottom: 24 }}> <div className="stats-grid" style={{ marginBottom: 24 }}>
<div className="stat-card stat-card-highlight"> <div className="stat-card stat-card-highlight">
<div className="stat-value" style={{ fontSize: 22 }}>${outstanding.toFixed(2)}</div> <div className="stat-value" style={{ fontSize: 22 }}>${outstanding.toFixed(2)}</div>
@@ -63,14 +93,14 @@ export default function MyInvoices() {
{loading ? ( {loading ? (
<p style={{ color: 'var(--text-muted)' }}>Loading...</p> <p style={{ color: 'var(--text-muted)' }}>Loading...</p>
) : invoices.length === 0 ? ( ) : visible.length === 0 ? (
<div className="empty-state"> <div className="empty-state">
<h3>No invoices yet</h3> <h3>No invoices yet</h3>
<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: 8 }}> <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
{invoices.map(inv => { {visible.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="interactive-surface" style={{ border: '1px solid var(--border)', borderRadius: 8, background: 'var(--card-bg)', padding: '14px 18px', display: 'flex', alignItems: 'center', gap: 16 }}> <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 }}>