feat: add all-accounts portfolio pages
Reuse account sections and group securities with per-account holdings. Use Mantine Select to preserve filters and reset pagination on scope changes. Document component reuse guardrails and cover routes, grouping and scope switching.
This commit is contained in:
@@ -5,13 +5,16 @@ import { AccountEventsPageError } from "./components/AccountEventsPageError.view
|
||||
import { AccountEventsPageLoaded } from "./components/AccountEventsPageLoaded.view";
|
||||
import { AccountEventsPageLoading } from "./components/AccountEventsPageLoading.view";
|
||||
|
||||
const AccountEventsPage: FC = () => {
|
||||
const viewModel = useAccountEventsPageViewModel();
|
||||
type AccountEventsPageProps = { accountId?: string };
|
||||
|
||||
const AccountEventsPage: FC<AccountEventsPageProps> = (props) => {
|
||||
const viewModel = useAccountEventsPageViewModel(props.accountId);
|
||||
if (viewModel.state === "loading") return <AccountEventsPageLoading />;
|
||||
if (viewModel.state === "error")
|
||||
return <AccountEventsPageError retry={viewModel.retry} />;
|
||||
if (viewModel.state === "empty") return <AccountEventsPageEmpty />;
|
||||
return <AccountEventsPageLoaded events={viewModel.events} />;
|
||||
if (viewModel.state === "empty")
|
||||
return <AccountEventsPageEmpty {...viewModel} />;
|
||||
return <AccountEventsPageLoaded {...viewModel} />;
|
||||
};
|
||||
|
||||
export { AccountEventsPage };
|
||||
|
||||
@@ -4,20 +4,37 @@ import {
|
||||
useBrokerageEvents,
|
||||
} from "@/entities/brokerage-event";
|
||||
|
||||
type EventsRefreshState = {
|
||||
refreshing?: boolean;
|
||||
refreshError?: boolean;
|
||||
retry?: () => void;
|
||||
};
|
||||
|
||||
type AccountEventsPageViewModel =
|
||||
| { state: "loading" }
|
||||
| { state: "empty" }
|
||||
| ({ state: "empty" } & EventsRefreshState)
|
||||
| { state: "error"; retry: () => void }
|
||||
| { state: "loaded"; events: BrokerageEvent[] };
|
||||
| ({ state: "loaded"; events: BrokerageEvent[] } & EventsRefreshState);
|
||||
|
||||
function useAccountEventsPageViewModel(): AccountEventsPageViewModel {
|
||||
const { accountId } = useParams();
|
||||
function useAccountEventsPageViewModel(
|
||||
scopeId?: string,
|
||||
): AccountEventsPageViewModel {
|
||||
const { accountId: routeAccountId } = useParams();
|
||||
const accountId = scopeId ?? routeAccountId;
|
||||
const events = useBrokerageEvents(accountId);
|
||||
if (events.isLoading) return { state: "loading" };
|
||||
if (events.isError) return { state: "error", retry: () => events.refetch() };
|
||||
if (events.isError && !events.data)
|
||||
return { state: "error", retry: () => events.refetch() };
|
||||
if (!events.data) return { state: "loading" };
|
||||
if (!events.data.events.length) return { state: "empty" };
|
||||
return { state: "loaded", events: events.data.events };
|
||||
const refresh = {
|
||||
refreshing: events.isFetching,
|
||||
refreshError: events.isError,
|
||||
retry: () => {
|
||||
void events.refetch();
|
||||
},
|
||||
};
|
||||
if (!events.data.events.length) return { state: "empty", ...refresh };
|
||||
return { state: "loaded", events: events.data.events, ...refresh };
|
||||
}
|
||||
|
||||
export { useAccountEventsPageViewModel };
|
||||
export { type EventsRefreshState, useAccountEventsPageViewModel };
|
||||
|
||||
@@ -1,13 +1,25 @@
|
||||
import { Stack, Text, Title } from "@mantine/core";
|
||||
import { Alert, Button, Progress, Stack, Text, Title } from "@mantine/core";
|
||||
import type { FC } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { StateView } from "@/shared/ui";
|
||||
import type { EventsRefreshState } from "../AccountEventsPage.vm";
|
||||
|
||||
const AccountEventsPageEmpty: FC = () => {
|
||||
const AccountEventsPageEmpty: FC<EventsRefreshState> = (props) => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Stack maw={960} gap="lg">
|
||||
<Title order={1}>{t("events.title")}</Title>
|
||||
{props.refreshing ? (
|
||||
<Progress value={100} animated aria-label={t("accounts.refreshing")} />
|
||||
) : null}
|
||||
{props.refreshError ? (
|
||||
<Alert color="yellow">
|
||||
{t("accounts.refreshError")}
|
||||
<Button variant="subtle" onClick={props.retry}>
|
||||
{t("accounts.retry")}
|
||||
</Button>
|
||||
</Alert>
|
||||
) : null}
|
||||
<Text c="dimmed">{t("events.hint")}</Text>
|
||||
<StateView title={t("events.emptyTitle")} text={t("events.emptyText")} />
|
||||
</Stack>
|
||||
|
||||
@@ -1,24 +1,46 @@
|
||||
import { Stack, Text, Title } from "@mantine/core";
|
||||
import { Alert, Button, Progress, Stack, Text, Title } from "@mantine/core";
|
||||
import type { FC } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
type BrokerageEvent,
|
||||
BrokerageEventCard,
|
||||
} from "@/entities/brokerage-event";
|
||||
import type { EventsRefreshState } from "../AccountEventsPage.vm";
|
||||
|
||||
type AccountEventsPageLoadedProps = { events: BrokerageEvent[] };
|
||||
type AccountEventsPageLoadedProps = EventsRefreshState & {
|
||||
events: BrokerageEvent[];
|
||||
};
|
||||
|
||||
const AccountEventsPageLoaded: FC<AccountEventsPageLoadedProps> = (props) => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Stack maw={960} gap="lg">
|
||||
<Title order={1}>{t("events.title")}</Title>
|
||||
{props.refreshing ? (
|
||||
<Progress value={100} animated aria-label={t("accounts.refreshing")} />
|
||||
) : null}
|
||||
{props.refreshError ? (
|
||||
<Alert color="yellow">
|
||||
{t("accounts.refreshError")}
|
||||
<Button variant="subtle" onClick={props.retry}>
|
||||
{t("accounts.retry")}
|
||||
</Button>
|
||||
</Alert>
|
||||
) : null}
|
||||
<Text c="dimmed">{t("events.hint")}</Text>
|
||||
<Stack gap="sm">
|
||||
{props.events.map((event) => (
|
||||
<BrokerageEventCard
|
||||
event={event}
|
||||
key={`${event.type}-${event.date}-${event.ticker}`}
|
||||
key={JSON.stringify([
|
||||
event.account?.id,
|
||||
event.type,
|
||||
event.date,
|
||||
event.instrumentUid,
|
||||
event.figi,
|
||||
event.ticker,
|
||||
event.logoName,
|
||||
])}
|
||||
/>
|
||||
))}
|
||||
</Stack>
|
||||
|
||||
Reference in New Issue
Block a user