feat(account): wrap portfolio sections in Mantine Accordion
All portfolio sections (stocks, bonds, other, cash, recent operations, recent events) are now collapsible via Mantine Accordion. Sections expand by default. Accordion control label uses h2 size to match previous Section headings. Panel horizontal padding removed. Smooth hover transition added. Removed unused Section component. Updated lazy-route bundle baseline for account chunk.
This commit is contained in:
@@ -2,7 +2,7 @@
|
|||||||
"src/app/routes/ProtectedLayout.tsx": 3057,
|
"src/app/routes/ProtectedLayout.tsx": 3057,
|
||||||
"src/pages/account-events/index.ts": 4364,
|
"src/pages/account-events/index.ts": 4364,
|
||||||
"src/pages/account-operations/index.ts": 13854,
|
"src/pages/account-operations/index.ts": 13854,
|
||||||
"src/pages/account/index.ts": 25407,
|
"src/pages/account/index.ts": 32466,
|
||||||
"src/pages/accounts/index.ts": 6145,
|
"src/pages/accounts/index.ts": 6145,
|
||||||
"src/pages/login/index.ts": 662,
|
"src/pages/login/index.ts": 662,
|
||||||
"src/pages/not-found/index.ts": 877,
|
"src/pages/not-found/index.ts": 877,
|
||||||
|
|||||||
@@ -90,3 +90,20 @@
|
|||||||
.mono {
|
.mono {
|
||||||
font-family: var(--mantine-font-family-monospace);
|
font-family: var(--mantine-font-family-monospace);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.accordionControl {
|
||||||
|
transition:
|
||||||
|
background-color 150ms ease,
|
||||||
|
color 150ms ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.accordionLabel {
|
||||||
|
font-size: var(--mantine-h2-font-size);
|
||||||
|
font-weight: var(--mantine-h2-font-weight);
|
||||||
|
line-height: var(--mantine-h2-line-height);
|
||||||
|
}
|
||||||
|
|
||||||
|
.accordionPanel {
|
||||||
|
padding-left: 0;
|
||||||
|
padding-right: 0;
|
||||||
|
}
|
||||||
|
|||||||
@@ -154,9 +154,9 @@ describe("AccountPage states", () => {
|
|||||||
"T-Bank временно недоступен. Показан последний успешный снимок.",
|
"T-Bank временно недоступен. Показан последний успешный снимок.",
|
||||||
),
|
),
|
||||||
).toBeInTheDocument();
|
).toBeInTheDocument();
|
||||||
expect(screen.getByRole("heading", { name: "Акции" })).toBeInTheDocument();
|
expect(screen.getByRole("button", { name: "Акции" })).toBeInTheDocument();
|
||||||
expect(
|
expect(
|
||||||
screen.getByRole("heading", { name: "Облигации" }),
|
screen.getByRole("button", { name: "Облигации" }),
|
||||||
).toBeInTheDocument();
|
).toBeInTheDocument();
|
||||||
const positions = screen
|
const positions = screen
|
||||||
.getAllByText(/Вторая|Первая/)
|
.getAllByText(/Вторая|Первая/)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Alert, Button, Stack, Text } from "@mantine/core";
|
import { Accordion, Alert, Button, Stack, Text } from "@mantine/core";
|
||||||
import type { FC } from "react";
|
import type { FC } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Link } from "react-router";
|
import { Link } from "react-router";
|
||||||
@@ -20,7 +20,6 @@ import {
|
|||||||
PositionSection,
|
PositionSection,
|
||||||
RecentEvents,
|
RecentEvents,
|
||||||
RecentOperations,
|
RecentOperations,
|
||||||
Section,
|
|
||||||
} from "./components";
|
} from "./components";
|
||||||
|
|
||||||
type AccountPageErrorProps = {
|
type AccountPageErrorProps = {
|
||||||
@@ -167,18 +166,50 @@ const AccountPageLoaded: FC<AccountPageLoadedProps> = (props) => {
|
|||||||
})}
|
})}
|
||||||
</Text>
|
</Text>
|
||||||
<PortfolioTotals totals={portfolio.totalsByCurrency} />
|
<PortfolioTotals totals={portfolio.totalsByCurrency} />
|
||||||
|
<Accordion
|
||||||
|
multiple
|
||||||
|
defaultValue={[
|
||||||
|
...positionSections.map((s) => s.kind),
|
||||||
|
"cash",
|
||||||
|
"recentOperations",
|
||||||
|
"recentEvents",
|
||||||
|
]}
|
||||||
|
classNames={{
|
||||||
|
control: classes.accordionControl,
|
||||||
|
label: classes.accordionLabel,
|
||||||
|
chevron: classes.accordionChevron,
|
||||||
|
panel: classes.accordionPanel,
|
||||||
|
}}
|
||||||
|
>
|
||||||
{positionSections.map((section) => (
|
{positionSections.map((section) => (
|
||||||
<PositionSection
|
<Accordion.Item key={section.kind} value={section.kind}>
|
||||||
key={section.kind}
|
<Accordion.Control>
|
||||||
title={t(positionSectionTitleKey(section.kind))}
|
{t(positionSectionTitleKey(section.kind))}
|
||||||
section={section}
|
</Accordion.Control>
|
||||||
/>
|
<Accordion.Panel>
|
||||||
|
<PositionSection section={section} />
|
||||||
|
</Accordion.Panel>
|
||||||
|
</Accordion.Item>
|
||||||
))}
|
))}
|
||||||
<Section title={t("portfolio.cashBalances")}>
|
<Accordion.Item value="cash">
|
||||||
|
<Accordion.Control>{t("portfolio.cashBalances")}</Accordion.Control>
|
||||||
|
<Accordion.Panel>
|
||||||
<CashBalances balances={portfolio.cashBalances} />
|
<CashBalances balances={portfolio.cashBalances} />
|
||||||
</Section>
|
</Accordion.Panel>
|
||||||
|
</Accordion.Item>
|
||||||
|
<Accordion.Item value="recentOperations">
|
||||||
|
<Accordion.Control>{t("ledger.recentTitle")}</Accordion.Control>
|
||||||
|
<Accordion.Panel>
|
||||||
<RecentOperations operations={recentOperations} />
|
<RecentOperations operations={recentOperations} />
|
||||||
|
</Accordion.Panel>
|
||||||
|
</Accordion.Item>
|
||||||
|
<Accordion.Item value="recentEvents">
|
||||||
|
<Accordion.Control>{t("events.recentTitle")}</Accordion.Control>
|
||||||
|
<Accordion.Panel>
|
||||||
<RecentEvents events={recentEvents} />
|
<RecentEvents events={recentEvents} />
|
||||||
|
</Accordion.Panel>
|
||||||
|
</Accordion.Item>
|
||||||
|
</Accordion>
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,18 +2,15 @@ import { SimpleGrid } from "@mantine/core";
|
|||||||
import type { FC } from "react";
|
import type { FC } from "react";
|
||||||
import type { AccountPagePositionSection } from "../AccountPage.vm";
|
import type { AccountPagePositionSection } from "../AccountPage.vm";
|
||||||
import { PositionCard } from "./PositionCard.view";
|
import { PositionCard } from "./PositionCard.view";
|
||||||
import { Section } from "./Section.view";
|
|
||||||
|
|
||||||
type PositionSectionProps = {
|
type PositionSectionProps = {
|
||||||
title: string;
|
|
||||||
section: AccountPagePositionSection;
|
section: AccountPagePositionSection;
|
||||||
};
|
};
|
||||||
|
|
||||||
const PositionSection: FC<PositionSectionProps> = (props) => {
|
const PositionSection: FC<PositionSectionProps> = (props) => {
|
||||||
const { title, section } = props;
|
const { section } = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Section title={title}>
|
|
||||||
<SimpleGrid cols={section.wide ? 1 : { base: 1, sm: 2, md: 3 }}>
|
<SimpleGrid cols={section.wide ? 1 : { base: 1, sm: 2, md: 3 }}>
|
||||||
{section.positions.map((position) => (
|
{section.positions.map((position) => (
|
||||||
<PositionCard
|
<PositionCard
|
||||||
@@ -30,7 +27,6 @@ const PositionSection: FC<PositionSectionProps> = (props) => {
|
|||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</SimpleGrid>
|
</SimpleGrid>
|
||||||
</Section>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Alert, Button, Paper, Text } from "@mantine/core";
|
import { Alert, Button, Paper, Stack, Text } from "@mantine/core";
|
||||||
import type { FC } from "react";
|
import type { FC } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Link } from "react-router";
|
import { Link } from "react-router";
|
||||||
@@ -6,7 +6,6 @@ import type { BrokerageEvent } from "@/entities/brokerage-event";
|
|||||||
import { formatMoney } from "@/entities/portfolio";
|
import { formatMoney } from "@/entities/portfolio";
|
||||||
import classes from "../AccountPage.module.css";
|
import classes from "../AccountPage.module.css";
|
||||||
import type { AccountPageRecentEvents } from "../AccountPage.vm";
|
import type { AccountPageRecentEvents } from "../AccountPage.vm";
|
||||||
import { Section } from "./Section.view";
|
|
||||||
|
|
||||||
type RecentEventsProps = {
|
type RecentEventsProps = {
|
||||||
events: AccountPageRecentEvents;
|
events: AccountPageRecentEvents;
|
||||||
@@ -21,7 +20,7 @@ const RecentEvents: FC<RecentEventsProps> = (props) => {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Section title={t("events.recentTitle")}>
|
<Stack gap="sm">
|
||||||
{events.state === "loading" ? (
|
{events.state === "loading" ? (
|
||||||
<Text c="dimmed">{t("events.recentLoading")}</Text>
|
<Text c="dimmed">{t("events.recentLoading")}</Text>
|
||||||
) : null}
|
) : null}
|
||||||
@@ -42,7 +41,7 @@ const RecentEvents: FC<RecentEventsProps> = (props) => {
|
|||||||
<Button component={Link} to={events.to} variant="subtle">
|
<Button component={Link} to={events.to} variant="subtle">
|
||||||
{t("events.open")}
|
{t("events.open")}
|
||||||
</Button>
|
</Button>
|
||||||
</Section>
|
</Stack>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
import { Alert, Button, Paper, Text } from "@mantine/core";
|
import { Alert, Button, Paper, Stack, Text } from "@mantine/core";
|
||||||
import type { FC } from "react";
|
import type { FC } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Link } from "react-router";
|
import { Link } from "react-router";
|
||||||
import { formatMoney } from "@/entities/portfolio";
|
import { formatMoney } from "@/entities/portfolio";
|
||||||
import classes from "../AccountPage.module.css";
|
import classes from "../AccountPage.module.css";
|
||||||
import type { AccountPageRecentOperations } from "../AccountPage.vm";
|
import type { AccountPageRecentOperations } from "../AccountPage.vm";
|
||||||
import { Section } from "./Section.view";
|
|
||||||
|
|
||||||
type RecentOperationsProps = {
|
type RecentOperationsProps = {
|
||||||
operations: AccountPageRecentOperations;
|
operations: AccountPageRecentOperations;
|
||||||
@@ -16,7 +15,7 @@ const RecentOperations: FC<RecentOperationsProps> = (props) => {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Section title={t("ledger.recentTitle")}>
|
<Stack gap="sm">
|
||||||
{operations.state === "loading" ? (
|
{operations.state === "loading" ? (
|
||||||
<Text c="dimmed">{t("ledger.recentLoading")}</Text>
|
<Text c="dimmed">{t("ledger.recentLoading")}</Text>
|
||||||
) : null}
|
) : null}
|
||||||
@@ -41,7 +40,7 @@ const RecentOperations: FC<RecentOperationsProps> = (props) => {
|
|||||||
<Button component={Link} to={operations.to} variant="subtle">
|
<Button component={Link} to={operations.to} variant="subtle">
|
||||||
{t("ledger.open")}
|
{t("ledger.open")}
|
||||||
</Button>
|
</Button>
|
||||||
</Section>
|
</Stack>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
import { Stack, Title } from "@mantine/core";
|
|
||||||
import type { FC, PropsWithChildren } from "react";
|
|
||||||
|
|
||||||
type SectionProps = PropsWithChildren<{
|
|
||||||
title: string;
|
|
||||||
}>;
|
|
||||||
|
|
||||||
const Section: FC<SectionProps> = (props) => {
|
|
||||||
const { title, children } = props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Stack gap="sm">
|
|
||||||
<Title order={2}>{title}</Title>
|
|
||||||
{children}
|
|
||||||
</Stack>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export { Section };
|
|
||||||
@@ -5,4 +5,3 @@ export { PortfolioTotals } from "./PortfolioTotals.view";
|
|||||||
export { PositionSection } from "./PositionSection.view";
|
export { PositionSection } from "./PositionSection.view";
|
||||||
export { RecentEvents } from "./RecentEvents.view";
|
export { RecentEvents } from "./RecentEvents.view";
|
||||||
export { RecentOperations } from "./RecentOperations.view";
|
export { RecentOperations } from "./RecentOperations.view";
|
||||||
export { Section } from "./Section.view";
|
|
||||||
|
|||||||
Reference in New Issue
Block a user