fix(events): seven-date default period and picker from today (WealthLedger/frontend#63)
This commit is contained in:
@@ -20,7 +20,7 @@ import {
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { configureAuthClient } from "@/shared/api";
|
||||
import { defaultPeriod, formatDate } from "@/shared/lib";
|
||||
import { defaultPeriod, formatDate, moscowDate } from "@/shared/lib";
|
||||
import { createTestQueryClient } from "@/test/query";
|
||||
import { resource } from "@/test/resource";
|
||||
import { server } from "@/test/server";
|
||||
@@ -253,37 +253,58 @@ describe("Upcoming event date filters", () => {
|
||||
expect(from).toHaveTextContent("01.02.2026 – 28.02.2026");
|
||||
expect(screen.getByRole("button", { name: "Применить" })).toBeDisabled();
|
||||
expect(searches).toEqual(["?from=2026-02-01&to=2026-02-28"]);
|
||||
fireEvent.click(from);
|
||||
|
||||
// The calendar opens on the applied month, so reset to the default
|
||||
// range first, then pick today and a future date.
|
||||
fireEvent.click(screen.getByRole("button", { name: "Сбросить" }));
|
||||
await waitFor(() => expect(screen.getByLabelText("URL")).toBeEmptyDOMElement());
|
||||
const picker = await screen.findByLabelText("Период");
|
||||
|
||||
const [today] = defaultPeriod();
|
||||
const [year = 1970, month = 0, day = 1] = today.split("-").map(Number);
|
||||
|
||||
const future = moscowDate(new Date(
|
||||
Date.UTC(year, month - 1, day + 10, 12),
|
||||
));
|
||||
|
||||
// Mantine calendar labels follow dayjs default (English) locale.
|
||||
const calendarLabel = (date: string) => {
|
||||
const monthName = new Intl.DateTimeFormat("en", { month: "long" })
|
||||
.format(new Date(`${date}T12:00:00Z`));
|
||||
|
||||
const pattern = `${Number(date.slice(8))} ${monthName} ${date.slice(0, 4)}`;
|
||||
|
||||
// Dates are constructed above from validated defaultPeriod output.
|
||||
// eslint-disable-next-line security/detect-non-literal-regexp -- see comment above
|
||||
return new RegExp(pattern, "i");
|
||||
};
|
||||
|
||||
fireEvent.click(picker);
|
||||
fireEvent.click(
|
||||
await screen.findByRole("button", { name: /10 February 2026/i }),
|
||||
await screen.findByRole("button", { name: calendarLabel(today) }),
|
||||
);
|
||||
fireEvent.click(
|
||||
await screen.findByRole("button", { name: /28 February 2026/i }),
|
||||
await screen.findByRole("button", { name: calendarLabel(future) }),
|
||||
);
|
||||
expect(searches).toHaveLength(1);
|
||||
// The reset itself fetched the default range; picking dates applies only on submit.
|
||||
expect(searches).toEqual([
|
||||
"?from=2026-02-01&to=2026-02-28",
|
||||
`?from=${defaultFrom}&to=${defaultTo}`,
|
||||
]);
|
||||
fireEvent.click(screen.getByRole("button", { name: "Применить" }));
|
||||
await waitFor(() =>
|
||||
expect(searches.at(-1)).toBe("?from=2026-02-10&to=2026-02-28"),
|
||||
expect(searches.at(-1)).toBe(`?from=${today}&to=${future}`),
|
||||
);
|
||||
expect(await screen.findByLabelText("Период")).toHaveTextContent(
|
||||
"10.02.2026 – 28.02.2026",
|
||||
`${formatDate(today)} – ${formatDate(future)}`,
|
||||
);
|
||||
fireEvent.click(screen.getByRole("button", { name: "History back" }));
|
||||
await waitFor(() =>
|
||||
expect(screen.getByLabelText("Период")).toHaveTextContent(
|
||||
"01.02.2026 – 28.02.2026",
|
||||
`${formatDate(defaultFrom)} – ${formatDate(defaultTo)}`,
|
||||
),
|
||||
);
|
||||
expect(screen.getByLabelText("URL")).toHaveTextContent(
|
||||
"?from=2026-02-01&to=2026-02-28",
|
||||
);
|
||||
fireEvent.click(screen.getByRole("button", { name: "Сбросить" }));
|
||||
await waitFor(() =>
|
||||
expect(searches.at(-1)).toBe(`?from=${defaultFrom}&to=${defaultTo}`),
|
||||
);
|
||||
expect(await screen.findByLabelText("Период")).toHaveTextContent(
|
||||
`${formatDate(defaultFrom)} – ${formatDate(defaultTo)}`,
|
||||
);
|
||||
expect(screen.getByLabelText("URL")).toBeEmptyDOMElement();
|
||||
expect(screen.getByRole("button", { name: "Применить" })).toBeDisabled();
|
||||
expect(
|
||||
screen.getByText("За выбранный период событий не найдено"),
|
||||
|
||||
@@ -21,7 +21,7 @@ type AccountEventsPageViewModel
|
||||
canApply: boolean;
|
||||
draftPeriod: [null | string, null | string];
|
||||
events: BrokerageEvent[];
|
||||
maxDate: string;
|
||||
minDate: string;
|
||||
onApply: () => void;
|
||||
onPeriodChange: (value: [null | string, null | string]) => void;
|
||||
onPeriodPopoverOpen: () => void;
|
||||
@@ -49,7 +49,7 @@ function useAccountEventsPageViewModel(): AccountEventsPageViewModel {
|
||||
|
||||
const [draftFrom, draftTo] = draftPeriod;
|
||||
|
||||
const [maxDate, setMaxDate] = useState(() => moscowDate(new Date()));
|
||||
const [minDate, setMinDate] = useState(() => moscowDate(new Date()));
|
||||
|
||||
const scope = JSON.stringify([accountId, appliedFrom, appliedTo]);
|
||||
const [previousScope, setPreviousScope] = useState(scope);
|
||||
@@ -105,7 +105,7 @@ function useAccountEventsPageViewModel(): AccountEventsPageViewModel {
|
||||
&& isValidPeriod(draftFrom, draftTo),
|
||||
draftPeriod,
|
||||
events: events.data.events,
|
||||
maxDate,
|
||||
minDate,
|
||||
onApply: () => {
|
||||
if (!isValidPeriod(draftFrom, draftTo)) return;
|
||||
setSearchParameters({
|
||||
@@ -116,7 +116,7 @@ function useAccountEventsPageViewModel(): AccountEventsPageViewModel {
|
||||
onPeriodChange: value => setDraftPeriod(
|
||||
value[0] === null && value[1] === null ? defaultPeriod() : value,
|
||||
),
|
||||
onPeriodPopoverOpen: () => setMaxDate(moscowDate(new Date())),
|
||||
onPeriodPopoverOpen: () => setMinDate(moscowDate(new Date())),
|
||||
onReset,
|
||||
partial: events.meta?.completeness === "incomplete",
|
||||
source: events.meta,
|
||||
|
||||
+1
-1
@@ -72,7 +72,7 @@ const AccountEventsPageLoaded: FC<AccountEventsPageLoadedProps> = (props) => {
|
||||
clearable
|
||||
clearButtonProps={{ "aria-label": t("ledger.clearPeriod") }}
|
||||
label={t("ledger.period")}
|
||||
maxDate={props.maxDate}
|
||||
minDate={props.minDate}
|
||||
onChange={props.onPeriodChange}
|
||||
popoverProps={{
|
||||
middlewares: { shift: { crossAxis: true } },
|
||||
|
||||
Reference in New Issue
Block a user