diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index 5c382ca8ee33..37d939e13868 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -70,6 +70,9 @@ type MoneyRequestViewPropsWithoutTransaction = MoneyRequestViewOnyxPropsWithoutT /** Whether we should display the horizontal rule below the component */ shouldShowHorizontalRule: boolean; + + /** Whether we should display the animated banner above the component */ + shouldShowAnimatedBackground: boolean; }; type MoneyRequestViewProps = MoneyRequestViewTransactionOnyxProps & MoneyRequestViewPropsWithoutTransaction; @@ -84,6 +87,7 @@ function MoneyRequestView({ policyTagList, policy, transactionViolations, + shouldShowAnimatedBackground, }: MoneyRequestViewProps) { const theme = useTheme(); const styles = useThemeStyles(); @@ -237,9 +241,9 @@ function MoneyRequestView({ ); return ( - - - + + {shouldShowAnimatedBackground && } + {/* eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing */} {(showMapAsImage || hasReceipt) && ( | Empty ); } +/** + * Returns the reportID for the transaction thread associated with a report by iterating over the reportActions and identifying the IOU report actions with a childReportID. Returns a reportID if there is exactly one transaction thread for the report, and null otherwise. + */ +function getOneTransactionThreadReportID(reportActions: OnyxEntry | ReportAction[]): string | null { + const reportActionsArray = Object.values(reportActions ?? {}); + + if (!reportActionsArray.length) { + return null; + } + + // Get all IOU report actions for the report. + const iouRequestTypes: Array> = [CONST.IOU.REPORT_ACTION_TYPE.CREATE, CONST.IOU.REPORT_ACTION_TYPE.SPLIT, CONST.IOU.REPORT_ACTION_TYPE.PAY]; + const iouRequestActions = reportActionsArray.filter( + (action) => + action.actionName === CONST.REPORT.ACTIONS.TYPE.IOU && + (iouRequestTypes.includes(action.originalMessage.type) ?? []) && + action.childReportID && + action.originalMessage.IOUTransactionID, + ); + + // If we don't have any IOU request actions, or we have more than one IOU request actions, this isn't a oneTransaction report + if (!iouRequestActions.length || iouRequestActions.length > 1) { + return null; + } + + // Ensure we have a childReportID associated with the IOU report action + return iouRequestActions[0].childReportID ?? null; +} + /** * Sort an array of reportActions by their created timestamp first, and reportActionID second * This gives us a stable order even in the case of multiple reportActions created on the same millisecond @@ -1034,6 +1063,7 @@ function getReportActionMessageText(reportAction: OnyxEntry | Empt export { extractLinksFromMessageHtml, + getOneTransactionThreadReportID, getAllReportActions, getIOUReportIDFromReportActionPreview, getLastClosedReportAction, diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index acf02a2f78f5..97e420062ece 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -1265,6 +1265,23 @@ function isMoneyRequestReport(reportOrID: OnyxEntry | EmptyObject | stri return isIOUReport(report) || isExpenseReport(report); } +/** + * Checks if a report has only one transaction associated with it + */ +function isOneTransactionReport(reportID: string): boolean { + const reportActions = reportActionsByReport?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`] ?? ([] as ReportAction[]); + return ReportActionsUtils.getOneTransactionThreadReportID(reportActions) !== null; +} + +/** + * Checks if a report is a transaction thread associated with a report that has only one transaction + */ +function isOneTransactionThread(reportID: string, parentReportID: string): boolean { + const parentReportActions = reportActionsByReport?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${parentReportID}`] ?? ([] as ReportAction[]); + const transactionThreadReportID = ReportActionsUtils.getOneTransactionThreadReportID(parentReportActions); + return reportID === transactionThreadReportID; +} + /** * Should return true only for personal 1:1 report * @@ -1795,6 +1812,11 @@ function getIcons( }; const isManager = currentUserAccountID === report?.managerID; + // For one transaction IOUs, display a simplified report icon + if (isOneTransactionReport(report?.reportID ?? '0')) { + return [ownerIcon]; + } + return isManager ? [managerIcon, ownerIcon] : [ownerIcon, managerIcon]; } @@ -4398,6 +4420,11 @@ function shouldReportBeInOptionList({ return false; } + // If this is a transaction thread associated with a report that only has one transaction, omit it + if (isOneTransactionThread(report.reportID, report.parentReportID ?? '0')) { + return false; + } + // Include the currently viewed report. If we excluded the currently viewed report, then there // would be no way to highlight it in the options list and it would be confusing to users because they lose // a sense of context. @@ -4868,6 +4895,10 @@ function shouldReportShowSubscript(report: OnyxEntry): boolean { return true; } + if (isExpenseReport(report) && isOneTransactionReport(report?.reportID ?? '')) { + return true; + } + if (isWorkspaceTaskReport(report)) { return true; } @@ -5798,6 +5829,7 @@ export { hasSingleParticipant, getReportRecipientAccountIDs, isOneOnOneChat, + isOneTransactionThread, isPayer, goBackToDetailsPage, getTransactionReportName, diff --git a/src/pages/home/ReportScreen.tsx b/src/pages/home/ReportScreen.tsx index ddff1f15c38f..f1f8eb8047c7 100644 --- a/src/pages/home/ReportScreen.tsx +++ b/src/pages/home/ReportScreen.tsx @@ -635,6 +635,7 @@ function ReportScreen({ isLoadingNewerReportActions={reportMetadata?.isLoadingNewerReportActions} isLoadingOlderReportActions={reportMetadata?.isLoadingOlderReportActions} isReadyForCommentLinking={!shouldShowSkeleton} + transactionThreadReportID={ReportActionsUtils.getOneTransactionThreadReportID(reportActions ?? [])} /> )} diff --git a/src/pages/home/report/ReportActionItem.tsx b/src/pages/home/report/ReportActionItem.tsx index 8e28ac6e3696..42787e8bfcfc 100644 --- a/src/pages/home/report/ReportActionItem.tsx +++ b/src/pages/home/report/ReportActionItem.tsx @@ -49,6 +49,7 @@ import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; import SelectionScraper from '@libs/SelectionScraper'; +import * as TransactionUtils from '@libs/TransactionUtils'; import {ReactionListContext} from '@pages/home/ReportScreenContext'; import * as BankAccounts from '@userActions/BankAccounts'; import * as EmojiPickerAction from '@userActions/EmojiPickerAction'; @@ -100,12 +101,22 @@ type ReportActionItemOnyxProps = { /** The policy which the user has access to and which the report is tied to */ policy: OnyxEntry; + + /** Transaction associated with this report, if any */ + transaction: OnyxEntry; }; type ReportActionItemProps = { /** Report for this action */ report: OnyxTypes.Report; + /** The transaction thread report associated with the report for this action, if any */ + transactionThreadReport: OnyxEntry; + + /** Array of report actions for the report for this action */ + // eslint-disable-next-line react/no-unused-prop-types + reportActions: OnyxTypes.ReportAction[]; + /** Report action belonging to the report's parent */ parentReportAction: OnyxEntry; @@ -142,6 +153,7 @@ const isIOUReport = (actionObj: OnyxEntry): actionObj is function ReportActionItem({ action, report, + transactionThreadReport, linkedReportActionID, displayAsGroup, emojiReactions, @@ -155,6 +167,7 @@ function ReportActionItem({ shouldHideThreadDividerLine = false, shouldShowSubscriptAvatar = false, policy, + transaction, onPress = undefined, }: ReportActionItemProps) { const {translate} = useLocalize(); @@ -180,7 +193,7 @@ function ReportActionItem({ const originalReportID = ReportUtils.getOriginalReportID(report.reportID, action); const originalReport = report.reportID === originalReportID ? report : ReportUtils.getReport(originalReportID); const isReportActionLinked = linkedReportActionID && action.reportActionID && linkedReportActionID === action.reportActionID; - + const transactionCurrency = TransactionUtils.getCurrency(transaction); const reportScrollManager = useReportScrollManager(); const highlightedBackgroundColorIfNeeded = useMemo( @@ -743,6 +756,7 @@ function ReportActionItem({ ); @@ -782,11 +796,30 @@ function ReportActionItem({ if (ReportUtils.isExpenseReport(report) || ReportUtils.isIOUReport(report)) { return ( - + {transactionThreadReport && !isEmptyObject(transactionThreadReport) ? ( + <> + {transactionCurrency !== report.currency && ( + + )} + + + + + ) : ( + + )} ); } @@ -947,6 +980,14 @@ export default withOnyx({ userWallet: { key: ONYXKEYS.USER_WALLET, }, + transaction: { + key: ({transactionThreadReport, reportActions}) => { + const parentReportActionID = isEmptyObject(transactionThreadReport) ? '0' : transactionThreadReport.parentReportActionID; + const action = reportActions?.find((reportAction) => reportAction.reportActionID === parentReportActionID); + const transactionID = (action as OnyxTypes.OriginalMessageIOU)?.originalMessage.IOUTransactionID ? (action as OnyxTypes.OriginalMessageIOU).originalMessage.IOUTransactionID : 0; + return `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`; + }, + }, })( memo(ReportActionItem, (prevProps, nextProps) => { const prevParentReportAction = prevProps.parentReportAction; @@ -978,6 +1019,9 @@ export default withOnyx({ prevProps.linkedReportActionID === nextProps.linkedReportActionID && lodashIsEqual(prevProps.report.fieldList, nextProps.report.fieldList) && lodashIsEqual(prevProps.policy, nextProps.policy) && + lodashIsEqual(prevProps.transactionThreadReport, nextProps.transactionThreadReport) && + lodashIsEqual(prevProps.reportActions, nextProps.reportActions) && + lodashIsEqual(prevProps.transaction, nextProps.transaction) && lodashIsEqual(prevParentReportAction, nextParentReportAction) ); }), diff --git a/src/pages/home/report/ReportActionItemParentAction.tsx b/src/pages/home/report/ReportActionItemParentAction.tsx index c6bc181bdbe7..3d98973c86c4 100644 --- a/src/pages/home/report/ReportActionItemParentAction.tsx +++ b/src/pages/home/report/ReportActionItemParentAction.tsx @@ -31,6 +31,12 @@ type ReportActionItemParentActionProps = { /** The current report is displayed */ report: OnyxEntry; + /** The transaction thread report associated with the current report, if any */ + transactionThreadReport: OnyxEntry; + + /** Array of report actions for this report */ + reportActions: OnyxTypes.ReportAction[]; + /** Report actions belonging to the report's parent */ parentReportAction: OnyxEntry; @@ -38,7 +44,15 @@ type ReportActionItemParentActionProps = { shouldDisplayReplyDivider: boolean; }; -function ReportActionItemParentAction({report, parentReportAction, index = 0, shouldHideThreadDividerLine = false, shouldDisplayReplyDivider}: ReportActionItemParentActionProps) { +function ReportActionItemParentAction({ + report, + transactionThreadReport, + reportActions, + parentReportAction, + index = 0, + shouldHideThreadDividerLine = false, + shouldDisplayReplyDivider, +}: ReportActionItemParentActionProps) { const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); const {isSmallScreenWidth} = useWindowDimensions(); @@ -92,6 +106,8 @@ function ReportActionItemParentAction({report, parentReportAction, index = 0, sh onPress={() => Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(ancestor.report.parentReportID ?? ''))} parentReportAction={parentReportAction} report={ancestor.report} + reportActions={reportActions} + transactionThreadReport={transactionThreadReport} action={ancestor.reportAction} displayAsGroup={false} isMostRecentIOUReportAction={false} diff --git a/src/pages/home/report/ReportActionsList.tsx b/src/pages/home/report/ReportActionsList.tsx index 3b001d859ed8..d1b9c420b0af 100644 --- a/src/pages/home/report/ReportActionsList.tsx +++ b/src/pages/home/report/ReportActionsList.tsx @@ -41,6 +41,12 @@ type ReportActionsListProps = WithCurrentUserPersonalDetailsProps & { /** The report currently being looked at */ report: OnyxTypes.Report; + /** The transaction thread report associated with the current report, if any */ + transactionThreadReport: OnyxEntry; + + /** Array of report actions for the current report */ + reportActions: OnyxTypes.ReportAction[]; + /** The report's parentReportAction */ parentReportAction: OnyxEntry; @@ -125,6 +131,8 @@ const onScrollToIndexFailed = () => {}; function ReportActionsList({ report, + transactionThreadReport, + reportActions = [], parentReportAction, isLoadingInitialReportActions = false, isLoadingOlderReportActions = false, @@ -514,9 +522,11 @@ function ReportActionsList({ ({item: reportAction, index}: ListRenderItemInfo) => ( ; @@ -20,6 +23,9 @@ type ReportActionsListItemRendererProps = { /** Report for this action */ report: Report; + /** The transaction thread report associated with the report for this action, if any */ + transactionThreadReport: OnyxEntry; + /** Should the comment have the appearance of being grouped with the previous comment? */ displayAsGroup: boolean; @@ -41,9 +47,11 @@ type ReportActionsListItemRendererProps = { function ReportActionsListItemRenderer({ reportAction, + reportActions = [], parentReportAction, index, report, + transactionThreadReport, displayAsGroup, mostRecentIOUReportActionID = '', shouldHideThreadDividerLine, @@ -127,6 +135,8 @@ function ReportActionsListItemRenderer({ parentReportAction={parentReportAction} reportID={report.reportID} report={report} + reportActions={reportActions} + transactionThreadReport={transactionThreadReport} index={index} /> ) : ( @@ -134,7 +144,9 @@ function ReportActionsListItemRenderer({ shouldHideThreadDividerLine={shouldHideThreadDividerLine} parentReportAction={parentReportAction} report={report} + transactionThreadReport={transactionThreadReport} action={action} + reportActions={reportActions} linkedReportActionID={linkedReportActionID} displayAsGroup={displayAsGroup} shouldDisplayNewMarker={shouldDisplayNewMarker} diff --git a/src/pages/home/report/ReportActionsView.tsx b/src/pages/home/report/ReportActionsView.tsx index e81bfa0fcb6d..1b6a9614a466 100755 --- a/src/pages/home/report/ReportActionsView.tsx +++ b/src/pages/home/report/ReportActionsView.tsx @@ -28,6 +28,7 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type SCREENS from '@src/SCREENS'; import type * as OnyxTypes from '@src/types/onyx'; +import {isEmptyObject} from '@src/types/utils/EmptyObject'; import getInitialPaginationSize from './getInitialPaginationSize'; import PopoverReactionList from './ReactionList/PopoverReactionList'; import ReportActionsList from './ReportActionsList'; @@ -35,6 +36,12 @@ import ReportActionsList from './ReportActionsList'; type ReportActionsViewOnyxProps = { /** Session info for the currently logged in user. */ session: OnyxEntry; + + /** Array of report actions for the transaction thread report associated with the current report */ + transactionThreadReportActions: OnyxTypes.ReportAction[]; + + /** The transaction thread report associated with the current report, if any */ + transactionThreadReport: OnyxEntry; }; type ReportActionsViewProps = ReportActionsViewOnyxProps & { @@ -58,6 +65,10 @@ type ReportActionsViewProps = ReportActionsViewOnyxProps & { /** Whether the report is ready for comment linking */ isReadyForCommentLinking?: boolean; + + /** The reportID of the transaction thread report associated with this current report, if any */ + // eslint-disable-next-line react/no-unused-prop-types + transactionThreadReportID?: string | null; }; const DIFF_BETWEEN_SCREEN_HEIGHT_AND_LIST = 120; @@ -67,9 +78,11 @@ let listOldID = Math.round(Math.random() * 100); function ReportActionsView({ report, + transactionThreadReport, session, parentReportAction, reportActions: allReportActions = [], + transactionThreadReportActions = [], isLoadingInitialReportActions = false, isLoadingOlderReportActions = false, isLoadingNewerReportActions = false, @@ -97,24 +110,7 @@ function ReportActionsView({ const prevIsSmallScreenWidthRef = useRef(isSmallScreenWidth); const reportID = report.reportID; const isLoading = (!!reportActionID && isLoadingInitialReportActions) || !isReadyForCommentLinking; - - /** - * Retrieves the next set of report actions for the chat once we are nearing the end of what we are currently - * displaying. - */ - const fetchNewerAction = useCallback( - (newestReportAction: OnyxTypes.ReportAction) => { - if (isLoadingNewerReportActions || isLoadingInitialReportActions) { - return; - } - - Report.getNewerActions(reportID, newestReportAction.reportActionID); - }, - [isLoadingNewerReportActions, isLoadingInitialReportActions, reportID], - ); - const isReportFullyVisible = useMemo((): boolean => getIsReportFullyVisible(isFocused), [isFocused]); - const openReportIfNecessary = () => { if (!shouldFetchReport(report)) { return; @@ -148,32 +144,85 @@ function ReportActionsView({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [route, isLoadingInitialReportActions]); + // Get a sorted array of reportActions for both the current report and the transaction thread report associated with this report (if there is one) + // so that we display transaction-level and report-level report actions in order in the one-transaction view + const combinedReportActions = useMemo(() => { + if (isEmptyObject(transactionThreadReportActions)) { + return allReportActions; + } + + // Filter out the created action from the transaction thread report actions, since we already have the parent report's created action in `reportActions` + const filteredTransactionThreadReportActions = transactionThreadReportActions?.filter((action) => action.actionName !== CONST.REPORT.ACTIONS.TYPE.CREATED); + + // Filter out "created" IOU report actions because we don't want to show any preview actions for one transaction reports + const filteredReportActions = [...allReportActions, ...filteredTransactionThreadReportActions].filter( + (action) => ((action as OnyxTypes.OriginalMessageIOU).originalMessage?.type ?? '') !== CONST.IOU.REPORT_ACTION_TYPE.CREATE, + ); + return ReportActionsUtils.getSortedReportActions(filteredReportActions, true); + }, [allReportActions, transactionThreadReportActions]); + const indexOfLinkedAction = useMemo(() => { if (!reportActionID || isLoading) { return -1; } - return allReportActions.findIndex((obj) => String(obj.reportActionID) === String(isFirstLinkedActionRender.current ? reportActionID : currentReportActionID)); - }, [allReportActions, currentReportActionID, reportActionID, isLoading]); + return combinedReportActions.findIndex((obj) => String(obj.reportActionID) === String(isFirstLinkedActionRender.current ? reportActionID : currentReportActionID)); + }, [combinedReportActions, currentReportActionID, reportActionID, isLoading]); const reportActions = useMemo(() => { if (!reportActionID) { - return allReportActions; + return combinedReportActions; } + if (isLoading || indexOfLinkedAction === -1) { return []; } if (isFirstLinkedActionRender.current) { - return allReportActions.slice(indexOfLinkedAction); + return combinedReportActions.slice(indexOfLinkedAction); } const paginationSize = getInitialPaginationSize; - return allReportActions.slice(Math.max(indexOfLinkedAction - paginationSize, 0)); + return combinedReportActions.slice(Math.max(indexOfLinkedAction - paginationSize, 0)); + // currentReportActionID is needed to trigger batching once the report action has been positioned // eslint-disable-next-line react-hooks/exhaustive-deps - }, [reportActionID, allReportActions, indexOfLinkedAction, isLoading, currentReportActionID]); + }, [reportActionID, combinedReportActions, indexOfLinkedAction, isLoading, currentReportActionID]); - const hasMoreCached = reportActions.length < allReportActions.length; + const reportActionIDMap = useMemo(() => { + const reportActionIDs = allReportActions.map((action) => action.reportActionID); + return reportActions.map((action) => ({ + reportActionID: action.reportActionID, + reportID: reportActionIDs.includes(action.reportActionID) ? reportID : transactionThreadReport?.reportID, + })); + }, [allReportActions, reportID, transactionThreadReport, reportActions]); + + /** + * Retrieves the next set of report actions for the chat once we are nearing the end of what we are currently + * displaying. + */ + const fetchNewerAction = useCallback( + (newestReportAction: OnyxTypes.ReportAction) => { + if (isLoadingNewerReportActions || isLoadingInitialReportActions) { + return; + } + + // If this is a one transaction report, ensure we load newer actions for both this report and the report associated with the transaction + if (!isEmptyObject(transactionThreadReport)) { + // Get newer actions based on the newest reportAction for the current report + const newestActionCurrentReport = reportActionIDMap.find((item) => item.reportID === reportID); + Report.getNewerActions(newestActionCurrentReport?.reportID ?? '0', newestActionCurrentReport?.reportActionID ?? '0'); + + // Get newer actions based on the newest reportAction for the transaction thread report + const newestActionTransactionThreadReport = reportActionIDMap.find((item) => item.reportID === transactionThreadReport.reportID); + Report.getNewerActions(newestActionTransactionThreadReport?.reportID ?? '0', newestActionTransactionThreadReport?.reportActionID ?? '0'); + } else { + Report.getNewerActions(reportID, newestReportAction.reportActionID); + } + }, + [isLoadingNewerReportActions, isLoadingInitialReportActions, reportID, transactionThreadReport, reportActionIDMap], + ); + + const hasMoreCached = reportActions.length < combinedReportActions.length; const newestReportAction = useMemo(() => reportActions?.[0], [reportActions]); const handleReportActionPagination = useCallback( ({firstReportActionID}: {firstReportActionID: string}) => { @@ -192,8 +241,7 @@ function ReportActionsView({ const mostRecentIOUReportActionID = useMemo(() => ReportActionsUtils.getMostRecentIOURequestActionID(reportActions), [reportActions]); const hasCachedActionOnFirstRender = useInitialValue(() => reportActions.length > 0); - const hasNewestReportAction = reportActions[0]?.created === report.lastVisibleActionCreated; - + const hasNewestReportAction = reportActions[0]?.created === report.lastVisibleActionCreated || reportActions[0]?.created === transactionThreadReport?.lastVisibleActionCreated; const oldestReportAction = useMemo(() => reportActions?.at(-1), [reportActions]); const hasCreatedAction = oldestReportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.CREATED; @@ -312,9 +360,20 @@ function ReportActionsView({ if (!oldestReportAction || hasCreatedAction) { return; } - // Retrieve the next REPORT.ACTIONS.LIMIT sized page of comments - Report.getOlderActions(reportID, oldestReportAction.reportActionID); - }, [network.isOffline, isLoadingOlderReportActions, isLoadingInitialReportActions, oldestReportAction, hasCreatedAction, reportID]); + + if (!isEmptyObject(transactionThreadReport)) { + // Get newer actions based on the newest reportAction for the current report + const oldestActionCurrentReport = reportActionIDMap.findLast((item) => item.reportID === reportID); + Report.getNewerActions(oldestActionCurrentReport?.reportID ?? '0', oldestActionCurrentReport?.reportActionID ?? '0'); + + // Get newer actions based on the newest reportAction for the transaction thread report + const oldestActionTransactionThreadReport = reportActionIDMap.findLast((item) => item.reportID === transactionThreadReport.reportID); + Report.getNewerActions(oldestActionTransactionThreadReport?.reportID ?? '0', oldestActionTransactionThreadReport?.reportActionID ?? '0'); + } else { + // Retrieve the next REPORT.ACTIONS.LIMIT sized page of comments + Report.getOlderActions(reportID, oldestReportAction.reportActionID); + } + }, [network.isOffline, isLoadingOlderReportActions, isLoadingInitialReportActions, oldestReportAction, hasCreatedAction, reportID, reportActionIDMap, transactionThreadReport]); const loadNewerChats = useCallback(() => { if (isLoadingInitialReportActions || isLoadingOlderReportActions || network.isOffline || newestReportAction.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE) { @@ -482,6 +541,8 @@ function ReportActionsView({ <> rendering'} session: { key: ONYXKEYS.SESSION, }, + transactionThreadReportActions: { + key: ({transactionThreadReportID}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${transactionThreadReportID}`, + canEvict: false, + selector: (reportActions: OnyxEntry) => ReportActionsUtils.getSortedReportActionsForDisplay(reportActions, true), + }, + transactionThreadReport: { + key: ({transactionThreadReportID}) => `${ONYXKEYS.COLLECTION.REPORT}${transactionThreadReportID}`, + }, })(MemoizedReportActionsView), ); diff --git a/src/pages/reportPropTypes.js b/src/pages/reportPropTypes.js index 3a056ee7c0a3..7422bad8061f 100644 --- a/src/pages/reportPropTypes.js +++ b/src/pages/reportPropTypes.js @@ -73,4 +73,7 @@ export default PropTypes.shape({ /** Custom fields attached to the report */ reportFields: PropTypes.objectOf(PropTypes.string), + + /** ID of the transaction thread associated with the report, if any */ + transactionThreadReportID: PropTypes.string, }); diff --git a/src/styles/utils/index.ts b/src/styles/utils/index.ts index b547f28137b3..a3357b8982a1 100644 --- a/src/styles/utils/index.ts +++ b/src/styles/utils/index.ts @@ -818,21 +818,18 @@ function getLineHeightStyle(lineHeight: number): TextStyle { /** * Gets the correct size for the empty state container based on screen dimensions */ -function getReportWelcomeContainerStyle(isSmallScreenWidth: boolean, isMoneyOrTaskReport = false): ViewStyle { +function getReportWelcomeContainerStyle(isSmallScreenWidth: boolean, isMoneyOrTaskReport = false, shouldShowAnimatedBackground = true): ViewStyle { const emptyStateBackground = isMoneyOrTaskReport ? CONST.EMPTY_STATE_BACKGROUND.MONEY_OR_TASK_REPORT : CONST.EMPTY_STATE_BACKGROUND; - if (isSmallScreenWidth) { - return { - minHeight: emptyStateBackground.SMALL_SCREEN.CONTAINER_MINHEIGHT, - display: 'flex', - justifyContent: 'space-between', - }; - } - - return { - minHeight: emptyStateBackground.WIDE_SCREEN.CONTAINER_MINHEIGHT, + const baseStyles: ViewStyle = { display: 'flex', justifyContent: 'space-between', }; + + if (shouldShowAnimatedBackground) { + baseStyles.minHeight = isSmallScreenWidth ? emptyStateBackground.SMALL_SCREEN.CONTAINER_MINHEIGHT : emptyStateBackground.WIDE_SCREEN.CONTAINER_MINHEIGHT; + } + + return baseStyles; } type GetBaseAutoCompleteSuggestionContainerStyleParams = { diff --git a/src/types/onyx/Report.ts b/src/types/onyx/Report.ts index 0893fde82c73..ce20462df372 100644 --- a/src/types/onyx/Report.ts +++ b/src/types/onyx/Report.ts @@ -183,7 +183,9 @@ type Report = OnyxCommon.OnyxValueWithOfflineFeedback< /** Pending members of the report */ pendingChatMembers?: PendingChatMember[]; - /** If the report contains reportFields, save the field id and its value */ + /** The ID of the single transaction thread report associated with this report, if one exists */ + transactionThreadReportID?: string; + fieldList?: Record; }, PolicyReportField['fieldID'] diff --git a/tests/actions/EnforceActionExportRestrictions.ts b/tests/actions/EnforceActionExportRestrictions.ts index 92d96869d02d..f9f77b314476 100644 --- a/tests/actions/EnforceActionExportRestrictions.ts +++ b/tests/actions/EnforceActionExportRestrictions.ts @@ -10,6 +10,10 @@ describe('ReportUtils', () => { // @ts-expect-error the test is asserting that it's undefined, so the TS error is normal expect(ReportUtils.getParentReport).toBeUndefined(); }); + it('does not export isOneTransactionReport', () => { + // @ts-expect-error the test is asserting that it's undefined, so the TS error is normal + expect(ReportUtils.isOneTransactionReport).toBeUndefined(); + }); }); describe('Task', () => { diff --git a/tests/perf-test/ReportActionsList.perf-test.tsx b/tests/perf-test/ReportActionsList.perf-test.tsx index 012d8dc8b90f..a952d149a598 100644 --- a/tests/perf-test/ReportActionsList.perf-test.tsx +++ b/tests/perf-test/ReportActionsList.perf-test.tsx @@ -103,6 +103,8 @@ function ReportActionsListWrapper() { listID={1} loadOlderChats={mockLoadChats} loadNewerChats={mockLoadChats} + transactionThreadReport={LHNTestUtilsModule.getFakeReport()} + reportActions={ReportTestUtils.getMockedSortedReportActions(500)} />