From a0ecd65e70b621a1e281b324e8204ebb9fa0c840 Mon Sep 17 00:00:00 2001 From: Matiss Janis Aboltins <matiss@mja.lv> Date: Sat, 12 Aug 2023 09:10:45 +0100 Subject: [PATCH] :sparkles: (reports) add loading indicators (#1491) * :sparkles: (reports) add loading indicators * Release notes --- .../components/reports/CategorySelector.tsx | 7 +- .../src/components/reports/Overview.js | 263 ++++++++++-------- .../graphs/category-spending-spreadsheet.tsx | 2 - .../src/components/reports/graphs/common.tsx | 2 +- upcoming-release-notes/1491.md | 6 + 5 files changed, 153 insertions(+), 127 deletions(-) create mode 100644 upcoming-release-notes/1491.md diff --git a/packages/desktop-client/src/components/reports/CategorySelector.tsx b/packages/desktop-client/src/components/reports/CategorySelector.tsx index a891d22dd..4b77f92b0 100644 --- a/packages/desktop-client/src/components/reports/CategorySelector.tsx +++ b/packages/desktop-client/src/components/reports/CategorySelector.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { Fragment, useState } from 'react'; import Eye from '../../icons/v2/Eye'; import EyeSlashed from '../../icons/v2/EyeSlashed'; @@ -67,7 +67,7 @@ export default function CategorySelector({ ), ); return ( - <> + <Fragment key={categoryGroup.id}> <li style={{ display: @@ -75,7 +75,6 @@ export default function CategorySelector({ marginBottom: 4, flexDirection: 'row', }} - key={categoryGroup.id} > <Checkbox id={`form_${categoryGroup.id}`} @@ -162,7 +161,7 @@ export default function CategorySelector({ })} </ul> </li> - </> + </Fragment> ); })} </ul> diff --git a/packages/desktop-client/src/components/reports/Overview.js b/packages/desktop-client/src/components/reports/Overview.js index 790feeb55..04308f7d5 100644 --- a/packages/desktop-client/src/components/reports/Overview.js +++ b/packages/desktop-client/src/components/reports/Overview.js @@ -8,6 +8,7 @@ import { integerToCurrency } from 'loot-core/src/shared/util'; import useCategories from '../../hooks/useCategories'; import useFeatureFlag from '../../hooks/useFeatureFlag'; +import AnimatedLoading from '../../icons/AnimatedLoading'; import { colors, styles } from '../../style'; import AnchorLink from '../common/AnchorLink'; import Block from '../common/Block'; @@ -63,6 +64,20 @@ function Card({ flex, to, style, children }) { return content; } +function LoadingIndicator() { + return ( + <View + style={{ + height: '100%', + alignItems: 'center', + justifyContent: 'center', + }} + > + <AnimatedLoading style={{ width: 25, height: 25 }} /> + </View> + ); +} + function NetWorthCard({ accounts }) { const end = monthUtils.currentMonth(); const start = monthUtils.subMonths(end, 5); @@ -76,10 +91,6 @@ function NetWorthCard({ accounts }) { ); const data = useReport('net_worth', params); - if (!data) { - return null; - } - return ( <Card flex={2} to="/reports/net-worth"> <View @@ -97,30 +108,39 @@ function NetWorthCard({ accounts }) { </Block> <DateRange start={start} end={end} /> </View> - <View style={{ textAlign: 'right' }}> - <Block - style={[styles.mediumText, { fontWeight: 500, marginBottom: 5 }]} - > + {data && ( + <View style={{ textAlign: 'right' }}> + <Block + style={[ + styles.mediumText, + { fontWeight: 500, marginBottom: 5 }, + ]} + > + <PrivacyFilter activationFilters={[!isCardHovered]}> + {integerToCurrency(data.netWorth)} + </PrivacyFilter> + </Block> <PrivacyFilter activationFilters={[!isCardHovered]}> - {integerToCurrency(data.netWorth)} + <Change + amount={data.totalChange} + style={{ color: colors.n6, fontWeight: 300 }} + /> </PrivacyFilter> - </Block> - <PrivacyFilter activationFilters={[!isCardHovered]}> - <Change - amount={data.totalChange} - style={{ color: colors.n6, fontWeight: 300 }} - /> - </PrivacyFilter> - </View> + </View> + )} </View> - <NetWorthGraph - start={start} - end={end} - graphData={data.graphData} - compact={true} - style={{ height: 'auto', flex: 1 }} - /> + {data ? ( + <NetWorthGraph + start={start} + end={end} + graphData={data.graphData} + compact={true} + style={{ height: 'auto', flex: 1 }} + /> + ) : ( + <LoadingIndicator /> + )} </View> </Card> ); @@ -136,13 +156,9 @@ function CashFlowCard() { const onCardHover = useCallback(() => setIsCardHovered(true)); const onCardHoverEnd = useCallback(() => setIsCardHovered(false)); - if (!data) { - return null; - } - - const { graphData } = data; - const expense = -(graphData.expense || 0); - const income = graphData.income || 0; + const { graphData } = data || {}; + const expense = -(graphData?.expense || 0); + const income = graphData?.income || 0; return ( <Card flex={1} to="/reports/cash-flow"> @@ -161,102 +177,108 @@ function CashFlowCard() { </Block> <DateRange start={start} end={end} /> </View> - <View style={{ textAlign: 'right' }}> - <PrivacyFilter activationFilters={[!isCardHovered]}> - <Change - amount={income - expense} - style={{ color: colors.n6, fontWeight: 300 }} - /> - </PrivacyFilter> - </View> + {data && ( + <View style={{ textAlign: 'right' }}> + <PrivacyFilter activationFilters={[!isCardHovered]}> + <Change + amount={income - expense} + style={{ color: colors.n6, fontWeight: 300 }} + /> + </PrivacyFilter> + </View> + )} </View> - <Container style={{ height: 'auto', flex: 1 }}> - {(width, height, portalHost) => ( - <VictoryGroup - colorScale={[theme.colors.blue, theme.colors.red]} - width={100} - height={height} - theme={theme} - domain={{ - x: [0, 100], - y: [0, Math.max(income, expense, 100)], - }} - containerComponent={ - <VictoryVoronoiContainer voronoiDimension="x" /> - } - labelComponent={ - <Tooltip - portalHost={portalHost} - offsetX={(width - 100) / 2} - offsetY={y => (y + 40 > height ? height - 40 : y)} - light={true} - forceActive={true} - style={{ - padding: 0, - }} - /> - } - padding={{ - top: 0, - bottom: 0, - left: 0, - right: 0, - }} - > - <VictoryBar - barWidth={13} - data={[ - { - x: 30, - y: Math.max(income, 5), - premadeLabel: ( - <View style={{ textAlign: 'right' }}> - Income - <View> - <PrivacyFilter activationFilters={[!isCardHovered]}> - {integerToCurrency(income)} - </PrivacyFilter> + {data ? ( + <Container style={{ height: 'auto', flex: 1 }}> + {(width, height, portalHost) => ( + <VictoryGroup + colorScale={[theme.colors.blue, theme.colors.red]} + width={100} + height={height} + theme={theme} + domain={{ + x: [0, 100], + y: [0, Math.max(income, expense, 100)], + }} + containerComponent={ + <VictoryVoronoiContainer voronoiDimension="x" /> + } + labelComponent={ + <Tooltip + portalHost={portalHost} + offsetX={(width - 100) / 2} + offsetY={y => (y + 40 > height ? height - 40 : y)} + light={true} + forceActive={true} + style={{ + padding: 0, + }} + /> + } + padding={{ + top: 0, + bottom: 0, + left: 0, + right: 0, + }} + > + <VictoryBar + barWidth={13} + data={[ + { + x: 30, + y: Math.max(income, 5), + premadeLabel: ( + <View style={{ textAlign: 'right' }}> + Income + <View> + <PrivacyFilter activationFilters={[!isCardHovered]}> + {integerToCurrency(income)} + </PrivacyFilter> + </View> </View> - </View> - ), - labelPosition: 'left', - }, - ]} - labels={d => d.premadeLabel} - /> - <VictoryBar - barWidth={13} - data={[ - { - x: 60, - y: Math.max(expense, 5), - premadeLabel: ( - <View> - Expenses + ), + labelPosition: 'left', + }, + ]} + labels={d => d.premadeLabel} + /> + <VictoryBar + barWidth={13} + data={[ + { + x: 60, + y: Math.max(expense, 5), + premadeLabel: ( <View> - <PrivacyFilter activationFilters={[!isCardHovered]}> - {integerToCurrency(expense)} - </PrivacyFilter> + Expenses + <View> + <PrivacyFilter activationFilters={[!isCardHovered]}> + {integerToCurrency(expense)} + </PrivacyFilter> + </View> </View> - </View> - ), - labelPosition: 'right', - fill: theme.colors.red, - }, - ]} - labels={d => d.premadeLabel} - /> - </VictoryGroup> - )} - </Container> + ), + labelPosition: 'right', + fill: theme.colors.red, + }, + ]} + labels={d => d.premadeLabel} + /> + </VictoryGroup> + )} + </Container> + ) : ( + <LoadingIndicator /> + )} </View> </Card> ); } function CategorySpendingCard() { - const categories = useCategories(); + const { list: categories = [] } = useCategories(); const end = monthUtils.currentDay(); const start = monthUtils.subMonths(end, 3); @@ -266,9 +288,7 @@ function CategorySpendingCard() { start, end, 3, - (categories.list || []).filter( - category => !category.is_income && !category.hidden, - ), + categories.filter(category => !category.is_income && !category.hidden), ); }, [start, end, categories]); @@ -289,13 +309,16 @@ function CategorySpendingCard() { </View> </View> </View> - {!perCategorySpending ? null : ( + + {perCategorySpending ? ( <CategorySpendingGraph start={start} end={end} graphData={perCategorySpending} compact={true} /> + ) : ( + <LoadingIndicator /> )} </Card> ); diff --git a/packages/desktop-client/src/components/reports/graphs/category-spending-spreadsheet.tsx b/packages/desktop-client/src/components/reports/graphs/category-spending-spreadsheet.tsx index 9dde112f7..d4bb392ed 100644 --- a/packages/desktop-client/src/components/reports/graphs/category-spending-spreadsheet.tsx +++ b/packages/desktop-client/src/components/reports/graphs/category-spending-spreadsheet.tsx @@ -52,7 +52,6 @@ export default function createSpreadsheet( setData: (graphData: CategorySpendingGraphData) => void, ) => { if (start === null || end === null || categories.length === 0) { - setData({ categories: [], tickValues: [], data: {} }); return; } @@ -72,7 +71,6 @@ export default function createSpreadsheet( .select('date'), ); if (firstTransaction.data.length === 0) { - setData({ categories: [], tickValues: [], data: {} }); return; } diff --git a/packages/desktop-client/src/components/reports/graphs/common.tsx b/packages/desktop-client/src/components/reports/graphs/common.tsx index 4c384cf54..c88ccd232 100644 --- a/packages/desktop-client/src/components/reports/graphs/common.tsx +++ b/packages/desktop-client/src/components/reports/graphs/common.tsx @@ -14,7 +14,7 @@ export function Area({ start, end, scale, range }: AreaProps) { const startX = scale.x(d.parseISO(start + '-01')); const endX = scale.x(d.parseISO(end + '-01')); - if (startX < 0 || endX < 0) { + if (startX < 0 || endX < 0 || startX === undefined || endX === undefined) { return null; } diff --git a/upcoming-release-notes/1491.md b/upcoming-release-notes/1491.md new file mode 100644 index 000000000..56ac7a9ad --- /dev/null +++ b/upcoming-release-notes/1491.md @@ -0,0 +1,6 @@ +--- +category: Enhancements +authors: [MatissJanis] +--- + +Add loading indicators to reports page -- GitLab