Fix Schedule "Details" button + search-history sync
Schedule:
- ScheduleSearchPage wires handleFlightClick to DayGroupedFlightList so
the "Детали рейса" button in the expanded flight body navigates to
/{lang}/schedule/{carrier}{flightNumber}-{yyyyMMdd} (Angular's
ScheduleNavigationService.toDetailsPage equivalent). Previously the
Details button fired onStatus → no handler → no-op.
Search history:
- useSearchHistory now broadcasts a custom `afl:search-history-changed`
window event on add/clear and listens for it in a useEffect. Fixes
the case where a route-level component (ScheduleSearchPage) adds to
storage while a sibling SearchHistory sidebar had already captured
an empty initial value via useState — the sidebar now re-reads
storage and shows the history without a page reload.
This commit is contained in:
@@ -25,6 +25,7 @@ import "./ScheduleSearchPage.scss";
|
||||
import { JsonLdRenderer } from "@/shared/seo/json-ld.js";
|
||||
import { useScheduleSearch } from "../hooks/useScheduleSearch.js";
|
||||
import { buildScheduleUrl } from "../url.js";
|
||||
import { buildFlightUrlParams } from "../../online-board/url.js";
|
||||
import { buildScheduleFlightListJsonLd } from "../json-ld.js";
|
||||
import type { ScheduleParams } from "../url.js";
|
||||
import type { IScheduleSearchRequest, ISimpleFlight } from "../types.js";
|
||||
@@ -122,6 +123,25 @@ export const ScheduleSearchPage: FC<ScheduleSearchPageProps> = ({ params }) => {
|
||||
const outbound = params.outbound;
|
||||
const inbound = params.type === "roundtrip" ? params.inbound : undefined;
|
||||
|
||||
// Mirrors Angular `ScheduleNavigationService.toDetailsPage` →
|
||||
// `UrlBuilder.getDetailsPageUrl` for direct/multi-leg flights:
|
||||
// /{lang}/schedule/{flightSegment}
|
||||
// where flightSegment is `{carrier}{flightNumber}-{yyyyMMdd}`. The
|
||||
// details route's catch-all parser skips 3-char airport codes, so
|
||||
// we don't need to insert them for the segment to resolve.
|
||||
const handleFlightClick = useCallback(
|
||||
(flight: ISimpleFlight) => {
|
||||
const segment = buildFlightUrlParams({
|
||||
carrier: flight.flightId.carrier,
|
||||
flightNumber: flight.flightId.flightNumber,
|
||||
...(flight.flightId.suffix ? { suffix: flight.flightId.suffix } : {}),
|
||||
date: flight.flightId.date,
|
||||
});
|
||||
void navigate(`/${locale}/schedule/${segment}`);
|
||||
},
|
||||
[locale, navigate],
|
||||
);
|
||||
|
||||
// Round-trip schedules render only one direction at a time, with a
|
||||
// `Туда / Обратно` switcher in the sticky header — matches Angular's
|
||||
// `schedule-direction-switch` (one-of-two button group). Default to
|
||||
@@ -373,6 +393,7 @@ export const ScheduleSearchPage: FC<ScheduleSearchPageProps> = ({ params }) => {
|
||||
<DayGroupedFlightList
|
||||
flights={outboundSimple}
|
||||
loading={outboundLoading}
|
||||
onFlightClick={handleFlightClick}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
@@ -380,6 +401,7 @@ export const ScheduleSearchPage: FC<ScheduleSearchPageProps> = ({ params }) => {
|
||||
<DayGroupedFlightList
|
||||
flights={inboundSimple}
|
||||
loading={inboundLoading}
|
||||
onFlightClick={handleFlightClick}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -9,10 +9,18 @@
|
||||
* @module
|
||||
*/
|
||||
|
||||
import { useState, useCallback } from "react";
|
||||
import { useState, useCallback, useEffect } from "react";
|
||||
import { z } from "zod";
|
||||
import { storage } from "@/shared/storage.js";
|
||||
|
||||
// Custom event fired whenever any hook instance mutates search history.
|
||||
// Other live hook instances listen for it and re-read from storage so
|
||||
// the sidebar on a search page stays in sync when the same route
|
||||
// performs a follow-up search (React Router reuses the page component,
|
||||
// the useState initializer does NOT re-run, and the cross-tab
|
||||
// `storage` event doesn't fire for same-tab writes).
|
||||
const SEARCH_HISTORY_CHANGED_EVENT = "afl:search-history-changed";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Types
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -119,6 +127,21 @@ export function useSearchHistory(lang: string): UseSearchHistoryResult {
|
||||
return (storage.get(key, searchHistorySchema) ?? []) as SearchHistoryItem[];
|
||||
});
|
||||
|
||||
// Re-read storage whenever any instance broadcasts a change. Covers
|
||||
// the case where a search-results page persists the search via one
|
||||
// hook instance and the sidebar on the same mounted page uses a
|
||||
// separate hook instance whose initial useState() has already run.
|
||||
useEffect(() => {
|
||||
if (typeof window === "undefined") return;
|
||||
const handler = (): void => {
|
||||
const fresh =
|
||||
(storage.get(key, searchHistorySchema) ?? []) as SearchHistoryItem[];
|
||||
setItems(fresh);
|
||||
};
|
||||
window.addEventListener(SEARCH_HISTORY_CHANGED_EVENT, handler);
|
||||
return () => window.removeEventListener(SEARCH_HISTORY_CHANGED_EVENT, handler);
|
||||
}, [key]);
|
||||
|
||||
const add = useCallback(
|
||||
(item: SearchHistoryItem) => {
|
||||
setItems((prev) => {
|
||||
@@ -132,6 +155,9 @@ export function useSearchHistory(lang: string): UseSearchHistoryResult {
|
||||
const next = [item, ...filtered].slice(0, MAX_HISTORY_ITEMS);
|
||||
|
||||
storage.set(key, next, searchHistorySchema);
|
||||
if (typeof window !== "undefined") {
|
||||
window.dispatchEvent(new Event(SEARCH_HISTORY_CHANGED_EVENT));
|
||||
}
|
||||
return next;
|
||||
});
|
||||
},
|
||||
@@ -141,6 +167,9 @@ export function useSearchHistory(lang: string): UseSearchHistoryResult {
|
||||
const clear = useCallback(() => {
|
||||
storage.delete(key);
|
||||
setItems([]);
|
||||
if (typeof window !== "undefined") {
|
||||
window.dispatchEvent(new Event(SEARCH_HISTORY_CHANGED_EVENT));
|
||||
}
|
||||
}, [key]);
|
||||
|
||||
return { items, add, clear };
|
||||
|
||||
Reference in New Issue
Block a user