mirror of
https://github.com/escalante29/WealthySmart.git
synced 2026-07-17 15:28:47 +02:00
Budget donuts: sequential ramps + categorical slots, Paleta toggle retired
lib/charts.ts centralizes the color system: fixed categorical slots (never cycled, >5 folds into Otros, 'Sin categoría' reads neutral) and color-mix ramps that stay correct in dark mode. Animations off so charts render deterministically. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -14,6 +14,7 @@ import {
|
||||
import { BarChart3, ChartPie } from 'lucide-react';
|
||||
|
||||
import api, { getRateHistory } from '@/lib/api';
|
||||
import { categoricalColor, MAX_SLICES } from '@/lib/charts';
|
||||
import ErrorState from '@/components/ErrorState';
|
||||
import BillingCycleSelector from '@/components/BillingCycleSelector';
|
||||
import { PageHeader } from '@/components/page-header';
|
||||
@@ -57,17 +58,6 @@ interface DailySpending {
|
||||
count: number;
|
||||
}
|
||||
|
||||
/** Fixed categorical slots (validated palette). Never cycled: categories
|
||||
* beyond the 5th fold into "Otros" (muted). */
|
||||
const SERIES_VARS = [
|
||||
'var(--chart-1)',
|
||||
'var(--chart-2)',
|
||||
'var(--chart-3)',
|
||||
'var(--chart-4)',
|
||||
'var(--chart-5)',
|
||||
];
|
||||
const OTROS_COLOR = 'var(--muted-foreground)';
|
||||
const MAX_SLICES = 5;
|
||||
|
||||
function formatCRC(value: number) {
|
||||
return `₡${Math.round(value).toLocaleString('es-CR')}`;
|
||||
@@ -90,9 +80,6 @@ function foldCategories(cats: CategorySpending[]) {
|
||||
];
|
||||
}
|
||||
|
||||
function sliceColor(index: number, name: string) {
|
||||
return name === 'Otros' ? OTROS_COLOR : SERIES_VARS[index % SERIES_VARS.length];
|
||||
}
|
||||
|
||||
function ChartEmpty({ description }: { description: string }) {
|
||||
return (
|
||||
@@ -175,7 +162,7 @@ export default function Analytics() {
|
||||
const pieChartConfig = folded.reduce<ChartConfig>((acc, cat, i) => {
|
||||
acc[cat.category_name] = {
|
||||
label: cat.category_name,
|
||||
color: sliceColor(i, cat.category_name),
|
||||
color: categoricalColor(i, cat.category_name),
|
||||
};
|
||||
return acc;
|
||||
}, {});
|
||||
@@ -225,7 +212,7 @@ export default function Analytics() {
|
||||
isAnimationActive={false}
|
||||
>
|
||||
{folded.map((cat, i) => (
|
||||
<Cell key={cat.category_name} fill={sliceColor(i, cat.category_name)} />
|
||||
<Cell key={cat.category_name} fill={categoricalColor(i, cat.category_name)} />
|
||||
))}
|
||||
</Pie>
|
||||
<ChartTooltip
|
||||
@@ -244,7 +231,7 @@ export default function Analytics() {
|
||||
<div key={cat.category_name} className="flex items-center gap-2 text-xs">
|
||||
<div
|
||||
className="w-2.5 h-2.5 rounded-full flex-shrink-0"
|
||||
style={{ background: sliceColor(i, cat.category_name) }}
|
||||
style={{ background: categoricalColor(i, cat.category_name) }}
|
||||
/>
|
||||
<span className="text-muted-foreground truncate">{cat.category_name}</span>
|
||||
<span data-sensitive className="text-muted-foreground/60 ml-auto">{cat.percentage}%</span>
|
||||
@@ -358,7 +345,7 @@ export default function Analytics() {
|
||||
<div key={cat.category_name} className="flex items-center gap-3">
|
||||
<div
|
||||
className="w-3 h-3 rounded-full flex-shrink-0"
|
||||
style={{ background: sliceColor(i, cat.category_name) }}
|
||||
style={{ background: categoricalColor(i, cat.category_name) }}
|
||||
/>
|
||||
<span className="text-sm flex-1">{cat.category_name}</span>
|
||||
<span data-sensitive className="text-xs text-muted-foreground">{cat.count} txns</span>
|
||||
@@ -370,7 +357,7 @@ export default function Analytics() {
|
||||
className="h-1.5 rounded-full"
|
||||
style={{
|
||||
width: `${Math.min(100, cat.percentage)}%`,
|
||||
background: sliceColor(i, cat.category_name),
|
||||
background: categoricalColor(i, cat.category_name),
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user