diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx
index d2702fa54a3d..ec52a6158ad7 100644
--- a/src/components/MoneyReportHeader.tsx
+++ b/src/components/MoneyReportHeader.tsx
@@ -8,7 +8,6 @@ import useThemeStyles from '@hooks/useThemeStyles';
import useWindowDimensions from '@hooks/useWindowDimensions';
import * as CurrencyUtils from '@libs/CurrencyUtils';
import * as HeaderUtils from '@libs/HeaderUtils';
-import Navigation from '@libs/Navigation/Navigation';
import * as ReportActionsUtils from '@libs/ReportActionsUtils';
import * as ReportUtils from '@libs/ReportUtils';
import * as TransactionUtils from '@libs/TransactionUtils';
@@ -21,17 +20,14 @@ import ROUTES from '@src/ROUTES';
import type * as OnyxTypes from '@src/types/onyx';
import type {PaymentMethodType} from '@src/types/onyx/OriginalMessage';
import {isEmptyObject} from '@src/types/utils/EmptyObject';
-import type IconAsset from '@src/types/utils/IconAsset';
import Button from './Button';
import ConfirmModal from './ConfirmModal';
import HeaderWithBackButton from './HeaderWithBackButton';
import Icon from './Icon';
import * as Expensicons from './Icon/Expensicons';
import MoneyReportHeaderStatusBar from './MoneyReportHeaderStatusBar';
-import type {MoneyRequestHeaderStatusBarProps} from './MoneyRequestHeaderStatusBar';
import MoneyRequestHeaderStatusBar from './MoneyRequestHeaderStatusBar';
import ProcessMoneyReportHoldMenu from './ProcessMoneyReportHoldMenu';
-import ProcessMoneyRequestHoldMenu from './ProcessMoneyRequestHoldMenu';
import SettlementButton from './SettlementButton';
type MoneyReportHeaderProps = {
@@ -60,35 +56,25 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea
const [nextStep] = useOnyx(`${ONYXKEYS.COLLECTION.NEXT_STEP}${moneyRequestReport.reportID}`);
const [transactionThreadReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${transactionThreadReportID}`);
const [session] = useOnyx(ONYXKEYS.SESSION);
- const requestParentReportAction = useMemo(() => {
- if (!reportActions || !transactionThreadReport?.parentReportActionID) {
- return null;
- }
- return reportActions.find((action) => action.reportActionID === transactionThreadReport.parentReportActionID) as OnyxTypes.ReportAction & OnyxTypes.OriginalMessageIOU;
- }, [reportActions, transactionThreadReport?.parentReportActionID]);
- const [transactions] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION);
- const [shownHoldUseExplanation] = useOnyx(ONYXKEYS.NVP_HOLD_USE_EXPLAINED, {initWithStoredValues: false});
- const transaction = transactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${requestParentReportAction?.originalMessage?.IOUTransactionID ?? 0}`] ?? null;
const styles = useThemeStyles();
const theme = useTheme();
const [isDeleteRequestModalVisible, setIsDeleteRequestModalVisible] = useState(false);
- const [shouldShowHoldMenu, setShouldShowHoldMenu] = useState(false);
const {translate} = useLocalize();
const {windowWidth} = useWindowDimensions();
const {reimbursableSpend} = ReportUtils.getMoneyRequestSpendBreakdown(moneyRequestReport);
const isSettled = ReportUtils.isSettled(moneyRequestReport.reportID);
- const isApproved = ReportUtils.isReportApproved(moneyRequestReport);
- const isOnHold = TransactionUtils.isOnHold(transaction);
- const isScanning = TransactionUtils.hasReceipt(transaction) && TransactionUtils.isReceiptBeingScanned(transaction);
+ const requestParentReportAction = useMemo(() => {
+ if (!reportActions || !transactionThreadReport?.parentReportActionID) {
+ return null;
+ }
+ return reportActions.find((action) => action.reportActionID === transactionThreadReport.parentReportActionID);
+ }, [reportActions, transactionThreadReport?.parentReportActionID]);
const isDeletedParentAction = ReportActionsUtils.isDeletedAction(requestParentReportAction as OnyxTypes.ReportAction);
- const canHoldOrUnholdRequest = !isEmptyObject(transaction) && !isSettled && !isApproved && !isDeletedParentAction;
// Only the requestor can delete the request, admins can only edit it.
const isActionOwner =
typeof requestParentReportAction?.actorAccountID === 'number' && typeof session?.accountID === 'number' && requestParentReportAction.actorAccountID === session?.accountID;
- const isPolicyAdmin = policy?.role === CONST.POLICY.ROLE.ADMIN;
- const isApprover = ReportUtils.isMoneyRequestReport(moneyRequestReport) && moneyRequestReport?.managerID !== null && session?.accountID === moneyRequestReport?.managerID;
const canDeleteRequest =
isActionOwner && (ReportUtils.canAddOrDeleteTransactions(moneyRequestReport) || ReportUtils.isTrackExpenseReport(transactionThreadReport)) && !isDeletedParentAction;
const [isHoldMenuVisible, setIsHoldMenuVisible] = useState(false);
@@ -100,8 +86,8 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea
const isDraft = ReportUtils.isOpenExpenseReport(moneyRequestReport);
const [isConfirmModalVisible, setIsConfirmModalVisible] = useState(false);
- const hasScanningReceipt = ReportUtils.getTransactionsWithReceipts(moneyRequestReport?.reportID).some((t) => TransactionUtils.isReceiptBeingScanned(t));
- const transactionIDs = TransactionUtils.getAllReportTransactions(moneyRequestReport?.reportID).map((t) => t.transactionID);
+ const hasScanningReceipt = ReportUtils.getTransactionsWithReceipts(moneyRequestReport?.reportID).some((transaction) => TransactionUtils.isReceiptBeingScanned(transaction));
+ const transactionIDs = TransactionUtils.getAllReportTransactions(moneyRequestReport?.reportID).map((transaction) => transaction.transactionID);
const allHavePendingRTERViolation = TransactionUtils.allHavePendingRTERViolation(transactionIDs);
const cancelPayment = useCallback(() => {
@@ -120,20 +106,18 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea
const shouldShowSettlementButton = (shouldShowPayButton || shouldShowApproveButton) && !allHavePendingRTERViolation;
- // allTransactions in TransactionUtils might have stale data
- const hasOnlyHeldExpenses = ReportUtils.hasOnlyHeldExpenses(moneyRequestReport.reportID, transactions);
- const shouldShowSubmitButton = isDraft && reimbursableSpend !== 0 && !allHavePendingRTERViolation && !hasOnlyHeldExpenses;
+ const shouldShowSubmitButton = isDraft && reimbursableSpend !== 0 && !allHavePendingRTERViolation;
const shouldDisableSubmitButton = shouldShowSubmitButton && !ReportUtils.isAllowedToSubmitDraftExpenseReport(moneyRequestReport);
- const shouldShowMarkAsCashButton = isDraft && allHavePendingRTERViolation && !hasOnlyHeldExpenses;
+ const shouldShowMarkAsCashButton = isDraft && allHavePendingRTERViolation;
const isFromPaidPolicy = policyType === CONST.POLICY.TYPE.TEAM || policyType === CONST.POLICY.TYPE.CORPORATE;
- const shouldShowStatusBar = allHavePendingRTERViolation || hasOnlyHeldExpenses || hasScanningReceipt;
- const shouldShowNextStep = !ReportUtils.isClosedExpenseReportWithNoExpenses(moneyRequestReport) && isFromPaidPolicy && !!nextStep?.message?.length && !shouldShowStatusBar;
+ const shouldShowNextStep =
+ !ReportUtils.isClosedExpenseReportWithNoExpenses(moneyRequestReport) && isFromPaidPolicy && !!nextStep?.message?.length && !allHavePendingRTERViolation && !hasScanningReceipt;
const shouldShowAnyButton = shouldShowSettlementButton || shouldShowApproveButton || shouldShowSubmitButton || shouldShowNextStep || allHavePendingRTERViolation;
const bankAccountRoute = ReportUtils.getBankAccountRoute(chatReport);
const formattedAmount = CurrencyUtils.convertToDisplayString(reimbursableSpend, moneyRequestReport.currency);
const [nonHeldAmount, fullAmount] = ReportUtils.getNonHeldAndFullAmount(moneyRequestReport, policy);
const displayedAmount = ReportUtils.hasHeldExpenses(moneyRequestReport.reportID) && canAllowSettlement ? nonHeldAmount : formattedAmount;
- const isMoreContentShown = shouldShowNextStep || shouldShowStatusBar || (shouldShowAnyButton && shouldUseNarrowLayout);
+ const isMoreContentShown = shouldShowNextStep || hasScanningReceipt || (shouldShowAnyButton && shouldUseNarrowLayout);
const confirmPayment = (type?: PaymentMethodType | undefined) => {
if (!type || !chatReport) {
@@ -182,43 +166,6 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea
TransactionActions.markAsCash(iouTransactionID, reportID);
}, [requestParentReportAction, transactionThreadReport?.reportID]);
- const changeMoneyRequestStatus = () => {
- if (!transactionThreadReport) {
- return;
- }
- const transactionID = requestParentReportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU ? requestParentReportAction.originalMessage?.IOUTransactionID ?? '' : '';
-
- if (isOnHold) {
- IOU.unholdRequest(transactionID, transactionThreadReport.reportID);
- } else {
- const activeRoute = encodeURIComponent(Navigation.getActiveRouteWithoutParams());
- Navigation.navigate(ROUTES.MONEY_REQUEST_HOLD_REASON.getRoute(policy?.type ?? CONST.POLICY.TYPE.PERSONAL, transactionID, transactionThreadReport.reportID, activeRoute));
- }
- };
-
- const getStatusIcon: (src: IconAsset) => React.ReactNode = (src) => (
-
- );
-
- const getStatusBarProps: () => MoneyRequestHeaderStatusBarProps | undefined = () => {
- if (hasOnlyHeldExpenses) {
- return {title: translate('iou.hold'), description: translate('iou.expensesOnHold'), danger: true};
- }
- if (allHavePendingRTERViolation) {
- return {title: getStatusIcon(Expensicons.Hourglass), description: translate('iou.pendingMatchWithCreditCardDescription')};
- }
- if (hasScanningReceipt) {
- return {title: getStatusIcon(Expensicons.ReceiptScan), description: translate('iou.receiptScanInProgressDescription')};
- }
- };
-
- const statusBarProps = getStatusBarProps();
-
// The submit button should be success green colour only if the user is submitter and the policy does not have Scheduled Submit turned on
const isWaitingForSubmissionFromCurrentUser = useMemo(
() => chatReport?.isOwnPolicyExpenseChat && !policy?.harvesting?.enabled,
@@ -226,49 +173,6 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea
);
const threeDotsMenuItems = [HeaderUtils.getPinMenuItem(moneyRequestReport)];
- if (canHoldOrUnholdRequest) {
- const isRequestIOU = ReportUtils.isIOUReport(chatReport);
- const isHoldCreator = ReportUtils.isHoldCreator(transaction, moneyRequestReport?.reportID) && isRequestIOU;
- const isTrackExpenseReport = ReportUtils.isTrackExpenseReport(moneyRequestReport);
- const canModifyStatus = !isTrackExpenseReport && (isPolicyAdmin || isActionOwner || isApprover);
- if (isOnHold && (isHoldCreator || (!isRequestIOU && canModifyStatus))) {
- threeDotsMenuItems.push({
- icon: Expensicons.Stopwatch,
- text: translate('iou.unholdExpense'),
- onSelected: () => changeMoneyRequestStatus(),
- });
- }
- if (!isOnHold && (isRequestIOU || canModifyStatus) && !isScanning) {
- threeDotsMenuItems.push({
- icon: Expensicons.Stopwatch,
- text: translate('iou.hold'),
- onSelected: () => changeMoneyRequestStatus(),
- });
- }
- }
-
- useEffect(() => {
- setShouldShowHoldMenu(isOnHold && !shownHoldUseExplanation);
- }, [isOnHold, shownHoldUseExplanation]);
-
- useEffect(() => {
- if (!shouldShowHoldMenu) {
- return;
- }
-
- if (shouldUseNarrowLayout) {
- if (Navigation.getActiveRoute().slice(1) === ROUTES.PROCESS_MONEY_REQUEST_HOLD) {
- Navigation.goBack();
- }
- } else {
- Navigation.navigate(ROUTES.PROCESS_MONEY_REQUEST_HOLD);
- }
- }, [shouldUseNarrowLayout, shouldShowHoldMenu]);
-
- const handleHoldRequestClose = () => {
- IOU.setShownHoldUseExplanation();
- };
-
if (isPayer && isSettled && ReportUtils.isExpenseReport(moneyRequestReport)) {
threeDotsMenuItems.push({
icon: Expensicons.Trashcan,
@@ -304,8 +208,8 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea
policy={policy}
shouldShowBackButton={shouldUseNarrowLayout}
onBackButtonPress={onBackButtonPress}
- // Shows border if no buttons or banners are showing below the header
- shouldShowBorderBottom={!isMoreContentShown}
+ // Shows border if no buttons or next steps are showing below the header
+ shouldShowBorderBottom={!isMoreContentShown && !allHavePendingRTERViolation}
shouldShowThreeDotsButton
threeDotsMenuItems={threeDotsMenuItems}
threeDotsAnchorPosition={styles.threeDotsPopoverOffsetNoCloseButton(windowWidth)}
@@ -325,7 +229,7 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea
shouldShowApproveButton={shouldShowApproveButton}
shouldDisableApproveButton={shouldDisableApproveButton}
style={[styles.pv2]}
- formattedAmount={!hasOnlyHeldExpenses ? displayedAmount : ''}
+ formattedAmount={!ReportUtils.hasOnlyHeldExpenses(moneyRequestReport.reportID) ? displayedAmount : ''}
isDisabled={!canAllowSettlement}
/>
@@ -354,55 +258,88 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea
)}
-
- {shouldShowSettlementButton && shouldUseNarrowLayout && (
-
+ {shouldShowMarkAsCashButton && shouldUseNarrowLayout && (
+
+
+
+ )}
+
+ }
+ description={translate('iou.pendingMatchWithCreditCardDescription')}
+ shouldShowBorderBottom
/>
+
+ )}
+
+ {shouldShowSettlementButton && shouldUseNarrowLayout && (
+
+
+
)}
{shouldShowSubmitButton && shouldUseNarrowLayout && (
-
{isHoldMenuVisible && requestType !== undefined && (
- {shouldUseNarrowLayout && shouldShowHoldMenu && (
-
- )}
);
}
diff --git a/src/components/MoneyRequestHeader.tsx b/src/components/MoneyRequestHeader.tsx
index ac6870c0692c..e5a61a8fa23c 100644
--- a/src/components/MoneyRequestHeader.tsx
+++ b/src/components/MoneyRequestHeader.tsx
@@ -121,17 +121,21 @@ function MoneyRequestHeader({report, parentReportAction, policy, shouldUseNarrow
const getStatusBarProps: () => MoneyRequestHeaderStatusBarProps | undefined = () => {
if (isOnHold) {
- return {title: translate('iou.hold'), description: isDuplicate ? translate('iou.expenseDuplicate') : translate('iou.expenseOnHold'), danger: true};
+ return {title: translate('iou.hold'), description: isDuplicate ? translate('iou.expenseDuplicate') : translate('iou.expenseOnHold'), danger: true, shouldShowBorderBottom: true};
}
if (TransactionUtils.isExpensifyCardTransaction(transaction) && TransactionUtils.isPending(transaction)) {
- return {title: getStatusIcon(Expensicons.CreditCardHourglass), description: translate('iou.transactionPendingDescription')};
+ return {title: getStatusIcon(Expensicons.CreditCardHourglass), description: translate('iou.transactionPendingDescription'), shouldShowBorderBottom: true};
}
if (TransactionUtils.hasPendingRTERViolation(TransactionUtils.getTransactionViolations(transaction?.transactionID ?? '', transactionViolations))) {
- return {title: getStatusIcon(Expensicons.Hourglass), description: translate('iou.pendingMatchWithCreditCardDescription')};
+ return {
+ title: getStatusIcon(Expensicons.Hourglass),
+ description: translate('iou.pendingMatchWithCreditCardDescription'),
+ shouldShowBorderBottom: true,
+ };
}
if (isScanning) {
- return {title: getStatusIcon(Expensicons.ReceiptScan), description: translate('iou.receiptScanInProgressDescription')};
+ return {title: getStatusIcon(Expensicons.ReceiptScan), description: translate('iou.receiptScanInProgressDescription'), shouldShowBorderBottom: true};
}
};
@@ -256,13 +260,12 @@ function MoneyRequestHeader({report, parentReportAction, policy, shouldUseNarrow
)}
{statusBarProps && (
-
-
-
+
)}
+
{typeof title === 'string' ? (
): boolean {
- const reportTransactions = TransactionUtils.getAllReportTransactions(iouReportID, transactions);
- return !reportTransactions.some((transaction) => !TransactionUtils.isOnHold(transaction));
+function hasOnlyHeldExpenses(iouReportID: string): boolean {
+ const transactions = TransactionUtils.getAllReportTransactions(iouReportID);
+ return !transactions.some((transaction) => !TransactionUtils.isOnHold(transaction));
}
/**
diff --git a/src/libs/TransactionUtils.ts b/src/libs/TransactionUtils.ts
index 33b409daf3d2..6e2ee68b4eff 100644
--- a/src/libs/TransactionUtils.ts
+++ b/src/libs/TransactionUtils.ts
@@ -561,12 +561,12 @@ function hasRoute(transaction: OnyxEntry, isDistanceRequestType: bo
return !!transaction?.routes?.route0?.geometry?.coordinates || (isDistanceRequestType && !!transaction?.comment?.customUnit?.quantity);
}
-function getAllReportTransactions(reportID?: string, transactions?: OnyxCollection): Transaction[] {
+function getAllReportTransactions(reportID?: string): Transaction[] {
// `reportID` from the `/CreateDistanceRequest` endpoint return's number instead of string for created `transaction`.
// For reference, https://github.com/Expensify/App/pull/26536#issuecomment-1703573277.
// We will update this in a follow-up Issue. According to this comment: https://github.com/Expensify/App/pull/26536#issuecomment-1703591019.
- const nonNullableTransactions: Transaction[] = Object.values(transactions ?? allTransactions ?? {}).filter((transaction): transaction is Transaction => transaction !== null);
- return nonNullableTransactions.filter((transaction) => `${transaction.reportID}` === `${reportID}`);
+ const transactions: Transaction[] = Object.values(allTransactions ?? {}).filter((transaction): transaction is Transaction => transaction !== null);
+ return transactions.filter((transaction) => `${transaction.reportID}` === `${reportID}`);
}
function waypointHasValidAddress(waypoint: RecentWaypoint | Waypoint): boolean {
diff --git a/src/pages/iou/SplitBillDetailsPage.tsx b/src/pages/iou/SplitBillDetailsPage.tsx
index 130d062d4ed6..af56d28f6841 100644
--- a/src/pages/iou/SplitBillDetailsPage.tsx
+++ b/src/pages/iou/SplitBillDetailsPage.tsx
@@ -104,20 +104,19 @@ function SplitBillDetailsPage({personalDetails, report, route, reportActions, tr
{isScanning && (
-
-
- }
- description={translate('iou.receiptScanInProgressDescription')}
- shouldStyleFlexGrow={false}
- />
-
+
+ }
+ description={translate('iou.receiptScanInProgressDescription')}
+ shouldShowBorderBottom
+ shouldStyleFlexGrow={false}
+ />
)}
{!!participants.length && (