Refactor and enhance various pages for improved UI and functionality
- Updated CashflowPage to streamline calculations and improve layout, including inline summaries for income, expenses, and savings. - Refined ClientsPage to enhance client management with a more organized grid layout and improved client statistics display. - Enhanced DebtsPage with a new view mode toggle and refined debt account presentation for better user experience. - Improved InvoicesPage with a summary of overdue, sent, and draft invoices, along with a more detailed display of recent transactions. - Updated NetWorthPage to include quick stats and a more visually appealing layout for assets and liabilities.
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import {Card, CardContent, CardHeader, CardTitle} from '@/components/ui/card';
|
||||
import {Button} from '@/components/ui/button';
|
||||
import {useAppSelector} from '@/store';
|
||||
import {AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer} from 'recharts';
|
||||
import {AreaChart, Area, XAxis, YAxis, Tooltip, ResponsiveContainer} from 'recharts';
|
||||
import {format} from 'date-fns';
|
||||
|
||||
export default function NetWorthPage() {
|
||||
@@ -20,118 +20,137 @@ export default function NetWorthPage() {
|
||||
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">Net Worth</h1>
|
||||
<div className="p-4">
|
||||
{/* Header + Summary inline */}
|
||||
<div className="mb-4 flex items-center justify-between">
|
||||
<div className="flex items-center gap-6">
|
||||
<h1 className="text-lg font-semibold">Net Worth</h1>
|
||||
<div className="flex gap-4 text-sm">
|
||||
<div><span className="text-muted-foreground">Assets</span> <span className="font-medium">{fmt(totalAssets)}</span></div>
|
||||
<div><span className="text-muted-foreground">Liabilities</span> <span className="font-medium">{fmt(totalLiabilities)}</span></div>
|
||||
<div><span className="text-muted-foreground">Net</span> <span className="font-semibold text-base">{fmt(netWorth)}</span></div>
|
||||
</div>
|
||||
</div>
|
||||
<Button size="sm">Record Snapshot</Button>
|
||||
</div>
|
||||
|
||||
{/* Summary Cards */}
|
||||
<div className="mb-5 grid grid-cols-3 gap-4">
|
||||
<Card className="card-elevated">
|
||||
<CardHeader className="pb-1 pt-3 px-4">
|
||||
<p className="text-xs text-muted-foreground">Total Assets</p>
|
||||
</CardHeader>
|
||||
<CardContent className="pb-3 px-4">
|
||||
<p className="text-xl font-semibold">{fmt(totalAssets)}</p>
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
{/* Chart - spans 2 cols */}
|
||||
<Card className="card-elevated col-span-2">
|
||||
<CardContent className="p-3">
|
||||
<div className="h-[200px]">
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<AreaChart data={chartData}>
|
||||
<defs>
|
||||
<linearGradient id="netWorthGradient" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0%" stopColor="oklch(0.7 0.08 260)" stopOpacity={0.3} />
|
||||
<stop offset="100%" stopColor="oklch(0.7 0.08 260)" stopOpacity={0} />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<XAxis dataKey="month" stroke="oklch(0.5 0 0)" tick={{fill: 'oklch(0.5 0 0)', fontSize: 10}} axisLine={false} tickLine={false} />
|
||||
<YAxis stroke="oklch(0.5 0 0)" tick={{fill: 'oklch(0.5 0 0)', fontSize: 10}} tickFormatter={v => `$${v / 1000}k`} axisLine={false} tickLine={false} width={45} />
|
||||
<Tooltip
|
||||
contentStyle={{background: 'oklch(0.28 0.01 260)', border: '1px solid oklch(1 0 0 / 0.1)', borderRadius: '6px', fontSize: 12}}
|
||||
labelStyle={{color: 'oklch(0.9 0 0)'}}
|
||||
formatter={(value: number) => [fmt(value), 'Net Worth']}
|
||||
/>
|
||||
<Area type="monotone" dataKey="netWorth" stroke="oklch(0.7 0.08 260)" strokeWidth={2} fill="url(#netWorthGradient)" />
|
||||
</AreaChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card className="card-elevated">
|
||||
<CardHeader className="pb-1 pt-3 px-4">
|
||||
<p className="text-xs text-muted-foreground">Total Liabilities</p>
|
||||
</CardHeader>
|
||||
<CardContent className="pb-3 px-4">
|
||||
<p className="text-xl font-semibold">{fmt(totalLiabilities)}</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card className="card-elevated">
|
||||
<CardHeader className="pb-1 pt-3 px-4">
|
||||
<p className="text-xs text-muted-foreground">Net Worth</p>
|
||||
</CardHeader>
|
||||
<CardContent className="pb-3 px-4">
|
||||
<p className="text-xl font-semibold">{fmt(netWorth)}</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Chart */}
|
||||
<Card className="card-elevated mb-5">
|
||||
<CardHeader className="pb-2 pt-3 px-4">
|
||||
<CardTitle className="text-sm font-medium">Net Worth Over Time</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="px-4 pb-3">
|
||||
<div className="h-[220px]">
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<AreaChart data={chartData}>
|
||||
<defs>
|
||||
<linearGradient id="netWorthGradient" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0%" stopColor="oklch(0.7 0 0)" stopOpacity={0.3} />
|
||||
<stop offset="100%" stopColor="oklch(0.7 0 0)" stopOpacity={0} />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<CartesianGrid strokeDasharray="3 3" stroke="oklch(0.26 0 0)" />
|
||||
<XAxis dataKey="month" stroke="oklch(0.55 0 0)" tick={{fill: 'oklch(0.55 0 0)', fontSize: 11}} />
|
||||
<YAxis stroke="oklch(0.55 0 0)" tick={{fill: 'oklch(0.55 0 0)', fontSize: 11}} tickFormatter={v => `$${v / 1000}k`} />
|
||||
<Tooltip
|
||||
contentStyle={{background: 'oklch(0.18 0 0)', border: '1px solid oklch(0.26 0 0)', borderRadius: '6px', fontSize: 12}}
|
||||
labelStyle={{color: 'oklch(0.92 0 0)'}}
|
||||
formatter={(value: number) => [fmt(value), 'Net Worth']}
|
||||
/>
|
||||
<Area type="monotone" dataKey="netWorth" stroke="oklch(0.85 0 0)" strokeWidth={2} fill="url(#netWorthGradient)" />
|
||||
</AreaChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
{/* Quick stats */}
|
||||
<div className="space-y-3">
|
||||
<Card className="card-elevated">
|
||||
<CardContent className="p-3">
|
||||
<p className="text-xs text-muted-foreground mb-1">Monthly Change</p>
|
||||
<p className="text-lg font-semibold text-green-400">+$7,500</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card className="card-elevated">
|
||||
<CardContent className="p-3">
|
||||
<p className="text-xs text-muted-foreground mb-1">YTD Growth</p>
|
||||
<p className="text-lg font-semibold">+23.8%</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card className="card-elevated">
|
||||
<CardContent className="p-3">
|
||||
<p className="text-xs text-muted-foreground mb-1">Accounts</p>
|
||||
<p className="text-lg font-semibold">{assets.length + liabilities.length}</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Assets & Liabilities */}
|
||||
<div className="grid gap-4 md:grid-cols-2">
|
||||
{/* Assets */}
|
||||
<Card className="card-elevated">
|
||||
<CardHeader className="flex flex-row items-center justify-between pb-2 pt-3 px-4">
|
||||
<CardHeader className="flex flex-row items-center justify-between p-3 pb-2">
|
||||
<CardTitle className="text-sm font-medium">Assets</CardTitle>
|
||||
<Button variant="secondary" size="sm">Add</Button>
|
||||
<Button variant="ghost" size="sm" className="h-6 px-2 text-xs">+ Add</Button>
|
||||
</CardHeader>
|
||||
<CardContent className="px-4 pb-3">
|
||||
{assets.length === 0 ? (
|
||||
<p className="text-sm text-muted-foreground py-2">No assets added yet</p>
|
||||
) : (
|
||||
<div className="divide-y divide-border rounded-md border border-border">
|
||||
{assets.map(asset => (
|
||||
<div key={asset.id} className="flex justify-between px-3 py-2">
|
||||
<div>
|
||||
<span className="text-sm">{asset.name}</span>
|
||||
<span className="ml-2 text-xs text-muted-foreground capitalize">{asset.type}</span>
|
||||
</div>
|
||||
<span className="text-sm font-medium">{fmt(asset.value)}</span>
|
||||
<CardContent className="p-3 pt-0">
|
||||
<div className="space-y-1.5 max-h-[180px] overflow-y-auto">
|
||||
{assets.map(asset => (
|
||||
<div key={asset.id} className="flex justify-between text-sm py-1 border-b border-border last:border-0">
|
||||
<div>
|
||||
<span>{asset.name}</span>
|
||||
<span className="ml-1.5 text-xs text-muted-foreground capitalize">{asset.type}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
<span className="font-medium">{fmt(asset.value)}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Liabilities */}
|
||||
<Card className="card-elevated">
|
||||
<CardHeader className="flex flex-row items-center justify-between pb-2 pt-3 px-4">
|
||||
<CardHeader className="flex flex-row items-center justify-between p-3 pb-2">
|
||||
<CardTitle className="text-sm font-medium">Liabilities</CardTitle>
|
||||
<Button variant="secondary" size="sm">Add</Button>
|
||||
<Button variant="ghost" size="sm" className="h-6 px-2 text-xs">+ Add</Button>
|
||||
</CardHeader>
|
||||
<CardContent className="px-4 pb-3">
|
||||
{liabilities.length === 0 ? (
|
||||
<p className="text-sm text-muted-foreground py-2">No liabilities added yet</p>
|
||||
) : (
|
||||
<div className="divide-y divide-border rounded-md border border-border">
|
||||
{liabilities.map(liability => (
|
||||
<div key={liability.id} className="flex justify-between px-3 py-2">
|
||||
<div>
|
||||
<span className="text-sm">{liability.name}</span>
|
||||
<span className="ml-2 text-xs text-muted-foreground capitalize">{liability.type.replace('_', ' ')}</span>
|
||||
</div>
|
||||
<span className="text-sm font-medium">{fmt(liability.balance)}</span>
|
||||
<CardContent className="p-3 pt-0">
|
||||
<div className="space-y-1.5 max-h-[180px] overflow-y-auto">
|
||||
{liabilities.map(liability => (
|
||||
<div key={liability.id} className="flex justify-between text-sm py-1 border-b border-border last:border-0">
|
||||
<div>
|
||||
<span>{liability.name}</span>
|
||||
<span className="ml-1.5 text-xs text-muted-foreground capitalize">{liability.type.replace('_', ' ')}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
<span className="font-medium">{fmt(liability.balance)}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Asset Allocation */}
|
||||
<Card className="card-elevated">
|
||||
<CardHeader className="p-3 pb-2">
|
||||
<CardTitle className="text-sm font-medium">Allocation</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="p-3 pt-0">
|
||||
<div className="space-y-2">
|
||||
{['cash', 'investment', 'property', 'vehicle'].map(type => {
|
||||
const total = assets.filter(a => a.type === type).reduce((s, a) => s + a.value, 0);
|
||||
const pct = totalAssets > 0 ? (total / totalAssets) * 100 : 0;
|
||||
if (total === 0) return null;
|
||||
return (
|
||||
<div key={type} className="flex items-center gap-2">
|
||||
<div className="flex-1">
|
||||
<div className="flex justify-between text-xs mb-0.5">
|
||||
<span className="capitalize">{type}</span>
|
||||
<span>{pct.toFixed(0)}%</span>
|
||||
</div>
|
||||
<div className="h-1 rounded-full bg-secondary overflow-hidden">
|
||||
<div className="h-full bg-foreground/40" style={{width: `${pct}%`}} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user