- Add locale?: string prop to ExpenseBreakdown and AvailableBalance - Replace bare <Tooltip /> in ExpenseBreakdown with custom content renderer - Add Tooltip import and custom content renderer to AvailableBalance - Pass locale to formatCurrency in AvailableBalance center text - Tooltip styled with shadcn design tokens (bg-background, border-border/50, shadow-xl)
79 lines
3.3 KiB
TypeScript
79 lines
3.3 KiB
TypeScript
import { useTranslation } from 'react-i18next'
|
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
|
import { PieChart, Pie, Cell, ResponsiveContainer, Tooltip } from 'recharts'
|
|
import type { BudgetDetail } from '@/lib/api'
|
|
import { formatCurrency } from '@/lib/format'
|
|
import { palette, headerGradient, type CategoryType } from '@/lib/palette'
|
|
import { cn } from '@/lib/utils'
|
|
|
|
interface Props {
|
|
budget: BudgetDetail
|
|
locale?: string
|
|
}
|
|
|
|
export function AvailableBalance({ budget, locale = 'en' }: Props) {
|
|
const { t } = useTranslation()
|
|
const { totals } = budget
|
|
|
|
const available = totals.available
|
|
|
|
const allData: Array<{ name: string; value: number; categoryType: CategoryType }> = [
|
|
{ name: t('dashboard.remaining'), value: Math.max(0, available), categoryType: 'carryover' },
|
|
{ name: t('dashboard.bills'), value: totals.bills_actual, categoryType: 'bill' },
|
|
{ name: t('dashboard.expenses'), value: totals.expenses_actual, categoryType: 'variable_expense' },
|
|
{ name: t('dashboard.debts'), value: totals.debts_actual, categoryType: 'debt' },
|
|
{ name: t('dashboard.savings'), value: totals.savings_actual, categoryType: 'saving' },
|
|
{ name: t('dashboard.investments'), value: totals.investments_actual, categoryType: 'investment' },
|
|
]
|
|
const data = allData.filter((d) => d.value > 0)
|
|
|
|
return (
|
|
<Card>
|
|
<CardHeader style={headerGradient('saving')} className="px-6 py-5">
|
|
<CardTitle className="text-2xl font-semibold">{t('dashboard.availableAmount')}</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="flex flex-col items-center gap-4 pt-6">
|
|
<div className="relative size-48">
|
|
<ResponsiveContainer width="100%" height="100%">
|
|
<PieChart>
|
|
<Pie
|
|
data={data}
|
|
cx="50%"
|
|
cy="50%"
|
|
innerRadius={55}
|
|
outerRadius={80}
|
|
paddingAngle={2}
|
|
dataKey="value"
|
|
>
|
|
{data.map((entry, index) => (
|
|
<Cell key={index} fill={palette[entry.categoryType]?.base ?? palette.carryover.base} />
|
|
))}
|
|
</Pie>
|
|
<Tooltip
|
|
content={({ active, payload }) => {
|
|
if (!active || !payload?.length) return null
|
|
const item = payload[0]
|
|
return (
|
|
<div className="rounded-lg border border-border/50 bg-background px-2.5 py-1.5 text-xs shadow-xl">
|
|
<p className="font-medium text-foreground">{item.name}</p>
|
|
<p className="font-mono tabular-nums text-muted-foreground">
|
|
{formatCurrency(Number(item.value), budget.currency, locale)}
|
|
</p>
|
|
</div>
|
|
)
|
|
}}
|
|
/>
|
|
</PieChart>
|
|
</ResponsiveContainer>
|
|
<div className="absolute inset-0 flex flex-col items-center justify-center">
|
|
<span className={cn('text-3xl font-bold tabular-nums', available >= 0 ? 'text-success' : 'text-destructive')}>
|
|
{formatCurrency(available, budget.currency, locale)}
|
|
</span>
|
|
<span className="text-xs text-muted-foreground">{t('dashboard.available', 'Available')}</span>
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
)
|
|
}
|