Display accounts balances
This commit is contained in:
@ -1,4 +1,5 @@
|
|||||||
import { APIClient } from "./ApiClient";
|
import { APIClient } from "./ApiClient";
|
||||||
|
import { Balances } from "./MovementsApi";
|
||||||
import { ServerApi } from "./ServerApi";
|
import { ServerApi } from "./ServerApi";
|
||||||
|
|
||||||
export interface Account {
|
export interface Account {
|
||||||
@ -9,6 +10,7 @@ export interface Account {
|
|||||||
time_update: number;
|
time_update: number;
|
||||||
type: string;
|
type: string;
|
||||||
default_account: boolean;
|
default_account: boolean;
|
||||||
|
balance: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class AccountsList {
|
export class AccountsList {
|
||||||
@ -32,6 +34,18 @@ export class AccountsList {
|
|||||||
get(id: number): Account | null {
|
get(id: number): Account | null {
|
||||||
return this.list.find((a) => a.id === id) ?? 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 {
|
export class AccountApi {
|
||||||
@ -41,7 +55,7 @@ export class AccountApi {
|
|||||||
static async GetList(): Promise<AccountsList> {
|
static async GetList(): Promise<AccountsList> {
|
||||||
const array = (
|
const array = (
|
||||||
await APIClient.exec({
|
await APIClient.exec({
|
||||||
uri: "/accounts",
|
uri: `/accounts?include_balances=true`,
|
||||||
method: "GET",
|
method: "GET",
|
||||||
})
|
})
|
||||||
).data;
|
).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 React from "react";
|
||||||
import { Account, AccountApi, AccountsList } from "../api/AccountApi";
|
import { Account, AccountApi, AccountsList } from "../api/AccountApi";
|
||||||
import { AsyncWidget } from "../widgets/AsyncWidget";
|
import { AsyncWidget } from "../widgets/AsyncWidget";
|
||||||
|
import { useSnackbar } from "./context_providers/SnackbarProvider";
|
||||||
|
import { MovementApi } from "../api/MovementsApi";
|
||||||
|
|
||||||
interface AccountContext {
|
interface AccountContext {
|
||||||
list: AccountsList;
|
list: AccountsList;
|
||||||
reload: () => Promise<void>;
|
reload: () => Promise<void>;
|
||||||
|
reloadBalances: () => Promise<void>;
|
||||||
get(id: number): Account | null;
|
get(id: number): Account | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -13,6 +16,8 @@ const AccountContextK = React.createContext<AccountContext | null>(null);
|
|||||||
export function AccountsListProvider(
|
export function AccountsListProvider(
|
||||||
p: React.PropsWithChildren
|
p: React.PropsWithChildren
|
||||||
): React.ReactElement {
|
): React.ReactElement {
|
||||||
|
const snackbar = useSnackbar();
|
||||||
|
|
||||||
const [list, setList] = React.useState<AccountsList | null>(null);
|
const [list, setList] = React.useState<AccountsList | null>(null);
|
||||||
|
|
||||||
const loadKey = React.useRef(1);
|
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 (
|
return (
|
||||||
<AsyncWidget
|
<AsyncWidget
|
||||||
ready={list !== null}
|
ready={list !== null}
|
||||||
@ -55,6 +70,7 @@ export function AccountsListProvider(
|
|||||||
return list!.get(id);
|
return list!.get(id);
|
||||||
},
|
},
|
||||||
reload: onReload,
|
reload: onReload,
|
||||||
|
reloadBalances,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{p.children}
|
{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 { useAccounts } from "../hooks/AccountsListProvider";
|
||||||
import { AccountWidget } from "./AccountWidget";
|
import { AccountWidget } from "./AccountWidget";
|
||||||
import { RouterLink } from "./RouterLink";
|
import { RouterLink } from "./RouterLink";
|
||||||
|
import { AmountWidget } from "./AmountWidget";
|
||||||
|
|
||||||
export function MoneyNavList(): React.ReactElement {
|
export function MoneyNavList(): React.ReactElement {
|
||||||
const accounts = useAccounts();
|
const accounts = useAccounts();
|
||||||
@ -61,6 +62,7 @@ export function MoneyNavList(): React.ReactElement {
|
|||||||
<NavLink
|
<NavLink
|
||||||
key={a.id}
|
key={a.id}
|
||||||
label={a.name}
|
label={a.name}
|
||||||
|
secondaryLabel={<AmountWidget amount={a.balance} />}
|
||||||
uri={`/account/${a.id}`}
|
uri={`/account/${a.id}`}
|
||||||
icon={<AccountWidget account={a} />}
|
icon={<AccountWidget account={a} />}
|
||||||
/>
|
/>
|
||||||
@ -72,14 +74,15 @@ export function MoneyNavList(): React.ReactElement {
|
|||||||
function NavLink(p: {
|
function NavLink(p: {
|
||||||
icon: React.ReactElement;
|
icon: React.ReactElement;
|
||||||
uri: string;
|
uri: string;
|
||||||
label: string;
|
label: string | React.ReactElement;
|
||||||
|
secondaryLabel?: string | React.ReactElement;
|
||||||
}): React.ReactElement {
|
}): React.ReactElement {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
return (
|
return (
|
||||||
<RouterLink to={p.uri}>
|
<RouterLink to={p.uri}>
|
||||||
<ListItemButton selected={p.uri === location.pathname}>
|
<ListItemButton selected={p.uri === location.pathname}>
|
||||||
<ListItemIcon>{p.icon}</ListItemIcon>
|
<ListItemIcon>{p.icon}</ListItemIcon>
|
||||||
<ListItemText primary={p.label} />
|
<ListItemText primary={p.label} secondary={p.secondaryLabel} />
|
||||||
</ListItemButton>
|
</ListItemButton>
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
);
|
);
|
||||||
|
Reference in New Issue
Block a user