From 3f1fd55a7b97403a9792c73eaaf4877b050de24c Mon Sep 17 00:00:00 2001 From: Neil <55785687+carkom@users.noreply.github.com> Date: Mon, 20 May 2024 17:23:59 +0100 Subject: [PATCH] Custom Reports: Table Totals Callback (#2768) * Table Totals Callback * notes * Update packages/desktop-client/src/components/reports/graphs/tableGraph/ReportTableRow.tsx Co-authored-by: Matiss Janis Aboltins <matiss@mja.lv> --------- Co-authored-by: Matiss Janis Aboltins <matiss@mja.lv> --- .../src/components/reports/ChooseGraph.tsx | 67 +--- .../graphs/tableGraph/RenderTableRow.tsx | 9 - .../reports/graphs/tableGraph/ReportTable.tsx | 133 +++++-- .../graphs/tableGraph/ReportTableHeader.tsx | 10 +- .../graphs/tableGraph/ReportTableList.tsx | 12 - .../graphs/tableGraph/ReportTableRow.tsx | 359 +++++++++--------- .../graphs/tableGraph/ReportTableTotals.tsx | 307 ++++----------- .../spreadsheets/custom-spreadsheet.ts | 2 +- .../reports/spreadsheets/recalculate.ts | 2 +- .../loot-core/src/types/models/reports.d.ts | 3 +- upcoming-release-notes/2768.md | 6 + 11 files changed, 391 insertions(+), 519 deletions(-) create mode 100644 upcoming-release-notes/2768.md diff --git a/packages/desktop-client/src/components/reports/ChooseGraph.tsx b/packages/desktop-client/src/components/reports/ChooseGraph.tsx index 6d2e557e1..b195b7eec 100644 --- a/packages/desktop-client/src/components/reports/ChooseGraph.tsx +++ b/packages/desktop-client/src/components/reports/ChooseGraph.tsx @@ -5,7 +5,6 @@ import { type RuleConditionEntity } from 'loot-core/types/models/rule'; import { type CSSProperties } from '../../style'; import { styles } from '../../style/styles'; -import { View } from '../common/View'; import { AreaGraph } from './graphs/AreaGraph'; import { BarGraph } from './graphs/BarGraph'; @@ -14,8 +13,6 @@ import { DonutGraph } from './graphs/DonutGraph'; import { LineGraph } from './graphs/LineGraph'; import { StackedBarGraph } from './graphs/StackedBarGraph'; import { ReportTable } from './graphs/tableGraph/ReportTable'; -import { ReportTableHeader } from './graphs/tableGraph/ReportTableHeader'; -import { ReportTableTotals } from './graphs/tableGraph/ReportTableTotals'; import { ReportOptions } from './ReportOptions'; type ChooseGraphProps = { @@ -170,51 +167,25 @@ export function ChooseGraph({ } if (graphType === 'TableGraph') { return ( - <View> - <ReportTableHeader - headerScrollRef={headerScrollRef} - handleScroll={handleScroll} - data={data.intervalData} - groupBy={groupBy} - interval={interval} - balanceType={balanceType} - compact={compact} - style={rowStyle} - compactStyle={compactStyle} - mode={mode} - /> - <ReportTable - saveScrollWidth={saveScrollWidth} - listScrollRef={listScrollRef} - handleScroll={handleScroll} - balanceTypeOp={balanceTypeOp} - groupBy={groupBy} - data={data} - filters={filters} - mode={mode} - intervalsCount={intervalsCount} - compact={compact} - style={rowStyle} - compactStyle={compactStyle} - showHiddenCategories={showHiddenCategories} - showOffBudget={showOffBudget} - /> - <ReportTableTotals - totalScrollRef={totalScrollRef} - handleScroll={handleScroll} - data={data} - mode={mode} - balanceTypeOp={balanceTypeOp} - intervalsCount={intervalsCount} - compact={compact} - style={rowStyle} - compactStyle={compactStyle} - groupBy={groupBy} - filters={filters} - showHiddenCategories={showHiddenCategories} - showOffBudget={showOffBudget} - /> - </View> + <ReportTable + saveScrollWidth={saveScrollWidth} + headerScrollRef={headerScrollRef} + listScrollRef={listScrollRef} + totalScrollRef={totalScrollRef} + handleScroll={handleScroll} + balanceTypeOp={balanceTypeOp} + groupBy={groupBy} + data={data} + filters={filters} + mode={mode} + intervalsCount={intervalsCount} + interval={interval} + compact={compact} + style={rowStyle} + compactStyle={compactStyle} + showHiddenCategories={showHiddenCategories} + showOffBudget={showOffBudget} + /> ); } return null; diff --git a/packages/desktop-client/src/components/reports/graphs/tableGraph/RenderTableRow.tsx b/packages/desktop-client/src/components/reports/graphs/tableGraph/RenderTableRow.tsx index aad73c342..3b1b92c5c 100644 --- a/packages/desktop-client/src/components/reports/graphs/tableGraph/RenderTableRow.tsx +++ b/packages/desktop-client/src/components/reports/graphs/tableGraph/RenderTableRow.tsx @@ -10,25 +10,19 @@ import { type renderRowProps } from './ReportTable'; type RenderTableRowProps = { index: number; parent_index?: number; - compact: boolean; renderRow: (arg: renderRowProps) => ReactNode; - intervalsCount: number; mode: string; metadata: GroupedEntity[]; style?: CSSProperties; - compactStyle?: CSSProperties; }; export function RenderTableRow({ index, parent_index, - compact, renderRow, - intervalsCount, mode, metadata, style, - compactStyle, }: RenderTableRowProps) { const child = metadata[index]; const parent = @@ -45,10 +39,7 @@ export function RenderTableRow({ {renderRow({ item, mode, - intervalsCount, - compact, style, - compactStyle, })} </View> ); 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 c45be017e..78882fc98 100644 --- a/packages/desktop-client/src/components/reports/graphs/tableGraph/ReportTable.tsx +++ b/packages/desktop-client/src/components/reports/graphs/tableGraph/ReportTable.tsx @@ -16,12 +16,16 @@ import { type CSSProperties } from '../../../../style'; import { Block } from '../../../common/Block'; import { View } from '../../../common/View'; +import { ReportTableHeader } from './ReportTableHeader'; import { ReportTableList } from './ReportTableList'; import { ReportTableRow } from './ReportTableRow'; +import { ReportTableTotals } from './ReportTableTotals'; type ReportTableProps = { saveScrollWidth: (value: number) => void; + headerScrollRef: RefObject<HTMLDivElement>; listScrollRef: RefObject<HTMLDivElement>; + totalScrollRef: RefObject<HTMLDivElement>; handleScroll: UIEventHandler<HTMLDivElement>; groupBy: string; balanceTypeOp: 'totalDebts' | 'totalTotals' | 'totalAssets'; @@ -29,6 +33,7 @@ type ReportTableProps = { filters?: RuleConditionEntity[]; mode: string; intervalsCount: number; + interval: string; compact: boolean; style?: CSSProperties; compactStyle?: CSSProperties; @@ -36,18 +41,25 @@ type ReportTableProps = { showOffBudget?: boolean; }; +export type renderTotalsProps = { + metadata: GroupedEntity; + mode: string; + totalsStyle: CSSProperties; + testStyle: CSSProperties; + scrollWidthTotals: number; +}; + export type renderRowProps = { item: GroupedEntity; mode: string; - intervalsCount: number; - compact: boolean; style?: CSSProperties; - compactStyle?: CSSProperties; }; export function ReportTable({ saveScrollWidth, + headerScrollRef, listScrollRef, + totalScrollRef, handleScroll, groupBy, balanceTypeOp, @@ -55,6 +67,7 @@ export function ReportTable({ filters, mode, intervalsCount, + interval, compact, style, compactStyle, @@ -69,18 +82,37 @@ export function ReportTable({ } }); - const renderRow = useCallback( + const renderRow = useCallback(({ item, mode, style }: renderRowProps) => { + return ( + <ReportTableRow + item={item} + balanceTypeOp={balanceTypeOp} + groupBy={groupBy} + mode={mode} + filters={filters} + startDate={data.startDate} + endDate={data.endDate} + intervalsCount={intervalsCount} + compact={compact} + style={style} + compactStyle={compactStyle} + showHiddenCategories={showHiddenCategories} + showOffBudget={showOffBudget} + /> + ); + }, []); + + const renderTotals = useCallback( ({ - item, + metadata, mode, - intervalsCount, - compact, - style, - compactStyle, - }: renderRowProps) => { + totalsStyle, + testStyle, + scrollWidthTotals, + }: renderTotalsProps) => { return ( <ReportTableRow - item={item} + item={metadata} balanceTypeOp={balanceTypeOp} groupBy={groupBy} mode={mode} @@ -89,10 +121,14 @@ export function ReportTable({ endDate={data.endDate} intervalsCount={intervalsCount} compact={compact} - style={style} + style={totalsStyle} compactStyle={compactStyle} showHiddenCategories={showHiddenCategories} showOffBudget={showOffBudget} + totalStyle={testStyle} + totalScrollRef={totalScrollRef} + handleScroll={handleScroll} + height={32 + scrollWidthTotals} /> ); }, @@ -100,39 +136,58 @@ export function ReportTable({ ); return ( - <View - style={{ - flex: 1, - flexDirection: 'row', - outline: 'none', - '& .animated .animated-row': { transition: '.25s transform' }, - }} - tabIndex={1} - > - <Block - innerRef={listScrollRef} - onScroll={handleScroll} - id="list" + <View> + <ReportTableHeader + headerScrollRef={headerScrollRef} + handleScroll={handleScroll} + data={data.intervalData} + groupBy={groupBy} + interval={interval} + balanceTypeOp={balanceTypeOp} + compact={compact} + style={style} + compactStyle={compactStyle} + mode={mode} + /> + <View style={{ - overflowY: 'auto', - scrollbarWidth: 'none', - '::-webkit-scrollbar': { display: 'none' }, flex: 1, + flexDirection: 'row', outline: 'none', '& .animated .animated-row': { transition: '.25s transform' }, }} + tabIndex={1} > - <ReportTableList - data={data} - intervalsCount={intervalsCount} - mode={mode} - groupBy={groupBy} - renderRow={renderRow} - compact={compact} - style={style} - compactStyle={compactStyle} - /> - </Block> + <Block + innerRef={listScrollRef} + onScroll={handleScroll} + id="list" + style={{ + overflowY: 'auto', + scrollbarWidth: 'none', + '::-webkit-scrollbar': { display: 'none' }, + flex: 1, + outline: 'none', + '& .animated .animated-row': { transition: '.25s transform' }, + }} + > + <ReportTableList + data={data} + mode={mode} + groupBy={groupBy} + renderRow={renderRow} + style={style} + /> + </Block> + </View> + <ReportTableTotals + data={data} + mode={mode} + totalScrollRef={totalScrollRef} + compact={compact} + style={style} + renderTotals={renderTotals} + /> </View> ); } diff --git a/packages/desktop-client/src/components/reports/graphs/tableGraph/ReportTableHeader.tsx b/packages/desktop-client/src/components/reports/graphs/tableGraph/ReportTableHeader.tsx index 4543fda45..2dea557de 100644 --- a/packages/desktop-client/src/components/reports/graphs/tableGraph/ReportTableHeader.tsx +++ b/packages/desktop-client/src/components/reports/graphs/tableGraph/ReportTableHeader.tsx @@ -12,12 +12,12 @@ type ReportTableHeaderProps = { groupBy: string; interval: string; data: IntervalEntity[]; - balanceType: string; + balanceTypeOp: 'totalDebts' | 'totalTotals' | 'totalAssets'; headerScrollRef: RefObject<HTMLDivElement>; handleScroll: UIEventHandler<HTMLDivElement>; compact: boolean; - style: CSSProperties; - compactStyle: CSSProperties; + style?: CSSProperties; + compactStyle?: CSSProperties; mode: string; }; @@ -25,7 +25,7 @@ export function ReportTableHeader({ groupBy, interval, data, - balanceType, + balanceTypeOp, headerScrollRef, handleScroll, compact, @@ -84,7 +84,7 @@ export function ReportTableHeader({ /> ); }) - : balanceType === 'Net' && ( + : balanceTypeOp === 'totalTotals' && ( <> <Cell style={{ 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 f2076605a..f3ba6bad8 100644 --- a/packages/desktop-client/src/components/reports/graphs/tableGraph/ReportTableList.tsx +++ b/packages/desktop-client/src/components/reports/graphs/tableGraph/ReportTableList.tsx @@ -15,23 +15,17 @@ import { type renderRowProps } from './ReportTable'; type ReportTableListProps = { data: DataEntity; mode: string; - intervalsCount: number; groupBy: string; renderRow: (arg: renderRowProps) => ReactNode; - compact: boolean; style?: CSSProperties; - compactStyle?: CSSProperties; }; export function ReportTableList({ data, - intervalsCount, mode, groupBy, renderRow, - compact, style, - compactStyle, }: ReportTableListProps) { const metadata: GroupedEntity[] | undefined = groupBy === 'Category' @@ -60,9 +54,7 @@ export function ReportTableList({ <View key={index}> <RenderTableRow index={index} - compact={compact} renderRow={renderRow} - intervalsCount={intervalsCount} mode={mode} metadata={metadata} style={{ @@ -73,7 +65,6 @@ export function ReportTableList({ }), ...style, }} - compactStyle={compactStyle} /> {item.categories && ( <> @@ -84,14 +75,11 @@ export function ReportTableList({ <RenderTableRow key={category.id} index={i} - compact={compact} renderRow={renderRow} - intervalsCount={intervalsCount} mode={mode} metadata={metadata} parent_index={index} style={style} - compactStyle={compactStyle} /> ); }, 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 d759a90d7..cf3de8cf1 100644 --- a/packages/desktop-client/src/components/reports/graphs/tableGraph/ReportTableRow.tsx +++ b/packages/desktop-client/src/components/reports/graphs/tableGraph/ReportTableRow.tsx @@ -1,4 +1,4 @@ -import React, { memo } from 'react'; +import React, { memo, type RefObject, type UIEventHandler } from 'react'; import { amountToCurrency, @@ -13,6 +13,7 @@ import { useCategories } from '../../../../hooks/useCategories'; import { useNavigate } from '../../../../hooks/useNavigate'; import { useResponsive } from '../../../../ResponsiveProvider'; import { type CSSProperties, theme } from '../../../../style'; +import { View } from '../../../common/View'; import { Row, Cell } from '../../../table'; import { showActivity } from '../showActivity'; @@ -28,8 +29,12 @@ type ReportTableRowProps = { compact: boolean; style?: CSSProperties; compactStyle?: CSSProperties; + totalStyle?: CSSProperties; showHiddenCategories?: boolean; showOffBudget?: boolean; + totalScrollRef?: RefObject<HTMLDivElement>; + handleScroll?: UIEventHandler<HTMLDivElement>; + height?: number; }; export const ReportTableRow = memo( @@ -45,8 +50,12 @@ export const ReportTableRow = memo( compact, style, compactStyle, + totalStyle, showHiddenCategories = false, showOffBudget = false, + totalScrollRef, + handleScroll, + height, }: ReportTableRowProps) => { const average = amountToInteger(item[balanceTypeOp]) / intervalsCount; const groupByItem = groupBy === 'Interval' ? 'date' : 'name'; @@ -77,6 +86,7 @@ export const ReportTableRow = memo( return ( <Row key={item.id} + height={height} collapsed={true} style={{ color: theme.tableText, @@ -84,176 +94,187 @@ export const ReportTableRow = memo( ...style, }} > - <Cell - value={item[groupByItem]} - title={item[groupByItem] ?? undefined} + <View + innerRef={totalScrollRef} + onScroll={handleScroll} + id={totalScrollRef ? 'total' : item.id} style={{ - width: compact ? 80 : 125, - flexShrink: 0, + flexDirection: 'row', + flex: 1, + ...totalStyle, }} - valueStyle={compactStyle} - /> - {item.intervalData && mode === 'time' - ? item.intervalData.map(intervalItem => { - return ( - <Cell - key={amountToCurrency(intervalItem[balanceTypeOp])} - style={{ - minWidth: compact ? 50 : 85, - }} - linkStyle={hoverUnderline} - valueStyle={compactStyle} - value={amountToCurrency(intervalItem[balanceTypeOp])} - title={ - Math.abs(intervalItem[balanceTypeOp]) > 100000 - ? 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 - /> - ); - }) - : balanceTypeOp === 'totalTotals' && ( - <> - <Cell - value={amountToCurrency(item.totalAssets)} - title={ - Math.abs(item.totalAssets) > 100000 - ? amountToCurrency(item.totalAssets) - : undefined - } - width="flex" - privacyFilter - 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)} - title={ - Math.abs(item.totalDebts) > 100000 - ? amountToCurrency(item.totalDebts) - : undefined - } - width="flex" - privacyFilter - 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, - }) - } - /> - </> - )} - <Cell - value={amountToCurrency(item[balanceTypeOp])} - title={ - Math.abs(item[balanceTypeOp]) > 100000 - ? amountToCurrency(item[balanceTypeOp]) - : undefined - } - style={{ - 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 - /> - <Cell - value={integerToCurrency(Math.round(average))} - title={ - Math.abs(Math.round(average / 100)) > 100000 - ? integerToCurrency(Math.round(average)) - : undefined - } - style={{ - fontWeight: 600, - minWidth: compact ? 50 : 85, - }} - valueStyle={compactStyle} - width="flex" - privacyFilter - /> + > + <Cell + value={item[groupByItem]} + title={item[groupByItem]} + style={{ + width: compact ? 80 : 125, + flexShrink: 0, + }} + valueStyle={compactStyle} + /> + {item.intervalData && mode === 'time' + ? item.intervalData.map((intervalItem, index) => { + return ( + <Cell + key={index} + style={{ + minWidth: compact ? 50 : 85, + }} + linkStyle={hoverUnderline} + valueStyle={compactStyle} + value={amountToCurrency(intervalItem[balanceTypeOp])} + title={ + Math.abs(intervalItem[balanceTypeOp]) > 100000 + ? 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.intervalStartDate || '', + field: groupBy.toLowerCase(), + id: item.id, + }) + } + width="flex" + privacyFilter + /> + ); + }) + : balanceTypeOp === 'totalTotals' && ( + <> + <Cell + value={amountToCurrency(item.totalAssets)} + title={ + Math.abs(item.totalAssets) > 100000 + ? amountToCurrency(item.totalAssets) + : undefined + } + width="flex" + privacyFilter + 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)} + title={ + Math.abs(item.totalDebts) > 100000 + ? amountToCurrency(item.totalDebts) + : undefined + } + width="flex" + privacyFilter + 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, + }) + } + /> + </> + )} + <Cell + value={amountToCurrency(item[balanceTypeOp])} + title={ + Math.abs(item[balanceTypeOp]) > 100000 + ? amountToCurrency(item[balanceTypeOp]) + : undefined + } + style={{ + 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 + /> + <Cell + value={integerToCurrency(Math.round(average))} + title={ + Math.abs(Math.round(average / 100)) > 100000 + ? integerToCurrency(Math.round(average)) + : undefined + } + style={{ + fontWeight: 600, + minWidth: compact ? 50 : 85, + }} + valueStyle={compactStyle} + width="flex" + privacyFilter + /> + </View> </Row> ); }, 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 f9f01a6df..8690fc709 100644 --- a/packages/desktop-client/src/components/reports/graphs/tableGraph/ReportTableTotals.tsx +++ b/packages/desktop-client/src/components/reports/graphs/tableGraph/ReportTableTotals.tsx @@ -1,59 +1,67 @@ import React, { - type UIEventHandler, + type ReactNode, useLayoutEffect, useState, type RefObject, } from 'react'; import { - amountToCurrency, - amountToInteger, - 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'; + type GroupedEntity, + type DataEntity, +} from 'loot-core/src/types/models/reports'; -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'; + +import { type renderTotalsProps } from './ReportTable'; + +type RenderTotalsRowProps = { + metadata: GroupedEntity; + mode: string; + totalsStyle: CSSProperties; + testStyle: CSSProperties; + scrollWidthTotals: number; + renderTotals: (arg: renderTotalsProps) => ReactNode; +}; +function RenderTotalsRow({ + metadata, + mode, + totalsStyle, + testStyle, + scrollWidthTotals, + renderTotals, +}: RenderTotalsRowProps) { + return ( + <View> + {renderTotals({ + metadata, + mode, + totalsStyle, + testStyle, + scrollWidthTotals, + })} + </View> + ); +} type ReportTableTotalsProps = { data: DataEntity; - balanceTypeOp: 'totalAssets' | 'totalDebts' | 'totalTotals'; mode: string; - intervalsCount: number; totalScrollRef: RefObject<HTMLDivElement>; - handleScroll: UIEventHandler<HTMLDivElement>; compact: boolean; style?: CSSProperties; - compactStyle?: CSSProperties; - groupBy: string; - filters?: RuleConditionEntity[]; - showHiddenCategories?: boolean; - showOffBudget?: boolean; + renderTotals: (arg: renderTotalsProps) => ReactNode; }; export function ReportTableTotals({ data, - balanceTypeOp, mode, - intervalsCount, totalScrollRef, - handleScroll, compact, style, - compactStyle, - groupBy, - filters = [], - showHiddenCategories = false, - showOffBudget = false, + renderTotals, }: ReportTableTotalsProps) { const [scrollWidthTotals, setScrollWidthTotals] = useState(0); @@ -70,214 +78,47 @@ export function ReportTableTotals({ setScrollWidthTotals(parent > 0 && child > 0 ? parent - child : 0); } }); - const average = amountToInteger(data[balanceTypeOp]) / intervalsCount; - const navigate = useNavigate(); - const { isNarrowWidth } = useResponsive(); - const categories = useCategories(); - const accounts = useAccounts(); + const metadata: GroupedEntity = { + id: '', + name: 'Totals', + intervalData: data.intervalData, + totalAssets: data.totalAssets, + totalDebts: data.totalDebts, + totalTotals: data.totalTotals, + }; - const pointer = - !isNarrowWidth && !['Group', 'Interval'].includes(groupBy) - ? 'pointer' - : 'inherit'; + const totalsStyle: CSSProperties = { + borderTopWidth: 1, + borderColor: theme.tableBorder, + justifyContent: 'center', + color: theme.tableHeaderText, + backgroundColor: theme.tableHeaderBackground, + fontWeight: 600, + ...style, + }; - const hoverUnderline = - !isNarrowWidth && !['Group', 'Interval'].includes(groupBy) - ? { - cursor: pointer, - ':hover': { textDecoration: 'underline' }, - flexGrow: 0, - } - : {}; + const testStyle: CSSProperties = { + overflowX: 'auto', + scrollbarWidth: compact ? 'none' : 'inherit', + ...styles.horizontalScrollbar, + '::-webkit-scrollbar': { + backgroundColor: theme.tableBackground, + height: 12, + dispaly: compact && 'none', + }, + flexDirection: 'row', + flex: 1, + }; return ( - <Row - collapsed={true} - height={32 + scrollWidthTotals} - style={{ - borderTopWidth: 1, - borderColor: theme.tableBorder, - justifyContent: 'center', - color: theme.tableHeaderText, - backgroundColor: theme.tableHeaderBackground, - fontWeight: 600, - ...style, - }} - > - <View - innerRef={totalScrollRef} - onScroll={handleScroll} - id="total" - style={{ - overflowX: 'auto', - scrollbarWidth: compact ? 'none' : 'inherit', - ...styles.horizontalScrollbar, - '::-webkit-scrollbar': { - backgroundColor: theme.tableBackground, - height: 12, - dispaly: compact && 'none', - }, - flexDirection: 'row', - flex: 1, - }} - > - <Cell - style={{ - width: compact ? 80 : 125, - flexShrink: 0, - }} - valueStyle={compactStyle} - value="Totals" - /> - {mode === 'time' - ? data.intervalData.map(item => { - return ( - <Cell - style={{ - minWidth: compact ? 50 : 85, - }} - linkStyle={hoverUnderline} - valueStyle={compactStyle} - key={amountToCurrency(item[balanceTypeOp])} - value={amountToCurrency(item[balanceTypeOp])} - title={ - Math.abs(item[balanceTypeOp]) > 100000 - ? 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 - /> - ); - }) - : balanceTypeOp === 'totalTotals' && ( - <> - <Cell - style={{ - minWidth: compact ? 50 : 85, - }} - linkStyle={hoverUnderline} - valueStyle={compactStyle} - value={amountToCurrency(data.totalAssets)} - title={ - Math.abs(data.totalAssets) > 100000 - ? 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 - /> - <Cell - style={{ - minWidth: compact ? 50 : 85, - }} - linkStyle={hoverUnderline} - valueStyle={compactStyle} - value={amountToCurrency(data.totalDebts)} - title={ - Math.abs(data.totalDebts) > 100000 - ? 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 - /> - </> - )} - <Cell - style={{ - minWidth: compact ? 50 : 85, - }} - linkStyle={hoverUnderline} - valueStyle={compactStyle} - value={amountToCurrency(data[balanceTypeOp])} - title={ - Math.abs(data[balanceTypeOp]) > 100000 - ? 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 - /> - <Cell - style={{ - minWidth: compact ? 50 : 85, - }} - valueStyle={compactStyle} - value={integerToCurrency(Math.round(average))} - title={ - Math.abs(Math.round(average / 100)) > 100000 - ? integerToCurrency(Math.round(average)) - : undefined - } - width="flex" - privacyFilter - /> - </View> - </Row> + <RenderTotalsRow + metadata={metadata} + mode={mode} + totalsStyle={totalsStyle} + testStyle={testStyle} + scrollWidthTotals={scrollWidthTotals} + renderTotals={renderTotals} + /> ); } 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 ad734c270..ab8b3a0e1 100644 --- a/packages/desktop-client/src/components/reports/spreadsheets/custom-spreadsheet.ts +++ b/packages/desktop-client/src/components/reports/spreadsheets/custom-spreadsheet.ts @@ -214,7 +214,7 @@ export function createCustomSpreadsheet({ ReportOptions.intervalFormat.get(interval) || '', ), ...stacked, - dateStart: intervalItem, + intervalStartDate: intervalItem, totalDebts: integerToAmount(perIntervalDebts), totalAssets: integerToAmount(perIntervalAssets), totalTotals: integerToAmount(perIntervalDebts + perIntervalAssets), diff --git a/packages/desktop-client/src/components/reports/spreadsheets/recalculate.ts b/packages/desktop-client/src/components/reports/spreadsheets/recalculate.ts index a7262dde8..a01332e4b 100644 --- a/packages/desktop-client/src/components/reports/spreadsheets/recalculate.ts +++ b/packages/desktop-client/src/components/reports/spreadsheets/recalculate.ts @@ -77,7 +77,7 @@ export function recalculate({ totalDebts: integerToAmount(intervalDebts), totalTotals: integerToAmount(intervalAssets + intervalDebts), change, - dateLookup: intervalItem, + intervalStartDate: intervalItem, }); return arr; diff --git a/packages/loot-core/src/types/models/reports.d.ts b/packages/loot-core/src/types/models/reports.d.ts index 563de0b1f..ca893d5f1 100644 --- a/packages/loot-core/src/types/models/reports.d.ts +++ b/packages/loot-core/src/types/models/reports.d.ts @@ -77,9 +77,8 @@ type LegendEntity = { export type IntervalEntity = { date?: string; - dateStart?: string; change?: number; - dateLookup?: string; + intervalStartDate?: string; totalAssets: number; totalDebts: number; totalTotals: number; diff --git a/upcoming-release-notes/2768.md b/upcoming-release-notes/2768.md new file mode 100644 index 000000000..7ec924000 --- /dev/null +++ b/upcoming-release-notes/2768.md @@ -0,0 +1,6 @@ +--- +category: Bugfix +authors: [carkom] +--- + +Creating a callback for the table totals to fix a bug that created duplicate columns while rendering. -- GitLab