Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
e207f04
feat: Standardize Expense Report and Expense header
thelullabyy May 27, 2026
2ff6f59
fix: carousel issue
thelullabyy May 27, 2026
a6ce54d
fix: lint check
thelullabyy May 27, 2026
0edd5c2
Merge branch 'main' of https://github.com/Expensify/App into fix-lull…
thelullabyy May 29, 2026
ee93d35
resolved conflicts
thelullabyy Jun 1, 2026
5734431
fix: conflicts
thelullabyy Jun 1, 2026
050cc33
Merge branch 'main' of https://github.com/Expensify/App into fix-lull…
thelullabyy Jun 4, 2026
fc2feef
fix: update to use snapshot data for carousel
thelullabyy Jun 4, 2026
e1e361e
merge main
thelullabyy Jun 8, 2026
0685dca
Merge branch 'main' of https://github.com/Expensify/App into fix-lull…
thelullabyy Jun 11, 2026
76b4167
fix: carousel
thelullabyy Jun 11, 2026
08f0836
merge main
thelullabyy Jun 18, 2026
c76524b
Merge branch 'main' of https://github.com/Expensify/App into fix-lull…
thelullabyy Jun 18, 2026
9b6e3f4
fix: carousel issue
thelullabyy Jun 18, 2026
257b7fb
resolve conflicts
thelullabyy Jun 29, 2026
5eab122
merge main
thelullabyy Jul 1, 2026
901e83f
refactor: streamline parent report actions collection in MoneyRequest…
thelullabyy Jul 1, 2026
9910c5a
feat: add tests
thelullabyy Jul 1, 2026
4bc56ec
fix: linter
thelullabyy Jul 1, 2026
c316521
fix: bug
thelullabyy Jul 1, 2026
32c7291
merge main
thelullabyy Jul 9, 2026
badac3b
fix: fmt
thelullabyy Jul 9, 2026
00d3b9f
merge main
thelullabyy Jul 13, 2026
243a735
fix: lint
thelullabyy Jul 13, 2026
a696b33
Merge branch 'main' of https://github.com/Expensify/App into fix-lull…
thelullabyy Jul 13, 2026
dafb349
merge main
thelullabyy Jul 14, 2026
cb0389a
Merge branch 'main' of https://github.com/Expensify/App into fix-lull…
thelullabyy Jul 15, 2026
1c1cdfb
fix: test
thelullabyy Jul 15, 2026
0b1e0d6
Merge branch 'main' of https://github.com/Expensify/App into fix-lull…
thelullabyy Jul 15, 2026
db60b5d
fix: refactor MoneyReportHeader and MoneyRequestReportNavigation comp…
thelullabyy Jul 15, 2026
55149dc
fix: remove code comments
thelullabyy Jul 15, 2026
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
5 changes: 5 additions & 0 deletions src/ONYXKEYS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -749,6 +749,10 @@ const ONYXKEYS = {
/** List of transaction IDs used when navigating to prev/next transaction when viewing it in RHP */
TRANSACTION_THREAD_NAVIGATION_TRANSACTION_IDS: 'transactionThreadNavigationTransactionIDs',

/** Hash of the search snapshot that holds the transactions referenced by TRANSACTION_THREAD_NAVIGATION_TRANSACTION_IDS.
* Used to fall back to snapshot data when the live transaction collection hasn't loaded those transactions yet (e.g. opening an expense from the Spend page as an approver). */
TRANSACTION_THREAD_NAVIGATION_SNAPSHOT_HASH: 'transactionThreadNavigationSnapshotHash',

/** Optional map of transactionID -> sibling descriptor for prev/next navigation in snapshot-backed flows (e.g. Home "Recently added"), where siblings may be absent from the main Onyx collections. When set, navigation resolves (and lazily creates) each sibling's thread on demand from its descriptor. */
TRANSACTION_THREAD_NAVIGATION_THREAD_REPORT_IDS: 'transactionThreadNavigationThreadReportIDs',

Expand Down Expand Up @@ -1711,6 +1715,7 @@ type OnyxValuesMapping = {
[ONYXKEYS.REPORT_NAVIGATION_LAST_SEARCH_QUERY]: OnyxTypes.LastSearchParams;
[ONYXKEYS.NVP_LAST_ANDROID_LOGIN]: string;
[ONYXKEYS.TRANSACTION_THREAD_NAVIGATION_TRANSACTION_IDS]: string[];
[ONYXKEYS.TRANSACTION_THREAD_NAVIGATION_SNAPSHOT_HASH]: number;
[ONYXKEYS.TRANSACTION_THREAD_NAVIGATION_THREAD_REPORT_IDS]: Record<string, TransactionThreadNavigationDescriptor>;
[ONYXKEYS.NVP_INTEGRATION_SERVER_EXPORT_TEMPLATES]: OnyxTypes.ExportTemplate[];
[ONYXKEYS.ONBOARDING_USER_REPORTED_INTEGRATION]: OnboardingAccounting;
Expand Down
46 changes: 34 additions & 12 deletions src/components/MoneyReportHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ import HeaderWithBackButton from './HeaderWithBackButton';
import MoneyReportHeaderActions from './MoneyReportHeaderActions';
import MoneyReportHeaderModals from './MoneyReportHeaderModals';
import MoneyReportHeaderMoreContent from './MoneyReportHeaderMoreContent';
import MoneyRequestReportNavigation from './MoneyRequestReportView/MoneyRequestReportNavigation';
import MoneyRequestReportTransactionsNavigation from './MoneyRequestReportView/MoneyRequestReportTransactionsNavigation';
import {PaymentAnimationsProvider} from './PaymentAnimationsContext';
import {useSearchSelectionActions} from './Search/SearchContext';

Expand Down Expand Up @@ -76,14 +78,23 @@ function MoneyReportHeaderContent({reportID: reportIDProp, shouldDisplayBackButt

const transactions = Object.values(reportTransactions);

const [activeTransactionIDs] = useOnyx(ONYXKEYS.TRANSACTION_THREAD_NAVIGATION_TRANSACTION_IDS);

const singleTransactionID = transactions.length === 1 ? transactions.at(0)?.transactionID : undefined;

const anchorTransactionIDFromRoute = route.name === SCREENS.RIGHT_MODAL.SEARCH_REPORT ? route.params.anchorTransactionID : undefined;
const multiTxAnchorTransactionID = anchorTransactionIDFromRoute && activeTransactionIDs?.includes(anchorTransactionIDFromRoute) ? anchorTransactionIDFromRoute : undefined;
const carouselAnchorTransactionID = singleTransactionID ?? multiTxAnchorTransactionID;
const shouldShowTransactionNavigation = !!carouselAnchorTransactionID && !!activeTransactionIDs?.includes(carouselAnchorTransactionID);

const styles = useThemeStyles();

const {isWideRHPDisplayedOnWideLayout, isSuperWideRHPDisplayedOnWideLayout} = useResponsiveLayoutOnWideRHP();

const shouldShowHeaderButtonsInHeaderRow = isInLandscapeMode || !shouldDisplayNarrowVersion || isWideRHPDisplayedOnWideLayout || isSuperWideRHPDisplayedOnWideLayout;
const isReportInRHP = route.name !== SCREENS.REPORT;
const shouldDisplaySearchRouter = !isReportInRHP || isSmallScreenWidth;
const isReportInSearch = route.name === SCREENS.RIGHT_MODAL.SEARCH_REPORT || route.name === SCREENS.RIGHT_MODAL.SEARCH_MONEY_REQUEST_REPORT;
const shouldDisplaySearchRouter = !isReportInRHP || (isSmallScreenWidth && !isReportInSearch);

const backTo = (route.params as {backTo?: Route} | undefined)?.backTo;

Expand Down Expand Up @@ -132,24 +143,35 @@ function MoneyReportHeaderContent({reportID: reportIDProp, shouldDisplayBackButt
shouldEnableDetailPageNavigation
openParentReportInCurrentTab
>
{shouldShowHeaderButtonsInHeaderRow && (
{isReportInSearch &&
(shouldShowTransactionNavigation && carouselAnchorTransactionID ? (
<MoneyRequestReportTransactionsNavigation
currentTransactionID={carouselAnchorTransactionID}
shouldDisplayNarrowVersion={!shouldShowHeaderButtonsInHeaderRow}
/>
) : (
<MoneyRequestReportNavigation
reportID={reportIDProp}
shouldDisplayNarrowVersion={!shouldShowHeaderButtonsInHeaderRow}
/>
))}
</HeaderWithBackButton>
{!shouldShowHeaderButtonsInHeaderRow && (
<View style={styles.mtn1}>
<MoneyReportHeaderActions
reportID={reportIDProp}
primaryAction={primaryAction}
isReportInSearch={isReportInSearch}
backTo={backTo}
/>
)}
</HeaderWithBackButton>
{!shouldShowHeaderButtonsInHeaderRow && (
<MoneyReportHeaderActions
reportID={reportIDProp}
primaryAction={primaryAction}
isReportInSearch={isReportInSearch}
backTo={backTo}
/>
</View>
)}
<MoneyReportHeaderMoreContent reportID={reportIDProp} />
<MoneyReportHeaderMoreContent
reportID={reportIDProp}
primaryAction={primaryAction}
backTo={backTo}
shouldShowHeaderButtonsInHeaderRow={shouldShowHeaderButtonsInHeaderRow}
/>
<HeaderLoadingBar />
</View>
);
Expand Down
46 changes: 32 additions & 14 deletions src/components/MoneyReportHeaderMoreContent.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import useMoneyReportHeaderStatusBar from '@hooks/useMoneyReportHeaderStatusBar';
import useOnyx from '@hooks/useOnyx';
import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useResponsiveLayoutOnWideRHP from '@hooks/useResponsiveLayoutOnWideRHP';
import useThemeStyles from '@hooks/useThemeStyles';

import getNonEmptyStringOnyxID from '@libs/getNonEmptyStringOnyxID';
Expand All @@ -12,6 +10,7 @@ import {isInvoiceReport as isInvoiceReportUtil} from '@libs/ReportUtils';

import type CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import type {Route} from '@src/ROUTES';
import SCREENS from '@src/SCREENS';
import type * as OnyxTypes from '@src/types/onyx';

Expand All @@ -22,20 +21,25 @@ import {useRoute} from '@react-navigation/native';
import React from 'react';
import {View} from 'react-native';

import type {MoneyReportHeaderActionsProps} from './MoneyReportHeaderActions/types';

import MoneyReportHeaderActions from './MoneyReportHeaderActions';
import MoneyReportHeaderNextStep from './MoneyReportHeaderNextStep';
import MoneyReportHeaderStatusBarSection from './MoneyReportHeaderStatusBarSection';
import {useMoneyReportTransactionThread} from './MoneyReportTransactionThreadContext';
import MoneyRequestReportNavigation from './MoneyRequestReportView/MoneyRequestReportNavigation';

type MoneyReportHeaderMoreContentProps = {
reportID: string | undefined;
primaryAction: MoneyReportHeaderActionsProps['primaryAction'];
backTo: Route | undefined;
shouldShowHeaderButtonsInHeaderRow: boolean;
};

/**
* Cheap visibility gate — fetches minimal data to decide whether the more-content section
* should render at all, avoiding expensive hooks in the body when nothing is shown.
*/
function MoneyReportHeaderMoreContent({reportID}: MoneyReportHeaderMoreContentProps) {
function MoneyReportHeaderMoreContent({reportID, primaryAction, backTo, shouldShowHeaderButtonsInHeaderRow}: MoneyReportHeaderMoreContentProps) {
const route = useRoute<
| PlatformStackRouteProp<ReportsSplitNavigatorParamList, typeof SCREENS.REPORT>
| PlatformStackRouteProp<RightModalNavigatorParamList, typeof SCREENS.RIGHT_MODAL.EXPENSE_REPORT>
Expand All @@ -50,8 +54,10 @@ function MoneyReportHeaderMoreContent({reportID}: MoneyReportHeaderMoreContentPr

const isInvoiceReport = isInvoiceReportUtil(moneyRequestReport);
const shouldShowNextStep = isGroupPolicy(policy) && !isInvoiceReport && !shouldShowStatusBar;
const hasStatusOrNextStep = shouldShowNextStep || !!statusBarType;
const shouldRenderActionsInRow = shouldShowHeaderButtonsInHeaderRow;

const shouldShowMoreContent = shouldShowNextStep || !!statusBarType || isReportInSearch;
const shouldShowMoreContent = hasStatusOrNextStep || shouldRenderActionsInRow;

if (!shouldShowMoreContent) {
return null;
Expand All @@ -63,6 +69,9 @@ function MoneyReportHeaderMoreContent({reportID}: MoneyReportHeaderMoreContentPr
statusBarType={statusBarType}
isReportInSearch={isReportInSearch}
shouldShowNextStep={shouldShowNextStep}
primaryAction={primaryAction}
backTo={backTo}
shouldRenderActionsInRow={shouldRenderActionsInRow}
/>
);
}
Expand All @@ -72,20 +81,27 @@ type MoneyReportHeaderMoreContentBodyProps = {
statusBarType: ValueOf<typeof CONST.REPORT.STATUS_BAR_TYPE> | undefined;
isReportInSearch: boolean;
shouldShowNextStep: boolean;
primaryAction: MoneyReportHeaderActionsProps['primaryAction'];
backTo: Route | undefined;
shouldRenderActionsInRow: boolean;
};

function MoneyReportHeaderMoreContentBody({moneyRequestReport, statusBarType, isReportInSearch, shouldShowNextStep}: MoneyReportHeaderMoreContentBodyProps) {
function MoneyReportHeaderMoreContentBody({
moneyRequestReport,
statusBarType,
isReportInSearch,
shouldShowNextStep,
primaryAction,
backTo,
shouldRenderActionsInRow,
}: MoneyReportHeaderMoreContentBodyProps) {
const styles = useThemeStyles();
const {shouldUseNarrowLayout, isMediumScreenWidth} = useResponsiveLayout();
const shouldDisplayNarrowVersion = shouldUseNarrowLayout || isMediumScreenWidth;
const {isWideRHPDisplayedOnWideLayout, isSuperWideRHPDisplayedOnWideLayout} = useResponsiveLayoutOnWideRHP();
const shouldDisplayNarrowMoreButton = !shouldDisplayNarrowVersion || isWideRHPDisplayedOnWideLayout || isSuperWideRHPDisplayedOnWideLayout;

const reportID = moneyRequestReport?.reportID;
const {iouTransactionID} = useMoneyReportTransactionThread();

return (
<View style={[styles.flexRow, styles.gap2, styles.justifyContentStart, styles.flexNoWrap, styles.ph5, styles.pb3]}>
<View style={[styles.flexRow, styles.gap2, styles.justifyContentStart, styles.flexNoWrap, styles.ph5, styles.pb3, styles.mtn1, shouldShowNextStep && styles.pt0]}>
<View style={[styles.flexShrink1, styles.flexGrow1, styles.mnw0, styles.flexWrap, styles.justifyContentCenter]}>
{shouldShowNextStep && <MoneyReportHeaderNextStep reportID={reportID} />}
<MoneyReportHeaderStatusBarSection
Expand All @@ -94,10 +110,12 @@ function MoneyReportHeaderMoreContentBody({moneyRequestReport, statusBarType, is
iouTransactionID={iouTransactionID}
/>
</View>
{isReportInSearch && (
<MoneyRequestReportNavigation
{shouldRenderActionsInRow && (
<MoneyReportHeaderActions
reportID={reportID}
shouldDisplayNarrowVersion={!shouldDisplayNarrowMoreButton}
primaryAction={primaryAction}
isReportInSearch={isReportInSearch}
backTo={backTo}
/>
)}
</View>
Expand Down
32 changes: 22 additions & 10 deletions src/components/MoneyRequestHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ function MoneyRequestHeader({reportID: reportIDProp, onBackButtonPress}: MoneyRe
const shouldDisplayTransactionNavigation = !!(reportID && isReportInRHP);
const shouldOpenParentReportInCurrentTab = !isSelfDM(parentReport);
const shouldDisplayButtonsInSeparateLine = useShouldDisplayButtonsInSeparateLine() && (wideRHPRouteKeys.length === 0 || isSmallScreenWidth);
const shouldDisplayNarrowVersion = shouldDisplayButtonsInSeparateLine;

const getStatusIcon: (src: IconAsset) => ReactNode = (src) => (
<Icon
Expand Down Expand Up @@ -179,7 +180,7 @@ function MoneyRequestHeader({reportID: reportIDProp, onBackButtonPress}: MoneyRe
shouldEnableDetailPageNavigation
openParentReportInCurrentTab={shouldOpenParentReportInCurrentTab}
>
{!shouldDisplayButtonsInSeparateLine && (
{!shouldDisplayButtonsInSeparateLine && !statusBarProps && (
<MoneyRequestHeaderActions
reportID={reportID}
onBackButtonPress={onBackButtonPress}
Expand All @@ -189,21 +190,32 @@ function MoneyRequestHeader({reportID: reportIDProp, onBackButtonPress}: MoneyRe
<MoneyRequestReportTransactionsNavigation
currentTransactionID={transaction.transactionID}
isFromReviewDuplicates={isFromReviewDuplicates}
shouldDisplayNarrowVersion={shouldDisplayNarrowVersion}
/>
)}
</HeaderWithBackButton>
{shouldDisplayButtonsInSeparateLine && (
<MoneyRequestHeaderActions
reportID={reportID}
onBackButtonPress={onBackButtonPress}
/>
<View style={styles.mtn1}>
<MoneyRequestHeaderActions
reportID={reportID}
onBackButtonPress={onBackButtonPress}
/>
</View>
)}
{!!statusBarProps && (
<View style={[styles.ph5, styles.pb3]}>
<MoneyRequestHeaderStatusBar
icon={statusBarProps.icon}
description={statusBarProps.description}
/>
<View style={[styles.flexRow, styles.gap2, styles.justifyContentStart, styles.flexNoWrap, styles.ph5, styles.pb3, styles.mtn1]}>
<View style={[styles.flexShrink1, styles.flexGrow1, styles.mnw0, styles.flexWrap, styles.justifyContentCenter]}>
<MoneyRequestHeaderStatusBar
icon={statusBarProps.icon}
description={statusBarProps.description}
/>
</View>
{!shouldDisplayButtonsInSeparateLine && (
<MoneyRequestHeaderActions
reportID={reportID}
onBackButtonPress={onBackButtonPress}
/>
)}
</View>
)}
<HeaderLoadingBar />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {useSearchResultsContext} from '@components/Search/SearchContext';
import Text from '@components/Text';

import useFilterPendingDeleteReports from '@hooks/useFilterPendingDeleteReports';
import useLocalize from '@hooks/useLocalize';
import useOnyx from '@hooks/useOnyx';
import useSearchSections from '@hooks/useSearchSections';
import useThemeStyles from '@hooks/useThemeStyles';
Expand All @@ -21,6 +22,7 @@ import type LastSearchParams from '@src/types/onyx/ReportNavigation';

import type {OnyxEntry} from 'react-native-onyx';

import {useIsFocused} from '@react-navigation/native';
import React, {startTransition, useEffect, useState} from 'react';
import {View} from 'react-native';

Expand Down Expand Up @@ -113,6 +115,8 @@ function MoneyRequestReportNavigationStandalone({onReportsChange}: MoneyRequestR

function MoneyRequestReportNavigationContent({reportID, shouldDisplayNarrowVersion, contextReports}: MoneyRequestReportNavigationContentProps) {
const styles = useThemeStyles();
const {translate} = useLocalize();
const isFocused = useIsFocused();

// Lightweight subscriptions only: the current search query and its loading flag. These never mount
// the heavy useSearchSections subscription set, so the fast context path stays cheap.
Expand Down Expand Up @@ -151,7 +155,7 @@ function MoneyRequestReportNavigationContent({reportID, shouldDisplayNarrowVersi
const shouldDisplayNavigationArrows = effectiveAllReports.length > 1 && currentIndex !== -1 && !!lastSearchQuery?.queryJSON;

useEffect(() => {
if (!lastSearchQuery?.queryJSON) {
if (!isFocused || !lastSearchQuery?.queryJSON) {
return;
}

Expand Down Expand Up @@ -181,7 +185,7 @@ function MoneyRequestReportNavigationContent({reportID, shouldDisplayNarrowVersi
...lastSearchQuery,
previousLengthOfResults: effectiveAllReports.length,
});
}, [currentIndex, allReportsCount, effectiveAllReports.length, lastSearchQuery?.queryJSON, lastSearchQuery]);
}, [isFocused, currentIndex, allReportsCount, effectiveAllReports.length, lastSearchQuery?.queryJSON, lastSearchQuery]);

const goToReportId = (reportId?: string) => {
if (!reportId) {
Expand Down Expand Up @@ -242,7 +246,11 @@ function MoneyRequestReportNavigationContent({reportID, shouldDisplayNarrowVersi
{!shouldUseContextReports && <MoneyRequestReportNavigationStandalone onReportsChange={setStandaloneReports} />}
{shouldDisplayNavigationArrows && (
<View style={[styles.flexRow, styles.alignItemsCenter, styles.gap2]}>
{!shouldDisplayNarrowVersion && <Text style={styles.mutedTextLabel}>{`${currentIndex + 1} of ${allReportsCount}`}</Text>}
{!shouldDisplayNarrowVersion && (
<Text style={[styles.mutedTextLabel, styles.textAlignRight, styles.mnw8]}>
{translate('common.currentOfTotal', {current: currentIndex + 1, total: allReportsCount})}
</Text>
)}
<PrevNextButtons
isPrevButtonDisabled={hidePrevButton}
isNextButtonDisabled={hideNextButton}
Expand Down
Loading
Loading