From b9abbbbf255a23bcf98a010dbdbea5890e34a91c Mon Sep 17 00:00:00 2001 From: Thorben <38816229+tklein1801@users.noreply.github.com> Date: Sun, 7 Apr 2024 14:32:10 +0200 Subject: [PATCH 1/8] refactor: Remove some components --- .../Base/Charts/ApexPieChart.component.tsx | 1 - src/routes/Dashboard/Budget.view.tsx | 18 +----------------- src/routes/Dashboard/Stocks.view.tsx | 5 ----- 3 files changed, 1 insertion(+), 23 deletions(-) diff --git a/src/components/Base/Charts/ApexPieChart.component.tsx b/src/components/Base/Charts/ApexPieChart.component.tsx index 42c24988..2159dc1d 100644 --- a/src/components/Base/Charts/ApexPieChart.component.tsx +++ b/src/components/Base/Charts/ApexPieChart.component.tsx @@ -55,7 +55,6 @@ export const ApexPieChart: React.FC = ({data, ...props}) => }, }, labels: data.map(({label}) => label), - dataLabels: { // @ts-ignore formatter(val, opts) { diff --git a/src/routes/Dashboard/Budget.view.tsx b/src/routes/Dashboard/Budget.view.tsx index ff6f1309..39b45b66 100644 --- a/src/routes/Dashboard/Budget.view.tsx +++ b/src/routes/Dashboard/Budget.view.tsx @@ -1,21 +1,11 @@ import React from 'react'; -import {BudgetList, BudgetProgressWrapper, StatsWrapper, useFetchBudgetProgress} from '@/components/Budget'; +import {BudgetList, StatsWrapper} from '@/components/Budget'; import {CategorySpendingsChart, CategoryIncomeChart} from '@/components/Category'; import {Grid} from '@mui/material'; import {DailyTransactionChart} from '@/components/Transaction'; -import {CircularProgress} from '@/components/Loading'; import {MonthlyBalanceChartCard, MonthlyBalanceWidget} from '@/components/Transaction/MonthlyBalance'; -export const DATE_RANGE_INPUT_FORMAT = 'dd.MM'; -export type TChartContentType = 'INCOME' | 'SPENDINGS'; -export const ChartContentTypes = [ - {type: 'INCOME' as TChartContentType, label: 'Income'}, - {type: 'SPENDINGS' as TChartContentType, label: 'Spendings'}, -]; - export const BudgetView = () => { - const {budgetProgress, loading: loadingBudgetProgress} = useFetchBudgetProgress(); - return ( @@ -31,12 +21,6 @@ export const BudgetView = () => { - - {loadingBudgetProgress ? ( - - ) : ( - - )} diff --git a/src/routes/Dashboard/Stocks.view.tsx b/src/routes/Dashboard/Stocks.view.tsx index fccb259d..3a3edbd0 100644 --- a/src/routes/Dashboard/Stocks.view.tsx +++ b/src/routes/Dashboard/Stocks.view.tsx @@ -7,7 +7,6 @@ import {useAuthContext} from '@/components/Auth'; import {formatBalance, getSocketIOClient} from '@/utils'; import { StockList, - StockNews, DividendTable, useFetchStockPositions, useStockStore, @@ -101,10 +100,6 @@ export const StocksView = () => { - - - - ); From dfb7c2f6161a9cd2d7ec57c4bd60cfb458226752 Mon Sep 17 00:00:00 2001 From: Thorben <38816229+tklein1801@users.noreply.github.com> Date: Sun, 7 Apr 2024 14:34:16 +0200 Subject: [PATCH 2/8] refactor(ui): Apply multiple minor ui changes --- .../Analytics/CategoryAnalytics.component.tsx | 2 +- .../Category/Chart/IncomeChart.component.tsx | 7 ++++++- src/routes/Dashboard/DashboardLayout.component.tsx | 14 +++++++++++++- src/routes/Transactions.route.tsx | 11 +---------- 4 files changed, 21 insertions(+), 13 deletions(-) diff --git a/src/components/Category/Analytics/CategoryAnalytics.component.tsx b/src/components/Category/Analytics/CategoryAnalytics.component.tsx index adc7b616..2822253e 100644 --- a/src/components/Category/Analytics/CategoryAnalytics.component.tsx +++ b/src/components/Category/Analytics/CategoryAnalytics.component.tsx @@ -4,7 +4,7 @@ import {BarChart, Card, StyledAutocompleteOption, type TBarChartData} from '@/co import {ArrowRightRounded} from '@mui/icons-material'; import {Autocomplete, TextField, Button, Box, Skeleton, Paper, Typography} from '@mui/material'; import {useFetchCategories} from '..'; -import {TCategory} from '@budgetbuddyde/types'; +import {type TCategory} from '@budgetbuddyde/types'; import {ParentSize} from '@visx/responsive'; import {useFetchTransactions} from '@/components/Transaction'; import {format, isBefore, isSameMonth, isSameYear} from 'date-fns'; diff --git a/src/components/Category/Chart/IncomeChart.component.tsx b/src/components/Category/Chart/IncomeChart.component.tsx index ada49bec..666722ec 100644 --- a/src/components/Category/Chart/IncomeChart.component.tsx +++ b/src/components/Category/Chart/IncomeChart.component.tsx @@ -65,7 +65,12 @@ export const CategoryIncomeChart: React.FC = () => { ) : ( - + )} diff --git a/src/routes/Dashboard/DashboardLayout.component.tsx b/src/routes/Dashboard/DashboardLayout.component.tsx index 7f594d70..c916114b 100644 --- a/src/routes/Dashboard/DashboardLayout.component.tsx +++ b/src/routes/Dashboard/DashboardLayout.component.tsx @@ -4,7 +4,12 @@ import {Outlet, useLocation, useNavigate} from 'react-router-dom'; import {withAuthLayout} from '@/components/Auth/Layout'; import {useAuthContext} from '@/components/Auth'; import {ContentGrid} from '@/components/Layout'; -import {type TDashboardView, DashboardViewMapping, DashboardViewDescriptionMapping} from './index'; +import { + type TDashboardView, + DashboardViewMapping, + DashboardViewDescriptionMapping, + DashboardViewIconMapping, +} from './index'; import {ActionPaper} from '@/components/Base'; export type TDashboardLayoutProps = React.PropsWithChildren<{ @@ -34,6 +39,13 @@ const DashboardLayout: React.FC = ({children, useOutletIn exclusive> {Object.entries(DashboardViewMapping).map(([path, view]: [string, TDashboardView]) => ( + {React.isValidElement(DashboardViewIconMapping[view]) && + // @ts-expect-error + React.cloneElement(DashboardViewIconMapping[view], { + sx: { + mr: 0.5, + }, + })} {view.substring(0, 1).toUpperCase() + view.substring(1)} ))} diff --git a/src/routes/Transactions.route.tsx b/src/routes/Transactions.route.tsx index 7cf6c187..392a275b 100644 --- a/src/routes/Transactions.route.tsx +++ b/src/routes/Transactions.route.tsx @@ -244,16 +244,7 @@ export const Transactions = () => { dispatchCreateDrawer({type: 'close'})} /> - dispatchEditDrawer({type: 'close'})} - // open={showEditTransactionDrawer} - // onChangeOpen={(isOpen) => { - // setShowEditTransactionDrawer(isOpen); - // if (!isOpen) setEditTransaction(null); - // }} - // transaction={editTransaction} - /> + dispatchEditDrawer({type: 'close'})} /> Date: Sun, 7 Apr 2024 14:34:43 +0200 Subject: [PATCH 3/8] refactor: Create global formatter service for better DX in the future --- src/routes/Stock.route.tsx | 11 +++-------- src/services/Formatter.service.ts | 29 +++++++++++++++++++++++++++++ src/services/index.ts | 2 ++ src/utils/formatBalance.util.ts | 1 + 4 files changed, 35 insertions(+), 8 deletions(-) create mode 100644 src/services/Formatter.service.ts diff --git a/src/routes/Stock.route.tsx b/src/routes/Stock.route.tsx index 5dad1d55..a625612e 100644 --- a/src/routes/Stock.route.tsx +++ b/src/routes/Stock.route.tsx @@ -60,6 +60,7 @@ import {CreateEntityDrawerState, useEntityDrawer} from '@/hooks/useEntityDrawer. import {useSnackbarContext} from '@/components/Snackbar'; import {DeleteDialog} from '@/components/DeleteDialog.component'; import {CircularProgress} from '@/components/Loading'; +import {Formatter} from '@/services'; const NoStockMessage = () => ( @@ -140,13 +141,7 @@ export const Stock = () => { colors: theme.palette.text.primary, }, formatter(val: number) { - let formattedVal: number | string = val as number; - if (formattedVal >= 1000000000) { - formattedVal = (formattedVal / 1000000000).toFixed(2) + ' Mrd.'; - } else if (formattedVal >= 1000000) { - formattedVal = (formattedVal / 1000000).toFixed(2) + ' Mio.'; - } - return formattedVal as string; + return Formatter.shortenNumber(val); }, }, }, @@ -161,7 +156,7 @@ export const Stock = () => { theme: 'dark', y: { formatter(val: number) { - return formatBalance(val as number); + return Formatter.formatBalance(val); }, }, }, diff --git a/src/services/Formatter.service.ts b/src/services/Formatter.service.ts new file mode 100644 index 00000000..b2ab9c93 --- /dev/null +++ b/src/services/Formatter.service.ts @@ -0,0 +1,29 @@ +export class Formatter { + /** + * Formats the balance amount as a currency string. + * + * @param balance - The balance amount to format. + * @param currency - The currency code to use for formatting. Defaults to 'EUR'. + * @returns The formatted balance as a string. + */ + static formatBalance(balance: number, currency = 'EUR'): string { + return balance.toLocaleString('de-DE', {style: 'currency', currency: currency}); + } + + /** + * Formats a number by shortening it with appropriate suffixes (Mio., Mrd., K.). + * @param number - The number to be formatted. + * @returns The formatted number as a string. + */ + static shortenNumber(number: number): string { + let formattedVal: number | string = number; + if (formattedVal >= 1000000000) { + formattedVal = (formattedVal / 1000000000).toFixed(2) + ' Mrd.'; + } else if (formattedVal >= 1000000) { + formattedVal = (formattedVal / 1000000).toFixed(2) + ' Mio.'; + } else if (formattedVal >= 1000) { + formattedVal = (formattedVal / 1000).toFixed(2) + ' K.'; + } + return formattedVal as string; + } +} diff --git a/src/services/index.ts b/src/services/index.ts index eda1f9c8..bb070563 100644 --- a/src/services/index.ts +++ b/src/services/index.ts @@ -1,2 +1,4 @@ export * from './Date.service'; export * from './User.service'; +export * from './File.service'; +export * from './Formatter.service'; diff --git a/src/utils/formatBalance.util.ts b/src/utils/formatBalance.util.ts index d24d08a5..253cb207 100644 --- a/src/utils/formatBalance.util.ts +++ b/src/utils/formatBalance.util.ts @@ -1,5 +1,6 @@ /** * // TODO: Get language and currency from browser-settings(current location?) + * @deprecated Use Formatter.formatBalance instead */ export function formatBalance(balance: number, currency = 'EUR'): string { return balance.toLocaleString('de-DE', {style: 'currency', currency: currency}); From a5a099cda3e2b58a8fc2d325cd6c7324fa8e2f61 Mon Sep 17 00:00:00 2001 From: Thorben <38816229+tklein1801@users.noreply.github.com> Date: Sun, 7 Apr 2024 14:36:29 +0200 Subject: [PATCH 4/8] chore(dependency): Update `@budgetbuddyde/types` --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index c3385f76..26150eb5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,7 +8,7 @@ "name": "webapp", "version": "0.0.0", "dependencies": { - "@budgetbuddyde/types": "^1.0.20", + "@budgetbuddyde/types": "^1.0.21", "@emotion/react": "^11.11.1", "@emotion/styled": "^11.11.0", "@mui/icons-material": "^5.14.15", @@ -728,9 +728,9 @@ "dev": true }, "node_modules/@budgetbuddyde/types": { - "version": "1.0.20", - "resolved": "https://registry.npmjs.org/@budgetbuddyde/types/-/types-1.0.20.tgz", - "integrity": "sha512-DltjZKilw4Qw3Cdhq1iq0TcsFw4V5jtkoc+tQ2gHNuB+zF2sQ91oRTf+AUUaCyW7To7miIG2LrH/e60d2hABHQ==", + "version": "1.0.21", + "resolved": "https://registry.npmjs.org/@budgetbuddyde/types/-/types-1.0.21.tgz", + "integrity": "sha512-IJpjvQ7J8dtoulyaGrW8gcLdM59JhUOUaWB8U5qxd8obSO9wcz5lo7Vn1IJlw84MrAIXCp2rC1yw5z4+8el0nQ==", "dependencies": { "rimraf": "^5.0.5", "typescript": "^4.4.3", diff --git a/package.json b/package.json index 052d1f68..7212bfd6 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "format": "prettier --write ./src" }, "dependencies": { - "@budgetbuddyde/types": "^1.0.20", + "@budgetbuddyde/types": "^1.0.21", "@emotion/react": "^11.11.1", "@emotion/styled": "^11.11.0", "@mui/icons-material": "^5.14.15", From 0431cc199ac0182ddd8c7337be2e420045d2c42b Mon Sep 17 00:00:00 2001 From: Thorben <38816229+tklein1801@users.noreply.github.com> Date: Sun, 7 Apr 2024 14:54:57 +0200 Subject: [PATCH 5/8] fix(chart): Provide correct chart xAxis categories --- src/routes/Stock.route.tsx | 43 +++++++++++++++++++++++++++---- src/services/Date.service.ts | 19 ++++++++++++-- src/services/Formatter.service.ts | 6 +++++ 3 files changed, 61 insertions(+), 7 deletions(-) diff --git a/src/routes/Stock.route.tsx b/src/routes/Stock.route.tsx index a625612e..cd714b57 100644 --- a/src/routes/Stock.route.tsx +++ b/src/routes/Stock.route.tsx @@ -124,7 +124,6 @@ export const Stock = () => { colors: theme.palette.text.primary, }, }, - categories: stockDetails?.details.securityDetails?.annualFinancials.map(({date}) => date.getFullYear()).reverse(), }, dataLabels: { enabled: false, @@ -228,6 +227,8 @@ export const Stock = () => { }, }; + React.useEffect(() => console.log('stockDetails', stockDetails?.details.securityDetails), [stockDetails]); + React.useLayoutEffect(() => { if (!params.isin) return; socket.connect(); @@ -356,7 +357,15 @@ export const Stock = () => { type="bar" width={'100%'} height={300} - options={chartOptions} + options={{ + ...chartOptions, + xaxis: { + ...chartOptions.xaxis, + categories: stockDetails?.details.securityDetails?.annualFinancials + .map(({date}) => date.getFullYear()) + .reverse(), + }, + }} series={[ { name: `Revenue (${stockDetails.details.securityDetails.currency})`, @@ -380,7 +389,15 @@ export const Stock = () => { type="bar" width={'100%'} height={300} - options={chartOptions} + options={{ + ...chartOptions, + xaxis: { + ...chartOptions.xaxis, + categories: stockDetails?.details.securityDetails?.quarterlyFinancials + .map(({date}) => `${Formatter.formatDate().shortMonthName(date)} ${date.getFullYear()}`) + .reverse(), + }, + }} series={[ { name: `Revenue (${stockDetails.details.securityDetails.currency})`, @@ -423,7 +440,15 @@ export const Stock = () => { type="bar" width={'100%'} height={300} - options={chartOptions} + options={{ + ...chartOptions, + xaxis: { + ...chartOptions.xaxis, + categories: stockDetails?.details.securityDetails?.annualFinancials + .map(({date}) => date.getFullYear()) + .reverse(), + }, + }} series={[ { name: `Revenue (${stockDetails.details.securityDetails.currency})`, @@ -459,7 +484,15 @@ export const Stock = () => { type="bar" width={'100%'} height={300} - options={chartOptions} + options={{ + ...chartOptions, + xaxis: { + ...chartOptions.xaxis, + categories: stockDetails?.details.securityDetails?.quarterlyFinancials + .map(({date}) => `${Formatter.formatDate().shortMonthName(date)} ${date.getFullYear()}`) + .reverse(), + }, + }} series={[ { name: `Revenue (${stockDetails.details.securityDetails.currency})`, diff --git a/src/services/Date.service.ts b/src/services/Date.service.ts index fb76396b..c141a864 100644 --- a/src/services/Date.service.ts +++ b/src/services/Date.service.ts @@ -13,12 +13,27 @@ export class DateService { 'November', 'December', ]; + months = DateService.months; + /** + * @deprecated Use Formatter.formatDate().getMonthFromDate() instead. + */ static getMonthFromDate(date: Date = new Date()) { return this.months[date.getMonth()]; } - static shortMonthName(date: Date = new Date()) { - return this.getMonthFromDate(date).substring(0, 3); + getMonthFromDate(date: Date = new Date()) { + return DateService.getMonthFromDate(date); + } + + /** + * @deprecated Use Formatter.formatDate().shortMonthName() instead. + */ + static shortMonthName(date: Date = new Date(), maxLength = 3) { + return this.getMonthFromDate(date).substring(0, maxLength); + } + + shortMonthName(date: Date = new Date(), maxLength = 3) { + return DateService.shortMonthName(date, maxLength); } } diff --git a/src/services/Formatter.service.ts b/src/services/Formatter.service.ts index b2ab9c93..db9d43e3 100644 --- a/src/services/Formatter.service.ts +++ b/src/services/Formatter.service.ts @@ -1,3 +1,5 @@ +import {DateService} from './Date.service'; + export class Formatter { /** * Formats the balance amount as a currency string. @@ -26,4 +28,8 @@ export class Formatter { } return formattedVal as string; } + + static formatDate() { + return new DateService(); + } } From e2520801e70802d2e5b67da13ecc185d1c567e0d Mon Sep 17 00:00:00 2001 From: Thorben <38816229+tklein1801@users.noreply.github.com> Date: Sun, 7 Apr 2024 15:25:57 +0200 Subject: [PATCH 6/8] feat: Calculate usage per category & payment-method --- src/components/Category/Chip.component.tsx | 24 +++++++++-- .../PaymentMethod/Chip.component.tsx | 30 ++++++++++++-- .../Transaction/Transaction.service.ts | 41 +++++++++++++++++++ .../Transaction/Transaction.store.ts | 22 ++++++++-- src/routes/Categories.route.tsx | 2 +- src/routes/PaymentMethods.route.tsx | 2 +- 6 files changed, 109 insertions(+), 12 deletions(-) diff --git a/src/components/Category/Chip.component.tsx b/src/components/Category/Chip.component.tsx index 2533a211..79545d94 100644 --- a/src/components/Category/Chip.component.tsx +++ b/src/components/Category/Chip.component.tsx @@ -1,12 +1,14 @@ import React from 'react'; -import {Chip, type ChipProps} from '@mui/material'; +import {Chip, Tooltip, type ChipProps} from '@mui/material'; import type {TCategory} from '@budgetbuddyde/types'; import {useFilterStore} from '../Filter'; +import {useTransactionStore} from '../Transaction'; -export type TCategoryChipProps = ChipProps & {category: TCategory}; +export type TCategoryChipProps = ChipProps & {category: TCategory; showUsage?: boolean}; -export const CategoryChip: React.FC = ({category, ...otherProps}) => { +export const CategoryChip: React.FC = ({category, showUsage = false, ...otherProps}) => { const {filters, setFilters} = useFilterStore(); + const {categoryUsage} = useTransactionStore(); const handleChipClick = () => { if (!filters.categories) { @@ -30,6 +32,22 @@ export const CategoryChip: React.FC = ({category, ...otherPr }); }; + if (showUsage) { + return ( + + + + ); + } return ( = ({paymentMethod, ...otherProps}) => { +export const PaymentMethodChip: React.FC = ({ + paymentMethod, + showUsage = false, + ...otherProps +}) => { const {filters, setFilters} = useFilterStore(); + const {paymentMethodUsage} = useTransactionStore(); const handleChipClick = () => { if (!filters.paymentMethods) { @@ -30,6 +36,24 @@ export const PaymentMethodChip: React.FC = ({paymentMet }); }; + if (showUsage) { + return ( + + + + ); + } return ( { data: T; @@ -8,6 +9,8 @@ export interface IBaseStore { } export interface ITransactionStore extends IBaseStore { + categoryUsage: Map; + paymentMethodUsage: Map; fetchedBy: TUser['uuid'] | null; fetchedAt: Date | null; setFetchedData: (data: TTransaction[], fetchedBy: TUser['uuid'] | null) => void; @@ -15,9 +18,20 @@ export interface ITransactionStore extends IBaseStore { export const useTransactionStore = create(set => ({ data: [], + categoryUsage: new Map(), + paymentMethodUsage: new Map(), fetchedBy: null, fetchedAt: null, - set: data => set({data: data}), - setFetchedData: (data, fetchedBy) => set({data: data, fetchedBy: fetchedBy, fetchedAt: new Date()}), - clear: () => set({data: [], fetchedBy: null, fetchedAt: null}), + set: data => { + const categoryUsage = TransactionService.calculateUsagePerCategory(data); + const paymentMethodUsage = TransactionService.calculateUsagePerPaymentMethod(data); + set({data, categoryUsage, paymentMethodUsage}); + }, + setFetchedData: (data, fetchedBy) => { + const categoryUsage = TransactionService.calculateUsagePerCategory(data); + const paymentMethodUsage = TransactionService.calculateUsagePerPaymentMethod(data); + set({data, categoryUsage, paymentMethodUsage, fetchedBy: fetchedBy, fetchedAt: new Date()}); + }, + clear: () => + set({data: [], categoryUsage: new Map(), paymentMethodUsage: new Map(), fetchedBy: null, fetchedAt: null}), })); diff --git a/src/routes/Categories.route.tsx b/src/routes/Categories.route.tsx index 49c4ceb3..8b80ac8a 100644 --- a/src/routes/Categories.route.tsx +++ b/src/routes/Categories.route.tsx @@ -153,7 +153,7 @@ export const Categories = () => { /> - + {category.description ?? 'No Description'} diff --git a/src/routes/PaymentMethods.route.tsx b/src/routes/PaymentMethods.route.tsx index fee29d7d..eba2a11d 100644 --- a/src/routes/PaymentMethods.route.tsx +++ b/src/routes/PaymentMethods.route.tsx @@ -151,7 +151,7 @@ export const PaymentMethods = () => { /> - + {/* TODO: Format when is IBAN */} From 1be9b77357062d7be78a41dfeda0dbcd50436c23 Mon Sep 17 00:00:00 2001 From: Thorben <38816229+tklein1801@users.noreply.github.com> Date: Sun, 7 Apr 2024 19:06:07 +0200 Subject: [PATCH 7/8] feat(ui): Implement button for JSON file downloads --- .../Download/DownloadButton.component.tsx | 48 +++++++++++++++++++ src/components/Download/index.ts | 1 + 2 files changed, 49 insertions(+) create mode 100644 src/components/Download/DownloadButton.component.tsx create mode 100644 src/components/Download/index.ts diff --git a/src/components/Download/DownloadButton.component.tsx b/src/components/Download/DownloadButton.component.tsx new file mode 100644 index 00000000..b632ba85 --- /dev/null +++ b/src/components/Download/DownloadButton.component.tsx @@ -0,0 +1,48 @@ +import React from 'react'; +import {Button, Tooltip, type ButtonProps} from '@mui/material'; +import {useSnackbarContext} from '@/components/Snackbar'; + +export type TDownloadButtonProps = ButtonProps & { + exportFormat: /*'CSV' |*/ 'JSON'; + exportFileName: string; + data: object; + withTooltip?: boolean; +}; + +export const DownloadButton: React.FC = ({ + exportFormat, + exportFileName, + data, + ...buttonProps +}) => { + const {showSnackbar} = useSnackbarContext(); + const downloadBtnRef = React.useRef(null); + + const downloadContent = () => { + if (!downloadBtnRef.current) + return showSnackbar({ + message: 'Download button not found', + action: , + }); + downloadBtnRef.current.setAttribute( + 'href', + `data:text/json;charset=utf-8,${encodeURIComponent(JSON.stringify(data, null, 2))}`, + ); + downloadBtnRef.current?.setAttribute('download', `${exportFileName}.${exportFormat.toLowerCase()}`); + downloadBtnRef.current.click(); + showSnackbar({message: 'Download started'}); + }; + + return ( + + {buttonProps.withTooltip ? ( + +