feat: unify upcoming event cards across pages

This commit is contained in:
2026-09-09 17:58:02 +03:00
parent 3017fc810e
commit 451400b63b
20 changed files with 313 additions and 140 deletions
@@ -1,11 +1,12 @@
import { MantineProvider } from "@mantine/core";
import { render, screen } from "@testing-library/react";
import { fireEvent, render, screen } from "@testing-library/react";
import { MemoryRouter, Route, Routes } from "react-router";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { useBrokerageEvents } from "@/entities/brokerage-event";
import { AccountEventsPage } from "./AccountEventsPage";
vi.mock("@/entities/brokerage-event", () => ({
vi.mock("@/entities/brokerage-event", async (importOriginal) => ({
...(await importOriginal<typeof import("@/entities/brokerage-event")>()),
useBrokerageEvents: vi.fn(),
}));
@@ -81,6 +82,8 @@ describe("AccountEventsPage", () => {
projectedAmount: { currency: "RUB", value: "1250" },
ticker: "OFZ",
type: "coupon",
logoName: "ofz.png",
isin: "RU000A0JX0J2",
},
{
date: "2026-08-25",
@@ -91,6 +94,8 @@ describe("AccountEventsPage", () => {
projectedAmount: null,
ticker: "OFZ",
type: "offer",
logoName: null,
isin: null,
},
],
},
@@ -98,9 +103,26 @@ describe("AccountEventsPage", () => {
isLoading: false,
refetch: vi.fn(),
} as never);
renderPage();
const { container } = renderPage();
expect(screen.getByText("1 250 ₽")).toBeInTheDocument();
expect(screen.getByText("Сумма неизвестна")).toBeInTheDocument();
expect(screen.getByText("Купон · ОФЗ 26238")).toBeVisible();
expect(screen.getByText("Оферта · ОФЗ 26238")).toBeVisible();
expect(screen.getByText("20 авг. 2026 г. · RU000A0JX0J2")).toBeVisible();
expect(screen.getAllByText(/RU000A0JX0J2$/)).toHaveLength(1);
expect(screen.getByText("25 авг. 2026 г.")).toBeVisible();
expect(screen.queryByText(/Дата события/)).not.toBeInTheDocument();
expect(screen.getAllByText("Ожидаемая сумма")).toHaveLength(2);
const logo = container.querySelector("img");
expect(logo).toHaveAttribute(
"src",
"https://invest-brands.cdn-tinkoff.ru/ofzx160.png",
);
expect(screen.getAllByText("O")).toHaveLength(1);
if (!logo) throw new Error("Expected the event logo");
fireEvent.error(logo);
expect(container.querySelector("img")).toBeNull();
expect(screen.getAllByText("O")).toHaveLength(2);
});
it("renders empty and retryable error states", () => {
@@ -1,44 +0,0 @@
import { Card, Group, Stack, Text } from "@mantine/core";
import type { FC } from "react";
import { useTranslation } from "react-i18next";
import type { BrokerageEvent } from "@/entities/brokerage-event";
import { formatMoney } from "@/shared/lib";
type AccountEventRowProps = { event: BrokerageEvent };
const AccountEventRow: FC<AccountEventRowProps> = (props) => {
const { event } = props;
const { t } = useTranslation();
return (
<Card withBorder>
<Group justify="space-between" align="flex-start" wrap="wrap">
<Stack gap={2}>
<Text fw={700}>{event.displayName}</Text>
<Text c="dimmed" size="sm">
{formatCalendarDate(event.date)} ·{" "}
{t(`events.dateKind.${event.dateKind}`)}
</Text>
</Stack>
<Stack gap={2} align="flex-end">
<Text fw={700}>
{event.projectedAmount
? formatMoney(event.projectedAmount)
: t("events.noAmount")}
</Text>
<Text c="dimmed" size="sm">
{t(`events.type.${event.type}`)}
</Text>
</Stack>
</Group>
</Card>
);
};
function formatCalendarDate(date: string) {
return new Intl.DateTimeFormat("ru-RU", {
dateStyle: "medium",
timeZone: "UTC",
}).format(new Date(`${date}T00:00:00Z`));
}
export { AccountEventRow };
@@ -1,8 +1,10 @@
import { Stack, Text, Title } from "@mantine/core";
import type { FC } from "react";
import { useTranslation } from "react-i18next";
import type { BrokerageEvent } from "@/entities/brokerage-event";
import { AccountEventRow } from "./AccountEventRow.view";
import {
type BrokerageEvent,
BrokerageEventCard,
} from "@/entities/brokerage-event";
type AccountEventsPageLoadedProps = { events: BrokerageEvent[] };
@@ -12,12 +14,14 @@ const AccountEventsPageLoaded: FC<AccountEventsPageLoadedProps> = (props) => {
<Stack maw={960} gap="lg">
<Title order={1}>{t("events.title")}</Title>
<Text c="dimmed">{t("events.hint")}</Text>
{props.events.map((event) => (
<AccountEventRow
event={event}
key={`${event.type}-${event.date}-${event.ticker}`}
/>
))}
<Stack gap="sm">
{props.events.map((event) => (
<BrokerageEventCard
event={event}
key={`${event.type}-${event.date}-${event.ticker}`}
/>
))}
</Stack>
</Stack>
);
};