feat: group all-account rows by instrument and day (WealthLedger/main#47) (#126)
Frontend verify / commits (push) Skipped
Frontend verify / security (push) Successful in 9s
Frontend verify / verify (push) Failing after 6m1s

This commit was merged in pull request #126.
This commit is contained in:
2026-10-30 07:28:19 +03:00
parent 6792f5b70a
commit 71126c91f0
40 changed files with 1564 additions and 102 deletions
@@ -237,4 +237,79 @@ describe("AccountEventsPage", () => {
screen.queryByText("За выбранный период событий не найдено"),
).not.toBeInTheDocument();
});
it("groups same-day instrument events only in all-account mode", () => {
const events = [
{
account: { id: "broker", name: "Брокерский" },
date: "2026-08-20",
dateKind: "event",
displayName: "ОФЗ 26238",
figi: "FIGI",
instrumentUid: "UID",
isin: "RU000A0JX0J2",
logoName: null,
projectedAmount: { currency: "RUB", value: "200" },
ticker: "OFZ",
type: "coupon",
},
{
account: { id: "iis", name: "ИИС" },
date: "2026-08-20",
dateKind: "event",
displayName: "ОФЗ 26238",
figi: "FIGI",
instrumentUid: "UID",
isin: "RU000A0JX0J2",
logoName: null,
projectedAmount: { currency: "RUB", value: "100" },
ticker: "OFZ",
type: "coupon",
},
] as const;
query.mockReturnValue({
data: {
accountId: "all",
calculatedAt: "2026-08-14T00:00:00Z",
events: [...events],
},
isError: false,
isLoading: false,
meta: {
availability: "available",
completeness: "complete",
lastAttempt: {
finishedAt: null,
problems: [],
startedAt: null,
state: "none",
},
},
refetch: vi.fn(),
} as never);
render(
<MantineProvider>
<MemoryRouter initialEntries={["/accounts/all/events"]}>
<Routes>
<Route
element={<AccountEventsPage />}
path="/accounts/:accountId/events"
/>
</Routes>
</MemoryRouter>
</MantineProvider>,
);
expect(screen.getByText("Купон · ОФЗ 26238")).toBeInTheDocument();
expect(screen.getByText("300 ₽")).toBeInTheDocument();
const accounts = within(screen.getByRole("list", {
name: /Купон · ОФЗ 26238, 20\.08\.2026, 300 ₽/,
}));
expect(accounts.getByText("Брокерский")).toBeInTheDocument();
expect(accounts.getByText("ИИС")).toBeInTheDocument();
});
});
@@ -18,6 +18,7 @@ import {
type AccountEventsPageViewModel
= | {
allAccounts: boolean;
canApply: boolean;
draftPeriod: [null | string, null | string];
events: BrokerageEvent[];
@@ -29,6 +30,7 @@ type AccountEventsPageViewModel
partial?: boolean;
source?: InvestmentResourceMeta;
state: "loaded";
totalVisible: boolean;
}
| { onReset: () => void; state: "error" }
| { state: "loading" };
@@ -100,6 +102,7 @@ function useAccountEventsPageViewModel(): AccountEventsPageViewModel {
: { state: "loading" };
return {
allAccounts: accountId === "all",
canApply:
(draftFrom !== appliedFrom || draftTo !== appliedTo)
&& isValidPeriod(draftFrom, draftTo),
@@ -121,6 +124,7 @@ function useAccountEventsPageViewModel(): AccountEventsPageViewModel {
partial: events.meta?.completeness === "incomplete",
source: events.meta,
state: "loaded",
totalVisible: events.meta?.completeness === "complete",
};
}
@@ -13,8 +13,11 @@ import { useTranslation } from "react-i18next";
import {
BrokerageEventCard,
BrokerageEventGroup,
brokerageEventGroupKey,
brokerageEventKey,
} from "@/entities/brokerage-event";
import { groupItems } from "@/shared/lib";
import { SourceSyncStatus } from "@/shared/ui";
import type { AccountEventsPageViewModel } from "../AccountEventsPage.vm";
@@ -30,14 +33,32 @@ const AccountEventsPageLoadedEvents: FC<AccountEventsPageLoadedProps> = (
props,
) => {
if (props.events.length > 0) {
const entries = groupItems(
props.events,
props.allAccounts ? brokerageEventGroupKey : () => null,
);
return (
<Stack gap="sm">
{props.events.map(event => (
<BrokerageEventCard
event={event}
key={brokerageEventKey(event)}
/>
))}
<Stack
component="ul"
gap="sm"
m={0}
p={0}
style={{ listStyle: "none" }}
>
{entries.map(entry => entry.type === "group"
? (
<BrokerageEventGroup
events={entry.items}
key={entry.key}
totalVisible={props.totalVisible}
/>
)
: (
<li key={brokerageEventKey(entry.item)}>
<BrokerageEventCard event={entry.item} />
</li>
))}
</Stack>
);
}