From 8a70d2464d20c2bebae4bc6778e82641f410561f Mon Sep 17 00:00:00 2001
From: Neil <55785687+carkom@users.noreply.github.com>
Date: Tue, 7 May 2024 18:02:17 +0100
Subject: [PATCH] Custom Reports: Show table activity (#2696)

* Show table activity

* notes

* lint fix

* add underline

* add linkstyle

* fix table aligment
---
 .../budget/BalanceWithCarryover.tsx           |   6 +-
 .../src/components/reports/ChooseGraph.tsx    |  15 ++-
 .../components/reports/graphs/showActivity.ts |  87 ++++++++++++
 .../reports/graphs/tableGraph/ReportTable.tsx |  28 ++--
 .../graphs/tableGraph/ReportTableList.tsx     |  21 +--
 .../graphs/tableGraph/ReportTableRow.tsx      | 126 +++++++++++++++++-
 .../graphs/tableGraph/ReportTableTotals.tsx   | 104 ++++++++++++++-
 .../desktop-client/src/components/table.tsx   |  12 +-
 .../loot-core/src/types/models/reports.d.ts   |   2 +
 upcoming-release-notes/2696.md                |   6 +
 10 files changed, 369 insertions(+), 38 deletions(-)
 create mode 100644 packages/desktop-client/src/components/reports/graphs/showActivity.ts
 create mode 100644 upcoming-release-notes/2696.md

diff --git a/packages/desktop-client/src/components/budget/BalanceWithCarryover.tsx b/packages/desktop-client/src/components/budget/BalanceWithCarryover.tsx
index bc3275e44..2db27cbe1 100644
--- a/packages/desktop-client/src/components/budget/BalanceWithCarryover.tsx
+++ b/packages/desktop-client/src/components/budget/BalanceWithCarryover.tsx
@@ -16,7 +16,6 @@ type BalanceWithCarryoverProps = {
   goal?: ComponentProps<typeof CellValue>['binding'];
   budgeted?: ComponentProps<typeof CellValue>['binding'];
   disabled?: boolean;
-  style?: CSSProperties;
   balanceStyle?: CSSProperties;
   carryoverStyle?: CSSProperties;
 };
@@ -26,7 +25,6 @@ export function BalanceWithCarryover({
   goal,
   budgeted,
   disabled,
-  style,
   balanceStyle,
   carryoverStyle,
 }: BalanceWithCarryoverProps) {
@@ -36,7 +34,7 @@ export function BalanceWithCarryover({
   const budgetedValue = useSheetValue(budgeted);
   const isGoalTemplatesEnabled = useFeatureFlag('goalTemplatesEnabled');
   return (
-    <View style={style}>
+    <>
       <CellValue
         binding={balance}
         type="financial"
@@ -80,6 +78,6 @@ export function BalanceWithCarryover({
           />
         </View>
       )}
-    </View>
+    </>
   );
 }
diff --git a/packages/desktop-client/src/components/reports/ChooseGraph.tsx b/packages/desktop-client/src/components/reports/ChooseGraph.tsx
index a68045360..c8afbdd50 100644
--- a/packages/desktop-client/src/components/reports/ChooseGraph.tsx
+++ b/packages/desktop-client/src/components/reports/ChooseGraph.tsx
@@ -54,12 +54,6 @@ export function ChooseGraph({
 }: ChooseGraphProps) {
   const graphStyle = compact ? { ...style } : { flexGrow: 1 };
   const balanceTypeOp = ReportOptions.balanceTypeMap.get(balanceType);
-  const groupByData =
-    groupBy === 'Category'
-      ? 'groupedData'
-      : groupBy === 'Interval'
-        ? 'intervalData'
-        : 'data';
 
   const saveScrollWidth = value => {
     setScrollWidth(!value ? 0 : value);
@@ -180,12 +174,15 @@ export function ChooseGraph({
           handleScroll={handleScroll}
           balanceTypeOp={balanceTypeOp}
           groupBy={groupBy}
-          data={data[groupByData]}
+          data={data}
+          filters={filters}
           mode={mode}
           intervalsCount={intervalsCount}
           compact={compact}
           style={rowStyle}
           compactStyle={compactStyle}
+          showHiddenCategories={showHiddenCategories}
+          showOffBudget={showOffBudget}
         />
         <ReportTableTotals
           totalScrollRef={totalScrollRef}
@@ -197,6 +194,10 @@ export function ChooseGraph({
           compact={compact}
           style={rowStyle}
           compactStyle={compactStyle}
+          groupBy={groupBy}
+          filters={filters}
+          showHiddenCategories={showHiddenCategories}
+          showOffBudget={showOffBudget}
         />
       </View>
     );
diff --git a/packages/desktop-client/src/components/reports/graphs/showActivity.ts b/packages/desktop-client/src/components/reports/graphs/showActivity.ts
new file mode 100644
index 000000000..f0f6673bd
--- /dev/null
+++ b/packages/desktop-client/src/components/reports/graphs/showActivity.ts
@@ -0,0 +1,87 @@
+import { type NavigateFunction } from 'react-router-dom';
+
+import { type AccountEntity } from 'loot-core/types/models/account';
+import { type CategoryEntity } from 'loot-core/types/models/category';
+import { type CategoryGroupEntity } from 'loot-core/types/models/category-group';
+import { type RuleConditionEntity } from 'loot-core/types/models/rule';
+
+type showActivityProps = {
+  navigate: NavigateFunction;
+  categories: { list: CategoryEntity[]; grouped: CategoryGroupEntity[] };
+  accounts: AccountEntity[];
+  balanceTypeOp: string;
+  filters: RuleConditionEntity[];
+  showHiddenCategories: boolean;
+  showOffBudget: boolean;
+  type: string;
+  startDate: string;
+  endDate?: string;
+  field?: string;
+  id?: string;
+};
+
+export function showActivity({
+  navigate,
+  categories,
+  accounts,
+  balanceTypeOp,
+  filters,
+  showHiddenCategories,
+  showOffBudget,
+  type,
+  startDate,
+  endDate,
+  field,
+  id,
+}: showActivityProps) {
+  const amount =
+    balanceTypeOp === 'totalDebts' || type === 'debts' ? 'lte' : 'gte';
+  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,
+    id && { field, op: 'is', value: id, type: 'id' },
+    {
+      field: 'date',
+      op: type === 'time' ? 'is' : 'gte',
+      value: startDate,
+      options: { date: true },
+    },
+    type !== 'time' && {
+      field: 'date',
+      op: 'lte',
+      value: endDate,
+      options: { date: true },
+    },
+    !(
+      balanceTypeOp === 'totalTotals' &&
+      (type === 'totals' || type === 'time')
+    ) && {
+      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,
+    },
+  });
+}
diff --git a/packages/desktop-client/src/components/reports/graphs/tableGraph/ReportTable.tsx b/packages/desktop-client/src/components/reports/graphs/tableGraph/ReportTable.tsx
index 45939a4bc..9987321db 100644
--- a/packages/desktop-client/src/components/reports/graphs/tableGraph/ReportTable.tsx
+++ b/packages/desktop-client/src/components/reports/graphs/tableGraph/ReportTable.tsx
@@ -7,7 +7,8 @@ import React, {
 } from 'react';
 import { type RefProp } from 'react-spring';
 
-import { type DataEntity } from 'loot-core/src/types/models/reports';
+import { type GroupedEntity } from 'loot-core/src/types/models/reports';
+import { type RuleConditionEntity } from 'loot-core/types/models/rule';
 
 import { type CSSProperties } from '../../../../style';
 import { Block } from '../../../common/Block';
@@ -22,12 +23,15 @@ type ReportTableProps = {
   handleScroll: UIEventHandler<HTMLDivElement>;
   groupBy: string;
   balanceTypeOp: 'totalDebts' | 'totalTotals' | 'totalAssets';
-  data: DataEntity[];
+  data: GroupedEntity;
+  filters?: RuleConditionEntity[];
   mode: string;
   intervalsCount: number;
   compact: boolean;
   style?: CSSProperties;
   compactStyle?: CSSProperties;
+  showHiddenCategories?: boolean;
+  showOffBudget?: boolean;
 };
 
 export function ReportTable({
@@ -37,11 +41,14 @@ export function ReportTable({
   groupBy,
   balanceTypeOp,
   data,
+  filters,
   mode,
   intervalsCount,
   compact,
   style,
   compactStyle,
+  showHiddenCategories,
+  showOffBudget,
 }: ReportTableProps) {
   const contentRef = useRef<HTMLDivElement>(null);
 
@@ -52,25 +59,22 @@ export function ReportTable({
   });
 
   const renderItem = useCallback(
-    ({
-      item,
-      groupByItem,
-      mode,
-      intervalsCount,
-      compact,
-      style,
-      compactStyle,
-    }) => {
+    ({ item, mode, intervalsCount, compact, style, compactStyle }) => {
       return (
         <ReportTableRow
           item={item}
           balanceTypeOp={balanceTypeOp}
-          groupByItem={groupByItem}
+          groupBy={groupBy}
           mode={mode}
+          filters={filters}
+          startDate={data.startDate}
+          endDate={data.endDate}
           intervalsCount={intervalsCount}
           compact={compact}
           style={style}
           compactStyle={compactStyle}
+          showHiddenCategories={showHiddenCategories}
+          showOffBudget={showOffBudget}
         />
       );
     },
diff --git a/packages/desktop-client/src/components/reports/graphs/tableGraph/ReportTableList.tsx b/packages/desktop-client/src/components/reports/graphs/tableGraph/ReportTableList.tsx
index 1cdc0e160..e619d4e0b 100644
--- a/packages/desktop-client/src/components/reports/graphs/tableGraph/ReportTableList.tsx
+++ b/packages/desktop-client/src/components/reports/graphs/tableGraph/ReportTableList.tsx
@@ -1,14 +1,14 @@
 // @ts-strict-ignore
 import React from 'react';
 
-import { type DataEntity } from 'loot-core/src/types/models/reports';
+import { type GroupedEntity } from 'loot-core/src/types/models/reports';
 
 import { type CSSProperties, theme } from '../../../../style';
 import { View } from '../../../common/View';
 import { Row } from '../../../table';
 
 type ReportTableListProps = {
-  data: DataEntity[];
+  data: GroupedEntity;
   mode?: string;
   intervalsCount?: number;
   groupBy: string;
@@ -28,7 +28,13 @@ export function ReportTableList({
   style,
   compactStyle,
 }: ReportTableListProps) {
-  const groupByItem = groupBy === 'Interval' ? 'date' : 'name';
+  const groupByData =
+    groupBy === 'Category'
+      ? 'groupedData'
+      : groupBy === 'Interval'
+        ? 'intervalData'
+        : 'data';
+  const metadata = data[groupByData];
 
   type RenderRowProps = {
     index: number;
@@ -46,12 +52,11 @@ export function ReportTableList({
   }: RenderRowProps) {
     const item =
       parent_index === undefined
-        ? data[index]
-        : data[parent_index].categories[index];
+        ? metadata[index]
+        : metadata[parent_index].categories[index];
 
     return renderItem({
       item,
-      groupByItem,
       mode,
       intervalsCount,
       compact,
@@ -62,9 +67,9 @@ export function ReportTableList({
 
   return (
     <View>
-      {data ? (
+      {metadata ? (
         <View>
-          {data.map((item, index) => {
+          {metadata.map((item, index) => {
             return (
               <View key={item.id}>
                 <RenderRow
diff --git a/packages/desktop-client/src/components/reports/graphs/tableGraph/ReportTableRow.tsx b/packages/desktop-client/src/components/reports/graphs/tableGraph/ReportTableRow.tsx
index fabe9698f..54c1c650f 100644
--- a/packages/desktop-client/src/components/reports/graphs/tableGraph/ReportTableRow.tsx
+++ b/packages/desktop-client/src/components/reports/graphs/tableGraph/ReportTableRow.tsx
@@ -6,33 +6,74 @@ import {
   integerToCurrency,
 } from 'loot-core/src/shared/util';
 import { type DataEntity } 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 { useResponsive } from '../../../../ResponsiveProvider';
 import { type CSSProperties, theme } from '../../../../style';
 import { Row, Cell } from '../../../table';
+import { showActivity } from '../showActivity';
 
 type ReportTableRowProps = {
   item: DataEntity;
   balanceTypeOp: 'totalAssets' | 'totalDebts' | 'totalTotals';
-  groupByItem: 'id' | 'name';
+  groupBy: string;
   mode: string;
+  filters?: RuleConditionEntity[];
+  startDate?: string;
+  endDate?: string;
   intervalsCount: number;
   compact: boolean;
   style?: CSSProperties;
   compactStyle?: CSSProperties;
+  showHiddenCategories?: boolean;
+  showOffBudget?: boolean;
 };
 
 export const ReportTableRow = memo(
   ({
     item,
     balanceTypeOp,
-    groupByItem,
+    groupBy,
     mode,
+    filters = [],
+    startDate = '',
+    endDate,
     intervalsCount,
     compact,
     style,
     compactStyle,
+    showHiddenCategories = false,
+    showOffBudget = false,
   }: ReportTableRowProps) => {
     const average = amountToInteger(item[balanceTypeOp]) / intervalsCount;
+    const groupByItem = groupBy === 'Interval' ? 'date' : 'name';
+
+    const navigate = useNavigate();
+    const { isNarrowWidth } = useResponsive();
+    const categories = useCategories();
+    const accounts = useAccounts();
+
+    const pointer =
+      !isNarrowWidth &&
+      !['Group', 'Interval'].includes(groupBy) &&
+      !categories.grouped.map(g => g.id).includes(item.id)
+        ? 'pointer'
+        : 'inherit';
+
+    const hoverUnderline =
+      !isNarrowWidth &&
+      !['Group', 'Interval'].includes(groupBy) &&
+      !categories.grouped.map(g => g.id).includes(item.id)
+        ? {
+            cursor: pointer,
+            ':hover': { textDecoration: 'underline' },
+            flexGrow: 0,
+          }
+        : {};
+
     return (
       <Row
         key={item.id}
@@ -45,7 +86,7 @@ export const ReportTableRow = memo(
       >
         <Cell
           value={item[groupByItem]}
-          title={item[groupByItem].length > 12 ? item[groupByItem] : undefined}
+          title={item[groupByItem] ?? undefined}
           style={{
             width: compact ? 80 : 125,
             flexShrink: 0,
@@ -60,6 +101,7 @@ export const ReportTableRow = memo(
                   style={{
                     minWidth: compact ? 50 : 85,
                   }}
+                  linkStyle={hoverUnderline}
                   valueStyle={compactStyle}
                   value={amountToCurrency(intervalItem[balanceTypeOp])}
                   title={
@@ -67,6 +109,24 @@ export const ReportTableRow = memo(
                       ? amountToCurrency(intervalItem[balanceTypeOp])
                       : undefined
                   }
+                  onClick={() =>
+                    !isNarrowWidth &&
+                    !['Group', 'Interval'].includes(groupBy) &&
+                    !categories.grouped.map(g => g.id).includes(item.id) &&
+                    showActivity({
+                      navigate,
+                      categories,
+                      accounts,
+                      balanceTypeOp,
+                      filters,
+                      showHiddenCategories,
+                      showOffBudget,
+                      type: 'time',
+                      startDate: intervalItem.dateLookup || '',
+                      field: groupBy.toLowerCase(),
+                      id: item.id,
+                    })
+                  }
                   width="flex"
                   privacyFilter
                 />
@@ -86,7 +146,27 @@ export const ReportTableRow = memo(
                   style={{
                     minWidth: compact ? 50 : 85,
                   }}
+                  linkStyle={hoverUnderline}
                   valueStyle={compactStyle}
+                  onClick={() =>
+                    !isNarrowWidth &&
+                    !['Group', 'Interval'].includes(groupBy) &&
+                    !categories.grouped.map(g => g.id).includes(item.id) &&
+                    showActivity({
+                      navigate,
+                      categories,
+                      accounts,
+                      balanceTypeOp,
+                      filters,
+                      showHiddenCategories,
+                      showOffBudget,
+                      type: 'assets',
+                      startDate,
+                      endDate,
+                      field: groupBy.toLowerCase(),
+                      id: item.id,
+                    })
+                  }
                 />
                 <Cell
                   value={amountToCurrency(item.totalDebts)}
@@ -100,7 +180,27 @@ export const ReportTableRow = memo(
                   style={{
                     minWidth: compact ? 50 : 85,
                   }}
+                  linkStyle={hoverUnderline}
                   valueStyle={compactStyle}
+                  onClick={() =>
+                    !isNarrowWidth &&
+                    !['Group', 'Interval'].includes(groupBy) &&
+                    !categories.grouped.map(g => g.id).includes(item.id) &&
+                    showActivity({
+                      navigate,
+                      categories,
+                      accounts,
+                      balanceTypeOp,
+                      filters,
+                      showHiddenCategories,
+                      showOffBudget,
+                      type: 'debts',
+                      startDate,
+                      endDate,
+                      field: groupBy.toLowerCase(),
+                      id: item.id,
+                    })
+                  }
                 />
               </>
             )}
@@ -115,7 +215,27 @@ export const ReportTableRow = memo(
             fontWeight: 600,
             minWidth: compact ? 50 : 85,
           }}
+          linkStyle={hoverUnderline}
           valueStyle={compactStyle}
+          onClick={() =>
+            !isNarrowWidth &&
+            !['Group', 'Interval'].includes(groupBy) &&
+            !categories.grouped.map(g => g.id).includes(item.id) &&
+            showActivity({
+              navigate,
+              categories,
+              accounts,
+              balanceTypeOp,
+              filters,
+              showHiddenCategories,
+              showOffBudget,
+              type: 'totals',
+              startDate,
+              endDate,
+              field: groupBy.toLowerCase(),
+              id: item.id,
+            })
+          }
           width="flex"
           privacyFilter
         />
diff --git a/packages/desktop-client/src/components/reports/graphs/tableGraph/ReportTableTotals.tsx b/packages/desktop-client/src/components/reports/graphs/tableGraph/ReportTableTotals.tsx
index 8cae27600..48b16e581 100644
--- a/packages/desktop-client/src/components/reports/graphs/tableGraph/ReportTableTotals.tsx
+++ b/packages/desktop-client/src/components/reports/graphs/tableGraph/ReportTableTotals.tsx
@@ -8,12 +8,18 @@ import {
   integerToCurrency,
 } 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 { useResponsive } from '../../../../ResponsiveProvider';
 import { theme } from '../../../../style';
 import { styles } from '../../../../style/styles';
 import { type CSSProperties } from '../../../../style/types';
 import { View } from '../../../common/View';
 import { Row, Cell } from '../../../table';
+import { showActivity } from '../showActivity';
 
 type ReportTableTotalsProps = {
   data: GroupedEntity;
@@ -25,6 +31,10 @@ type ReportTableTotalsProps = {
   compact: boolean;
   style?: CSSProperties;
   compactStyle?: CSSProperties;
+  groupBy: string;
+  filters?: RuleConditionEntity[];
+  showHiddenCategories?: boolean;
+  showOffBudget?: boolean;
 };
 
 export function ReportTableTotals({
@@ -37,6 +47,10 @@ export function ReportTableTotals({
   compact,
   style,
   compactStyle,
+  groupBy,
+  filters = [],
+  showHiddenCategories = false,
+  showOffBudget = false,
 }: ReportTableTotalsProps) {
   const [scrollWidthTotals, setScrollWidthTotals] = useState(0);
 
@@ -44,15 +58,34 @@ export function ReportTableTotals({
     if (totalScrollRef.current) {
       const [parent, child] = [
         totalScrollRef.current.offsetParent
-          ? totalScrollRef.current.parentElement.scrollHeight
+          ? totalScrollRef.current.parentElement.scrollHeight || 0
           : 0,
         totalScrollRef.current ? totalScrollRef.current.scrollHeight : 0,
       ];
       setScrollWidthTotals(parent > 0 && child > 0 && parent - child);
     }
   });
-
   const average = amountToInteger(data[balanceTypeOp]) / intervalsCount;
+
+  const navigate = useNavigate();
+  const { isNarrowWidth } = useResponsive();
+  const categories = useCategories();
+  const accounts = useAccounts();
+
+  const pointer =
+    !isNarrowWidth && !['Group', 'Interval'].includes(groupBy)
+      ? 'pointer'
+      : 'inherit';
+
+  const hoverUnderline =
+    !isNarrowWidth && !['Group', 'Interval'].includes(groupBy)
+      ? {
+          cursor: pointer,
+          ':hover': { textDecoration: 'underline' },
+          flexGrow: 0,
+        }
+      : {};
+
   return (
     <Row
       collapsed={true}
@@ -99,6 +132,7 @@ export function ReportTableTotals({
                   style={{
                     minWidth: compact ? 50 : 85,
                   }}
+                  linkStyle={hoverUnderline}
                   valueStyle={compactStyle}
                   key={amountToCurrency(item[balanceTypeOp])}
                   value={amountToCurrency(item[balanceTypeOp])}
@@ -107,6 +141,21 @@ export function ReportTableTotals({
                       ? amountToCurrency(item[balanceTypeOp])
                       : undefined
                   }
+                  onClick={() =>
+                    !isNarrowWidth &&
+                    !['Group', 'Interval'].includes(groupBy) &&
+                    showActivity({
+                      navigate,
+                      categories,
+                      accounts,
+                      balanceTypeOp,
+                      filters,
+                      showHiddenCategories,
+                      showOffBudget,
+                      type: 'time',
+                      startDate: item.dateStart || '',
+                    })
+                  }
                   width="flex"
                   privacyFilter
                 />
@@ -118,6 +167,7 @@ export function ReportTableTotals({
                   style={{
                     minWidth: compact ? 50 : 85,
                   }}
+                  linkStyle={hoverUnderline}
                   valueStyle={compactStyle}
                   value={amountToCurrency(data.totalAssets)}
                   title={
@@ -125,6 +175,22 @@ export function ReportTableTotals({
                       ? amountToCurrency(data.totalAssets)
                       : undefined
                   }
+                  onClick={() =>
+                    !isNarrowWidth &&
+                    !['Group', 'Interval'].includes(groupBy) &&
+                    showActivity({
+                      navigate,
+                      categories,
+                      accounts,
+                      balanceTypeOp,
+                      filters,
+                      showHiddenCategories,
+                      showOffBudget,
+                      type: 'assets',
+                      startDate: data.startDate || '',
+                      endDate: data.endDate,
+                    })
+                  }
                   width="flex"
                   privacyFilter
                 />
@@ -132,6 +198,7 @@ export function ReportTableTotals({
                   style={{
                     minWidth: compact ? 50 : 85,
                   }}
+                  linkStyle={hoverUnderline}
                   valueStyle={compactStyle}
                   value={amountToCurrency(data.totalDebts)}
                   title={
@@ -139,6 +206,22 @@ export function ReportTableTotals({
                       ? amountToCurrency(data.totalDebts)
                       : undefined
                   }
+                  onClick={() =>
+                    !isNarrowWidth &&
+                    !['Group', 'Interval'].includes(groupBy) &&
+                    showActivity({
+                      navigate,
+                      categories,
+                      accounts,
+                      balanceTypeOp,
+                      filters,
+                      showHiddenCategories,
+                      showOffBudget,
+                      type: 'debts',
+                      startDate: data.startDate || '',
+                      endDate: data.endDate,
+                    })
+                  }
                   width="flex"
                   privacyFilter
                 />
@@ -148,6 +231,7 @@ export function ReportTableTotals({
           style={{
             minWidth: compact ? 50 : 85,
           }}
+          linkStyle={hoverUnderline}
           valueStyle={compactStyle}
           value={amountToCurrency(data[balanceTypeOp])}
           title={
@@ -155,6 +239,22 @@ export function ReportTableTotals({
               ? amountToCurrency(data[balanceTypeOp])
               : undefined
           }
+          onClick={() =>
+            !isNarrowWidth &&
+            !['Group', 'Interval'].includes(groupBy) &&
+            showActivity({
+              navigate,
+              categories,
+              accounts,
+              balanceTypeOp,
+              filters,
+              showHiddenCategories,
+              showOffBudget,
+              type: 'totals',
+              startDate: data.startDate || '',
+              endDate: data.endDate,
+            })
+          }
           width="flex"
           privacyFilter
         />
diff --git a/packages/desktop-client/src/components/table.tsx b/packages/desktop-client/src/components/table.tsx
index 96fd84131..cfd2ad90e 100644
--- a/packages/desktop-client/src/components/table.tsx
+++ b/packages/desktop-client/src/components/table.tsx
@@ -113,7 +113,8 @@ export const Field = forwardRef<HTMLDivElement, FieldProps>(function Field(
 export function UnexposedCellContent({
   value,
   formatter,
-}: Pick<CellProps, 'value' | 'formatter'>) {
+  linkStyle,
+}: Pick<CellProps, 'value' | 'formatter' | 'linkStyle'>) {
   return (
     <Text
       style={{
@@ -121,6 +122,7 @@ export function UnexposedCellContent({
         whiteSpace: 'nowrap',
         overflow: 'hidden',
         textOverflow: 'ellipsis',
+        ...linkStyle,
       }}
     >
       {formatter ? formatter(value) : value}
@@ -139,6 +141,7 @@ type CellProps = Omit<ComponentProps<typeof View>, 'children' | 'value'> & {
   unexposedContent?: ReactNode;
   value?: string;
   valueStyle?: CSSProperties;
+  linkStyle?: CSSProperties;
   onExpose?: (name: string) => void;
   privacyFilter?: ComponentProps<
     typeof ConditionalPrivacyFilter
@@ -158,6 +161,7 @@ export function Cell({
   plain,
   style,
   valueStyle,
+  linkStyle,
   unexposedContent,
   privacyFilter,
   ...viewProps
@@ -229,7 +233,11 @@ export function Cell({
             }
           >
             {unexposedContent || (
-              <UnexposedCellContent value={value} formatter={formatter} />
+              <UnexposedCellContent
+                linkStyle={linkStyle}
+                value={value}
+                formatter={formatter}
+              />
             )}
           </View>
         )}
diff --git a/packages/loot-core/src/types/models/reports.d.ts b/packages/loot-core/src/types/models/reports.d.ts
index eb9fe981a..f1c01d6ab 100644
--- a/packages/loot-core/src/types/models/reports.d.ts
+++ b/packages/loot-core/src/types/models/reports.d.ts
@@ -86,6 +86,7 @@ export type ItemEntity = {
 
 export type IntervalData = {
   date: string;
+  dateLookup: string;
   totalAssets: number;
   totalDebts: number;
   totalTotals: number;
@@ -95,6 +96,7 @@ export interface DataEntity {
   id: string;
   name: string;
   date?: string;
+  dateStart?: string;
   intervalData: IntervalData[];
   categories?: ItemEntity[];
   totalAssets: number;
diff --git a/upcoming-release-notes/2696.md b/upcoming-release-notes/2696.md
new file mode 100644
index 000000000..588b982ac
--- /dev/null
+++ b/upcoming-release-notes/2696.md
@@ -0,0 +1,6 @@
+---
+category: Enhancements
+authors: [carkom]
+---
+
+Custom Reports - show transactions when table cell is clicked.
-- 
GitLab