diff --git a/src/features/flights-map/components/MapCanvas.test.tsx b/src/features/flights-map/components/MapCanvas.test.tsx index acf3cbe4..cee61dc6 100644 --- a/src/features/flights-map/components/MapCanvas.test.tsx +++ b/src/features/flights-map/components/MapCanvas.test.tsx @@ -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( { 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( { />, ); 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", () => { diff --git a/src/features/flights-map/components/MapCanvas.tsx b/src/features/flights-map/components/MapCanvas.tsx index a8fc3c85..33ae9617 100644 --- a/src/features/flights-map/components/MapCanvas.tsx +++ b/src/features/flights-map/components/MapCanvas.tsx @@ -324,10 +324,11 @@ export const MapCanvas: FC = ({ } 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 = ({ // --- Re-sync visibility + tooltips when toggles change --- useEffect(() => { syncVisibility(); - syncPolylines(); syncTooltips(); }, [domestic, international]); diff --git a/src/features/online-board/components/OnlineBoardSearchPage.tsx b/src/features/online-board/components/OnlineBoardSearchPage.tsx index bc13733b..1fa06a6c 100644 --- a/src/features/online-board/components/OnlineBoardSearchPage.tsx +++ b/src/features/online-board/components/OnlineBoardSearchPage.tsx @@ -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) {