Skip to content
Open
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
46 changes: 46 additions & 0 deletions src/components/ReportActionItem/MoneyRequestView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@ import {Str} from 'expensify-common';
import React, {useState} from 'react';
import {View} from 'react-native';
import type {OnyxCollection, OnyxEntry} from 'react-native-onyx';
// Use the original useOnyx hook to scan the live report collection when resolving the trip room.
// @hooks/useOnyx redirects collection reads to the search snapshot under Search routes, which
// typically does not include the grandparent trip room and would leave the Trip row hidden.
// eslint-disable-next-line no-restricted-imports
import {useOnyx as originalUseOnyx} from 'react-native-onyx';
import type {ValueOf} from 'type-fest';
import DotIndicatorMessage from '@components/DotIndicatorMessage';
import Icon from '@components/Icon';
Expand Down Expand Up @@ -455,6 +460,32 @@ function MoneyRequestView({
const tripID = getTripIDFromTransactionParentReportID(parentReport?.parentReportID);
const shouldShowViewTripDetails = hasReservationList(transaction) && !!tripID;

const transactionTripID = transaction?.comment?.tripID;

// Spotnana expense reports are parented under the trip room, so try that O(1) hop before scanning.
// Use originalUseOnyx so the lookup hits the live report collection - the grandparent trip room
// is typically not in the Search snapshot.
const grandparentReportID = parentReport?.parentReportID;
const tripRoomReportSelector = (reports: OnyxCollection<OnyxTypes.Report>) => {
if (!transactionTripID || !reports) {
return undefined;
}
const grandparent = grandparentReportID ? reports[`${ONYXKEYS.COLLECTION.REPORT}${grandparentReportID}`] : undefined;
const match =
grandparent?.tripData?.tripID === transactionTripID ? grandparent : Object.values(reports).find((candidateReport) => candidateReport?.tripData?.tripID === transactionTripID);
if (!match?.reportID) {
return undefined;
}
return {
reportID: match.reportID,
name: getReportName(match, reportAttributes) || match.reportName,
};
};
const [tripRoomInfo] = originalUseOnyx(ONYXKEYS.COLLECTION.REPORT, {selector: tripRoomReportSelector}, [transactionTripID, grandparentReportID, reportAttributes]);
const tripRoomReportID = tripRoomInfo?.reportID;
const tripRoomName = tripRoomInfo?.name;
const shouldShowTripRoomLink = !!tripRoomReportID && !!tripRoomName;

const {getViolationsForField} = useViolations(transactionViolations ?? [], isTransactionScanning || !isPaidGroupPolicy(transactionThreadReport));
const hasViolations = (field: ViolationField, data?: OnyxTypes.TransactionViolation['data'], policyHasDependentTags = false, tagValue?: string): boolean =>
getViolationsForField(field, data, policyHasDependentTags, tagValue).length > 0;
Expand Down Expand Up @@ -1341,6 +1372,21 @@ function MoneyRequestView({
/>
</OfflineWithFeedback>
)}
{shouldShowTripRoomLink && (
<>
<MenuItemWithTopDescription
title={tripRoomName}
description={translate('travel.trip')}
style={[styles.moneyRequestMenuItem]}
titleStyle={[styles.flex1, styles.textBlue]}
onPress={() => {
Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(tripRoomReportID, undefined, undefined, Navigation.getActiveRoute()));
}}
interactive
/>
<View style={styles.reportHorizontalRule} />
</>
)}
{/* Note: "View trip details" should be always the last item */}
{shouldShowViewTripDetails && (
<MenuItem
Expand Down
2 changes: 2 additions & 0 deletions src/libs/DebugUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1072,6 +1072,7 @@ function validateTransactionDraftProperty(key: keyof Transaction, value: string)
odometerEnd: CONST.RED_BRICK_ROAD_PENDING_ACTION,
odometerStartImage: CONST.RED_BRICK_ROAD_PENDING_ACTION,
odometerEndImage: CONST.RED_BRICK_ROAD_PENDING_ACTION,
tripID: CONST.RED_BRICK_ROAD_PENDING_ACTION,
attendees: CONST.RED_BRICK_ROAD_PENDING_ACTION,
amount: CONST.RED_BRICK_ROAD_PENDING_ACTION,
taxAmount: CONST.RED_BRICK_ROAD_PENDING_ACTION,
Expand Down Expand Up @@ -1194,6 +1195,7 @@ function validateTransactionDraftProperty(key: keyof Transaction, value: string)
odometerEnd: 'number',
odometerStartImage: 'object',
odometerEndImage: 'object',
tripID: 'string',
});
case 'accountant':
return validateObject<ObjectElement<Transaction, 'accountant'>>(value, {
Expand Down
3 changes: 3 additions & 0 deletions src/types/onyx/Transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,9 @@ type Comment = {

/** Odometer end image (File object with uri on web, URI string on native) */
odometerEndImage?: FileObject | string;

/** Spotnana trip ID, set on travel transactions and used to link the expense to its trip room */
tripID?: string;
};

/** Model of transaction custom unit */
Expand Down
Loading