diff --git a/src/CONST.ts b/src/CONST.ts index 6de5700366f3..8ac06a52a8a5 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -1158,27 +1158,10 @@ const CONST = { APPROVE: 'approve', PAY: 'pay', EXPORT_TO_ACCOUNTING: 'exportToAccounting', - REMOVE_HOLD: 'removeHold', - MARK_AS_CASH: 'markAsCash', - }, - TRANSACTION_PRIMARY_ACTIONS: { REMOVE_HOLD: 'removeHold', REVIEW_DUPLICATES: 'reviewDuplicates', MARK_AS_CASH: 'markAsCash', }, - REPORT_PREVIEW_ACTIONS: { - VIEW: 'view', - REVIEW: 'review', - SUBMIT: 'submit', - APPROVE: 'approve', - PAY: 'pay', - EXPORT_TO_ACCOUNTING: 'exportToAccounting', - }, - TRANSACTION_SECONDARY_ACTIONS: { - HOLD: 'hold', - VIEW_DETAILS: 'viewDetails', - DELETE: 'delete', - }, ACTIONS: { LIMIT: 50, // OldDot Actions render getMessage from Web-Expensify/lib/Report/Action PHP files via getMessageOfOldDotReportAction in ReportActionsUtils.ts diff --git a/src/components/AvatarWithDisplayName.tsx b/src/components/AvatarWithDisplayName.tsx index 28a73d32362f..a8b3e1d35119 100644 --- a/src/components/AvatarWithDisplayName.tsx +++ b/src/components/AvatarWithDisplayName.tsx @@ -30,6 +30,7 @@ import ROUTES from '@src/ROUTES'; import type {Policy, Report} from '@src/types/onyx'; import type {Icon} from '@src/types/onyx/OnyxCommon'; import {getButtonRole} from './Button/utils'; +import CaretWrapper from './CaretWrapper'; import DisplayNames from './DisplayNames'; import {FallbackAvatar} from './Icon/Expensicons'; import MultipleAvatars from './MultipleAvatars'; @@ -154,14 +155,16 @@ function AvatarWithDisplayName({policy, report, isAnonymous = false, size = CONS - + + + {Object.keys(parentNavigationSubtitleData).length > 0 && ( { if (!reportActions || !transactionThreadReport?.parentReportActionID) { return null; } return reportActions.find((action): action is OnyxTypes.ReportAction => action.reportActionID === transactionThreadReport.parentReportActionID); }, [reportActions, transactionThreadReport?.parentReportActionID]); - const [transactions = []] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION, { + const [transactions] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION, { selector: (_transactions) => reportTransactionsSelector(_transactions, moneyRequestReport?.reportID), initialValue: [], }); const [transaction] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}${isMoneyRequestAction(requestParentReportAction) && getOriginalMessage(requestParentReportAction)?.IOUTransactionID}`); const [dismissedHoldUseExplanation, dismissedHoldUseExplanationResult] = useOnyx(ONYXKEYS.NVP_DISMISSED_HOLD_USE_EXPLANATION, {initialValue: true}); const isLoadingHoldUseExplained = isLoadingOnyxValue(dismissedHoldUseExplanationResult); - const [session] = useOnyx(ONYXKEYS.SESSION); - - const isExported = isExportedUtils(reportActions); - const [markAsExportedModalVisible, setMarkAsExportedModalVisible] = useState(false); - const [downloadErrorModalVisible, setDownloadErrorModalVisible] = useState(false); - const [isCancelPaymentModalVisible, setIsCancelPaymentModalVisible] = useState(false); - const [isDeleteModalVisible, setIsDeleteModalVisible] = useState(false); - - const {isPaidAnimationRunning, isApprovedAnimationRunning, startAnimation, stopAnimation, startApprovedAnimation} = usePaymentAnimations(); + const {isPaidAnimationRunning, isApprovedAnimationRunning, stopAnimation, startAnimation, startApprovedAnimation} = usePaymentAnimations(); const styles = useThemeStyles(); const theme = useTheme(); + const [isDeleteRequestModalVisible, setIsDeleteRequestModalVisible] = useState(false); const {translate} = useLocalize(); const {isOffline} = useNetwork(); const {reimbursableSpend} = getMoneyRequestSpendBreakdown(moneyRequestReport); const isOnHold = isOnHoldTransactionUtils(transaction); + const isDeletedParentAction = !!requestParentReportAction && isDeletedAction(requestParentReportAction); + const isDuplicate = isDuplicateTransactionUtils(transaction?.transactionID) && (!isReportApproved({report: moneyRequestReport}) || isApprovedAnimationRunning); + // 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 canDeleteRequest = isActionOwner && canDeleteTransaction(moneyRequestReport) && !isDeletedParentAction; const [isHoldMenuVisible, setIsHoldMenuVisible] = useState(false); const [paymentType, setPaymentType] = useState(); const [requestType, setRequestType] = useState(); const canAllowSettlement = hasUpdatedTotal(moneyRequestReport, policy); const policyType = policy?.type; const connectedIntegration = getConnectedIntegration(policy); + const navigateBackToAfterDelete = useRef(); const hasScanningReceipt = getTransactionsWithReceipts(moneyRequestReport?.reportID).some((t) => isReceiptBeingScanned(t)); const hasOnlyPendingTransactions = useMemo(() => { return !!transactions && transactions.length > 0 && transactions.every((t) => isExpensifyCardTransaction(t) && isPending(t)); @@ -289,8 +290,12 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea const shouldShowSelectedTransactionsButton = !!selectedTransactionsOptions.length; const canIOUBePaid = useMemo(() => getCanIOUBePaid(), [getCanIOUBePaid]); + const canIOUBePaidAndApproved = useMemo(() => getCanIOUBePaid(false, false), [getCanIOUBePaid]); const onlyShowPayElsewhere = useMemo(() => !canIOUBePaid && getCanIOUBePaid(true), [canIOUBePaid, getCanIOUBePaid]); + const shouldShowMarkAsCashButton = + !!transactionThreadReportID && checkIfShouldShowMarkAsCashButton(hasAllPendingRTERViolations, shouldShowBrokenConnectionViolation, moneyRequestReport, policy); + const shouldShowPayButton = isPaidAnimationRunning || canIOUBePaid || onlyShowPayElsewhere; const shouldShowApproveButton = useMemo( @@ -300,6 +305,22 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea const shouldDisableApproveButton = shouldShowApproveButton && !isAllowedToApproveExpenseReport(moneyRequestReport); + const isAdmin = policy?.role === CONST.POLICY.ROLE.ADMIN; + + const filteredTransactions = transactions?.filter((t) => t) ?? []; + const shouldShowSubmitButton = canSubmitReport(moneyRequestReport, policy, filteredTransactions, violations); + + const shouldShowExportIntegrationButton = !shouldShowPayButton && !shouldShowSubmitButton && connectedIntegration && isAdmin && canBeExported(moneyRequestReport); + + const shouldShowSettlementButton = + !shouldShowSelectedTransactionsButton && + !shouldShowSubmitButton && + (shouldShowPayButton || shouldShowApproveButton) && + !shouldShowRTERViolationMessage(transactions) && + !shouldShowExportIntegrationButton && + !shouldShowBrokenConnectionViolation; + + const shouldDisableSubmitButton = shouldShowSubmitButton && !isAllowedToSubmitDraftExpenseReport(moneyRequestReport); const isFromPaidPolicy = policyType === CONST.POLICY.TYPE.TEAM || policyType === CONST.POLICY.TYPE.CORPORATE; const hasDuplicates = hasDuplicateTransactions(moneyRequestReport?.reportID); @@ -314,11 +335,21 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea const optimisticNextStep = isSubmitterSameAsNextApprover && policy?.preventSelfApproval ? buildOptimisticNextStepForPreventSelfApprovalsEnabled() : nextStep; const shouldShowNextStep = isFromPaidPolicy && !!optimisticNextStep?.message?.length && !shouldShowStatusBar; + const shouldShowAnyButton = + shouldShowSelectedTransactionsButton || + isDuplicate || + shouldShowSettlementButton || + shouldShowApproveButton || + shouldShowSubmitButton || + shouldShowNextStep || + shouldShowMarkAsCashButton || + shouldShowExportIntegrationButton; const bankAccountRoute = getBankAccountRoute(chatReport); const formattedAmount = convertToDisplayString(reimbursableSpend, moneyRequestReport?.currency); const {nonHeldAmount, fullAmount, hasValidNonHeldAmount} = getNonHeldAndFullAmount(moneyRequestReport, shouldShowPayButton); const isAnyTransactionOnHold = hasHeldExpensesReportUtils(moneyRequestReport?.reportID); const displayedAmount = isAnyTransactionOnHold && canAllowSettlement && hasValidNonHeldAmount ? nonHeldAmount : formattedAmount; + const isMoreContentShown = shouldShowNextStep || shouldShowStatusBar || (shouldShowAnyButton && shouldUseNarrowLayout); const {isDelegateAccessRestricted} = useDelegateUserDetails(); const [isNoDelegateAccessMenuVisible, setIsNoDelegateAccessMenuVisible] = useState(false); const [isLoadingReportData] = useOnyx(ONYXKEYS.IS_LOADING_REPORT_DATA); @@ -360,6 +391,19 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea } }; + const deleteTransaction = useCallback(() => { + if (requestParentReportAction) { + const iouTransactionID = isMoneyRequestAction(requestParentReportAction) ? getOriginalMessage(requestParentReportAction)?.IOUTransactionID : undefined; + if (isTrackExpenseAction(requestParentReportAction)) { + navigateBackToAfterDelete.current = deleteTrackExpense(moneyRequestReport?.reportID, iouTransactionID, requestParentReportAction, true); + } else { + navigateBackToAfterDelete.current = deleteMoneyRequest(iouTransactionID, requestParentReportAction, true); + } + } + + setIsDeleteRequestModalVisible(false); + }, [moneyRequestReport?.reportID, requestParentReportAction, setIsDeleteRequestModalVisible]); + const markAsCash = useCallback(() => { if (!requestParentReportAction) { return; @@ -373,14 +417,6 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea markAsCashAction(iouTransactionID, reportID); }, [requestParentReportAction, transactionThreadReport?.reportID]); - const confirmManualExport = useCallback(() => { - if (!connectedIntegration || !moneyRequestReport) { - throw new Error('Missing data'); - } - - markAsManuallyExported(moneyRequestReport.reportID, connectedIntegration); - }, [connectedIntegration, moneyRequestReport]); - const getStatusIcon: (src: IconAsset) => React.ReactNode = (src) => ( isWaitingForSubmissionFromCurrentUserReportUtils(chatReport, policy), [chatReport, policy]); + + const shouldDuplicateButtonBeSuccess = useMemo( + () => isDuplicate && !shouldShowSettlementButton && !shouldShowExportIntegrationButton && !shouldShowSubmitButton && !shouldShowMarkAsCashButton, + [isDuplicate, shouldShowSettlementButton, shouldShowExportIntegrationButton, shouldShowSubmitButton, shouldShowMarkAsCashButton], + ); useEffect(() => { // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing @@ -441,238 +488,21 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea Navigation.navigate(ROUTES.PROCESS_MONEY_REQUEST_HOLD.getRoute(Navigation.getReportRHPActiveRoute())); }, [dismissedHoldUseExplanation, isLoadingHoldUseExplained, isOnHold]); - const primaryAction = useMemo(() => { - // It's necessary to allow payment animation to finish before button is changed - if (isPaidAnimationRunning) { - return CONST.REPORT.PRIMARY_ACTIONS.PAY; - } - if (!moneyRequestReport) { - return ''; - } - return getReportPrimaryAction(moneyRequestReport, transactions, violations, policy); - }, [isPaidAnimationRunning, moneyRequestReport, policy, transactions, violations]); - - const primaryActionsImplementation = { - [CONST.REPORT.PRIMARY_ACTIONS.SUBMIT]: ( -