diff --git a/android/app/build.gradle b/android/app/build.gradle index 35870b94ecc7..0e68ee108601 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -107,8 +107,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1001048014 - versionName "1.4.80-14" + versionCode 1001048015 + versionName "1.4.80-15" // Supported language variants must be declared here to avoid from being removed during the compilation. // This also helps us to not include unnecessary language variants in the APK. resConfigs "en", "es" diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index e8871f5fa0d5..ac3a6c87bf3a 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -40,7 +40,7 @@ CFBundleVersion - 1.4.80.14 + 1.4.80.15 FullStory OrgId diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index c9b7c849b1b0..0d6e738bb2ff 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -19,6 +19,6 @@ CFBundleSignature ???? CFBundleVersion - 1.4.80.14 + 1.4.80.15 diff --git a/ios/NotificationServiceExtension/Info.plist b/ios/NotificationServiceExtension/Info.plist index c24f19f3defa..75782a691c03 100644 --- a/ios/NotificationServiceExtension/Info.plist +++ b/ios/NotificationServiceExtension/Info.plist @@ -13,7 +13,7 @@ CFBundleShortVersionString 1.4.80 CFBundleVersion - 1.4.80.14 + 1.4.80.15 NSExtension NSExtensionPointIdentifier diff --git a/package-lock.json b/package-lock.json index 35c458610a4c..0dcad82de6cb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "new.expensify", - "version": "1.4.80-14", + "version": "1.4.80-15", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "1.4.80-14", + "version": "1.4.80-15", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index ba121028ec12..228dbc09ff6d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "1.4.80-14", + "version": "1.4.80-15", "author": "Expensify, Inc.", "homepage": "https://new.expensify.com", "description": "New Expensify is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.", 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 && ( + +