first build commit
This commit is contained in:
79
src/components/graphs/BudgetChart.tsx
Normal file
79
src/components/graphs/BudgetChart.tsx
Normal file
@@ -0,0 +1,79 @@
|
||||
'use client'
|
||||
|
||||
import {
|
||||
BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer, Cell,
|
||||
} from 'recharts'
|
||||
import { formatCents, formatCentsAbbrev } from '@/lib/utils/currency'
|
||||
|
||||
interface BudgetBar {
|
||||
name: string
|
||||
spendCents: number
|
||||
limitCents: number
|
||||
color: string | null
|
||||
}
|
||||
|
||||
function ChartTooltip({ active, payload, label }: {
|
||||
active?: boolean
|
||||
payload?: Array<{ name: string; value: number }>
|
||||
label?: string
|
||||
}) {
|
||||
if (!active || !payload?.length) return null
|
||||
return (
|
||||
<div className="rounded-md border bg-card p-3 shadow-md text-sm">
|
||||
<p className="font-medium mb-1">{label}</p>
|
||||
{payload.map((p) => (
|
||||
<p key={p.name} className="text-muted-foreground">
|
||||
{p.name}: {formatCents(p.value)}
|
||||
</p>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function BudgetChart({ data }: { data: BudgetBar[] }) {
|
||||
if (data.length === 0) {
|
||||
return (
|
||||
<div className="flex h-[300px] items-center justify-center text-sm text-muted-foreground">
|
||||
No active budgets.
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const chartHeight = Math.max(200, data.length * 60 + 60)
|
||||
|
||||
return (
|
||||
<ResponsiveContainer width="100%" height={chartHeight}>
|
||||
<BarChart
|
||||
layout="vertical"
|
||||
data={data}
|
||||
margin={{ top: 4, right: 24, left: 8, bottom: 0 }}
|
||||
barGap={4}
|
||||
>
|
||||
<CartesianGrid strokeDasharray="3 3" className="stroke-muted" horizontal={false} />
|
||||
<XAxis
|
||||
type="number"
|
||||
tickFormatter={formatCentsAbbrev}
|
||||
tick={{ fontSize: 12 }}
|
||||
tickLine={false}
|
||||
axisLine={false}
|
||||
/>
|
||||
<YAxis
|
||||
type="category"
|
||||
dataKey="name"
|
||||
tick={{ fontSize: 12 }}
|
||||
tickLine={false}
|
||||
axisLine={false}
|
||||
width={90}
|
||||
/>
|
||||
<Tooltip content={<ChartTooltip />} />
|
||||
<Legend wrapperStyle={{ fontSize: 12 }} />
|
||||
<Bar dataKey="spendCents" name="Spent" radius={[0, 4, 4, 0]} maxBarSize={20}>
|
||||
{data.map((d, i) => (
|
||||
<Cell key={i} fill={d.color ?? '#6366f1'} />
|
||||
))}
|
||||
</Bar>
|
||||
<Bar dataKey="limitCents" name="Limit" fill="#e2e8f0" radius={[0, 4, 4, 0]} maxBarSize={20} />
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
)
|
||||
}
|
||||
60
src/components/graphs/CashFlowChart.tsx
Normal file
60
src/components/graphs/CashFlowChart.tsx
Normal file
@@ -0,0 +1,60 @@
|
||||
'use client'
|
||||
|
||||
import {
|
||||
BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer,
|
||||
} from 'recharts'
|
||||
import { formatCents, formatCentsAbbrev } from '@/lib/utils/currency'
|
||||
|
||||
interface DataPoint {
|
||||
label: string
|
||||
creditsCents: number
|
||||
debitsCents: number
|
||||
}
|
||||
|
||||
function ChartTooltip({ active, payload, label }: {
|
||||
active?: boolean
|
||||
payload?: Array<{ name: string; value: number; color: string }>
|
||||
label?: string
|
||||
}) {
|
||||
if (!active || !payload?.length) return null
|
||||
return (
|
||||
<div className="rounded-md border bg-card p-3 shadow-md text-sm">
|
||||
<p className="font-medium mb-1">{label}</p>
|
||||
{payload.map((p) => (
|
||||
<p key={p.name} style={{ color: p.color }}>
|
||||
{p.name}: {formatCents(p.value)}
|
||||
</p>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function CashFlowChart({ data }: { data: DataPoint[] }) {
|
||||
if (data.length === 0) {
|
||||
return (
|
||||
<div className="flex h-[300px] items-center justify-center text-sm text-muted-foreground">
|
||||
No bank transaction data yet.
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<ResponsiveContainer width="100%" height={300}>
|
||||
<BarChart data={data} margin={{ top: 4, right: 16, left: 16, bottom: 0 }} barGap={4}>
|
||||
<CartesianGrid strokeDasharray="3 3" className="stroke-muted" vertical={false} />
|
||||
<XAxis dataKey="label" tick={{ fontSize: 12 }} tickLine={false} axisLine={false} />
|
||||
<YAxis
|
||||
tickFormatter={formatCentsAbbrev}
|
||||
tick={{ fontSize: 12 }}
|
||||
tickLine={false}
|
||||
axisLine={false}
|
||||
width={64}
|
||||
/>
|
||||
<Tooltip content={<ChartTooltip />} />
|
||||
<Legend wrapperStyle={{ fontSize: 12 }} />
|
||||
<Bar dataKey="creditsCents" name="Income" fill="#22c55e" radius={[4, 4, 0, 0]} maxBarSize={40} />
|
||||
<Bar dataKey="debitsCents" name="Spending" fill="#ef4444" radius={[4, 4, 0, 0]} maxBarSize={40} />
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
)
|
||||
}
|
||||
68
src/components/graphs/CategoryBreakdownChart.tsx
Normal file
68
src/components/graphs/CategoryBreakdownChart.tsx
Normal file
@@ -0,0 +1,68 @@
|
||||
'use client'
|
||||
|
||||
import {
|
||||
PieChart, Pie, Cell, Tooltip, Legend, ResponsiveContainer,
|
||||
} from 'recharts'
|
||||
import { formatCents } from '@/lib/utils/currency'
|
||||
|
||||
interface DataPoint {
|
||||
category: string
|
||||
totalCents: number
|
||||
}
|
||||
|
||||
const COLORS = [
|
||||
'#6366f1', '#22c55e', '#f59e0b', '#ef4444', '#8b5cf6',
|
||||
'#14b8a6', '#f97316', '#ec4899', '#0ea5e9', '#84cc16',
|
||||
]
|
||||
|
||||
function ChartTooltip({ active, payload }: {
|
||||
active?: boolean
|
||||
payload?: Array<{ name: string; value: number; payload: DataPoint }>
|
||||
}) {
|
||||
if (!active || !payload?.length) return null
|
||||
const item = payload[0]
|
||||
return (
|
||||
<div className="rounded-md border bg-card p-3 shadow-md text-sm">
|
||||
<p className="font-medium">{item.payload.category}</p>
|
||||
<p className="text-muted-foreground">{formatCents(item.value)}</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function CategoryBreakdownChart({ data }: { data: DataPoint[] }) {
|
||||
if (data.length === 0) {
|
||||
return (
|
||||
<div className="flex h-[320px] items-center justify-center text-sm text-muted-foreground">
|
||||
No spending this month.
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<ResponsiveContainer width="100%" height={320}>
|
||||
<PieChart>
|
||||
<Pie
|
||||
data={data}
|
||||
dataKey="totalCents"
|
||||
nameKey="category"
|
||||
cx="50%"
|
||||
cy="45%"
|
||||
innerRadius={70}
|
||||
outerRadius={110}
|
||||
paddingAngle={2}
|
||||
>
|
||||
{data.map((_, i) => (
|
||||
<Cell key={i} fill={COLORS[i % COLORS.length]} />
|
||||
))}
|
||||
</Pie>
|
||||
<Tooltip content={<ChartTooltip />} />
|
||||
<Legend
|
||||
iconType="circle"
|
||||
iconSize={8}
|
||||
wrapperStyle={{ fontSize: 12 }}
|
||||
formatter={(value) => <span className="text-foreground">{value}</span>}
|
||||
/>
|
||||
</PieChart>
|
||||
</ResponsiveContainer>
|
||||
)
|
||||
}
|
||||
53
src/components/graphs/MonthlySpendingChart.tsx
Normal file
53
src/components/graphs/MonthlySpendingChart.tsx
Normal file
@@ -0,0 +1,53 @@
|
||||
'use client'
|
||||
|
||||
import {
|
||||
BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer,
|
||||
} from 'recharts'
|
||||
import { formatCents, formatCentsAbbrev } from '@/lib/utils/currency'
|
||||
|
||||
interface DataPoint {
|
||||
label: string
|
||||
totalCents: number
|
||||
}
|
||||
|
||||
function ChartTooltip({ active, payload, label }: {
|
||||
active?: boolean
|
||||
payload?: Array<{ value: number }>
|
||||
label?: string
|
||||
}) {
|
||||
if (!active || !payload?.length) return null
|
||||
return (
|
||||
<div className="rounded-md border bg-card p-3 shadow-md text-sm">
|
||||
<p className="font-medium mb-1">{label}</p>
|
||||
<p className="text-foreground">Total: {formatCents(payload[0].value)}</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function MonthlySpendingChart({ data }: { data: DataPoint[] }) {
|
||||
if (data.length === 0) {
|
||||
return (
|
||||
<div className="flex h-[300px] items-center justify-center text-sm text-muted-foreground">
|
||||
No spending data yet.
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<ResponsiveContainer width="100%" height={300}>
|
||||
<BarChart data={data} margin={{ top: 4, right: 16, left: 16, bottom: 0 }}>
|
||||
<CartesianGrid strokeDasharray="3 3" className="stroke-muted" vertical={false} />
|
||||
<XAxis dataKey="label" tick={{ fontSize: 12 }} tickLine={false} axisLine={false} />
|
||||
<YAxis
|
||||
tickFormatter={formatCentsAbbrev}
|
||||
tick={{ fontSize: 12 }}
|
||||
tickLine={false}
|
||||
axisLine={false}
|
||||
width={64}
|
||||
/>
|
||||
<Tooltip content={<ChartTooltip />} />
|
||||
<Bar dataKey="totalCents" name="Spending" fill="#8b5cf6" radius={[4, 4, 0, 0]} maxBarSize={48} />
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
)
|
||||
}
|
||||
68
src/components/graphs/NetWorthTrendChart.tsx
Normal file
68
src/components/graphs/NetWorthTrendChart.tsx
Normal file
@@ -0,0 +1,68 @@
|
||||
'use client'
|
||||
|
||||
import {
|
||||
AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer,
|
||||
} from 'recharts'
|
||||
import { formatCents, formatCentsAbbrev } from '@/lib/utils/currency'
|
||||
|
||||
interface DataPoint {
|
||||
label: string
|
||||
totalCents: number
|
||||
}
|
||||
|
||||
function ChartTooltip({ active, payload, label }: {
|
||||
active?: boolean
|
||||
payload?: Array<{ value: number }>
|
||||
label?: string
|
||||
}) {
|
||||
if (!active || !payload?.length) return null
|
||||
return (
|
||||
<div className="rounded-md border bg-card p-3 shadow-md text-sm">
|
||||
<p className="font-medium mb-1">{label}</p>
|
||||
<p className="text-primary">{formatCents(payload[0].value)}</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function NetWorthTrendChart({ data }: { data: DataPoint[] }) {
|
||||
if (data.length === 0) {
|
||||
return (
|
||||
<div className="flex h-[300px] items-center justify-center text-sm text-muted-foreground">
|
||||
No snapshot data yet — upload transactions to populate this chart.
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<ResponsiveContainer width="100%" height={300}>
|
||||
<AreaChart data={data} margin={{ top: 4, right: 16, left: 16, bottom: 0 }}>
|
||||
<defs>
|
||||
<linearGradient id="netWorthGradient" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="5%" stopColor="#6366f1" stopOpacity={0.3} />
|
||||
<stop offset="95%" stopColor="#6366f1" stopOpacity={0} />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<CartesianGrid strokeDasharray="3 3" className="stroke-muted" />
|
||||
<XAxis dataKey="label" tick={{ fontSize: 12 }} tickLine={false} axisLine={false} />
|
||||
<YAxis
|
||||
tickFormatter={formatCentsAbbrev}
|
||||
tick={{ fontSize: 12 }}
|
||||
tickLine={false}
|
||||
axisLine={false}
|
||||
width={64}
|
||||
/>
|
||||
<Tooltip content={<ChartTooltip />} />
|
||||
<Area
|
||||
type="monotone"
|
||||
dataKey="totalCents"
|
||||
name="Net Worth"
|
||||
stroke="#6366f1"
|
||||
strokeWidth={2}
|
||||
fill="url(#netWorthGradient)"
|
||||
dot={false}
|
||||
activeDot={{ r: 4 }}
|
||||
/>
|
||||
</AreaChart>
|
||||
</ResponsiveContainer>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user