Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix - Distance- Waypoint is saved when distance editor RHP is refreshed and dismissed without saving #54475

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 15 additions & 4 deletions src/libs/actions/TransactionEdit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ let connection: Connection;
/**
* Makes a backup copy of a transaction object that can be restored when the user cancels editing a transaction.
*/
function createBackupTransaction(transaction: OnyxEntry<Transaction>) {
function createBackupTransaction(transaction: OnyxEntry<Transaction>, isDraft: boolean) {
if (!transaction) {
return;
}
Expand All @@ -20,9 +20,20 @@ function createBackupTransaction(transaction: OnyxEntry<Transaction>) {
const newTransaction = {
...transaction,
};

// Use set so that it will always fully overwrite any backup transaction that could have existed before
Onyx.set(`${ONYXKEYS.COLLECTION.TRANSACTION_BACKUP}${transaction.transactionID}`, newTransaction);
const conn = Onyx.connect({
key: `${ONYXKEYS.COLLECTION.TRANSACTION_BACKUP}${transaction.transactionID}`,
callback: (transactionBackup) => {
Onyx.disconnect(conn);
if (transactionBackup) {
// If the transactionBackup exists it means we haven't properly restored original value on unmount
// such as on page refresh, so we will just restore the transaction from the transactionBackup here.
Onyx.set(`${isDraft ? ONYXKEYS.COLLECTION.TRANSACTION_DRAFT : ONYXKEYS.COLLECTION.TRANSACTION}${transaction.transactionID}`, transactionBackup);
return;
}
// Use set so that it will always fully overwrite any backup transaction that could have existed before
Onyx.set(`${ONYXKEYS.COLLECTION.TRANSACTION_BACKUP}${transaction.transactionID}`, newTransaction);
},
});
}

/**
Expand Down
108 changes: 59 additions & 49 deletions src/pages/iou/request/step/IOURequestStepDistance.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,29 +20,40 @@ import useNetwork from '@hooks/useNetwork';
import usePolicy from '@hooks/usePolicy';
import usePrevious from '@hooks/usePrevious';
import useThemeStyles from '@hooks/useThemeStyles';
import * as Report from '@libs/actions/Report';
import {
createDistanceRequest,
getIOURequestPolicyID,
resetSplitShares,
setMoneyRequestAmount,
setMoneyRequestMerchant,
setMoneyRequestParticipantsFromReport,
setMoneyRequestPendingFields,
setSplitShares,
trackExpense,
updateMoneyRequestDistance,
} from '@libs/actions/IOU';
import {init, stop} from '@libs/actions/MapboxToken';
import {openReport} from '@libs/actions/Report';
import {openDraftDistanceExpense, removeWaypoint, updateWaypoints as updateWaypointsUtil} from '@libs/actions/Transaction';
import {createBackupTransaction, removeBackupTransaction, restoreOriginalTransactionFromBackup} from '@libs/actions/TransactionEdit';
import DistanceRequestUtils from '@libs/DistanceRequestUtils';
import type {MileageRate} from '@libs/DistanceRequestUtils';
import * as ErrorUtils from '@libs/ErrorUtils';
import * as IOUUtils from '@libs/IOUUtils';
import {getLatestErrorField} from '@libs/ErrorUtils';
import {shouldUseTransactionDraft} from '@libs/IOUUtils';
import Navigation from '@libs/Navigation/Navigation';
import * as OptionsListUtils from '@libs/OptionsListUtils';
import * as PolicyUtils from '@libs/PolicyUtils';
import * as ReportUtils from '@libs/ReportUtils';
import {getParticipantsOption, getReportOption} from '@libs/OptionsListUtils';
import {getPersonalPolicy, getPolicy} from '@libs/PolicyUtils';
import {isArchivedReport, isPolicyExpenseChat as isPolicyExpenseChatUtil} from '@libs/ReportUtils';
import playSound, {SOUNDS} from '@libs/Sound';
import * as TransactionUtils from '@libs/TransactionUtils';
import * as IOU from '@userActions/IOU';
import * as MapboxToken from '@userActions/MapboxToken';
import * as TransactionAction from '@userActions/Transaction';
import * as TransactionEdit from '@userActions/TransactionEdit';
import {getDistanceInMeters, getRateID, getRequestType, getValidWaypoints, isCustomUnitRateIDForP2P} from '@libs/TransactionUtils';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import type SCREENS from '@src/SCREENS';
import type * as OnyxTypes from '@src/types/onyx';
import type {Participant} from '@src/types/onyx/IOU';
import type {Errors} from '@src/types/onyx/OnyxCommon';
import type {Waypoint, WaypointCollection} from '@src/types/onyx/Transaction';
import type Transaction from '@src/types/onyx/Transaction';
import StepScreenWrapper from './StepScreenWrapper';
import withFullTransactionOrNotFound from './withFullTransactionOrNotFound';
import type {WithWritableReportOrNotFoundProps} from './withWritableReportOrNotFound';
Expand All @@ -51,7 +62,7 @@ import withWritableReportOrNotFound from './withWritableReportOrNotFound';
type IOURequestStepDistanceProps = WithCurrentUserPersonalDetailsProps &
WithWritableReportOrNotFoundProps<typeof SCREENS.MONEY_REQUEST.STEP_DISTANCE | typeof SCREENS.MONEY_REQUEST.CREATE> & {
/** The transaction object being modified in Onyx */
transaction: OnyxEntry<OnyxTypes.Transaction>;
transaction: OnyxEntry<Transaction>;
};

function IOURequestStepDistance({
Expand Down Expand Up @@ -91,7 +102,7 @@ function IOURequestStepDistance({
transaction,
waypoints,
action,
IOUUtils.shouldUseTransactionDraft(action) ? CONST.TRANSACTION.STATE.DRAFT : CONST.TRANSACTION.STATE.CURRENT,
shouldUseTransactionDraft(action) ? CONST.TRANSACTION.STATE.DRAFT : CONST.TRANSACTION.STATE.CURRENT,
);
const waypointsList = Object.keys(waypoints);
const previousWaypoints = usePrevious(waypoints);
Expand Down Expand Up @@ -121,8 +132,8 @@ function IOURequestStepDistance({
const transactionWasSaved = useRef(false);
const isCreatingNewRequest = !(backTo || isEditing);
const [recentWaypoints, {status: recentWaypointsStatus}] = useOnyx(ONYXKEYS.NVP_RECENT_WAYPOINTS);
const iouRequestType = TransactionUtils.getRequestType(transaction);
const customUnitRateID = TransactionUtils.getRateID(transaction);
const iouRequestType = getRequestType(transaction);
const customUnitRateID = getRateID(transaction);

// Sets `amount` and `split` share data before moving to the next step to avoid briefly showing `0.00` as the split share for participants
const setDistanceRequestData = useCallback(
Expand All @@ -132,26 +143,26 @@ function IOURequestStepDistance({
const selectedReportID = participants?.length === 1 ? participants.at(0)?.reportID ?? reportID : reportID;
const policyReport = participants.at(0) ? allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${selectedReportID}`] : report;

const IOUpolicyID = IOU.getIOURequestPolicyID(transaction, policyReport);
const IOUpolicy = PolicyUtils.getPolicy(report?.policyID ?? IOUpolicyID);
const policyCurrency = policy?.outputCurrency ?? PolicyUtils.getPersonalPolicy()?.outputCurrency ?? CONST.CURRENCY.USD;
const IOUpolicyID = getIOURequestPolicyID(transaction, policyReport);
const IOUpolicy = getPolicy(report?.policyID ?? IOUpolicyID);
const policyCurrency = policy?.outputCurrency ?? getPersonalPolicy()?.outputCurrency ?? CONST.CURRENCY.USD;

const mileageRates = DistanceRequestUtils.getMileageRates(IOUpolicy);
const defaultMileageRate = DistanceRequestUtils.getDefaultMileageRate(IOUpolicy);
const mileageRate: MileageRate | undefined = TransactionUtils.isCustomUnitRateIDForP2P(transaction)
const mileageRate: MileageRate | undefined = isCustomUnitRateIDForP2P(transaction)
? DistanceRequestUtils.getRateForP2P(policyCurrency, transaction)
: // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
(customUnitRateID && mileageRates?.[customUnitRateID]) || defaultMileageRate;

const {unit, rate} = mileageRate ?? {};
const distance = TransactionUtils.getDistanceInMeters(transaction, unit);
const distance = getDistanceInMeters(transaction, unit);
const currency = mileageRate?.currency ?? policyCurrency;
const amount = DistanceRequestUtils.getDistanceRequestAmount(distance, unit ?? CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES, rate ?? 0);
IOU.setMoneyRequestAmount(transactionID, amount, currency);
setMoneyRequestAmount(transactionID, amount, currency);

const participantAccountIDs: number[] | undefined = participants?.map((participant) => Number(participant.accountID ?? CONST.DEFAULT_NUMBER_ID));
if (isSplitRequest && amount && currency && !isPolicyExpenseChat) {
IOU.setSplitShares(transaction, amount, currency ?? '', participantAccountIDs ?? []);
setSplitShares(transaction, amount, currency ?? '', participantAccountIDs ?? []);
}
},
[report, allReports, transaction, transactionID, isSplitRequest, policy?.outputCurrency, reportID, customUnitRateID],
Expand All @@ -166,8 +177,8 @@ function IOURequestStepDistance({

return (
iouType !== CONST.IOU.TYPE.SPLIT &&
!ReportUtils.isArchivedReport(report, reportNameValuePairs) &&
!(ReportUtils.isPolicyExpenseChat(report) && ((policy?.requiresCategory ?? false) || (policy?.requiresTag ?? false)))
!isArchivedReport(report, reportNameValuePairs) &&
!(isPolicyExpenseChatUtil(report) && ((policy?.requiresCategory ?? false) || (policy?.requiresTag ?? false)))
);
}, [report, skipConfirmation, policy, reportNameValuePairs, iouType]);
let buttonText = !isCreatingNewRequest ? translate('common.save') : translate('common.next');
Expand All @@ -188,12 +199,12 @@ function IOURequestStepDistance({

// Only load the recent waypoints if they have been read from Onyx as undefined
// If the account doesn't have recent waypoints they will be returned as an empty array
TransactionAction.openDraftDistanceExpense();
openDraftDistanceExpense();
}, [iouRequestType, recentWaypointsStatus, recentWaypoints, isOffline]);

useEffect(() => {
MapboxToken.init();
return MapboxToken.stop;
init();
return stop;
}, []);

useEffect(() => {
Expand All @@ -218,24 +229,24 @@ function IOURequestStepDistance({
if (isCreatingNewRequest) {
return () => {};
}

const isDraft = shouldUseTransactionDraft(action);
// On mount, create the backup transaction.
TransactionEdit.createBackupTransaction(transaction);
createBackupTransaction(transaction, isDraft);

return () => {
// If the user cancels out of the modal without without saving changes, then the original transaction
// needs to be restored from the backup so that all changes are removed.
if (transactionWasSaved.current) {
TransactionEdit.removeBackupTransaction(transaction?.transactionID);
removeBackupTransaction(transaction?.transactionID);
return;
}
TransactionEdit.restoreOriginalTransactionFromBackup(transaction?.transactionID, IOUUtils.shouldUseTransactionDraft(action));
restoreOriginalTransactionFromBackup(transaction?.transactionID, isDraft);

// If the user opens IOURequestStepDistance in offline mode and then goes online, re-open the report to fill in missing fields from the transaction backup
if (!transaction?.reportID) {
return;
}
Report.openReport(transaction?.reportID);
openReport(transaction?.reportID);
};
// eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps
}, []);
Expand Down Expand Up @@ -285,7 +296,7 @@ function IOURequestStepDistance({

const navigateToNextStep = useCallback(() => {
if (transaction?.splitShares) {
IOU.resetSplitShares(transaction);
resetSplitShares(transaction);
}
if (backTo) {
Navigation.goBack(backTo);
Expand All @@ -298,20 +309,20 @@ function IOURequestStepDistance({
// In this case, the participants can be automatically assigned from the report and the user can skip the participants step and go straight
// to the confirm step.
// If the user started this flow using the Create expense option (combined submit/track flow), they should be redirected to the participants page.
if (report?.reportID && !ReportUtils.isArchivedReport(report, reportNameValuePairs) && iouType !== CONST.IOU.TYPE.CREATE) {
const selectedParticipants = IOU.setMoneyRequestParticipantsFromReport(transactionID, report);
if (report?.reportID && !isArchivedReport(report, reportNameValuePairs) && iouType !== CONST.IOU.TYPE.CREATE) {
const selectedParticipants = setMoneyRequestParticipantsFromReport(transactionID, report);
const participants = selectedParticipants.map((participant) => {
const participantAccountID = participant?.accountID ?? CONST.DEFAULT_NUMBER_ID;
return participantAccountID ? OptionsListUtils.getParticipantsOption(participant, personalDetails) : OptionsListUtils.getReportOption(participant);
return participantAccountID ? getParticipantsOption(participant, personalDetails) : getReportOption(participant);
});
setDistanceRequestData(participants);
if (shouldSkipConfirmation) {
IOU.setMoneyRequestPendingFields(transactionID, {waypoints: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD});
IOU.setMoneyRequestMerchant(transactionID, translate('iou.fieldPending'), false);
setMoneyRequestPendingFields(transactionID, {waypoints: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD});
setMoneyRequestMerchant(transactionID, translate('iou.fieldPending'), false);
const participant = participants.at(0);
if (iouType === CONST.IOU.TYPE.TRACK && participant) {
playSound(SOUNDS.DONE);
IOU.trackExpense(
trackExpense(
report,
0,
transaction?.currency ?? 'USD',
Expand All @@ -332,7 +343,7 @@ function IOURequestStepDistance({
undefined,
undefined,
undefined,
TransactionUtils.getValidWaypoints(waypoints, true),
getValidWaypoints(waypoints, true),
undefined,
undefined,
undefined,
Expand All @@ -343,7 +354,7 @@ function IOURequestStepDistance({
}

playSound(SOUNDS.DONE);
IOU.createDistanceRequest({
createDistanceRequest({
report,
participants,
currentUserLogin: currentUserPersonalDetails.login,
Expand All @@ -357,14 +368,14 @@ function IOURequestStepDistance({
currency: transaction?.currency ?? 'USD',
merchant: translate('iou.fieldPending'),
billable: !!policy?.defaultBillable,
validWaypoints: TransactionUtils.getValidWaypoints(waypoints, true),
validWaypoints: getValidWaypoints(waypoints, true),
customUnitRateID: DistanceRequestUtils.getCustomUnitRateID(report.reportID),
splitShares: transaction?.splitShares,
},
});
return;
}
IOU.setMoneyRequestParticipantsFromReport(transactionID, report);
setMoneyRequestParticipantsFromReport(transactionID, report);
navigateToConfirmationPage();
return;
}
Expand Down Expand Up @@ -394,7 +405,7 @@ function IOURequestStepDistance({
const getError = () => {
// Get route error if available else show the invalid number of waypoints error.
if (hasRouteError) {
return ErrorUtils.getLatestErrorField(transaction, 'route');
return getLatestErrorField(transaction, 'route');
}
if (duplicateWaypointsError) {
return {duplicateWaypointsError: translate('iou.error.duplicateWaypointsErrorMessage')} as Errors;
Expand Down Expand Up @@ -427,8 +438,8 @@ function IOURequestStepDistance({

setOptimisticWaypoints(newWaypoints);
Promise.all([
TransactionAction.removeWaypoint(transaction, emptyWaypointIndex.toString(), IOUUtils.shouldUseTransactionDraft(action)),
TransactionAction.updateWaypoints(transactionID, newWaypoints, IOUUtils.shouldUseTransactionDraft(action)),
removeWaypoint(transaction, emptyWaypointIndex.toString(), shouldUseTransactionDraft(action)),
updateWaypointsUtil(transactionID, newWaypoints, shouldUseTransactionDraft(action)),
]).then(() => {
setOptimisticWaypoints(null);
});
Expand Down Expand Up @@ -456,9 +467,8 @@ function IOURequestStepDistance({
navigateBack();
return;
}

if (transaction?.transactionID && report?.reportID) {
IOU.updateMoneyRequestDistance({
updateMoneyRequestDistance({
transactionID: transaction?.transactionID,
transactionThreadReportID: report?.reportID,
waypoints,
Expand Down
Loading