Skip to content

Commit

Permalink
🚲 Bike station migration 🚲 : Moving to mobility API (v2) (#470)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
bendiksk authored Sep 22, 2021
1 parent 1a9ecbd commit 566b89a
Show file tree
Hide file tree
Showing 16 changed files with 197 additions and 140 deletions.
14 changes: 7 additions & 7 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
16 changes: 6 additions & 10 deletions src/components/Map/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { BikeRentalStation } from '@entur/sdk'
import React, { useState, memo, useRef } from 'react'

import { InteractiveMap, Marker } from 'react-map-gl'
Expand All @@ -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'

Expand Down Expand Up @@ -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],
},
}),
)
Expand Down Expand Up @@ -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
Expand Down
12 changes: 7 additions & 5 deletions src/containers/Admin/EditTab/BikePanel/index.tsx
Original file line number Diff line number Diff line change
@@ -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'
Expand Down Expand Up @@ -59,19 +59,21 @@ function BikePanel(props: Props): JSX.Element {
<Checkbox
key={id}
id={id}
name={name}
name={getTranslation(name) || ''}
checked={!hiddenStations.includes(id)}
onChange={onToggleStation}
>
<span className="bike-panel__eds-paragraph">{name}</span>
<span className="bike-panel__eds-paragraph">
{getTranslation(name) || ''}
</span>
</Checkbox>
))}
</Fieldset>
)
}

interface Props {
stations: BikeRentalStation[]
stations: Station[]
}

export default BikePanel
40 changes: 29 additions & 11 deletions src/containers/Admin/EditTab/BikeSearch/index.tsx
Original file line number Diff line number Diff line change
@@ -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<BikeRentalStation[]>([])
const [stations, setStations] = useState<Station[]>([])

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])

Expand All @@ -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)),
),
)
}
Expand Down
4 changes: 2 additions & 2 deletions src/containers/Admin/EditTab/StopPlaceSearch/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ async function getItems(query: string): Promise<Item[]> {
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)
Expand All @@ -59,4 +59,4 @@ interface Props {
handleAddNewStop: (stopId: string) => void
}

export default SelectionPanelSearch
export default StopPlaceSearch
67 changes: 36 additions & 31 deletions src/containers/Admin/EditTab/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import React, {
useCallback,
SyntheticEvent,
} from 'react'
import { BikeRentalStation } from '@entur/sdk'
import {
Heading2,
Heading3,
Expand All @@ -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'

Expand All @@ -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,
Expand Down Expand Up @@ -95,7 +99,10 @@ const EditTab = (): JSX.Element => {
}, [debouncedDistance, setSettings, settings])

const [stopPlaces, setStopPlaces] = useState<StopPlaceWithLines[]>([])
const [stations, setStations] = useState<BikeRentalStation[]>([])
const bikeRentalStations: Station[] | null = useBikeRentalStations(false)
const [sortedBikeRentalStations, setSortedBikeRentalStations] = useState<
Station[]
>([])

const nearestPlaces = useNearestPlaces(
settings?.coordinates,
Expand All @@ -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) => ({
Expand All @@ -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) => {
Expand All @@ -179,6 +183,7 @@ const EditTab = (): JSX.Element => {

const addNewStation = useCallback(
(stationId: string) => {
if (newStations.includes(stationId)) return
setSettings({
newStations: [...newStations, stationId],
})
Expand Down Expand Up @@ -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 },
Expand All @@ -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 },
Expand All @@ -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 },
Expand All @@ -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 },
Expand Down Expand Up @@ -395,7 +400,7 @@ const EditTab = (): JSX.Element => {
position={settings?.coordinates}
onSelected={addNewStation}
/>
<BikePanel stations={stations} />
<BikePanel stations={sortedBikeRentalStations} />
</div>
<div key="scooterPanel" className="edit-tab__tile">
<div className="edit-tab__header">
Expand Down
Loading

0 comments on commit 566b89a

Please sign in to comment.