Files
accounted/components/kpi/KPIExpenseMixChart.tsx
T
Jakob Wennberg b800dcd403 style(ui): system-wide UX/UI polish pass — design-system conformance + copy cleanup (#835)
* style(ui): system-wide UX/UI polish pass — design-system conformance + copy cleanup

Multi-agent scan of all 404 UI files against the locked design system, then
141 verified surgical fixes across 109 files (net -32 lines):

- Remove forbidden elevation/motion: shadow-* and rounded-xl on cards, active:scale
  bounce, hover:shadow on list items, transition-all -> transition-colors.
- Drop font-medium from single-weight Hedvig display headings/numerals.
- Replace raw rainbow Tailwind status colors with Badge variants / brand tokens /
  neutral surfaces (achromatic chrome, semantic colors stay data-only).
- Route raw dates through formatDate(), hand-rolled currency through formatCurrency(),
  add tabular-nums to financial figures; text-gray-* -> text-foreground tokens.
- Swap hand-rolled skeletons for the Skeleton primitive; off-scale spacing -> token scale.
- Fix copy: mislabeled "Leverantörsfakturor" -> "Utgifter" on bank-import outflow total,
  collapse no-op identical-branch ternaries, broken Swedish diacritics (mojibake),
  correct mismatch-password toast, correct supplier currency-field label.
- Remove PII-leaking debug console.log on register, stray console.logs.

Verified: tsc clean on all changed files, eslint clean, production build passes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(auth): sanitize residual error logs in register flow

Follow-up to PR review (compliance swarm V16 / GDPR Art.5(1)(f)): the
remaining console.error calls in the register flow passed raw error
objects, which Supabase may populate with PII (email) in nested fields.
Log only sanitized message strings instead.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 13:14:13 +02:00

112 lines
3.7 KiB
TypeScript

'use client'
import { useMemo } from 'react'
import { useTranslations } from 'next-intl'
import { PieChart, Pie, Cell, Tooltip, ResponsiveContainer } from 'recharts'
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
import { formatCurrency } from '@/lib/utils'
interface KPIExpenseMixChartProps {
composition: {
class4: number
class5: number
class6: number
class7: number
}
}
const SEGMENT_COLORS = [
'hsl(var(--chart-1))',
'hsl(var(--chart-3))',
'hsl(var(--chart-2))',
'hsl(var(--chart-4))',
]
export function KPIExpenseMixChart({ composition }: KPIExpenseMixChartProps) {
const t = useTranslations('kpi')
const { class4, class5, class6, class7 } = composition
const chartData = useMemo(
() =>
[
{ name: t('expense_mix_class4'), value: class4 },
{ name: t('expense_mix_class5'), value: class5 },
{ name: t('expense_mix_class6'), value: class6 },
{ name: t('expense_mix_class7'), value: class7 },
].filter((s) => s.value > 0),
[class4, class5, class6, class7, t]
)
const total = class4 + class5 + class6 + class7
const totalCompact =
new Intl.NumberFormat('sv-SE', {
notation: 'compact',
maximumFractionDigits: 1,
}).format(total) + ' kr'
return (
<Card>
<CardHeader className="pb-2">
<CardTitle className="text-base">{t('expense_mix_title')}</CardTitle>
</CardHeader>
<CardContent>
{chartData.length === 0 ? (
<div className="flex h-[240px] items-center justify-center text-sm text-muted-foreground">
{t('expense_mix_empty')}
</div>
) : (
<div className="relative flex flex-col items-center">
<ResponsiveContainer width="100%" height={180}>
<PieChart>
<Pie
data={chartData}
cx="50%"
cy="50%"
innerRadius={58}
outerRadius={84}
paddingAngle={2}
dataKey="value"
>
{chartData.map((_, index) => (
<Cell key={index} fill={SEGMENT_COLORS[index % SEGMENT_COLORS.length]} />
))}
</Pie>
<Tooltip
formatter={(value) => [formatCurrency(Number(value)), '']}
contentStyle={{
fontSize: '12px',
borderRadius: '8px',
border: '1px solid hsl(var(--border))',
backgroundColor: 'hsl(var(--card))',
}}
/>
</PieChart>
</ResponsiveContainer>
<div className="pointer-events-none absolute left-0 right-0 top-0 h-[180px] flex flex-col items-center justify-center">
<span className="text-[10px] uppercase tracking-wider text-muted-foreground">
{t('expense_mix_total')}
</span>
<span
className="font-display text-lg tabular-nums"
title={formatCurrency(total)}
>
{totalCompact}
</span>
</div>
<div className="mt-2 flex flex-wrap justify-center gap-x-4 gap-y-2 text-[11px] text-muted-foreground">
{chartData.map((seg, i) => (
<div key={seg.name} className="flex items-center gap-2">
<span
className="h-2 w-2 rounded-[2px]"
style={{ backgroundColor: SEGMENT_COLORS[i % SEGMENT_COLORS.length] }}
/>
<span>{seg.name}</span>
</div>
))}
</div>
</div>
)}
</CardContent>
</Card>
)
}