Display accounts balances

This commit is contained in:
2025-04-21 12:31:00 +02:00
parent 6ee250d872
commit 1621fe41e2
5 changed files with 60 additions and 3 deletions

View File

@ -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;

View 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;
}
}

View File

@ -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}

View 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>
);
}

View File

@ -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>
);