Display in a chip the number of unmatched inbox entries
This commit is contained in:
parent
0b586039c3
commit
1e8064946a
@ -21,6 +21,17 @@ export interface InboxEntryUpdate {
|
||||
}
|
||||
|
||||
export class InboxApi {
|
||||
/**
|
||||
* Get the number of unmatched entries
|
||||
*/
|
||||
static async CountUnmatched(): Promise<number> {
|
||||
return (
|
||||
await APIClient.exec({
|
||||
uri: `/inbox/count?include_attached=false`,
|
||||
method: "GET",
|
||||
})
|
||||
).data.count;
|
||||
}
|
||||
/**
|
||||
* Create a new inbox entry
|
||||
*/
|
||||
|
@ -0,0 +1,67 @@
|
||||
import React from "react";
|
||||
import { InboxApi } from "../api/InboxApi";
|
||||
import { AsyncWidget } from "../widgets/AsyncWidget";
|
||||
|
||||
interface UnmatchedInboxEntriesCountContext {
|
||||
count: number;
|
||||
reload: () => Promise<void>;
|
||||
}
|
||||
|
||||
const UnmatchedInboxEntriesCountContextK =
|
||||
React.createContext<UnmatchedInboxEntriesCountContext | null>(null);
|
||||
|
||||
export function UnmatchedInboxEntriesCountProvider(
|
||||
p: React.PropsWithChildren
|
||||
): React.ReactElement {
|
||||
const [count, setCount] = React.useState<number | null>(null);
|
||||
|
||||
const loadKey = React.useRef(1);
|
||||
|
||||
const loadPromise = React.useRef<(() => void) | null>(null);
|
||||
|
||||
const load = async () => {
|
||||
setCount(await InboxApi.CountUnmatched());
|
||||
};
|
||||
|
||||
const onReload = async () => {
|
||||
loadKey.current += 1;
|
||||
|
||||
load();
|
||||
|
||||
return new Promise<void>((res) => {
|
||||
loadPromise.current = () => {
|
||||
res();
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<AsyncWidget
|
||||
ready={true}
|
||||
loadKey={loadKey.current}
|
||||
load={load}
|
||||
errMsg="Failed to get the number of unread inbox entries!"
|
||||
build={() => {
|
||||
if (loadPromise.current != null) {
|
||||
loadPromise.current();
|
||||
loadPromise.current = null;
|
||||
}
|
||||
|
||||
return (
|
||||
<UnmatchedInboxEntriesCountContextK
|
||||
value={{
|
||||
count: count ?? 0,
|
||||
reload: onReload,
|
||||
}}
|
||||
>
|
||||
{p.children}
|
||||
</UnmatchedInboxEntriesCountContextK>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function useUnmatchedInboxEntriesCount(): UnmatchedInboxEntriesCountContext {
|
||||
return React.use(UnmatchedInboxEntriesCountContextK)!;
|
||||
}
|
@ -7,11 +7,14 @@ import { useLoadingMessage } from "../hooks/context_providers/LoadingMessageProv
|
||||
import { AsyncWidget } from "../widgets/AsyncWidget";
|
||||
import { MoneyMgrWebRouteContainer } from "../widgets/MoneyMgrWebRouteContainer";
|
||||
import { NewMovementWidget } from "../widgets/NewMovementWidget";
|
||||
import { useUnmatchedInboxEntriesCount } from "../hooks/UnmatchedInboxEntriesCountProvider";
|
||||
|
||||
export function InboxRoute(): React.ReactElement {
|
||||
const loadingMessage = useLoadingMessage();
|
||||
const alert = useAlert();
|
||||
|
||||
const unmatched = useUnmatchedInboxEntriesCount();
|
||||
|
||||
const [entries, setEntries] = React.useState<InboxEntry[] | undefined>();
|
||||
const [includeAttached, setIncludeAttached] = React.useState(false);
|
||||
|
||||
@ -24,7 +27,7 @@ export function InboxRoute(): React.ReactElement {
|
||||
const reload = async (skipEntries: boolean) => {
|
||||
try {
|
||||
loadingMessage.show("Refreshing the list of inbox entries...");
|
||||
// TODO : trigger reload number of inbox entries
|
||||
unmatched.reload();
|
||||
if (!skipEntries) await load();
|
||||
} catch (e) {
|
||||
console.error("Failed to load list of inbox entries!", e);
|
||||
|
@ -1,13 +1,14 @@
|
||||
import { Box, Button } from "@mui/material";
|
||||
import * as React from "react";
|
||||
import { Outlet, useNavigate } from "react-router-dom";
|
||||
import { useAuth } from "../App";
|
||||
import { AuthApi, AuthInfo } from "../api/AuthApi";
|
||||
import { useAuth } from "../App";
|
||||
import { AccountsListProvider } from "../hooks/AccountsListProvider";
|
||||
import { ChooseAccountDialogProvider } from "../hooks/context_providers/ChooseAccountDialogProvider";
|
||||
import { UnmatchedInboxEntriesCountProvider } from "../hooks/UnmatchedInboxEntriesCountProvider";
|
||||
import { AsyncWidget } from "./AsyncWidget";
|
||||
import { MoneyNavList } from "./MoneyNavList";
|
||||
import { MoneyWebAppBar } from "./MoneyWebAppBar";
|
||||
import { ChooseAccountDialogProvider } from "../hooks/context_providers/ChooseAccountDialogProvider";
|
||||
|
||||
interface AuthInfoContext {
|
||||
info: AuthInfo;
|
||||
@ -48,6 +49,7 @@ export function BaseAuthenticatedPage(): React.ReactElement {
|
||||
reloadAuthInfo: load,
|
||||
}}
|
||||
>
|
||||
<UnmatchedInboxEntriesCountProvider>
|
||||
<AccountsListProvider>
|
||||
<ChooseAccountDialogProvider>
|
||||
<Box
|
||||
@ -90,6 +92,7 @@ export function BaseAuthenticatedPage(): React.ReactElement {
|
||||
</Box>
|
||||
</ChooseAccountDialogProvider>
|
||||
</AccountsListProvider>
|
||||
</UnmatchedInboxEntriesCountProvider>
|
||||
</AuthInfoContextK>
|
||||
)}
|
||||
/>
|
||||
|
@ -1,8 +1,10 @@
|
||||
import { mdiCashMultiple, mdiHome, mdiInbox } from "@mdi/js";
|
||||
import Icon from "@mdi/react";
|
||||
import {
|
||||
Chip,
|
||||
Divider,
|
||||
List,
|
||||
ListItem,
|
||||
ListItemButton,
|
||||
ListItemIcon,
|
||||
ListItemText,
|
||||
@ -15,10 +17,12 @@ import { usePublicMode } from "../hooks/context_providers/PublicModeProvider";
|
||||
import { AccountWidget } from "./AccountWidget";
|
||||
import { AmountWidget } from "./AmountWidget";
|
||||
import { RouterLink } from "./RouterLink";
|
||||
import { useUnmatchedInboxEntriesCount } from "../hooks/UnmatchedInboxEntriesCountProvider";
|
||||
|
||||
export function MoneyNavList(): React.ReactElement {
|
||||
const publicMode = usePublicMode();
|
||||
const accounts = useAccounts();
|
||||
const unmatched = useUnmatchedInboxEntriesCount();
|
||||
return (
|
||||
<List
|
||||
dense
|
||||
@ -35,11 +39,17 @@ export function MoneyNavList(): React.ReactElement {
|
||||
uri="/accounts"
|
||||
icon={<Icon path={mdiCashMultiple} size={1} />}
|
||||
/>
|
||||
{/* TODO : show number of unmatched */}
|
||||
<NavLink
|
||||
label="Inbox"
|
||||
uri="/inbox"
|
||||
icon={<Icon path={mdiInbox} size={1} />}
|
||||
secondary={
|
||||
unmatched.count > 0 ? (
|
||||
<Chip label={unmatched.count} size="small" />
|
||||
) : (
|
||||
<></>
|
||||
)
|
||||
}
|
||||
/>
|
||||
<Divider />
|
||||
{accounts.list.isEmpty && (
|
||||
@ -76,14 +86,17 @@ function NavLink(p: {
|
||||
uri: string;
|
||||
label: string | React.ReactElement;
|
||||
secondaryLabel?: string | React.ReactElement;
|
||||
secondary?: React.ReactElement;
|
||||
}): React.ReactElement {
|
||||
const location = useLocation();
|
||||
return (
|
||||
<RouterLink to={p.uri}>
|
||||
<ListItem secondaryAction={p.secondary} disablePadding>
|
||||
<ListItemButton selected={p.uri === location.pathname}>
|
||||
<ListItemIcon>{p.icon}</ListItemIcon>
|
||||
<ListItemText primary={p.label} secondary={p.secondaryLabel} />
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
</RouterLink>
|
||||
);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user