113 lines
2.9 KiB
TypeScript
113 lines
2.9 KiB
TypeScript
/**
|
|
* React hook for schedule search pages.
|
|
*
|
|
* Calls `searchSchedule` on param change, manages loading/error/data state.
|
|
* Supports AbortController-based cancellation (§4.1.12).
|
|
* No SignalR -- schedule data is static.
|
|
*
|
|
* @module
|
|
*/
|
|
|
|
import { useState, useEffect, useCallback, useRef } from "react";
|
|
import { useApiClient } from "@/shared/api/provider.js";
|
|
import { searchSchedule } from "../api.js";
|
|
import type { IScheduleSearchRequest, IFlight } from "../types.js";
|
|
import type { ApiError } from "@/shared/api/errors.js";
|
|
|
|
export interface UseScheduleSearchResult {
|
|
flights: IFlight[];
|
|
loading: boolean;
|
|
error: ApiError | null;
|
|
refresh: () => void;
|
|
/** Cancel the in-flight request (§4.1.12). Resets loading to false. */
|
|
cancel: () => void;
|
|
}
|
|
|
|
/**
|
|
* Hook for schedule search pages. Fetches flights based on search params
|
|
* and provides refresh + cancel capability.
|
|
*/
|
|
export function useScheduleSearch(
|
|
params: IScheduleSearchRequest,
|
|
enabled = true,
|
|
): UseScheduleSearchResult {
|
|
const client = useApiClient();
|
|
const [flights, setFlights] = useState<IFlight[]>([]);
|
|
const [loading, setLoading] = useState(enabled);
|
|
const [error, setError] = useState<ApiError | null>(null);
|
|
const [refreshKey, setRefreshKey] = useState(0);
|
|
|
|
const paramsRef = useRef(params);
|
|
paramsRef.current = params;
|
|
|
|
// AbortController for the current in-flight request (§4.1.12)
|
|
const abortRef = useRef<AbortController | null>(null);
|
|
|
|
const refresh = useCallback(() => {
|
|
setRefreshKey((k) => k + 1);
|
|
}, []);
|
|
|
|
const cancel = useCallback(() => {
|
|
if (abortRef.current) {
|
|
abortRef.current.abort();
|
|
abortRef.current = null;
|
|
}
|
|
setLoading(false);
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
if (!enabled) {
|
|
if (abortRef.current) {
|
|
abortRef.current.abort();
|
|
abortRef.current = null;
|
|
}
|
|
setFlights([]);
|
|
setLoading(false);
|
|
setError(null);
|
|
return;
|
|
}
|
|
|
|
// Abort any previous in-flight request (§4.1.12 — new search aborts in-flight)
|
|
if (abortRef.current) {
|
|
abortRef.current.abort();
|
|
}
|
|
const controller = new AbortController();
|
|
abortRef.current = controller;
|
|
|
|
setLoading(true);
|
|
setError(null);
|
|
|
|
searchSchedule(client, paramsRef.current, controller.signal)
|
|
.then((response) => {
|
|
if (!controller.signal.aborted) {
|
|
setFlights(response);
|
|
setLoading(false);
|
|
}
|
|
})
|
|
.catch((err: ApiError) => {
|
|
// Ignore aborted requests — the user cancelled intentionally
|
|
if (controller.signal.aborted) return;
|
|
setError(err);
|
|
setLoading(false);
|
|
});
|
|
|
|
return () => {
|
|
controller.abort();
|
|
abortRef.current = null;
|
|
};
|
|
}, [
|
|
client,
|
|
params.departure,
|
|
params.arrival,
|
|
params.dateFrom,
|
|
params.dateTo,
|
|
params.timeFrom,
|
|
params.timeTo,
|
|
params.connections,
|
|
enabled,
|
|
refreshKey,
|
|
]);
|
|
|
|
return { flights, loading, error, refresh, cancel };
|
|
}
|