Refactor: Update dashboard components and remove unused cards (#8)

This commit is contained in:
Víctor Falcón 2025-12-03 11:56:41 +01:00 committed by GitHub
parent 4b11843916
commit 8202ebe139
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 62 additions and 261 deletions

View File

@ -1,9 +1,9 @@
import { EncryptedText } from '@/components/encrypted-text';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Account } from '@/types/account';
import { AmountTrendIndicator } from './amount-trend-indicator';
import { AccountTypeIcon } from './account-type-icon';
import { Line, LineChart, ResponsiveContainer, Tooltip } from 'recharts';
import { AccountTypeIcon } from './account-type-icon';
import { AmountTrendIndicator } from './amount-trend-indicator';
interface AccountBalanceCardProps {
account: Account & {
@ -43,8 +43,14 @@ export function AccountBalanceCard({
return (
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium flex items-center">
{account.bank.logo && <img src={account.bank.logo} alt={account.bank.name} className="inline-block size-5 mr-2 rounded-full object-contain" />}
<CardTitle className="flex items-center text-sm font-medium">
{account.bank.logo && (
<img
src={account.bank.logo}
alt={account.bank.name}
className="mr-2 inline-block size-5 rounded-full object-contain"
/>
)}
<EncryptedText
encryptedText={account.name}
@ -53,23 +59,28 @@ export function AccountBalanceCard({
/>
</CardTitle>
<div className="text-xs font-medium text-muted-foreground">
<AccountTypeIcon type={account.type} className="inline-block mr-1" />
<AccountTypeIcon
type={account.type}
className="mr-1 inline-block"
/>
</div>
</CardHeader>
<CardContent>
<div className="flex gap-6 items-center justify-between">
<div className='flex flex-col gap-1'>
<div className="flex items-center justify-between gap-6">
<div className="flex flex-col gap-1">
<div className="text-2xl font-medium">
{formatter.format(account.currentBalance / 100)}
</div>
<AmountTrendIndicator
isPositive={isPositive}
trend={formatter.format(Math.abs(account.diff) / 100)}
trend={formatter.format(
Math.abs(account.diff) / 100,
)}
label="vs last month"
className='text-sm'
className="text-sm"
/>
</div>
<div className="h-[70px] max-w-[250px] w-full flex-1">
<div className="h-[70px] w-full max-w-[250px] flex-1">
<ResponsiveContainer width="100%" height="100%">
<LineChart data={account.history}>
<Tooltip

View File

@ -1,20 +1,33 @@
import { cn } from "@/lib/utils";
import { AccountType } from "@/types/account";
import { BadgeQuestionMarkIcon, Building2, CreditCard, FolderKanban, Landmark, LucideIcon, PiggyBank } from "lucide-react";
import { cn } from '@/lib/utils';
import { AccountType } from '@/types/account';
import {
BadgeQuestionMarkIcon,
Building2,
CreditCard,
FolderKanban,
Landmark,
LucideIcon,
PiggyBank,
} from 'lucide-react';
export function AccountTypeIcon({ type, className }: { type: AccountType, className?: string }) {
export function AccountTypeIcon({
type,
className,
}: {
type: AccountType;
className?: string;
}) {
const typeMap: Record<AccountType, LucideIcon> = {
checking: Building2, // 🏦 - bank / institution
checking: Building2, // 🏦 - bank / institution
credit_card: CreditCard, // 💳 - card
loan: Landmark, // 🏠 - "institution/loan", or use Home if it's a mortgage
savings: PiggyBank, // 💰 - savings
others: FolderKanban, // 📁 - miscellaneous/other
loan: Landmark, // 🏠 - "institution/loan", or use Home if it's a mortgage
savings: PiggyBank, // 💰 - savings
others: FolderKanban, // 📁 - miscellaneous/other
};
const Icon = typeMap[type] || BadgeQuestionMarkIcon;
return <Icon className={cn([
"h-5 w-5 text-muted-foreground",
className
])} />;
return (
<Icon className={cn(['h-5 w-5 text-muted-foreground', className])} />
);
}

View File

@ -1,5 +1,5 @@
import { cn } from "@/lib/utils";
import { TrendingUp, TrendingDown } from "lucide-react";
import { cn } from '@/lib/utils';
import { TrendingDown, TrendingUp } from 'lucide-react';
export function AmountTrendIndicator({
trend,
@ -20,10 +20,7 @@ export function AmountTrendIndicator({
: 'text-red-600/70 dark:text-red-400/70';
return (
<div className={cn([
"flex items-center gap-1",
className
])}>
<div className={cn(['flex items-center gap-1', className])}>
<span
className={
isPositive ? 'bg-green-100/25 dark:bg-green-900/25' : ''

View File

@ -1,53 +0,0 @@
import { StatCard } from '@/components/dashboard/stat-card';
import { ArrowLeftRightIcon } from 'lucide-react';
interface CashFlowCardProps {
income: number;
expense: number;
previous?: {
income: number;
expense: number;
};
loading?: boolean;
}
export function CashFlowCard({
income,
expense,
previous,
loading,
}: CashFlowCardProps) {
const formatter = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
minimumFractionDigits: 0,
maximumFractionDigits: 0,
});
const currentNet = income - expense;
const calculateTrend = () => {
if (!previous) return undefined;
const previousNet = previous.income - previous.expense;
if (previousNet === 0) return undefined;
const diff = currentNet - previousNet;
const percentage = (diff / Math.abs(previousNet)) * 100;
return {
value: percentage,
label: 'from previous period',
};
};
return (
<StatCard
title="Cash Flow"
value={formatter.format(currentNet / 100)}
icon={ArrowLeftRightIcon}
trend={calculateTrend()}
description={`Income: ${formatter.format(income / 100)} • Expense: ${formatter.format(expense / 100)}`}
loading={loading}
/>
);
}

View File

@ -1,42 +0,0 @@
import { StatCard } from '@/components/dashboard/stat-card';
import { WalletIcon } from 'lucide-react';
interface NetWorthCardProps {
current: number;
previous: number;
loading?: boolean;
}
export function NetWorthCard({
current,
previous,
loading,
}: NetWorthCardProps) {
const formatter = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
minimumFractionDigits: 0,
maximumFractionDigits: 0,
});
const calculateTrend = () => {
if (!previous) return undefined;
const diff = current - previous;
const percentage = (diff / previous) * 100;
return {
value: percentage,
label: 'from previous period',
};
};
return (
<StatCard
title="Net Worth"
value={formatter.format(current / 100)}
icon={WalletIcon}
trend={calculateTrend()}
loading={loading}
/>
);
}

View File

@ -1,4 +1,4 @@
import { TrendingUp, TrendingDown } from "lucide-react";
import { TrendingDown, TrendingUp } from 'lucide-react';
export function PercentageTrendIndicator({
trend,

View File

@ -1,43 +0,0 @@
import { StatCard } from '@/components/dashboard/stat-card';
import { CreditCardIcon } from 'lucide-react';
interface SpendingSummaryCardProps {
current: number;
previous?: number;
loading?: boolean;
}
export function SpendingSummaryCard({
current,
previous,
loading,
}: SpendingSummaryCardProps) {
const formatter = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
minimumFractionDigits: 0,
maximumFractionDigits: 0,
});
const calculateTrend = () => {
if (!previous) return undefined;
const diff = current - previous;
const percentage = (diff / previous) * 100;
return {
value: percentage,
label: 'from previous period',
};
};
return (
<StatCard
title="Monthly Spending"
value={formatter.format(current / 100)}
icon={CreditCardIcon}
trend={calculateTrend()}
description="Total spending this month"
loading={loading}
/>
);
}

View File

@ -17,24 +17,6 @@ export interface NetWorthEvolutionData {
}
export interface DashboardData {
netWorth: {
current: number;
previous: number;
diff: number;
};
monthlySpending: {
current: number;
limit: null;
previous: number;
};
cashFlow: {
income: number;
expense: number;
previous: {
income: number;
expense: number;
};
};
netWorthEvolution: NetWorthEvolutionData;
accounts: Array<
Account & {
@ -52,13 +34,6 @@ export interface DashboardData {
export function useDashboardData(): DashboardData {
const [data, setData] = useState<Omit<DashboardData, 'isLoading'>>({
netWorth: { current: 0, previous: 0, diff: 0 },
monthlySpending: { current: 0, limit: null, previous: 0 },
cashFlow: {
income: 0,
expense: 0,
previous: { income: 0, expense: 0 },
},
netWorthEvolution: { data: [], accounts: {} },
accounts: [],
topCategories: [],
@ -72,7 +47,6 @@ export function useDashboardData(): DashboardData {
const now = new Date();
const to = format(now, 'yyyy-MM-dd');
// Last 12 months for evolution charts
const from12Months = format(subMonths(now, 12), 'yyyy-MM-dd');
const params12Months = new URLSearchParams({
from: from12Months,
@ -80,7 +54,6 @@ export function useDashboardData(): DashboardData {
});
const query12Months = `?${params12Months.toString()}`;
// Last 30 days for top blocks and categories
const from30Days = format(subDays(now, 30), 'yyyy-MM-dd');
const params30Days = new URLSearchParams({
from: from30Days,
@ -88,50 +61,20 @@ export function useDashboardData(): DashboardData {
});
const query30Days = `?${params30Days.toString()}`;
const [
netWorth,
monthlySpending,
cashFlow,
netWorthEvolution,
accountBalances,
topCategories,
] = await Promise.all([
fetch(`/api/dashboard/net-worth${query30Days}`).then((r) =>
r.json(),
),
fetch(`/api/dashboard/monthly-spending${query30Days}`).then(
(r) => r.json(),
),
fetch(`/api/dashboard/cash-flow${query30Days}`).then((r) =>
r.json(),
),
fetch(
`/api/dashboard/net-worth-evolution${query12Months}`,
).then((r) => r.json()),
fetch(
`/api/dashboard/account-balances${query12Months}`,
).then((r) => r.json()),
fetch(`/api/dashboard/top-categories${query30Days}`).then(
(r) => r.json(),
),
]);
const [netWorthEvolution, accountBalances, topCategories] =
await Promise.all([
fetch(
`/api/dashboard/net-worth-evolution${query12Months}`,
).then((r) => r.json()),
fetch(
`/api/dashboard/account-balances${query12Months}`,
).then((r) => r.json()),
fetch(
`/api/dashboard/top-categories${query30Days}`,
).then((r) => r.json()),
]);
setData({
netWorth: {
current: netWorth.current,
previous: netWorth.previous,
diff: netWorth.current - netWorth.previous,
},
monthlySpending: {
current: monthlySpending.current,
previous: monthlySpending.previous,
limit: null,
},
cashFlow: {
income: cashFlow.current.income,
expense: cashFlow.current.expense,
previous: cashFlow.previous,
},
netWorthEvolution:
netWorthEvolution as NetWorthEvolutionData,
accounts: accountBalances.map(

View File

@ -1,8 +1,5 @@
import { AccountBalanceCard } from '@/components/dashboard/account-balance-card';
import { CashFlowCard } from '@/components/dashboard/cash-flow-card';
import { NetWorthCard } from '@/components/dashboard/net-worth-card';
import { NetWorthChart as NetWorthChartComponent } from '@/components/dashboard/net-worth-chart';
import { SpendingSummaryCard } from '@/components/dashboard/spending-summary-card';
import { TopCategoriesCard } from '@/components/dashboard/top-categories-card';
import HeadingSmall from '@/components/heading-small';
import { useDashboardData } from '@/hooks/use-dashboard-data';
@ -20,9 +17,6 @@ const breadcrumbs: BreadcrumbItem[] = [
export default function Dashboard() {
const {
netWorth,
monthlySpending,
cashFlow,
netWorthEvolution,
accounts: accountMetrics,
topCategories,
@ -39,25 +33,6 @@ export default function Dashboard() {
description="Overview of your financial health"
/>
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
<NetWorthCard
current={netWorth.current}
previous={netWorth.previous}
loading={isLoading}
/>
<SpendingSummaryCard
current={monthlySpending.current}
previous={monthlySpending.previous}
loading={isLoading}
/>
<CashFlowCard
income={cashFlow.income}
expense={cashFlow.expense}
previous={cashFlow.previous}
loading={isLoading}
/>
</div>
<div className="grid gap-4 md:grid-cols-1 lg:grid-cols-3">
<NetWorthChartComponent
data={netWorthEvolution}