Skip to content

Commit 9ae271a

Browse files
committed
fixed navigating back afte save on mobile
1 parent 4f14af9 commit 9ae271a

5 files changed

Lines changed: 83 additions & 105 deletions

File tree

src/components/ReportActionItem/ReportActionItemImage.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,13 @@ function ReportActionItemImage({
118118
const isMapDistanceRequest = !!transaction && isDistanceRequest(transaction) && !isManualDistanceRequest(transaction);
119119
const hasPendingWaypoints = transaction && isFetchingWaypointsFromServer(transaction);
120120
const hasErrors = !isEmptyObject(transaction?.errors) || !isEmptyObject(transaction?.errorFields?.route) || !isEmptyObject(transaction?.errorFields?.waypoints);
121-
const showMapAsImage = isMapDistanceRequest && (hasErrors || hasPendingWaypoints);
121+
// After a distance/rate edit the BE regenerates the receipt and invalidates the prior URL, but
122+
// the local `receipt.source` only refreshes when the Pusher push arrives. Render `ConfirmedRoute`
123+
// (which draws the map from `routes.coordinates`, independent of the URL) while any of these
124+
// edits are pending so the thumbnail doesn't briefly try to load the now-404'd URL.
125+
const pf = transaction?.pendingFields as Record<string, unknown> | undefined;
126+
const hasPendingReceiptRegeneration = !!pf && (!!pf.distance || !!pf.merchant || !!pf.customUnitRateID);
127+
const showMapAsImage = isMapDistanceRequest && (hasErrors || !!hasPendingWaypoints || hasPendingReceiptRegeneration);
122128

123129
if (showMapAsImage) {
124130
return (

src/libs/TransactionUtils/index.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -748,6 +748,13 @@ function getUpdatedTransaction({
748748
updatedTransaction.amount = updatedAmount;
749749
updatedTransaction.modifiedAmount = updatedAmount;
750750
updatedTransaction.modifiedMerchant = updatedMerchant;
751+
752+
// Sync `customUnit.quantity` to the new route distance. Without this the prior manual
753+
// quantity (set when the user edited distance manually before changing waypoints) would
754+
// linger and drive `getDistanceInMeters`, since that helper prefers quantity over routes.
755+
if (unit) {
756+
lodashSet(updatedTransaction, 'comment.customUnit.quantity', roundToTwoDecimalPlaces(DistanceRequestUtils.convertDistanceUnit(distanceInMeters, unit)));
757+
}
751758
}
752759
}
753760

src/libs/actions/IOU/UpdateMoneyRequest.ts

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -509,7 +509,10 @@ function updateMoneyRequestDistance({
509509
// Don't sanitize waypoints here - keep all fields for Onyx optimistic data (e.g., keyForList)
510510
// Sanitization happens when building API params
511511
...(waypoints && {waypoints}),
512-
routes,
512+
// Only include routes when the caller explicitly provided them. Including `routes: undefined`
513+
// would make the optimistic merge wipe the existing route, briefly blanking the map thumbnail
514+
// and report preview before the server response restores it.
515+
...(routes !== undefined && {routes}),
513516
...(distance && {distance}),
514517
...(odometerStart !== undefined && {odometerStart}),
515518
...(odometerEnd !== undefined && {odometerEnd}),
@@ -966,6 +969,15 @@ function getUpdateMoneyRequestParams(params: GetUpdateMoneyRequestParamsType): U
966969
.filter((key) => !(shouldSuppressWaypointsAsPending && key === 'waypoints'))
967970
.map((key) => [key, CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE]),
968971
);
972+
// Flag `merchant` as pending on any edit that causes the BE to regenerate the receipt
973+
// (waypoints / distance / rate). `merchant` isn't in `transactionChanges`, so the success-data
974+
// merge won't clear it via `clearedPendingFields` — it persists through the gap between API ack
975+
// and the Pusher push that delivers the new `receipt.source`. The Pusher push then clears all
976+
// pendingFields atomically together with the new URL, eliminating the broken-image flash.
977+
// It also drives the Distance row's offline-feedback strikethrough for pure distance edits.
978+
if ('waypoints' in transactionChanges || 'distance' in transactionChanges || 'customUnitRateID' in transactionChanges) {
979+
pendingFields.merchant = CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE;
980+
}
969981
const clearedPendingFields = getClearedPendingFields(transactionChanges);
970982
const errorFields = Object.fromEntries(Object.keys(pendingFields).map((key) => [key, getMicroSecondOnyxErrorWithTranslationKey('iou.error.genericEditFailureMessage')]));
971983

@@ -1274,7 +1286,11 @@ function getUpdateMoneyRequestParams(params: GetUpdateMoneyRequestParamsType): U
12741286
// Clear out the error fields and loading states on success.
12751287
// Only clear `routes` when waypoints/rate changed (the server will push a fresh route via Pusher).
12761288
// For pure distance edits the route is unchanged, and clearing it would make the map briefly disappear.
1277-
const shouldClearRoutes = haveWaypointsActuallyChanged || hasModifiedDistanceRate;
1289+
// When the caller already supplied a valid optimistic route (waypoint edit with route pre-fetched
1290+
// locally), keep it so the receipt thumbnail and ConfirmedRoute don't flicker between success and
1291+
// the Pusher route push.
1292+
const hasValidOptimisticRoute = !!transactionChanges.routes?.route0?.geometry?.coordinates?.length;
1293+
const shouldClearRoutes = (haveWaypointsActuallyChanged || hasModifiedDistanceRate) && !hasValidOptimisticRoute;
12781294
successData.push({
12791295
onyxMethod: Onyx.METHOD.MERGE,
12801296
key: `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`,

src/pages/iou/request/step/IOURequestStepDistance.tsx

Lines changed: 26 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,8 @@ import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react';
55
import type {ScrollView as RNScrollView} from 'react-native';
66
import type {RenderItemParams} from 'react-native-draggable-flatlist/lib/typescript/types';
77
import type {OnyxEntry} from 'react-native-onyx';
8-
import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView';
98
import DistanceRequestRenderItem from '@components/DistanceRequest/DistanceRequestRenderItem';
10-
import HeaderWithBackButton from '@components/HeaderWithBackButton';
119
import type {NumberWithSymbolFormRef} from '@components/NumberWithSymbolForm';
12-
import ScreenWrapper from '@components/ScreenWrapper';
1310
import TabSelector from '@components/TabSelector/TabSelector';
1411
import type {BaseTextInputRef} from '@components/TextInput/BaseTextInput/types';
1512
import withCurrentUserPersonalDetails from '@components/withCurrentUserPersonalDetails';
@@ -37,7 +34,6 @@ import {init, stop} from '@libs/actions/MapboxToken';
3734
import {openReport} from '@libs/actions/Report';
3835
import {openDraftDistanceExpense, removeWaypoint, updateWaypoints as updateWaypointsUtil} from '@libs/actions/Transaction';
3936
import {createBackupTransaction, removeBackupTransaction, restoreOriginalTransactionFromBackup} from '@libs/actions/TransactionEdit';
40-
import {canUseTouchScreen} from '@libs/DeviceCapabilities';
4137
import DistanceRequestUtils from '@libs/DistanceRequestUtils';
4238
import type {MileageRate} from '@libs/DistanceRequestUtils';
4339
import {getLatestErrorField} from '@libs/ErrorUtils';
@@ -333,6 +329,10 @@ function IOURequestStepDistance({
333329
Navigation.goBack(backTo);
334330
}, [backTo]);
335331

332+
const navigateBackAfterSave = useCallback(() => {
333+
Navigation.closeRHPFlow();
334+
}, []);
335+
336336
/**
337337
* Takes the user to the page for editing a specific waypoint
338338
* @param index of the waypoint to edit
@@ -503,7 +503,7 @@ function IOURequestStepDistance({
503503
{waypoints: currentTransaction?.comment?.waypoints, routes: currentTransaction?.routes},
504504
policy,
505505
);
506-
navigateBack();
506+
navigateBackAfterSave();
507507
return;
508508
}
509509

@@ -515,24 +515,17 @@ function IOURequestStepDistance({
515515
const hasRouteChanged = !deepEqual(transactionBackup?.routes, transaction?.routes);
516516
if (deepEqual(oldAddresses, addresses)) {
517517
transactionWasSaved.current = true;
518-
navigateBack();
518+
navigateBackAfterSave();
519519
return;
520520
}
521521
if (transaction?.transactionID && report?.reportID) {
522-
// Send the new route distance so the server replaces any stale manual quantity.
523-
// Use full precision (no rounding) to avoid triggering the `increasedDistance`
524-
// violation when the rounded display value drifts above the exact route distance.
525-
const routeDistanceInMeters = transaction?.routes?.route0?.distance;
526-
const routeDistanceInUnit = routeDistanceInMeters != null ? DistanceRequestUtils.convertDistanceUnit(routeDistanceInMeters, distanceUnit) : undefined;
527-
528522
updateMoneyRequestDistance({
529523
transaction,
530524
transactionThreadReport: report,
531525
parentReport,
532526
waypoints,
533527
recentWaypoints,
534528
...(hasRouteChanged ? {routes: transaction?.routes} : {}),
535-
...(routeDistanceInUnit != null ? {distance: routeDistanceInUnit} : {}),
536529
policy,
537530
policyTagList: policyTags,
538531
policyCategories,
@@ -547,7 +540,7 @@ function IOURequestStepDistance({
547540
// Remove the backup eagerly so the parent report view reads the optimistic transaction
548541
// immediately, instead of the stale backup, while the API request is still in flight.
549542
removeBackupTransaction(transaction?.transactionID);
550-
navigateBack();
543+
navigateBackAfterSave();
551544
return;
552545
}
553546

@@ -561,13 +554,13 @@ function IOURequestStepDistance({
561554
isLoading,
562555
isCreatingNewRequest,
563556
navigateToNextStep,
557+
navigateBackAfterSave,
564558
isEditingSplit,
565559
originalSplitTransactionDraft,
566560
transactionBackup,
567561
waypoints,
568562
transaction,
569563
report,
570-
navigateBack,
571564
currentTransaction?.comment?.waypoints,
572565
currentTransaction?.routes,
573566
policy,
@@ -579,7 +572,6 @@ function IOURequestStepDistance({
579572
currentUserEmailParam,
580573
isASAPSubmitBetaEnabled,
581574
parentReportNextStep,
582-
distanceUnit,
583575
]);
584576

585577
const submitManualDistance = useCallback(() => {
@@ -599,7 +591,7 @@ function IOURequestStepDistance({
599591
if (isEditingSplit && transaction) {
600592
setMoneyRequestDistance(transactionID, distanceAsFloat, shouldUseTransactionDraft(action, iouType), distanceUnit);
601593
setDraftSplitTransaction(CONST.IOU.OPTIMISTIC_TRANSACTION_ID, splitDraftTransaction, {distance: distanceAsFloat}, policy);
602-
navigateBack();
594+
navigateBackAfterSave();
603595
return;
604596
}
605597

@@ -616,7 +608,7 @@ function IOURequestStepDistance({
616608

617609
if (!isDistanceChanged && !isDistanceUnitChanged && !haveWaypointsChanged) {
618610
transactionWasSaved.current = true;
619-
navigateBack();
611+
navigateBackAfterSave();
620612
return;
621613
}
622614

@@ -639,7 +631,7 @@ function IOURequestStepDistance({
639631
recentWaypoints,
640632
});
641633
transactionWasSaved.current = true;
642-
navigateBack();
634+
navigateBackAfterSave();
643635
}, [
644636
translate,
645637
distanceRate,
@@ -652,7 +644,7 @@ function IOURequestStepDistance({
652644
currentTransaction?.comment?.customUnit?.distanceUnit,
653645
splitDraftTransaction,
654646
policy,
655-
navigateBack,
647+
navigateBackAfterSave,
656648
currentDistance,
657649
waypoints,
658650
transactionBackup,
@@ -738,27 +730,23 @@ function IOURequestStepDistance({
738730
);
739731

740732
if (isEditing) {
741-
const hasRequiredData = !!currentTransaction?.comment?.waypoints && !shouldShowNotFoundPage;
742733
return (
743-
<ScreenWrapper
744-
shouldEnableMaxHeight={canUseTouchScreen()}
734+
<StepScreenWrapper
735+
headerTitle={translate('common.distance')}
736+
onBackButtonPress={navigateBack}
745737
testID="IOURequestStepDistance"
738+
shouldShowNotFoundPage={!currentTransaction?.comment?.waypoints || shouldShowNotFoundPage}
739+
shouldShowWrapper
746740
>
747-
<FullPageNotFoundView shouldShow={!hasRequiredData}>
748-
<HeaderWithBackButton
749-
title={translate('common.distance')}
750-
onBackButtonPress={navigateBack}
751-
/>
752-
<OnyxTabNavigator
753-
id={CONST.TAB.DISTANCE_EDIT_TYPE}
754-
defaultSelectedTab={CONST.TAB_REQUEST.DISTANCE_MAP}
755-
tabBar={TabSelector}
756-
>
757-
<TopTab.Screen name={CONST.TAB_REQUEST.DISTANCE_MAP}>{renderMapTab}</TopTab.Screen>
758-
<TopTab.Screen name={CONST.TAB_REQUEST.DISTANCE_MANUAL}>{renderManualTab}</TopTab.Screen>
759-
</OnyxTabNavigator>
760-
</FullPageNotFoundView>
761-
</ScreenWrapper>
741+
<OnyxTabNavigator
742+
id={CONST.TAB.DISTANCE_EDIT_TYPE}
743+
defaultSelectedTab={CONST.TAB_REQUEST.DISTANCE_MAP}
744+
tabBar={TabSelector}
745+
>
746+
<TopTab.Screen name={CONST.TAB_REQUEST.DISTANCE_MAP}>{renderMapTab}</TopTab.Screen>
747+
<TopTab.Screen name={CONST.TAB_REQUEST.DISTANCE_MANUAL}>{renderManualTab}</TopTab.Screen>
748+
</OnyxTabNavigator>
749+
</StepScreenWrapper>
762750
);
763751
}
764752

0 commit comments

Comments
 (0)