From 818a49e79956f16d71e01736593cec762bb67a46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vi=CC=81ctor=20Falco=CC=81n?= Date: Sat, 27 Dec 2025 16:49:02 +0000 Subject: [PATCH] fix(charts): mobile ui, and desktop tooltips --- .../accounts/account-balance-chart.tsx | 74 +++++++----- .../components/charts/chart-view-toggle.tsx | 44 ++++--- resources/js/components/charts/mom-chart.tsx | 110 +++++++++++------- .../components/charts/mom-percent-chart.tsx | 100 ++++++++++------ 4 files changed, 206 insertions(+), 122 deletions(-) diff --git a/resources/js/components/accounts/account-balance-chart.tsx b/resources/js/components/accounts/account-balance-chart.tsx index 2192c567..639d8092 100644 --- a/resources/js/components/accounts/account-balance-chart.tsx +++ b/resources/js/components/accounts/account-balance-chart.tsx @@ -25,7 +25,7 @@ import { } from '@/hooks/use-chart-views'; import { Account } from '@/types/account'; import { format, subMonths } from 'date-fns'; -import { useEffect, useMemo, useState } from 'react'; +import { useEffect, useMemo, useRef, useState } from 'react'; import { Bar, BarChart, XAxis } from 'recharts'; interface BalanceDataPoint { @@ -189,6 +189,17 @@ export function AccountBalanceChart({ return formatCurrency(value, account.currency_code); }; + const scrollContainerRef = useRef(null); + const minBarWidth = 50; + const minChartWidth = chartData.length * minBarWidth; + + useEffect(() => { + if (scrollContainerRef.current) { + scrollContainerRef.current.scrollLeft = + scrollContainerRef.current.scrollWidth; + } + }, [chartData]); + if (initialLoading || isLoading) { return ( @@ -264,33 +275,42 @@ export function AccountBalanceChart({ {chartViews.currentView === 'stacked' && ( - - - - - } - /> - - - + + + + + } + /> + + + + )} {chartViews.currentView === 'mom' && ( = { - stacked: { icon: BarChart3, label: 'Accounts' }, - mom: { icon: TrendingUp, label: 'MoM' }, - mom_percent: { icon: Percent, label: 'MoM%' }, + stacked: { + icon: BarChart3, + label: 'Aggregate', + tooltip: 'Monthly balance', + }, + mom: { icon: TrendingUp, label: 'MoM', tooltip: 'Month over month change' }, + mom_percent: { + icon: Percent, + label: 'MoM%', + tooltip: 'Month over month change (%)', + }, }; export function ChartViewToggle({ @@ -40,15 +53,20 @@ export function ChartViewToggle({ const config = viewConfig[view]; const Icon = config.icon; return ( - - - {config.label} - + + + + + + + + {config.tooltip} + + ); })} diff --git a/resources/js/components/charts/mom-chart.tsx b/resources/js/components/charts/mom-chart.tsx index 45694ff0..843a939b 100644 --- a/resources/js/components/charts/mom-chart.tsx +++ b/resources/js/components/charts/mom-chart.tsx @@ -2,6 +2,7 @@ import { AmountDisplay } from '@/components/ui/amount-display'; import { ChartConfig, ChartContainer } from '@/components/ui/chart'; import { ChangeDataPoint } from '@/lib/chart-calculations'; import { cn } from '@/lib/utils'; +import { useEffect, useRef } from 'react'; import { Bar, BarChart, @@ -17,6 +18,7 @@ interface MoMChartProps { currencyCode: string; xAxisFormatter?: (value: string) => string; className?: string; + minBarWidth?: number; } const chartConfig: ChartConfig = { @@ -77,56 +79,76 @@ export function MoMChart({ currencyCode, xAxisFormatter, className, + minBarWidth = 50, }: MoMChartProps) { + const scrollContainerRef = useRef(null); + const minChartWidth = data.length * minBarWidth; + + useEffect(() => { + if (scrollContainerRef.current) { + scrollContainerRef.current.scrollLeft = + scrollContainerRef.current.scrollWidth; + } + }, [data]); + const chartData = data.map((point) => ({ ...point, displayValue: point.change, })); return ( - - - - { - return new Intl.NumberFormat('en-US', { - notation: 'compact', - compactDisplay: 'short', - }).format(value / 100); - }} - width={50} - /> - - } - cursor={{ fill: 'var(--color-muted)', opacity: 0.3 }} - /> - - {chartData.map((entry, index) => { - const value = entry.displayValue; - let fill = 'var(--color-muted)'; - if (value !== null) { - fill = - value >= 0 - ? 'var(--color-chart-2)' - : 'var(--color-chart-5)'; - } - return ; - })} - - - +
+ + + + { + return new Intl.NumberFormat('en-US', { + notation: 'compact', + compactDisplay: 'short', + }).format(value / 100); + }} + width={50} + /> + + } + cursor={{ fill: 'var(--color-muted)', opacity: 0.3 }} + /> + + {chartData.map((entry, index) => { + const value = entry.displayValue; + let fill = 'var(--color-muted)'; + if (value !== null) { + fill = + value >= 0 + ? 'var(--color-chart-2)' + : 'var(--color-chart-5)'; + } + return ; + })} + + + +
); } diff --git a/resources/js/components/charts/mom-percent-chart.tsx b/resources/js/components/charts/mom-percent-chart.tsx index abeab2bb..04adeeb1 100644 --- a/resources/js/components/charts/mom-percent-chart.tsx +++ b/resources/js/components/charts/mom-percent-chart.tsx @@ -1,6 +1,7 @@ import { ChartConfig, ChartContainer } from '@/components/ui/chart'; import { formatPercentValue, PercentDataPoint } from '@/lib/chart-calculations'; import { cn } from '@/lib/utils'; +import { useEffect, useRef } from 'react'; import { Bar, BarChart, @@ -15,6 +16,7 @@ interface MoMPercentChartProps { data: PercentDataPoint[]; xAxisFormatter?: (value: string) => string; className?: string; + minBarWidth?: number; } const chartConfig: ChartConfig = { @@ -68,51 +70,73 @@ export function MoMPercentChart({ data, xAxisFormatter, className, + minBarWidth = 50, }: MoMPercentChartProps) { + const scrollContainerRef = useRef(null); + const minChartWidth = data.length * minBarWidth; + + useEffect(() => { + if (scrollContainerRef.current) { + scrollContainerRef.current.scrollLeft = + scrollContainerRef.current.scrollWidth; + } + }, [data]); + const chartData = data.map((point) => ({ ...point, displayValue: point.percent, })); return ( - - - - `${value.toFixed(0)}%`} - width={50} - /> - - } - cursor={{ fill: 'var(--color-muted)', opacity: 0.3 }} - /> - - {chartData.map((entry, index) => { - const value = entry.displayValue; - let fill = 'var(--color-muted)'; - if (value !== null) { - fill = - value >= 0 - ? 'var(--color-chart-2)' - : 'var(--color-chart-5)'; +
+ + + + + `${value.toFixed(0)}%` } - return ; - })} - - - + width={50} + /> + + } + cursor={{ fill: 'var(--color-muted)', opacity: 0.3 }} + /> + + {chartData.map((entry, index) => { + const value = entry.displayValue; + let fill = 'var(--color-muted)'; + if (value !== null) { + fill = + value >= 0 + ? 'var(--color-chart-2)' + : 'var(--color-chart-5)'; + } + return ; + })} + + + +
); }