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:
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user