diff --git a/packages/desktop-client/src/components/reports/ChooseGraph.tsx b/packages/desktop-client/src/components/reports/ChooseGraph.tsx index 3e294fbff4848bfd15aa2b34dd4c3e4816b1fe69..97c2c2acafb45fbc3f4c79f792b51e2e7f66ebdd 100644 --- a/packages/desktop-client/src/components/reports/ChooseGraph.tsx +++ b/packages/desktop-client/src/components/reports/ChooseGraph.tsx @@ -140,8 +140,12 @@ export function ChooseGraph({ style={graphStyle} compact={compact} data={data} + filters={filters} viewLabels={viewLabels} balanceTypeOp={balanceTypeOp} + groupBy={groupBy} + showHiddenCategories={showHiddenCategories} + showOffBudget={showOffBudget} /> ); } diff --git a/packages/desktop-client/src/components/reports/graphs/StackedBarGraph.tsx b/packages/desktop-client/src/components/reports/graphs/StackedBarGraph.tsx index 2a3136fe6e36da4a80437e211f26a200bc198de4..7264e94fdfe3cc5700969433f96e425786f90285 100644 --- a/packages/desktop-client/src/components/reports/graphs/StackedBarGraph.tsx +++ b/packages/desktop-client/src/components/reports/graphs/StackedBarGraph.tsx @@ -1,5 +1,5 @@ // @ts-strict-ignore -import React from 'react'; +import React, { useState } from 'react'; import { css } from 'glamor'; import { @@ -18,7 +18,11 @@ import { amountToCurrencyNoDecimal, } from 'loot-core/src/shared/util'; import { type GroupedEntity } from 'loot-core/src/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'; @@ -41,6 +45,7 @@ type PayloadItem = { type CustomTooltipProps = { compact: boolean; + tooltip: string; active?: boolean; payload?: PayloadItem[]; label?: string; @@ -48,6 +53,7 @@ type CustomTooltipProps = { const CustomTooltip = ({ compact, + tooltip, active, payload, label, @@ -83,7 +89,11 @@ const CustomTooltip = ({ key={pay.name} left={pay.name} right={amountToCurrency(pay.value)} - style={{ color: pay.color }} + style={{ + color: pay.color, + textDecoration: + tooltip === pay.name ? 'underline' : 'inherit', + }} /> ) ); @@ -128,25 +138,86 @@ const customLabel = props => { type StackedBarGraphProps = { style?: CSSProperties; data: GroupedEntity; + filters: RuleConditionEntity[]; + groupBy: string; compact?: boolean; viewLabels: boolean; balanceTypeOp: string; + showHiddenCategories?: boolean; + showOffBudget?: boolean; }; export function StackedBarGraph({ style, data, + filters, + groupBy, compact, viewLabels, balanceTypeOp, + showHiddenCategories, + showOffBudget, }: StackedBarGraphProps) { + 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 : 0; + + const onShowActivity = (item, id) => { + 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: item.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 ( <Container style={{ @@ -164,9 +235,12 @@ export function StackedBarGraph({ height={height} data={data.intervalData} margin={{ top: 0, right: 0, left: leftMargin, bottom: 0 }} + style={{ cursor: pointer }} > <Tooltip - content={<CustomTooltip compact={compact} />} + content={ + <CustomTooltip compact={compact} tooltip={tooltip} /> + } formatter={numberFormatterTooltip} isAnimationActive={false} cursor={{ fill: 'transparent' }} @@ -201,6 +275,20 @@ export function StackedBarGraph({ dataKey={entry.name} stackId="a" fill={entry.color} + onMouseLeave={() => { + setPointer(''); + setTooltip(''); + }} + onMouseEnter={() => { + setTooltip(entry.name); + if (!['Group', 'Interval'].includes(groupBy)) { + setPointer('pointer'); + } + }} + onClick={e => + !['Group', 'Interval'].includes(groupBy) && + onShowActivity(e, entry.id) + } > {viewLabels && !compact && ( <LabelList dataKey={entry.name} content={customLabel} /> diff --git a/packages/desktop-client/src/components/reports/reports/CustomReport.jsx b/packages/desktop-client/src/components/reports/reports/CustomReport.jsx index 81292f60e053c74a17eda1f9a95e3c8a8bd11dc3..d69f94675176a4713d1146015035e41001cecd1b 100644 --- a/packages/desktop-client/src/components/reports/reports/CustomReport.jsx +++ b/packages/desktop-client/src/components/reports/reports/CustomReport.jsx @@ -293,17 +293,22 @@ export function CustomReport() { ? defaultsList.modeGraphsMap.get(item) : chooseGraph; if (disabledList.modeGraphsMap.get(item).includes(graphType)) { + setSessionReport('graphType', newGraph); setGraphType(newGraph); } if (disabledList.graphSplitMap.get(item).get(newGraph).includes(groupBy)) { - setGroupBy(defaultsList.graphSplitMap.get(item).get(newGraph)); + const cond = defaultsList.graphSplitMap.get(item).get(newGraph); + setSessionReport('groupBy', cond); + setGroupBy(cond); } if ( disabledList.graphTypeMap.get(item).get(newGraph).includes(balanceType) ) { - setBalanceType(defaultsList.graphTypeMap.get(item).get(newGraph)); + const cond = defaultsList.graphTypeMap.get(item).get(newGraph); + setSessionReport('balanceType', cond); + setBalanceType(cond); } }; @@ -312,12 +317,16 @@ export function CustomReport() { if ( disabledList.graphSplitMap.get(mode).get(chooseGraph).includes(groupBy) ) { - setGroupBy(defaultsList.graphSplitMap.get(mode).get(chooseGraph)); + const cond = defaultsList.graphSplitMap.get(mode).get(chooseGraph); + setSessionReport('groupBy', cond); + setGroupBy(cond); } if ( disabledList.graphTypeMap.get(mode).get(chooseGraph).includes(balanceType) ) { - setBalanceType(defaultsList.graphTypeMap.get(mode).get(chooseGraph)); + const cond = defaultsList.graphTypeMap.get(mode).get(chooseGraph); + setSessionReport('balanceType', cond); + setBalanceType(cond); } }; diff --git a/packages/desktop-client/src/components/reports/spreadsheets/calculateLegend.ts b/packages/desktop-client/src/components/reports/spreadsheets/calculateLegend.ts index db3976bd9d0dfc10f143142446e6ff23a8794ddc..f19bc69049cba4e6791065ce019368ab08845470 100644 --- a/packages/desktop-client/src/components/reports/spreadsheets/calculateLegend.ts +++ b/packages/desktop-client/src/components/reports/spreadsheets/calculateLegend.ts @@ -16,11 +16,16 @@ export function calculateLegend( const colorScale = getColorScale('qualitative'); const chooseData = groupBy === 'Interval' - ? intervalData.map(c => c.date) - : calcDataFiltered.map(c => c.name); - return chooseData.map((name, index) => { + ? intervalData.map(c => { + return { name: c.date, id: null }; + }) + : calcDataFiltered.map(c => { + return { name: c.name, id: c.id }; + }); + return chooseData.map((item, index) => { return { - name, + id: item.id, + name: item.name, color: graphType === 'DonutGraph' ? colorScale[index % colorScale.length] diff --git a/packages/desktop-client/src/components/reports/spreadsheets/custom-spreadsheet.ts b/packages/desktop-client/src/components/reports/spreadsheets/custom-spreadsheet.ts index d950c615e71e509b764afe40fed4f7eff80384d5..d79175b2effb8dfd24839558335dfe9de8c7f311 100644 --- a/packages/desktop-client/src/components/reports/spreadsheets/custom-spreadsheet.ts +++ b/packages/desktop-client/src/components/reports/spreadsheets/custom-spreadsheet.ts @@ -221,6 +221,7 @@ export function createCustomSpreadsheet({ d.format(d.parseISO(`${intervalItem}-01`), "MMM ''yy") : intervalItem, ...stacked, + dateStart: intervalItem, totalDebts: integerToAmount(perIntervalDebts), totalAssets: integerToAmount(perIntervalAssets), totalTotals: integerToAmount(perIntervalDebts + perIntervalAssets), diff --git a/packages/loot-core/src/types/models/reports.d.ts b/packages/loot-core/src/types/models/reports.d.ts index 0b1af87a75cd7f80ca586259f49979e15f53bfb8..19789df763f2890bef09295dff5c146e9a6ddf38 100644 --- a/packages/loot-core/src/types/models/reports.d.ts +++ b/packages/loot-core/src/types/models/reports.d.ts @@ -38,6 +38,7 @@ export interface GroupedEntity { type LegendEntity = { name: string; + id: string | null; color: string; }; diff --git a/upcoming-release-notes/2606.md b/upcoming-release-notes/2606.md new file mode 100644 index 0000000000000000000000000000000000000000..0e8c6321bb5c60112646e6474e2fee355fd1a67b --- /dev/null +++ b/upcoming-release-notes/2606.md @@ -0,0 +1,6 @@ +--- +category: Enhancements +authors: [carkom] +--- + +Enables the ability to show transactions when StackedBarGraph is clicked.