Skip to content
Snippets Groups Projects
Unverified Commit e745a407 authored by Neil's avatar Neil Committed by GitHub
Browse files

Custom Reports: fix table rendering (#2192)


* reorg

* notes

* Add render

* privacy Filter additions

* merge fixes

* notes

* merge fixes

* Apply patches for strict mode

* review fixes

---------

Co-authored-by: default avatarMatiss Janis Aboltins <matiss@mja.lv>
parent 7d1a895b
No related branches found
No related tags found
No related merge requests found
Showing
with 301 additions and 264 deletions
...@@ -2,8 +2,11 @@ import { type HTMLProps, type Ref } from 'react'; ...@@ -2,8 +2,11 @@ import { type HTMLProps, type Ref } from 'react';
import { css } from 'glamor'; import { css } from 'glamor';
import { type CSSProperties } from '../../style';
type BlockProps = HTMLProps<HTMLDivElement> & { type BlockProps = HTMLProps<HTMLDivElement> & {
innerRef?: Ref<HTMLDivElement>; innerRef?: Ref<HTMLDivElement>;
style?: CSSProperties;
}; };
export function Block(props: BlockProps) { export function Block(props: BlockProps) {
......
...@@ -12,7 +12,6 @@ import { LineGraph } from './graphs/LineGraph'; ...@@ -12,7 +12,6 @@ import { LineGraph } from './graphs/LineGraph';
import { StackedBarGraph } from './graphs/StackedBarGraph'; import { StackedBarGraph } from './graphs/StackedBarGraph';
import { ReportTable } from './graphs/tableGraph/ReportTable'; import { ReportTable } from './graphs/tableGraph/ReportTable';
import { ReportTableHeader } from './graphs/tableGraph/ReportTableHeader'; import { ReportTableHeader } from './graphs/tableGraph/ReportTableHeader';
import { ReportTableList } from './graphs/tableGraph/ReportTableList';
import { ReportTableTotals } from './graphs/tableGraph/ReportTableTotals'; import { ReportTableTotals } from './graphs/tableGraph/ReportTableTotals';
import { ReportOptions } from './ReportOptions'; import { ReportOptions } from './ReportOptions';
...@@ -42,6 +41,12 @@ export function ChooseGraph({ ...@@ -42,6 +41,12 @@ export function ChooseGraph({
viewLabels, viewLabels,
}: ChooseGraphProps) { }: ChooseGraphProps) {
const balanceTypeOp = ReportOptions.balanceTypeMap.get(balanceType); const balanceTypeOp = ReportOptions.balanceTypeMap.get(balanceType);
const groupByData =
groupBy === 'Category'
? 'groupedData'
: ['Month', 'Year'].includes(groupBy)
? 'monthData'
: 'data';
const saveScrollWidth = value => { const saveScrollWidth = value => {
setScrollWidth(!value ? 0 : value); setScrollWidth(!value ? 0 : value);
...@@ -128,16 +133,12 @@ export function ChooseGraph({ ...@@ -128,16 +133,12 @@ export function ChooseGraph({
saveScrollWidth={saveScrollWidth} saveScrollWidth={saveScrollWidth}
listScrollRef={listScrollRef} listScrollRef={listScrollRef}
handleScroll={handleScroll} handleScroll={handleScroll}
> balanceTypeOp={balanceTypeOp}
<ReportTableList groupBy={groupBy}
data={data} data={data[groupByData]}
empty={showEmpty} mode={mode}
monthsCount={months.length} monthsCount={months.length}
balanceTypeOp={balanceTypeOp} />
mode={mode}
groupBy={groupBy}
/>
</ReportTable>
<ReportTableTotals <ReportTableTotals
totalScrollRef={totalScrollRef} totalScrollRef={totalScrollRef}
handleScroll={handleScroll} handleScroll={handleScroll}
......
...@@ -7,9 +7,9 @@ import { ...@@ -7,9 +7,9 @@ import {
} from 'loot-core/src/types/models'; } from 'loot-core/src/types/models';
const balanceTypeOptions = [ const balanceTypeOptions = [
{ description: 'Payment', format: 'totalDebts' }, { description: 'Payment', format: 'totalDebts' as const },
{ description: 'Deposit', format: 'totalAssets' }, { description: 'Deposit', format: 'totalAssets' as const },
{ description: 'Net', format: 'totalTotals' }, { description: 'Net', format: 'totalTotals' as const },
]; ];
const groupByOptions = [ const groupByOptions = [
......
export type DataEntity = { export type DataEntity = {
data: Array<ItemEntity>; data: GroupedEntity[];
monthData: Array<MonthData>; monthData: GroupedEntity[];
groupedData: Array<GroupedEntity>; groupedData: GroupedEntity[];
legend: LegendEntity[]; legend: LegendEntity[];
startDate: string; startDate: string;
endDate: string; endDate: string;
...@@ -31,7 +31,7 @@ export type MonthData = { ...@@ -31,7 +31,7 @@ export type MonthData = {
totalTotals: number; totalTotals: number;
}; };
type GroupedEntity = { export type GroupedEntity = {
id: string; id: string;
name: string; name: string;
date?: string; date?: string;
......
// @ts-strict-ignore // @ts-strict-ignore
import React, { import React, {
type UIEventHandler, useCallback,
useLayoutEffect, useLayoutEffect,
useRef, useRef,
type ReactNode, type UIEventHandler,
} from 'react'; } from 'react';
import { type RefProp } from 'react-spring'; import { type RefProp } from 'react-spring';
import { type CSSProperties } from '../../../../style'; import { type CSSProperties } from '../../../../style';
import { Block } from '../../../common/Block';
import { View } from '../../../common/View'; import { View } from '../../../common/View';
import { type GroupedEntity } from '../../entities';
import { ReportTableList } from './ReportTableList';
import { ReportTableRow } from './ReportTableRow';
type ReportTableProps = { type ReportTableProps = {
saveScrollWidth?: (value: number) => void; saveScrollWidth: (value: number) => void;
listScrollRef?: RefProp<HTMLDivElement>; listScrollRef: RefProp<HTMLDivElement>;
handleScroll: UIEventHandler<HTMLDivElement>;
style?: CSSProperties; style?: CSSProperties;
children?: ReactNode; groupBy: string;
handleScroll?: UIEventHandler<HTMLDivElement>; balanceTypeOp: 'totalDebts' | 'totalTotals' | 'totalAssets';
data: GroupedEntity[];
mode: string;
monthsCount: number;
}; };
export function ReportTable({ export function ReportTable({
saveScrollWidth, saveScrollWidth,
listScrollRef, listScrollRef,
style,
children,
handleScroll, handleScroll,
style,
groupBy,
balanceTypeOp,
data,
mode,
monthsCount,
}: ReportTableProps) { }: ReportTableProps) {
const contentRef = useRef<HTMLDivElement>(null); const contentRef = useRef<HTMLDivElement>(null);
...@@ -33,25 +46,56 @@ export function ReportTable({ ...@@ -33,25 +46,56 @@ export function ReportTable({
} }
}); });
const renderItem = useCallback(
({ item, groupByItem, mode, style, key, monthsCount }) => {
return (
<ReportTableRow
key={key}
item={item}
balanceTypeOp={balanceTypeOp}
groupByItem={groupByItem}
mode={mode}
style={style}
monthsCount={monthsCount}
/>
);
},
[],
);
return ( return (
<View <View
innerRef={listScrollRef}
onScroll={handleScroll}
id="list"
style={{ style={{
overflowY: 'auto',
scrollbarWidth: 'none',
'::-webkit-scrollbar': { display: 'none' },
flex: 1, flex: 1,
flexDirection: 'row',
outline: 'none', outline: 'none',
'& .animated .animated-row': { transition: '.25s transform' }, '& .animated .animated-row': { transition: '.25s transform' },
...style, ...style,
}} }}
tabIndex={1} tabIndex={1}
> >
<View> <Block
<div ref={contentRef}>{children}</div> innerRef={listScrollRef}
</View> onScroll={handleScroll}
id="list"
style={{
overflowY: 'auto',
scrollbarWidth: 'none',
'::-webkit-scrollbar': { display: 'none' },
flex: 1,
outline: 'none',
'& .animated .animated-row': { transition: '.25s transform' },
...style,
}}
>
<ReportTableList
data={data}
monthsCount={monthsCount}
mode={mode}
groupBy={groupBy}
renderItem={renderItem}
/>
</Block>
</View> </View>
); );
} }
...@@ -5,15 +5,15 @@ import { type RefProp } from 'react-spring'; ...@@ -5,15 +5,15 @@ import { type RefProp } from 'react-spring';
import { styles, theme } from '../../../../style'; import { styles, theme } from '../../../../style';
import { View } from '../../../common/View'; import { View } from '../../../common/View';
import { Row, Cell } from '../../../table'; import { Row, Cell } from '../../../table';
import { type MonthData } from '../../entities'; import { type GroupedEntity } from '../../entities';
type ReportTableHeaderProps = { type ReportTableHeaderProps = {
scrollWidth?: number; scrollWidth?: number;
groupBy: string; groupBy: string;
interval?: MonthData[]; interval?: GroupedEntity[];
balanceType: string; balanceType: string;
headerScrollRef: RefProp<HTMLDivElement>; headerScrollRef: RefProp<HTMLDivElement>;
handleScroll?: UIEventHandler<HTMLDivElement>; handleScroll: UIEventHandler<HTMLDivElement>;
}; };
export function ReportTableHeader({ export function ReportTableHeader({
......
// @ts-strict-ignore // @ts-strict-ignore
import React, { memo } from 'react'; import React from 'react';
import { import { type CSSProperties, theme } from '../../../../style';
amountToCurrency,
amountToInteger,
integerToCurrency,
} from 'loot-core/src/shared/util';
import { type CSSProperties, styles, theme } from '../../../../style';
import { View } from '../../../common/View'; import { View } from '../../../common/View';
import { Row, Cell } from '../../../table'; import { Cell, Row } from '../../../table';
import { type GroupedEntity } from '../../entities';
type TableRowProps = { type ReportTableListProps = {
item: { data: GroupedEntity[];
date: string; mode?: string;
name: string; monthsCount?: number;
monthData: []; groupBy: string;
totalAssets: number; renderItem;
totalDebts: number;
};
balanceTypeOp?: string;
groupByItem: string;
mode: string;
monthsCount: number;
style?: CSSProperties;
}; };
const TableRow = memo(
({
item,
balanceTypeOp,
groupByItem,
mode,
monthsCount,
style,
}: TableRowProps) => {
const average = amountToInteger(item[balanceTypeOp]) / monthsCount;
return (
<Row
key={item[groupByItem]}
collapsed={true}
style={{
color: theme.tableText,
backgroundColor: theme.tableBackground,
...style,
}}
>
<Cell
value={item[groupByItem]}
title={item[groupByItem].length > 12 && item[groupByItem]}
style={{
width: 120,
flexShrink: 0,
...styles.tnum,
}}
/>
{item.monthData && mode === 'time'
? item.monthData.map(month => {
return (
<Cell
style={{
minWidth: 85,
...styles.tnum,
}}
key={amountToCurrency(month[balanceTypeOp])}
value={amountToCurrency(month[balanceTypeOp])}
title={
Math.abs(month[balanceTypeOp]) > 100000 &&
amountToCurrency(month[balanceTypeOp])
}
width="flex"
privacyFilter
/>
);
})
: balanceTypeOp === 'totalTotals' && (
<>
<Cell
value={amountToCurrency(item.totalAssets)}
title={
Math.abs(item.totalAssets) > 100000 &&
amountToCurrency(item.totalAssets)
}
width="flex"
style={{
minWidth: 85,
...styles.tnum,
}}
/>
<Cell
value={amountToCurrency(item.totalDebts)}
title={
Math.abs(item.totalDebts) > 100000 &&
amountToCurrency(item.totalDebts)
}
width="flex"
style={{
minWidth: 85,
...styles.tnum,
}}
/>
</>
)}
<Cell
value={amountToCurrency(item[balanceTypeOp])}
title={
Math.abs(item[balanceTypeOp]) > 100000 &&
amountToCurrency(item[balanceTypeOp])
}
style={{
fontWeight: 600,
minWidth: 85,
...styles.tnum,
}}
width="flex"
privacyFilter
/>
<Cell
value={integerToCurrency(Math.round(average))}
title={
Math.abs(Math.round(average / 100)) > 100000 &&
integerToCurrency(Math.round(average))
}
style={{
fontWeight: 600,
minWidth: 85,
...styles.tnum,
}}
width="flex"
privacyFilter
/>
</Row>
);
},
);
function GroupedTableRow({
item,
balanceTypeOp,
groupByItem,
mode,
monthsCount,
empty,
}) {
return (
<>
<TableRow
key={item.id}
item={item}
balanceTypeOp={balanceTypeOp}
groupByItem={groupByItem}
mode={mode}
monthsCount={monthsCount}
style={{
color: theme.tableRowHeaderText,
backgroundColor: theme.tableRowHeaderBackground,
fontWeight: 600,
}}
/>
<View>
{item.categories
.filter(i =>
!empty
? balanceTypeOp === 'totalTotals'
? i.totalAssets !== 0 ||
i.totalDebts !== 0 ||
i.totalTotals !== 0
: i[balanceTypeOp] !== 0
: true,
)
.map(cat => {
return (
<TableRow
key={cat.id}
item={cat}
balanceTypeOp={balanceTypeOp}
groupByItem={groupByItem}
mode={mode}
monthsCount={monthsCount}
/>
);
})}
</View>
<Row height={20} />
</>
);
}
export function ReportTableList({ export function ReportTableList({
data, data,
empty,
monthsCount, monthsCount,
balanceTypeOp,
mode, mode,
groupBy, groupBy,
}) { renderItem,
}: ReportTableListProps) {
const groupByItem = ['Month', 'Year'].includes(groupBy) ? 'date' : 'name'; const groupByItem = ['Month', 'Year'].includes(groupBy) ? 'date' : 'name';
const groupByData =
groupBy === 'Category' type RenderRowProps = {
? 'groupedData' key: string;
: ['Month', 'Year'].includes(groupBy) index: number;
? 'monthData' parent_index?: number;
: 'data'; style?: CSSProperties;
};
function RenderRow({ index, parent_index, style, key }: RenderRowProps) {
const item = parent_index
? data[parent_index].categories[index]
: data[index];
return renderItem({
item,
groupByItem,
mode,
style,
key,
monthsCount,
});
}
return ( return (
<View> <View>
{data[groupByData] {data.map((item, index) => {
.filter(i => return (
!empty <View key={item.id}>
? balanceTypeOp === 'totalTotals' {data ? (
? i.totalAssets !== 0 || i.totalDebts !== 0 || i.totalTotals !== 0 <>
: i[balanceTypeOp] !== 0 <RenderRow
: true, key={item.id}
) index={index}
.map(item => { style={
if (groupBy === 'Category') { item.categories && {
return ( color: theme.tableRowHeaderText,
<GroupedTableRow backgroundColor: theme.tableRowHeaderBackground,
key={item.id} fontWeight: 600,
item={item} }
balanceTypeOp={balanceTypeOp} }
groupByItem={groupByItem} />
mode={mode} {item.categories && (
monthsCount={monthsCount} <>
empty={empty} <View>
/> {item.categories.map((category, i) => {
); return (
} else { <RenderRow
return ( key={category.id}
<TableRow index={i}
key={item.id} parent_index={index}
item={item} />
balanceTypeOp={balanceTypeOp} );
groupByItem={groupByItem} })}
mode={mode} </View>
monthsCount={monthsCount} <Row height={20} />
/> </>
); )}
} </>
})} ) : (
<Cell width="flex" />
)}
</View>
);
})}
</View> </View>
); );
} }
import React, { memo } from 'react';
import {
amountToCurrency,
amountToInteger,
integerToCurrency,
} from 'loot-core/src/shared/util';
import { type CSSProperties, styles, theme } from '../../../../style';
import { Row, Cell } from '../../../table';
import { type GroupedEntity } from '../../entities';
type ReportTableRowProps = {
item: GroupedEntity;
balanceTypeOp: 'totalAssets' | 'totalDebts' | 'totalTotals';
groupByItem: 'id' | 'name';
mode: string;
style?: CSSProperties;
monthsCount: number;
};
export const ReportTableRow = memo(
({
item,
balanceTypeOp,
groupByItem,
mode,
style,
monthsCount,
}: ReportTableRowProps) => {
const average = amountToInteger(item[balanceTypeOp]) / monthsCount;
return (
<Row
collapsed={true}
style={{
color: theme.tableText,
backgroundColor: theme.tableBackground,
...style,
}}
>
<Cell
value={item[groupByItem]}
title={item[groupByItem].length > 12 ? item[groupByItem] : undefined}
style={{
width: 120,
flexShrink: 0,
...styles.tnum,
}}
/>
{item.monthData && mode === 'time'
? item.monthData.map(month => {
return (
<Cell
key={amountToCurrency(month[balanceTypeOp])}
style={{
minWidth: 85,
...styles.tnum,
}}
value={amountToCurrency(month[balanceTypeOp])}
title={
Math.abs(month[balanceTypeOp]) > 100000
? amountToCurrency(month[balanceTypeOp])
: undefined
}
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: 85,
...styles.tnum,
}}
/>
<Cell
value={amountToCurrency(item.totalDebts)}
title={
Math.abs(item.totalDebts) > 100000
? amountToCurrency(item.totalDebts)
: undefined
}
width="flex"
privacyFilter
style={{
minWidth: 85,
...styles.tnum,
}}
/>
</>
)}
<Cell
value={amountToCurrency(item[balanceTypeOp])}
title={
Math.abs(item[balanceTypeOp]) > 100000
? amountToCurrency(item[balanceTypeOp])
: undefined
}
style={{
fontWeight: 600,
minWidth: 85,
...styles.tnum,
}}
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: 85,
...styles.tnum,
}}
width="flex"
privacyFilter
/>
</Row>
);
},
);
...@@ -110,6 +110,7 @@ export function ReportTableTotals({ ...@@ -110,6 +110,7 @@ export function ReportTableTotals({
amountToCurrency(data.totalAssets) amountToCurrency(data.totalAssets)
} }
width="flex" width="flex"
privacyFilter
/> />
<Cell <Cell
style={{ style={{
...@@ -122,6 +123,7 @@ export function ReportTableTotals({ ...@@ -122,6 +123,7 @@ export function ReportTableTotals({
amountToCurrency(data.totalDebts) amountToCurrency(data.totalDebts)
} }
width="flex" width="flex"
privacyFilter
/> />
</> </>
)} )}
......
---
category: Maintenance
authors: [carkom]
---
Fix table graph rendering issue for custom reports.
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment