From c98a21b030d1af94dd87f8c7733b643307874dee Mon Sep 17 00:00:00 2001
From: Neil <55785687+carkom@users.noreply.github.com>
Date: Sun, 21 Apr 2024 18:26:32 +0100
Subject: [PATCH] Custom Reports StackedBar show activity (#2606)

* StackedBar Activity

* LineGraph and some session updates

* notes

* table graphs

* Revert "table graphs"

This reverts commit 69b5a441261a7e257fb84f353907d5d101c0fa48.

* revert Line

* notes update

* adust session code and add filters

* review fixes
---
 .../src/components/reports/ChooseGraph.tsx    |  4 +
 .../reports/graphs/StackedBarGraph.tsx        | 94 ++++++++++++++++++-
 .../reports/reports/CustomReport.jsx          | 17 +++-
 .../reports/spreadsheets/calculateLegend.ts   | 13 ++-
 .../spreadsheets/custom-spreadsheet.ts        |  1 +
 .../loot-core/src/types/models/reports.d.ts   |  1 +
 upcoming-release-notes/2606.md                |  6 ++
 7 files changed, 125 insertions(+), 11 deletions(-)
 create mode 100644 upcoming-release-notes/2606.md

diff --git a/packages/desktop-client/src/components/reports/ChooseGraph.tsx b/packages/desktop-client/src/components/reports/ChooseGraph.tsx
index 3e294fbff..97c2c2aca 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 2a3136fe6..7264e94fd 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 81292f60e..d69f94675 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 db3976bd9..f19bc6904 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 d950c615e..d79175b2e 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 0b1af87a7..19789df76 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 000000000..0e8c6321b
--- /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.
-- 
GitLab