Fix onlineboard empty results and flights-map polyline zoom hazard
Online board: the /board endpoint treats dateFrom/dateTo as a half-open interval, so sending the same date for both yielded zero rows on routes that obviously have flights (e.g. SVO-LED). Mirror Angular's OnlineBoardApiService.getFlightsByRoute and use dateTo = date + 1 day. Flights map: two stacked problems made arcs disappear on zoom. - syncPolylines gated endpoints on map.hasLayer(marker); when syncVisibility removed a zoom-tier layer, its arc went with it. - The zoomend and toggle effects both called syncPolylines, which captured a stale closure from the first render (polylines = []) and wiped the layer. Polyline coords are geographic — Leaflet rescales them on zoom — so the rebuild was never necessary. Arcs now render once per polylines prop change and stay put through zoom and filter toggles.
This commit is contained in:
@@ -452,7 +452,10 @@ describe("MapCanvas — polylines (C.3)", () => {
|
||||
expect(createdPolylines.length).toBe(0);
|
||||
});
|
||||
|
||||
it("skips intermediate cities whose marker is not on the map", () => {
|
||||
it("draws multi-hop polylines through every city in the index", () => {
|
||||
// Route/domestic filtering happens upstream in filterRoutes — by the time
|
||||
// a polyline reaches MapCanvas, every city on its path is expected to be
|
||||
// drawn, independent of zoom-tier visibility.
|
||||
render(
|
||||
<MapCanvas
|
||||
markers={[cm("A"), cm("B", "other"), cm("C")]}
|
||||
@@ -461,11 +464,6 @@ describe("MapCanvas — polylines (C.3)", () => {
|
||||
tileUrl="t"
|
||||
/>,
|
||||
);
|
||||
const map = createdMaps[0]!;
|
||||
// Reset so we count only polylines drawn by the next sync cycle.
|
||||
createdPolylines.length = 0;
|
||||
map.setZoom(6);
|
||||
map.fireZoomend();
|
||||
|
||||
expect(createdPolylines.length).toBe(1);
|
||||
});
|
||||
@@ -487,7 +485,7 @@ describe("MapCanvas — polylines (C.3)", () => {
|
||||
expect(opts?.dashArray).toBe("4 14");
|
||||
});
|
||||
|
||||
it("rebuilds polylines on zoomend", () => {
|
||||
it("does not rebuild polylines on zoomend (they are zoom-independent)", () => {
|
||||
render(
|
||||
<MapCanvas
|
||||
markers={[cm("A"), cm("B")]}
|
||||
@@ -496,14 +494,17 @@ describe("MapCanvas — polylines (C.3)", () => {
|
||||
/>,
|
||||
);
|
||||
const map = createdMaps[0]!;
|
||||
map.setZoom(6);
|
||||
map.fireZoomend();
|
||||
const before = createdPolylines.length;
|
||||
const afterInitialRender = createdPolylines.length;
|
||||
|
||||
map.setZoom(4);
|
||||
map.fireZoomend();
|
||||
map.setZoom(6);
|
||||
map.fireZoomend();
|
||||
|
||||
expect(createdPolylines.length).toBeGreaterThan(before);
|
||||
// Polyline coords are fixed by city lat/lng — Leaflet's SVG pane rescales
|
||||
// them on zoom without us recomputing. Rebuilding from the zoomend
|
||||
// handler also carried a stale-closure hazard.
|
||||
expect(createdPolylines.length).toBe(afterInitialRender);
|
||||
});
|
||||
|
||||
it("silently skips polylines with unknown city codes", () => {
|
||||
|
||||
@@ -324,10 +324,11 @@ export const MapCanvas: FC<MapCanvasProps> = ({
|
||||
}
|
||||
zoomLayersRef.current = zoomLayers;
|
||||
|
||||
// Rerun visibility + tooltip rules on every zoom change.
|
||||
// Rerun visibility + tooltip rules on every zoom change. Polylines are
|
||||
// zoom-independent (Leaflet rescales the SVG automatically) so no rebuild
|
||||
// is needed here — rebuilding from a stale closure in fact wiped them.
|
||||
map.on("zoomend", () => {
|
||||
syncVisibility();
|
||||
syncPolylines();
|
||||
syncTooltips();
|
||||
});
|
||||
|
||||
@@ -441,7 +442,6 @@ export const MapCanvas: FC<MapCanvasProps> = ({
|
||||
// --- Re-sync visibility + tooltips when toggles change ---
|
||||
useEffect(() => {
|
||||
syncVisibility();
|
||||
syncPolylines();
|
||||
syncTooltips();
|
||||
}, [domestic, international]);
|
||||
|
||||
|
||||
@@ -61,6 +61,30 @@ function formatDateForApi(yyyymmdd: string): string {
|
||||
return yyyymmdd.includes("T") ? yyyymmdd : `${yyyymmdd}T00:00:00`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the yyyyMMdd date one day after `yyyymmdd`. The API treats the
|
||||
* board range as a half-open interval: `dateFrom=D, dateTo=D` yields zero
|
||||
* rows. Matching Angular's OnlineBoardApiService.getFlightsByRoute.
|
||||
*/
|
||||
function addOneDayYyyymmdd(yyyymmdd: string): string {
|
||||
const iso = yyyymmdd.includes("T") ? yyyymmdd.split("T")[0]! : yyyymmdd;
|
||||
let year: number, month: number, day: number;
|
||||
if (iso.includes("-")) {
|
||||
const [y, m, d] = iso.split("-");
|
||||
year = Number(y); month = Number(m) - 1; day = Number(d);
|
||||
} else {
|
||||
year = Number(iso.slice(0, 4));
|
||||
month = Number(iso.slice(4, 6)) - 1;
|
||||
day = Number(iso.slice(6, 8));
|
||||
}
|
||||
const dt = new Date(year, month, day);
|
||||
dt.setDate(dt.getDate() + 1);
|
||||
const y = dt.getFullYear().toString();
|
||||
const m = (dt.getMonth() + 1).toString().padStart(2, "0");
|
||||
const d = dt.getDate().toString().padStart(2, "0");
|
||||
return `${y}${m}${d}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert parsed online board URL params into API search params.
|
||||
* The API expects dateFrom/dateTo in yyyy-MM-ddT00:00:00 format.
|
||||
@@ -69,9 +93,10 @@ function toSearchParams(
|
||||
params: OnlineBoardSearchPageProps["params"],
|
||||
): SearchFlightsParams {
|
||||
const apiDate = formatDateForApi(params.date);
|
||||
const apiDateTo = formatDateForApi(addOneDayYyyymmdd(params.date));
|
||||
const base: SearchFlightsParams = {
|
||||
dateFrom: apiDate,
|
||||
dateTo: apiDate,
|
||||
dateTo: apiDateTo,
|
||||
};
|
||||
|
||||
switch (params.type) {
|
||||
|
||||
Reference in New Issue
Block a user