Add invoice dialog and enhance Cashflow, Clients, Invoices, and Net Worth pages

- Introduced AddInvoiceDialog component for creating new invoices with client selection and form validation.
- Updated CashflowPage to include dialogs for adding income, expenses, and transactions, improving user interaction.
- Enhanced ClientsPage with a dialog for adding new clients, streamlining client management.
- Improved InvoicesPage with a dialog for creating new invoices, facilitating better invoice management.
- Updated NetWorthPage to include dialogs for adding assets and liabilities, enhancing financial tracking capabilities.
This commit is contained in:
2025-12-07 11:44:50 -05:00
parent 1761931a73
commit d3f5df403d
5 changed files with 214 additions and 72 deletions

View File

@@ -0,0 +1,96 @@
import {useState} from 'react';
import {Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle} from '@/components/ui/dialog';
import {Select, SelectContent, SelectItem, SelectTrigger, SelectValue} from '@/components/ui/select';
import {Button} from '@/components/ui/button';
import {Input} from '@/components/ui/input';
import {Label} from '@/components/ui/label';
import {useAppDispatch, useAppSelector, addInvoice} from '@/store';
interface Props {
open: boolean;
onOpenChange: (open: boolean) => void;
}
export default function AddInvoiceDialog({open, onOpenChange}: Props) {
const dispatch = useAppDispatch();
const {clients} = useAppSelector(state => state.invoices);
const [form, setForm] = useState({
clientId: '',
description: '',
amount: '',
dueDate: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString().split('T')[0],
});
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
const now = new Date().toISOString();
const invoiceNumber = `INV-${new Date().getFullYear()}-${String(Math.floor(Math.random() * 1000)).padStart(3, '0')}`;
const amount = parseFloat(form.amount) || 0;
dispatch(addInvoice({
id: crypto.randomUUID(),
invoiceNumber,
clientId: form.clientId,
status: 'draft',
issueDate: now.split('T')[0],
dueDate: form.dueDate,
lineItems: [{
id: crypto.randomUUID(),
description: form.description,
quantity: 1,
unitPrice: amount,
total: amount,
}],
subtotal: amount,
tax: 0,
total: amount,
createdAt: now,
updatedAt: now,
}));
onOpenChange(false);
setForm({clientId: '', description: '', amount: '', dueDate: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString().split('T')[0]});
};
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="card-elevated sm:max-w-md">
<DialogHeader>
<DialogTitle>New Invoice</DialogTitle>
<DialogDescription>Create a new invoice</DialogDescription>
</DialogHeader>
<form onSubmit={handleSubmit}>
<div className="grid gap-4 py-4">
<div className="grid gap-2">
<Label>Client</Label>
<Select value={form.clientId} onValueChange={v => setForm({...form, clientId: v})} required>
<SelectTrigger className="input-depth"><SelectValue placeholder="Select client" /></SelectTrigger>
<SelectContent>
{clients.map(c => <SelectItem key={c.id} value={c.id}>{c.name}</SelectItem>)}
</SelectContent>
</Select>
</div>
<div className="grid gap-2">
<Label htmlFor="description">Description</Label>
<Input id="description" placeholder="e.g., Web Development Services" value={form.description} onChange={e => setForm({...form, description: e.target.value})} className="input-depth" required />
</div>
<div className="grid grid-cols-2 gap-4">
<div className="grid gap-2">
<Label htmlFor="amount">Amount</Label>
<Input id="amount" type="number" step="0.01" min="0" placeholder="0.00" value={form.amount} onChange={e => setForm({...form, amount: e.target.value})} className="input-depth" required />
</div>
<div className="grid gap-2">
<Label htmlFor="dueDate">Due Date</Label>
<Input id="dueDate" type="date" value={form.dueDate} onChange={e => setForm({...form, dueDate: e.target.value})} className="input-depth" required />
</div>
</div>
</div>
<DialogFooter>
<Button type="button" variant="secondary" onClick={() => onOpenChange(false)}>Cancel</Button>
<Button type="submit" disabled={!form.clientId || !form.description || !form.amount}>Create Invoice</Button>
</DialogFooter>
</form>
</DialogContent>
</Dialog>
);
}

View File

