Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Violations - Pending Receipts] Display the rter Violation with the Pending Pattern #40354

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
5f1d0b8
Merge branch 'main' into smelaa/pending-and-scanning/39533
smelaa Apr 17, 2024
68d480d
Display rter violation in MoneyRequestHeader & ReportPreview
smelaa Apr 17, 2024
3409d9c
Display rter violations in MoneyReportHeader
smelaa Apr 17, 2024
a350cda
Merge branch 'main' into smelaa/pending-and-scanning/39533
smelaa Apr 18, 2024
5275089
Fix util function
smelaa Apr 18, 2024
046dbc2
Add rter violation information to MoneyRequestPreviewContent header
smelaa Apr 18, 2024
a13a1f7
Merge branch 'main' into smelaa/pending-and-scanning/39533
smelaa Apr 24, 2024
68b9e49
Mocking data
smelaa Apr 24, 2024
aea3766
Merge branch 'main' into smelaa/pending-and-scanning/39533
smelaa Apr 24, 2024
07d3bc5
Disable eslint on mocking data
smelaa Apr 26, 2024
3de40c1
Merge branch 'main' into smelaa/pending-and-scanning/39533
smelaa Apr 26, 2024
c20119d
Move message from header to footer
smelaa Apr 29, 2024
306fc8f
Merge branch 'main' into smelaa/pending-and-scanning/39533
smelaa Apr 29, 2024
8f00196
Fix eslint error
smelaa Apr 29, 2024
7a89a59
Styling fixes
smelaa May 6, 2024
82e2fd5
Merge branch 'main' into smelaa/pending-and-scanning/39533
smelaa May 6, 2024
c47e30b
Fixes after merging with main
smelaa May 6, 2024
81d200e
Styling
smelaa May 7, 2024
937d677
Merge branch 'main' into smelaa/pending-and-scanning/39533
smelaa May 7, 2024
8d72c93
Eslint
smelaa May 7, 2024
8463360
Eslint
smelaa May 7, 2024
c4cdb4b
Addressing review comments
smelaa May 8, 2024
03fb6ee
Remove mocking data
smelaa May 8, 2024
5560114
Eslint
smelaa May 8, 2024
6524120
Merge branch 'brtqkr/pending-and-scanning/38688' into smelaa/pending-…
smelaa May 8, 2024
a3d217e
Address review comments
smelaa May 8, 2024
c4da3cc
Merge branch 'main' into smelaa/pending-and-scanning/39533
smelaa May 9, 2024
f48a9c4
Merge branch 'brtqkr/pending-and-scanning/38688' into smelaa/pending-…
smelaa May 10, 2024
34b43dc
Update Spanish translations
smelaa May 10, 2024
8e8bd95
Merge branch 'main' into smelaa/pending-and-scanning/39533
smelaa May 13, 2024
75d88b1
Address review comments
smelaa May 13, 2024
48b387c
Rename variables & change icon color
smelaa May 13, 2024
e5670c0
Fix a type & rename function
smelaa May 14, 2024
e90103f
Create icon & description getter functions
smelaa May 14, 2024
4920af0
Move types declaration to ReportUtils
smelaa May 14, 2024
7da3c45
Address review comments
smelaa May 14, 2024
027b40e
Address review comments
smelaa May 15, 2024
40f471c
Reuse MoneyRequestHeaderStatusBar
smelaa May 15, 2024
1855b01
Add JSDoc comment
smelaa May 15, 2024
02d87f4
Do not display next step banner in case of pending rter violation
smelaa May 15, 2024
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
31 changes: 27 additions & 4 deletions src/components/MoneyReportHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,15 @@ import {View} from 'react-native';
import type {OnyxEntry} from 'react-native-onyx';
import {withOnyx} from 'react-native-onyx';
import useLocalize from '@hooks/useLocalize';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import useWindowDimensions from '@hooks/useWindowDimensions';
import * as CurrencyUtils from '@libs/CurrencyUtils';
import * as HeaderUtils from '@libs/HeaderUtils';
import * as ReportActionsUtils from '@libs/ReportActionsUtils';
import * as ReportUtils from '@libs/ReportUtils';
import * as TransactionUtils from '@libs/TransactionUtils';
import variables from '@styles/variables';
import * as IOU from '@userActions/IOU';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
Expand All @@ -19,8 +22,10 @@ import {isEmptyObject} from '@src/types/utils/EmptyObject';
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 MoneyRequestHeaderStatusBar from './MoneyRequestHeaderStatusBar';
import ProcessMoneyReportHoldMenu from './ProcessMoneyReportHoldMenu';
import SettlementButton from './SettlementButton';

