diff --git a/src/app/routes/AppRoutes/AllAccounts.test.tsx b/src/app/routes/AppRoutes/AllAccounts.test.tsx index 0363249..c2e8452 100644 --- a/src/app/routes/AppRoutes/AllAccounts.test.tsx +++ b/src/app/routes/AppRoutes/AllAccounts.test.tsx @@ -217,6 +217,7 @@ beforeEach(() => { }), ), incomeTotalsByCurrency: [], + window: { from: "2026-09-01", to: "2026-10-01" }, }), ), ), diff --git a/src/pages/account-events/ui/AccountEventsPage/AccountEventsPage.filters.test.tsx b/src/pages/account-events/ui/AccountEventsPage/AccountEventsPage.filters.test.tsx index 71c8821..33cd2d6 100644 --- a/src/pages/account-events/ui/AccountEventsPage/AccountEventsPage.filters.test.tsx +++ b/src/pages/account-events/ui/AccountEventsPage/AccountEventsPage.filters.test.tsx @@ -176,7 +176,11 @@ describe("Upcoming event date filters", () => { ({ request }) => { searches.push(new URL(request.url).search); - return HttpResponse.json(resource({ events: [] })); + return HttpResponse.json(resource({ + calculatedAt: "2026-08-14T00:00:00Z", + events: [], + window: { from: "2026-08-14", to: "2026-09-13" }, + })); }, ), ); @@ -223,7 +227,11 @@ describe("Upcoming event date filters", () => { "*/api/v1/brokerage-accounts/:accountId/events", ({ request }) => new URL(request.url).searchParams.get("from") === defaultFrom - ? HttpResponse.json(resource({ events: [] })) + ? HttpResponse.json(resource({ + calculatedAt: "2026-08-14T00:00:00Z", + events: [], + window: { from: "2026-08-14", to: "2026-09-13" }, + })) : HttpResponse.json({ code: "VALIDATION_ERROR" }, { status: 400 }), ), ); @@ -256,6 +264,7 @@ describe("Upcoming event date filters", () => { calculatedAt: "2099-01-01T00:00:00Z", events: [], incomeTotalsByCurrency: [], + window: { from: "2099-01-01", to: "2099-01-31" }, }), ); }, @@ -264,6 +273,7 @@ describe("Upcoming event date filters", () => { renderEvents(accountId, ["?from=2026-02-01&to=2026-02-28"]); const from = await screen.findByLabelText("Период"); expect(from).toHaveTextContent("01.02.2026 – 28.02.2026"); + expect(screen.getByText(/Прогноз от .* на 01\.01\.2099 – 31\.01\.2099/)).toBeVisible(); expect(screen.getByRole("button", { name: "Применить" })).toBeDisabled(); expect(searches).toEqual(["?from=2026-02-01&to=2026-02-28"]); diff --git a/src/pages/account-events/ui/AccountEventsPage/AccountEventsPage.retry.test.tsx b/src/pages/account-events/ui/AccountEventsPage/AccountEventsPage.retry.test.tsx index db2644f..cb39af1 100644 --- a/src/pages/account-events/ui/AccountEventsPage/AccountEventsPage.retry.test.tsx +++ b/src/pages/account-events/ui/AccountEventsPage/AccountEventsPage.retry.test.tsx @@ -17,6 +17,8 @@ const RETRIES_EXHAUSTED_MS = 6000; const TEST_MS = 10_000; const snapshot = resource({ + accountId: "account-1", + calculatedAt: "2026-08-14T00:00:00Z", events: [ { date: "2026-10-01", @@ -32,6 +34,7 @@ const snapshot = resource({ }, ], incomeTotalsByCurrency: [{ currency: "RUB", value: "123.45" }], + window: { from: "2026-08-14", to: "2026-09-13" }, }); function renderEvents() { @@ -100,7 +103,13 @@ describe("AccountEventsPage read retry", () => { async () => { let isFailing = false; let requests = 0; + let imports = 0; server.use( + http.post("*/api/v1/tbank-sync", () => { + imports += 1; + + return HttpResponse.json({}, { status: 202 }); + }), http.get("*/api/v1/brokerage-accounts/:accountId/events", () => { requests += 1; @@ -111,6 +120,7 @@ describe("AccountEventsPage read retry", () => { ); const { queryClient } = renderEvents(); await screen.findByText("Купон · Synthetic coupon"); + expect(screen.getByText(/Прогноз от .* на 14\.08\.2026 – 13\.09\.2026/)).toBeVisible(); expect(screen.queryByRole("button", { name: "Повторить" })) .not.toBeInTheDocument(); @@ -127,6 +137,7 @@ describe("AccountEventsPage read retry", () => { expect(screen.getByText("Не удалось обновить данные")).toBeInTheDocument(); expect(screen.getByText("Купон · Synthetic coupon")).toBeInTheDocument(); + expect(screen.getByText(/Прогноз от .* на 14\.08\.2026 – 13\.09\.2026/)).toBeVisible(); expect(requests).toBe(4); isFailing = false; @@ -136,6 +147,7 @@ describe("AccountEventsPage read retry", () => { expect(screen.queryByText("Не удалось обновить данные")) .not.toBeInTheDocument()); expect(requests).toBe(5); + expect(imports).toBe(0); }, ); diff --git a/src/pages/account-events/ui/AccountEventsPage/AccountEventsPage.test.tsx b/src/pages/account-events/ui/AccountEventsPage/AccountEventsPage.test.tsx index 7a3db60..3d32729 100644 --- a/src/pages/account-events/ui/AccountEventsPage/AccountEventsPage.test.tsx +++ b/src/pages/account-events/ui/AccountEventsPage/AccountEventsPage.test.tsx @@ -47,6 +47,7 @@ describe("AccountEventsPage", () => { accountId: "account", calculatedAt: "2026-08-14T00:00:00Z", events: [], + window: { from: "2026-08-14", to: "2026-09-13" }, }, isError: false, isLoading: false, @@ -136,6 +137,7 @@ describe("AccountEventsPage", () => { type: "offer", }, ], + window: { from: "2026-08-14", to: "2026-09-13" }, }, isError: false, isLoading: false, @@ -276,6 +278,7 @@ describe("AccountEventsPage", () => { accountId: "all", calculatedAt: "2026-08-14T00:00:00Z", events: [...events], + window: { from: "2026-08-14", to: "2026-09-13" }, }, isError: false, isLoading: false, diff --git a/src/pages/account-events/ui/AccountEventsPage/AccountEventsPage.vm.ts b/src/pages/account-events/ui/AccountEventsPage/AccountEventsPage.vm.ts index 010c865..fe6c4d1 100644 --- a/src/pages/account-events/ui/AccountEventsPage/AccountEventsPage.vm.ts +++ b/src/pages/account-events/ui/AccountEventsPage/AccountEventsPage.vm.ts @@ -18,6 +18,7 @@ import { type AccountEventsPageViewModel = | { allAccounts: boolean; + calculatedAt: string; canApply: boolean; draftPeriod: [null | string, null | string]; events: BrokerageEvent[]; @@ -31,6 +32,7 @@ type AccountEventsPageViewModel source?: InvestmentResourceMeta; state: "loaded"; totalVisible: boolean; + window: { from: string; to: string }; } | { onReset: () => void; @@ -110,6 +112,7 @@ function useAccountEventsPageVm(): AccountEventsPageViewModel { return { allAccounts: accountId === "all", + calculatedAt: events.data.calculatedAt, canApply: (draftFrom !== appliedFrom || draftTo !== appliedTo) && isValidPeriod(draftFrom, draftTo), @@ -133,6 +136,7 @@ function useAccountEventsPageVm(): AccountEventsPageViewModel { source: events.meta, state: "loaded", totalVisible: events.meta?.completeness === "complete", + window: events.data.window, }; } diff --git a/src/pages/account-events/ui/AccountEventsPage/components/AccountEventsPageLoaded.view.tsx b/src/pages/account-events/ui/AccountEventsPage/components/AccountEventsPageLoaded.view.tsx index 514d839..5e79c56 100644 --- a/src/pages/account-events/ui/AccountEventsPage/components/AccountEventsPageLoaded.view.tsx +++ b/src/pages/account-events/ui/AccountEventsPage/components/AccountEventsPageLoaded.view.tsx @@ -17,7 +17,7 @@ import { brokerageEventGroupKey, brokerageEventKey, } from "@/entities/brokerage-event"; -import { groupItems } from "@/shared/lib"; +import { formatDate, formatSnapshotTime, groupItems } from "@/shared/lib"; import { ReadRetry, SourceSyncStatus } from "@/shared/ui"; import type { AccountEventsPageViewModel } from "../AccountEventsPage.vm"; @@ -89,6 +89,14 @@ const AccountEventsPageLoaded: FC = (props) => { {t("events.hint")} + + {t("events.forecastWindow", { + date: formatSnapshotTime(props.calculatedAt), + from: formatDate(props.window.from), + to: formatDate(props.window.to), + })} + +