feat(04-02): add locale prop and custom currency tooltips to chart components

- 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)
This commit is contained in:
2026-03-12 09:26:13 +01:00
parent 1412aacf92
commit f141c4ff73
2 changed files with 35 additions and 5 deletions

View File

@@ -1,6 +1,6 @@
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
import { PieChart, Pie, Cell, ResponsiveContainer } from 'recharts' import { PieChart, Pie, Cell, ResponsiveContainer, Tooltip } from 'recharts'
import type { BudgetDetail } from '@/lib/api' import type { BudgetDetail } from '@/lib/api'
import { formatCurrency } from '@/lib/format' import { formatCurrency } from '@/lib/format'
import { palette, headerGradient, type CategoryType } from '@/lib/palette' import { palette, headerGradient, type CategoryType } from '@/lib/palette'
@@ -8,9 +8,10 @@ import { cn } from '@/lib/utils'
interface Props { interface Props {
budget: BudgetDetail budget: BudgetDetail
locale?: string
} }
export function AvailableBalance({ budget }: Props) { export function AvailableBalance({ budget, locale = 'en' }: Props) {
const { t } = useTranslation() const { t } = useTranslation()
const { totals } = budget const { totals } = budget
@@ -48,11 +49,25 @@ export function AvailableBalance({ budget }: Props) {
<Cell key={index} fill={palette[entry.categoryType]?.base ?? palette.carryover.base} /> <Cell key={index} fill={palette[entry.categoryType]?.base ?? palette.carryover.base} />
))} ))}
</Pie> </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> </PieChart>
</ResponsiveContainer> </ResponsiveContainer>
<div className="absolute inset-0 flex flex-col items-center justify-center"> <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')}> <span className={cn('text-3xl font-bold tabular-nums', available >= 0 ? 'text-success' : 'text-destructive')}>
{formatCurrency(available, budget.currency)} {formatCurrency(available, budget.currency, locale)}
</span> </span>
<span className="text-xs text-muted-foreground">{t('dashboard.available', 'Available')}</span> <span className="text-xs text-muted-foreground">{t('dashboard.available', 'Available')}</span>
</div> </div>

View File

@@ -2,13 +2,15 @@ import { useTranslation } from 'react-i18next'
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
import { PieChart, Pie, Cell, ResponsiveContainer, Tooltip } from 'recharts' import { PieChart, Pie, Cell, ResponsiveContainer, Tooltip } from 'recharts'
import type { BudgetDetail } from '@/lib/api' import type { BudgetDetail } from '@/lib/api'
import { formatCurrency } from '@/lib/format'
import { palette, headerGradient, type CategoryType } from '@/lib/palette' import { palette, headerGradient, type CategoryType } from '@/lib/palette'
interface Props { interface Props {
budget: BudgetDetail budget: BudgetDetail
locale?: string
} }
export function ExpenseBreakdown({ budget }: Props) { export function ExpenseBreakdown({ budget, locale = 'en' }: Props) {
const { t } = useTranslation() const { t } = useTranslation()
const expenses = budget.items const expenses = budget.items
.filter((i) => i.category_type === 'variable_expense' && i.actual_amount > 0) .filter((i) => i.category_type === 'variable_expense' && i.actual_amount > 0)
@@ -41,7 +43,20 @@ export function ExpenseBreakdown({ budget }: Props) {
<Cell key={index} fill={palette[entry.categoryType]?.base ?? palette.carryover.base} /> <Cell key={index} fill={palette[entry.categoryType]?.base ?? palette.carryover.base} />
))} ))}
</Pie> </Pie>
<Tooltip /> <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> </PieChart>
</ResponsiveContainer> </ResponsiveContainer>
</div> </div>