@@ -1,8 +1,15 @@
import {useState} from 'react';
import {Card, CardContent, CardHeader, CardTitle} from '@/components/ui/card'; import {Card, CardContent, CardHeader, CardTitle} from '@/components/ui/card';
import {Button} from '@/components/ui/button'; import {Button} from '@/components/ui/button';
import {useAppSelector} from '@/store'; import {useAppSelector} from '@/store';
import AddIncomeDialog from '@/components/dialogs/AddIncomeDialog';
import AddExpenseDialog from '@/components/dialogs/AddExpenseDialog';
import AddTransactionDialog from '@/components/dialogs/AddTransactionDialog';
export default function CashflowPage() { export default function CashflowPage() {
const [incomeDialogOpen, setIncomeDialogOpen] = useState(false);
const [expenseDialogOpen, setExpenseDialogOpen] = useState(false);
const [transactionDialogOpen, setTransactionDialogOpen] = useState(false);
const {incomeSources, expenses, transactions} = useAppSelector(state => state.cashflow); const {incomeSources, expenses, transactions} = useAppSelector(state => state.cashflow);
const getMonthlyAmount = (amount: number, frequency: string) => { const getMonthlyAmount = (amount: number, frequency: string) => {
@@ -21,9 +28,6 @@ export default function CashflowPage() {
const monthlySavings = monthlyIncome - monthlyExpenses; const monthlySavings = monthlyIncome - monthlyExpenses;
const savingsRate = monthlyIncome > 0 ? (monthlySavings / monthlyIncome) * 100 : 0; const savingsRate = monthlyIncome > 0 ? (monthlySavings / monthlyIncome) * 100 : 0;
const essentialTotal = expenses.filter(e => e.isActive && e.isEssential).reduce((sum, e) => sum + getMonthlyAmount(e.amount, e.frequency), 0);
const discretionaryTotal = expenses.filter(e => e.isActive && !e.isEssential).reduce((sum, e) => sum + getMonthlyAmount(e.amount, e.frequency), 0);
const fmt = (value: number) => new Intl.NumberFormat('en-US', {style: 'currency', currency: 'USD', maximumFractionDigits: 0}).format(value); const fmt = (value: number) => new Intl.NumberFormat('en-US', {style: 'currency', currency: 'USD', maximumFractionDigits: 0}).format(value);
const expensesByCategory = expenses.filter(e => e.isActive).reduce((acc, e) => { const expensesByCategory = expenses.filter(e => e.isActive).reduce((acc, e) => {
@@ -33,68 +37,64 @@ export default function CashflowPage() {
}, {} as Record<string, number>); }, {} as Record<string, number>);
const sortedCategories = Object.entries(expensesByCategory).sort((a, b) => b[1] - a[1]); const sortedCategories = Object.entries(expensesByCategory).sort((a, b) => b[1] - a[1]);
const topExpenses = expenses.filter(e => e.isActive).sort((a, b) => getMonthlyAmount(b.amount, b.frequency) - getMonthlyAmount(a.amount, a.frequency));
return ( return (
<div className="p-4"> <div className="p-4">
{/* Header + Summary inline */} {/* Header */}
<div className="mb-4 flex items-center justify-between"> <div className="mb-4 flex items-center justify-between">
<div className="flex items-center gap-6"> <div className="flex items-center gap-6">
<h1 className="text-lg font-semibold">Cashflow</h1> <h1 className="text-lg font-semibold">Cashflow</h1>
<div className="flex gap-4 text-sm"> <div className="flex gap-4 text-sm">
<div><span className="text-muted-foreground">Income</span> <span className="font-medium text-green-400">{fmt(monthlyIncome)}</span></div> <div><span className="text-muted-foreground">Income</span> <span className="font-medium text-green-400">{fmt(monthlyIncome)}</span></div>
<div><span className="text-muted-foreground">Expenses</span> <span className="font-medium">{fmt(monthlyExpenses)}</span></div> <div><span className="text-muted-foreground">Expenses</span> <span className="font-medium">{fmt(monthlyExpenses)}</span></div>
<div><span className="text-muted-foreground">Savings</span> <span className={`font-semibold ${monthlySavings >= 0 ? 'text-green-400' : 'text-red-400'}`}>{fmt(monthlySavings)}</span></div> <div><span className="text-muted-foreground">Net</span> <span className={`font-semibold ${monthlySavings >= 0 ? 'text-green-400' : 'text-red-400'}`}>{fmt(monthlySavings)}</span></div>
<div><span className="text-muted-foreground">Rate</span> <span className={`font-medium ${savingsRate >= 20 ? 'text-green-400' : ''}`}>{savingsRate.toFixed(0)}%</span></div> <div><span className="text-muted-foreground">Savings</span> <span className={`font-medium ${savingsRate >= 20 ? 'text-green-400' : ''}`}>{savingsRate.toFixed(0)}%</span></div>
</div> </div>
</div> </div>
<div className="flex gap-2"> <div className="flex gap-2">
<Button variant="secondary" size="sm">Add Income</Button> <Button variant="secondary" size="sm" onClick={() => setIncomeDialogOpen(true)}>Add Income</Button>
<Button size="sm">Add Expense</Button> <Button size="sm" onClick={() => setExpenseDialogOpen(true)}>Add Expense</Button>
</div> </div>
</div> </div>
<div className="grid grid-cols-4 gap-4"> <div className="grid grid-cols-12 gap-4">
{/* Income Sources */} {/* Income Sources */}
<Card className="card-elevated"> <Card className="card-elevated col-span-3">
<CardHeader className="p-3 pb-2"> <CardHeader className="p-3 pb-2">
<CardTitle className="text-sm font-medium">Income Sources</CardTitle> <CardTitle className="text-xs font-medium text-muted-foreground uppercase tracking-wide">Income</CardTitle>
</CardHeader> </CardHeader>
<CardContent className="p-3 pt-0"> <CardContent className="p-3 pt-0">
<div className="space-y-1.5 max-h-[250px] overflow-y-auto"> <div className="space-y-3">
{incomeSources.filter(i => i.isActive).map(income => ( {incomeSources.filter(i => i.isActive).map(income => (
<div key={income.id} className="flex justify-between text-sm py-1 border-b border-border last:border-0"> <div key={income.id} className="flex justify-between items-baseline">
<div> <div>
<p className="text-sm">{income.name}</p> <p className="text-sm font-medium">{income.name}</p>
<p className="text-xs text-muted-foreground">{income.frequency}</p> <p className="text-xs text-muted-foreground">{income.frequency}</p>
</div> </div>
<div className="text-right"> <p className="text-sm font-medium tabular-nums">{fmt(income.amount)}</p>
<p className="font-medium">{fmt(income.amount)}</p>
<p className="text-xs text-muted-foreground">{fmt(getMonthlyAmount(income.amount, income.frequency))}/mo</p>
</div>
</div> </div>
))} ))}
</div> </div>
</CardContent> </CardContent>
</Card> </Card>
{/* Expenses by Category */} {/* Expenses Breakdown */}
<Card className="card-elevated"> <Card className="card-elevated col-span-5">
<CardHeader className="p-3 pb-2"> <CardHeader className="p-3 pb-2">
<CardTitle className="text-sm font-medium">By Category</CardTitle> <CardTitle className="text-xs font-medium text-muted-foreground uppercase tracking-wide">Expenses by Category</CardTitle>
</CardHeader> </CardHeader>
<CardContent className="p-3 pt-0"> <CardContent className="p-3 pt-0">
<div className="space-y-2 max-h-[250px] overflow-y-auto"> <div className="space-y-2">
{sortedCategories.map(([category, amount]) => { {sortedCategories.map(([category, amount]) => {
const pct = (amount / monthlyExpenses) * 100; const pct = (amount / monthlyExpenses) * 100;
return ( return (
<div key={category}> <div key={category} className="flex items-center gap-3">
<div className="flex justify-between text-xs mb-0.5"> <div className="w-24 text-sm truncate">{category}</div>
<span>{category}</span> <div className="flex-1 h-1.5 rounded-full bg-foreground/10 overflow-hidden">
<span className="font-medium">{fmt(amount)}</span> <div className="h-full rounded-full bg-foreground/30" style={{width: `${pct}%`}} />
</div>
<div className="h-1 rounded-full bg-secondary overflow-hidden">
<div className="h-full bg-foreground/40" style={{width: `${pct}%`}} />
</div> </div>
<div className="w-16 text-right text-sm font-medium tabular-nums">{fmt(amount)}</div>
</div> </div>
); );
})} })}
@@ -102,57 +102,82 @@ export default function CashflowPage() {
</CardContent> </CardContent>
</Card> </Card>
{/* Essential vs Discretionary */}
<Card className="card-elevated">
<CardHeader className="p-3 pb-2">
<CardTitle className="text-sm font-medium">Essential vs Discretionary</CardTitle>
</CardHeader>
<CardContent className="p-3 pt-0">
<div className="grid grid-cols-2 gap-2 mb-3">
<div className="rounded border border-border p-2 text-center">
<p className="text-xs text-muted-foreground">Essential</p>
<p className="font-semibold">{fmt(essentialTotal)}</p>
</div>
<div className="rounded border border-border p-2 text-center">
<p className="text-xs text-muted-foreground">Discretionary</p>
<p className="font-semibold">{fmt(discretionaryTotal)}</p>
</div>
</div>
<div className="space-y-1 max-h-[150px] overflow-y-auto text-sm">
{expenses.filter(e => e.isActive).sort((a, b) => getMonthlyAmount(b.amount, b.frequency) - getMonthlyAmount(a.amount, a.frequency)).slice(0, 8).map(expense => (
<div key={expense.id} className="flex justify-between py-0.5">
<span className={expense.isEssential ? '' : 'text-muted-foreground'}>{expense.name}</span>
<span className="font-medium">{fmt(getMonthlyAmount(expense.amount, expense.frequency))}</span>
</div>
))}
</div>
</CardContent>
</Card>
{/* Recent Transactions */} {/* Recent Transactions */}
<Card className="card-elevated"> <Card className="card-elevated col-span-4">
<CardHeader className="flex flex-row items-center justify-between p-3 pb-2"> <CardHeader className="flex flex-row items-center justify-between p-3 pb-2">
<CardTitle className="text-sm font-medium">Recent</CardTitle> <CardTitle className="text-xs font-medium text-muted-foreground uppercase tracking-wide">Recent Activity</CardTitle>
<Button variant="ghost" size="sm" className="h-6 px-2 text-xs">+ Add</Button> <Button variant="ghost" size="sm" className="h-5 px-2 text-xs" onClick={() => setTransactionDialogOpen(true)}>+</Button>
</CardHeader> </CardHeader>
<CardContent className="p-3 pt-0"> <CardContent className="p-3 pt-0">
<div className="space-y-1.5 max-h-[250px] overflow-y-auto"> <div className="space-y-2">
{transactions.slice(0, 10).map(tx => ( {transactions.slice(0, 8).map(tx => (
<div key={tx.id} className="flex justify-between text-sm py-1 border-b border-border last:border-0"> <div key={tx.id} className="flex justify-between items-baseline">
<div> <div>
<p>{tx.name}</p> <p className="text-sm">{tx.name}</p>
<p className="text-xs text-muted-foreground">{tx.date}</p> <p className="text-xs text-muted-foreground">{tx.date}</p>
</div> </div>
<span className={`font-medium ${tx.type === 'income' ? 'text-green-400' : ''}`}> <span className={`text-sm font-medium tabular-nums ${tx.type === 'income' ? 'text-green-400' : ''}`}>
{tx.type === 'income' ? '+' : '-'}{fmt(tx.amount)} {tx.type === 'income' ? '+' : ''}{fmt(tx.amount)}
</span> </span>
</div> </div>
))} ))}
</div> </div>
</CardContent> </CardContent>
</Card> </Card>
{/* Top Expenses */}
<Card className="card-elevated col-span-6">
<CardHeader className="p-3 pb-2">
<CardTitle className="text-xs font-medium text-muted-foreground uppercase tracking-wide">Top Monthly Expenses</CardTitle>
</CardHeader>
<CardContent className="p-3 pt-0">
<div className="grid grid-cols-2 gap-x-6 gap-y-2">
{topExpenses.slice(0, 10).map(expense => (
<div key={expense.id} className="flex justify-between items-baseline py-0.5">
<span className={`text-sm ${expense.isEssential ? '' : 'text-muted-foreground'}`}>{expense.name}</span>
<span className="text-sm font-medium tabular-nums">{fmt(getMonthlyAmount(expense.amount, expense.frequency))}</span>
</div> </div>
))}
</div>
</CardContent>
</Card>
{/* Summary Stats */}
<Card className="card-elevated col-span-6">
<CardHeader className="p-3 pb-2">
<CardTitle className="text-xs font-medium text-muted-foreground uppercase tracking-wide">Monthly Summary</CardTitle>
</CardHeader>
<CardContent className="p-3 pt-0">
<div className="grid grid-cols-3 gap-4">
<div>
<p className="text-2xl font-semibold text-green-400">{fmt(monthlyIncome)}</p>
<p className="text-xs text-muted-foreground">Total Income</p>
</div>
<div>
<p className="text-2xl font-semibold">{fmt(monthlyExpenses)}</p>
<p className="text-xs text-muted-foreground">Total Expenses</p>
</div>
<div>
<p className={`text-2xl font-semibold ${monthlySavings >= 0 ? 'text-green-400' : 'text-red-400'}`}>{fmt(monthlySavings)}</p>
<p className="text-xs text-muted-foreground">Net Savings</p>
</div>
</div>
<div className="mt-4 pt-3 border-t border-border">
<div className="flex justify-between text-sm mb-1">
<span className="text-muted-foreground">Savings Rate</span>
<span className={`font-medium ${savingsRate >= 20 ? 'text-green-400' : ''}`}>{savingsRate.toFixed(1)}%</span>
</div>
<div className="h-2 rounded-full bg-foreground/10 overflow-hidden">
<div className={`h-full rounded-full ${savingsRate >= 20 ? 'bg-green-400/50' : 'bg-foreground/30'}`} style={{width: `${Math.min(savingsRate, 100)}%`}} />
</div>
</div>
</CardContent>
</Card>
</div>
<AddIncomeDialog open={incomeDialogOpen} onOpenChange={setIncomeDialogOpen} />
<AddExpenseDialog open={expenseDialogOpen} onOpenChange={setExpenseDialogOpen} />
<AddTransactionDialog open={transactionDialogOpen} onOpenChange={setTransactionDialogOpen} />
</div> </div>
); );
} }

View File

@@ -1,8 +1,11 @@
import {useState} from 'react';
import {Card, CardContent} from '@/components/ui/card'; import {Card, CardContent} from '@/components/ui/card';
import {Button} from '@/components/ui/button'; import {Button} from '@/components/ui/button';
import {useAppSelector} from '@/store'; import {useAppSelector} from '@/store';
import AddClientDialog from '@/components/dialogs/AddClientDialog';
export default function ClientsPage() { export default function ClientsPage() {
const [dialogOpen, setDialogOpen] = useState(false);
const {clients, invoices} = useAppSelector(state => state.invoices); const {clients, invoices} = useAppSelector(state => state.invoices);
const getClientStats = (clientId: string) => { const getClientStats = (clientId: string) => {
@@ -29,7 +32,7 @@ export default function ClientsPage() {
<div><span className="text-muted-foreground">Outstanding</span> <span className="font-medium">{fmt(totalOutstanding)}</span></div> <div><span className="text-muted-foreground">Outstanding</span> <span className="font-medium">{fmt(totalOutstanding)}</span></div>
</div> </div>
</div> </div>
<Button size="sm">Add Client</Button> <Button size="sm" onClick={() => setDialogOpen(true)}>Add Client</Button>
</div> </div>
{/* Clients Grid */} {/* Clients Grid */}
@@ -64,12 +67,14 @@ export default function ClientsPage() {
})} })}
{/* Add client card */} {/* Add client card */}
<Card className="card-elevated border-dashed cursor-pointer hover:bg-accent/50 transition-colors"> <Card className="card-elevated border-dashed cursor-pointer hover:bg-accent/50 transition-colors" onClick={() => setDialogOpen(true)}>
<CardContent className="p-4 flex items-center justify-center h-full min-h-[100px]"> <CardContent className="p-4 flex items-center justify-center h-full min-h-[100px]">
<span className="text-sm text-muted-foreground">+ Add Client</span> <span className="text-sm text-muted-foreground">+ Add Client</span>
</CardContent> </CardContent>
</Card> </Card>
</div> </div>
<AddClientDialog open={dialogOpen} onOpenChange={setDialogOpen} />
</div> </div>
); );
} }

View File

@@ -1,9 +1,14 @@
import {useState} from 'react';
import {Card, CardContent, CardHeader, CardTitle} from '@/components/ui/card'; import {Card, CardContent, CardHeader, CardTitle} from '@/components/ui/card';
import {Button} from '@/components/ui/button'; import {Button} from '@/components/ui/button';
import {useAppSelector} from '@/store'; import {useAppSelector} from '@/store';
import {format} from 'date-fns'; import {format} from 'date-fns';
import AddClientDialog from '@/components/dialogs/AddClientDialog';
import AddInvoiceDialog from '@/components/dialogs/AddInvoiceDialog';
export default function InvoicesPage() { export default function InvoicesPage() {
const [clientDialogOpen, setClientDialogOpen] = useState(false);
const [invoiceDialogOpen, setInvoiceDialogOpen] = useState(false);
const {invoices, clients} = useAppSelector(state => state.invoices); const {invoices, clients} = useAppSelector(state => state.invoices);
const getClientName = (clientId: string) => clients.find(c => c.id === clientId)?.name ?? 'Unknown'; const getClientName = (clientId: string) => clients.find(c => c.id === clientId)?.name ?? 'Unknown';
@@ -34,8 +39,8 @@ export default function InvoicesPage() {
</div> </div>
</div> </div>
<div className="flex gap-2"> <div className="flex gap-2">
<Button variant="secondary" size="sm">Add Client</Button> <Button variant="secondary" size="sm" onClick={() => setClientDialogOpen(true)}>Add Client</Button>
<Button size="sm">New Invoice</Button> <Button size="sm" onClick={() => setInvoiceDialogOpen(true)}>New Invoice</Button>
</div> </div>
</div> </div>
@@ -139,6 +144,9 @@ export default function InvoicesPage() {
</CardContent> </CardContent>
</Card> </Card>
</div> </div>
<AddClientDialog open={clientDialogOpen} onOpenChange={setClientDialogOpen} />
<AddInvoiceDialog open={invoiceDialogOpen} onOpenChange={setInvoiceDialogOpen} />
</div> </div>
); );
} }

View File

@@ -1,10 +1,15 @@
import {useState} from 'react';
import {Card, CardContent, CardHeader, CardTitle} from '@/components/ui/card'; import {Card, CardContent, CardHeader, CardTitle} from '@/components/ui/card';
import {Button} from '@/components/ui/button'; import {Button} from '@/components/ui/button';
import {useAppSelector} from '@/store'; import {useAppSelector} from '@/store';
import {AreaChart, Area, XAxis, YAxis, Tooltip, ResponsiveContainer} from 'recharts'; import {AreaChart, Area, XAxis, YAxis, Tooltip, ResponsiveContainer} from 'recharts';
import {format} from 'date-fns'; import {format} from 'date-fns';
import AddAssetDialog from '@/components/dialogs/AddAssetDialog';
import AddLiabilityDialog from '@/components/dialogs/AddLiabilityDialog';
export default function NetWorthPage() { export default function NetWorthPage() {
const [assetDialogOpen, setAssetDialogOpen] = useState(false);
const [liabilityDialogOpen, setLiabilityDialogOpen] = useState(false);
const {assets, liabilities, snapshots} = useAppSelector(state => state.netWorth); const {assets, liabilities, snapshots} = useAppSelector(state => state.netWorth);
const chartData = snapshots.map(s => ({ const chartData = snapshots.map(s => ({
@@ -87,7 +92,7 @@ export default function NetWorthPage() {
<Card className="card-elevated"> <Card className="card-elevated">
<CardHeader className="flex flex-row items-center justify-between p-3 pb-2"> <CardHeader className="flex flex-row items-center justify-between p-3 pb-2">
<CardTitle className="text-sm font-medium">Assets</CardTitle> <CardTitle className="text-sm font-medium">Assets</CardTitle>
<Button variant="ghost" size="sm" className="h-6 px-2 text-xs">+ Add</Button> <Button variant="ghost" size="sm" className="h-6 px-2 text-xs" onClick={() => setAssetDialogOpen(true)}>+ Add</Button>
</CardHeader> </CardHeader>
<CardContent className="p-3 pt-0"> <CardContent className="p-3 pt-0">
<div className="space-y-1.5 max-h-[180px] overflow-y-auto"> <div className="space-y-1.5 max-h-[180px] overflow-y-auto">
@@ -108,7 +113,7 @@ export default function NetWorthPage() {
<Card className="card-elevated"> <Card className="card-elevated">
<CardHeader className="flex flex-row items-center justify-between p-3 pb-2"> <CardHeader className="flex flex-row items-center justify-between p-3 pb-2">
<CardTitle className="text-sm font-medium">Liabilities</CardTitle> <CardTitle className="text-sm font-medium">Liabilities</CardTitle>
<Button variant="ghost" size="sm" className="h-6 px-2 text-xs">+ Add</Button> <Button variant="ghost" size="sm" className="h-6 px-2 text-xs" onClick={() => setLiabilityDialogOpen(true)}>+ Add</Button>
</CardHeader> </CardHeader>
<CardContent className="p-3 pt-0"> <CardContent className="p-3 pt-0">
<div className="space-y-1.5 max-h-[180px] overflow-y-auto"> <div className="space-y-1.5 max-h-[180px] overflow-y-auto">
@@ -154,6 +159,9 @@ export default function NetWorthPage() {
</CardContent> </CardContent>
</Card> </Card>
</div> </div>
<AddAssetDialog open={assetDialogOpen} onOpenChange={setAssetDialogOpen} />
<AddLiabilityDialog open={liabilityDialogOpen} onOpenChange={setLiabilityDialogOpen} />
</div> </div>
); );
} }