diff --git a/src/features/schedule/components/ScheduleFilter.test.tsx b/src/features/schedule/components/ScheduleFilter.test.tsx
index 81b048c1..a2403a4a 100644
--- a/src/features/schedule/components/ScheduleFilter.test.tsx
+++ b/src/features/schedule/components/ScheduleFilter.test.tsx
@@ -470,9 +470,28 @@ describe("ScheduleFilter – validation per TZ §4.1.9.4", () => {
expect(screen.queryByTestId("schedule-date-clear")).toBeNull();
expect(screen.queryByTestId("schedule-return-date-clear")).toBeNull();
});
+
+ it("clears stale return dates when return flights toggle changes", () => {
+ render(
+ ,
+ );
+
+ expect(screen.queryByTestId("schedule-return-date-clear")).toBeTruthy();
+ fireEvent.click(screen.getByTestId("schedule-return-flights").querySelector("input")!);
+ fireEvent.click(screen.getByTestId("schedule-return-flights").querySelector("input")!);
+ expect(screen.queryByTestId("schedule-return-date-clear")).toBeNull();
+ });
});
-describe("ScheduleFilter – repeated-submit lock", () => {
+describe("ScheduleFilter – submit parity", () => {
beforeEach(() => {
vi.clearAllMocks();
scheduleCalendarMock.days = [];
@@ -482,7 +501,7 @@ describe("ScheduleFilter – repeated-submit lock", () => {
_sliderOnChanges.length = 0;
});
- it("keeps identical resubmits locked but allows an immediate changed search", () => {
+ it("allows immediate repeated submits like Angular", () => {
render(
{
fireEvent.submit(screen.getByTestId("search-form"));
expect(mockNavigate).toHaveBeenCalledTimes(1);
- expect((screen.getByTestId("search-submit") as HTMLButtonElement).disabled).toBe(true);
+ expect((screen.getByTestId("search-submit") as HTMLButtonElement).disabled).toBe(false);
+
+ fireEvent.submit(screen.getByTestId("search-form"));
+ expect(mockNavigate).toHaveBeenCalledTimes(2);
fireEvent.change(screen.getByTestId("schedule-arrival-input"), {
target: { value: "KUF" },
@@ -502,7 +524,7 @@ describe("ScheduleFilter – repeated-submit lock", () => {
expect((screen.getByTestId("search-submit") as HTMLButtonElement).disabled).toBe(false);
fireEvent.submit(screen.getByTestId("search-form"));
- expect(mockNavigate).toHaveBeenCalledTimes(2);
+ expect(mockNavigate).toHaveBeenCalledTimes(3);
expect(mockNavigate).toHaveBeenLastCalledWith(
"/ru-ru/schedule/route/SVO-KUF-20260601-20260607",
);
diff --git a/src/features/schedule/components/ScheduleFilter.tsx b/src/features/schedule/components/ScheduleFilter.tsx
index 1b1ba477..8c0987ca 100644
--- a/src/features/schedule/components/ScheduleFilter.tsx
+++ b/src/features/schedule/components/ScheduleFilter.tsx
@@ -251,47 +251,6 @@ export const ScheduleFilter: FC = ({
[returnAvailableDays, returnCalendarLoaded, scheduleMinDate, scheduleMaxDate],
);
- // §4.1.11 — submit button locked for 30 seconds after each search.
- // The 30-second constant is intentionally hardcoded (not configurable).
- const [submitLock, setSubmitLock] = useState<{ until: number; key: string } | null>(null);
- const [nowTs, setNowTs] = useState(() => Date.now());
- useEffect(() => {
- if (!submitLock || nowTs >= submitLock.until) return;
- const id = setTimeout(() => setNowTs(Date.now()), 1000);
- return () => clearTimeout(id);
- }, [submitLock, nowTs]);
- const formSearchKey = useMemo(
- () =>
- JSON.stringify({
- departure: departure.trim().toUpperCase(),
- arrival: arrival.trim().toUpperCase(),
- dateFrom: dateRange[0] ? dateToYyyymmdd(dateRange[0]) : "",
- dateTo: dateRange[1] ? dateToYyyymmdd(dateRange[1]) : "",
- timeFrom: timeRange[0],
- timeTo: timeRange[1],
- directOnly,
- returnFlights,
- returnDateFrom: returnDateRange[0] ? dateToYyyymmdd(returnDateRange[0]) : "",
- returnDateTo: returnDateRange[1] ? dateToYyyymmdd(returnDateRange[1]) : "",
- returnTimeFrom: returnTimeRange[0],
- returnTimeTo: returnTimeRange[1],
- }),
- [
- departure,
- arrival,
- dateRange,
- timeRange,
- directOnly,
- returnFlights,
- returnDateRange,
- returnTimeRange,
- ],
- );
- const isSubmitLocked = useMemo(
- () => Boolean(submitLock && nowTs < submitLock.until && submitLock.key === formSearchKey),
- [submitLock, nowTs, formSearchKey],
- );
-
// TZ §4.1.9.4 Table 16: when the outbound range moves forward such
// that the already-chosen return starts before the new outbound
// dateTo, blank the return picker and any coupled error so the user
@@ -393,7 +352,6 @@ export const ScheduleFilter: FC = ({
const handleSubmit = useCallback(
(e: FormEvent) => {
e.preventDefault();
- if (isSubmitLocked) return;
const dep = departure.trim().toUpperCase();
const arr = arrival.trim().toUpperCase();
if (!dep || !arr) return;
@@ -516,10 +474,6 @@ export const ScheduleFilter: FC = ({
: {}),
searchExecuted: true,
});
- // Lock submit for 30 seconds (§4.1.11 — hardcoded, not configurable)
- const submittedAt = Date.now();
- setSubmitLock({ until: submittedAt + 30_000, key: formSearchKey });
- setNowTs(submittedAt);
void navigate(`/${locale}/${url}`);
},
[
@@ -533,8 +487,6 @@ export const ScheduleFilter: FC = ({
returnTimeRange,
navigate,
locale,
- isSubmitLocked,
- formSearchKey,
],
);
@@ -691,7 +643,11 @@ export const ScheduleFilter: FC = ({
setReturnFlights(e.target.checked)}
+ onChange={(e) => {
+ setReturnFlights(e.target.checked);
+ setReturnDateRange([null, null]);
+ setReturnBeforeOutboundError(null);
+ }}
/>
{t("SHARED.RETURN_FLIGHT_VIEW")}
@@ -794,8 +750,6 @@ export const ScheduleFilter: FC = ({
type="submit"
className="search-button"
data-testid="search-submit"
- disabled={isSubmitLocked}
- aria-disabled={isSubmitLocked}
>
{t("SHARED.SCHEDULES_VIEW")}
diff --git a/src/features/schedule/components/ScheduleSearchPage.scss b/src/features/schedule/components/ScheduleSearchPage.scss
index e9f3c57d..eeeaee28 100644
--- a/src/features/schedule/components/ScheduleSearchPage.scss
+++ b/src/features/schedule/components/ScheduleSearchPage.scss
@@ -40,11 +40,11 @@
}
}
- // §4.1.11 — block filter/tabs/breadcrumbs while loading
+ // Angular parity: loading blocks only the sticky results controls.
+ // The sidebar filter stays interactive so users can correct criteria
+ // while the previous request is still in-flight.
&[data-searching="true"] {
- .page-layout__left,
- .page-layout__sticky,
- .page-layout__breadcrumbs {
+ .page-layout__sticky {
pointer-events: none;
opacity: 0.6;
}
diff --git a/tests/e2e/schedule-filter-resubmit.spec.ts b/tests/e2e/schedule-filter-resubmit.spec.ts
index 5d0c6809..bdc5479c 100644
--- a/tests/e2e/schedule-filter-resubmit.spec.ts
+++ b/tests/e2e/schedule-filter-resubmit.spec.ts
@@ -15,7 +15,7 @@ test.describe("Schedule results filter", () => {
const submit = page.getByTestId("search-submit");
await expect(submit).toBeEnabled();
await submit.click();
- await expect(submit).toBeDisabled();
+ await expect(submit).toBeEnabled();
await page.getByTestId("swap-cities-button").click();