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 {
|
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
|
* 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 { AsyncWidget } from "../widgets/AsyncWidget";
|
||||||
import { MoneyMgrWebRouteContainer } from "../widgets/MoneyMgrWebRouteContainer";
|
import { MoneyMgrWebRouteContainer } from "../widgets/MoneyMgrWebRouteContainer";
|
||||||
import { NewMovementWidget } from "../widgets/NewMovementWidget";
|
import { NewMovementWidget } from "../widgets/NewMovementWidget";
|
||||||
|
import { useUnmatchedInboxEntriesCount } from "../hooks/UnmatchedInboxEntriesCountProvider";
|
||||||
|
|
||||||
export function InboxRoute(): React.ReactElement {
|
export function InboxRoute(): React.ReactElement {
|
||||||
const loadingMessage = useLoadingMessage();
|
const loadingMessage = useLoadingMessage();
|
||||||
const alert = useAlert();
|
const alert = useAlert();
|
||||||
|
|
||||||
|
const unmatched = useUnmatchedInboxEntriesCount();
|
||||||
|
|
||||||
const [entries, setEntries] = React.useState<InboxEntry[] | undefined>();
|
const [entries, setEntries] = React.useState<InboxEntry[] | undefined>();
|
||||||
const [includeAttached, setIncludeAttached] = React.useState(false);
|
const [includeAttached, setIncludeAttached] = React.useState(false);
|
||||||
|
|
||||||
@ -24,7 +27,7 @@ export function InboxRoute(): React.ReactElement {
|
|||||||
const reload = async (skipEntries: boolean) => {
|
const reload = async (skipEntries: boolean) => {
|
||||||
try {
|
try {
|
||||||
loadingMessage.show("Refreshing the list of inbox entries...");
|
loadingMessage.show("Refreshing the list of inbox entries...");
|
||||||
// TODO : trigger reload number of inbox entries
|
unmatched.reload();
|
||||||
if (!skipEntries) await load();
|
if (!skipEntries) await load();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("Failed to load list of inbox entries!", e);
|
console.error("Failed to load list of inbox entries!", e);
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
import { Box, Button } from "@mui/material";
|
import { Box, Button } from "@mui/material";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { Outlet, useNavigate } from "react-router-dom";
|
import { Outlet, useNavigate } from "react-router-dom";
|
||||||
import { useAuth } from "../App";
|
|
||||||
import { AuthApi, AuthInfo } from "../api/AuthApi";
|
import { AuthApi, AuthInfo } from "../api/AuthApi";
|
||||||
|
import { useAuth } from "../App";
|
||||||
import { AccountsListProvider } from "../hooks/AccountsListProvider";
|
import { AccountsListProvider } from "../hooks/AccountsListProvider";
|
||||||
|
import { ChooseAccountDialogProvider } from "../hooks/context_providers/ChooseAccountDialogProvider";
|
||||||
|
import { UnmatchedInboxEntriesCountProvider } from "../hooks/UnmatchedInboxEntriesCountProvider";
|
||||||
import { AsyncWidget } from "./AsyncWidget";
|
import { AsyncWidget } from "./AsyncWidget";
|
||||||
import { MoneyNavList } from "./MoneyNavList";
|
import { MoneyNavList } from "./MoneyNavList";
|
||||||
import { MoneyWebAppBar } from "./MoneyWebAppBar";
|
import { MoneyWebAppBar } from "./MoneyWebAppBar";
|
||||||
import { ChooseAccountDialogProvider } from "../hooks/context_providers/ChooseAccountDialogProvider";
|
|
||||||
|
|
||||||
interface AuthInfoContext {
|
interface AuthInfoContext {
|
||||||
info: AuthInfo;
|
info: AuthInfo;
|
||||||
@ -48,6 +49,7 @@ export function BaseAuthenticatedPage(): React.ReactElement {
|
|||||||
reloadAuthInfo: load,
|
reloadAuthInfo: load,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
<UnmatchedInboxEntriesCountProvider>
|
||||||
<AccountsListProvider>
|
<AccountsListProvider>
|
||||||
<ChooseAccountDialogProvider>
|
<ChooseAccountDialogProvider>
|
||||||
<Box
|
<Box
|
||||||
@ -90,6 +92,7 @@ export function BaseAuthenticatedPage(): React.ReactElement {
|
|||||||
</Box>
|
</Box>
|
||||||
</ChooseAccountDialogProvider>
|
</ChooseAccountDialogProvider>
|
||||||
</AccountsListProvider>
|
</AccountsListProvider>
|
||||||
|
</UnmatchedInboxEntriesCountProvider>
|
||||||
</AuthInfoContextK>
|
</AuthInfoContextK>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
import { mdiCashMultiple, mdiHome, mdiInbox } from "@mdi/js";
|
import { mdiCashMultiple, mdiHome, mdiInbox } from "@mdi/js";
|
||||||
import Icon from "@mdi/react";
|
import Icon from "@mdi/react";
|
||||||
import {
|
import {
|
||||||
|
Chip,
|
||||||
Divider,
|
Divider,
|
||||||
List,
|
List,
|
||||||
|
ListItem,
|
||||||
ListItemButton,
|
ListItemButton,
|
||||||
ListItemIcon,
|
ListItemIcon,
|
||||||
ListItemText,
|
ListItemText,
|
||||||
@ -15,10 +17,12 @@ import { usePublicMode } from "../hooks/context_providers/PublicModeProvider";
|
|||||||
import { AccountWidget } from "./AccountWidget";
|
import { AccountWidget } from "./AccountWidget";
|
||||||
import { AmountWidget } from "./AmountWidget";
|
import { AmountWidget } from "./AmountWidget";
|
||||||
import { RouterLink } from "./RouterLink";
|
import { RouterLink } from "./RouterLink";
|
||||||
|
import { useUnmatchedInboxEntriesCount } from "../hooks/UnmatchedInboxEntriesCountProvider";
|
||||||
|
|
||||||
export function MoneyNavList(): React.ReactElement {
|
export function MoneyNavList(): React.ReactElement {
|
||||||
const publicMode = usePublicMode();
|
const publicMode = usePublicMode();
|
||||||
const accounts = useAccounts();
|
const accounts = useAccounts();
|
||||||
|
const unmatched = useUnmatchedInboxEntriesCount();
|
||||||
return (
|
return (
|
||||||
<List
|
<List
|
||||||
dense
|
dense
|
||||||
@ -35,11 +39,17 @@ export function MoneyNavList(): React.ReactElement {
|
|||||||
uri="/accounts"
|
uri="/accounts"
|
||||||
icon={<Icon path={mdiCashMultiple} size={1} />}
|
icon={<Icon path={mdiCashMultiple} size={1} />}
|
||||||
/>
|
/>
|
||||||
{/* TODO : show number of unmatched */}
|
|
||||||
<NavLink
|
<NavLink
|
||||||
label="Inbox"
|
label="Inbox"
|
||||||
uri="/inbox"
|
uri="/inbox"
|
||||||
icon={<Icon path={mdiInbox} size={1} />}
|
icon={<Icon path={mdiInbox} size={1} />}
|
||||||
|
secondary={
|
||||||
|
unmatched.count > 0 ? (
|
||||||
|
<Chip label={unmatched.count} size="small" />
|
||||||
|
) : (
|
||||||
|
<></>
|
||||||
|
)
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
<Divider />
|
<Divider />
|
||||||
{accounts.list.isEmpty && (
|
{accounts.list.isEmpty && (
|
||||||
@ -76,14 +86,17 @@ function NavLink(p: {
|
|||||||
uri: string;
|
uri: string;
|
||||||
label: string | React.ReactElement;
|
label: string | React.ReactElement;
|
||||||
secondaryLabel?: string | React.ReactElement;
|
secondaryLabel?: string | React.ReactElement;
|
||||||
|
secondary?: React.ReactElement;
|
||||||
}): React.ReactElement {
|
}): React.ReactElement {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
return (
|
return (
|
||||||
<RouterLink to={p.uri}>
|
<RouterLink to={p.uri}>
|
||||||
|
<ListItem secondaryAction={p.secondary} disablePadding>
|
||||||
<ListItemButton selected={p.uri === location.pathname}>
|
<ListItemButton selected={p.uri === location.pathname}>
|
||||||
<ListItemIcon>{p.icon}</ListItemIcon>
|
<ListItemIcon>{p.icon}</ListItemIcon>
|
||||||
<ListItemText primary={p.label} secondary={p.secondaryLabel} />
|
<ListItemText primary={p.label} secondary={p.secondaryLabel} />
|
||||||
</ListItemButton>
|
</ListItemButton>
|
||||||
|
</ListItem>
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user