diff --git a/client/src/components/MapView/NavigationMarkers.tsx b/client/src/components/MapView/NavigationMarkers.tsx
index 6d043038ae9..2ae62cab8f2 100644
--- a/client/src/components/MapView/NavigationMarkers.tsx
+++ b/client/src/components/MapView/NavigationMarkers.tsx
@@ -2,6 +2,7 @@ import { TripQueryVariables } from '../../gql/graphql.ts';
import { Marker } from 'react-map-gl';
import markerFlagStart from '../../static/img/marker-flag-start-shadowed.png';
import markerFlagEnd from '../../static/img/marker-flag-end-shadowed.png';
+import { useCoordinateResolver } from './useCoordinateResolver.ts';
export function NavigationMarkers({
setCursor,
@@ -14,13 +15,16 @@ export function NavigationMarkers({
setTripQueryVariables: (variables: TripQueryVariables) => void;
loading: boolean;
}) {
+ const fromCoordinates = useCoordinateResolver(tripQueryVariables.from);
+ const toCoordinates = useCoordinateResolver(tripQueryVariables.to);
+
return (
<>
- {tripQueryVariables.from.coordinates && (
+ {fromCoordinates && (
setCursor('grabbing')}
onDragEnd={(e) => {
setCursor('auto');
@@ -36,11 +40,11 @@ export function NavigationMarkers({
)}
- {tripQueryVariables.to.coordinates && (
+ {toCoordinates && (
setCursor('grabbing')}
onDragEnd={(e) => {
setCursor('auto');
diff --git a/client/src/components/MapView/useCoordinateResolver.ts b/client/src/components/MapView/useCoordinateResolver.ts
new file mode 100644
index 00000000000..e44472df549
--- /dev/null
+++ b/client/src/components/MapView/useCoordinateResolver.ts
@@ -0,0 +1,30 @@
+import { Location } from '../../gql/graphql.ts';
+import { useQuayCoordinateQuery } from '../../hooks/useQuayCoordinateQuery.ts';
+
+interface Coordinates {
+ latitude: number;
+ longitude: number;
+}
+
+export function useCoordinateResolver(location: Location): Coordinates | undefined {
+ const quay = useQuayCoordinateQuery(location);
+
+ if (quay) {
+ const { latitude, longitude } = quay;
+
+ if (latitude && longitude) {
+ return {
+ latitude,
+ longitude,
+ };
+ }
+ }
+
+ if (location.coordinates) {
+ return {
+ ...location.coordinates,
+ };
+ }
+
+ return undefined;
+}
diff --git a/client/src/components/SearchBar/LocationInputField.tsx b/client/src/components/SearchBar/LocationInputField.tsx
index bfa707776f1..70584e0e6d8 100644
--- a/client/src/components/SearchBar/LocationInputField.tsx
+++ b/client/src/components/SearchBar/LocationInputField.tsx
@@ -1,8 +1,37 @@
import { Form } from 'react-bootstrap';
-import { COORDINATE_PRECISION } from './constants.ts';
-import { Location } from '../../gql/graphql.ts';
+import { toString, parseLocation } from '../../util/locationConverter.ts';
+import { Location, TripQueryVariables } from '../../gql/graphql.ts';
+import { useCallback, useEffect, useState } from 'react';
+
+interface Props {
+ id: string;
+ label: string;
+ tripQueryVariables: TripQueryVariables;
+ setTripQueryVariables: (tripQueryVariables: TripQueryVariables) => void;
+ locationFieldKey: 'from' | 'to';
+}
+
+export function LocationInputField({ id, label, tripQueryVariables, setTripQueryVariables, locationFieldKey }: Props) {
+ const [value, setValue] = useState('');
+
+ useEffect(() => {
+ const initialLocation: Location = tripQueryVariables[locationFieldKey];
+
+ setValue(toString(initialLocation) || '');
+ }, [tripQueryVariables, locationFieldKey]);
+
+ const onLocationChange = useCallback(
+ (value: string) => {
+ const newLocation = parseLocation(value) || {};
+
+ setTripQueryVariables({
+ ...tripQueryVariables,
+ [locationFieldKey]: newLocation,
+ });
+ },
+ [tripQueryVariables, setTripQueryVariables, locationFieldKey],
+ );
-export function LocationInputField({ location, id, label }: { location: Location; id: string; label: string }) {
return (
@@ -14,16 +43,13 @@ export function LocationInputField({ location, id, label }: { location: Location
size="sm"
placeholder="[Click in map]"
className="input-medium"
- // Intentionally empty for now, but needed because of
- // https://react.dev/reference/react-dom/components/input#controlling-an-input-with-a-state-variable
- onChange={() => {}}
- value={
- location.coordinates
- ? `${location.coordinates?.latitude.toPrecision(
- COORDINATE_PRECISION,
- )} ${location.coordinates?.longitude.toPrecision(COORDINATE_PRECISION)}`
- : ''
- }
+ onChange={(e) => {
+ setValue(e.target.value);
+ }}
+ onBlur={(event) => {
+ onLocationChange(event.target.value);
+ }}
+ value={value}
/>
);
diff --git a/client/src/components/SearchBar/SearchBar.tsx b/client/src/components/SearchBar/SearchBar.tsx
index 73df12fe103..47781532179 100644
--- a/client/src/components/SearchBar/SearchBar.tsx
+++ b/client/src/components/SearchBar/SearchBar.tsx
@@ -38,9 +38,21 @@ export function SearchBar({ onRoute, tripQueryVariables, setTripQueryVariables,
{showServerInfo && }
-
+
-
+
diff --git a/client/src/hooks/useQuayCoordinateQuery.ts b/client/src/hooks/useQuayCoordinateQuery.ts
new file mode 100644
index 00000000000..6d4655a6838
--- /dev/null
+++ b/client/src/hooks/useQuayCoordinateQuery.ts
@@ -0,0 +1,36 @@
+import { useEffect, useState } from 'react';
+import { request } from 'graphql-request';
+import { Location, QueryType } from '../gql/graphql.ts';
+import { getApiUrl } from '../util/getApiUrl.ts';
+import { graphql } from '../gql';
+
+const query = graphql(`
+ query quayCoordinate($id: String!) {
+ quay(id: $id) {
+ latitude
+ longitude
+ }
+ }
+`);
+
+export const useQuayCoordinateQuery = (location: Location) => {
+ const [data, setData] = useState(null);
+
+ useEffect(() => {
+ const fetchData = async () => {
+ if (location.place) {
+ const variables = { id: location.place };
+ try {
+ setData((await request(getApiUrl(), query, variables)) as QueryType);
+ } catch (e) {
+ console.error('Error at useQuayCoordinateQuery', e);
+ }
+ } else {
+ setData(null);
+ }
+ };
+ fetchData();
+ }, [location]);
+
+ return data?.quay;
+};
diff --git a/client/src/hooks/useTripQuery.ts b/client/src/hooks/useTripQuery.ts
index 515cfae290a..f1f8c859bd0 100644
--- a/client/src/hooks/useTripQuery.ts
+++ b/client/src/hooks/useTripQuery.ts
@@ -1,6 +1,6 @@
import { useCallback, useEffect, useState } from 'react';
import { request } from 'graphql-request';
-import { QueryType, TripQueryVariables } from '../gql/graphql.ts';
+import { Location, QueryType, TripQueryVariables } from '../gql/graphql.ts';
import { getApiUrl } from '../util/getApiUrl.ts';
import { query } from '../static/query/tripQuery.tsx';
@@ -22,10 +22,14 @@ export const useTripQuery: TripQueryHook = (variables) => {
} else {
if (variables) {
setLoading(true);
- if (pageCursor) {
- setData((await request(getApiUrl(), query, { ...variables, pageCursor })) as QueryType);
- } else {
- setData((await request(getApiUrl(), query, variables)) as QueryType);
+ try {
+ if (pageCursor) {
+ setData((await request(getApiUrl(), query, { ...variables, pageCursor })) as QueryType);
+ } else {
+ setData((await request(getApiUrl(), query, variables)) as QueryType);
+ }
+ } catch (e) {
+ console.error('Error at useTripQuery', e);
}
setLoading(false);
} else {
@@ -37,10 +41,14 @@ export const useTripQuery: TripQueryHook = (variables) => {
);
useEffect(() => {
- if (variables?.from.coordinates && variables?.to.coordinates) {
+ if (validLocation(variables?.from) && validLocation(variables?.to)) {
callback();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [variables?.from, variables?.to]);
return [data, loading, callback];
};
+
+function validLocation(location: Location | undefined) {
+ return location && (location.coordinates || location.place);
+}
diff --git a/client/src/util/locationConverter.ts b/client/src/util/locationConverter.ts
new file mode 100644
index 00000000000..952a36a1dc4
--- /dev/null
+++ b/client/src/util/locationConverter.ts
@@ -0,0 +1,37 @@
+import { COORDINATE_PRECISION } from '../components/SearchBar/constants.ts';
+import { Location } from '../gql/graphql.ts';
+
+const DOUBLE_PATTERN = '-{0,1}\\d+(\\.\\d+){0,1}';
+
+const LAT_LON_PATTERN = '(' + DOUBLE_PATTERN + ')(\\s+)(' + DOUBLE_PATTERN + ')';
+
+export function parseLocation(value: string): Location | null {
+ const latLonMatch = value.match(LAT_LON_PATTERN);
+
+ if (latLonMatch) {
+ return {
+ coordinates: {
+ latitude: +latLonMatch[1],
+ longitude: +latLonMatch[4],
+ },
+ };
+ }
+
+ return {
+ place: value,
+ };
+}
+
+export function toString(location: Location): string | null {
+ if (location.coordinates) {
+ return `${location.coordinates?.latitude.toPrecision(
+ COORDINATE_PRECISION,
+ )} ${location.coordinates?.longitude.toPrecision(COORDINATE_PRECISION)}`;
+ }
+
+ if (location.place) {
+ return location.place;
+ }
+
+ return null;
+}