show account historical balance change in side-nav hover tooltip (#5085)

This commit is contained in:
Matiss Janis Aboltins
2025-06-13 20:07:16 +01:00
committed by GitHub
parent 0a5acebeaf
commit b5f29ccb4a
11 changed files with 162 additions and 3 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 104 KiB

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 73 KiB

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 73 KiB

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

After

Width:  |  Height:  |  Size: 71 KiB

View File

@@ -197,7 +197,7 @@ export function NetWorthGraph({
</defs>
<Area
type="linear"
type="monotone"
dot={false}
activeDot={false}
animationDuration={0}

View File

@@ -16,6 +16,8 @@ import { css, cx } from '@emotion/css';
import * as Platform from 'loot-core/shared/platform';
import { type AccountEntity } from 'loot-core/types/models';
import { BalanceHistoryGraph } from './BalanceHistoryGraph';
import { Link } from '@desktop-client/components/common/Link';
import { Notes } from '@desktop-client/components/Notes';
import {
@@ -282,16 +284,18 @@ export function Account<FieldName extends SheetFields<'account'>>({
<Text
style={{
fontWeight: 'bold',
borderBottom: accountNote ? `1px solid ${theme.tableBorder}` : 0,
marginBottom: accountNote ? '0.5rem' : 0,
}}
>
{name}
</Text>
{account && <BalanceHistoryGraph accountId={account.id} />}
{accountNote && (
<Notes
getStyle={() => ({
borderTop: `1px solid ${theme.tableBorder}`,
padding: 0,
paddingTop: '0.5rem',
marginTop: '0.5rem',
})}
notes={accountNote}
/>

View File

@@ -0,0 +1,149 @@
import React, { useState, useEffect, useMemo } from 'react';
import { SpaceBetween } from '@actual-app/components/space-between';
import { styles } from '@actual-app/components/styles';
import { Text } from '@actual-app/components/text';
import { theme } from '@actual-app/components/theme';
import { subMonths, format, eachMonthOfInterval, endOfMonth } from 'date-fns';
import { LineChart, Line, YAxis, Tooltip as RechartsTooltip } from 'recharts';
import { send } from 'loot-core/platform/client/fetch';
import { integerToCurrency } from 'loot-core/shared/util';
import { LoadingIndicator } from '@desktop-client/components/reports/LoadingIndicator';
const CHART_HEIGHT = 70;
const CHART_WIDTH = 280;
const LABEL_WIDTH = 70;
const TOTAL_WIDTH = CHART_WIDTH + LABEL_WIDTH;
type BalanceHistoryGraphProps = {
accountId: string;
};
export function BalanceHistoryGraph({ accountId }: BalanceHistoryGraphProps) {
const [balanceData, setBalanceData] = useState<
Array<{ date: string; balance: number }>
>([]);
const [loading, setLoading] = useState(true);
const [hoveredValue, setHoveredValue] = useState<{
date: string;
balance: number;
} | null>(null);
const percentageChange = useMemo(() => {
if (balanceData.length < 2) return 0;
const firstBalance = balanceData[0].balance;
const lastBalance = balanceData[balanceData.length - 1].balance;
if (firstBalance === 0) return 0;
return ((lastBalance - firstBalance) / Math.abs(firstBalance)) * 100;
}, [balanceData]);
useEffect(() => {
async function fetchBalanceHistory() {
const endDate = new Date();
const startDate = subMonths(endDate, 12);
const months = eachMonthOfInterval({ start: startDate, end: endDate });
const balances = await Promise.all(
months.map(async date => {
const balance = await send('api/account-balance', {
id: accountId,
cutoff: endOfMonth(date),
});
return {
date: format(date, 'MMM yyyy'),
balance: balance || 0,
};
}),
);
setBalanceData(balances);
setHoveredValue(balances[balances.length - 1]);
setLoading(false);
}
fetchBalanceHistory();
}, [accountId]);
if (loading) {
return (
<div style={{ width: TOTAL_WIDTH, height: CHART_HEIGHT, marginTop: 10 }}>
<LoadingIndicator />
</div>
);
}
return (
<div style={{ width: TOTAL_WIDTH, marginTop: 10 }}>
<div
style={{
display: 'flex',
alignItems: 'stretch',
justifyContent: 'space-between',
}}
>
<LineChart data={balanceData} width={CHART_WIDTH} height={CHART_HEIGHT}>
<YAxis domain={['dataMin', 'dataMax']} hide={true} />
<RechartsTooltip
contentStyle={{
display: 'none',
}}
labelFormatter={(label, items) => {
const data = items[0]?.payload;
if (data) {
setHoveredValue(data);
}
return '';
}}
isAnimationActive={false}
/>
<Line
type="monotone"
dataKey="balance"
stroke={
percentageChange >= 0 ? theme.noticeTextLight : theme.errorText
}
strokeWidth={2}
dot={false}
animationDuration={0}
/>
</LineChart>
<SpaceBetween
direction="vertical"
style={{
alignItems: 'flex-end',
justifyContent: 'space-between',
width: LABEL_WIDTH,
textAlign: 'right',
...styles.verySmallText,
}}
>
{percentageChange === 0 ? (
<div />
) : (
<Text
style={{
color:
percentageChange >= 0
? theme.noticeTextLight
: theme.errorText,
}}
>
{percentageChange >= 0 ? '+' : ''}
{percentageChange.toFixed(1)}%
</Text>
)}
{hoveredValue && (
<Text>
<div style={{ fontWeight: 800 }}>{hoveredValue.date}</div>
<div>{integerToCurrency(hoveredValue.balance)}</div>
</Text>
)}
</SpaceBetween>
</div>
</div>
);
}

View File

@@ -0,0 +1,6 @@
---
category: Features
authors: [MatissJanis]
---
Show account balance historical change in side nav hover tooltip