From bd123ec5d1d19a44c348aecd8b0dfae5f683f4dd Mon Sep 17 00:00:00 2001 From: GCyganek Date: Wed, 15 Jul 2026 11:05:00 +0200 Subject: [PATCH 1/6] Init add alternate route for map-based mileage expenses --- src/components/ConfirmedRoute.tsx | 3 +- .../DistanceRequest/DistanceRequestFooter.tsx | 30 ++++++- src/components/MapView/Direction.tsx | 38 +++++--- src/components/MapView/Directions.tsx | 88 +++++++++++++++++++ src/components/MapView/DistanceSymbol.tsx | 65 ++++++++++++++ src/components/MapView/MapView.tsx | 74 +++++----------- src/components/MapView/MapViewTypes.ts | 60 ++++++++++++- .../sections/selectors.ts | 4 +- src/libs/DebugUtils.ts | 2 + .../TransactionUtils/getDistanceInMeters.ts | 6 +- src/libs/actions/IOU/SplitExpenseItems.ts | 12 ++- src/libs/actions/Transaction.ts | 45 ++++++---- .../step/IOURequestStepConfirmation.tsx | 3 + ...andleMoneyRequestStepDistanceNavigation.ts | 8 +- .../step/confirmation/useExpenseSubmission.ts | 33 ++++++- src/styles/index.ts | 25 ++++++ src/types/onyx/Transaction.ts | 3 + 17 files changed, 397 insertions(+), 102 deletions(-) create mode 100644 src/components/MapView/Directions.tsx create mode 100644 src/components/MapView/DistanceSymbol.tsx diff --git a/src/components/ConfirmedRoute.tsx b/src/components/ConfirmedRoute.tsx index 40bef760b3df..e6816261de1a 100644 --- a/src/components/ConfirmedRoute.tsx +++ b/src/components/ConfirmedRoute.tsx @@ -47,7 +47,8 @@ type ConfirmedRouteProps = { function ConfirmedRoute({transaction, isSmallerIcon, shouldHaveBorderRadius = true, requireRouteToDisplayMap = false, interactive, shouldDisplayCompass = true}: ConfirmedRouteProps) { const {isOffline} = useNetwork(); - const {route0: route} = transaction?.routes ?? {}; + const selectedRouteKey = transaction?.comment?.selectedRouteKey ?? 'route0'; + const route = transaction?.routes?.[selectedRouteKey] ?? transaction?.routes?.route0; const waypoints = transaction?.comment?.waypoints ?? {}; const coordinates = route?.geometry?.coordinates ?? []; const styles = useThemeStyles(); diff --git a/src/components/DistanceRequest/DistanceRequestFooter.tsx b/src/components/DistanceRequest/DistanceRequestFooter.tsx index 0e42539535fa..2325bb1318b0 100644 --- a/src/components/DistanceRequest/DistanceRequestFooter.tsx +++ b/src/components/DistanceRequest/DistanceRequestFooter.tsx @@ -10,6 +10,7 @@ import useOnyx from '@hooks/useOnyx'; import usePolicy from '@hooks/usePolicy'; import useThemeStyles from '@hooks/useThemeStyles'; +import {setSelectedRoute} from '@libs/actions/Transaction'; import DistanceRequestUtils from '@libs/DistanceRequestUtils'; import {getDistanceInMeters, getWaypointIndex, isCustomUnitRateIDForP2P} from '@libs/TransactionUtils'; @@ -18,6 +19,7 @@ import ONYXKEYS from '@src/ONYXKEYS'; import type {Policy} from '@src/types/onyx'; import type {WaypointCollection} from '@src/types/onyx/Transaction'; import type Transaction from '@src/types/onyx/Transaction'; +import type TransactionState from '@src/types/utils/TransactionStateType'; import type {ReactNode} from 'react'; import type {StyleProp, ViewStyle} from 'react-native'; @@ -43,9 +45,19 @@ type DistanceRequestFooterProps = { /** Optional style for the map container */ mapContainerStyle?: StyleProp; + + /** The state of the transaction (draft, current, etc.) used to persist route selection to the correct Onyx key */ + transactionState?: TransactionState; }; -function DistanceRequestFooter({waypoints, transaction, navigateToWaypointEditPage, policy, mapContainerStyle}: DistanceRequestFooterProps) { +function DistanceRequestFooter({ + waypoints, + transaction, + navigateToWaypointEditPage, + policy, + mapContainerStyle, + transactionState = CONST.TRANSACTION.STATE.DRAFT, +}: DistanceRequestFooterProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); const expensifyIcons = useMemoizedLazyExpensifyIcons(['Plus']); @@ -63,6 +75,10 @@ function DistanceRequestFooter({waypoints, transaction, navigateToWaypointEditPa const policyCurrency = (policy ?? activePolicy ?? personalPolicy)?.outputCurrency ?? CONST.CURRENCY.USD; const mileageRate = isCustomUnitRateIDForP2P(transaction) ? DistanceRequestUtils.getRateForP2P(policyCurrency, transaction) : defaultMileageRate; const {unit} = mileageRate ?? {}; + const isAlternativeDirectionSelected = transaction?.comment?.selectedRouteKey === 'route1'; + const handleRouteSelection = (isAlternative: boolean) => { + setSelectedRoute(transaction?.transactionID ?? CONST.IOU.OPTIMISTIC_TRANSACTION_ID, isAlternative ? 'route1' : 'route0', transactionState); + }; const waypointMarkers: WayPoint[] = []; for (const [key, waypoint] of Object.entries(waypoints ?? {})) { @@ -109,11 +125,21 @@ function DistanceRequestFooter({waypoints, transaction, navigateToWaypointEditPa location: waypointMarkers?.at(0)?.coordinate ?? CONST.MAPBOX.DEFAULT_COORDINATE, }} directionCoordinates={(transaction?.routes?.route0?.geometry?.coordinates as Array<[number, number]>) ?? []} + alternativeDirection={ + transaction?.routes?.route1?.geometry?.coordinates && transaction?.routes?.route1?.distance + ? { + coordinates: (transaction?.routes?.route1?.geometry?.coordinates as Array<[number, number]>) ?? [], + distanceInMeters: transaction?.routes?.route1?.distance, + isSelected: isAlternativeDirectionSelected, + } + : undefined + } + setIsAlternativeDirectionSelected={handleRouteSelection} style={[styles.mapView, styles.mapEditView]} waypoints={waypointMarkers} styleURL={CONST.MAPBOX.STYLE_URL} overlayStyle={styles.mapEditView} - distanceInMeters={getDistanceInMeters(transaction, undefined)} + distanceInMeters={transaction?.routes?.route0?.distance ?? getDistanceInMeters(transaction, undefined)} unit={unit} /> diff --git a/src/components/MapView/Direction.tsx b/src/components/MapView/Direction.tsx index 37b5867b6b60..4c4d0597cab7 100644 --- a/src/components/MapView/Direction.tsx +++ b/src/components/MapView/Direction.tsx @@ -8,7 +8,15 @@ import type {DirectionProps} from './MapViewTypes'; import utils from './utils'; -function Direction({coordinates, belowLayerID}: DirectionProps) { +function getId(idSuffix: string, idPrefix?: string) { + if (!idPrefix) { + return idSuffix; + } + + return `${idPrefix}-${idSuffix}`; +} + +function Direction({coordinates, belowLayerID, isSelected = true, id, onPress}: DirectionProps) { const styles = useThemeStyles(); // If not a single segment route, we need to handle route split into multiple separate segments @@ -24,8 +32,8 @@ function Direction({coordinates, belowLayerID}: DirectionProps) { ))} @@ -57,7 +66,7 @@ function Direction({coordinates, belowLayerID}: DirectionProps) { return ( ); diff --git a/src/components/MapView/Directions.tsx b/src/components/MapView/Directions.tsx new file mode 100644 index 000000000000..7b3c5a27c7ee --- /dev/null +++ b/src/components/MapView/Directions.tsx @@ -0,0 +1,88 @@ +import type {DirectionsProps} from './MapViewTypes'; + +import Direction from './Direction'; +import DistanceSymbol from './DistanceSymbol'; +import utils from './utils'; + +type MainDirectionProps = Pick & { + isAlternativeDirectionSelected: boolean; +}; + +function MainDirection({directionCoordinates, setIsAlternativeDirectionSelected, distanceInMeters, isAlternativeDirectionSelected, unit, waypoints}: MainDirectionProps) { + if (!directionCoordinates) { + return null; + } + + return ( + <> + setIsAlternativeDirectionSelected?.(false)} + /> + + + ); +} + +type AlternativeDirectionProps = Pick; + +function AlternativeDirection({alternativeDirection, setIsAlternativeDirectionSelected, unit, waypoints}: AlternativeDirectionProps) { + const alternativeDirectionCoordinates = alternativeDirection?.coordinates; + + if (!alternativeDirection || !alternativeDirectionCoordinates?.length) { + return null; + } + + return ( + <> + setIsAlternativeDirectionSelected?.(true)} + /> + + + ); +} + +function Directions({directionCoordinates, alternativeDirection, setIsAlternativeDirectionSelected, distanceInMeters, unit, waypoints}: DirectionsProps) { + if (!directionCoordinates) { + return null; + } + + return ( + <> + + + + ); +} + +export default Directions; diff --git a/src/components/MapView/DistanceSymbol.tsx b/src/components/MapView/DistanceSymbol.tsx new file mode 100644 index 000000000000..36bc8440411a --- /dev/null +++ b/src/components/MapView/DistanceSymbol.tsx @@ -0,0 +1,65 @@ +import Text from '@components/Text'; + +import useThemeStyles from '@hooks/useThemeStyles'; + +import DistanceRequestUtils from '@libs/DistanceRequestUtils'; + +import CONST from '@src/CONST'; + +import {MarkerView} from '@rnmapbox/maps'; +import {View} from 'react-native'; + +import type {DistanceSymbolProps} from './MapViewTypes'; + +import ToggleDistanceUnitButton from './ToggleDistanceUnitButton'; +import useDistanceUnit from './useDistanceUnit'; +import utils from './utils'; + +function DistanceSymbol({distanceInMeters, unit, directionCoordinates, waypoints, isSelected = true}: DistanceSymbolProps) { + const styles = useThemeStyles(); + const {distanceUnit, toggleDistanceUnit} = useDistanceUnit(unit); + + const distanceLabelText = DistanceRequestUtils.getDistanceForDisplayLabel(distanceInMeters ?? 0, distanceUnit ?? CONST.CUSTOM_UNITS.DISTANCE_UNIT_KILOMETERS); + + const getDistanceSymbolCoordinate = () => { + if (!directionCoordinates?.length || !waypoints?.length) { + return; + } + const {northEast, southWest} = utils.getBounds( + waypoints.map((waypoint) => waypoint.coordinate), + directionCoordinates, + ); + const boundsCenter = utils.getBoundsCenter({northEast, southWest}); + + return utils.findClosestCoordinateOnLineFromCenter(boundsCenter, directionCoordinates); + }; + + const distanceSymbolCoordinate = getDistanceSymbolCoordinate(); + + if (!distanceInMeters || !distanceUnit || !distanceSymbolCoordinate) { + return null; + } + + return ( + + + + + {distanceLabelText} + + + + + ); +} + +export default DistanceSymbol; diff --git a/src/components/MapView/MapView.tsx b/src/components/MapView/MapView.tsx index 9512be34218c..7f24fb728dc8 100644 --- a/src/components/MapView/MapView.tsx +++ b/src/components/MapView/MapView.tsx @@ -8,7 +8,6 @@ import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import {clearUserLocation, setUserLocation} from '@libs/actions/UserLocation'; -import DistanceRequestUtils from '@libs/DistanceRequestUtils'; import getCurrentPosition from '@libs/getCurrentPosition'; import type {GeolocationErrorCallback} from '@libs/getCurrentPosition/getCurrentPosition.types'; import {GeolocationErrorCode} from '@libs/getCurrentPosition/getCurrentPosition.types'; @@ -30,12 +29,10 @@ import {useSharedValue} from 'react-native-reanimated'; import type {MapViewProps} from './MapViewTypes'; import Compass from './Compass'; -import Direction from './Direction'; +import Directions from './Directions'; import PendingMapView from './PendingMapView'; import responder from './responder'; -import ToggleDistanceUnitButton from './ToggleDistanceUnitButton'; import useAccessToken from './useAccessToken'; -import useDistanceUnit from './useDistanceUnit'; import utils from './utils'; function MapView({ @@ -47,6 +44,8 @@ function MapView({ initialState, waypoints, directionCoordinates: directionCoordinatesProp, + alternativeDirection, + setIsAlternativeDirectionSelected, onMapReady, interactive = true, distanceInMeters, @@ -56,6 +55,10 @@ function MapView({ shouldDisplayCompass = true, }: MapViewProps) { const directionCoordinates = !directionCoordinatesProp || utils.isSingleSegmentRoute(directionCoordinatesProp) ? directionCoordinatesProp : directionCoordinatesProp.flat(); + const alternativeDirectionCoordinates = alternativeDirection?.coordinates; + const alternativeDirectionCoordinatesFlattened = + !alternativeDirectionCoordinates || utils.isSingleSegmentRoute(alternativeDirectionCoordinates) ? alternativeDirectionCoordinates : alternativeDirectionCoordinates.flat(); + const allDirectionCoordinates = [...(directionCoordinates ?? []), ...(alternativeDirectionCoordinatesFlattened ?? [])]; const [userLocation] = useOnyx(ONYXKEYS.USER_LOCATION); const navigation = useNavigation(); @@ -72,13 +75,6 @@ function MapView({ const shouldInitializeCurrentPosition = useRef(true); const isAccessTokenSet = useAccessToken({accessToken}); - const {distanceUnit, toggleDistanceUnit} = useDistanceUnit(unit); - - const distanceLabelText = useMemo( - () => DistanceRequestUtils.getDistanceForDisplayLabel(distanceInMeters ?? 0, distanceUnit ?? CONST.CUSTOM_UNITS.DISTANCE_UNIT_KILOMETERS), - [distanceInMeters, distanceUnit], - ); - // Determines if map can be panned to user's detected // location without bothering the user. It will return // false if user has already started dragging the map or @@ -183,11 +179,11 @@ function MapView({ } else { const {southWest, northEast} = utils.getBounds( waypoints.map((waypoint) => waypoint.coordinate), - directionCoordinates, + allDirectionCoordinates, ); cameraRef.current?.fitBounds(northEast, southWest, mapPadding, 1000); } - }, [mapPadding, waypoints, isIdle, directionCoordinates]), + }, [mapPadding, waypoints, isIdle, allDirectionCoordinates]), ); useEffect(() => { @@ -222,8 +218,8 @@ function MapView({ const centerMap = useCallback(() => { const waypointCoordinates = waypoints?.map((waypoint) => waypoint.coordinate) ?? []; - if (waypointCoordinates.length > 1 || (directionCoordinates ?? []).length > 1) { - const {southWest, northEast} = utils.getBounds(waypoints?.map((waypoint) => waypoint.coordinate) ?? [], directionCoordinates); + if (waypointCoordinates.length > 1 || (allDirectionCoordinates ?? []).length > 1) { + const {southWest, northEast} = utils.getBounds(waypoints?.map((waypoint) => waypoint.coordinate) ?? [], allDirectionCoordinates); cameraRef.current?.fitBounds(southWest, northEast, mapPadding, CONST.MAPBOX.ANIMATION_DURATION_ON_CENTER_ME); return; } @@ -233,7 +229,7 @@ function MapView({ animationDuration: CONST.MAPBOX.ANIMATION_DURATION_ON_CENTER_ME, zoomLevel: CONST.MAPBOX.SINGLE_MARKER_ZOOM, }); - }, [directionCoordinates, currentPosition?.longitude, currentPosition?.latitude, mapPadding, waypoints]); + }, [allDirectionCoordinates, currentPosition?.longitude, currentPosition?.latitude, mapPadding, waypoints]); const centerCoordinate = useMemo(() => (currentPosition ? [currentPosition.longitude, currentPosition.latitude] : initialState?.location), [currentPosition, initialState?.location]); @@ -243,10 +239,10 @@ function MapView({ } const {northEast, southWest} = utils.getBounds( waypoints.map((waypoint) => waypoint.coordinate), - directionCoordinates, + allDirectionCoordinates, ); return {ne: northEast, sw: southWest}; - }, [waypoints, directionCoordinates]); + }, [waypoints, allDirectionCoordinates]); const defaultSettings: Mapbox.CameraStop | undefined = useMemo(() => { if (interactive) { @@ -269,19 +265,6 @@ function MapView({ const initCenterCoordinate = useMemo(() => (interactive ? centerCoordinate : undefined), [interactive, centerCoordinate]); const initBounds = useMemo(() => (interactive ? undefined : waypointsBounds), [interactive, waypointsBounds]); - const distanceSymbolCoordinate = useMemo(() => { - if (!directionCoordinates?.length || !waypoints?.length) { - return; - } - const {northEast, southWest} = utils.getBounds( - waypoints.map((waypoint) => waypoint.coordinate), - directionCoordinates, - ); - const boundsCenter = utils.getBoundsCenter({northEast, southWest}); - - return utils.findClosestCoordinateOnLineFromCenter(boundsCenter, directionCoordinates); - }, [waypoints, directionCoordinates]); - return !isOffline && isAccessTokenSet && !!defaultSettings ? ( } - {!!distanceSymbolCoordinate && !!distanceInMeters && !!distanceUnit && ( - - - - - {distanceLabelText} - - - - - )} + void; // Callback to call when the map is idle / ready. onMapReady?: () => void; // Whether the map is interactive or not @@ -83,14 +94,59 @@ type GPSDirectionProps = { belowLayerID?: string; }; +type DistanceSymbolProps = { + // Distance displayed on the map in meters. + distanceInMeters?: number; + + // Unit of measurement for distance + unit?: Unit; + + // List of coordinates which together forms a direction. + directionCoordinates?: Coordinate[]; + + // List of waypoints on the map + waypoints?: WayPoint[]; + + // Whether the direction to which the symbol is assigned to is selected, true by default if not provided. + // Should be used if alternative directions are available. Determines what style to apply to the symbol. + isSelected?: boolean; +}; + type DirectionProps = { // Coordinates of points that constitute the direction - coordinates: Coordinate[] | Coordinate[][]; + coordinates: Coordinate[] | Coordinate[][]; // Single segment route or multiple segments route // ID of the layer to place the line layer below belowLayerID?: string; + + // Whether the direction is selected, true by default if not provided. Should be used if alternative directions are available + // Determines what style to apply to the direction. + isSelected?: boolean; + + id?: string; + + // Callback to call when the direction is pressed + onPress?: () => void; }; +type DirectionsProps = { + // List of coordinates which together forms a direction. + directionCoordinates?: Coordinate[] | Coordinate[][]; + // List of coordinates which together forms an alternative direction. + alternativeDirection?: { + coordinates: Coordinate[] | Coordinate[][]; + isSelected: boolean; + distanceInMeters: number; + }; + // Callback to call when the alternative direction is selected + setIsAlternativeDirectionSelected?: (isAlternativeDirectionSelected: boolean) => void; + // Distance displayed on the map in meters. + distanceInMeters?: number; + // Unit of measurement for distance + unit?: Unit; + // List of waypoints on the map + waypoints?: WayPoint[]; +}; type PendingMapViewProps = { /** Title message below the icon */ title?: string; @@ -128,4 +184,4 @@ type MapViewHandle = { fitBounds: (ne: Coordinate, sw: Coordinate, paddingConfig?: number | number[], animationDuration?: number) => void; }; -export type {WayPoint, MapViewProps, GPSMapViewProps, DirectionProps, PendingMapViewProps, Coordinate, GPSDirectionProps, CompassProps}; +export type {WayPoint, MapViewProps, GPSMapViewProps, DirectionProps, PendingMapViewProps, Coordinate, GPSDirectionProps, CompassProps, DistanceSymbolProps, DirectionsProps}; diff --git a/src/components/MoneyRequestConfirmationList/sections/selectors.ts b/src/components/MoneyRequestConfirmationList/sections/selectors.ts index 8a58713d8814..c5e0268b6da7 100644 --- a/src/components/MoneyRequestConfirmationList/sections/selectors.ts +++ b/src/components/MoneyRequestConfirmationList/sections/selectors.ts @@ -245,7 +245,7 @@ const invoiceSenderSliceSelector = (t: OnyxEntry): OnyxEntry & { - comment: {waypoints: NonNullable['waypoints']} | undefined; + comment: {waypoints: NonNullable['waypoints']; selectedRouteKey: NonNullable['selectedRouteKey']} | undefined; }; const distanceMapSliceSelector = (t: OnyxEntry): OnyxEntry => { @@ -257,7 +257,7 @@ const distanceMapSliceSelector = (t: OnyxEntry): OnyxEntry>(value, { diff --git a/src/libs/TransactionUtils/getDistanceInMeters.ts b/src/libs/TransactionUtils/getDistanceInMeters.ts index 8c1909327bb5..beef80c59646 100644 --- a/src/libs/TransactionUtils/getDistanceInMeters.ts +++ b/src/libs/TransactionUtils/getDistanceInMeters.ts @@ -15,8 +15,10 @@ function getDistanceInMeters(transaction: OnyxInputOrEntry, unit: U } // If we are creating a new distance request, the distance is available in routes.route0.distance and it's already in meters. - if (transaction?.routes?.route0?.distance) { - return transaction.routes.route0.distance; + // Use the user-selected route key if available, otherwise fall back to route0. + const selectedRouteKey = transaction?.comment?.selectedRouteKey ?? 'route0'; + if (transaction?.routes?.[selectedRouteKey]?.distance) { + return transaction.routes[selectedRouteKey].distance ?? 0; } return 0; diff --git a/src/libs/actions/IOU/SplitExpenseItems.ts b/src/libs/actions/IOU/SplitExpenseItems.ts index 9da213154c5d..f509296c9dc1 100644 --- a/src/libs/actions/IOU/SplitExpenseItems.ts +++ b/src/libs/actions/IOU/SplitExpenseItems.ts @@ -570,9 +570,13 @@ function updateSplitExpenseField( if (transactionDetails?.created !== item.created) { shouldResetDateRange = true; } + const splitSelectedRouteKey = splitExpenseDraftTransaction?.comment?.selectedRouteKey ?? 'route0'; let quantity: number | undefined; - if (splitExpenseDraftTransaction?.routes?.route0?.distance && splitExpenseDraftTransaction?.comment?.customUnit?.distanceUnit) { - quantity = DistanceRequestUtils.convertDistanceUnit(splitExpenseDraftTransaction?.routes?.route0?.distance, splitExpenseDraftTransaction?.comment?.customUnit?.distanceUnit); + if (splitExpenseDraftTransaction?.routes?.[splitSelectedRouteKey]?.distance && splitExpenseDraftTransaction?.comment?.customUnit?.distanceUnit) { + quantity = DistanceRequestUtils.convertDistanceUnit( + splitExpenseDraftTransaction.routes[splitSelectedRouteKey].distance ?? 0, + splitExpenseDraftTransaction?.comment?.customUnit?.distanceUnit, + ); } else { quantity = splitExpenseDraftTransaction?.comment?.customUnit?.quantity ?? 0; } @@ -603,9 +607,9 @@ function updateSplitExpenseField( if (rate && rate > 0) { // Get distance from routes or customUnit.quantity (same logic as in initSplitExpense) let distanceInUnits: number | undefined; - if (splitExpenseDraftTransaction?.routes?.route0?.distance && splitExpenseDraftTransaction?.comment?.customUnit?.distanceUnit) { + if (splitExpenseDraftTransaction?.routes?.[splitSelectedRouteKey]?.distance && splitExpenseDraftTransaction?.comment?.customUnit?.distanceUnit) { distanceInUnits = DistanceRequestUtils.convertDistanceUnit( - splitExpenseDraftTransaction.routes.route0.distance, + splitExpenseDraftTransaction.routes[splitSelectedRouteKey].distance ?? 0, splitExpenseDraftTransaction.comment.customUnit.distanceUnit, ); } else { diff --git a/src/libs/actions/Transaction.ts b/src/libs/actions/Transaction.ts index 39d9f173b32a..6c926d577c7c 100644 --- a/src/libs/actions/Transaction.ts +++ b/src/libs/actions/Transaction.ts @@ -148,16 +148,8 @@ function saveWaypoint({transactionID, index, waypoint, isDraft = false, recentWa route: null, }, - // Clear the existing route so that we don't show an old route - routes: { - route0: { - // Clear the existing distance to recalculate next time - distance: null, - geometry: { - coordinates: null, - }, - }, - }, + // Clear all existing routes so that we don't show stale routes (backend may return multiple alternatives) + routes: null, }); // If current location is used, we would want to avoid saving it as a recent waypoint. This prevents the 'Your Location' @@ -459,15 +451,29 @@ function updateWaypoints(transactionID: string, waypoints: WaypointCollection, t route: null, }, - // Clear the existing route so that we don't show an old route - routes: { - route0: { - // Clear the existing distance to recalculate next time - distance: null, - geometry: { - coordinates: null, - }, - }, + // Clear all existing routes so that we don't show stale routes (backend may return multiple alternatives) + routes: null, + }); +} + +function setSelectedRoute(transactionID: string, routeKey: string, transactionState: TransactionState = CONST.TRANSACTION.STATE.CURRENT): Promise { + let keyPrefix; + switch (transactionState) { + case CONST.TRANSACTION.STATE.DRAFT: + keyPrefix = ONYXKEYS.COLLECTION.TRANSACTION_DRAFT; + break; + case CONST.TRANSACTION.STATE.SPLIT_DRAFT: + keyPrefix = ONYXKEYS.COLLECTION.SPLIT_TRANSACTION_DRAFT; + break; + case CONST.TRANSACTION.STATE.CURRENT: + default: + keyPrefix = ONYXKEYS.COLLECTION.TRANSACTION; + break; + } + + return Onyx.merge(`${keyPrefix}${transactionID}`, { + comment: { + selectedRouteKey: routeKey, }, }); } @@ -1972,4 +1978,5 @@ export { getDefaultP2PMileageRate, mergeTransactionIdsHighlightOnSearchRoute, getDuplicateTransactionDetails, + setSelectedRoute, }; diff --git a/src/pages/iou/request/step/IOURequestStepConfirmation.tsx b/src/pages/iou/request/step/IOURequestStepConfirmation.tsx index 3446132b55c7..59b556ab92a2 100644 --- a/src/pages/iou/request/step/IOURequestStepConfirmation.tsx +++ b/src/pages/iou/request/step/IOURequestStepConfirmation.tsx @@ -66,6 +66,7 @@ import { hasReceipt, isDistanceRequest as isDistanceRequestTransactionUtils, isManualDistanceRequest as isManualDistanceRequestTransactionUtils, + isMapDistanceRequest as isMapDistanceRequestTransactionUtils, isOdometerDistanceRequest as isOdometerDistanceRequestTransactionUtils, isScanRequest, } from '@libs/TransactionUtils'; @@ -230,6 +231,7 @@ function IOURequestStepConfirmation({ const [receiptFiles, setReceiptFiles] = useState>({}); const isDistanceRequest = isDistanceRequestTransactionUtils(transaction); + const isMapDistanceRequest = isMapDistanceRequestTransactionUtils(transaction); const isManualDistanceRequest = isManualDistanceRequestTransactionUtils(transaction); const isManualRequest = transaction?.iouRequestType === CONST.IOU.REQUEST_TYPE.MANUAL; const isOdometerDistanceRequest = isOdometerDistanceRequestTransactionUtils(transaction); @@ -493,6 +495,7 @@ function IOURequestStepConfirmation({ action, requestType, isDistanceRequest, + isMapDistanceRequest, isManualDistanceRequest, isOdometerDistanceRequest, isPerDiemRequest, diff --git a/src/pages/iou/request/step/IOURequestStepDistance/handleMoneyRequestStepDistanceNavigation.ts b/src/pages/iou/request/step/IOURequestStepDistance/handleMoneyRequestStepDistanceNavigation.ts index 4244211a0834..7f4549edcc27 100644 --- a/src/pages/iou/request/step/IOURequestStepDistance/handleMoneyRequestStepDistanceNavigation.ts +++ b/src/pages/iou/request/step/IOURequestStepDistance/handleMoneyRequestStepDistanceNavigation.ts @@ -205,6 +205,10 @@ function handleMoneyRequestStepDistanceNavigation({ const isManualDistance = manualDistance !== undefined; const isOdometerDistance = odometerDistance !== undefined; const isGPSDistance = gpsDistance !== undefined && gpsCoordinates !== undefined; + const isMapDistance = waypoints && !isGPSDistance; + const selectedRouteKey = transaction?.comment?.selectedRouteKey; + const effectiveGpsCoordinates = + isMapDistance && selectedRouteKey && selectedRouteKey !== 'route0' ? JSON.stringify(transaction?.routes?.[selectedRouteKey]?.geometry?.coordinates) : gpsCoordinates; const distanceRequestType = getDistanceRequestType(transaction); if (transaction?.splitShares && !isManualDistance && !isOdometerDistance) { @@ -308,7 +312,7 @@ function handleMoneyRequestStepDistanceNavigation({ expenseDate: transaction?.created, }), attendees: transaction?.comment?.attendees, - gpsCoordinates, + gpsCoordinates: effectiveGpsCoordinates, distanceRequestType, odometerStart, odometerEnd, @@ -384,7 +388,7 @@ function handleMoneyRequestStepDistanceNavigation({ }), splitShares: transaction?.splitShares, attendees: transaction?.comment?.attendees, - gpsCoordinates, + gpsCoordinates: effectiveGpsCoordinates, distanceRequestType, odometerStart, odometerEnd, diff --git a/src/pages/iou/request/step/confirmation/useExpenseSubmission.ts b/src/pages/iou/request/step/confirmation/useExpenseSubmission.ts index 4e40e78e941b..c53422b9e433 100644 --- a/src/pages/iou/request/step/confirmation/useExpenseSubmission.ts +++ b/src/pages/iou/request/step/confirmation/useExpenseSubmission.ts @@ -128,6 +128,7 @@ type UseExpenseSubmissionParams = { isDistanceRequest: boolean; isManualDistanceRequest: boolean; isOdometerDistanceRequest: boolean; + isMapDistanceRequest: boolean; isPerDiemRequest: boolean; isTimeRequest: boolean; isMovingTransactionFromTrackExpense: boolean; @@ -178,6 +179,7 @@ function useExpenseSubmission(params: UseExpenseSubmissionParams) { iouType, action, isDistanceRequest, + isMapDistanceRequest, isManualDistanceRequest, isOdometerDistanceRequest, isPerDiemRequest, @@ -657,6 +659,20 @@ function useExpenseSubmission(params: UseExpenseSubmissionParams) { const itemDistance = isManualDistanceRequest || isOdometerDistanceRequest || isGPSDistanceRequest ? (item.comment?.customUnit?.quantity ?? undefined) : undefined; const email = currentUserPersonalDetails.email ?? ''; + + let gpsCoordinates: string | undefined; + if (isGPSDistanceRequest) { + gpsCoordinates = getStringifiedGPSCoordinates(gpsDraftDetails); + } else if (isMapDistanceRequest) { + const routeKey = item.comment?.selectedRouteKey; + if (routeKey && routeKey !== 'route0') { + const coords = item.routes?.[routeKey]?.geometry?.coordinates; + if (coords) { + gpsCoordinates = JSON.stringify(coords); + } + } + } + trackExpenseIOUActions({ report: trackReport, isDraftPolicy, @@ -699,7 +715,7 @@ function useExpenseSubmission(params: UseExpenseSubmissionParams) { odometerStart: isOdometerDistanceRequest ? item.comment?.odometerStart : undefined, odometerEnd: isOdometerDistanceRequest ? item.comment?.odometerEnd : undefined, isFromGlobalCreate: getIsFromGlobalCreate(item), - gpsCoordinates: isGPSDistanceRequest ? getStringifiedGPSCoordinates(gpsDraftDetails) : undefined, + gpsCoordinates, distanceRequestType, }, accountantParams: { @@ -748,6 +764,19 @@ function useExpenseSubmission(params: UseExpenseSubmissionParams) { return; } + let gpsCoordinates: string | undefined; + if (isGPSDistanceRequest) { + gpsCoordinates = getStringifiedGPSCoordinates(gpsDraftDetails); + } else if (isMapDistanceRequest) { + const routeKey = transaction.comment?.selectedRouteKey; + if (routeKey && routeKey !== 'route0') { + const coords = transaction.routes?.[routeKey]?.geometry?.coordinates; + if (coords) { + gpsCoordinates = JSON.stringify(coords); + } + } + } + const {chatReportID: distanceChatReportID, transactionID: distanceTransactionID} = createDistanceRequestIOUActions({ report, participants: selectedParticipantsForRequest, @@ -784,7 +813,7 @@ function useExpenseSubmission(params: UseExpenseSubmissionParams) { odometerStart: isOdometerDistanceRequest ? transaction.comment?.odometerStart : undefined, odometerEnd: isOdometerDistanceRequest ? transaction.comment?.odometerEnd : undefined, isFromGlobalCreate: getIsFromGlobalCreate(transaction), - gpsCoordinates: isGPSDistanceRequest ? getStringifiedGPSCoordinates(gpsDraftDetails) : undefined, + gpsCoordinates, distanceRequestType, }, isASAPSubmitBetaEnabled, diff --git a/src/styles/index.ts b/src/styles/index.ts index 167081fda20a..c7344fe720d0 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -4204,6 +4204,19 @@ const staticStyles = (theme: ThemeColors) => color: colors.productLight100, }, + alternativeDistanceLabelWrapper: { + backgroundColor: colors.green200, + paddingHorizontal: 8, + paddingVertical: 4, + borderRadius: 4, + textAlign: 'center', + }, + alternativeDistanceLabelText: { + fontSize: 13, + fontWeight: FontUtils.fontWeight.bold, + color: colors.productLight900, + }, + productTrainingTooltipWrapper: { backgroundColor: theme.tooltipHighlightBG, borderRadius: variables.componentBorderRadiusNormal, @@ -6928,6 +6941,18 @@ const plainStyles = (theme: ThemeColors) => lineCap: 'round', } satisfies MapDirectionStyle, + alternativeMapDirection: { + lineColor: colors.green200, + lineWidth: 6, + lineCap: 'round', + }, + + alternativeMapDirectionBorder: { + lineColor: colors.green600, + lineWidth: 8, + lineCap: 'round', + }, + mapDirectionLayer: { layout: {'line-join': 'round', 'line-cap': 'round'}, paint: {'line-color': colors.green400, 'line-width': 6}, diff --git a/src/types/onyx/Transaction.ts b/src/types/onyx/Transaction.ts index c3b1caf12d6b..e556748edbfa 100644 --- a/src/types/onyx/Transaction.ts +++ b/src/types/onyx/Transaction.ts @@ -150,6 +150,9 @@ type Comment = { /** Odometer end image (File object with uri on web, URI string on native) */ odometerEndImage?: FileObject | string; + /** Key of the route selected by the user when multiple alternative routes are available (e.g. 'route0', 'route1') */ + selectedRouteKey?: string; + /** Spotnana trip ID, set on travel transactions and used to link the expense to its trip room */ tripID?: string; }; From 4bef03f6bac1d6515a6ff00ec139dea0b053ece9 Mon Sep 17 00:00:00 2001 From: GCyganek Date: Wed, 15 Jul 2026 11:23:51 +0200 Subject: [PATCH 2/6] Fix lint and ts --- config/eslint/eslint.seatbelt.tsv | 2 +- src/components/MapView/Direction.tsx | 2 -- src/components/MapView/MapView.tsx | 1 - tests/unit/hooks/useExpenseSubmission.test.ts | 1 + 4 files changed, 2 insertions(+), 4 deletions(-) diff --git a/config/eslint/eslint.seatbelt.tsv b/config/eslint/eslint.seatbelt.tsv index deccb27ae793..6b4c46479183 100644 --- a/config/eslint/eslint.seatbelt.tsv +++ b/config/eslint/eslint.seatbelt.tsv @@ -151,7 +151,7 @@ "../../src/components/DelegateNoAccessModalProvider/index.tsx" "@typescript-eslint/no-deprecated/ConfirmModal" 1 "../../src/components/DialogLabelContext.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../src/components/DisplayNames/DisplayNamesWithTooltip.tsx" "react-hooks/refs" 6 -"../../src/components/DistanceRequest/DistanceRequestFooter.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/DistanceRequest/DistanceRequestFooter.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 "../../src/components/DistanceRequest/DistanceRequestFooter.tsx" "no-restricted-imports" 1 "../../src/components/DistanceRequest/DistanceRequestRenderItem.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../src/components/Domain/CopyableTextField.tsx" "no-restricted-imports" 1 diff --git a/src/components/MapView/Direction.tsx b/src/components/MapView/Direction.tsx index 4c4d0597cab7..1f201372acbd 100644 --- a/src/components/MapView/Direction.tsx +++ b/src/components/MapView/Direction.tsx @@ -30,8 +30,6 @@ function Direction({coordinates, belowLayerID, isSelected = true, id, onPress}: <> {validSegments.map((segmentCoordinates, index) => ( [ requestType: undefined, isDistanceRequest: false, isManualDistanceRequest: false, + isMapDistanceRequest: false, isOdometerDistanceRequest: false, isPerDiemRequest: false, isTimeRequest: false, From d02b07def72aa00e9240625c80d447c0ff690c25 Mon Sep 17 00:00:00 2001 From: GCyganek Date: Wed, 15 Jul 2026 15:08:27 +0200 Subject: [PATCH 3/6] Code cleanup, adjust styles to Figma --- src/components/MapView/Direction.tsx | 4 ++-- src/components/MapView/DistanceSymbol.tsx | 2 -- src/components/MapView/MapView.tsx | 12 ++++------ src/components/MapView/MapViewTypes.ts | 29 ++++++++++++++++++----- src/components/MapView/utils.ts | 13 +++++++++- src/styles/index.ts | 10 ++------ 6 files changed, 43 insertions(+), 27 deletions(-) diff --git a/src/components/MapView/Direction.tsx b/src/components/MapView/Direction.tsx index 1f201372acbd..9605dc0ca2f9 100644 --- a/src/components/MapView/Direction.tsx +++ b/src/components/MapView/Direction.tsx @@ -50,7 +50,7 @@ function Direction({coordinates, belowLayerID, isSelected = true, id, onPress}: ))} @@ -83,7 +83,7 @@ function Direction({coordinates, belowLayerID, isSelected = true, id, onPress}: ); diff --git a/src/components/MapView/DistanceSymbol.tsx b/src/components/MapView/DistanceSymbol.tsx index 36bc8440411a..7e84b47e2784 100644 --- a/src/components/MapView/DistanceSymbol.tsx +++ b/src/components/MapView/DistanceSymbol.tsx @@ -43,8 +43,6 @@ function DistanceSymbol({distanceInMeters, unit, directionCoordinates, waypoints return ( diff --git a/src/components/MapView/MapView.tsx b/src/components/MapView/MapView.tsx index 16be685535df..2a52c20aadaa 100644 --- a/src/components/MapView/MapView.tsx +++ b/src/components/MapView/MapView.tsx @@ -42,7 +42,7 @@ function MapView({ pitchEnabled, initialState, waypoints, - directionCoordinates: directionCoordinatesProp, + directionCoordinates, alternativeDirection, setIsAlternativeDirectionSelected, onMapReady, @@ -53,12 +53,6 @@ function MapView({ shouldDisplayCurrentLocation = true, shouldDisplayCompass = true, }: MapViewProps) { - const directionCoordinates = !directionCoordinatesProp || utils.isSingleSegmentRoute(directionCoordinatesProp) ? directionCoordinatesProp : directionCoordinatesProp.flat(); - const alternativeDirectionCoordinates = alternativeDirection?.coordinates; - const alternativeDirectionCoordinatesFlattened = - !alternativeDirectionCoordinates || utils.isSingleSegmentRoute(alternativeDirectionCoordinates) ? alternativeDirectionCoordinates : alternativeDirectionCoordinates.flat(); - const allDirectionCoordinates = [...(directionCoordinates ?? []), ...(alternativeDirectionCoordinatesFlattened ?? [])]; - const [userLocation] = useOnyx(ONYXKEYS.USER_LOCATION); const navigation = useNavigation(); const {isOffline} = useNetwork(); @@ -160,6 +154,8 @@ function MapView({ [], ); + const allDirectionCoordinates = utils.getCoordinatesFromAllDirections(directionCoordinates, alternativeDirection); + // When the page loses focus, we temporarily set the "idled" state to false. // When the page regains focus, the onIdled method of the map will set the actual "idled" state, // which in turn triggers the callback. @@ -325,7 +321,7 @@ function MapView({ })} void; }; +type AlternativeDirection = { + // List of coordinates which together forms a direction. + coordinates: Coordinate[] | Coordinate[][]; + // Whether the alternative direction is selected + isSelected: boolean; + // Distance displayed on the map in meters. + distanceInMeters: number; +}; + type DirectionsProps = { // List of coordinates which together forms a direction. directionCoordinates?: Coordinate[] | Coordinate[][]; // List of coordinates which together forms an alternative direction. - alternativeDirection?: { - coordinates: Coordinate[] | Coordinate[][]; - isSelected: boolean; - distanceInMeters: number; - }; + alternativeDirection?: AlternativeDirection; // Callback to call when the alternative direction is selected setIsAlternativeDirectionSelected?: (isAlternativeDirectionSelected: boolean) => void; // Distance displayed on the map in meters. @@ -184,4 +189,16 @@ type MapViewHandle = { fitBounds: (ne: Coordinate, sw: Coordinate, paddingConfig?: number | number[], animationDuration?: number) => void; }; -export type {WayPoint, MapViewProps, GPSMapViewProps, DirectionProps, PendingMapViewProps, Coordinate, GPSDirectionProps, CompassProps, DistanceSymbolProps, DirectionsProps}; +export type { + WayPoint, + MapViewProps, + GPSMapViewProps, + DirectionProps, + PendingMapViewProps, + Coordinate, + GPSDirectionProps, + CompassProps, + DistanceSymbolProps, + DirectionsProps, + AlternativeDirection, +}; diff --git a/src/components/MapView/utils.ts b/src/components/MapView/utils.ts index ab50d6bbbed9..934e655fe571 100644 --- a/src/components/MapView/utils.ts +++ b/src/components/MapView/utils.ts @@ -1,4 +1,4 @@ -import type {Coordinate} from './MapViewTypes'; +import type {AlternativeDirection, Coordinate} from './MapViewTypes'; /** A geographic point as a plain longitude/latitude pair. Mapbox's `LngLat` became a class in mapbox-gl 3.x, but these helpers only read `.lng`/`.lat`, so a literal shape is all that's needed. */ type LngLatLiteral = {lng: number; lat: number}; @@ -139,6 +139,16 @@ function getBoundsCenter(bounds: {southWest: Coordinate; northEast: Coordinate}) return {lng: latitudeCenter, lat: longitudeCenter}; } +function getCoordinatesFromAllDirections(directionCoordinates: Coordinate[] | Coordinate[][] | undefined, alternativeDirection: AlternativeDirection | undefined) { + const directionCoordinatesFlattened = !directionCoordinates || isSingleSegmentRoute(directionCoordinates) ? directionCoordinates : directionCoordinates.flat(); + + const alternativeDirectionCoordinates = alternativeDirection?.coordinates; + const alternativeDirectionCoordinatesFlattened = + !alternativeDirectionCoordinates || isSingleSegmentRoute(alternativeDirectionCoordinates) ? alternativeDirectionCoordinates : alternativeDirectionCoordinates.flat(); + + return [...(directionCoordinatesFlattened ?? []), ...(alternativeDirectionCoordinatesFlattened ?? [])]; +} + export default { getBounds, areSameCoordinate, @@ -147,4 +157,5 @@ export default { getBoundsCenter, simpleInterpolateCoordinate, isSingleSegmentRoute, + getCoordinatesFromAllDirections, }; diff --git a/src/styles/index.ts b/src/styles/index.ts index c7344fe720d0..33bf2c1a93ab 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -4205,7 +4205,7 @@ const staticStyles = (theme: ThemeColors) => }, alternativeDistanceLabelWrapper: { - backgroundColor: colors.green200, + backgroundColor: colors.green100, paddingHorizontal: 8, paddingVertical: 4, borderRadius: 4, @@ -4214,7 +4214,7 @@ const staticStyles = (theme: ThemeColors) => alternativeDistanceLabelText: { fontSize: 13, fontWeight: FontUtils.fontWeight.bold, - color: colors.productLight900, + color: colors.green800, }, productTrainingTooltipWrapper: { @@ -6947,12 +6947,6 @@ const plainStyles = (theme: ThemeColors) => lineCap: 'round', }, - alternativeMapDirectionBorder: { - lineColor: colors.green600, - lineWidth: 8, - lineCap: 'round', - }, - mapDirectionLayer: { layout: {'line-join': 'round', 'line-cap': 'round'}, paint: {'line-color': colors.green400, 'line-width': 6}, From 0223d92267628cf0d305af0af86f1f49fef7410b Mon Sep 17 00:00:00 2001 From: GCyganek Date: Wed, 15 Jul 2026 15:31:32 +0200 Subject: [PATCH 4/6] Fix directions layers order when switching between directions --- .../MapView/AlternateDirections.tsx | 109 ++++++++++++++++++ src/components/MapView/Directions.tsx | 88 ++++---------- 2 files changed, 133 insertions(+), 64 deletions(-) create mode 100644 src/components/MapView/AlternateDirections.tsx diff --git a/src/components/MapView/AlternateDirections.tsx b/src/components/MapView/AlternateDirections.tsx new file mode 100644 index 000000000000..ee88f5112c63 --- /dev/null +++ b/src/components/MapView/AlternateDirections.tsx @@ -0,0 +1,109 @@ +import useThemeStyles from '@hooks/useThemeStyles'; + +import CONST from '@src/CONST'; + +import type {Feature, FeatureCollection, LineString, MultiLineString} from 'geojson'; + +import Mapbox from '@rnmapbox/maps'; + +import type {Coordinate, DirectionsProps} from './MapViewTypes'; + +import utils from './utils'; + +type DirectionFeatureProperties = { + isAlternative: boolean; + isSelected: boolean; +}; + +type AlternateDirectionsProps = Required> & Pick; + +const SOURCE_ID = `alternate-directions-${CONST.MAP_VIEW_LAYERS.ROUTE_SOURCE}`; +const UNSELECTED_FILL_ID = `alternate-directions-unselected-${CONST.MAP_VIEW_LAYERS.ROUTE_FILL}`; +const UNSELECTED_BORDER_ID = `alternate-directions-unselected-${CONST.MAP_VIEW_LAYERS.ROUTE_BORDER}`; +const SELECTED_FILL_ID = `alternate-directions-selected-${CONST.MAP_VIEW_LAYERS.ROUTE_FILL}`; +const SELECTED_BORDER_ID = `alternate-directions-selected-${CONST.MAP_VIEW_LAYERS.ROUTE_BORDER}`; + +function getDirectionFeature( + coordinates: Coordinate[] | Coordinate[][], + isAlternative: boolean, + isSelected: boolean, +): Feature | undefined { + if (utils.isSingleSegmentRoute(coordinates)) { + if (coordinates.length < 2) { + return undefined; + } + + return { + type: 'Feature', + properties: {isAlternative, isSelected}, + geometry: { + type: 'LineString', + coordinates, + }, + }; + } + + const validSegments = coordinates.filter((segment) => segment.length >= 2); + if (validSegments.length === 0) { + return undefined; + } + + return { + type: 'Feature', + properties: {isAlternative, isSelected}, + geometry: { + type: 'MultiLineString', + coordinates: validSegments, + }, + }; +} + +function AlternateDirections({directionCoordinates, alternativeDirection, setIsAlternativeDirectionSelected}: AlternateDirectionsProps) { + const styles = useThemeStyles(); + const directionShape: FeatureCollection = { + type: 'FeatureCollection', + features: [ + getDirectionFeature(directionCoordinates, false, !alternativeDirection.isSelected), + getDirectionFeature(alternativeDirection.coordinates, true, alternativeDirection.isSelected), + ].filter((feature): feature is Feature => !!feature), + }; + + return ( + { + const properties = features.at(0)?.properties; + if (typeof properties?.isAlternative !== 'boolean') { + return; + } + setIsAlternativeDirectionSelected?.(properties.isAlternative); + }} + > + + + + + + ); +} + +export default AlternateDirections; diff --git a/src/components/MapView/Directions.tsx b/src/components/MapView/Directions.tsx index 7b3c5a27c7ee..62a46b5355ac 100644 --- a/src/components/MapView/Directions.tsx +++ b/src/components/MapView/Directions.tsx @@ -1,85 +1,45 @@ import type {DirectionsProps} from './MapViewTypes'; +import AlternateDirections from './AlternateDirections'; import Direction from './Direction'; import DistanceSymbol from './DistanceSymbol'; import utils from './utils'; -type MainDirectionProps = Pick & { - isAlternativeDirectionSelected: boolean; -}; - -function MainDirection({directionCoordinates, setIsAlternativeDirectionSelected, distanceInMeters, isAlternativeDirectionSelected, unit, waypoints}: MainDirectionProps) { +function Directions({directionCoordinates, alternativeDirection, setIsAlternativeDirectionSelected, distanceInMeters, unit, waypoints}: DirectionsProps) { if (!directionCoordinates) { return null; } - return ( - <> - setIsAlternativeDirectionSelected?.(false)} - /> - - - ); -} - -type AlternativeDirectionProps = Pick; - -function AlternativeDirection({alternativeDirection, setIsAlternativeDirectionSelected, unit, waypoints}: AlternativeDirectionProps) { const alternativeDirectionCoordinates = alternativeDirection?.coordinates; - - if (!alternativeDirection || !alternativeDirectionCoordinates?.length) { - return null; - } + const hasAlternativeDirection = !!alternativeDirection && !!alternativeDirectionCoordinates?.length; + const isAlternativeDirectionSelected = !!alternativeDirection?.isSelected; return ( <> - setIsAlternativeDirectionSelected?.(true)} - /> + {hasAlternativeDirection ? ( + <> + + + + ) : ( + + )} - - ); -} - -function Directions({directionCoordinates, alternativeDirection, setIsAlternativeDirectionSelected, distanceInMeters, unit, waypoints}: DirectionsProps) { - if (!directionCoordinates) { - return null; - } - - return ( - <> - - ); From 72942c4460a94ee1d65a991b6d69cd9e28a06f51 Mon Sep 17 00:00:00 2001 From: GCyganek Date: Wed, 15 Jul 2026 15:32:55 +0200 Subject: [PATCH 5/6] Revert changes in Direction.tsx --- src/components/MapView/Direction.tsx | 36 +++++++++++----------------- 1 file changed, 14 insertions(+), 22 deletions(-) diff --git a/src/components/MapView/Direction.tsx b/src/components/MapView/Direction.tsx index 9605dc0ca2f9..37b5867b6b60 100644 --- a/src/components/MapView/Direction.tsx +++ b/src/components/MapView/Direction.tsx @@ -8,15 +8,7 @@ import type {DirectionProps} from './MapViewTypes'; import utils from './utils'; -function getId(idSuffix: string, idPrefix?: string) { - if (!idPrefix) { - return idSuffix; - } - - return `${idPrefix}-${idSuffix}`; -} - -function Direction({coordinates, belowLayerID, isSelected = true, id, onPress}: DirectionProps) { +function Direction({coordinates, belowLayerID}: DirectionProps) { const styles = useThemeStyles(); // If not a single segment route, we need to handle route split into multiple separate segments @@ -30,8 +22,10 @@ function Direction({coordinates, belowLayerID, isSelected = true, id, onPress}: <> {validSegments.map((segmentCoordinates, index) => ( @@ -64,7 +57,7 @@ function Direction({coordinates, belowLayerID, isSelected = true, id, onPress}: return ( From 8319296be473a99c44a48ea0a5b64782950e1c06 Mon Sep 17 00:00:00 2001 From: GCyganek Date: Wed, 15 Jul 2026 15:43:58 +0200 Subject: [PATCH 6/6] Add missing geojson types --- package-lock.json | 23 ++++++++++------------- package.json | 1 + 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6d83ed94e2e3..da7d9f85b218 100644 --- a/package-lock.json +++ b/package-lock.json @@ -198,6 +198,7 @@ "@types/canvas-size": "^1.2.2", "@types/concurrently": "^7.0.3", "@types/d3-scale": "^4.0.9", + "@types/geojson": "^7946.0.16", "@types/howler": "^2.2.12", "@types/jest": "^29.5.14", "@types/jest-when": "^3.5.2", @@ -10884,12 +10885,6 @@ "pbf": "^4.0.2" } }, - "node_modules/@mapbox/vector-tile/node_modules/@types/geojson": { - "version": "7946.0.16", - "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", - "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", - "license": "MIT" - }, "node_modules/@maplibre/maplibre-gl-style-spec": { "version": "19.3.3", "resolved": "https://registry.npmjs.org/@maplibre/maplibre-gl-style-spec/-/maplibre-gl-style-spec-19.3.3.tgz", @@ -18025,7 +18020,9 @@ "license": "MIT" }, "node_modules/@types/geojson": { - "version": "7946.0.8", + "version": "7946.0.16", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", + "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", "license": "MIT" }, "node_modules/@types/geojson-vt": { @@ -27009,6 +27006,12 @@ "url": "https://opencollective.com/turf" } }, + "node_modules/geojson-rbush/node_modules/@types/geojson": { + "version": "7946.0.8", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.8.tgz", + "integrity": "sha512-1rkryxURpr6aWP7R786/UQOkJ3PcpQiWkAXBmdWc7ryFWqN6a4xfK7BtjXvFBKO9LjQ+MWQSWxYeZX1OApnArA==", + "license": "MIT" + }, "node_modules/geojson-vt": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/geojson-vt/-/geojson-vt-4.0.3.tgz", @@ -32667,12 +32670,6 @@ "tinyqueue": "^3.0.0" } }, - "node_modules/mapbox-gl/node_modules/@types/geojson": { - "version": "7946.0.16", - "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", - "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", - "license": "MIT" - }, "node_modules/mapbox-gl/node_modules/quickselect": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/quickselect/-/quickselect-3.0.0.tgz", diff --git a/package.json b/package.json index c38548709563..fbcd8887d507 100644 --- a/package.json +++ b/package.json @@ -272,6 +272,7 @@ "@types/canvas-size": "^1.2.2", "@types/concurrently": "^7.0.3", "@types/d3-scale": "^4.0.9", + "@types/geojson": "^7946.0.16", "@types/howler": "^2.2.12", "@types/jest": "^29.5.14", "@types/jest-when": "^3.5.2",