first build commit
This commit is contained in:
61
src/components/dashboard/BudgetSummary.tsx
Normal file
61
src/components/dashboard/BudgetSummary.tsx
Normal file
@@ -0,0 +1,61 @@
|
||||
import Link from 'next/link'
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { BudgetProgress } from '@/components/budgets/BudgetProgress'
|
||||
import { formatCents } from '@/lib/utils/currency'
|
||||
import { ArrowRight } from 'lucide-react'
|
||||
|
||||
interface BudgetItem {
|
||||
id: string
|
||||
name: string
|
||||
limitCents: number | null
|
||||
color: string | null
|
||||
spendCents: number
|
||||
}
|
||||
|
||||
export function BudgetSummary({ budgets }: { budgets: BudgetItem[] }) {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader className="pb-2 flex flex-row items-center justify-between space-y-0">
|
||||
<CardTitle className="text-sm font-medium text-muted-foreground">Budgets</CardTitle>
|
||||
<Link
|
||||
href="/budgets"
|
||||
className="flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground transition-colors"
|
||||
>
|
||||
Manage <ArrowRight className="h-3 w-3" />
|
||||
</Link>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{budgets.length === 0 ? (
|
||||
<p className="text-sm text-muted-foreground py-4 text-center">No active budgets.</p>
|
||||
) : (
|
||||
<div className="space-y-4">
|
||||
{budgets.map((b) => (
|
||||
<div key={b.id} className="space-y-1">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2 min-w-0">
|
||||
{b.color && (
|
||||
<span
|
||||
className="h-2.5 w-2.5 rounded-full shrink-0"
|
||||
style={{ backgroundColor: b.color }}
|
||||
/>
|
||||
)}
|
||||
<span className="text-sm font-medium truncate">{b.name}</span>
|
||||
</div>
|
||||
<span className="text-sm tabular-nums text-muted-foreground shrink-0 ml-2">
|
||||
{formatCents(b.spendCents)}
|
||||
{b.limitCents ? ` / ${formatCents(b.limitCents)}` : ''}
|
||||
</span>
|
||||
</div>
|
||||
{b.limitCents ? (
|
||||
<BudgetProgress spendCents={b.spendCents} limitCents={b.limitCents} />
|
||||
) : (
|
||||
<div className="h-1.5 rounded-full bg-muted" />
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
49
src/components/dashboard/CashFlowCard.tsx
Normal file
49
src/components/dashboard/CashFlowCard.tsx
Normal file
@@ -0,0 +1,49 @@
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { formatCents } from '@/lib/utils/currency'
|
||||
import { ArrowDownLeft, ArrowUpRight, TrendingUp } from 'lucide-react'
|
||||
|
||||
interface CashFlowCardProps {
|
||||
monthLabel: string
|
||||
creditsCents: number
|
||||
debitsCents: number
|
||||
netCents: number
|
||||
}
|
||||
|
||||
export function CashFlowCard({ monthLabel, creditsCents, debitsCents, netCents }: CashFlowCardProps) {
|
||||
const isPositive = netCents >= 0
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader className="pb-2">
|
||||
<CardTitle className="text-sm font-medium text-muted-foreground">
|
||||
Cash Flow · {monthLabel}
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<TrendingUp className={`h-5 w-5 shrink-0 ${isPositive ? 'text-green-500' : 'text-red-500'}`} />
|
||||
<span className={`text-3xl font-bold tabular-nums ${isPositive ? 'text-green-600' : 'text-red-600'}`}>
|
||||
{isPositive ? '+' : ''}{formatCents(netCents)}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1">
|
||||
<div className="flex items-center justify-between text-sm">
|
||||
<span className="flex items-center gap-1.5 text-muted-foreground">
|
||||
<ArrowDownLeft className="h-3.5 w-3.5 text-green-500" />
|
||||
Income
|
||||
</span>
|
||||
<span className="tabular-nums font-medium text-green-600">{formatCents(creditsCents)}</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between text-sm">
|
||||
<span className="flex items-center gap-1.5 text-muted-foreground">
|
||||
<ArrowUpRight className="h-3.5 w-3.5 text-red-500" />
|
||||
Spending
|
||||
</span>
|
||||
<span className="tabular-nums font-medium text-red-600">{formatCents(debitsCents)}</span>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
41
src/components/dashboard/NetWorthCard.tsx
Normal file
41
src/components/dashboard/NetWorthCard.tsx
Normal file
@@ -0,0 +1,41 @@
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { formatCents } from '@/lib/utils/currency'
|
||||
|
||||
interface BankAccount {
|
||||
id: string
|
||||
name: string
|
||||
currentBalanceCents: number
|
||||
}
|
||||
|
||||
interface NetWorthCardProps {
|
||||
netWorthCents: number
|
||||
bankAccounts: BankAccount[]
|
||||
}
|
||||
|
||||
export function NetWorthCard({ netWorthCents, bankAccounts }: NetWorthCardProps) {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader className="pb-2">
|
||||
<CardTitle className="text-sm font-medium text-muted-foreground">Net Worth</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-3">
|
||||
<p className="text-3xl font-bold tabular-nums">{formatCents(netWorthCents)}</p>
|
||||
{bankAccounts.length > 0 && (
|
||||
<div className="space-y-1">
|
||||
{bankAccounts.map((a) => (
|
||||
<div key={a.id} className="flex items-center justify-between text-sm">
|
||||
<span className="text-muted-foreground truncate">{a.name}</span>
|
||||
<span className="tabular-nums font-medium ml-4 shrink-0">
|
||||
{formatCents(a.currentBalanceCents)}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{bankAccounts.length === 0 && (
|
||||
<p className="text-sm text-muted-foreground">No bank accounts yet.</p>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
58
src/components/dashboard/RecentTransactions.tsx
Normal file
58
src/components/dashboard/RecentTransactions.tsx
Normal file
@@ -0,0 +1,58 @@
|
||||
import Link from 'next/link'
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { formatCents } from '@/lib/utils/currency'
|
||||
import { ArrowRight } from 'lucide-react'
|
||||
|
||||
interface RecentTx {
|
||||
id: string
|
||||
date: string
|
||||
description: string
|
||||
amountCents: number
|
||||
type: string
|
||||
accountName: string
|
||||
}
|
||||
|
||||
export function RecentTransactions({ transactions }: { transactions: RecentTx[] }) {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader className="pb-2 flex flex-row items-center justify-between space-y-0">
|
||||
<CardTitle className="text-sm font-medium text-muted-foreground">Recent Transactions</CardTitle>
|
||||
<Link
|
||||
href="/transactions"
|
||||
className="flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground transition-colors"
|
||||
>
|
||||
View all <ArrowRight className="h-3 w-3" />
|
||||
</Link>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{transactions.length === 0 ? (
|
||||
<p className="text-sm text-muted-foreground py-4 text-center">No transactions yet.</p>
|
||||
) : (
|
||||
<div className="space-y-3">
|
||||
{transactions.map((tx) => {
|
||||
const date = new Date(tx.date)
|
||||
const label = date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' })
|
||||
return (
|
||||
<div key={tx.id} className="flex items-start justify-between gap-2">
|
||||
<div className="min-w-0">
|
||||
<p className="text-sm font-medium truncate">{tx.description}</p>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{label} · {tx.accountName}
|
||||
</p>
|
||||
</div>
|
||||
<span
|
||||
className={`text-sm font-medium tabular-nums shrink-0 ${
|
||||
tx.type === 'CREDIT' ? 'text-green-600' : 'text-foreground'
|
||||
}`}
|
||||
>
|
||||
{tx.type === 'CREDIT' ? '+' : '-'}{formatCents(tx.amountCents)}
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user