feat(account): add concentration tab with top-5 slices and chart (WealthLedger/main#48) (#127)
Frontend verify / commits (push) Skipped
Frontend verify / security (push) Successful in 10s
Frontend verify / verify (push) Failing after 9m31s

This commit was merged in pull request #127.
This commit is contained in:
2026-11-01 19:28:52 +03:00
parent 7242240b21
commit 70a2c335fd
15 changed files with 1503 additions and 22 deletions
@@ -18,6 +18,7 @@ import { formatMoney } from "@/shared/lib";
import { SourceSyncStatus } from "@/shared/ui";
import { PortfolioStructure } from "@/widgets/portfolio-structure";
import type { Concentration as ConcentrationModel, Slice } from "../../../model/concentration";
import type { AccountPagePositionSection } from "../../../model/positions";
import type {
AccountPageIncomeSummary,
@@ -27,6 +28,7 @@ import type {
import classes from "../AccountPage.module.css";
import { AccountHeading } from "./AccountHeading.view";
import { Concentration } from "./Concentration/Concentration.view";
import { CurrencyCards } from "./CurrencyCards/CurrencyCards.view";
import { IncomeSummary } from "./IncomeSummary.view";
import { PositionSection } from "./PositionSection.view";
@@ -35,19 +37,29 @@ import { RecentOperations } from "./RecentOperations.view";
type AccountPageLoadedProps = {
account?: BrokerageAccount;
accountId?: string;
concentration: ConcentrationModel | null;
currency: null | string;
incomeSummary?: AccountPageIncomeSummary;
onCurrencyChange: (currency: null | string) => void;
onSliceChange: (slice: Slice) => void;
onTabChange: (tab: null | string) => void;
partial?: boolean;
portfolio: Portfolio;
positionSections: AccountPagePositionSection[];
recentEvents: AccountPageRecentEvents;
recentOperations: AccountPageRecentOperations;
slice: Slice;
source?: InvestmentResourceMeta;
tab: string;
unrealizedReturns: Portfolio["totalsByCurrency"];
};
const AccountPageLoaded: FC<AccountPageLoadedProps> = (props) => {
const { t } = useTranslation();
const [filter, setFilter] = useState<string>();
const tab = props.tab;
const onTabChange = props.onTabChange;
const activeSection = props.positionSections.find(
section => section.kind === filter,
@@ -75,7 +87,9 @@ const AccountPageLoaded: FC<AccountPageLoadedProps> = (props) => {
/>
{props.partial
? <Alert role="status">{t("investment.partial")}</Alert>
? tab !== "concentration" && (
<Alert role="status">{t("investment.partial")}</Alert>
)
: <CurrencyCards amounts={props.portfolio.totalsByCurrency} />}
{!props.partial && props.unrealizedReturns.length > 0 && (
@@ -107,14 +121,21 @@ const AccountPageLoaded: FC<AccountPageLoadedProps> = (props) => {
<Tabs
classNames={{ list: classes.tabsList }}
defaultValue="positions"
keepMounted={false}
onChange={onTabChange}
value={tab}
>
<Tabs.List aria-label={t("portfolio.sections")} mb="md">
<Tabs.Tab value="positions">
{t("portfolio.tabs.positions")}
</Tabs.Tab>
{props.accountId !== "all" && (
<Tabs.Tab value="concentration">
{t("portfolio.concentration.title")}
</Tabs.Tab>
)}
<Tabs.Tab value="cash">{t("portfolio.tabs.cash")}</Tabs.Tab>
<Tabs.Tab value="operations">
@@ -143,6 +164,22 @@ const AccountPageLoaded: FC<AccountPageLoadedProps> = (props) => {
</Stack>
</Tabs.Panel>
{props.accountId !== "all" && (
<Tabs.Panel value="concentration">
{props.concentration && (
<Concentration
currency={props.currency}
key={`${props.slice}-${props.currency}`}
model={props.concentration}
onCurrencyChange={props.onCurrencyChange}
onSliceChange={props.onSliceChange}
partial={!!props.partial}
slice={props.slice}
/>
)}
</Tabs.Panel>
)}
<Tabs.Panel value="cash">
<CurrencyCards amounts={props.portfolio.cashBalances} />
</Tabs.Panel>