Files
personal-finance/frontend-web/src/pages/ClientsPage.tsx
Alexander Zinn 043f0bd316 Enhance frontend-web with new features and dependencies
- Added new font support for 'Oxanium' and integrated Radix UI components including Dialog and Select.
- Updated CSS to set the default font to 'Oxanium Variable' and adjusted HTML font size.
- Introduced AddAccountDialog component for managing debt accounts, enhancing user experience.
- Refactored DebtsPage to utilize the new AddAccountDialog and improved account management features.
- Updated Redux store to support debt categories and accounts, including actions for adding, updating, and removing accounts.
- Mock data added for clients and invoices to facilitate development and testing.
2025-12-07 11:10:33 -05:00

95 lines
3.9 KiB
TypeScript

import {Card, CardContent, CardHeader, CardTitle} from '@/components/ui/card';
import {Button} from '@/components/ui/button';
import {useAppSelector} from '@/store';
export default function ClientsPage() {
const {clients, invoices} = useAppSelector(state => state.invoices);
const getClientStats = (clientId: string) => {
const clientInvoices = invoices.filter(i => i.clientId === clientId);
const totalBilled = clientInvoices.reduce((sum, i) => sum + i.total, 0);
const outstanding = clientInvoices.filter(i => i.status === 'sent' || i.status === 'overdue').reduce((sum, i) => sum + i.total, 0);
return {totalBilled, outstanding, count: clientInvoices.length};
};
const fmt = (value: number) =>
new Intl.NumberFormat('en-US', {style: 'currency', currency: 'USD', maximumFractionDigits: 0}).format(value);
return (
<div className="p-6">
{/* Header */}
<div className="mb-5 flex items-center justify-between">
<h1 className="text-xl font-medium">Clients</h1>
<Button size="sm">Add Client</Button>
</div>
{/* Summary Cards */}
<div className="mb-5 grid grid-cols-2 gap-4">
<Card className="card-elevated">
<CardHeader className="pb-1 pt-3 px-4">
<p className="text-xs text-muted-foreground">Total Clients</p>
</CardHeader>
<CardContent className="pb-3 px-4">
<p className="text-xl font-semibold">{clients.length}</p>
</CardContent>
</Card>
<Card className="card-elevated">
<CardHeader className="pb-1 pt-3 px-4">
<p className="text-xs text-muted-foreground">With Active Invoices</p>
</CardHeader>
<CardContent className="pb-3 px-4">
<p className="text-xl font-semibold">
{clients.filter(c => getClientStats(c.id).count > 0).length}
</p>
</CardContent>
</Card>
</div>
{/* Clients List */}
<Card className="card-elevated">
<CardHeader className="pb-2 pt-3 px-4">
<CardTitle className="text-sm font-medium">All Clients</CardTitle>
</CardHeader>
<CardContent className="px-4 pb-3">
{clients.length === 0 ? (
<div className="py-8 text-center">
<p className="text-muted-foreground">No clients yet</p>
<Button size="sm" className="mt-3">Add Client</Button>
</div>
) : (
<div className="divide-y divide-border rounded-md border border-border">
{clients.map(client => {
const stats = getClientStats(client.id);
return (
<div key={client.id} className="flex items-center justify-between px-3 py-2.5">
<div>
<p className="text-sm font-medium">{client.name}</p>
<p className="text-xs text-muted-foreground">
{client.email}{client.company && ` · ${client.company}`}
</p>
</div>
<div className="flex items-center gap-6 text-right">
<div>
<p className="text-xs text-muted-foreground">Billed</p>
<p className="text-sm font-medium">{fmt(stats.totalBilled)}</p>
</div>
<div>
<p className="text-xs text-muted-foreground">Outstanding</p>
<p className="text-sm font-medium">{fmt(stats.outstanding)}</p>
</div>
<div>
<p className="text-xs text-muted-foreground">Invoices</p>
<p className="text-sm font-medium">{stats.count}</p>
</div>
</div>
</div>
);
})}
</div>
)}
</CardContent>
</Card>
</div>
);
}