Expand Down Expand Up @@ -71,6 +76,7 @@ function MoneyReportHeader({
onBackButtonPress,
}: MoneyReportHeaderProps) {
const styles = useThemeStyles();
const theme = useTheme();
const [isDeleteRequestModalVisible, setIsDeleteRequestModalVisible] = useState(false);
const {translate} = useLocalize();
const {windowWidth} = useWindowDimensions();
Expand Down Expand Up @@ -98,6 +104,9 @@ function MoneyReportHeader({
const isDraft = ReportUtils.isOpenExpenseReport(moneyRequestReport);
const [isConfirmModalVisible, setIsConfirmModalVisible] = useState(false);

const transactionIDs = TransactionUtils.getAllReportTransactions(moneyRequestReport?.reportID).map((transaction) => transaction.transactionID);
const allHavePendingRTERViolation = TransactionUtils.allHavePendingRTERViolation(transactionIDs);

const cancelPayment = useCallback(() => {
if (!chatReport) {
return;
Expand All @@ -112,12 +121,12 @@ function MoneyReportHeader({

const shouldDisableApproveButton = shouldShowApproveButton && !ReportUtils.isAllowedToApproveExpenseReport(moneyRequestReport);

const shouldShowSettlementButton = !ReportUtils.isInvoiceReport(moneyRequestReport) && (shouldShowPayButton || shouldShowApproveButton);
const shouldShowSettlementButton = !ReportUtils.isInvoiceReport(moneyRequestReport) && (shouldShowPayButton || shouldShowApproveButton) && !allHavePendingRTERViolation;

const shouldShowSubmitButton = isDraft && reimbursableSpend !== 0;
const shouldShowSubmitButton = isDraft && reimbursableSpend !== 0 && !allHavePendingRTERViolation;
const shouldDisableSubmitButton = shouldShowSubmitButton && !ReportUtils.isAllowedToSubmitDraftExpenseReport(moneyRequestReport);
const isFromPaidPolicy = policyType === CONST.POLICY.TYPE.TEAM || policyType === CONST.POLICY.TYPE.CORPORATE;
const shouldShowNextStep = !ReportUtils.isClosedExpenseReportWithNoExpenses(moneyRequestReport) && isFromPaidPolicy && !!nextStep?.message?.length;
const shouldShowNextStep = !ReportUtils.isClosedExpenseReportWithNoExpenses(moneyRequestReport) && isFromPaidPolicy && !!nextStep?.message?.length && !allHavePendingRTERViolation;
const shouldShowAnyButton = shouldShowSettlementButton || shouldShowApproveButton || shouldShowSubmitButton || shouldShowNextStep;
const bankAccountRoute = ReportUtils.getBankAccountRoute(chatReport);
const formattedAmount = CurrencyUtils.convertToDisplayString(reimbursableSpend, moneyRequestReport.currency);
Expand Down Expand Up @@ -203,7 +212,7 @@ function MoneyReportHeader({
shouldShowBackButton={shouldUseNarrowLayout}
onBackButtonPress={onBackButtonPress}
// Shows border if no buttons or next steps are showing below the header
shouldShowBorderBottom={!(shouldShowAnyButton && shouldUseNarrowLayout) && !(shouldShowNextStep && !shouldUseNarrowLayout)}
shouldShowBorderBottom={!(shouldShowAnyButton && shouldUseNarrowLayout) && !(shouldShowNextStep && !shouldUseNarrowLayout) && !allHavePendingRTERViolation}
shouldShowThreeDotsButton
threeDotsMenuItems={threeDotsMenuItems}
threeDotsAnchorPosition={styles.threeDotsPopoverOffsetNoCloseButton(windowWidth)}
Expand Down Expand Up @@ -241,6 +250,20 @@ function MoneyReportHeader({
</View>
)}
</HeaderWithBackButton>
{allHavePendingRTERViolation && (
<MoneyRequestHeaderStatusBar
title={
<Icon
src={Expensicons.Hourglass}
height={variables.iconSizeSmall}
width={variables.iconSizeSmall}
fill={theme.icon}
/>
}
description={translate('iou.pendingMatchWithCreditCardDescription')}
shouldShowBorderBottom
/>
)}
<View style={isMoreContentShown ? [styles.dFlex, styles.flexColumn, styles.borderBottom] : []}>
{shouldShowSettlementButton && shouldUseNarrowLayout && (
<View style={[styles.ph5, styles.pb2]}>
Expand Down
89 changes: 50 additions & 39 deletions src/components/MoneyRequestHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import type {ReactNode} from 'react';
import React, {useCallback, useEffect, useState} from 'react';
import {View} from 'react-native';
import {withOnyx} from 'react-native-onyx';
import type {OnyxEntry} from 'react-native-onyx';
import type {OnyxCollection, OnyxEntry} from 'react-native-onyx';
import useLocalize from '@hooks/useLocalize';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
Expand All @@ -16,12 +17,14 @@ import * as IOU from '@userActions/IOU';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import type {Policy, Report, ReportAction, ReportActions, Session, Transaction} from '@src/types/onyx';
import type {Policy, Report, ReportAction, ReportActions, Session, Transaction, TransactionViolations} from '@src/types/onyx';
import type {OriginalMessageIOU} from '@src/types/onyx/OriginalMessage';
import type IconAsset from '@src/types/utils/IconAsset';
import ConfirmModal from './ConfirmModal';
import HeaderWithBackButton from './HeaderWithBackButton';
import Icon from './Icon';
import * as Expensicons from './Icon/Expensicons';
import type {MoneyRequestHeaderStatusBarProps} from './MoneyRequestHeaderStatusBar';
import MoneyRequestHeaderStatusBar from './MoneyRequestHeaderStatusBar';
import ProcessMoneyRequestHoldMenu from './ProcessMoneyRequestHoldMenu';

Expand All @@ -35,6 +38,9 @@ type MoneyRequestHeaderOnyxProps = {
/** All the data for the transaction */
transaction: OnyxEntry<Transaction>;

/** The violations of the transaction */
transactionViolations: OnyxCollection<TransactionViolations>;

/** All report actions */
// eslint-disable-next-line react/no-unused-prop-types
parentReportActions: OnyxEntry<ReportActions>;
Expand Down Expand Up @@ -65,6 +71,7 @@ function MoneyRequestHeader({
parentReport,
report,
parentReportAction,
transactionViolations,
transaction,
shownHoldUseExplanation = false,
policy,
Expand Down Expand Up @@ -101,7 +108,6 @@ function MoneyRequestHeader({
}, [parentReport?.reportID, parentReportAction, setIsDeleteModalVisible]);

const isScanning = TransactionUtils.hasReceipt(transaction) && TransactionUtils.isReceiptBeingScanned(transaction);
const isPending = TransactionUtils.isExpensifyCardTransaction(transaction) && TransactionUtils.isPending(transaction);

const isDeletedParentAction = ReportActionsUtils.isDeletedAction(parentReportAction);
const canHoldOrUnholdRequest = !isSettled && !isApproved && !isDeletedParentAction;
Expand All @@ -120,6 +126,33 @@ function MoneyRequestHeader({
}
};

const getStatusIcon: (src: IconAsset) => ReactNode = (src) => (
<Icon
src={src}
height={variables.iconSizeSmall}
width={variables.iconSizeSmall}
fill={theme.icon}
/>
);

const getStatusBarProps: () => MoneyRequestHeaderStatusBarProps | undefined = () => {
if (isOnHold) {
return {title: translate('iou.hold'), description: translate('iou.expenseOnHold'), danger: true, shouldShowBorderBottom: true};
}

if (TransactionUtils.isExpensifyCardTransaction(transaction) && TransactionUtils.isPending(transaction)) {
return {title: getStatusIcon(Expensicons.CreditCardHourglass), description: translate('iou.transactionPendingDescription'), shouldShowBorderBottom: true};
}
if (isScanning) {
return {title: getStatusIcon(Expensicons.ReceiptScan), description: translate('iou.receiptScanInProgressDescription'), shouldShowBorderBottom: true};
}
if (TransactionUtils.hasPendingRTERViolation(TransactionUtils.getTransactionViolations(transaction?.transactionID ?? '', transactionViolations))) {
return {title: getStatusIcon(Expensicons.Hourglass), description: translate('iou.pendingMatchWithCreditCardDescription'), shouldShowBorderBottom: true};
}
};

const statusBarProps = getStatusBarProps();

useEffect(() => {
if (canDeleteRequest) {
return;
Expand Down Expand Up @@ -184,7 +217,7 @@ function MoneyRequestHeader({
<>
<View style={[styles.pl0]}>
<HeaderWithBackButton
shouldShowBorderBottom={!isScanning && !isPending && !isOnHold}
shouldShowBorderBottom={!statusBarProps && !isOnHold}
shouldShowReportAvatarWithDisplay
shouldEnableDetailPageNavigation
shouldShowPinButton={false}
Expand All @@ -199,40 +232,12 @@ function MoneyRequestHeader({
shouldShowBackButton={shouldUseNarrowLayout}
onBackButtonPress={onBackButtonPress}
/>
{isPending && (
<MoneyRequestHeaderStatusBar
title={
<Icon
src={Expensicons.CreditCardHourglass}
height={variables.iconSizeSmall}
width={variables.iconSizeSmall}
fill={theme.icon}
/>
}
description={translate('iou.transactionPendingDescription')}
shouldShowBorderBottom={!isScanning}
/>
)}
{isScanning && (
{statusBarProps && (
<MoneyRequestHeaderStatusBar
title={
<Icon
src={Expensicons.ReceiptScan}
height={variables.iconSizeSmall}
width={variables.iconSizeSmall}
fill={theme.icon}
/>
}
description={translate('iou.receiptScanInProgressDescription')}
shouldShowBorderBottom
/>
)}
{isOnHold && (
<MoneyRequestHeaderStatusBar
title={translate('iou.hold')}
description={translate('iou.expenseOnHold')}
shouldShowBorderBottom
danger
title={statusBarProps.title}
description={statusBarProps.description}
danger={statusBarProps.danger}
shouldShowBorderBottom={statusBarProps.shouldShowBorderBottom}
/>
)}
</View>
Expand All @@ -259,7 +264,7 @@ function MoneyRequestHeader({

MoneyRequestHeader.displayName = 'MoneyRequestHeader';

const MoneyRequestHeaderWithTransaction = withOnyx<MoneyRequestHeaderProps, Pick<MoneyRequestHeaderOnyxProps, 'transaction' | 'shownHoldUseExplanation'>>({
const MoneyRequestHeaderWithTransaction = withOnyx<MoneyRequestHeaderProps, Pick<MoneyRequestHeaderOnyxProps, 'transactionViolations' | 'transaction' | 'shownHoldUseExplanation'>>({
transaction: {
key: ({report, parentReportActions}) => {
const parentReportAction = (report.parentReportActionID && parentReportActions ? parentReportActions[report.parentReportActionID] : {}) as ReportAction & OriginalMessageIOU;
Expand All @@ -270,9 +275,15 @@ const MoneyRequestHeaderWithTransaction = withOnyx<MoneyRequestHeaderProps, Pick
key: ONYXKEYS.NVP_HOLD_USE_EXPLAINED,
initWithStoredValues: true,
},
transactionViolations: {
key: ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS,
},
})(MoneyRequestHeader);

export default withOnyx<Omit<MoneyRequestHeaderProps, 'transaction' | 'shownHoldUseExplanation'>, Omit<MoneyRequestHeaderOnyxProps, 'transaction' | 'shownHoldUseExplanation'>>({
export default withOnyx<
Omit<MoneyRequestHeaderProps, 'transactionViolations' | 'transaction' | 'shownHoldUseExplanation'>,
Omit<MoneyRequestHeaderOnyxProps, 'transactionViolations' | 'transaction' | 'shownHoldUseExplanation'>
>({
session: {
key: ONYXKEYS.SESSION,
},
Expand Down
2 changes: 2 additions & 0 deletions src/components/MoneyRequestHeaderStatusBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,5 @@ function MoneyRequestHeaderStatusBar({title, description, shouldShowBorderBottom
MoneyRequestHeaderStatusBar.displayName = 'MoneyRequestHeaderStatusBar';

export default MoneyRequestHeaderStatusBar;

export type {MoneyRequestHeaderStatusBarProps};
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ import CONST from '@src/CONST';
import type {IOUMessage} from '@src/types/onyx/OriginalMessage';
import type {EmptyObject} from '@src/types/utils/EmptyObject';
import {isEmptyObject} from '@src/types/utils/EmptyObject';
import type {MoneyRequestPreviewProps} from './types';
import type {MoneyRequestPreviewProps, PendingMessageProps} from './types';

function MoneyRequestPreviewContent({
iouReport,
Expand Down Expand Up @@ -84,7 +84,6 @@ function MoneyRequestPreviewContent({
const requestMerchant = truncate(merchant, {length: CONST.REQUEST_PREVIEW.MAX_LENGTH});
const hasReceipt = TransactionUtils.hasReceipt(transaction);
const isScanning = hasReceipt && TransactionUtils.isReceiptBeingScanned(transaction);
const isPending = TransactionUtils.isPending(transaction);
const isOnHold = TransactionUtils.isOnHold(transaction);
const isSettlementOrApprovalPartial = Boolean(iouReport?.pendingFields?.partial);
const isPartialHold = isSettlementOrApprovalPartial && isOnHold;
Expand Down Expand Up @@ -184,6 +183,21 @@ function MoneyRequestPreviewContent({
return message;
};

const getPendingMessageProps: () => PendingMessageProps = () => {
if (isScanning) {
return {shouldShow: true, messageIcon: ReceiptScan, messageDescription: translate('iou.receiptScanInProgress')};
}
if (TransactionUtils.isPending(transaction)) {
return {shouldShow: true, messageIcon: Expensicons.CreditCardHourglass, messageDescription: translate('iou.transactionPending')};
}
if (TransactionUtils.hasPendingUI(transaction, TransactionUtils.getTransactionViolations(transaction?.transactionID ?? '', transactionViolations))) {
return {shouldShow: true, messageIcon: Expensicons.Hourglass, messageDescription: translate('iou.pendingMatchWithCreditCard')};
}
return {shouldShow: false};
};

const pendingMessageProps = getPendingMessageProps();

const getDisplayAmountText = (): string => {
if (isScanning) {
return translate('iou.receiptScanning');
Expand Down Expand Up @@ -312,26 +326,15 @@ function MoneyRequestPreviewContent({
</Text>
)}
</View>
{isScanning && (
<View style={[styles.flexRow, styles.alignItemsCenter, styles.mt2]}>
<Icon
src={ReceiptScan}
height={variables.iconSizeExtraSmall}
width={variables.iconSizeExtraSmall}
fill={theme.textSupporting}
/>
<Text style={[styles.textMicroSupporting, styles.ml1, styles.amountSplitPadding]}>{translate('iou.receiptScanInProgress')}</Text>
</View>
)}
{isPending && (
{pendingMessageProps.shouldShow && (
<View style={[styles.flexRow, styles.alignItemsCenter, styles.mt2]}>
<Icon
src={Expensicons.CreditCardHourglass}
src={pendingMessageProps.messageIcon}
height={variables.iconSizeExtraSmall}
width={variables.iconSizeExtraSmall}
fill={theme.textSupporting}
fill={theme.icon}
/>
<Text style={[styles.textMicroSupporting, styles.ml1, styles.amountSplitPadding]}>{translate('iou.transactionPending')}</Text>
<Text style={[styles.textMicroSupporting, styles.ml1, styles.amountSplitPadding]}>{pendingMessageProps.messageDescription}</Text>
</View>
)}
</View>
Expand Down
18 changes: 17 additions & 1 deletion src/components/ReportActionItem/MoneyRequestPreview/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type {GestureResponderEvent, StyleProp, ViewStyle} from 'react-native';
import type {OnyxCollection, OnyxEntry} from 'react-native-onyx';
import type {ContextMenuAnchor} from '@pages/home/report/ContextMenu/ReportActionContextMenu';
import type * as OnyxTypes from '@src/types/onyx';
import type IconAsset from '@src/types/utils/IconAsset';

type MoneyRequestPreviewOnyxProps = {
/** All of the personal details for everyone */
Expand Down Expand Up @@ -71,4 +72,19 @@ type MoneyRequestPreviewProps = MoneyRequestPreviewOnyxProps & {
isWhisper?: boolean;
};

export type {MoneyRequestPreviewProps, MoneyRequestPreviewOnyxProps};
type NoPendingProps = {shouldShow: false};

type PendingProps = {
/** Whether to show the pending message or not */
shouldShow: true;
smelaa marked this conversation as resolved.
Show resolved Hide resolved

/** The icon to be displayed if a request is pending */
messageIcon: IconAsset;

/** The description to be displayed if a request is pending */
messageDescription: string;
};

type PendingMessageProps = PendingProps | NoPendingProps;

export type {MoneyRequestPreviewProps, MoneyRequestPreviewOnyxProps, PendingMessageProps};
Loading
Loading