diff --git a/tavla/app/(admin)/utils/fetch.ts b/tavla/app/(admin)/utils/fetch.ts index 9d753fc36..8e6da1712 100644 --- a/tavla/app/(admin)/utils/fetch.ts +++ b/tavla/app/(admin)/utils/fetch.ts @@ -1,6 +1,12 @@ import type { NormalizedDropdownItemType } from '@entur/dropdown' import { uniq } from 'lodash' -import { CLIENT_NAME, COUNTY_ENDPOINT, GEOCODER_ENDPOINT } from 'src/assets/env' +import { + CLIENT_NAME, + COUNTY_ENDPOINT, + GEOCODER_ENDPOINT, + GRAPHQL_ENDPOINTS, +} from 'src/assets/env' +import { StopPlacesHaveDeparturesQuery } from 'src/graphql' import type { LocationDB } from 'src/types/db-types/boards' import { getIcons, type TCategory } from '../tavler/[id]/utils' @@ -68,6 +74,36 @@ export async function fetchCounties(): Promise { }) } +async function fetchStopPlaceIdsWithDepartures( + ids: string[], +): Promise> { + if (ids.length === 0) return new Set() + + const response = await fetch(GRAPHQL_ENDPOINTS['journey-planner'], { + headers: { + 'Content-Type': 'application/json', + 'ET-Client-Name': CLIENT_NAME, + }, + body: JSON.stringify({ + query: StopPlacesHaveDeparturesQuery.toString(), + variables: { ids }, + }), + method: 'POST', + }) + + const json = await response.json() + const stopPlaces = json.data?.stopPlaces ?? [] + + return new Set( + stopPlaces + .filter( + (sp: { id: string; estimatedCalls: unknown[] } | null) => + sp && sp.estimatedCalls.length > 0, + ) + .map((sp: { id: string }) => sp.id), + ) +} + export async function fetchStopPlaces( text: string, countyIds?: string[], @@ -76,7 +112,7 @@ export async function fetchStopPlaces( const searchParams = new URLSearchParams({ lang: 'no', - size: '5', + size: '10', layers: 'venue,address', text, }) @@ -84,27 +120,42 @@ export async function fetchStopPlaces( if (countyIds && countyIds.length > 0) searchParams.append('boundary.county_ids', countyIds.join(',')) - return fetch(`${GEOCODER_ENDPOINT}/autocomplete?${searchParams}`, { - headers: { - 'ET-Client-Name': CLIENT_NAME, + const data: TPartialGeoResponse = await fetch( + `${GEOCODER_ENDPOINT}/autocomplete?${searchParams}`, + { + headers: { + 'ET-Client-Name': CLIENT_NAME, + }, }, - }) - .then((res) => res.json()) - .then((data: TPartialGeoResponse) => { - return data.features.map(({ properties, geometry }) => ({ - value: { - id: properties.id ?? '', - county: properties.county, - category: properties.category, - coordinates: toGeoCoordinate(geometry.coordinates), - layer: properties.layer, - }, - label: properties.label || '', - icons: uniq(getIcons(properties.layer, properties.category)), - county: properties.county, - itemKey: properties.id ?? properties.label ?? '', - })) - }) + ).then((res) => res.json()) + + const items = data.features.map(({ properties, geometry }) => ({ + value: { + id: properties.id ?? '', + county: properties.county, + category: properties.category, + coordinates: toGeoCoordinate(geometry.coordinates), + layer: properties.layer, + }, + label: properties.label || '', + icons: uniq(getIcons(properties.layer, properties.category)), + county: properties.county, + itemKey: properties.id ?? properties.label ?? '', + })) + + const venueIds = items + .filter((item) => item.value.layer === 'venue' && item.value.id) + .map((item) => item.value.id) + + const idsWithDepartures = await fetchStopPlaceIdsWithDepartures(venueIds) + + return items + .filter( + (item) => + item.value.layer !== 'venue' || + idsWithDepartures.has(item.value.id), + ) + .slice(0, 5) } export async function fetchClosestStopPlaces( @@ -112,28 +163,38 @@ export async function fetchClosestStopPlaces( numberOfStopPlaces: number, areaRadiusInKm: number = 1, ): Promise[]> { - return fetch( - `${GEOCODER_ENDPOINT}/reverse?point.lat=${coordinates.lat}&point.lon=${coordinates.lon}&boundary.circle.radius=${areaRadiusInKm}&layers=venue&size=${numberOfStopPlaces}`, + const requestSize = numberOfStopPlaces * 2 + + const data: TPartialGeoResponse = await fetch( + `${GEOCODER_ENDPOINT}/reverse?point.lat=${coordinates.lat}&point.lon=${coordinates.lon}&boundary.circle.radius=${areaRadiusInKm}&layers=venue&size=${requestSize}`, { headers: { 'ET-Client-Name': CLIENT_NAME, }, }, - ) - .then((res) => res.json()) - .then((data: TPartialGeoResponse) => { - return data.features.map(({ properties, geometry }) => ({ - value: { - id: properties.id ?? '', - county: properties.county, - coordinates: toGeoCoordinate(geometry.coordinates), - name: properties.name ?? '', - }, - label: properties.label || '', - icons: uniq(getIcons(properties.layer, properties.category)), - county: properties.county, - })) - }) + ).then((res) => res.json()) + + const items = data.features.map(({ properties, geometry }) => ({ + value: { + id: properties.id ?? '', + county: properties.county, + coordinates: toGeoCoordinate(geometry.coordinates), + name: properties.name ?? '', + }, + label: properties.label || '', + icons: uniq(getIcons(properties.layer, properties.category)), + county: properties.county, + })) + + const venueIds = items + .filter((item) => item.value.id) + .map((item) => item.value.id) + + const idsWithDepartures = await fetchStopPlaceIdsWithDepartures(venueIds) + + return items + .filter((item) => idsWithDepartures.has(item.value.id)) + .slice(0, numberOfStopPlaces) } export async function fetchPoints( diff --git a/tavla/src/graphql/index.ts b/tavla/src/graphql/index.ts index a4e9a5009..754ebf123 100644 --- a/tavla/src/graphql/index.ts +++ b/tavla/src/graphql/index.ts @@ -379,6 +379,19 @@ export const StopPlaceNameQuery = new TypedDocumentString(` Types.TStopPlaceNameQuery, Types.TStopPlaceNameQueryVariables > +export const StopPlacesHaveDeparturesQuery = new TypedDocumentString(` + query StopPlacesHaveDepartures($ids: [String]) { + stopPlaces(ids: $ids) { + id + estimatedCalls(numberOfDepartures: 1, timeRange: 86400) { + expectedDepartureTime + } + } +} + `) as unknown as TypedDocumentString< + Types.TStopPlacesHaveDeparturesQuery, + Types.TStopPlacesHaveDeparturesQueryVariables +> export const WalkDistanceQuery = new TypedDocumentString(` query walkDistance($from: InputCoordinates!, $to: InputCoordinates!) { trip( diff --git a/tavla/src/graphql/queries/stopPlacesDepartures.graphql b/tavla/src/graphql/queries/stopPlacesDepartures.graphql new file mode 100644 index 000000000..6e4c674a6 --- /dev/null +++ b/tavla/src/graphql/queries/stopPlacesDepartures.graphql @@ -0,0 +1,8 @@ +query StopPlacesHaveDepartures($ids: [String]) { + stopPlaces(ids: $ids) { + id + estimatedCalls(numberOfDepartures: 1, timeRange: 86400) { + expectedDepartureTime + } + } +} diff --git a/tavla/src/types/graphql-schema.ts b/tavla/src/types/graphql-schema.ts index 144a91924..16d0aa927 100644 --- a/tavla/src/types/graphql-schema.ts +++ b/tavla/src/types/graphql-schema.ts @@ -2605,6 +2605,25 @@ export type TStopPlaceNameQuery = { stopPlace: { __typename?: 'StopPlace'; name: string; id: string } | null } +export type TStopPlacesHaveDeparturesQueryVariables = Exact<{ + ids?: InputMaybe< + | Array> + | InputMaybe + > +}> + +export type TStopPlacesHaveDeparturesQuery = { + __typename?: 'QueryType' + stopPlaces: Array<{ + __typename?: 'StopPlace' + id: string + estimatedCalls: Array<{ + __typename?: 'EstimatedCall' + expectedDepartureTime: DateTime + }> + } | null> +} + export type TWalkDistanceQueryVariables = Exact<{ from: TInputCoordinates to: TInputCoordinates