Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import {navigationRef} from '@libs/Navigation/Navigation';
import {getIOUActionForTransactionID} from '@libs/ReportActionsUtils';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We missed a bunch of edge cases with this PR like selection of deleted expenses in offline mode, this caused #63083, be little more careful reviewing @allgandalf 😿

import {getMoneyRequestSpendBreakdown, isIOUReport} from '@libs/ReportUtils';
import {compareValues} from '@libs/SearchUIUtils';
import {getTransactionPendingAction} from '@libs/TransactionUtils';
import shouldShowTransactionYear from '@libs/TransactionUtils/shouldShowTransactionYear';
import Navigation from '@navigation/Navigation';
import variables from '@styles/variables';
Expand Down Expand Up @@ -122,6 +123,11 @@ function MoneyRequestReportTransactionList({
const formattedCompanySpendAmount = convertToDisplayString(nonReimbursableSpend, report?.currency);
const shouldShowBreakdown = !!nonReimbursableSpend && !!reimbursableSpend;

const pendingActionsOpacity = useMemo(() => {
const pendingAction = transactions.some(getTransactionPendingAction);
return pendingAction && styles.opacitySemiTransparent;
}, [styles.opacitySemiTransparent, transactions]);

const {bind} = useHover();
const {isMouseDownOnInput, setMouseUp} = useMouseContext();

Expand Down Expand Up @@ -228,6 +234,7 @@ function MoneyRequestReportTransactionList({
)}
<View style={[listHorizontalPadding, styles.gap2, styles.pb4]}>
{sortedTransactions.map((transaction) => {
const isPendingDelete = getTransactionPendingAction(transaction) === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE;
return (
<PressableWithFeedback
key={transaction.transactionID}
Expand Down Expand Up @@ -265,6 +272,7 @@ function MoneyRequestReportTransactionList({
setSelectedTransactionID(transaction.transactionID);
setIsModalVisible(true);
}}
disabled={isPendingDelete}
>
<TransactionItemRow
transactionItem={transaction}
Expand Down Expand Up @@ -314,7 +322,7 @@ function MoneyRequestReportTransactionList({
</Animated.Text>
<View style={[styles.dFlex, styles.flexRow, styles.alignItemsCenter, styles.pr3]}>
<Text style={[styles.mr3, styles.textLabelSupporting]}>{translate('common.total')}</Text>
<Text style={[shouldUseNarrowLayout ? styles.mnw64p : styles.mnw100p, styles.textAlignRight, styles.textBold]}>
<Text style={[shouldUseNarrowLayout ? styles.mnw64p : styles.mnw100p, styles.textAlignRight, styles.textBold, pendingActionsOpacity]}>
{convertToDisplayString(totalDisplaySpend, report?.currency)}
</Text>
</View>
Expand Down
83 changes: 73 additions & 10 deletions src/components/TransactionItemRow/TransactionItemRowRBR.tsx
Original file line number Diff line number Diff line change
@@ -1,34 +1,97 @@
import React from 'react';
import React, {useCallback} from 'react';
import type {ViewStyle} from 'react-native';
import {View} from 'react-native';
import Icon from '@components/Icon';
import {DotIndicator} from '@components/Icon/Expensicons';
import type {LocaleContextProps} from '@components/LocaleContextProvider';
import RenderHTML from '@components/RenderHTML';
import useLocalize from '@hooks/useLocalize';
import usePaginatedReportActions from '@hooks/usePaginatedReportActions';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import useTransactionViolations from '@hooks/useTransactionViolations';
import {isReceiptError} from '@libs/ErrorUtils';
import {getIOUActionForTransactionID} from '@libs/ReportActionsUtils';
import ViolationsUtils from '@libs/Violations/ViolationsUtils';
import variables from '@styles/variables';
import type {Errors} from '@src/types/onyx/OnyxCommon';
import type ReportAction from '@src/types/onyx/ReportAction';
import type Transaction from '@src/types/onyx/Transaction';
import type {ReceiptError, ReceiptErrors} from '@src/types/onyx/Transaction';

function TransactionItemRowRBR({transaction, containerStyles}: {transaction: Transaction; containerStyles?: ViewStyle[]}) {
type TransactionItemRowRBRProps = {
/** Transaction item */
transaction: Transaction;

/** Styles for the RBR messages container */
containerStyles?: ViewStyle[];
};

/**
* Extracts unique error messages from errors and actions
*/
const extractErrorMessages = (errors: Errors | ReceiptErrors, errorActions: ReportAction[], translate: LocaleContextProps['translate']): string[] => {
const uniqueMessages = new Set<string>();

// Combine transaction and action errors
let allErrors: Record<string, string | Errors | ReceiptError | null | undefined> = {...errors};
errorActions.forEach((action) => {
if (!action.errors) {
return;
}
allErrors = {...allErrors, ...action.errors};
});

// Extract error messages
Object.values(allErrors).forEach((errorValue) => {
if (!errorValue) {
return;
}
if (typeof errorValue === 'string') {
uniqueMessages.add(errorValue);
} else if (isReceiptError(errorValue)) {
uniqueMessages.add(translate('iou.error.receiptFailureMessageShort'));
} else {
Object.values(errorValue).forEach((nestedErrorValue) => {
if (!nestedErrorValue) {
return;
}
uniqueMessages.add(nestedErrorValue);
});
}
});

return Array.from(uniqueMessages);
};

function TransactionItemRowRBR({transaction, containerStyles}: TransactionItemRowRBRProps) {
const styles = useThemeStyles();
const transactionViolations = useTransactionViolations(transaction?.transactionID);
const {translate} = useLocalize();
const theme = useTheme();

// Some violations end with a period already so lets make sure the connected messages have only single period between them
// and end with a single dot.
const RBRMessages = transactionViolations
.map((violation) => {
const {sortedAllReportActions: transactionActions} = usePaginatedReportActions(transaction.reportID);
const transactionThreadId = transactionActions ? getIOUActionForTransactionID(transactionActions, transaction.transactionID)?.childReportID : undefined;
const {sortedAllReportActions: transactionThreadActions} = usePaginatedReportActions(transactionThreadId);
const getErrorMessages = useCallback(
(errors: Errors | ReceiptErrors | undefined = {}, errorActions: ReportAction[] | undefined = []) => extractErrorMessages(errors, errorActions, translate),
[translate],
);

const RBRMessages = [
...getErrorMessages(
transaction?.errors,
transactionThreadActions?.filter((e) => !!e.errors),
),
// Some violations end with a period already so lets make sure the connected messages have only single period between them
// and end with a single dot.
...transactionViolations.map((violation) => {
Comment on lines +81 to +88

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This array was missing FE-based required errors for fields like Merchant, leading to this issue:

which we addressed by including these FE-based required errors.

const message = ViolationsUtils.getViolationTranslation(violation, translate);
return message.endsWith('.') || transactionViolations.length === 1 ? message : `${message}.`;
})
.join(' ');

}),
].join(' ');
return (
transactionViolations.length > 0 && (
RBRMessages.length > 0 && (
<View style={[styles.flexRow, styles.alignItemsCenter, styles.gap1, containerStyles, styles.w100]}>
<Icon
src={DotIndicator}
Expand Down
Loading
Loading