From 566b89a52fa2fae5bace1975c5cbf7d5f27c512c Mon Sep 17 00:00:00 2001 From: Bendik Sem Kvernevik <32192420+bendiksk@users.noreply.github.com> Date: Wed, 22 Sep 2021 14:36:00 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=9A=B2=20Bike=20station=20migration=20?= =?UTF-8?q?=F0=9F=9A=B2=20:=20Moving=20to=20mobility=20API=20(v2)=20(#470)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Migrate to mobility v2 API for bike stations * Upgraded @entur/sdk package to 4.2.0 * Changed bikeStations hook to use mobilityV2 api * Migrated from type BikeRentalStation to Station.. * Part of the migration to use mobility v2 api to fetch bike rental stations * The new API returns data of the type Station, with different properties * Implemented getTranslation utility function * .. to be used with objects of the TranslatedString type from entur/sdk * enables easier language-change if future translations are added to the API Fixed render loop in useEffect * Fixed missing bike-tile * Refactored useEffect into smaller responsibilities Changed implementation to use custom useBikeRentalStations hook in EditTab migrating to new bike rental stations in BikeSearch * Migrating to fetch from mobility API v2 in BikeSearch * Type migration BikeRentalStation -> Station * Refactor: Renamed exported component in StopPlaceSearch to match filename * added argument removeHiddenStations which may be set to false to enable usability for EditTab where hidden stations is needed in toggle-list Fix: Disabled ability to add duplicate stations * Fixed conditionless adding of bike rental station added through search field. Now checks if ID is already stored in settings.newStations * Refactor: Cleanup useEffects using AbortController * Changed cleanups in useEffects (in EditTab/*) to use AbortController instead of primitive boolean variable * Fixed defaults when missing station name in EditTab/ * Default to empty string '' * Sorting station names defaults to putting stations with missing name last --- package-lock.json | 14 +-- package.json | 2 +- src/components/Map/index.tsx | 16 +-- .../Admin/EditTab/BikePanel/index.tsx | 12 +- .../Admin/EditTab/BikeSearch/index.tsx | 40 +++++-- .../Admin/EditTab/StopPlaceSearch/index.tsx | 4 +- src/containers/Admin/EditTab/index.tsx | 67 +++++------ src/containers/DashboardWrapper/index.tsx | 5 +- src/dashboards/BusStop/MapTile/index.tsx | 5 +- src/dashboards/Chrono/BikeTile/index.tsx | 16 +-- src/dashboards/Chrono/MapTile/index.tsx | 6 +- src/dashboards/Compact/BikeTile/index.tsx | 16 +-- src/dashboards/Compact/MapTile/index.tsx | 6 +- src/logic/useBikeRentalStations.ts | 104 +++++++++++------- src/logic/useWalkInfoBike.ts | 11 +- src/utils.tsx | 13 +++ 16 files changed, 197 insertions(+), 140 deletions(-) diff --git a/package-lock.json b/package-lock.json index b162dcc41..d5899181e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,7 +24,7 @@ "@entur/loader": "^0.4.1", "@entur/menu": "^4.0.0", "@entur/modal": "^1.5.4", - "@entur/sdk": "^4.0.0", + "@entur/sdk": "^4.2.0", "@entur/tab": "^0.4.24", "@entur/table": "^4.3.15", "@entur/tokens": "^3.1.0", @@ -2780,9 +2780,9 @@ } }, "node_modules/@entur/sdk": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@entur/sdk/-/sdk-4.1.1.tgz", - "integrity": "sha512-6U9jB/SN77lJpRzg37sKtNk7vKkMRa7pSnC+nOZ7P1hmuCe8ZSqHgGppIp7nGCdvzMYmG9bEGZONUEzdBCqD+Q==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@entur/sdk/-/sdk-4.2.0.tgz", + "integrity": "sha512-OpuJfUSuPl39A6m7bu407OoPoG9+MSvaO+IqGnsSohn9CBv9wNVqTtJZ8++FsywC0KLuO5UldHyfZwlqcJvrtQ==", "dependencies": { "@babel/polyfill": "^7.12.1", "@turf/bbox": "^6.3.0", @@ -26217,9 +26217,9 @@ } }, "@entur/sdk": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@entur/sdk/-/sdk-4.1.1.tgz", - "integrity": "sha512-6U9jB/SN77lJpRzg37sKtNk7vKkMRa7pSnC+nOZ7P1hmuCe8ZSqHgGppIp7nGCdvzMYmG9bEGZONUEzdBCqD+Q==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@entur/sdk/-/sdk-4.2.0.tgz", + "integrity": "sha512-OpuJfUSuPl39A6m7bu407OoPoG9+MSvaO+IqGnsSohn9CBv9wNVqTtJZ8++FsywC0KLuO5UldHyfZwlqcJvrtQ==", "requires": { "@babel/polyfill": "^7.12.1", "@turf/bbox": "^6.3.0", diff --git a/package.json b/package.json index f3cece078..b63ee0ffb 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ "@entur/loader": "^0.4.1", "@entur/menu": "^4.0.0", "@entur/modal": "^1.5.4", - "@entur/sdk": "^4.0.0", + "@entur/sdk": "^4.2.0", "@entur/tab": "^0.4.24", "@entur/table": "^4.3.15", "@entur/tokens": "^3.1.0", diff --git a/src/components/Map/index.tsx b/src/components/Map/index.tsx index eca378e48..2028280b3 100644 --- a/src/components/Map/index.tsx +++ b/src/components/Map/index.tsx @@ -1,4 +1,3 @@ -import { BikeRentalStation } from '@entur/sdk' import React, { useState, memo, useRef } from 'react' import { InteractiveMap, Marker } from 'react-map-gl' @@ -7,7 +6,7 @@ import useSupercluster from 'use-supercluster' import type { ClusterProperties } from 'supercluster' -import { Vehicle } from '@entur/sdk/lib/mobility/types' +import { Station, Vehicle } from '@entur/sdk/lib/mobility/types' import PositionPin from '../../assets/icons/positionPin' @@ -51,20 +50,17 @@ const Map = ({ }, })) const bikeRentalStationPoints = bikeRentalStations?.map( - (bikeRentalStation: BikeRentalStation) => ({ + (bikeRentalStation: Station) => ({ type: 'Feature' as const, properties: { cluster: false, stationId: bikeRentalStation.id, - bikesAvailable: bikeRentalStation.bikesAvailable, - spacesAvailable: bikeRentalStation.spacesAvailable, + bikesAvailable: bikeRentalStation.numBikesAvailable, + spacesAvailable: bikeRentalStation.numDocksAvailable, }, geometry: { type: 'Point' as const, - coordinates: [ - bikeRentalStation.longitude, - bikeRentalStation.latitude, - ], + coordinates: [bikeRentalStation.lon, bikeRentalStation.lat], }, }), ) @@ -224,7 +220,7 @@ const Map = ({ interface Props { stopPlaces?: StopPlaceWithDepartures[] | null - bikeRentalStations?: BikeRentalStation[] | null + bikeRentalStations?: Station[] | null scooters?: Vehicle[] | null walkTimes?: Array<{ stopId: string; walkTime: number }> | null interactive: boolean diff --git a/src/containers/Admin/EditTab/BikePanel/index.tsx b/src/containers/Admin/EditTab/BikePanel/index.tsx index ff18213e3..130201c5d 100644 --- a/src/containers/Admin/EditTab/BikePanel/index.tsx +++ b/src/containers/Admin/EditTab/BikePanel/index.tsx @@ -1,10 +1,10 @@ import React, { useCallback } from 'react' -import { BikeRentalStation } from '@entur/sdk' +import { Station } from '@entur/sdk/lib/mobility/types' import { Checkbox, Fieldset } from '@entur/form' import { Paragraph } from '@entur/typography' -import { toggleValueInList } from '../../../../utils' +import { getTranslation, toggleValueInList } from '../../../../utils' import { useSettingsContext } from '../../../../settings' import './styles.scss' @@ -59,11 +59,13 @@ function BikePanel(props: Props): JSX.Element { - {name} + + {getTranslation(name) || ''} + ))} @@ -71,7 +73,7 @@ function BikePanel(props: Props): JSX.Element { } interface Props { - stations: BikeRentalStation[] + stations: Station[] } export default BikePanel diff --git a/src/containers/Admin/EditTab/BikeSearch/index.tsx b/src/containers/Admin/EditTab/BikeSearch/index.tsx index 242694b4c..696b8dfd5 100644 --- a/src/containers/Admin/EditTab/BikeSearch/index.tsx +++ b/src/containers/Admin/EditTab/BikeSearch/index.tsx @@ -1,40 +1,56 @@ import React, { useState, useEffect } from 'react' -import { Coordinates, BikeRentalStation } from '@entur/sdk' +import { Coordinates } from '@entur/sdk' import { Dropdown } from '@entur/dropdown' +import { Station } from '@entur/sdk/lib/mobility/types' import service from '../../../../service' import './styles.scss' +import { getTranslation } from '../../../../utils' + +const MAX_SEARCH_RANGE = 100_000 interface Item { value: string label: string } -function mapFeaturesToItems(features: BikeRentalStation[]): Item[] { +function mapFeaturesToItems(features: Station[]): Item[] { return features.map(({ id, name }) => ({ value: id, - label: name, + label: getTranslation(name) || '', })) } const BikePanelSearch = ({ onSelected, position }: Props): JSX.Element => { - const [stations, setStations] = useState([]) + const [stations, setStations] = useState([]) useEffect(() => { - let isMounted = true + const controller = new AbortController() if (position) { - service - .getBikeRentalStationsByPosition(position, 100000) + service.mobility + .getStations( + { + lat: position.latitude, + lon: position.longitude, + range: MAX_SEARCH_RANGE, + }, + { + signal: controller.signal, + }, + ) .then((data) => { - if (isMounted) { - setStations(data) + setStations(data) + }) + .catch((err) => { + if (!controller.signal.aborted) { + throw err } }) } return () => { - isMounted = false + controller.abort() } }, [position]) @@ -45,7 +61,9 @@ const BikePanelSearch = ({ onSelected, position }: Props): JSX.Element => { return mapFeaturesToItems( stations.filter((station) => - station.name.toLowerCase().match(new RegExp(inputValue)), + getTranslation(station.name) + ?.toLowerCase() + .match(new RegExp(inputValue)), ), ) } diff --git a/src/containers/Admin/EditTab/StopPlaceSearch/index.tsx b/src/containers/Admin/EditTab/StopPlaceSearch/index.tsx index 5528401fd..32accc2b7 100644 --- a/src/containers/Admin/EditTab/StopPlaceSearch/index.tsx +++ b/src/containers/Admin/EditTab/StopPlaceSearch/index.tsx @@ -33,7 +33,7 @@ async function getItems(query: string): Promise { return mapFeaturesToItems(featuresData) } -const SelectionPanelSearch = ({ handleAddNewStop }: Props): JSX.Element => { +const StopPlaceSearch = ({ handleAddNewStop }: Props): JSX.Element => { const onItemSelected = (item: Item | null): void => { if (item) { handleAddNewStop(item.value) @@ -59,4 +59,4 @@ interface Props { handleAddNewStop: (stopId: string) => void } -export default SelectionPanelSearch +export default StopPlaceSearch diff --git a/src/containers/Admin/EditTab/index.tsx b/src/containers/Admin/EditTab/index.tsx index 7a9a9cde7..8573770ae 100644 --- a/src/containers/Admin/EditTab/index.tsx +++ b/src/containers/Admin/EditTab/index.tsx @@ -5,7 +5,6 @@ import React, { useCallback, SyntheticEvent, } from 'react' -import { BikeRentalStation } from '@entur/sdk' import { Heading2, Heading3, @@ -17,7 +16,7 @@ import { Switch, TextField } from '@entur/form' import { Tooltip } from '@entur/tooltip' import { WidthProvider, Responsive } from 'react-grid-layout' -import { FormFactor } from '@entur/sdk/lib/mobility/types' +import { FormFactor, Station } from '@entur/sdk/lib/mobility/types' import { useSettingsContext, Mode } from '../../../settings' @@ -26,12 +25,17 @@ import { toggleValueInList, isNotNullOrUndefined, isMobileWeb, + getTranslation, } from '../../../utils' import { DEFAULT_DISTANCE, DEFAULT_ZOOM } from '../../../constants' import { StopPlaceWithLines } from '../../../types' -import { useNearestPlaces, useMobility } from '../../../logic' -import service, { getStopPlacesWithLines } from '../../../service' +import { + useNearestPlaces, + useMobility, + useBikeRentalStations, +} from '../../../logic' +import { getStopPlacesWithLines } from '../../../service' import { saveToLocalStorage, getFromLocalStorage, @@ -95,7 +99,10 @@ const EditTab = (): JSX.Element => { }, [debouncedDistance, setSettings, settings]) const [stopPlaces, setStopPlaces] = useState([]) - const [stations, setStations] = useState([]) + const bikeRentalStations: Station[] | null = useBikeRentalStations(false) + const [sortedBikeRentalStations, setSortedBikeRentalStations] = useState< + Station[] + >([]) const nearestPlaces = useNearestPlaces( settings?.coordinates, @@ -114,13 +121,13 @@ const EditTab = (): JSX.Element => { const scooters = useMobility(FormFactor.SCOOTER) useEffect(() => { - let ignoreResponse = false + const controller = new AbortController() const ids = [...newStops, ...nearestStopPlaceIds] getStopPlacesWithLines( ids.map((id: string) => id.replace(/-\d+$/, '')), ).then((resultingStopPlaces) => { - if (ignoreResponse) return + if (controller.signal.aborted) return setStopPlaces( resultingStopPlaces.map((s, index) => ({ @@ -131,34 +138,31 @@ const EditTab = (): JSX.Element => { }) return (): void => { - ignoreResponse = true + controller.abort() } }, [nearestPlaces, nearestStopPlaceIds, newStops]) useEffect(() => { - let ignoreResponse = false - - const nearestBikeRentalStationIds = nearestPlaces - .filter(({ type }) => type === 'BikeRentalStation') - .map(({ id }) => id) - - const ids = [...newStations, ...nearestBikeRentalStationIds] + const controller = new AbortController() - service.getBikeRentalStations(ids).then((freshStations) => { - if (ignoreResponse) return - - const sortedStations = freshStations + if (bikeRentalStations) { + const sortedStations = bikeRentalStations .filter(isNotNullOrUndefined) - .sort((a: BikeRentalStation, b: BikeRentalStation) => - a.name.localeCompare(b.name, 'no'), - ) - setStations(sortedStations) - }) + .sort((a: Station, b: Station) => { + const aName = getTranslation(a.name) + const bName = getTranslation(b.name) + if (!aName) return 1 + if (!bName) return -1 + return aName.localeCompare(bName, 'no') + }) + if (controller.signal.aborted) return + setSortedBikeRentalStations(sortedStations) + } return (): void => { - ignoreResponse = true + controller.abort() } - }, [nearestPlaces, newStations]) + }, [bikeRentalStations]) const addNewStop = useCallback( (stopId: string) => { @@ -179,6 +183,7 @@ const EditTab = (): JSX.Element => { const addNewStation = useCallback( (stationId: string) => { + if (newStations.includes(stationId)) return setSettings({ newStations: [...newStations, stationId], }) @@ -239,7 +244,7 @@ const EditTab = (): JSX.Element => { x: 1.5, y: 0, w: 1.5, - h: 1.55 + tileHeight(stations.length, 0.24, 0), + h: 1.55 + tileHeight(sortedBikeRentalStations.length, 0.24, 0), }, { i: 'scooterPanel', x: 1.5, y: 3.2, w: 1.5, h: 1.4 }, { i: 'mapPanel', x: 3, y: 5, w: 1.5, h: 3.2 }, @@ -258,7 +263,7 @@ const EditTab = (): JSX.Element => { x: 2, y: 0, w: 1, - h: 1.55 + tileHeight(stations.length, 0.24, 0), + h: 1.55 + tileHeight(sortedBikeRentalStations.length, 0.24, 0), }, { i: 'scooterPanel', x: 2, y: 3, w: 1, h: 1.75 }, { i: 'mapPanel', x: 0, y: 7, w: 2, h: 3 }, @@ -277,7 +282,7 @@ const EditTab = (): JSX.Element => { x: 0, y: 3, w: 1, - h: 1.4 + tileHeight(stations.length, 0.24, 0), + h: 1.4 + tileHeight(sortedBikeRentalStations.length, 0.24, 0), }, { i: 'scooterPanel', x: 0, y: 5, w: 1, h: 1.2 }, { i: 'mapPanel', x: 0, y: 9.5, w: 1, h: 3 }, @@ -296,7 +301,7 @@ const EditTab = (): JSX.Element => { x: 0, y: 3, w: 1, - h: 1.4 + tileHeight(stations.length, 0.265, 0), + h: 1.4 + tileHeight(sortedBikeRentalStations.length, 0.265, 0), }, { i: 'scooterPanel', x: 0, y: 5, w: 1, h: 1.6 }, { i: 'mapPanel', x: 0, y: 9.5, w: 1, h: 3 }, @@ -395,7 +400,7 @@ const EditTab = (): JSX.Element => { position={settings?.coordinates} onSelected={addNewStation} /> - +
diff --git a/src/containers/DashboardWrapper/index.tsx b/src/containers/DashboardWrapper/index.tsx index a8d0ae774..305fd568a 100644 --- a/src/containers/DashboardWrapper/index.tsx +++ b/src/containers/DashboardWrapper/index.tsx @@ -1,8 +1,7 @@ import React from 'react' -import { BikeRentalStation } from '@entur/sdk' import { Loader } from '@entur/loader' -import { Vehicle } from '@entur/sdk/lib/mobility/types' +import { Station, Vehicle } from '@entur/sdk/lib/mobility/types' import { useCounter, isDarkOrDefaultTheme } from '../../utils' import { useSettingsContext } from '../../settings' @@ -80,7 +79,7 @@ function DashboardWrapper(props: Props): JSX.Element { interface Props { stopPlacesWithDepartures?: StopPlaceWithDepartures[] | null - bikeRentalStations?: BikeRentalStation[] | null + bikeRentalStations?: Station[] | null scooters?: Vehicle[] | null className: string children: JSX.Element | JSX.Element[] diff --git a/src/dashboards/BusStop/MapTile/index.tsx b/src/dashboards/BusStop/MapTile/index.tsx index d5197f4e4..6c63b06a7 100644 --- a/src/dashboards/BusStop/MapTile/index.tsx +++ b/src/dashboards/BusStop/MapTile/index.tsx @@ -1,8 +1,7 @@ import React from 'react' import 'mapbox-gl/dist/mapbox-gl.css' -import { BikeRentalStation } from '@entur/sdk' -import { Vehicle } from '@entur/sdk/lib/mobility/types' +import { Station, Vehicle } from '@entur/sdk/lib/mobility/types' import MapView from '../../../components/Map' @@ -21,7 +20,7 @@ function MapTile(data: Props): JSX.Element { interface Props { stopPlaces: StopPlaceWithDepartures[] | null - bikeRentalStations: BikeRentalStation[] | null + bikeRentalStations: Station[] | null scooters: Vehicle[] | null walkTimes: Array<{ stopId: string; walkTime: number }> | null latitude: number diff --git a/src/dashboards/Chrono/BikeTile/index.tsx b/src/dashboards/Chrono/BikeTile/index.tsx index 57403d778..bf2b2688c 100644 --- a/src/dashboards/Chrono/BikeTile/index.tsx +++ b/src/dashboards/Chrono/BikeTile/index.tsx @@ -1,14 +1,14 @@ import React, { useState, useEffect } from 'react' -import { BikeRentalStation } from '@entur/sdk' import { colors } from '@entur/tokens' import { BicycleIcon } from '@entur/icons' +import { Station } from '@entur/sdk/lib/mobility/types' import Tile from '../components/Tile' import './styles.scss' import { useSettingsContext } from '../../../settings' import { IconColorType } from '../../../types' -import { getIconColorType } from '../../../utils' +import { getIconColorType, getTranslation } from '../../../utils' import useWalkInfoBike, { WalkInfoBike } from '../../../logic/useWalkInfoBike' import TileRow from '../components/TileRow' @@ -57,19 +57,19 @@ const BikeTile = ({ stations }: Props): JSX.Element => { ? getWalkInfoBike(walkInfoBike || [], station.id) : undefined } - label={station.name} + label={getTranslation(station.name) || ''} subLabels={[ { time: - station.bikesAvailable === 1 + station.numBikesAvailable === 1 ? '1 sykkel' - : `${station.bikesAvailable} sykler`, + : `${station.numBikesAvailable} sykler`, }, { time: - station.spacesAvailable === 1 + station.numDocksAvailable === 1 ? '1 lås' - : `${station.spacesAvailable} låser`, + : `${station.numBikesAvailable} låser`, }, ]} /> @@ -79,7 +79,7 @@ const BikeTile = ({ stations }: Props): JSX.Element => { } interface Props { - stations: BikeRentalStation[] + stations: Station[] } export default BikeTile diff --git a/src/dashboards/Chrono/MapTile/index.tsx b/src/dashboards/Chrono/MapTile/index.tsx index 51083c5a0..5bab69678 100644 --- a/src/dashboards/Chrono/MapTile/index.tsx +++ b/src/dashboards/Chrono/MapTile/index.tsx @@ -1,9 +1,7 @@ import React from 'react' import 'mapbox-gl/dist/mapbox-gl.css' -import { BikeRentalStation } from '@entur/sdk' - -import { Vehicle } from '@entur/sdk/lib/mobility/types' +import { Station, Vehicle } from '@entur/sdk/lib/mobility/types' import MapView from '../../../components/Map' @@ -21,7 +19,7 @@ function MapTile(data: Props): JSX.Element { interface Props { stopPlaces: StopPlaceWithDepartures[] | null - bikeRentalStations: BikeRentalStation[] | null + bikeRentalStations: Station[] | null scooters: Vehicle[] | null walkTimes: Array<{ stopId: string; walkTime: number }> | null latitude: number diff --git a/src/dashboards/Compact/BikeTile/index.tsx b/src/dashboards/Compact/BikeTile/index.tsx index 6b78d72b8..927216508 100644 --- a/src/dashboards/Compact/BikeTile/index.tsx +++ b/src/dashboards/Compact/BikeTile/index.tsx @@ -1,13 +1,13 @@ import React, { useState, useEffect } from 'react' -import { BikeRentalStation } from '@entur/sdk' import { colors } from '@entur/tokens' import { BicycleIcon } from '@entur/icons' +import { Station } from '@entur/sdk/lib/mobility/types' import Tile from '../components/Tile' import TileRow from '../components/TileRow' import { useSettingsContext } from '../../../settings' import { IconColorType } from '../../../types' -import { getIconColorType } from '../../../utils' +import { getIconColorType, getTranslation } from '../../../utils' import useWalkInfoBike, { WalkInfoBike } from '../../../logic/useWalkInfoBike' function getWalkInfoBike( @@ -54,19 +54,19 @@ const BikeTile = ({ stations }: Props): JSX.Element => { ? getWalkInfoBike(walkInfoBike || [], station.id) : undefined } - label={station.name} + label={getTranslation(station.name) || ''} subLabels={[ { time: - station.bikesAvailable === 1 + station.numBikesAvailable === 1 ? '1 sykkel' - : `${station.bikesAvailable} sykler`, + : `${station.numBikesAvailable} sykler`, }, { time: - station.spacesAvailable === 1 + station.numDocksAvailable === 1 ? '1 lås' - : `${station.spacesAvailable} låser`, + : `${station.numDocksAvailable} låser`, }, ]} /> @@ -76,7 +76,7 @@ const BikeTile = ({ stations }: Props): JSX.Element => { } interface Props { - stations: BikeRentalStation[] + stations: Station[] } export default BikeTile diff --git a/src/dashboards/Compact/MapTile/index.tsx b/src/dashboards/Compact/MapTile/index.tsx index 51083c5a0..5bab69678 100644 --- a/src/dashboards/Compact/MapTile/index.tsx +++ b/src/dashboards/Compact/MapTile/index.tsx @@ -1,9 +1,7 @@ import React from 'react' import 'mapbox-gl/dist/mapbox-gl.css' -import { BikeRentalStation } from '@entur/sdk' - -import { Vehicle } from '@entur/sdk/lib/mobility/types' +import { Station, Vehicle } from '@entur/sdk/lib/mobility/types' import MapView from '../../../components/Map' @@ -21,7 +19,7 @@ function MapTile(data: Props): JSX.Element { interface Props { stopPlaces: StopPlaceWithDepartures[] | null - bikeRentalStations: BikeRentalStation[] | null + bikeRentalStations: Station[] | null scooters: Vehicle[] | null walkTimes: Array<{ stopId: string; walkTime: number }> | null latitude: number diff --git a/src/logic/useBikeRentalStations.ts b/src/logic/useBikeRentalStations.ts index e471b665f..a6b3dbf56 100644 --- a/src/logic/useBikeRentalStations.ts +++ b/src/logic/useBikeRentalStations.ts @@ -1,61 +1,89 @@ -import { useState, useEffect, useMemo } from 'react' -import { isEqual } from 'lodash' -import { BikeRentalStation } from '@entur/sdk' +import { useState, useEffect } from 'react' +import { Coordinates } from '@entur/sdk' +import { Station } from '@entur/sdk/lib/mobility/types' -import { usePrevious, isNotNullOrUndefined } from '../utils' import service from '../service' import { useSettingsContext } from '../settings' -import { REFRESH_INTERVAL } from '../constants' -import useNearestPlaces from './useNearestPlaces' - -async function fetchBikeRentalStations( +async function fetchBikeRentalStationsById( allStationIds: string[], -): Promise { - const allStations = await service.getBikeRentalStations(allStationIds) - return allStations.filter(isNotNullOrUndefined) +): Promise { + const allStations = await service.mobility.getStationsById({ + stationIds: allStationIds, + }) + return allStations +} + +async function fetchBikeRentalStationsNearby( + coordinates: Coordinates, + distance: number, +): Promise { + const allStations = await service.mobility.getStations({ + lat: coordinates.latitude, + lon: coordinates.longitude, + range: distance, + }) + return allStations } -export default function useBikeRentalStations(): BikeRentalStation[] | null { +export default function useBikeRentalStations( + removeHiddenStations = true, +): Station[] | null { const [settings] = useSettingsContext() const [bikeRentalStations, setBikeRentalStations] = useState< - BikeRentalStation[] | null + Station[] | null >(null) - const nearestPlaces = useNearestPlaces( - settings?.coordinates, - settings?.distance, + const [nearbyStations, setNearbyStations] = useState([]) + const [userSelectedStations, setUserSelectedStations] = useState( + [], ) - const { newStations = [], hiddenStations, hiddenModes } = settings || {} - - const nearestBikeRentalStations = useMemo( - () => - nearestPlaces - .filter(({ type }) => type === 'BikeRentalStation') - .map(({ id }) => id), - [nearestPlaces], - ) + const { + coordinates, + distance, + newStations = [], + hiddenStations = [], + hiddenModes, + } = settings || {} - const allStationIds = [...newStations, ...nearestBikeRentalStations] - .filter((id) => !hiddenStations?.includes(id)) - .filter((id, index, ids) => ids.indexOf(id) === index) + const isDisabled = Boolean(hiddenModes?.includes('bysykkel')) - const prevStationIds = usePrevious(allStationIds) + useEffect(() => { + if (!coordinates || !distance || isDisabled) { + return setBikeRentalStations(null) + } + fetchBikeRentalStationsNearby(coordinates, distance).then((stations) => + setNearbyStations(stations || []), + ) + }, [coordinates, distance, isDisabled]) - const isDisabled = Boolean(hiddenModes?.includes('bysykkel')) useEffect(() => { - const isStationsEqual = isEqual(allStationIds, prevStationIds) if (isDisabled) { return setBikeRentalStations(null) } - if (!isStationsEqual) { - fetchBikeRentalStations(allStationIds).then(setBikeRentalStations) + fetchBikeRentalStationsById(newStations).then((stations) => + setUserSelectedStations(stations || []), + ) + }, [newStations, isDisabled]) + + useEffect(() => { + if (isDisabled) { + return setBikeRentalStations(null) } - const intervalId = setInterval(() => { - fetchBikeRentalStations(allStationIds).then(setBikeRentalStations) - }, REFRESH_INTERVAL) - return (): void => clearInterval(intervalId) - }, [allStationIds, isDisabled, prevStationIds]) + const uniqueUserStations = userSelectedStations.filter( + (userStation) => + !nearbyStations.some( + (nearbyStation) => nearbyStation.id === userStation.id, + ), + ) + setBikeRentalStations([...nearbyStations, ...uniqueUserStations]) + }, [nearbyStations, userSelectedStations, isDisabled]) + + if (removeHiddenStations && bikeRentalStations) { + return bikeRentalStations.filter( + (station) => !hiddenStations.includes(station.id), + ) + } return bikeRentalStations } diff --git a/src/logic/useWalkInfoBike.ts b/src/logic/useWalkInfoBike.ts index bc11152df..9f1718a30 100644 --- a/src/logic/useWalkInfoBike.ts +++ b/src/logic/useWalkInfoBike.ts @@ -1,7 +1,8 @@ import { useState, useEffect } from 'react' import { isEqual } from 'lodash' -import { BikeRentalStation, Coordinates, QueryMode } from '@entur/sdk' +import { Coordinates, QueryMode } from '@entur/sdk' +import { Station } from '@entur/sdk/lib/mobility/types' import service from '../service' import { useSettingsContext } from '../settings' @@ -14,7 +15,7 @@ export type WalkInfoBike = { } async function getWalkInfoBike( - rentalStations: BikeRentalStation[], + rentalStations: Station[], from: Coordinates, signal: AbortSignal, ): Promise { @@ -29,8 +30,8 @@ async function getWalkInfoBike( }, to: { coordinates: { - longitude: stopPlace.longitude, - latitude: stopPlace.latitude, + longitude: stopPlace.lon, + latitude: stopPlace.lat, }, }, modes: [QueryMode.FOOT], @@ -59,7 +60,7 @@ async function getWalkInfoBike( } export default function useTravelTime( - rentalStations: BikeRentalStation[] | null, + rentalStations: Station[] | null, ): WalkInfoBike[] | null { const [settings] = useSettingsContext() const [travelTime, setTravelTime] = useState(null) diff --git a/src/utils.tsx b/src/utils.tsx index 579429788..84b1b3bde 100644 --- a/src/utils.tsx +++ b/src/utils.tsx @@ -19,6 +19,7 @@ import { import { colors } from '@entur/tokens' import { Departure, LegMode, TransportMode, TransportSubmode } from '@entur/sdk' +import { TranslatedString, Translation } from '@entur/sdk/lib/mobility/types' import { LineData, TileSubLabel, Theme, IconColorType } from './types' import { useSettingsContext } from './settings' @@ -393,3 +394,15 @@ export const ConditionalWrapper = ({ wrapper, children, }: WrapperProps) => (condition ? wrapper(children) : children) + +export function getTranslation( + translationObject: TranslatedString, + languageId = 'nb', +): string | null { + const translations: Translation[] = translationObject.translation + const match = translations.find( + (currentTranslation) => currentTranslation.language === languageId, + ) + if (!match) return null + return match.value +}