diff --git a/src/CONST.ts b/src/CONST.ts index 344c71a8f8472..826215398dcdf 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -1189,6 +1189,24 @@ const CONST = { REVIEW_DUPLICATES: 'reviewDuplicates', 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 fb6eb080740bd..b1e735676d015 100644 --- a/src/components/AvatarWithDisplayName.tsx +++ b/src/components/AvatarWithDisplayName.tsx @@ -30,7 +30,6 @@ 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'; @@ -155,16 +154,14 @@ function AvatarWithDisplayName({policy, report, isAnonymous = false, size = CONS - - - + {Object.keys(parentNavigationSubtitleData).length > 0 && ( => 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: [], canBeMissing: true, @@ -143,28 +144,28 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea const [dismissedHoldUseExplanation, dismissedHoldUseExplanationResult] = useOnyx(ONYXKEYS.NVP_DISMISSED_HOLD_USE_EXPLANATION, {initialValue: true, canBeMissing: true}); const isLoadingHoldUseExplained = isLoadingOnyxValue(dismissedHoldUseExplanationResult); - const {isPaidAnimationRunning, isApprovedAnimationRunning, stopAnimation, startAnimation, startApprovedAnimation} = usePaymentAnimations(); + 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 [isUnapproveModalVisible, setIsUnapproveModalVisible] = useState(false); + + const {isPaidAnimationRunning, isApprovedAnimationRunning, startAnimation, stopAnimation, 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)); @@ -199,12 +200,8 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea const shouldShowSelectedTransactionsButton = !!selectedTransactionsOptions.length && !transactionThreadReportID; 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( @@ -214,22 +211,6 @@ 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); @@ -244,21 +225,11 @@ 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, {canBeMissing: true}); @@ -300,19 +271,6 @@ 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; @@ -326,6 +284,14 @@ 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], - ); + const shouldAddGapToContents = shouldUseNarrowLayout && (!!statusBarProps || shouldShowNextStep); useEffect(() => { // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing @@ -397,13 +352,246 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea Navigation.navigate(ROUTES.PROCESS_MONEY_REQUEST_HOLD.getRoute(Navigation.getReportRHPActiveRoute())); }, [dismissedHoldUseExplanation, isLoadingHoldUseExplained, isOnHold]); - useEffect(() => { - if (canDeleteRequest) { - return; + 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]: ( +