diff --git a/packages/desktop-client/src/components/reports/ChooseGraph.tsx b/packages/desktop-client/src/components/reports/ChooseGraph.tsx index 97c2c2acafb..a680453607d 100644 --- a/packages/desktop-client/src/components/reports/ChooseGraph.tsx +++ b/packages/desktop-client/src/components/reports/ChooseGraph.tsx @@ -132,7 +132,18 @@ export function ChooseGraph({ ); } if (graphType === 'LineGraph') { - return ; + return ( + + ); } if (graphType === 'StackedBarGraph') { return ( diff --git a/packages/desktop-client/src/components/reports/ReportTopbar.jsx b/packages/desktop-client/src/components/reports/ReportTopbar.jsx index 6a78e7d8d9a..0bb9c45f99e 100644 --- a/packages/desktop-client/src/components/reports/ReportTopbar.jsx +++ b/packages/desktop-client/src/components/reports/ReportTopbar.jsx @@ -83,9 +83,7 @@ export function ReportTopbar({ title="Line Graph" selected={customReportItems.graphType === 'LineGraph'} onSelect={() => { - onReportChange({ type: 'modify' }); - setGraphType('LineGraph'); - defaultItems('LineGraph'); + onChangeGraph('LineGraph'); }} style={{ marginRight: 15 }} disabled={disabledItems('LineGraph')} diff --git a/packages/desktop-client/src/components/reports/graphs/LineGraph.tsx b/packages/desktop-client/src/components/reports/graphs/LineGraph.tsx index 2fe7f59bc98..3b69d289411 100644 --- a/packages/desktop-client/src/components/reports/graphs/LineGraph.tsx +++ b/packages/desktop-client/src/components/reports/graphs/LineGraph.tsx @@ -1,5 +1,5 @@ // @ts-strict-ignore -import React from 'react'; +import React, { useState } from 'react'; import { css } from 'glamor'; import { @@ -12,13 +12,22 @@ import { ResponsiveContainer, } from 'recharts'; -import { amountToCurrency } from 'loot-core/src/shared/util'; +import { + amountToCurrency, + amountToCurrencyNoDecimal, +} from 'loot-core/src/shared/util'; +import { type GroupedEntity } from 'loot-core/types/models/reports'; +import { type RuleConditionEntity } from 'loot-core/types/models/rule'; +import { useAccounts } from '../../../hooks/useAccounts'; +import { useCategories } from '../../../hooks/useCategories'; +import { useNavigate } from '../../../hooks/useNavigate'; +import { usePrivacyMode } from '../../../hooks/usePrivacyMode'; import { theme } from '../../../style'; import { type CSSProperties } from '../../../style'; import { AlignedText } from '../../common/AlignedText'; -import { PrivacyFilter } from '../../PrivacyFilter'; import { Container } from '../Container'; +import { getCustomTick } from '../getCustomTick'; import { numberFormatterTooltip } from '../numberFormatter'; type PayloadItem = { @@ -32,12 +41,20 @@ type PayloadItem = { }; type CustomTooltipProps = { + compact: boolean; + tooltip: string; active?: boolean; payload?: PayloadItem[]; }; -const CustomTooltip = ({ active, payload }: CustomTooltipProps) => { +const CustomTooltip = ({ + compact, + tooltip, + active, + payload, +}: CustomTooltipProps) => { if (active && payload && payload.length) { + let sumTotals = 0; return (
{ {payload[0].payload.date}
- - {payload - .sort((p1: PayloadItem, p2: PayloadItem) => p2.value - p1.value) - .map((p: PayloadItem, index: number) => ( - - ))} - + {payload + .sort((p1: PayloadItem, p2: PayloadItem) => p2.value - p1.value) + .map((p: PayloadItem, index: number) => { + sumTotals += p.value; + return ( + (compact ? index < 4 : true) && ( + + ) + ); + })} + {payload.length > 5 && compact && '...'} +
@@ -76,13 +108,83 @@ const CustomTooltip = ({ active, payload }: CustomTooltipProps) => { type LineGraphProps = { style?: CSSProperties; - data; + data: GroupedEntity; + filters: RuleConditionEntity[]; + groupBy: string; compact?: boolean; + balanceTypeOp: string; + showHiddenCategories?: boolean; + showOffBudget?: boolean; }; -export function LineGraph({ style, data, compact }: LineGraphProps) { - const tickFormatter = tick => { - return `${Math.round(tick).toLocaleString()}`; // Formats the tick values as strings with commas +export function LineGraph({ + style, + data, + filters, + groupBy, + compact, + balanceTypeOp, + showHiddenCategories, + showOffBudget, +}: LineGraphProps) { + const navigate = useNavigate(); + const categories = useCategories(); + const accounts = useAccounts(); + const privacyMode = usePrivacyMode(); + const [pointer, setPointer] = useState(''); + const [tooltip, setTooltip] = useState(''); + + const largestValue = data.intervalData + .map(c => c[balanceTypeOp]) + .reduce((acc, cur) => (Math.abs(cur) > Math.abs(acc) ? cur : acc), 0); + + const leftMargin = Math.abs(largestValue) > 1000000 ? 20 : 5; + + const onShowActivity = (item, id, payload) => { + const amount = balanceTypeOp === 'totalDebts' ? 'lte' : 'gte'; + const field = groupBy === 'Interval' ? null : groupBy.toLowerCase(); + const hiddenCategories = categories.list + .filter(f => f.hidden) + .map(e => e.id); + const offBudgetAccounts = accounts.filter(f => f.offbudget).map(e => e.id); + + const conditions = [ + ...filters, + { field, op: 'is', value: id, type: 'id' }, + { + field: 'date', + op: 'is', + value: payload.payload.dateStart, + options: { date: true }, + }, + balanceTypeOp !== 'totalTotals' && { + field: 'amount', + op: amount, + value: 0, + type: 'number', + }, + hiddenCategories.length > 0 && + !showHiddenCategories && { + field: 'category', + op: 'notOneOf', + value: hiddenCategories, + type: 'id', + }, + offBudgetAccounts.length > 0 && + !showOffBudget && { + field: 'account', + op: 'notOneOf', + value: offBudgetAccounts, + type: 'id', + }, + ].filter(f => f); + navigate('/accounts', { + state: { + goBack: true, + conditions, + categoryId: item.id, + }, + }); }; return ( @@ -101,18 +203,35 @@ export function LineGraph({ style, data, compact }: LineGraphProps) { width={width} height={height} data={data.intervalData} - margin={{ top: 10, right: 10, left: 10, bottom: 10 }} + margin={{ top: 10, right: 10, left: leftMargin, bottom: 10 }} + style={{ cursor: pointer }} > } + content={ + + } formatter={numberFormatterTooltip} isAnimationActive={false} /> {!compact && ( <> - - + + + getCustomTick( + amountToCurrencyNoDecimal(value), + privacyMode, + ) + } + tick={{ fill: theme.pageText }} + tickLine={{ stroke: theme.pageText }} + tickSize={0} + /> )} {data.legend.map((entry, index) => { @@ -123,6 +242,22 @@ export function LineGraph({ style, data, compact }: LineGraphProps) { type="monotone" dataKey={entry.name} stroke={entry.color} + activeDot={{ + r: entry.name === tooltip && !compact ? 8 : 3, + onMouseEnter: () => { + setTooltip(entry.name); + if (!['Group', 'Interval'].includes(groupBy)) { + setPointer('pointer'); + } + }, + onMouseLeave: () => { + setPointer(''); + setTooltip(''); + }, + onClick: (e, payload) => + !['Group', 'Interval'].includes(groupBy) && + onShowActivity(e, entry.id, payload), + }} /> ); })} diff --git a/packages/desktop-client/src/components/reports/graphs/StackedBarGraph.tsx b/packages/desktop-client/src/components/reports/graphs/StackedBarGraph.tsx index 7264e94fdfe..227319d2dea 100644 --- a/packages/desktop-client/src/components/reports/graphs/StackedBarGraph.tsx +++ b/packages/desktop-client/src/components/reports/graphs/StackedBarGraph.tsx @@ -245,13 +245,13 @@ export function StackedBarGraph({ isAnimationActive={false} cursor={{ fill: 'transparent' }} /> - {!compact && ( <> + diff --git a/upcoming-release-notes/2636.md b/upcoming-release-notes/2636.md new file mode 100644 index 00000000000..8f411a8b0ad --- /dev/null +++ b/upcoming-release-notes/2636.md @@ -0,0 +1,6 @@ +--- +category: Enhancements +authors: [carkom] +--- + +Enables the ability to show transactions when LineGraph is clicked. Also adds missing formatting to lineGraph.