refactor: align frontend ownership boundaries
Close audit gaps across API drift, query policy, UI, and tests.
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
import { Accordion, Alert, Stack, Text } from "@mantine/core";
|
||||
import type { FC } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import type { BrokerageAccount } from "@/entities/brokerage-account";
|
||||
import type { Portfolio } from "@/entities/portfolio";
|
||||
import { formatSnapshotTime } from "@/shared/lib";
|
||||
import classes from "../AccountPage.module.css";
|
||||
import type {
|
||||
AccountPageIncomeSummary,
|
||||
AccountPagePositionSection,
|
||||
AccountPageRecentEvents,
|
||||
AccountPageRecentOperations,
|
||||
} from "../AccountPage.vm";
|
||||
import { AccountHeading } from "./AccountHeading.view";
|
||||
import { CashBalances } from "./CashBalances.view";
|
||||
import { IncomeSummary } from "./IncomeSummary.view";
|
||||
import { PortfolioRefreshState } from "./PortfolioRefreshState.view";
|
||||
import { PortfolioTotals } from "./PortfolioTotals.view";
|
||||
import { PositionSection } from "./PositionSection.view";
|
||||
import { RecentEvents } from "./RecentEvents.view";
|
||||
import { RecentOperations } from "./RecentOperations.view";
|
||||
|
||||
type AccountPageLoadedProps = {
|
||||
account?: BrokerageAccount;
|
||||
portfolio: Portfolio;
|
||||
positionSections: AccountPagePositionSection[];
|
||||
refreshing?: boolean;
|
||||
refreshError?: boolean;
|
||||
retry?: () => void;
|
||||
recentOperations: AccountPageRecentOperations;
|
||||
recentEvents: AccountPageRecentEvents;
|
||||
incomeSummary?: AccountPageIncomeSummary;
|
||||
};
|
||||
|
||||
const AccountPageLoaded: FC<AccountPageLoadedProps> = (props) => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Stack className={classes.page} gap="lg">
|
||||
<AccountHeading account={props.account} />
|
||||
<PortfolioRefreshState
|
||||
refreshing={props.refreshing ?? false}
|
||||
refreshError={props.refreshError ?? false}
|
||||
retry={props.retry}
|
||||
/>
|
||||
{props.portfolio.isStale ? (
|
||||
<Alert color="blue">{t("portfolio.staleWarning")}</Alert>
|
||||
) : null}
|
||||
<Text c="dimmed" size="sm">
|
||||
{t("portfolio.updatedAt", {
|
||||
date: formatSnapshotTime(props.portfolio.lastSuccessfulAt),
|
||||
})}
|
||||
</Text>
|
||||
<PortfolioTotals totals={props.portfolio.totalsByCurrency} />
|
||||
<Accordion
|
||||
multiple
|
||||
defaultValue={[
|
||||
...props.positionSections.map((section) => section.kind),
|
||||
"cash",
|
||||
"recentOperations",
|
||||
"recentEvents",
|
||||
"incomeSummary",
|
||||
]}
|
||||
classNames={{
|
||||
control: classes.accordionControl,
|
||||
label: classes.accordionLabel,
|
||||
chevron: classes.accordionChevron,
|
||||
panel: classes.accordionPanel,
|
||||
}}
|
||||
>
|
||||
{props.positionSections.map((section) => (
|
||||
<Accordion.Item key={section.kind} value={section.kind}>
|
||||
<Accordion.Control>
|
||||
{t(positionSectionTitleKey(section.kind))}
|
||||
</Accordion.Control>
|
||||
<Accordion.Panel>
|
||||
<PositionSection section={section} />
|
||||
</Accordion.Panel>
|
||||
</Accordion.Item>
|
||||
))}
|
||||
<Accordion.Item value="cash">
|
||||
<Accordion.Control>{t("portfolio.cashBalances")}</Accordion.Control>
|
||||
<Accordion.Panel>
|
||||
<CashBalances balances={props.portfolio.cashBalances} />
|
||||
</Accordion.Panel>
|
||||
</Accordion.Item>
|
||||
<Accordion.Item value="recentOperations">
|
||||
<Accordion.Control>{t("ledger.recentTitle")}</Accordion.Control>
|
||||
<Accordion.Panel>
|
||||
<RecentOperations operations={props.recentOperations} />
|
||||
</Accordion.Panel>
|
||||
</Accordion.Item>
|
||||
<Accordion.Item value="recentEvents">
|
||||
<Accordion.Control>{t("events.recentTitle")}</Accordion.Control>
|
||||
<Accordion.Panel>
|
||||
<RecentEvents events={props.recentEvents} />
|
||||
</Accordion.Panel>
|
||||
</Accordion.Item>
|
||||
<Accordion.Item value="incomeSummary">
|
||||
<Accordion.Control>{t("income.summaryTitle")}</Accordion.Control>
|
||||
<Accordion.Panel>
|
||||
{props.incomeSummary ? (
|
||||
<IncomeSummary {...props.incomeSummary} />
|
||||
) : null}
|
||||
</Accordion.Panel>
|
||||
</Accordion.Item>
|
||||
</Accordion>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
function positionSectionTitleKey(kind: AccountPagePositionSection["kind"]) {
|
||||
if (kind === "stocks") return "portfolio.stocks";
|
||||
if (kind === "bonds") return "portfolio.bonds";
|
||||
return "portfolio.otherPositions";
|
||||
}
|
||||
|
||||
export { AccountPageLoaded };
|
||||
Reference in New Issue
Block a user