diff --git a/src/components/ReportActionItem/MoneyRequestReportPreview/MoneyRequestReportPreviewContent.tsx b/src/components/ReportActionItem/MoneyRequestReportPreview/MoneyRequestReportPreviewContent.tsx index e4af0aa1e82e..073b5a8027b5 100644 --- a/src/components/ReportActionItem/MoneyRequestReportPreview/MoneyRequestReportPreviewContent.tsx +++ b/src/components/ReportActionItem/MoneyRequestReportPreview/MoneyRequestReportPreviewContent.tsx @@ -29,6 +29,7 @@ import ControlSelection from '@libs/ControlSelection'; import {convertToDisplayString} from '@libs/CurrencyUtils'; import {canUseTouchScreen} from '@libs/DeviceCapabilities'; import {getConnectedIntegration} from '@libs/PolicyUtils'; +import {getOriginalMessage, isActionOfType} from '@libs/ReportActionsUtils'; import { areAllRequestsBeingSmartScanned as areAllRequestsBeingSmartScannedReportUtils, canBeExported, @@ -107,6 +108,7 @@ function MoneyRequestReportPreviewContent({ renderItem, getCurrentWidth, reportPreviewStyles, + isInvoice, }: MoneyRequestReportPreviewContentProps) { const lastTransaction = transactions?.at(0); const transactionIDList = transactions?.map((reportTransaction) => reportTransaction.transactionID) ?? []; @@ -426,6 +428,14 @@ function MoneyRequestReportPreviewContent({ /> ); + const getPreviewName = () => { + if (isInvoice && isActionOfType(action, CONST.REPORT.ACTIONS.TYPE.REPORT_PREVIEW)) { + const originalMessage = getOriginalMessage(action); + return originalMessage && translate('iou.invoiceReportName', originalMessage); + } + return action.childReportName; + }; + return ( transactions.length > 0 && ( - {action.childReportName} + {getPreviewName()} {!doesReportNameOverflow && <> {approvedOrSettledicon}} @@ -522,17 +532,6 @@ function MoneyRequestReportPreviewContent({ )} - {shouldShowRBR && ( - - - - {translate('violations.reviewRequired')} - - - )} {}, onPaymentOptionsShow, onPaymentOptionsHide, + isInvoice = false, }: MoneyRequestReportPreviewProps) { const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); @@ -65,7 +66,7 @@ function MoneyRequestReportPreview({ isHovered={isHovered} iouReportID={iouReportID} onPreviewPressed={() => {}} - wrapperStyles={reportPreviewStyles.transactionPreviewStyle} + wrapperStyle={reportPreviewStyles.transactionPreviewStyle} containerStyles={[styles.h100, containerStyles]} transactionID={item.transactionID} /> @@ -98,6 +99,7 @@ function MoneyRequestReportPreview({ setCurrentWidth(e.nativeEvent.layout.width ?? 255); }} reportPreviewStyles={reportPreviewStyles} + isInvoice={isInvoice} /> ); } diff --git a/src/components/ReportActionItem/MoneyRequestReportPreview/types.ts b/src/components/ReportActionItem/MoneyRequestReportPreview/types.ts index c502df223adc..4d002e04f38d 100644 --- a/src/components/ReportActionItem/MoneyRequestReportPreview/types.ts +++ b/src/components/ReportActionItem/MoneyRequestReportPreview/types.ts @@ -4,11 +4,15 @@ import type {TransactionPreviewStyleType} from '@components/ReportActionItem/Tra import type {ContextMenuAnchor} from '@pages/home/report/ContextMenu/ReportActionContextMenu'; import type {PersonalDetails, Policy, Report, ReportAction, Transaction, TransactionViolation, TransactionViolations} from '@src/types/onyx'; +type TransactionPreviewStyle = { + [key in keyof TransactionPreviewStyleType]: number; +}; + type MoneyRequestReportPreviewStyleType = { flatListStyle: StyleProp; wrapperStyle: ViewStyle; contentContainerStyle: ViewStyle; - transactionPreviewStyle: TransactionPreviewStyleType; + transactionPreviewStyle: TransactionPreviewStyle; componentStyle: StyleProp; }; @@ -45,6 +49,9 @@ type MoneyRequestReportPreviewProps = { /** Whether the corresponding report action item is hovered */ isHovered?: boolean; + + /** Whether the report is an invoice preview */ + isInvoice?: boolean; }; type MoneyRequestReportPreviewContentOnyxProps = { diff --git a/src/components/ReportActionItem/TransactionPreview/TransactionPreviewContent.tsx b/src/components/ReportActionItem/TransactionPreview/TransactionPreviewContent.tsx index 0b79f75f9cbc..8859698fec95 100644 --- a/src/components/ReportActionItem/TransactionPreview/TransactionPreviewContent.tsx +++ b/src/components/ReportActionItem/TransactionPreview/TransactionPreviewContent.tsx @@ -49,7 +49,7 @@ function TransactionPreviewContent({ navigateToReviewFields, onPreviewPressed, containerStyles, - wrapperStyles, + wrapperStyle, isBillSplit, areThereDuplicates, sessionAccountID, @@ -205,7 +205,7 @@ function TransactionPreviewContent({ /> )} {shouldShowSkeleton ? ( - + ) : ( @@ -370,7 +370,7 @@ function TransactionPreviewContent({ accessibilityHint={convertToDisplayString(requestAmount, requestCurrency)} style={[ styles.moneyRequestPreviewBox, - wrapperStyles, + wrapperStyle, themeStyles, shouldDisableOnPress && styles.cursorDefault, (isIOUSettled || isApproved) && isSettlementOrApprovalPartial && styles.offlineFeedback.pending, diff --git a/src/components/ReportActionItem/TransactionPreview/types.ts b/src/components/ReportActionItem/TransactionPreview/types.ts index d8239c66f372..b785a1892b59 100644 --- a/src/components/ReportActionItem/TransactionPreview/types.ts +++ b/src/components/ReportActionItem/TransactionPreview/types.ts @@ -4,9 +4,10 @@ import type {ContextMenuAnchor} from '@pages/home/report/ContextMenu/ReportActio import type {PersonalDetailsList, Report, ReportAction, Transaction, TransactionViolations} from '@src/types/onyx'; import type {Errors} from '@src/types/onyx/OnyxCommon'; +// string type union is here for percentage values type TransactionPreviewStyleType = { - width: number; - maxWidth?: number; + width: number | string; + maxWidth?: number | string; }; type TransactionPreviewProps = { @@ -20,7 +21,7 @@ type TransactionPreviewProps = { reportID: string | undefined; /** Callback for the preview pressed */ - onPreviewPressed: (event?: GestureResponderEvent | KeyboardEvent) => void; + onPreviewPressed?: (event?: GestureResponderEvent | KeyboardEvent) => void; /** All the data of the action, used for showing context menu */ action: OnyxEntry; @@ -35,7 +36,7 @@ type TransactionPreviewProps = { containerStyles?: StyleProp; /** Optional custom styles to be applied to wrapper component. */ - wrapperStyles: TransactionPreviewStyleType; + wrapperStyle: TransactionPreviewStyleType; /** True if this IOU has a type of split */ isBillSplit: boolean; @@ -72,7 +73,7 @@ type TransactionPreviewContentProps = { navigateToReviewFields: () => void; /** General callback for handling presses on the preview component, can also handle keyboard events. */ - onPreviewPressed: (event?: GestureResponderEvent | KeyboardEvent | undefined) => void; + onPreviewPressed?: (event?: GestureResponderEvent | KeyboardEvent | undefined) => void; /** Whether the transaction is whisper. */ isWhisper?: boolean; @@ -84,7 +85,7 @@ type TransactionPreviewContentProps = { containerStyles?: StyleProp; /** Optional custom styles to be applied to wrapper component. */ - wrapperStyles: TransactionPreviewStyleType; + wrapperStyle: TransactionPreviewStyleType; /** Records any errors related to wallet terms. */ walletTermsErrors: Errors | undefined; diff --git a/src/components/TransactionPreviewSkeletonView.tsx b/src/components/TransactionPreviewSkeletonView.tsx index 959e7e8f0ca7..d8bbe784ae02 100644 --- a/src/components/TransactionPreviewSkeletonView.tsx +++ b/src/components/TransactionPreviewSkeletonView.tsx @@ -1,4 +1,5 @@ import React from 'react'; +import {View} from 'react-native'; import {Rect} from 'react-native-svg'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -6,52 +7,66 @@ import variables from '@styles/variables'; import SkeletonViewContentLoader from './SkeletonViewContentLoader'; type TransactionPreviewSkeletonViewProps = { - transactionPreviewWidth: number; + transactionPreviewWidth: number | string; }; function TransactionPreviewSkeletonView({transactionPreviewWidth}: TransactionPreviewSkeletonViewProps) { const theme = useTheme(); const styles = useThemeStyles(); + const isWidthANumber = typeof transactionPreviewWidth === 'number'; const {width, height} = { - width: transactionPreviewWidth - styles.p4.padding * 2, + width: isWidthANumber ? transactionPreviewWidth - styles.p4.padding * 2 : transactionPreviewWidth, height: variables.transactionPreviewSkeletonHeight, }; return ( - - - - - - + + + + + + + {/* This skeleton inverts the progress bar, which should be on the right, + so we don't need to know the width of the component to calculate it - works with percentages. + */} + + + + + + ); } diff --git a/src/languages/en.ts b/src/languages/en.ts index 66f9fc933f74..46a6f107cc63 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -2,6 +2,7 @@ import {CONST as COMMON_CONST} from 'expensify-common'; import startCase from 'lodash/startCase'; import CONST from '@src/CONST'; import type {Country} from '@src/CONST'; +import type OriginalMessage from '@src/types/onyx/OriginalMessage'; import type { AccountOwnerParams, ActionsAreCurrentlyRestricted, @@ -1047,6 +1048,7 @@ const translations = { `changed the ${translatedChangedField} to ${newMerchant} (previously ${oldMerchant}), which updated the amount to ${newAmountToDisplay} (previously ${oldAmountToDisplay})`, threadExpenseReportName: ({formattedAmount, comment}: ThreadRequestReportNameParams) => `${formattedAmount} ${comment ? `for ${comment}` : 'expense'}`, threadTrackReportName: ({formattedAmount, comment}: ThreadRequestReportNameParams) => `Tracking ${formattedAmount} ${comment ? `for ${comment}` : ''}`, + invoiceReportName: ({linkedReportID}: OriginalMessage) => `Invoice Report #${linkedReportID}`, threadPaySomeoneReportName: ({formattedAmount, comment}: ThreadSentMoneyReportNameParams) => `${formattedAmount} sent${comment ? ` for ${comment}` : ''}`, movedFromPersonalSpace: ({workspaceName, reportName}: MovedFromPersonalSpaceParams) => `moved expense from personal space to ${workspaceName ?? `chat with ${reportName}`}`, movedToPersonalSpace: 'moved expense to personal space', diff --git a/src/languages/es.ts b/src/languages/es.ts index a6388799c309..1025d898de6f 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -1,5 +1,6 @@ import {CONST as COMMON_CONST} from 'expensify-common'; import CONST from '@src/CONST'; +import type OriginalMessage from '@src/types/onyx/OriginalMessage'; import type en from './en'; import type { AccountOwnerParams, @@ -1045,6 +1046,7 @@ const translations = { `cambió la ${translatedChangedField} a ${newMerchant} (previamente ${oldMerchant}), lo que cambió el importe a ${newAmountToDisplay} (previamente ${oldAmountToDisplay})`, threadExpenseReportName: ({formattedAmount, comment}: ThreadRequestReportNameParams) => `${comment ? `${formattedAmount} para ${comment}` : `Gasto de ${formattedAmount}`}`, threadTrackReportName: ({formattedAmount, comment}: ThreadRequestReportNameParams) => `Seguimiento ${formattedAmount} ${comment ? `para ${comment}` : ''}`, + invoiceReportName: ({linkedReportID}: OriginalMessage) => `Informe de facturación #${linkedReportID}`, threadPaySomeoneReportName: ({formattedAmount, comment}: ThreadSentMoneyReportNameParams) => `${formattedAmount} enviado${comment ? ` para ${comment}` : ''}`, movedFromPersonalSpace: ({workspaceName, reportName}: MovedFromPersonalSpaceParams) => `movió el gasto desde su espacio personal a ${workspaceName ?? `un chat con ${reportName}`}`, movedToPersonalSpace: 'movió el gasto a su espacio personal', diff --git a/src/pages/home/report/PureReportActionItem.tsx b/src/pages/home/report/PureReportActionItem.tsx index b9e74bfc4ac8..930b744967e1 100644 --- a/src/pages/home/report/PureReportActionItem.tsx +++ b/src/pages/home/report/PureReportActionItem.tsx @@ -30,6 +30,7 @@ import MoneyRequestReportPreview from '@components/ReportActionItem/MoneyRequest import ReportPreview from '@components/ReportActionItem/ReportPreview'; import TaskAction from '@components/ReportActionItem/TaskAction'; import TaskPreview from '@components/ReportActionItem/TaskPreview'; +import TransactionPreview from '@components/ReportActionItem/TransactionPreview'; import TripRoomPreview from '@components/ReportActionItem/TripRoomPreview'; import {useSearchContext} from '@components/Search/SearchContext'; import {ShowContextMenuContext} from '@components/ShowContextMenuContext'; @@ -100,8 +101,10 @@ import { isReimbursementDeQueuedOrCanceledAction, isReimbursementQueuedAction, isRenamedAction, + isSplitBillAction as isSplitBillActionReportActionsUtils, isTagModificationAction, isTaskAction, + isTrackExpenseAction as isTrackExpenseActionReportActionsUtils, isTripPreview, isUnapprovedAction, isWhisperActionTargetedToOthers, @@ -426,6 +429,8 @@ function PureReportActionItem({ [StyleUtils, isReportActionLinked, theme.messageHighlightBG], ); + const reportPreviewStyles = StyleUtils.getMoneyRequestReportPreviewStyle(shouldUseNarrowLayout, undefined, true); + const isDeletedParentAction = isDeletedParentActionUtils(action); // IOUDetails only exists when we are sending money @@ -797,9 +802,26 @@ function PureReportActionItem({ /> ); - // Table Report View does not display these components as separate messages - if (canUseTableReportView) { - children = emptyHTML; + // Table Report View does not display these components as separate messages, except for self-DM + if (canUseTableReportView && report?.type === CONST.REPORT.TYPE.CHAT) { + if (report.chatType === CONST.REPORT.CHAT_TYPE.SELF_DM) { + children = ( + + {}} + isTrackExpense={isTrackExpenseActionReportActionsUtils(action)} + /> + + ); + } else { + children = emptyHTML; + } } } else if (isTripPreview(action)) { children = ( @@ -818,14 +840,14 @@ function PureReportActionItem({ } else if (action.actionName === CONST.REPORT.ACTIONS.TYPE.REPORT_PREVIEW && canUseTableReportView) { children = ( setIsPaymentMethodPopoverActive(true)} onPaymentOptionsHide={() => setIsPaymentMethodPopoverActive(false)} @@ -834,8 +856,7 @@ function PureReportActionItem({ } else if (action.actionName === CONST.REPORT.ACTIONS.TYPE.REPORT_PREVIEW) { children = ( = ({item}) => ( violations={item.errors ? violations : []} showContextMenu={() => undefined} offlineWithFeedbackOnClose={() => undefined} + onPreviewPressed={() => {}} navigateToReviewFields={() => undefined} - onPreviewPressed={() => true} isBillSplit={false} areThereDuplicates={false} sessionAccountID={11111111} walletTermsErrors={undefined} routeName={SCREENS.TRANSACTION_DUPLICATE.REVIEW} shouldHideOnDelete={false} - wrapperStyles={style.transactionPreviewStyle} + wrapperStyle={style.transactionPreviewStyle} containerStyles={[sizing.h100]} /> ); diff --git a/src/stories/TransactionPreviewContent.stories.tsx b/src/stories/TransactionPreviewContent.stories.tsx index b5cceeef83cd..df08d7e8ec66 100644 --- a/src/stories/TransactionPreviewContent.stories.tsx +++ b/src/stories/TransactionPreviewContent.stories.tsx @@ -112,7 +112,7 @@ const story: Meta = { walletTermsErrors: undefined, routeName: SCREENS.TRANSACTION_DUPLICATE.REVIEW, shouldHideOnDelete: false, - wrapperStyles: {width: 256}, + wrapperStyle: {width: 256}, }, argTypes: { ...disabledProperties, diff --git a/src/styles/index.ts b/src/styles/index.ts index e1d4c210c421..ca1d6cd266e0 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -3161,6 +3161,10 @@ const styles = (theme: ThemeColors) => transform: `rotate(180deg)`, }, + mirror: { + transform: `scaleX(-1)`, + }, + navigationScreenCardStyle: { backgroundColor: theme.appBG, height: '100%', diff --git a/tests/ui/MoneyRequestReportPreview.test.tsx b/tests/ui/MoneyRequestReportPreview.test.tsx index e94c113c2ebc..925dc3d64f4b 100644 --- a/tests/ui/MoneyRequestReportPreview.test.tsx +++ b/tests/ui/MoneyRequestReportPreview.test.tsx @@ -112,6 +112,7 @@ const mockOnyxTransactions: Record<`${typeof ONYXKEYS.COLLECTION.TRANSACTION}${s const mockOnyxViolations: Record<`${typeof ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${string}`, TransactionViolations> = { [`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${mockTransaction.transactionID}`]: mockViolations, + [`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${mockSecondTransaction.transactionID}`]: mockViolations, }; const arrayOfTransactions = Object.values(mockOnyxTransactions); @@ -150,7 +151,7 @@ describe('MoneyRequestReportPreview', () => { } }); - it('renders RBR for both transaction and report if there are violations', async () => { + it('renders RBR for every transaction with violations', async () => { renderPage({}); await waitForBatchedUpdatesWithAct(); await Onyx.multiSet({...mockOnyxTransactions, ...mockOnyxViolations});