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.
This commit is contained in:
2025-12-07 11:10:33 -05:00
parent bf00261e1d
commit 043f0bd316
17 changed files with 1374 additions and 422 deletions

View File

@@ -1,86 +1,69 @@
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from '@/components/ui/card';
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';
// Demo data for the chart
const demoData = [
{month: 'Jan', netWorth: 45000},
{month: 'Feb', netWorth: 47500},
{month: 'Mar', netWorth: 46200},
{month: 'Apr', netWorth: 52000},
{month: 'May', netWorth: 55800},
{month: 'Jun', netWorth: 58200},
];
import {AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer} from 'recharts';
import {format} from 'date-fns';
export default function NetWorthPage() {
const {assets, liabilities} = useAppSelector(state => state.netWorth);
const {assets, liabilities, snapshots} = useAppSelector(state => state.netWorth);
const chartData = snapshots.map(s => ({
month: format(new Date(s.date), 'MMM'),
netWorth: s.netWorth,
}));
const totalAssets = assets.reduce((sum, a) => sum + a.value, 0);
const totalLiabilities = liabilities.reduce((sum, l) => sum + l.balance, 0);
const netWorth = totalAssets - totalLiabilities;
const formatCurrency = (value: number) =>
new Intl.NumberFormat('en-US', {style: 'currency', currency: 'USD'}).format(value);
const fmt = (value: number) =>
new Intl.NumberFormat('en-US', {style: 'currency', currency: 'USD', maximumFractionDigits: 0}).format(value);
return (
<div className="p-8">
<div className="mb-8">
<h1 className="text-2xl font-medium tracking-tight">Net Worth</h1>
<p className="text-muted-foreground">Track your wealth over time</p>
<div className="p-6">
{/* Header */}
<div className="mb-5 flex items-center justify-between">
<h1 className="text-xl font-medium">Net Worth</h1>
<Button size="sm">Record Snapshot</Button>
</div>
{/* Summary Cards */}
<div className="mb-8 grid gap-4 md:grid-cols-3">
<div className="mb-5 grid grid-cols-3 gap-4">
<Card className="card-elevated">
<CardHeader className="pb-2">
<CardDescription>Total Assets</CardDescription>
<CardHeader className="pb-1 pt-3 px-4">
<p className="text-xs text-muted-foreground">Total Assets</p>
</CardHeader>
<CardContent>
<p className="text-2xl font-semibold">{formatCurrency(totalAssets)}</p>
<CardContent className="pb-3 px-4">
<p className="text-xl font-semibold">{fmt(totalAssets)}</p>
</CardContent>
</Card>
<Card className="card-elevated">
<CardHeader className="pb-2">
<CardDescription>Total Liabilities</CardDescription>
<CardHeader className="pb-1 pt-3 px-4">
<p className="text-xs text-muted-foreground">Total Liabilities</p>
</CardHeader>
<CardContent>
<p className="text-2xl font-semibold">{formatCurrency(totalLiabilities)}</p>
<CardContent className="pb-3 px-4">
<p className="text-xl font-semibold">{fmt(totalLiabilities)}</p>
</CardContent>
</Card>
<Card className="card-elevated">
<CardHeader className="pb-2">
<CardDescription>Net Worth</CardDescription>
<CardHeader className="pb-1 pt-3 px-4">
<p className="text-xs text-muted-foreground">Net Worth</p>
</CardHeader>
<CardContent>
<p className="text-2xl font-semibold">{formatCurrency(netWorth)}</p>
<CardContent className="pb-3 px-4">
<p className="text-xl font-semibold">{fmt(netWorth)}</p>
</CardContent>
</Card>
</div>
{/* Chart */}
<Card className="card-elevated mb-8">
<CardHeader>
<CardTitle className="text-lg font-medium">Net Worth Over Time</CardTitle>
<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>
<div className="h-[300px]">
<CardContent className="px-4 pb-3">
<div className="h-[220px]">
<ResponsiveContainer width="100%" height="100%">
<AreaChart data={demoData}>
<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} />
@@ -88,32 +71,14 @@ export default function NetWorthPage() {
</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)'}}
/>
<YAxis
stroke="oklch(0.55 0 0)"
tick={{fill: 'oklch(0.55 0 0)'}}
tickFormatter={value => `$${value / 1000}k`}
/>
<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: '8px',
}}
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) => [formatCurrency(value), 'Net Worth']}
/>
<Area
type="monotone"
dataKey="netWorth"
stroke="oklch(0.85 0 0)"
strokeWidth={2}
fill="url(#netWorthGradient)"
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>
@@ -121,55 +86,51 @@ export default function NetWorthPage() {
</Card>
{/* Assets & Liabilities */}
<div className="grid gap-6 md:grid-cols-2">
<div className="grid gap-4 md:grid-cols-2">
<Card className="card-elevated">
<CardHeader className="flex flex-row items-center justify-between">
<div>
<CardTitle className="text-lg font-medium">Assets</CardTitle>
<CardDescription>What you own</CardDescription>
</div>
<Button variant="secondary" size="sm">
Add Asset
</Button>
<CardHeader className="flex flex-row items-center justify-between pb-2 pt-3 px-4">
<CardTitle className="text-sm font-medium">Assets</CardTitle>
<Button variant="secondary" size="sm">Add</Button>
</CardHeader>
<CardContent>
<CardContent className="px-4 pb-3">
{assets.length === 0 ? (
<p className="text-sm text-muted-foreground">No assets added yet</p>
<p className="text-sm text-muted-foreground py-2">No assets added yet</p>
) : (
<ul className="space-y-2">
<div className="divide-y divide-border rounded-md border border-border">
{assets.map(asset => (
<li key={asset.id} className="flex justify-between">
<span>{asset.name}</span>
<span>{formatCurrency(asset.value)}</span>
</li>
<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>
</div>
))}
</ul>
</div>
)}
</CardContent>
</Card>
<Card className="card-elevated">
<CardHeader className="flex flex-row items-center justify-between">
<div>
<CardTitle className="text-lg font-medium">Liabilities</CardTitle>
<CardDescription>What you owe</CardDescription>
</div>
<Button variant="secondary" size="sm">
Add Liability
</Button>
<CardHeader className="flex flex-row items-center justify-between pb-2 pt-3 px-4">
<CardTitle className="text-sm font-medium">Liabilities</CardTitle>
<Button variant="secondary" size="sm">Add</Button>
</CardHeader>
<CardContent>
<CardContent className="px-4 pb-3">
{liabilities.length === 0 ? (
<p className="text-sm text-muted-foreground">No liabilities added yet</p>
<p className="text-sm text-muted-foreground py-2">No liabilities added yet</p>
) : (
<ul className="space-y-2">
<div className="divide-y divide-border rounded-md border border-border">
{liabilities.map(liability => (
<li key={liability.id} className="flex justify-between">
<span>{liability.name}</span>
<span>{formatCurrency(liability.balance)}</span>
</li>
<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>
</div>
))}
</ul>
</div>
)}
</CardContent>
</Card>
@@ -177,4 +138,3 @@ export default function NetWorthPage() {
</div>
);
}