From aba378cab9e27dac2f1300e9d63488dbcb59f20e Mon Sep 17 00:00:00 2001 From: 31b4 Date: Tue, 27 Oct 2026 13:43:37 +0100 Subject: [PATCH] Keep chart toggles on the legend row Toggling a reference line removed its legend row, shrinking the card and moving the toggle button. Render toggles in a footer slot that shares the legend row. --- .../analytics-module/NetWorthTrendChart.tsx | 17 ++++++++++------- .../analytics-module/PerAccountTrendChart.tsx | 5 ++--- .../common/interactive-area-chart.tsx | 15 ++++++++++----- .../PortfolioHistoryChart.tsx | 4 ++-- 4 files changed, 24 insertions(+), 17 deletions(-) diff --git a/client/src/components/analytics-module/NetWorthTrendChart.tsx b/client/src/components/analytics-module/NetWorthTrendChart.tsx index f9c12c6..3262345 100644 --- a/client/src/components/analytics-module/NetWorthTrendChart.tsx +++ b/client/src/components/analytics-module/NetWorthTrendChart.tsx @@ -24,6 +24,14 @@ export const NetWorthTrendChart = memo(function NetWorthTrendChart({ data, maste const unavailableReason = comparisonDisabledReason || comparison?.unavailableReason const comparing = compare && comparison !== undefined && !unavailableReason const hidden = privacyMode === 'hidden' + const showsChart = !comparing && data.length > 1 + const buttons = <> + {!comparing && !hidden && } + {comparison && } + return
{comparing ? <> @@ -36,14 +44,9 @@ export const NetWorthTrendChart = memo(function NetWorthTrendChart({ data, maste reference={showTrend ? { label: 'Trend', color: 'hsl(var(--muted-foreground))', dashed: true } : undefined} hidden={hidden} showReadout showChange className="h-[240px] sm:h-[280px]" + footer={(comparison || !hidden) ? buttons : undefined} /> : <>

{title}

Need more data points to show trend
} -
- {!comparing && !hidden && } - {comparison && } -
+ {!showsChart && (comparison || (!comparing && !hidden)) &&
{buttons}
} {comparison && unavailableReason &&

{unavailableReason}

}
}) diff --git a/client/src/components/analytics-module/PerAccountTrendChart.tsx b/client/src/components/analytics-module/PerAccountTrendChart.tsx index f3cd8a7..2dd5a0b 100644 --- a/client/src/components/analytics-module/PerAccountTrendChart.tsx +++ b/client/src/components/analytics-module/PerAccountTrendChart.tsx @@ -22,9 +22,8 @@ export const PerAccountTrendChart = memo(function PerAccountTrendChart({ account formatValue={value => value === null ? 'Unavailable' : `${value.toLocaleString('hu-HU', { maximumFractionDigits: 0 })} ${account.currency}`} reference={showTrend ? { label: 'Trend', color: 'hsl(var(--muted-foreground))', dashed: true } : undefined} hidden={privacyMode === 'hidden'} showReadout showChange className="h-[200px] sm:h-[240px]" + footer={privacyMode !== 'hidden' && } /> : <>

{account.name}

No transaction data in this period
} -

Current balance: {privacyMode === 'hidden' ? '••••••' : account.balance.toLocaleString('hu-HU', { maximumFractionDigits: 0 })} {account.currency}

- {privacyMode !== 'hidden' && data.length > 0 && } -
+

Current balance: {privacyMode === 'hidden' ? '••••••' : account.balance.toLocaleString('hu-HU', { maximumFractionDigits: 0 })} {account.currency}

}) diff --git a/client/src/components/common/interactive-area-chart.tsx b/client/src/components/common/interactive-area-chart.tsx index 0bd1312..f7d700e 100644 --- a/client/src/components/common/interactive-area-chart.tsx +++ b/client/src/components/common/interactive-area-chart.tsx @@ -25,6 +25,8 @@ type InteractiveAreaChartProps = { activeIndex?: number | null onInspect?: (index: number | null) => void renderTooltip?: (index: number) => ReactNode + // Shares the legend row, so toggling the reference line keeps it in place. + footer?: ReactNode } const clamp = (value: number, min: number, max: number) => Math.min(Math.max(value, min), max) @@ -32,7 +34,7 @@ const finite = (value: number | null | undefined): value is number => value != n const shortDate = (date: string) => new Date(`${date}T00:00:00Z`).toLocaleDateString('en', { month: 'short', day: 'numeric', timeZone: 'UTC' }) const fullDate = (date: string) => new Date(`${date}T00:00:00Z`).toLocaleDateString('en', { month: 'short', day: 'numeric', year: 'numeric', timeZone: 'UTC' }) -export function InteractiveAreaChart({ data, label, formatValue, color = 'hsl(var(--primary))', title, controls, showChange = false, periodLabel = 'in period', reference, hidden = false, showReadout = false, className, activeIndex, onInspect, renderTooltip }: InteractiveAreaChartProps) { +export function InteractiveAreaChart({ data, label, formatValue, color = 'hsl(var(--primary))', title, controls, showChange = false, periodLabel = 'in period', reference, hidden = false, showReadout = false, className, activeIndex, onInspect, renderTooltip, footer }: InteractiveAreaChartProps) { const uid = useId().replaceAll(':', '') const reduce = useReducedMotion() const wrapRef = useRef(null) @@ -125,7 +127,7 @@ export function InteractiveAreaChart({ data, label, formatValue, color = 'hsl(va inspect(nearest) } - if (hidden) return <>
{title &&

{title}

}{controls}
Chart hidden for privacy
+ if (hidden) return <>
{title &&

{title}

}{controls}
Chart hidden for privacy
{footer &&
{footer}
} if (!data.length) return
No data in this period
return
@@ -188,9 +190,12 @@ export function InteractiveAreaChart({ data, label, formatValue, color = 'hsl(va
} } - {reference &&
- {label} - {reference.label} + {(reference || footer) &&
+ {reference &&
+ {label} + {reference.label} +
} + {footer &&
{footer}
}
}
} diff --git a/client/src/components/investments-module/PortfolioHistoryChart.tsx b/client/src/components/investments-module/PortfolioHistoryChart.tsx index 233bbf2..95a4777 100644 --- a/client/src/components/investments-module/PortfolioHistoryChart.tsx +++ b/client/src/components/investments-module/PortfolioHistoryChart.tsx @@ -32,9 +32,9 @@ export function PortfolioHistoryChart({history,hidden,currency,currentValue}:{hi label="Portfolio value" formatValue={money} className="h-[220px] @min-[640px]:h-[300px]" reference={showInvested?{label:'Net invested',color:'hsl(var(--muted-foreground))',dashed:true,step:true}:undefined} activeIndex={selected?selection!.index:null} onInspect={index=>index===null?setSelection(null):readDay(index)} - renderTooltip={index=>}/>} + renderTooltip={index=>} + footer={}/>} - {!hidden&&data&&
} {data&&<>

{data.from} – {data.to} · {data.timezone} · read {new Date(data.as_of).toLocaleString()}{data.points.at(-1)?.provisional?' · today is provisional':''}

}