✨ show account historical balance change in side-nav hover tooltip (#5085)
|
Before Width: | Height: | Size: 104 KiB After Width: | Height: | Size: 106 KiB |
|
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 67 KiB |
|
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 73 KiB After Width: | Height: | Size: 73 KiB |
|
Before Width: | Height: | Size: 73 KiB After Width: | Height: | Size: 74 KiB |
|
Before Width: | Height: | Size: 71 KiB After Width: | Height: | Size: 71 KiB |
@@ -197,7 +197,7 @@ export function NetWorthGraph({
|
||||
</defs>
|
||||
|
||||
<Area
|
||||
type="linear"
|
||||
type="monotone"
|
||||
dot={false}
|
||||
activeDot={false}
|
||||
animationDuration={0}
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
6
upcoming-release-notes/5085.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
category: Features
|
||||
authors: [MatissJanis]
|
||||
---
|
||||
|
||||
Show account balance historical change in side nav hover tooltip
|
||||