Display accounts balances
This commit is contained in:
@ -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<AccountsList> {
|
||||
const array = (
|
||||
await APIClient.exec({
|
||||
uri: "/accounts",
|
||||
uri: `/accounts?include_balances=true`,
|
||||
method: "GET",
|
||||
})
|
||||
).data;
|
||||
|
19
moneymgr_web/src/api/MovementsApi.ts
Normal file
19
moneymgr_web/src/api/MovementsApi.ts
Normal file
@ -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<Balances> {
|
||||
return (
|
||||
await APIClient.exec({
|
||||
uri: `/accounts/balances`,
|
||||
method: "GET",
|
||||
})
|
||||
).data;
|
||||
}
|
||||
}
|
@ -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<void>;
|
||||
reloadBalances: () => Promise<void>;
|
||||
get(id: number): Account | null;
|
||||
}
|
||||
|
||||
@ -13,6 +16,8 @@ const AccountContextK = React.createContext<AccountContext | null>(null);
|
||||
export function AccountsListProvider(
|
||||
p: React.PropsWithChildren
|
||||
): React.ReactElement {
|
||||
const snackbar = useSnackbar();
|
||||
|
||||
const [list, setList] = React.useState<AccountsList | null>(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 (
|
||||
<AsyncWidget
|
||||
ready={list !== null}
|
||||
@ -55,6 +70,7 @@ export function AccountsListProvider(
|
||||
return list!.get(id);
|
||||
},
|
||||
reload: onReload,
|
||||
reloadBalances,
|
||||
}}
|
||||
>
|
||||
{p.children}
|
||||
|
5
moneymgr_web/src/widgets/AmountWidget.tsx
Normal file
5
moneymgr_web/src/widgets/AmountWidget.tsx
Normal file
@ -0,0 +1,5 @@
|
||||
export function AmountWidget(p: { amount: number }): React.ReactElement {
|
||||
return (
|
||||
<span style={{ color: p.amount < 0 ? "red" : "green" }}>{p.amount} €</span>
|
||||
);
|
||||
}
|
@ -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 {
|
||||
<NavLink
|
||||
key={a.id}
|
||||
label={a.name}
|
||||
secondaryLabel={<AmountWidget amount={a.balance} />}
|
||||
uri={`/account/${a.id}`}
|
||||
icon={<AccountWidget account={a} />}
|
||||
/>
|
||||
@ -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 (
|
||||
<RouterLink to={p.uri}>
|
||||
<ListItemButton selected={p.uri === location.pathname}>
|
||||
<ListItemIcon>{p.icon}</ListItemIcon>
|
||||
<ListItemText primary={p.label} />
|
||||
<ListItemText primary={p.label} secondary={p.secondaryLabel} />
|
||||
</ListItemButton>
|
||||
</RouterLink>
|
||||
);
|
||||
|
Reference in New Issue
Block a user