diff --git a/moneymgr_web/src/api/AccountApi.ts b/moneymgr_web/src/api/AccountApi.ts index cdb5538..f48408d 100644 --- a/moneymgr_web/src/api/AccountApi.ts +++ b/moneymgr_web/src/api/AccountApi.ts @@ -1,4 +1,5 @@ import { APIClient } from "./ApiClient"; +import { Balances } from "./MovementsApi"; import { ServerApi } from "./ServerApi"; export interface Account { @@ -9,6 +10,7 @@ export interface Account { time_update: number; type: string; default_account: boolean; + balance: number; } export class AccountsList { @@ -32,6 +34,18 @@ export class AccountsList { get(id: number): Account | null { return this.list.find((a) => a.id === id) ?? null; } + + /** + * Update all accounts balances + */ + mapBalances(balances: Balances): AccountsList { + return new AccountsList( + this.list.map((a) => { + a.balance = balances[a.id] ?? 0; + return a; + }) + ); + } } export class AccountApi { @@ -41,7 +55,7 @@ export class AccountApi { static async GetList(): Promise { const array = ( await APIClient.exec({ - uri: "/accounts", + uri: `/accounts?include_balances=true`, method: "GET", }) ).data; diff --git a/moneymgr_web/src/api/MovementsApi.ts b/moneymgr_web/src/api/MovementsApi.ts new file mode 100644 index 0000000..96b66ce --- /dev/null +++ b/moneymgr_web/src/api/MovementsApi.ts @@ -0,0 +1,19 @@ +import { APIClient } from "./ApiClient"; + +export interface Balances { + [key: number]: number; +} + +export class MovementApi { + /** + * Get all accounts balances + */ + static async GetAllBalances(): Promise { + return ( + await APIClient.exec({ + uri: `/accounts/balances`, + method: "GET", + }) + ).data; + } +} diff --git a/moneymgr_web/src/hooks/AccountsListProvider.tsx b/moneymgr_web/src/hooks/AccountsListProvider.tsx index d01fef5..3d9b4e7 100644 --- a/moneymgr_web/src/hooks/AccountsListProvider.tsx +++ b/moneymgr_web/src/hooks/AccountsListProvider.tsx @@ -1,10 +1,13 @@ import React from "react"; import { Account, AccountApi, AccountsList } from "../api/AccountApi"; import { AsyncWidget } from "../widgets/AsyncWidget"; +import { useSnackbar } from "./context_providers/SnackbarProvider"; +import { MovementApi } from "../api/MovementsApi"; interface AccountContext { list: AccountsList; reload: () => Promise; + reloadBalances: () => Promise; get(id: number): Account | null; } @@ -13,6 +16,8 @@ const AccountContextK = React.createContext(null); export function AccountsListProvider( p: React.PropsWithChildren ): React.ReactElement { + const snackbar = useSnackbar(); + const [list, setList] = React.useState(null); const loadKey = React.useRef(1); @@ -35,6 +40,16 @@ export function AccountsListProvider( }); }; + const reloadBalances = async () => { + try { + const balances = await MovementApi.GetAllBalances(); + setList(list!.mapBalances(balances)); + } catch (e) { + console.error("Failed to reload accounts balance!", e); + snackbar(`Failed to reload accounts balance! ${e}`); + } + }; + return ( {p.children} diff --git a/moneymgr_web/src/widgets/AmountWidget.tsx b/moneymgr_web/src/widgets/AmountWidget.tsx new file mode 100644 index 0000000..bbcb8f4 --- /dev/null +++ b/moneymgr_web/src/widgets/AmountWidget.tsx @@ -0,0 +1,5 @@ +export function AmountWidget(p: { amount: number }): React.ReactElement { + return ( + {p.amount} € + ); +} diff --git a/moneymgr_web/src/widgets/MoneyNavList.tsx b/moneymgr_web/src/widgets/MoneyNavList.tsx index da2e67b..5d99412 100644 --- a/moneymgr_web/src/widgets/MoneyNavList.tsx +++ b/moneymgr_web/src/widgets/MoneyNavList.tsx @@ -13,6 +13,7 @@ import { useLocation } from "react-router-dom"; import { useAccounts } from "../hooks/AccountsListProvider"; import { AccountWidget } from "./AccountWidget"; import { RouterLink } from "./RouterLink"; +import { AmountWidget } from "./AmountWidget"; export function MoneyNavList(): React.ReactElement { const accounts = useAccounts(); @@ -61,6 +62,7 @@ export function MoneyNavList(): React.ReactElement { } uri={`/account/${a.id}`} icon={} /> @@ -72,14 +74,15 @@ export function MoneyNavList(): React.ReactElement { function NavLink(p: { icon: React.ReactElement; uri: string; - label: string; + label: string | React.ReactElement; + secondaryLabel?: string | React.ReactElement; }): React.ReactElement { const location = useLocation(); return ( {p.icon} - + );