Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
ce177bb
fix create new report while cycling via reports
sumo-slonik Aug 25, 2025
ec0c295
working fix
sumo-slonik Aug 25, 2025
242cc99
working fix
sumo-slonik Aug 25, 2025
6b9a22d
revert revert PR
sumo-slonik Aug 26, 2025
d3a7544
Merge branch 'main' into feature/kuba_nowakowski/add_ability_to_cycle…
sumo-slonik Aug 26, 2025
774bf8e
Merge branch 'bugfix/kuba_nowakowski/eport-pagination-shows-0-of-2' i…
sumo-slonik Aug 26, 2025
58bb1b8
work in progress
sumo-slonik Aug 27, 2025
832b0f3
fix pagination after report creation
sumo-slonik Aug 27, 2025
29d7b80
fix highlight and distance
sumo-slonik Aug 27, 2025
bd60916
fix highlight
sumo-slonik Aug 27, 2025
ada820a
remove debuggeer
sumo-slonik Aug 28, 2025
1333c3e
Merge branch 'main' into feature/kuba_nowakowski/add_ability_to_cycle…
sumo-slonik Aug 28, 2025
e27ca61
fix ts
sumo-slonik Aug 28, 2025
78aec84
remove unnecessary comment
sumo-slonik Aug 28, 2025
087411b
Merge branch 'main' into feature/kuba_nowakowski/add_ability_to_cycle…
sumo-slonik Aug 28, 2025
83bc3f6
Merge branch 'main' into feature/kuba_nowakowski/add_ability_to_cycle…
sumo-slonik Aug 28, 2025
d93408a
Merge branch 'main' into feature/kuba_nowakowski/add_ability_to_cycle…
sumo-slonik Aug 29, 2025
faaa735
remove backTo
sumo-slonik Aug 29, 2025
ecefd43
fixes after review
sumo-slonik Aug 29, 2025
fafe89b
fixes eslint
sumo-slonik Aug 29, 2025
846bff2
Merge branch 'main' into feature/kuba_nowakowski/add_ability_to_cycle…
sumo-slonik Aug 29, 2025
3ef0482
disabled next button
sumo-slonik Aug 29, 2025
d3d7beb
disabled next button
sumo-slonik Aug 29, 2025
16a8cd3
fix next button disabled
sumo-slonik Sep 1, 2025
3ef513c
Merge branch 'main' into feature/kuba_nowakowski/add_ability_to_cycle…
sumo-slonik Sep 1, 2025
85472bd
Merge branch 'main' into feature/kuba_nowakowski/add_ability_to_cycle…
sumo-slonik Sep 2, 2025
de51bb9
fix behavior on approved page
sumo-slonik Sep 4, 2025
9fa074a
Merge branch 'main' into feature/kuba_nowakowski/add_ability_to_cycle…
sumo-slonik Sep 4, 2025
25c31d6
prettier
sumo-slonik Sep 4, 2025
4396c43
Merge branch 'main' into feature/kuba_nowakowski/add_ability_to_cycle…
sumo-slonik Sep 5, 2025
828d603
Merge branch 'main' into feature/kuba_nowakowski/add_ability_to_cycle…
sumo-slonik Sep 8, 2025
f2e3749
Merge branch 'main' into feature/kuba_nowakowski/add_ability_to_cycle…
zfurtak Sep 10, 2025
d7aad36
First adjustments
zfurtak Sep 11, 2025
56b6976
Second round of adjustments
zfurtak Sep 11, 2025
55a5ca4
Merge branch 'main' into feature/kuba_nowakowski/add_ability_to_cycle…
zfurtak Sep 11, 2025
df34147
Merge branch 'main' into feature/kuba_nowakowski/add_ability_to_cycle…
zfurtak Sep 11, 2025
1c859a1
Merge branch 'main' into feature/kuba_nowakowski/add_ability_to_cycle…
zfurtak Sep 12, 2025
e5f15c1
Merge branch 'main' into feature/kuba_nowakowski/add_ability_to_cycle…
zfurtak Sep 16, 2025
65cc90a
Merge branch 'main' into feature/kuba_nowakowski/add_ability_to_cycle…
zfurtak Sep 23, 2025
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
3 changes: 3 additions & 0 deletions src/ONYXKEYS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -580,6 +580,8 @@ const ONYXKEYS = {
/** List of transaction thread IDs used when navigating to prev/next transaction when viewing it in RHP */
TRANSACTION_THREAD_NAVIGATION_REPORT_IDS: 'transactionThreadNavigationReportIDs',

REPORT_NAVIGATION_LAST_SEARCH_QUERY: 'ReportNavigationLastSearchQuery',

/** Timestamp of the last login on iOS */
NVP_LAST_ECASH_IOS_LOGIN: 'nvp_lastECashIOSLogin',
NVP_LAST_IPHONE_LOGIN: 'nvp_lastiPhoneLogin',
Expand Down Expand Up @@ -1267,6 +1269,7 @@ type OnyxValuesMapping = {
[ONYXKEYS.NVP_LAST_ECASH_IOS_LOGIN]: string;
[ONYXKEYS.NVP_LAST_ECASH_ANDROID_LOGIN]: string;
[ONYXKEYS.NVP_LAST_IPHONE_LOGIN]: string;
[ONYXKEYS.REPORT_NAVIGATION_LAST_SEARCH_QUERY]: OnyxTypes.LastSearchParams;
[ONYXKEYS.NVP_LAST_ANDROID_LOGIN]: string;
[ONYXKEYS.TRANSACTION_THREAD_NAVIGATION_REPORT_IDS]: string[];
[ONYXKEYS.NVP_INTEGRATION_SERVER_EXPORT_TEMPLATES]: OnyxTypes.ExportTemplate[];
Expand Down
93 changes: 40 additions & 53 deletions src/components/MoneyReportHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ import MoneyReportHeaderStatusBar from './MoneyReportHeaderStatusBar';
import MoneyReportHeaderStatusBarSkeleton from './MoneyReportHeaderStatusBarSkeleton';
import type {MoneyRequestHeaderStatusBarProps} from './MoneyRequestHeaderStatusBar';
import MoneyRequestHeaderStatusBar from './MoneyRequestHeaderStatusBar';
import MoneyRequestReportNavigation from './MoneyRequestReportView/MoneyRequestReportNavigation';
import type {PopoverMenuItem} from './PopoverMenu';
import type {ActionHandledType} from './ProcessMoneyReportHoldMenu';
import ProcessMoneyReportHoldMenu from './ProcessMoneyReportHoldMenu';
Expand Down Expand Up @@ -401,6 +402,8 @@ function MoneyReportHeader({

const isReportInRHP = route.name === SCREENS.SEARCH.REPORT_RHP;
const shouldDisplaySearchRouter = !isReportInRHP || isSmallScreenWidth;
const isReportInSearch = route.name === SCREENS.SEARCH.MONEY_REQUEST_REPORT;

const existingB2BInvoiceReport = useParticipantsInvoiceReport(activePolicyID, CONST.REPORT.INVOICE_RECEIVER_TYPE.BUSINESS, chatReport?.policyID);
const confirmPayment = useCallback(
(type?: PaymentMethodType | undefined, payAsBusiness?: boolean, methodID?: number, paymentMethod?: PaymentMethod) => {
Expand Down Expand Up @@ -456,14 +459,6 @@ function MoneyReportHeader({
} else {
startApprovedAnimation();
approveMoneyRequest(moneyRequestReport, true);
if (currentSearchQueryJSON) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we remove this?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AFAIK this is the expected behaviour now, we don't want to reload search after approving report, it should stay there for the whole flow.

search({
searchKey: currentSearchKey,
shouldCalculateTotals: true,
offset: 0,
queryJSON: currentSearchQueryJSON,
});
}
}
};

Expand Down Expand Up @@ -1251,44 +1246,6 @@ function MoneyReportHeader({
</KYCWall>
);

const hasOtherItems =
(shouldShowNextStep && !!optimisticNextStep?.message?.length) || (shouldShowNextStep && !optimisticNextStep && !!isLoadingInitialReportActions && !isOffline) || !!statusBarProps;

const moreContentUnfiltered = [
shouldShowSelectedTransactionsButton && shouldDisplayNarrowVersion && (
<View
style={[styles.dFlex, styles.w100, !hasOtherItems && styles.pb3]}
key="1"
>
<ButtonWithDropdownMenu
onPress={() => null}
options={selectedTransactionsOptions}
customText={translate('workspace.common.selected', {count: selectedTransactionIDs.length})}
isSplitButton={false}
shouldAlwaysShowDropdownMenu
wrapperStyle={styles.w100}
/>
</View>
),
shouldShowNextStep && !!optimisticNextStep?.message?.length && (
<MoneyReportHeaderStatusBar
nextStep={optimisticNextStep}
key="2"
/>
),
shouldShowNextStep && !optimisticNextStep && !!isLoadingInitialReportActions && !isOffline && <MoneyReportHeaderStatusBarSkeleton key="3" />,
!!statusBarProps && (
<MoneyRequestHeaderStatusBar
icon={statusBarProps.icon}
description={statusBarProps.description}
key="4"
/>
),
];
const moreContent = moreContentUnfiltered.filter(Boolean);
const isMoreContentShown = moreContent.length > 0;
const shouldAddGapToContents = moreContent.length > 1;

return (
<View style={[styles.pt0, styles.borderBottom]}>
<HeaderWithBackButton
Expand Down Expand Up @@ -1320,14 +1277,44 @@ function MoneyReportHeader({
</View>
)}
</HeaderWithBackButton>
{shouldDisplayNarrowVersion && !shouldShowSelectedTransactionsButton && (
<View style={[styles.flexRow, styles.gap2, styles.pb3, styles.ph5, styles.w100, styles.alignItemsCenter, styles.justifyContentCenter]}>
{!!primaryAction && <View style={[styles.flex1]}>{primaryActionsImplementation[primaryAction]}</View>}
{!!applicableSecondaryActions.length && KYCMoreDropdown}
</View>
)}

{isMoreContentShown && <View style={[styles.dFlex, styles.flexColumn, shouldAddGapToContents && styles.gap3, styles.pb3, styles.ph5]}>{moreContent}</View>}
{shouldDisplayNarrowVersion &&
(shouldShowSelectedTransactionsButton ? (
<View style={[styles.dFlex, styles.w100, styles.ph5]}>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lack of spacing for the view component caused padding missing between next steps message and selected button for medium screen here

<ButtonWithDropdownMenu
onPress={() => null}
options={selectedTransactionsOptions}
customText={translate('workspace.common.selected', {count: selectedTransactionIDs.length})}
isSplitButton={false}
shouldAlwaysShowDropdownMenu
wrapperStyle={styles.w100}
/>
</View>
) : (
<View style={[styles.flexRow, styles.gap2, styles.pb3, styles.ph5, styles.w100, styles.alignItemsCenter, styles.justifyContentCenter]}>
{!!primaryAction && <View style={[styles.flex1]}>{primaryActionsImplementation[primaryAction]}</View>}
{!!applicableSecondaryActions.length && KYCMoreDropdown}
</View>
))}

<View style={[styles.flexRow, styles.gap2, styles.justifyContentStart, styles.flexNoWrap, styles.ph5, styles.pb3]}>
<View style={[styles.flexShrink1, styles.flexGrow1, styles.mnw0, styles.flexWrap, styles.justifyContentCenter]}>
{shouldShowNextStep && !!optimisticNextStep?.message?.length && <MoneyReportHeaderStatusBar nextStep={optimisticNextStep} />}
{shouldShowNextStep && !optimisticNextStep && !!isLoadingInitialReportActions && !isOffline && <MoneyReportHeaderStatusBarSkeleton />}
{!!statusBarProps && (
<MoneyRequestHeaderStatusBar
icon={statusBarProps.icon}
description={statusBarProps.description}
/>
)}
</View>
{isReportInSearch && (
<MoneyRequestReportNavigation
reportID={moneyRequestReport?.reportID}
shouldDisplayNarrowVersion={shouldDisplayNarrowVersion}
/>
)}
</View>

<LoadingBar shouldShow={shouldShowLoadingBar && shouldUseNarrowLayout} />
{isHoldMenuVisible && requestType !== undefined && (
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import React, {useEffect} from 'react';
import {View} from 'react-native';
import PrevNextButtons from '@components/PrevNextButtons';
import Text from '@components/Text';
import useLocalize from '@hooks/useLocalize';
import useOnyx from '@hooks/useOnyx';
import useThemeStyles from '@hooks/useThemeStyles';
import {selectArchivedReportsIdSet, selectFilteredReportActions} from '@libs/ReportUtils';
import {getSections, getSortedSections} from '@libs/SearchUIUtils';
import Navigation from '@navigation/Navigation';
import {saveLastSearchParams} from '@userActions/ReportNavigation';
import {search} from '@userActions/Search';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';

type MoneyRequestReportNavigationProps = {
reportID?: string;
shouldDisplayNarrowVersion: boolean;
};

function MoneyRequestReportNavigation({reportID, shouldDisplayNarrowVersion}: MoneyRequestReportNavigationProps) {
const [lastSearchQuery] = useOnyx(ONYXKEYS.REPORT_NAVIGATION_LAST_SEARCH_QUERY, {canBeMissing: true});
const [currentSearchResults] = useOnyx(`${ONYXKEYS.COLLECTION.SNAPSHOT}${lastSearchQuery?.queryJSON?.hash}`, {canBeMissing: true});
const [accountID] = useOnyx(ONYXKEYS.SESSION, {canBeMissing: false, selector: (s) => s?.accountID});

const {localeCompare, formatPhoneNumber} = useLocalize();

const [exportReportActions] = useOnyx(ONYXKEYS.COLLECTION.REPORT_ACTIONS, {
canEvict: false,
canBeMissing: true,
selector: selectFilteredReportActions,
});

const [archivedReportsIdSet = new Set<string>()] = useOnyx(ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS, {
canBeMissing: true,
selector: selectArchivedReportsIdSet,
});

const {type, status, sortBy, sortOrder, groupBy} = lastSearchQuery?.queryJSON ?? {};
let results: Array<string | undefined> = [];
if (!!type && !!groupBy && !!currentSearchResults?.data && !!currentSearchResults?.search) {
const searchData = getSections(type, currentSearchResults.data, accountID, formatPhoneNumber, groupBy, exportReportActions, lastSearchQuery?.searchKey, archivedReportsIdSet);
results = getSortedSections(type, status ?? '', searchData, localeCompare, sortBy, sortOrder, groupBy).map((value) => value.reportID);
}
const allReports = results;

const currentIndex = allReports.indexOf(reportID);
const allReportsCount = lastSearchQuery?.previousLengthOfResults ?? 0;

const hideNextButton = !lastSearchQuery?.hasMoreResults && currentIndex === allReports.length - 1;
const hidePrevButton = currentIndex === 0;
const styles = useThemeStyles();
const shouldDisplayNavigationArrows = allReports && allReports.length > 1 && currentIndex !== -1 && !!lastSearchQuery?.queryJSON;

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

if (lastSearchQuery.allowPostSearchRecount) {
saveLastSearchParams({
...lastSearchQuery,
allowPostSearchRecount: false,
previousLengthOfResults: allReports.length,
});
return;
}

if (currentIndex < allReportsCount - 1) {
return;
}

saveLastSearchParams({
...lastSearchQuery,
previousLengthOfResults: allReports.length,
});
}, [currentIndex, allReportsCount, allReports.length, lastSearchQuery?.queryJSON, lastSearchQuery]);

const goToReportId = (reportId?: string) => {
if (!reportId) {
return;
}
Navigation.setParams({
reportID: reportId,
});
};

const goToNextReport = () => {
if (currentIndex === -1 || allReports.length === 0 || !lastSearchQuery?.queryJSON) {
return;
}
const threshold = Math.min(allReports.length * 0.75, allReports.length - 2);

if (currentIndex + 1 >= threshold && lastSearchQuery?.hasMoreResults) {
const newOffset = (lastSearchQuery.offset ?? 0) + CONST.SEARCH.RESULTS_PAGE_SIZE;
search({
queryJSON: lastSearchQuery.queryJSON,
offset: newOffset,
prevReportsLength: allReports.length,
shouldCalculateTotals: false,
searchKey: lastSearchQuery.searchKey,
});
}

const nextIndex = (currentIndex + 1) % allReports.length;
goToReportId(allReports.at(nextIndex));
};

const goToPrevReport = () => {
if (currentIndex === -1 || allReports.length === 0) {
return;
}

const prevIndex = (currentIndex - 1) % allReports.length;
goToReportId(allReports.at(prevIndex));
};

return (
shouldDisplayNavigationArrows && (
<View style={[styles.flexRow, styles.alignItemsCenter, styles.gap2]}>
{!shouldDisplayNarrowVersion && <Text style={styles.mutedTextLabel}>{`${currentIndex + 1} of ${allReportsCount}`}</Text>}
<PrevNextButtons
isPrevButtonDisabled={hidePrevButton}
isNextButtonDisabled={hideNextButton}
onNext={goToNextReport}
onPrevious={goToPrevReport}
/>
</View>
)
);
}

MoneyRequestReportNavigation.displayName = 'MoneyRequestReportNavigation';

export default MoneyRequestReportNavigation;
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ type MoneyRequestReportViewProps = {
/** Whether Report footer (that includes Composer) should be displayed */
shouldDisplayReportFooter: boolean;

/** Whether we should wait for the report to sync */
shouldWaitForReportSync: boolean;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's add the description for this prop


/** The `backTo` route that should be used when clicking back button */
backToRoute: Route | undefined;
};
Expand Down Expand Up @@ -86,7 +89,7 @@ function getParentReportAction(parentReportActions: OnyxEntry<OnyxTypes.ReportAc
return parentReportActions[parentReportActionID];
}

function MoneyRequestReportView({report, policy, reportMetadata, shouldDisplayReportFooter, backToRoute}: MoneyRequestReportViewProps) {
function MoneyRequestReportView({report, policy, reportMetadata, shouldDisplayReportFooter, backToRoute, shouldWaitForReportSync}: MoneyRequestReportViewProps) {
const styles = useThemeStyles();
const {isOffline} = useNetwork();

Expand All @@ -110,8 +113,7 @@ function MoneyRequestReportView({report, policy, reportMetadata, shouldDisplayRe
const transactionThreadReportID = getOneTransactionThreadReportID(report, chatReport, reportActions ?? [], isOffline, reportTransactionIDs);
const isSentMoneyReport = useMemo(() => reportActions.some((action) => isSentMoneyReportAction(action)), [reportActions]);

const newTransactions = useNewTransactions(reportMetadata?.hasOnceLoadedReportActions, transactions);

const newTransactions = useNewTransactions(reportMetadata?.hasOnceLoadedReportActions, shouldWaitForReportSync ? [] : transactions);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use shouldWaitForTransactions?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually we can’t, because this is tied to the Onyx issue in which, during the first render with a new Onyx key, the data returned is still linked to the previous one.
To avoid that we wait until they’re synced.

const [parentReportAction] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${getNonEmptyStringOnyxID(report?.parentReportID)}`, {
canEvict: false,
canBeMissing: true,
Expand Down Expand Up @@ -172,7 +174,7 @@ function MoneyRequestReportView({report, policy, reportMetadata, shouldDisplayRe
[backToRoute, isLoadingInitialReportActions, isTransactionThreadView, parentReportAction, policy, report, reportActions, transactionThreadReportID],
);

if (!!(isLoadingInitialReportActions && reportActions.length === 0 && !isOffline) || shouldWaitForTransactions) {
if (!!(isLoadingInitialReportActions && reportActions.length === 0 && !isOffline) || shouldWaitForTransactions || shouldWaitForReportSync) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Coming from #71268:
Regarding || shouldWaitForReportSync condition:
@sumo-slonik do we need to show skeleton while loading report from Onyx? This affects only android. I don't see skeleton in other platforms (maybe due to fast Onyx loading).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the absence of this condition was causing some GUI elements to flicker.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see any flickers

return <InitialLoadingSkeleton styles={styles} />;
}

Expand Down
4 changes: 2 additions & 2 deletions src/components/PrevNextButtons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ function PrevNextButtons({isPrevButtonDisabled, isNextButtonDisabled, onNext, on
accessibilityRole={CONST.ROLE.BUTTON}
accessibilityLabel={CONST.ROLE.BUTTON}
disabled={isPrevButtonDisabled}
style={[styles.h10, styles.mr1, styles.alignItemsCenter, styles.justifyContentCenter]}
style={[styles.h7, styles.mr1, styles.alignItemsCenter, styles.justifyContentCenter]}
onPress={onPrevious}
>
<View style={[styles.reportActionContextMenuMiniButton, {backgroundColor: theme.borderLighter}, isPrevButtonDisabled && styles.buttonOpacityDisabled]}>
Expand All @@ -50,7 +50,7 @@ function PrevNextButtons({isPrevButtonDisabled, isNextButtonDisabled, onNext, on
accessibilityRole={CONST.ROLE.BUTTON}
accessibilityLabel={CONST.ROLE.BUTTON}
disabled={isNextButtonDisabled}
style={[styles.h10, styles.alignItemsCenter, styles.justifyContentCenter]}
style={[styles.h7, styles.alignItemsCenter, styles.justifyContentCenter]}
onPress={onNext}
>
<View style={[styles.reportActionContextMenuMiniButton, {backgroundColor: theme.borderLighter}, isNextButtonDisabled && styles.buttonOpacityDisabled]}>
Expand Down
Loading
Loading