From c3268eff35a8c6af361ae2e1d0b6795431427219 Mon Sep 17 00:00:00 2001 From: Trevor Coleman Date: Wed, 3 Jan 2024 14:18:38 -0500 Subject: [PATCH 01/53] feat(Violations): add transaction violation message to MoneyRequestPreview. --- .../ReportActionItem/MoneyRequestPreview.js | 17 ++++++++++++++++- src/languages/en.ts | 1 + src/languages/es.ts | 1 + src/libs/TransactionUtils.ts | 5 +++++ 4 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/components/ReportActionItem/MoneyRequestPreview.js b/src/components/ReportActionItem/MoneyRequestPreview.js index f0e818ddff4d..71b306043ecc 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview.js +++ b/src/components/ReportActionItem/MoneyRequestPreview.js @@ -28,6 +28,7 @@ import * as ReceiptUtils from '@libs/ReceiptUtils'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; import * as TransactionUtils from '@libs/TransactionUtils'; +import {transactionViolationsPropType} from '@libs/Violations/propTypes'; import walletTermsPropTypes from '@pages/EnablePayments/walletTermsPropTypes'; import reportActionPropTypes from '@pages/home/report/reportActionPropTypes'; import iouReportPropTypes from '@pages/iouReportPropTypes'; @@ -104,6 +105,9 @@ const propTypes = { /** Whether a message is a whisper */ isWhisper: PropTypes.bool, + + /** All transactionViolations */ + transactionViolations: transactionViolationsPropType, }; const defaultProps = { @@ -123,6 +127,7 @@ const defaultProps = { transaction: {}, shouldShowPendingConversionMessage: false, isWhisper: false, + transactionViolations: {}, }; function MoneyRequestPreview(props) { @@ -155,7 +160,8 @@ function MoneyRequestPreview(props) { const description = requestComment; const hasReceipt = TransactionUtils.hasReceipt(props.transaction); const isScanning = hasReceipt && TransactionUtils.isReceiptBeingScanned(props.transaction); - const hasFieldErrors = TransactionUtils.hasMissingSmartscanFields(props.transaction); + const hasViolations = TransactionUtils.hasViolation(props.transaction.transactionId, props.transactionViolations); + const hasFieldErrors = TransactionUtils.hasMissingSmartscanFields(props.transaction) || hasViolations; const isDistanceRequest = TransactionUtils.isDistanceRequest(props.transaction); const isExpensifyCardTransaction = TransactionUtils.isExpensifyCardTransaction(props.transaction); const isSettled = ReportUtils.isSettled(props.iouReport.reportID); @@ -208,6 +214,12 @@ function MoneyRequestPreview(props) { } let message = translate('iou.cash'); + if (hasViolations) { + const violations = TransactionUtils.getTransactionViolations(props.transaction.transactionId, props.transactionViolations); + const firstViolationName = translate(`violations.${violations[0].name}`); + const isTooLong = violations.length > 1 || firstViolationName.length > 15; + message += ` • ${isTooLong ? translate('violations.reviewRequired') : firstViolationName}`; + } if (ReportUtils.isGroupPolicyExpenseReport(props.iouReport) && ReportUtils.isReportApproved(props.iouReport) && !ReportUtils.isSettled(props.iouReport)) { message += ` • ${translate('iou.approved')}`; } else if (props.iouReport.isWaitingOnBankAccount) { @@ -390,4 +402,7 @@ export default withOnyx({ walletTerms: { key: ONYXKEYS.WALLET_TERMS, }, + transactionViolations: { + key: ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS, + }, })(MoneyRequestPreview); diff --git a/src/languages/en.ts b/src/languages/en.ts index 6e177c1df141..654454939a8b 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -2074,6 +2074,7 @@ export default { perDayLimit: ({limit}: ViolationsPerDayLimitParams) => `Amount over daily ${limit}/person category limit`, receiptNotSmartScanned: 'Receipt not verified. Please confirm accuracy.', receiptRequired: ({amount, category}: ViolationsReceiptRequiredParams) => `Receipt required over ${amount} ${category ? ' category limit' : ''}`, + reviewRequired: 'dummy.violations.reviewRequired.EN', rter: ({brokenBankConnection, email, isAdmin, isTransactionOlderThan7Days, member}: ViolationsRterParams) => { if (brokenBankConnection) { return isAdmin diff --git a/src/languages/es.ts b/src/languages/es.ts index 990554b0b502..cce9b6a98eca 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -2561,6 +2561,7 @@ export default { perDayLimit: ({limit}: ViolationsPerDayLimitParams) => `Importe supera el límite diario de la categoría de ${limit}/persona`, receiptNotSmartScanned: 'Recibo no verificado. Por favor, confirma su exactitud', receiptRequired: ({amount, category}: ViolationsReceiptRequiredParams) => `Recibo obligatorio para importes sobre ${category ? 'el limite de la categoría de ' : ''}${amount}`, + reviewRequired: 'dummy.violations.reviewRequired>ES', rter: ({brokenBankConnection, isAdmin, email, isTransactionOlderThan7Days, member}: ViolationsRterParams) => { if (brokenBankConnection) { return isAdmin diff --git a/src/libs/TransactionUtils.ts b/src/libs/TransactionUtils.ts index 5cb962b27cdc..d669cc0d917d 100644 --- a/src/libs/TransactionUtils.ts +++ b/src/libs/TransactionUtils.ts @@ -526,6 +526,10 @@ function hasViolation(transactionID: string, transactionViolations: TransactionV return Boolean(transactionViolations[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transactionID]?.some((violation: TransactionViolation) => violation.type === 'violation')); } +function getTransactionViolations(transactionID: string, transactionViolations: TransactionViolations): TransactionViolation[] | null { + return (transactionViolations[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transactionID] as TransactionViolation[]) ?? null; +} + /** * this is the formulae to calculate tax */ @@ -564,6 +568,7 @@ export { getCategory, getBillable, getTag, + getTransactionViolations, getLinkedTransaction, getAllReportTransactions, hasReceipt, From 54006d9ef2e37eb2cbfbf1c1eb4cfbcaf61714f1 Mon Sep 17 00:00:00 2001 From: Trevor Coleman Date: Wed, 3 Jan 2024 16:59:57 -0500 Subject: [PATCH 02/53] feat(Violations): fix broken import --- src/components/ViolationMessages.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ViolationMessages.tsx b/src/components/ViolationMessages.tsx index 310c2deafee5..41ad44a54381 100644 --- a/src/components/ViolationMessages.tsx +++ b/src/components/ViolationMessages.tsx @@ -2,7 +2,7 @@ import React, {useMemo} from 'react'; import {View} from 'react-native'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; -import ViolationsUtils from '@libs/ViolationsUtils'; +import ViolationsUtils from '@libs/Violations/ViolationsUtils'; import {TransactionViolation} from '@src/types/onyx'; import Text from './Text'; From 92f340a97955f06f3b73a7e55e059566215ad794 Mon Sep 17 00:00:00 2001 From: Trevor Coleman Date: Wed, 3 Jan 2024 17:00:54 -0500 Subject: [PATCH 03/53] feat(Violations): refactor for consistency and readability --- src/components/ReportActionItem/MoneyRequestPreview.js | 10 +++++----- src/libs/TransactionUtils.ts | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestPreview.js b/src/components/ReportActionItem/MoneyRequestPreview.js index 71b306043ecc..4a79fc5f6232 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview.js +++ b/src/components/ReportActionItem/MoneyRequestPreview.js @@ -160,7 +160,7 @@ function MoneyRequestPreview(props) { const description = requestComment; const hasReceipt = TransactionUtils.hasReceipt(props.transaction); const isScanning = hasReceipt && TransactionUtils.isReceiptBeingScanned(props.transaction); - const hasViolations = TransactionUtils.hasViolation(props.transaction.transactionId, props.transactionViolations); + const hasViolations = TransactionUtils.hasViolation(props.transaction, props.transactionViolations); const hasFieldErrors = TransactionUtils.hasMissingSmartscanFields(props.transaction) || hasViolations; const isDistanceRequest = TransactionUtils.isDistanceRequest(props.transaction); const isExpensifyCardTransaction = TransactionUtils.isExpensifyCardTransaction(props.transaction); @@ -215,10 +215,10 @@ function MoneyRequestPreview(props) { let message = translate('iou.cash'); if (hasViolations) { - const violations = TransactionUtils.getTransactionViolations(props.transaction.transactionId, props.transactionViolations); - const firstViolationName = translate(`violations.${violations[0].name}`); - const isTooLong = violations.length > 1 || firstViolationName.length > 15; - message += ` • ${isTooLong ? translate('violations.reviewRequired') : firstViolationName}`; + const violations = TransactionUtils.getTransactionViolations(props.transaction, props.transactionViolations); + const violation = translate(`violations.${violations[0]?.name}`, violations[0]?.data); + const isTooLong = violations?.length > 1 || violation.length > 15; + message += ` • ${isTooLong ? translate('violations.reviewRequired') : violation}`; } if (ReportUtils.isGroupPolicyExpenseReport(props.iouReport) && ReportUtils.isReportApproved(props.iouReport) && !ReportUtils.isSettled(props.iouReport)) { message += ` • ${translate('iou.approved')}`; diff --git a/src/libs/TransactionUtils.ts b/src/libs/TransactionUtils.ts index d669cc0d917d..63f831b81706 100644 --- a/src/libs/TransactionUtils.ts +++ b/src/libs/TransactionUtils.ts @@ -522,11 +522,11 @@ function getRecentTransactions(transactions: Record, size = 2): /** * Checks if any violations for the provided transaction are of type 'violation' */ -function hasViolation(transactionID: string, transactionViolations: TransactionViolations): boolean { - return Boolean(transactionViolations[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transactionID]?.some((violation: TransactionViolation) => violation.type === 'violation')); +function hasViolation(transaction: Transaction, transactionViolations: TransactionViolations): boolean { + return Boolean(transactionViolations[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transaction.transactionID]?.some((violation: TransactionViolation) => violation.type === 'violation')); } -function getTransactionViolations(transactionID: string, transactionViolations: TransactionViolations): TransactionViolation[] | null { +function getTransactionViolations({transactionID}: Transaction, transactionViolations: TransactionViolations): TransactionViolation[] | null { return (transactionViolations[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transactionID] as TransactionViolation[]) ?? null; } From 48c8083a6cf09c95e2e45f9667fb8a76a58bae8b Mon Sep 17 00:00:00 2001 From: Trevor Coleman Date: Wed, 3 Jan 2024 17:03:48 -0500 Subject: [PATCH 04/53] feat(Violations): make dummy string < 15 chars> --- src/languages/en.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index 654454939a8b..08f38fb58e3b 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -2074,7 +2074,7 @@ export default { perDayLimit: ({limit}: ViolationsPerDayLimitParams) => `Amount over daily ${limit}/person category limit`, receiptNotSmartScanned: 'Receipt not verified. Please confirm accuracy.', receiptRequired: ({amount, category}: ViolationsReceiptRequiredParams) => `Receipt required over ${amount} ${category ? ' category limit' : ''}`, - reviewRequired: 'dummy.violations.reviewRequired.EN', + reviewRequired: 'XreviewRequired', rter: ({brokenBankConnection, email, isAdmin, isTransactionOlderThan7Days, member}: ViolationsRterParams) => { if (brokenBankConnection) { return isAdmin From 2b51f6a68b802890c463b140b2ec3e5aaf99bcd4 Mon Sep 17 00:00:00 2001 From: Trevor Coleman Date: Wed, 3 Jan 2024 17:09:26 -0500 Subject: [PATCH 05/53] fix lint --- src/components/ReportActionItem/MoneyRequestPreview.js | 4 ++-- src/libs/Violations/ViolationsUtils.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestPreview.js b/src/components/ReportActionItem/MoneyRequestPreview.js index 4a79fc5f6232..5456f29f9109 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview.js +++ b/src/components/ReportActionItem/MoneyRequestPreview.js @@ -216,8 +216,8 @@ function MoneyRequestPreview(props) { let message = translate('iou.cash'); if (hasViolations) { const violations = TransactionUtils.getTransactionViolations(props.transaction, props.transactionViolations); - const violation = translate(`violations.${violations[0]?.name}`, violations[0]?.data); - const isTooLong = violations?.length > 1 || violation.length > 15; + const violation = translate(`violations.${violations[0].name}`, violations[0].data); + const isTooLong = violations.length > 1 || violation.length > 15; message += ` • ${isTooLong ? translate('violations.reviewRequired') : violation}`; } if (ReportUtils.isGroupPolicyExpenseReport(props.iouReport) && ReportUtils.isReportApproved(props.iouReport) && !ReportUtils.isSettled(props.iouReport)) { diff --git a/src/libs/Violations/ViolationsUtils.ts b/src/libs/Violations/ViolationsUtils.ts index 09c97ff1133c..0dd467220352 100644 --- a/src/libs/Violations/ViolationsUtils.ts +++ b/src/libs/Violations/ViolationsUtils.ts @@ -1,9 +1,9 @@ import reject from 'lodash/reject'; import Onyx from 'react-native-onyx'; +import {Phrase, PhraseParameters} from '@libs/Localize'; import {TranslationPaths} from '@src/languages/types'; import ONYXKEYS from '@src/ONYXKEYS'; import {PolicyCategories, PolicyTags, Transaction, TransactionViolation} from '@src/types/onyx'; -import {Phrase, PhraseParameters} from './Localize'; const ViolationsUtils = { /** From 062a878ea38a88e8151c83cf8f74aede1459243e Mon Sep 17 00:00:00 2001 From: Trevor Coleman Date: Thu, 4 Jan 2024 17:09:42 -0500 Subject: [PATCH 06/53] feat(Violations): add translation --- src/languages/en.ts | 2 +- src/languages/es.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index 08f38fb58e3b..2e1dbacb5bc3 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -2074,7 +2074,7 @@ export default { perDayLimit: ({limit}: ViolationsPerDayLimitParams) => `Amount over daily ${limit}/person category limit`, receiptNotSmartScanned: 'Receipt not verified. Please confirm accuracy.', receiptRequired: ({amount, category}: ViolationsReceiptRequiredParams) => `Receipt required over ${amount} ${category ? ' category limit' : ''}`, - reviewRequired: 'XreviewRequired', + reviewRequired: 'Review required', rter: ({brokenBankConnection, email, isAdmin, isTransactionOlderThan7Days, member}: ViolationsRterParams) => { if (brokenBankConnection) { return isAdmin diff --git a/src/languages/es.ts b/src/languages/es.ts index cce9b6a98eca..e264595ad978 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -2561,7 +2561,7 @@ export default { perDayLimit: ({limit}: ViolationsPerDayLimitParams) => `Importe supera el límite diario de la categoría de ${limit}/persona`, receiptNotSmartScanned: 'Recibo no verificado. Por favor, confirma su exactitud', receiptRequired: ({amount, category}: ViolationsReceiptRequiredParams) => `Recibo obligatorio para importes sobre ${category ? 'el limite de la categoría de ' : ''}${amount}`, - reviewRequired: 'dummy.violations.reviewRequired>ES', + reviewRequired: 'Revisión requerida', rter: ({brokenBankConnection, isAdmin, email, isTransactionOlderThan7Days, member}: ViolationsRterParams) => { if (brokenBankConnection) { return isAdmin From 3ea41321b126be3b4005a58d539ef79c3bad2da7 Mon Sep 17 00:00:00 2001 From: Trevor Coleman Date: Tue, 9 Jan 2024 15:04:03 -0500 Subject: [PATCH 07/53] feat(Violations): fix import --- src/libs/Violations/ViolationsUtils.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/libs/Violations/ViolationsUtils.ts b/src/libs/Violations/ViolationsUtils.ts index f9971e713649..86f60ed04aa5 100644 --- a/src/libs/Violations/ViolationsUtils.ts +++ b/src/libs/Violations/ViolationsUtils.ts @@ -1,10 +1,9 @@ import reject from 'lodash/reject'; import Onyx from 'react-native-onyx'; +import type {Phrase, PhraseParameters} from '@libs/Localize'; import type {TranslationPaths} from '@src/languages/types'; import ONYXKEYS from '@src/ONYXKEYS'; import type {PolicyCategories, PolicyTags, Transaction, TransactionViolation} from '@src/types/onyx'; -import type {Phrase, PhraseParameters} from '../Localize'; - const ViolationsUtils = { /** @@ -135,7 +134,7 @@ const ViolationsUtils = { case 'nonExpensiworksExpense': return translate('violations.nonExpensiworksExpense'); case 'overAutoApprovalLimit': - return translate('violations.overAutoApprovalLimit', {formattedLimitAmount: violation.data?.formattedLimitAmount ?? ''}); + return translate('violations.overAutoApprovalLimit', {formattedLimitAmount: violation.data?.formattedLimit ?? ''}); case 'overCategoryLimit': return translate('violations.overCategoryLimit', {categoryLimit: violation.data?.categoryLimit ?? ''}); case 'overLimit': @@ -148,7 +147,7 @@ const ViolationsUtils = { return translate('violations.receiptNotSmartScanned'); case 'receiptRequired': return translate('violations.receiptRequired', { - amount: violation.data?.amount ?? '0', + formattedLimit: violation.data?.formattedLimit ?? '0', category: violation.data?.category ?? '', }); case 'rter': From dd39fd141627e40c0e3c7647d1511551b9ebec5f Mon Sep 17 00:00:00 2001 From: Trevor Coleman Date: Tue, 9 Jan 2024 15:05:28 -0500 Subject: [PATCH 08/53] feat(Violations): move condition outside of component body to allow memoization --- .../ReportActionItem/MoneyRequestPreview.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestPreview.js b/src/components/ReportActionItem/MoneyRequestPreview.js index 6b145d86d80a..c14175689934 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview.js +++ b/src/components/ReportActionItem/MoneyRequestPreview.js @@ -131,6 +131,13 @@ const defaultProps = { transactionViolations: {}, }; +// We should not render the component if there is no iouReport and it's not a split. +// Moved outside of the component scope to allow memoization of values later. +function MoneyRequestPreviewWrapper(props) { + // eslint-disable-next-line react/jsx-props-no-spreading + return _.isEmpty(props.iouReport) && !props.isBillSplit ? null : ; +} + function MoneyRequestPreview(props) { const theme = useTheme(); const styles = useThemeStyles(); @@ -138,10 +145,6 @@ function MoneyRequestPreview(props) { const {translate} = useLocalize(); const {isSmallScreenWidth, windowWidth} = useWindowDimensions(); - if (_.isEmpty(props.iouReport) && !props.isBillSplit) { - return null; - } - const sessionAccountID = lodashGet(props.session, 'accountID', null); const managerID = props.iouReport.managerID || ''; const ownerAccountID = props.iouReport.ownerAccountID || ''; From d037f7d7a27e347524b1bfd2f648307daf205a9a Mon Sep 17 00:00:00 2001 From: Trevor Coleman Date: Tue, 9 Jan 2024 15:07:47 -0500 Subject: [PATCH 09/53] feat(Violations): update translations to handle null cases. correct data prop names. --- src/languages/en.ts | 4 ++-- src/languages/es.ts | 5 +++-- src/languages/types.ts | 4 ++-- src/types/onyx/TransactionViolation.ts | 2 +- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index 8e94aecc1b79..1e723ea54cfe 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -2078,7 +2078,7 @@ export default { maxAge: ({maxAge}: ViolationsMaxAgeParams) => `Date older than ${maxAge} days`, missingCategory: 'Missing category', missingComment: 'Description required for selected category', - missingTag: ({tagName}: ViolationsMissingTagParams) => `Missing ${tagName ?? 'tag'}`, + missingTag: (params: ViolationsMissingTagParams) => `Missing ${params?.tagName ?? 'tag'}`, modifiedAmount: 'Amount greater than scanned receipt', modifiedDate: 'Date differs from scanned receipt', nonExpensiworksExpense: 'Non-Expensiworks expense', @@ -2088,7 +2088,7 @@ export default { overLimitAttendee: ({amount}: ViolationsOverLimitParams) => `Amount over ${amount}/person limit`, perDayLimit: ({limit}: ViolationsPerDayLimitParams) => `Amount over daily ${limit}/person category limit`, receiptNotSmartScanned: 'Receipt not verified. Please confirm accuracy.', - receiptRequired: ({amount, category}: ViolationsReceiptRequiredParams) => `Receipt required over ${amount} ${category ? ' category limit' : ''}`, + receiptRequired: (params: ViolationsReceiptRequiredParams) => `Receipt required${params ? ` over ${params.formattedLimit}${params.category ? ` category limit` : ''}` : ''}`, reviewRequired: 'Review required', rter: ({brokenBankConnection, email, isAdmin, isTransactionOlderThan7Days, member}: ViolationsRterParams) => { if (brokenBankConnection) { diff --git a/src/languages/es.ts b/src/languages/es.ts index 8760ef652d1d..cead5beb9c2a 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -2565,7 +2565,7 @@ export default { maxAge: ({maxAge}: ViolationsMaxAgeParams) => `Fecha de más de ${maxAge} días`, missingCategory: 'Falta categoría', missingComment: 'Descripción obligatoria para categoría seleccionada', - missingTag: ({tagName}: ViolationsMissingTagParams) => `Falta ${tagName}`, + missingTag: (params: ViolationsMissingTagParams) => `Falta ${params?.tagName ?? 'etiqueta'}`, modifiedAmount: 'Importe superior al del recibo escaneado', modifiedDate: 'Fecha difiere del recibo escaneado', nonExpensiworksExpense: 'Gasto no es de Expensiworks', @@ -2575,7 +2575,8 @@ export default { overLimitAttendee: ({amount}: ViolationsOverLimitParams) => `Importe supera el límite de ${amount}/persona`, perDayLimit: ({limit}: ViolationsPerDayLimitParams) => `Importe supera el límite diario de la categoría de ${limit}/persona`, receiptNotSmartScanned: 'Recibo no verificado. Por favor, confirma su exactitud', - receiptRequired: ({amount, category}: ViolationsReceiptRequiredParams) => `Recibo obligatorio para importes sobre ${category ? 'el limite de la categoría de ' : ''}${amount}`, + receiptRequired: (params: ViolationsReceiptRequiredParams) => + `Recibo obligatorio${params ? ` para importes sobre${params.category ? ` el limite de la categoría de` : ''} ${params.formattedLimit}` : ''}`, reviewRequired: 'Revisión requerida', rter: ({brokenBankConnection, isAdmin, email, isTransactionOlderThan7Days, member}: ViolationsRterParams) => { if (brokenBankConnection) { diff --git a/src/languages/types.ts b/src/languages/types.ts index 3185b7a8f6f1..e69b4bd49686 100644 --- a/src/languages/types.ts +++ b/src/languages/types.ts @@ -223,7 +223,7 @@ type ViolationsInvoiceMarkupParams = {invoiceMarkup?: number}; type ViolationsMaxAgeParams = {maxAge: number}; -type ViolationsMissingTagParams = {tagName?: string}; +type ViolationsMissingTagParams = {tagName?: string} | null; type ViolationsOverAutoApprovalLimitParams = {formattedLimitAmount: string}; @@ -233,7 +233,7 @@ type ViolationsOverLimitParams = {amount: string}; type ViolationsPerDayLimitParams = {limit: string}; -type ViolationsReceiptRequiredParams = {amount: string; category?: string}; +type ViolationsReceiptRequiredParams = {formattedLimit: string; category?: string} | null; type ViolationsRterParams = { brokenBankConnection: boolean; diff --git a/src/types/onyx/TransactionViolation.ts b/src/types/onyx/TransactionViolation.ts index 253e2e8e2a29..b01a5fe38a62 100644 --- a/src/types/onyx/TransactionViolation.ts +++ b/src/types/onyx/TransactionViolation.ts @@ -18,7 +18,7 @@ type TransactionViolation = { invoiceMarkup?: number; maxAge?: number; tagName?: string; - formattedLimitAmount?: string; + formattedLimit?: string; categoryLimit?: string; limit?: string; category?: string; From 3b87f1343846608b7be3d312250bf8b4fc692b6c Mon Sep 17 00:00:00 2001 From: Trevor Coleman Date: Tue, 9 Jan 2024 15:36:39 -0500 Subject: [PATCH 10/53] feat(Violations): filter violations to remove 'notification' type messages --- src/components/ReportActionItem/MoneyRequestPreview.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ReportActionItem/MoneyRequestPreview.js b/src/components/ReportActionItem/MoneyRequestPreview.js index c14175689934..dcc342e795d6 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview.js +++ b/src/components/ReportActionItem/MoneyRequestPreview.js @@ -222,7 +222,7 @@ function MoneyRequestPreview(props) { if (hasViolations) { const violations = TransactionUtils.getTransactionViolations(props.transaction, props.transactionViolations); const violation = translate(`violations.${violations[0].name}`, violations[0].data); - const isTooLong = violations.length > 1 || violation.length > 15; + const isTooLong = _.filter(violations, (v) => v.type === 'violation').length > 1 || violation.length > 15; message += ` • ${isTooLong ? translate('violations.reviewRequired') : violation}`; } if (ReportUtils.isPaidGroupPolicyExpenseReport(props.iouReport) && ReportUtils.isReportApproved(props.iouReport) && !ReportUtils.isSettled(props.iouReport)) { From ebcff3806996d04dcc8fb5b4dbf78e4f8d3b02d4 Mon Sep 17 00:00:00 2001 From: Trevor Coleman Date: Tue, 9 Jan 2024 15:37:09 -0500 Subject: [PATCH 11/53] feat(Violations): memoize preview header text --- .../ReportActionItem/MoneyRequestPreview.js | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestPreview.js b/src/components/ReportActionItem/MoneyRequestPreview.js index dcc342e795d6..4cbb9330f962 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview.js +++ b/src/components/ReportActionItem/MoneyRequestPreview.js @@ -1,7 +1,7 @@ import {truncate} from 'lodash'; import lodashGet from 'lodash/get'; import PropTypes from 'prop-types'; -import React from 'react'; +import React, {useMemo} from 'react'; import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; @@ -197,7 +197,7 @@ function MoneyRequestPreview(props) { showContextMenuForReport(event, props.contextMenuAnchor, props.chatReportID, props.action, props.checkIfContextMenuActive); }; - const getPreviewHeaderText = () => { + const previewHeaderText = useMemo(() => { if (isDistanceRequest) { return translate('common.distance'); } @@ -232,8 +232,20 @@ function MoneyRequestPreview(props) { } else if (props.iouReport.isCancelledIOU) { message += ` • ${translate('iou.canceled')}`; } - return message; - }; + return message + (isSettled && !props.iouReport.isCancelledIOU ? ` • ${getSettledMessage()}` : ''); + }, [ + getSettledMessage, + hasViolations, + isDistanceRequest, + isExpensifyCardTransaction, + isScanning, + isSettled, + props.iouReport, + props.isBillSplit, + props.transaction, + props.transactionViolations, + translate, + ]); const getDisplayAmountText = () => { if (isDistanceRequest) { @@ -294,9 +306,7 @@ function MoneyRequestPreview(props) { ) : ( - - {getPreviewHeaderText() + (isSettled && !props.iouReport.isCancelledIOU ? ` • ${getSettledMessage()}` : '')} - + {previewHeaderText} {!isSettled && hasFieldErrors && ( Date: Tue, 9 Jan 2024 15:38:04 -0500 Subject: [PATCH 12/53] feat(Violations): add proptypes and call wrapper component --- src/components/ReportActionItem/MoneyRequestPreview.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/components/ReportActionItem/MoneyRequestPreview.js b/src/components/ReportActionItem/MoneyRequestPreview.js index 4cbb9330f962..5c7d89f535b3 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview.js +++ b/src/components/ReportActionItem/MoneyRequestPreview.js @@ -397,6 +397,9 @@ function MoneyRequestPreview(props) { MoneyRequestPreview.propTypes = propTypes; MoneyRequestPreview.defaultProps = defaultProps; MoneyRequestPreview.displayName = 'MoneyRequestPreview'; +MoneyRequestPreviewWrapper.propTypes = propTypes; +MoneyRequestPreviewWrapper.defaultProps = defaultProps; +MoneyRequestPreviewWrapper.displayName = 'MoneyRequestPreviewWrapper'; export default withOnyx({ personalDetails: { @@ -420,4 +423,4 @@ export default withOnyx({ transactionViolations: { key: ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS, }, -})(MoneyRequestPreview); +})(MoneyRequestPreviewWrapper); From cd0f3a81fd156581b1d3b2dc80f83d2ffaa32899 Mon Sep 17 00:00:00 2001 From: Trevor Coleman Date: Tue, 9 Jan 2024 15:38:10 -0500 Subject: [PATCH 13/53] feat(Violations): fix import --- src/libs/actions/IOU.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index f2584cb8accd..6d20c3badac5 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -22,7 +22,7 @@ import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; import * as TransactionUtils from '@libs/TransactionUtils'; import * as UserUtils from '@libs/UserUtils'; -import ViolationsUtils from '@libs/ViolationsUtils'; +import ViolationsUtils from '@libs/Violations/ViolationsUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; From 96470a37550bba0ff0bdb4634112d7294e3e6637 Mon Sep 17 00:00:00 2001 From: Trevor Coleman Date: Tue, 9 Jan 2024 16:59:53 -0500 Subject: [PATCH 14/53] feat(Violations): memoize strings --- .../ReportActionItem/MoneyRequestPreview.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestPreview.js b/src/components/ReportActionItem/MoneyRequestPreview.js index 5c7d89f535b3..738ba6c40f59 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview.js +++ b/src/components/ReportActionItem/MoneyRequestPreview.js @@ -186,12 +186,12 @@ function MoneyRequestPreview(props) { const receiptImages = hasReceipt ? [ReceiptUtils.getThumbnailAndImageURIs(props.transaction)] : []; - const getSettledMessage = () => { + const getSettledMessage = useMemo(() => { if (isExpensifyCardTransaction) { return translate('common.done'); } return translate('iou.settledExpensify'); - }; + }, [isExpensifyCardTransaction, translate]); const showContextMenu = (event) => { showContextMenuForReport(event, props.contextMenuAnchor, props.chatReportID, props.action, props.checkIfContextMenuActive); @@ -232,7 +232,7 @@ function MoneyRequestPreview(props) { } else if (props.iouReport.isCancelledIOU) { message += ` • ${translate('iou.canceled')}`; } - return message + (isSettled && !props.iouReport.isCancelledIOU ? ` • ${getSettledMessage()}` : ''); + return message + (isSettled && !props.iouReport.isCancelledIOU ? ` • ${getSettledMessage}` : ''); }, [ getSettledMessage, hasViolations, @@ -247,7 +247,7 @@ function MoneyRequestPreview(props) { translate, ]); - const getDisplayAmountText = () => { + const displayAmountText = useMemo(() => { if (isDistanceRequest) { return requestAmount && !hasPendingWaypoints ? CurrencyUtils.convertToDisplayString(requestAmount, props.transaction.currency) : translate('common.tbd'); } @@ -261,9 +261,9 @@ function MoneyRequestPreview(props) { } return CurrencyUtils.convertToDisplayString(requestAmount, requestCurrency); - }; + }, [hasPendingWaypoints, isDistanceRequest, isScanning, props.transaction, requestAmount, requestCurrency, translate]); - const getDisplayDeleteAmountText = () => { + const displayDeleteAmountText = useMemo(() => { const {amount, currency} = ReportUtils.getTransactionDetails(props.action.originalMessage); if (isDistanceRequest) { @@ -271,9 +271,9 @@ function MoneyRequestPreview(props) { } return CurrencyUtils.convertToDisplayString(amount, currency); - }; + }, [isDistanceRequest, props.action.originalMessage]); - const displayAmount = isDeleted ? getDisplayDeleteAmountText() : getDisplayAmountText(); + const displayAmount = isDeleted ? displayDeleteAmountText : displayAmountText; const childContainer = ( From 5b0e92f9ed559f826d53ab2cfa41ca3baf7276ab Mon Sep 17 00:00:00 2001 From: Trevor Coleman Date: Thu, 11 Jan 2024 13:49:29 -0500 Subject: [PATCH 15/53] lint --- src/libs/SidebarUtils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 2392a064ff37..6ffa6cc26450 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -129,8 +129,8 @@ function getOrderedReportIDs( const cachedReportsKey = JSON.stringify( [currentReportId, allReports, betas, policies, priorityMode, reportActionCount], /** - * Exclude some properties not to overwhelm a cached key value with huge data, - * which we don't need to store in a cacheKey + * Exclude some properties not to overwhelm a cached key value with huge data, + * which we don't need to store in a cacheKey */ (key, value: unknown) => (['participantAccountIDs', 'participants', 'lastMessageText', 'visibleChatMemberAccountIDs'].includes(key) ? undefined : value), ); From 37cfb7936a1c696bb7d0d0224d1784750e9b4752 Mon Sep 17 00:00:00 2001 From: Trevor Coleman Date: Thu, 11 Jan 2024 13:53:48 -0500 Subject: [PATCH 16/53] feat(Violations): remove duplicate from merge --- src/CONST.ts | 36 ------------------------------------ 1 file changed, 36 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index 1c15f62169fe..c14db74ff173 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -3104,42 +3104,6 @@ const CONST = { TAX_REQUIRED: 'taxRequired', }, - VIOLATIONS: { - ALL_TAG_LEVELS_REQUIRED: 'allTagLevelsRequired', - AUTO_REPORTED_REJECTED_EXPENSE: 'autoReportedRejectedExpense', - BILLABLE_EXPENSE: 'billableExpense', - CASH_EXPENSE_WITH_NO_RECEIPT: 'cashExpenseWithNoReceipt', - CATEGORY_OUT_OF_POLICY: 'categoryOutOfPolicy', - CONVERSION_SURCHARGE: 'conversionSurcharge', - CUSTOM_UNIT_OUT_OF_POLICY: 'customUnitOutOfPolicy', - DUPLICATED_TRANSACTION: 'duplicatedTransaction', - FIELD_REQUIRED: 'fieldRequired', - FUTURE_DATE: 'futureDate', - INVOICE_MARKUP: 'invoiceMarkup', - MAX_AGE: 'maxAge', - MISSING_CATEGORY: 'missingCategory', - MISSING_COMMENT: 'missingComment', - MISSING_TAG: 'missingTag', - MODIFIED_AMOUNT: 'modifiedAmount', - MODIFIED_DATE: 'modifiedDate', - NON_EXPENSIWORKS_EXPENSE: 'nonExpensiworksExpense', - OVER_AUTO_APPROVAL_LIMIT: 'overAutoApprovalLimit', - OVER_CATEGORY_LIMIT: 'overCategoryLimit', - OVER_LIMIT: 'overLimit', - OVER_LIMIT_ATTENDEE: 'overLimitAttendee', - PER_DAY_LIMIT: 'perDayLimit', - RECEIPT_NOT_SMART_SCANNED: 'receiptNotSmartScanned', - RECEIPT_REQUIRED: 'receiptRequired', - RTER: 'rter', - SMARTSCAN_FAILED: 'smartscanFailed', - SOME_TAG_LEVELS_REQUIRED: 'someTagLevelsRequired', - TAG_OUT_OF_POLICY: 'tagOutOfPolicy', - TAX_AMOUNT_CHANGED: 'taxAmountChanged', - TAX_OUT_OF_POLICY: 'taxOutOfPolicy', - TAX_RATE_CHANGED: 'taxRateChanged', - TAX_REQUIRED: 'taxRequired', - }, - /** Context menu types */ CONTEXT_MENU_TYPES: { LINK: 'LINK', From d166df1afb29b376f79adaee8d4c987c63b7962f Mon Sep 17 00:00:00 2001 From: Trevor Coleman Date: Thu, 11 Jan 2024 13:54:29 -0500 Subject: [PATCH 17/53] feat(Violations): take transaction or transactionId for hasViolation --- src/libs/TransactionUtils.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/libs/TransactionUtils.ts b/src/libs/TransactionUtils.ts index 9cce4fd09b98..b2824e47e06c 100644 --- a/src/libs/TransactionUtils.ts +++ b/src/libs/TransactionUtils.ts @@ -524,8 +524,9 @@ function getRecentTransactions(transactions: Record, size = 2): /** * Checks if any violations for the provided transaction are of type 'violation' */ -function hasViolation(transaction: Transaction, transactionViolations: TransactionViolations): boolean { - return Boolean(transactionViolations[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transaction.transactionID]?.some((violation: TransactionViolation) => violation.type === 'violation')); +function hasViolation(transaction: Transaction | string, transactionViolations: TransactionViolations): boolean { + const transactionId = typeof transaction === 'string' ? transaction : transaction.transactionID; + return Boolean(transactionViolations[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transactionId]?.some((violation: TransactionViolation) => violation.type === 'violation')); } function getTransactionViolations({transactionID}: Transaction, transactionViolations: TransactionViolations): TransactionViolation[] | null { From 44229197e06e266e2540f33b8d5c911561044763 Mon Sep 17 00:00:00 2001 From: Lizzi Lindboe Date: Thu, 11 Jan 2024 14:47:23 -0500 Subject: [PATCH 18/53] feat(Violations): prettier --- src/libs/SidebarUtils.ts | 3 ++- src/libs/TransactionUtils.ts | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 07bc248dd0ed..a58d79a79778 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -126,7 +126,8 @@ function getOrderedReportIDs( const reportActionCount = allReportActions?.[reportIDKey]?.length ?? 1; // Generate a unique cache key based on the function arguments - const cachedReportsKey = JSON.stringify([currentReportId, allReports, betas, policies, priorityMode, reportActionCount], + const cachedReportsKey = JSON.stringify( + [currentReportId, allReports, betas, policies, priorityMode, reportActionCount], /** * Exclude some properties not to overwhelm a cached key value with huge data, * which we don't need to store in a cacheKey diff --git a/src/libs/TransactionUtils.ts b/src/libs/TransactionUtils.ts index aedc881528a7..cd75b8eb03e3 100644 --- a/src/libs/TransactionUtils.ts +++ b/src/libs/TransactionUtils.ts @@ -533,7 +533,6 @@ function getTransactionViolations({transactionID}: Transaction, transactionViola return transactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transactionID] ?? null; } - /** * this is the formulae to calculate tax */ From 38015459c9140e3fb4710dc2f71ee4730f778ee7 Mon Sep 17 00:00:00 2001 From: Trevor Coleman Date: Mon, 22 Jan 2024 16:50:39 -0500 Subject: [PATCH 19/53] feat(Violations): fix dependency array --- src/components/ReportActionItem/MoneyRequestPreview.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ReportActionItem/MoneyRequestPreview.js b/src/components/ReportActionItem/MoneyRequestPreview.js index b425c9e070e6..87b33ee6f3c4 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview.js +++ b/src/components/ReportActionItem/MoneyRequestPreview.js @@ -264,7 +264,7 @@ function MoneyRequestPreview(props) { } return CurrencyUtils.convertToDisplayString(requestAmount, requestCurrency); - }, [hasPendingWaypoints, isDistanceRequest, isScanning, props.transaction, requestAmount, requestCurrency, translate]); + }, [hasPendingWaypoints, isDistanceRequest, isScanning, isSettled, props.transaction, requestAmount, requestCurrency, translate]); const displayDeleteAmountText = useMemo(() => { const {amount, currency} = ReportUtils.getTransactionDetails(props.action.originalMessage); From c155fe3ba4fefe04ee8979f48c05fee7ed8d4011 Mon Sep 17 00:00:00 2001 From: Trevor Coleman Date: Wed, 24 Jan 2024 13:19:31 -0500 Subject: [PATCH 20/53] feat(Violations): make translations consistent --- src/languages/en.ts | 13 +++--- src/languages/es.ts | 15 ++++--- src/languages/types.ts | 12 +++--- src/libs/Violations/ViolationsUtils.ts | 60 ++++++++++++++++---------- 4 files changed, 57 insertions(+), 43 deletions(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index 7a4cc6ec4559..8ca47f88cea2 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -83,7 +83,6 @@ import type { ViolationsInvoiceMarkupParams, ViolationsMaxAgeParams, ViolationsMissingTagParams, - ViolationsOverAutoApprovalLimitParams, ViolationsOverCategoryLimitParams, ViolationsOverLimitParams, ViolationsPerDayLimitParams, @@ -2086,7 +2085,7 @@ export default { allTagLevelsRequired: 'All tags required', autoReportedRejectedExpense: ({rejectReason, rejectedBy}: ViolationsAutoReportedRejectedExpenseParams) => `${rejectedBy} rejected this expense with the comment "${rejectReason}"`, billableExpense: 'Billable no longer valid', - cashExpenseWithNoReceipt: ({amount}: ViolationsCashExpenseWithNoReceiptParams) => `Receipt required over ${amount}`, + cashExpenseWithNoReceipt: ({formattedLimit}: ViolationsCashExpenseWithNoReceiptParams) => `Receipt required${formattedLimit ? ` over ${formattedLimit}` : ''}`, categoryOutOfPolicy: 'Category no longer valid', conversionSurcharge: ({surcharge}: ViolationsConversionSurchargeParams) => `Applied ${surcharge}% conversion surcharge`, customUnitOutOfPolicy: 'Unit no longer valid', @@ -2101,11 +2100,11 @@ export default { modifiedAmount: 'Amount greater than scanned receipt', modifiedDate: 'Date differs from scanned receipt', nonExpensiworksExpense: 'Non-Expensiworks expense', - overAutoApprovalLimit: ({formattedLimitAmount}: ViolationsOverAutoApprovalLimitParams) => `Expense exceeds auto approval limit of ${formattedLimitAmount}`, - overCategoryLimit: ({categoryLimit}: ViolationsOverCategoryLimitParams) => `Amount over ${categoryLimit}/person category limit`, - overLimit: ({amount}: ViolationsOverLimitParams) => `Amount over ${amount}/person limit`, - overLimitAttendee: ({amount}: ViolationsOverLimitParams) => `Amount over ${amount}/person limit`, - perDayLimit: ({limit}: ViolationsPerDayLimitParams) => `Amount over daily ${limit}/person category limit`, + overAutoApprovalLimit: ({formattedLimit}: ViolationsOverLimitParams) => `Expense exceeds auto approval limit of ${formattedLimit}`, + overCategoryLimit: ({formattedLimit}: ViolationsOverCategoryLimitParams) => `Amount over ${formattedLimit}/person category limit`, + overLimit: ({formattedLimit}: ViolationsOverLimitParams) => `Amount over ${formattedLimit}/person limit`, + overLimitAttendee: ({formattedLimit}: ViolationsOverLimitParams) => `Amount over ${formattedLimit}/person limit`, + perDayLimit: ({formattedLimit}: ViolationsPerDayLimitParams) => `Amount over daily ${formattedLimit}/person category limit`, receiptNotSmartScanned: 'Receipt not verified. Please confirm accuracy.', receiptRequired: (params: ViolationsReceiptRequiredParams) => `Receipt required${params ? ` over ${params.formattedLimit}${params.category ? ` category limit` : ''}` : ''}`, reviewRequired: 'Review required', diff --git a/src/languages/es.ts b/src/languages/es.ts index 07cb81827d11..411c62ee42f2 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -2573,9 +2573,9 @@ export default { allTagLevelsRequired: 'Todas las etiquetas son obligatorias', autoReportedRejectedExpense: ({rejectedBy, rejectReason}: ViolationsAutoReportedRejectedExpenseParams) => `${rejectedBy} rechazó la solicitud y comentó "${rejectReason}"`, billableExpense: 'La opción facturable ya no es válida', - cashExpenseWithNoReceipt: ({amount}: ViolationsCashExpenseWithNoReceiptParams) => `Recibo obligatorio para montos mayores a ${amount}`, + cashExpenseWithNoReceipt: ({formattedLimit}: ViolationsCashExpenseWithNoReceiptParams) => `Recibo obligatorio para montos mayores a ${formattedLimit}`, categoryOutOfPolicy: 'La categoría ya no es válida', - conversionSurcharge: ({surcharge}: ViolationsConversionSurchargeParams) => `${surcharge}% de recargo aplicado`, + conversionSurcharge: ({surcharge}: ViolationsConversionSurchargeParams = {}) => `${surcharge}% de recargo aplicado`, customUnitOutOfPolicy: 'Unidad ya no es válida', duplicatedTransaction: 'Potencial duplicado', fieldRequired: 'Los campos del informe son obligatorios', @@ -2588,11 +2588,12 @@ export default { modifiedAmount: 'Importe superior al del recibo escaneado', modifiedDate: 'Fecha difiere del recibo escaneado', nonExpensiworksExpense: 'Gasto no es de Expensiworks', - overAutoApprovalLimit: ({formattedLimitAmount}: ViolationsOverAutoApprovalLimitParams) => `Importe supera el límite de aprobación automática de ${formattedLimitAmount}`, - overCategoryLimit: ({categoryLimit}: ViolationsOverCategoryLimitParams) => `Importe supera el límite para la categoría de ${categoryLimit}/persona`, - overLimit: ({amount}: ViolationsOverLimitParams) => `Importe supera el límite de ${amount}/persona`, - overLimitAttendee: ({amount}: ViolationsOverLimitParams) => `Importe supera el límite de ${amount}/persona`, - perDayLimit: ({limit}: ViolationsPerDayLimitParams) => `Importe supera el límite diario de la categoría de ${limit}/persona`, + overAutoApprovalLimit: ({formattedLimit}: ViolationsOverAutoApprovalLimitParams) => + `Importe supera el límite de aprobación automática${formattedLimit ? ` de ${formattedLimit}` : ''}`, + overCategoryLimit: ({formattedLimit}: ViolationsOverCategoryLimitParams) => `Importe supera el límite para la categoría${formattedLimit ? ` de ${formattedLimit}/persona` : ''}`, + overLimit: ({formattedLimit}: ViolationsOverLimitParams) => `Importe supera el límite${formattedLimit ? ` de ${formattedLimit}/persona` : ''}`, + overLimitAttendee: ({formattedLimit}: ViolationsOverLimitParams) => `Importe supera el límite${formattedLimit ? ` de ${formattedLimit}/persona` : ''}`, + perDayLimit: ({formattedLimit}: ViolationsPerDayLimitParams) => `Importe supera el límite diario de la categoría${formattedLimit ? ` de ${formattedLimit}/persona` : ''}`, receiptNotSmartScanned: 'Recibo no verificado. Por favor, confirma su exactitud', receiptRequired: (params: ViolationsReceiptRequiredParams) => `Recibo obligatorio${params ? ` para importes sobre${params.category ? ` el limite de la categoría de` : ''} ${params.formattedLimit}` : ''}`, diff --git a/src/languages/types.ts b/src/languages/types.ts index e69b4bd49686..2168f324b331 100644 --- a/src/languages/types.ts +++ b/src/languages/types.ts @@ -215,7 +215,7 @@ type WalletProgramParams = {walletProgram: string}; type ViolationsAutoReportedRejectedExpenseParams = {rejectedBy: string; rejectReason: string}; -type ViolationsCashExpenseWithNoReceiptParams = {amount: string}; +type ViolationsCashExpenseWithNoReceiptParams = {formattedLimit?: string}; type ViolationsConversionSurchargeParams = {surcharge?: number}; @@ -225,15 +225,15 @@ type ViolationsMaxAgeParams = {maxAge: number}; type ViolationsMissingTagParams = {tagName?: string} | null; -type ViolationsOverAutoApprovalLimitParams = {formattedLimitAmount: string}; +type ViolationsOverAutoApprovalLimitParams = {formattedLimit?: string}; -type ViolationsOverCategoryLimitParams = {categoryLimit: string}; +type ViolationsOverCategoryLimitParams = {formattedLimit?: string}; -type ViolationsOverLimitParams = {amount: string}; +type ViolationsOverLimitParams = {formattedLimit?: string}; -type ViolationsPerDayLimitParams = {limit: string}; +type ViolationsPerDayLimitParams = {formattedLimit?: string}; -type ViolationsReceiptRequiredParams = {formattedLimit: string; category?: string} | null; +type ViolationsReceiptRequiredParams = {formattedLimit?: string; category?: string} | null; type ViolationsRterParams = { brokenBankConnection: boolean; diff --git a/src/libs/Violations/ViolationsUtils.ts b/src/libs/Violations/ViolationsUtils.ts index 86f60ed04aa5..5b94116e36e6 100644 --- a/src/libs/Violations/ViolationsUtils.ts +++ b/src/libs/Violations/ViolationsUtils.ts @@ -93,22 +93,39 @@ const ViolationsUtils = { violation: TransactionViolation, translate: (phraseKey: TKey, ...phraseParameters: PhraseParameters>) => string, ): string { + const { + brokenBankConnection = false, + isAdmin = false, + email, + isTransactionOlderThan7Days = false, + member, + category, + rejectedBy = '', + rejectReason = '', + formattedLimit, + surcharge, + invoiceMarkup, + maxAge = 0, + tagName, + taxName, + } = violation.data ?? {}; + switch (violation.name) { case 'allTagLevelsRequired': return translate('violations.allTagLevelsRequired'); case 'autoReportedRejectedExpense': return translate('violations.autoReportedRejectedExpense', { - rejectedBy: violation.data?.rejectedBy ?? '', - rejectReason: violation.data?.rejectReason ?? '', + rejectedBy, + rejectReason, }); case 'billableExpense': return translate('violations.billableExpense'); case 'cashExpenseWithNoReceipt': - return translate('violations.cashExpenseWithNoReceipt', {amount: violation.data?.amount ?? ''}); + return translate('violations.cashExpenseWithNoReceipt', {formattedLimit}); case 'categoryOutOfPolicy': return translate('violations.categoryOutOfPolicy'); case 'conversionSurcharge': - return translate('violations.conversionSurcharge', {surcharge: violation.data?.surcharge}); + return translate('violations.conversionSurcharge', {surcharge}); case 'customUnitOutOfPolicy': return translate('violations.customUnitOutOfPolicy'); case 'duplicatedTransaction': @@ -118,15 +135,15 @@ const ViolationsUtils = { case 'futureDate': return translate('violations.futureDate'); case 'invoiceMarkup': - return translate('violations.invoiceMarkup', {invoiceMarkup: violation.data?.invoiceMarkup}); + return translate('violations.invoiceMarkup', {invoiceMarkup}); case 'maxAge': - return translate('violations.maxAge', {maxAge: violation.data?.maxAge ?? 0}); + return translate('violations.maxAge', {maxAge}); case 'missingCategory': return translate('violations.missingCategory'); case 'missingComment': return translate('violations.missingComment'); case 'missingTag': - return translate('violations.missingTag', {tagName: violation.data?.tagName}); + return translate('violations.missingTag', {tagName}); case 'modifiedAmount': return translate('violations.modifiedAmount'); case 'modifiedDate': @@ -134,40 +151,37 @@ const ViolationsUtils = { case 'nonExpensiworksExpense': return translate('violations.nonExpensiworksExpense'); case 'overAutoApprovalLimit': - return translate('violations.overAutoApprovalLimit', {formattedLimitAmount: violation.data?.formattedLimit ?? ''}); + return translate('violations.overAutoApprovalLimit', {formattedLimit}); case 'overCategoryLimit': - return translate('violations.overCategoryLimit', {categoryLimit: violation.data?.categoryLimit ?? ''}); + return translate('violations.overCategoryLimit', {formattedLimit}); case 'overLimit': - return translate('violations.overLimit', {amount: violation.data?.amount ?? ''}); + return translate('violations.overLimit', {formattedLimit}); case 'overLimitAttendee': - return translate('violations.overLimitAttendee', {amount: violation.data?.amount ?? ''}); + return translate('violations.overLimitAttendee', {formattedLimit}); case 'perDayLimit': - return translate('violations.perDayLimit', {limit: violation.data?.limit ?? ''}); + return translate('violations.perDayLimit', {formattedLimit}); case 'receiptNotSmartScanned': return translate('violations.receiptNotSmartScanned'); case 'receiptRequired': - return translate('violations.receiptRequired', { - formattedLimit: violation.data?.formattedLimit ?? '0', - category: violation.data?.category ?? '', - }); + return translate('violations.receiptRequired', {formattedLimit, category}); case 'rter': return translate('violations.rter', { - brokenBankConnection: violation.data?.brokenBankConnection ?? false, - isAdmin: violation.data?.isAdmin ?? false, - email: violation.data?.email, - isTransactionOlderThan7Days: Boolean(violation.data?.isTransactionOlderThan7Days), - member: violation.data?.member, + brokenBankConnection, + isAdmin, + email, + isTransactionOlderThan7Days, + member, }); case 'smartscanFailed': return translate('violations.smartscanFailed'); case 'someTagLevelsRequired': return translate('violations.someTagLevelsRequired'); case 'tagOutOfPolicy': - return translate('violations.tagOutOfPolicy', {tagName: violation.data?.tagName}); + return translate('violations.tagOutOfPolicy', {tagName}); case 'taxAmountChanged': return translate('violations.taxAmountChanged'); case 'taxOutOfPolicy': - return translate('violations.taxOutOfPolicy', {taxName: violation.data?.taxName}); + return translate('violations.taxOutOfPolicy', {taxName}); case 'taxRateChanged': return translate('violations.taxRateChanged'); case 'taxRequired': From ef89600453a3a98a85b308a1aa6e78cc9b83bfff Mon Sep 17 00:00:00 2001 From: Trevor Coleman Date: Wed, 24 Jan 2024 13:59:13 -0500 Subject: [PATCH 21/53] feat(Violations): refactor translations --- src/languages/en.ts | 2 +- src/languages/es.ts | 6 +++--- src/languages/types.ts | 4 ++-- src/libs/Violations/ViolationsUtils.ts | 6 +++--- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index 7bde214af13f..38d848b013ea 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -2096,7 +2096,7 @@ export default { maxAge: ({maxAge}: ViolationsMaxAgeParams) => `Date older than ${maxAge} days`, missingCategory: 'Missing category', missingComment: 'Description required for selected category', - missingTag: (params: ViolationsMissingTagParams) => `Missing ${params?.tagName ?? 'tag'}`, + missingTag: ({tagName}: ViolationsMissingTagParams) => `Missing ${tagName ?? 'tag'}`, modifiedAmount: 'Amount greater than scanned receipt', modifiedDate: 'Date differs from scanned receipt', nonExpensiworksExpense: 'Non-Expensiworks expense', diff --git a/src/languages/es.ts b/src/languages/es.ts index 5326369965a8..9dd1a5145d98 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -2584,7 +2584,7 @@ export default { maxAge: ({maxAge}: ViolationsMaxAgeParams) => `Fecha de más de ${maxAge} días`, missingCategory: 'Falta categoría', missingComment: 'Descripción obligatoria para la categoría seleccionada', - missingTag: (params: ViolationsMissingTagParams) => `Falta ${params?.tagName ?? 'etiqueta'}`, + missingTag: ({tagName}: ViolationsMissingTagParams) => `Falta ${tagName ?? 'etiqueta'}`, modifiedAmount: 'Importe superior al del recibo escaneado', modifiedDate: 'Fecha difiere del recibo escaneado', nonExpensiworksExpense: 'Gasto no proviene de Expensiworks', @@ -2595,8 +2595,8 @@ export default { overLimitAttendee: ({formattedLimit}: ViolationsOverLimitParams) => `Importe supera el límite${formattedLimit ? ` de ${formattedLimit}/persona` : ''}`, perDayLimit: ({formattedLimit}: ViolationsPerDayLimitParams) => `Importe supera el límite diario de la categoría${formattedLimit ? ` de ${formattedLimit}/persona` : ''}`, receiptNotSmartScanned: 'Recibo no verificado. Por favor, confirma su exactitud', - receiptRequired: (params: ViolationsReceiptRequiredParams) => - `Recibo obligatorio${params ? ` para importes sobre${params.category ? ` el limite de la categoría de` : ''} ${params.formattedLimit}` : ''}`, + receiptRequired: ({category, formattedLimit}: ViolationsReceiptRequiredParams) => + `Recibo obligatorio${category || formattedLimit ? ` para importes sobre${category ? ` el limite de la categoría de` : ''} ${formattedLimit}` : ''}`, reviewRequired: 'Revisión requerida', rter: ({brokenBankConnection, isAdmin, email, isTransactionOlderThan7Days, member}: ViolationsRterParams) => { if (brokenBankConnection) { diff --git a/src/languages/types.ts b/src/languages/types.ts index e2449800703c..fd96c9c8c1dd 100644 --- a/src/languages/types.ts +++ b/src/languages/types.ts @@ -223,7 +223,7 @@ type ViolationsInvoiceMarkupParams = {invoiceMarkup?: number}; type ViolationsMaxAgeParams = {maxAge: number}; -type ViolationsMissingTagParams = {tagName?: string} | null; +type ViolationsMissingTagParams = {tagName?: string}; type ViolationsOverAutoApprovalLimitParams = {formattedLimit?: string}; @@ -233,7 +233,7 @@ type ViolationsOverLimitParams = {formattedLimit?: string}; type ViolationsPerDayLimitParams = {formattedLimit?: string}; -type ViolationsReceiptRequiredParams = {formattedLimit?: string; category?: string} | null; +type ViolationsReceiptRequiredParams = {formattedLimit?: string; category?: string}; type ViolationsRterParams = { brokenBankConnection: boolean; diff --git a/src/libs/Violations/ViolationsUtils.ts b/src/libs/Violations/ViolationsUtils.ts index 5b94116e36e6..20c29b0fb208 100644 --- a/src/libs/Violations/ViolationsUtils.ts +++ b/src/libs/Violations/ViolationsUtils.ts @@ -103,10 +103,10 @@ const ViolationsUtils = { rejectedBy = '', rejectReason = '', formattedLimit, - surcharge, - invoiceMarkup, + surcharge = 0, + invoiceMarkup = 0, maxAge = 0, - tagName, + tagName = 'etiqueta', taxName, } = violation.data ?? {}; From 7c4f4e60ecc6ab4c294be70f6378fa7c63db589c Mon Sep 17 00:00:00 2001 From: Trevor Coleman Date: Wed, 24 Jan 2024 14:14:57 -0500 Subject: [PATCH 22/53] feat(Violations): extract MoneyRequestPreviewWrapper and PropTypes --- .../ReportActionItem/MoneyRequestPreview.js | 139 +----------------- .../MoneyRequestPreviewWrapper.js | 41 ++++++ .../moneyRequestPreviewPropTypes.js | 103 +++++++++++++ 3 files changed, 147 insertions(+), 136 deletions(-) create mode 100644 src/components/ReportActionItem/MoneyRequestPreviewWrapper.js create mode 100644 src/components/ReportActionItem/moneyRequestPreviewPropTypes.js diff --git a/src/components/ReportActionItem/MoneyRequestPreview.js b/src/components/ReportActionItem/MoneyRequestPreview.js index 87b33ee6f3c4..9319f71631ed 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview.js +++ b/src/components/ReportActionItem/MoneyRequestPreview.js @@ -1,10 +1,8 @@ import ExpensiMark from 'expensify-common/lib/ExpensiMark'; import {truncate} from 'lodash'; import lodashGet from 'lodash/get'; -import PropTypes from 'prop-types'; import React, {useMemo} from 'react'; import {View} from 'react-native'; -import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; @@ -12,11 +10,9 @@ import MoneyRequestSkeletonView from '@components/MoneyRequestSkeletonView'; import MultipleAvatars from '@components/MultipleAvatars'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; import PressableWithFeedback from '@components/Pressable/PressableWithoutFeedback'; -import refPropTypes from '@components/refPropTypes'; import RenderHTML from '@components/RenderHTML'; import {showContextMenuForReport} from '@components/ShowContextMenuContext'; import Text from '@components/Text'; -import transactionPropTypes from '@components/transactionPropTypes'; import useLocalize from '@hooks/useLocalize'; import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; @@ -31,115 +27,13 @@ import * as ReceiptUtils from '@libs/ReceiptUtils'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; import * as TransactionUtils from '@libs/TransactionUtils'; -import {transactionViolationsPropType} from '@libs/Violations/propTypes'; -import walletTermsPropTypes from '@pages/EnablePayments/walletTermsPropTypes'; -import reportActionPropTypes from '@pages/home/report/reportActionPropTypes'; -import iouReportPropTypes from '@pages/iouReportPropTypes'; -import reportPropTypes from '@pages/reportPropTypes'; import * as PaymentMethods from '@userActions/PaymentMethods'; import * as Report from '@userActions/Report'; import CONST from '@src/CONST'; import * as Localize from '@src/libs/Localize'; -import ONYXKEYS from '@src/ONYXKEYS'; +import PropTypes from './moneyRequestPreviewPropTypes'; import ReportActionItemImages from './ReportActionItemImages'; -const propTypes = { - /** The active IOUReport, used for Onyx subscription */ - // eslint-disable-next-line react/no-unused-prop-types - iouReportID: PropTypes.string.isRequired, - - /** The associated chatReport */ - chatReportID: PropTypes.string.isRequired, - - /** Callback for the preview pressed */ - onPreviewPressed: PropTypes.func, - - /** All the data of the action, used for showing context menu */ - action: PropTypes.shape(reportActionPropTypes), - - /** Popover context menu anchor, used for showing context menu */ - contextMenuAnchor: refPropTypes, - - /** Callback for updating context menu active state, used for showing context menu */ - checkIfContextMenuActive: PropTypes.func, - - /** Extra styles to pass to View wrapper */ - // eslint-disable-next-line react/forbid-prop-types - containerStyles: PropTypes.arrayOf(PropTypes.object), - - /* Onyx Props */ - - /** chatReport associated with iouReport */ - chatReport: reportPropTypes, - - /** IOU report data object */ - iouReport: iouReportPropTypes, - - /** True if this is this IOU is a split instead of a 1:1 request */ - isBillSplit: PropTypes.bool.isRequired, - - /** True if the IOU Preview card is hovered */ - isHovered: PropTypes.bool, - - /** All of the personal details for everyone */ - personalDetails: PropTypes.objectOf( - PropTypes.shape({ - /** This is either the user's full name, or their login if full name is an empty string */ - displayName: PropTypes.string, - }), - ), - - /** The transaction attached to the action.message.iouTransactionID */ - transaction: transactionPropTypes, - - /** Session info for the currently logged in user. */ - session: PropTypes.shape({ - /** Currently logged in user email */ - email: PropTypes.string, - }), - - /** Information about the user accepting the terms for payments */ - walletTerms: walletTermsPropTypes, - - /** Whether or not an IOU report contains money requests in a different currency - * that are either created or cancelled offline, and thus haven't been converted to the report's currency yet - */ - shouldShowPendingConversionMessage: PropTypes.bool, - - /** Whether a message is a whisper */ - isWhisper: PropTypes.bool, - - /** All transactionViolations */ - transactionViolations: transactionViolationsPropType, -}; - -const defaultProps = { - iouReport: {}, - onPreviewPressed: null, - action: undefined, - contextMenuAnchor: undefined, - checkIfContextMenuActive: () => {}, - containerStyles: [], - walletTerms: {}, - chatReport: {}, - isHovered: false, - personalDetails: {}, - session: { - email: null, - }, - transaction: {}, - shouldShowPendingConversionMessage: false, - isWhisper: false, - transactionViolations: {}, -}; - -// We should not render the component if there is no iouReport and it's not a split. -// Moved outside of the component scope to allow memoization of values later. -function MoneyRequestPreviewWrapper(props) { - // eslint-disable-next-line react/jsx-props-no-spreading - return _.isEmpty(props.iouReport) && !props.isBillSplit ? null : ; -} - function MoneyRequestPreview(props) { const theme = useTheme(); const styles = useThemeStyles(); @@ -398,33 +292,6 @@ function MoneyRequestPreview(props) { ); } -MoneyRequestPreview.propTypes = propTypes; -MoneyRequestPreview.defaultProps = defaultProps; +MoneyRequestPreview.propTypes = PropTypes.propTypes; +MoneyRequestPreview.defaultProps = PropTypes.defaultProps; MoneyRequestPreview.displayName = 'MoneyRequestPreview'; -MoneyRequestPreviewWrapper.propTypes = propTypes; -MoneyRequestPreviewWrapper.defaultProps = defaultProps; -MoneyRequestPreviewWrapper.displayName = 'MoneyRequestPreviewWrapper'; - -export default withOnyx({ - personalDetails: { - key: ONYXKEYS.PERSONAL_DETAILS_LIST, - }, - chatReport: { - key: ({chatReportID}) => `${ONYXKEYS.COLLECTION.REPORT}${chatReportID}`, - }, - iouReport: { - key: ({iouReportID}) => `${ONYXKEYS.COLLECTION.REPORT}${iouReportID}`, - }, - session: { - key: ONYXKEYS.SESSION, - }, - transaction: { - key: ({action}) => `${ONYXKEYS.COLLECTION.TRANSACTION}${(action && action.originalMessage && action.originalMessage.IOUTransactionID) || 0}`, - }, - walletTerms: { - key: ONYXKEYS.WALLET_TERMS, - }, - transactionViolations: { - key: ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS, - }, -})(MoneyRequestPreviewWrapper); diff --git a/src/components/ReportActionItem/MoneyRequestPreviewWrapper.js b/src/components/ReportActionItem/MoneyRequestPreviewWrapper.js new file mode 100644 index 000000000000..221e0fd86c94 --- /dev/null +++ b/src/components/ReportActionItem/MoneyRequestPreviewWrapper.js @@ -0,0 +1,41 @@ +// We should not render the component if there is no iouReport and it's not a split. +// Moved outside of the component scope to allow memoization of values later. +import React from 'react'; +import {withOnyx} from 'react-native-onyx'; +import _ from 'underscore'; +import ONYXKEYS from '@src/ONYXKEYS'; +import MoneyRequestPreview from './MoneyRequestPreview'; +import MoneyRequestPreviewPropTypes from './moneyRequestPreviewPropTypes'; + +function MoneyRequestPreviewWrapper(props) { + // eslint-disable-next-line react/jsx-props-no-spreading + return _.isEmpty(props.iouReport) && !props.isBillSplit ? null : ; +} + +MoneyRequestPreviewWrapper.propTypes = MoneyRequestPreviewPropTypes.propTypes; +MoneyRequestPreviewWrapper.defaultProps = MoneyRequestPreviewPropTypes.defaultProps; +MoneyRequestPreviewWrapper.displayName = 'MoneyRequestPreviewWrapper'; + +export default withOnyx({ + personalDetails: { + key: ONYXKEYS.PERSONAL_DETAILS_LIST, + }, + chatReport: { + key: ({chatReportID}) => `${ONYXKEYS.COLLECTION.REPORT}${chatReportID}`, + }, + iouReport: { + key: ({iouReportID}) => `${ONYXKEYS.COLLECTION.REPORT}${iouReportID}`, + }, + session: { + key: ONYXKEYS.SESSION, + }, + transaction: { + key: ({action}) => `${ONYXKEYS.COLLECTION.TRANSACTION}${(action && action.originalMessage && action.originalMessage.IOUTransactionID) || 0}`, + }, + walletTerms: { + key: ONYXKEYS.WALLET_TERMS, + }, + transactionViolations: { + key: ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS, + }, +})(MoneyRequestPreviewWrapper); diff --git a/src/components/ReportActionItem/moneyRequestPreviewPropTypes.js b/src/components/ReportActionItem/moneyRequestPreviewPropTypes.js new file mode 100644 index 000000000000..27612727e7b6 --- /dev/null +++ b/src/components/ReportActionItem/moneyRequestPreviewPropTypes.js @@ -0,0 +1,103 @@ +import PropTypes from 'prop-types'; +import refPropTypes from '@components/refPropTypes'; +import transactionPropTypes from '@components/transactionPropTypes'; +import {transactionViolationsPropType} from '@libs/Violations/propTypes'; +import walletTermsPropTypes from '@pages/EnablePayments/walletTermsPropTypes'; +import reportActionPropTypes from '@pages/home/report/reportActionPropTypes'; +import iouReportPropTypes from '@pages/iouReportPropTypes'; +import reportPropTypes from '@pages/reportPropTypes'; + +const propTypes = { + /** The active IOUReport, used for Onyx subscription */ + // eslint-disable-next-line react/no-unused-prop-types + iouReportID: PropTypes.string.isRequired, + + /** The associated chatReport */ + chatReportID: PropTypes.string.isRequired, + + /** Callback for the preview pressed */ + onPreviewPressed: PropTypes.func, + + /** All the data of the action, used for showing context menu */ + action: PropTypes.shape(reportActionPropTypes), + + /** Popover context menu anchor, used for showing context menu */ + contextMenuAnchor: refPropTypes, + + /** Callback for updating context menu active state, used for showing context menu */ + checkIfContextMenuActive: PropTypes.func, + + /** Extra styles to pass to View wrapper */ + // eslint-disable-next-line react/forbid-prop-types + containerStyles: PropTypes.arrayOf(PropTypes.object), + + /* Onyx Props */ + + /** chatReport associated with iouReport */ + chatReport: reportPropTypes, + + /** IOU report data object */ + iouReport: iouReportPropTypes, + + /** True if this is this IOU is a split instead of a 1:1 request */ + isBillSplit: PropTypes.bool.isRequired, + + /** True if the IOU Preview card is hovered */ + isHovered: PropTypes.bool, + + /** All of the personal details for everyone */ + personalDetails: PropTypes.objectOf( + PropTypes.shape({ + /** This is either the user's full name, or their login if full name is an empty string */ + displayName: PropTypes.string, + }), + ), + + /** The transaction attached to the action.message.iouTransactionID */ + transaction: transactionPropTypes, + + /** Session info for the currently logged in user. */ + session: PropTypes.shape({ + /** Currently logged in user email */ + email: PropTypes.string, + }), + + /** Information about the user accepting the terms for payments */ + walletTerms: walletTermsPropTypes, + + /** Whether or not an IOU report contains money requests in a different currency + * that are either created or cancelled offline, and thus haven't been converted to the report's currency yet + */ + shouldShowPendingConversionMessage: PropTypes.bool, + + /** Whether a message is a whisper */ + isWhisper: PropTypes.bool, + + /** All transactionViolations */ + transactionViolations: transactionViolationsPropType, +}; + +const defaultProps = { + iouReport: {}, + onPreviewPressed: null, + action: undefined, + contextMenuAnchor: undefined, + checkIfContextMenuActive: () => {}, + containerStyles: [], + walletTerms: {}, + chatReport: {}, + isHovered: false, + personalDetails: {}, + session: { + email: null, + }, + transaction: {}, + shouldShowPendingConversionMessage: false, + isWhisper: false, + transactionViolations: {}, +}; + +export default { + propTypes, + defaultProps, +}; From 71c3790c22ea6581b4fef450cd4b76427552f87e Mon Sep 17 00:00:00 2001 From: Trevor Coleman Date: Wed, 24 Jan 2024 14:16:08 -0500 Subject: [PATCH 23/53] feat(Violations): restore comment --- src/components/ReportActionItem/MoneyRequestPreviewWrapper.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/ReportActionItem/MoneyRequestPreviewWrapper.js b/src/components/ReportActionItem/MoneyRequestPreviewWrapper.js index 221e0fd86c94..942c1e80b02e 100644 --- a/src/components/ReportActionItem/MoneyRequestPreviewWrapper.js +++ b/src/components/ReportActionItem/MoneyRequestPreviewWrapper.js @@ -7,6 +7,8 @@ import ONYXKEYS from '@src/ONYXKEYS'; import MoneyRequestPreview from './MoneyRequestPreview'; import MoneyRequestPreviewPropTypes from './moneyRequestPreviewPropTypes'; +// We should not render the component if there is no iouReport and it's not a split. +// Moved outside of the component scope to allow for easier use of hooks in the main component. function MoneyRequestPreviewWrapper(props) { // eslint-disable-next-line react/jsx-props-no-spreading return _.isEmpty(props.iouReport) && !props.isBillSplit ? null : ; From 789ff025cb33f91ea6194ea3edba0c8904307257 Mon Sep 17 00:00:00 2001 From: Trevor Coleman Date: Wed, 24 Jan 2024 14:16:54 -0500 Subject: [PATCH 24/53] feat(Violations): use lodash --- src/components/ReportActionItem/MoneyRequestPreviewWrapper.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestPreviewWrapper.js b/src/components/ReportActionItem/MoneyRequestPreviewWrapper.js index 942c1e80b02e..15a15cf3002f 100644 --- a/src/components/ReportActionItem/MoneyRequestPreviewWrapper.js +++ b/src/components/ReportActionItem/MoneyRequestPreviewWrapper.js @@ -1,8 +1,8 @@ // We should not render the component if there is no iouReport and it's not a split. // Moved outside of the component scope to allow memoization of values later. +import lodashIsEmpty from 'lodash/isEmpty'; import React from 'react'; import {withOnyx} from 'react-native-onyx'; -import _ from 'underscore'; import ONYXKEYS from '@src/ONYXKEYS'; import MoneyRequestPreview from './MoneyRequestPreview'; import MoneyRequestPreviewPropTypes from './moneyRequestPreviewPropTypes'; @@ -11,7 +11,7 @@ import MoneyRequestPreviewPropTypes from './moneyRequestPreviewPropTypes'; // Moved outside of the component scope to allow for easier use of hooks in the main component. function MoneyRequestPreviewWrapper(props) { // eslint-disable-next-line react/jsx-props-no-spreading - return _.isEmpty(props.iouReport) && !props.isBillSplit ? null : ; + return lodashIsEmpty(props.iouReport) && !props.isBillSplit ? null : ; } MoneyRequestPreviewWrapper.propTypes = MoneyRequestPreviewPropTypes.propTypes; From 41856044c4d73fadeb4b5e592bb769df0042c7b9 Mon Sep 17 00:00:00 2001 From: Trevor Coleman Date: Wed, 24 Jan 2024 14:22:08 -0500 Subject: [PATCH 25/53] feat(Violations): simplify --- src/components/ReportActionItem/MoneyRequestPreview.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/components/ReportActionItem/MoneyRequestPreview.js b/src/components/ReportActionItem/MoneyRequestPreview.js index 9319f71631ed..e33b194403bc 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview.js +++ b/src/components/ReportActionItem/MoneyRequestPreview.js @@ -129,7 +129,11 @@ function MoneyRequestPreview(props) { } else if (props.iouReport.isCancelledIOU) { message += ` • ${translate('iou.canceled')}`; } - return message + (isSettled && !props.iouReport.isCancelledIOU ? ` • ${getSettledMessage}` : ''); + + if (isSettled && !props.iouReport.isCancelledIOU) { + message += ` • ${getSettledMessage}`; + } + return message; }, [ getSettledMessage, hasViolations, From 02a3ff070d5f6e96ef72a385346446b4a79250e2 Mon Sep 17 00:00:00 2001 From: Trevor Coleman Date: Wed, 24 Jan 2024 16:58:14 -0500 Subject: [PATCH 26/53] WIP --- .../ReportActionItem/MoneyRequestPreview.js | 2 +- ...pper.js => MoneyRequestPreviewWrapper.tsx} | 35 +++++++++++++++++-- ...pes.js => moneyRequestPreviewPropTypes.ts} | 0 3 files changed, 33 insertions(+), 4 deletions(-) rename src/components/ReportActionItem/{MoneyRequestPreviewWrapper.js => MoneyRequestPreviewWrapper.tsx} (54%) rename src/components/ReportActionItem/{moneyRequestPreviewPropTypes.js => moneyRequestPreviewPropTypes.ts} (100%) diff --git a/src/components/ReportActionItem/MoneyRequestPreview.js b/src/components/ReportActionItem/MoneyRequestPreview.js index e33b194403bc..99a31d9d945d 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview.js +++ b/src/components/ReportActionItem/MoneyRequestPreview.js @@ -34,7 +34,7 @@ import * as Localize from '@src/libs/Localize'; import PropTypes from './moneyRequestPreviewPropTypes'; import ReportActionItemImages from './ReportActionItemImages'; -function MoneyRequestPreview(props) { +export default function MoneyRequestPreview(props) { const theme = useTheme(); const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); diff --git a/src/components/ReportActionItem/MoneyRequestPreviewWrapper.js b/src/components/ReportActionItem/MoneyRequestPreviewWrapper.tsx similarity index 54% rename from src/components/ReportActionItem/MoneyRequestPreviewWrapper.js rename to src/components/ReportActionItem/MoneyRequestPreviewWrapper.tsx index 15a15cf3002f..9964be443525 100644 --- a/src/components/ReportActionItem/MoneyRequestPreviewWrapper.js +++ b/src/components/ReportActionItem/MoneyRequestPreviewWrapper.tsx @@ -1,15 +1,44 @@ // We should not render the component if there is no iouReport and it's not a split. // Moved outside of the component scope to allow memoization of values later. import lodashIsEmpty from 'lodash/isEmpty'; +import type {Ref} from 'react'; import React from 'react'; +import type {StyleProp, ViewStyle} from 'react-native'; +import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; +import type {Text as RNText} from 'react-native/Libraries/Text/Text'; import ONYXKEYS from '@src/ONYXKEYS'; +import type {PersonalDetailsList, Report, ReportAction, Session, Transaction, TransactionViolation, WalletTerms} from '@src/types/onyx'; import MoneyRequestPreview from './MoneyRequestPreview'; import MoneyRequestPreviewPropTypes from './moneyRequestPreviewPropTypes'; +type MoneyRequestPreviewOnyxProps = { + chatReport: OnyxEntry; + iouReport: OnyxEntry; + personalDetails: OnyxEntry; + session: OnyxEntry; + transaction: OnyxEntry; + transactionViolations: OnyxCollection; + walletTerms: OnyxEntry; +}; + +type MoneyRequestPreviewProps = { + action?: OnyxEntry; + chatReportID?: string | null; + checkIfContextMenuActive: () => void; + containerStyles: StyleProp; + contextMenuAnchor?: Ref; + iouReportID?: string | null; + isBillSplit: boolean; + isHovered: boolean; + isWhisper: boolean; + onPreviewPressed: (() => void) | null; + shouldShowPendingConversionMessage: boolean; +} & MoneyRequestPreviewOnyxProps; + // We should not render the component if there is no iouReport and it's not a split. // Moved outside of the component scope to allow for easier use of hooks in the main component. -function MoneyRequestPreviewWrapper(props) { +function MoneyRequestPreviewWrapper(props: MoneyRequestPreviewProps) { // eslint-disable-next-line react/jsx-props-no-spreading return lodashIsEmpty(props.iouReport) && !props.isBillSplit ? null : ; } @@ -18,7 +47,7 @@ MoneyRequestPreviewWrapper.propTypes = MoneyRequestPreviewPropTypes.propTypes; MoneyRequestPreviewWrapper.defaultProps = MoneyRequestPreviewPropTypes.defaultProps; MoneyRequestPreviewWrapper.displayName = 'MoneyRequestPreviewWrapper'; -export default withOnyx({ +export default withOnyx({ personalDetails: { key: ONYXKEYS.PERSONAL_DETAILS_LIST, }, @@ -32,7 +61,7 @@ export default withOnyx({ key: ONYXKEYS.SESSION, }, transaction: { - key: ({action}) => `${ONYXKEYS.COLLECTION.TRANSACTION}${(action && action.originalMessage && action.originalMessage.IOUTransactionID) || 0}`, + key: ({action}) => `${ONYXKEYS.COLLECTION.TRANSACTION}${action?.originalMessage?.IOUTransactionID || 0}`, }, walletTerms: { key: ONYXKEYS.WALLET_TERMS, diff --git a/src/components/ReportActionItem/moneyRequestPreviewPropTypes.js b/src/components/ReportActionItem/moneyRequestPreviewPropTypes.ts similarity index 100% rename from src/components/ReportActionItem/moneyRequestPreviewPropTypes.js rename to src/components/ReportActionItem/moneyRequestPreviewPropTypes.ts From 69f4e5e8c72c6622f5f0d9e9077c5d686baf1d00 Mon Sep 17 00:00:00 2001 From: Trevor Coleman Date: Thu, 25 Jan 2024 13:31:27 -0500 Subject: [PATCH 27/53] feat(Violations): properly type personalDetails to allow for refactoring of MoneyRequestPreview --- src/libs/OptionsListUtils.js | 35 ++++++++++++++++++++++++++++++----- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/src/libs/OptionsListUtils.js b/src/libs/OptionsListUtils.js index 2973228af51f..a40b4f391ac5 100644 --- a/src/libs/OptionsListUtils.js +++ b/src/libs/OptionsListUtils.js @@ -24,6 +24,32 @@ import * as TaskUtils from './TaskUtils'; import * as TransactionUtils from './TransactionUtils'; import * as UserUtils from './UserUtils'; +/** PersonalDetails JSDoc + * @typedef {Object} PersonalDetails + * @property {number} accountID - ID of the current user from their personal details + * @property {string} [firstName] - First name of the current user from their personal details + * @property {string} [lastName] - Last name of the current user from their personal details + * @property {string} [displayName] - Display name of the current user from their personal details + * @property {boolean} [validated] - Is current user validated + * @property {string} [phoneNumber] - Phone number of the current user from their personal details + * @property {AvatarSource} avatar - Avatar URL of the current user from their personal details + * @property {string} [avatarThumbnail] - Avatar thumbnail URL of the current user from their personal details + * @property {boolean} [avatarUploading] - Flag to set when Avatar uploading + * @property {string} [login] - Login of the current user from their personal details + * @property {string} [pronouns] - Pronouns of the current user from their personal details + * @property {string} [localCurrencyCode] - Local currency for the user + * @property {Timezone} [timezone] - Timezone of the current user from their personal details + * @property {boolean} [isLoading] - Whether we are loading the data via the API + * @property {Object} [errorFields] - Field-specific server side errors keyed by microtime + * @property {Object} [pendingFields] - Field-specific pending states for offline UI status + * @property {string} [fallbackIcon] - A fallback avatar icon to display when there is an error on loading avatar from remote URL + * @property {Status} [status] - Status of the current user from their personal details + */ + +/** PersonalDetailsList JSDoc + * @typedef {Object.} PersonalDetailsList + */ + /** * OptionsListUtils is used to build a list options passed to the OptionsList component. Several different UI views can * be configured to display different results based on the options passed to the private getOptions() method. Public @@ -138,12 +164,11 @@ function addSMSDomainIfPhoneNumber(login) { } /** - * Returns avatar data for a list of user accountIDs - * - * @param {Array} accountIDs - * @param {Object} personalDetails + * Returns avatar data for a list of user accountIDs* + * @param {Array.} accountIDs + * @param {PersonalDetailsList} personalDetails * @param {Object} defaultValues {login: accountID} In workspace invite page, when new user is added we pass available data to opt in - * @returns {Object} + * @returns {Array.<{id: number, source: any, type: string, name: string}>} Array of avatar info objects */ function getAvatarsForAccountIDs(accountIDs, personalDetails, defaultValues = {}) { const reversedDefaultValues = {}; From d642ac4d513f26bf2c3f18cc912b61d84e04e687 Mon Sep 17 00:00:00 2001 From: Trevor Coleman Date: Thu, 25 Jan 2024 13:33:12 -0500 Subject: [PATCH 28/53] feat(Violations): add user defined type guard to isMoneyRequestAction --- src/libs/ReportActionsUtils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts index 3154f578c309..d25e202f643f 100644 --- a/src/libs/ReportActionsUtils.ts +++ b/src/libs/ReportActionsUtils.ts @@ -6,7 +6,7 @@ import OnyxUtils from 'react-native-onyx/lib/utils'; import type {ValueOf} from 'type-fest'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {ActionName, ChangeLog, OriginalMessageReimbursementDequeued} from '@src/types/onyx/OriginalMessage'; +import type {ActionName, ChangeLog, OriginalMessageIOU, OriginalMessageReimbursementDequeued} from '@src/types/onyx/OriginalMessage'; import type Report from '@src/types/onyx/Report'; import type {Message, ReportActionBase, ReportActions} from '@src/types/onyx/ReportAction'; import type ReportAction from '@src/types/onyx/ReportAction'; @@ -99,7 +99,7 @@ function isPendingRemove(reportAction: OnyxEntry | EmptyObject): b return reportAction?.message?.[0]?.moderationDecision?.decision === CONST.MODERATION.MODERATOR_DECISION_PENDING_REMOVE; } -function isMoneyRequestAction(reportAction: OnyxEntry): boolean { +function isMoneyRequestAction(reportAction: OnyxEntry): reportAction is ReportAction & OriginalMessageIOU { return reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU; } From 1824128521eac42739be896d5da2ead1a8437b16 Mon Sep 17 00:00:00 2001 From: Trevor Coleman Date: Thu, 25 Jan 2024 13:33:42 -0500 Subject: [PATCH 29/53] feat(Violations): add typescript types to MoneyRequestPreviewWrapper and extract props --- .../MoneyRequestPreviewWrapper.tsx | 36 ++----------------- .../moneyRequestPreviewProps.ts | 32 +++++++++++++++++ 2 files changed, 35 insertions(+), 33 deletions(-) create mode 100644 src/components/ReportActionItem/moneyRequestPreviewProps.ts diff --git a/src/components/ReportActionItem/MoneyRequestPreviewWrapper.tsx b/src/components/ReportActionItem/MoneyRequestPreviewWrapper.tsx index 9964be443525..e290f1056071 100644 --- a/src/components/ReportActionItem/MoneyRequestPreviewWrapper.tsx +++ b/src/components/ReportActionItem/MoneyRequestPreviewWrapper.tsx @@ -1,40 +1,12 @@ // We should not render the component if there is no iouReport and it's not a split. // Moved outside of the component scope to allow memoization of values later. import lodashIsEmpty from 'lodash/isEmpty'; -import type {Ref} from 'react'; import React from 'react'; -import type {StyleProp, ViewStyle} from 'react-native'; -import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; -import type {Text as RNText} from 'react-native/Libraries/Text/Text'; +import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {PersonalDetailsList, Report, ReportAction, Session, Transaction, TransactionViolation, WalletTerms} from '@src/types/onyx'; import MoneyRequestPreview from './MoneyRequestPreview'; -import MoneyRequestPreviewPropTypes from './moneyRequestPreviewPropTypes'; - -type MoneyRequestPreviewOnyxProps = { - chatReport: OnyxEntry; - iouReport: OnyxEntry; - personalDetails: OnyxEntry; - session: OnyxEntry; - transaction: OnyxEntry; - transactionViolations: OnyxCollection; - walletTerms: OnyxEntry; -}; - -type MoneyRequestPreviewProps = { - action?: OnyxEntry; - chatReportID?: string | null; - checkIfContextMenuActive: () => void; - containerStyles: StyleProp; - contextMenuAnchor?: Ref; - iouReportID?: string | null; - isBillSplit: boolean; - isHovered: boolean; - isWhisper: boolean; - onPreviewPressed: (() => void) | null; - shouldShowPendingConversionMessage: boolean; -} & MoneyRequestPreviewOnyxProps; +import type {MoneyRequestPreviewOnyxProps, MoneyRequestPreviewProps} from './moneyRequestPreviewProps'; // We should not render the component if there is no iouReport and it's not a split. // Moved outside of the component scope to allow for easier use of hooks in the main component. @@ -43,8 +15,6 @@ function MoneyRequestPreviewWrapper(props: MoneyRequestPreviewProps) { return lodashIsEmpty(props.iouReport) && !props.isBillSplit ? null : ; } -MoneyRequestPreviewWrapper.propTypes = MoneyRequestPreviewPropTypes.propTypes; -MoneyRequestPreviewWrapper.defaultProps = MoneyRequestPreviewPropTypes.defaultProps; MoneyRequestPreviewWrapper.displayName = 'MoneyRequestPreviewWrapper'; export default withOnyx({ @@ -61,7 +31,7 @@ export default withOnyx( key: ONYXKEYS.SESSION, }, transaction: { - key: ({action}) => `${ONYXKEYS.COLLECTION.TRANSACTION}${action?.originalMessage?.IOUTransactionID || 0}`, + key: ({action}) => `${ONYXKEYS.COLLECTION.TRANSACTION}${ReportActionsUtils.isMoneyRequestAction(action) ? action?.originalMessage?.IOUTransactionID : 0 ?? 0}`, }, walletTerms: { key: ONYXKEYS.WALLET_TERMS, diff --git a/src/components/ReportActionItem/moneyRequestPreviewProps.ts b/src/components/ReportActionItem/moneyRequestPreviewProps.ts new file mode 100644 index 000000000000..620d93c23399 --- /dev/null +++ b/src/components/ReportActionItem/moneyRequestPreviewProps.ts @@ -0,0 +1,32 @@ +import type {Ref} from 'react'; +// eslint-disable-next-line no-restricted-imports +import type {Text as RNText, StyleProp, ViewStyle} from 'react-native'; +import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; +import type {PersonalDetailsList, Report, Session, Transaction, TransactionViolation, WalletTerms} from '@src/types/onyx'; +import type {OriginalMessageIOU} from '@src/types/onyx/OriginalMessage'; +import type {ReportActionBase} from '@src/types/onyx/ReportAction'; + +type MoneyRequestPreviewOnyxProps = { + chatReport: OnyxEntry; + iouReport: OnyxEntry; + personalDetails: OnyxEntry; + session: OnyxEntry; + transaction: OnyxEntry; + transactionViolations: OnyxCollection; + walletTerms: OnyxEntry; +}; +type MoneyRequestPreviewProps = { + action: OnyxEntry; + chatReportID: string; + checkIfContextMenuActive: () => void; + containerStyles: StyleProp; + contextMenuAnchor?: Ref; + iouReportID: string; + isBillSplit: boolean; + isHovered: boolean; + isWhisper: boolean; + onPreviewPressed: (() => void) | null; + shouldShowPendingConversionMessage: boolean; +} & MoneyRequestPreviewOnyxProps; + +export type {MoneyRequestPreviewProps, MoneyRequestPreviewOnyxProps}; From 480a662dae34d3a497766ac62fb5dbc9b186db2e Mon Sep 17 00:00:00 2001 From: Trevor Coleman Date: Fri, 26 Jan 2024 17:04:37 -0500 Subject: [PATCH 30/53] create types compatible with MoneyRequestView's propTypes --- .../MoneyRequestPreview.js | 4 +- .../MoneyRequestPreviewWrapper.tsx | 3 +- .../MoneyRequestPreview/index.ts | 3 + .../moneyRequestPreviewPropTypes.ts | 2 +- .../moneyRequestPreviewProps.ts | 72 +++++++++++++++++++ .../moneyRequestPreviewProps.ts | 32 --------- src/languages/es.ts | 2 +- 7 files changed, 81 insertions(+), 37 deletions(-) rename src/components/ReportActionItem/{ => MoneyRequestPreview}/MoneyRequestPreview.js (99%) rename src/components/ReportActionItem/{ => MoneyRequestPreview}/MoneyRequestPreviewWrapper.tsx (91%) create mode 100644 src/components/ReportActionItem/MoneyRequestPreview/index.ts rename src/components/ReportActionItem/{ => MoneyRequestPreview}/moneyRequestPreviewPropTypes.ts (96%) create mode 100644 src/components/ReportActionItem/MoneyRequestPreview/moneyRequestPreviewProps.ts delete mode 100644 src/components/ReportActionItem/moneyRequestPreviewProps.ts diff --git a/src/components/ReportActionItem/MoneyRequestPreview.js b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreview.js similarity index 99% rename from src/components/ReportActionItem/MoneyRequestPreview.js rename to src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreview.js index 99a31d9d945d..e486f7575a04 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview.js +++ b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreview.js @@ -11,6 +11,7 @@ import MultipleAvatars from '@components/MultipleAvatars'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; import PressableWithFeedback from '@components/Pressable/PressableWithoutFeedback'; import RenderHTML from '@components/RenderHTML'; +import ReportActionItemImages from '@components/ReportActionItem/ReportActionItemImages'; import {showContextMenuForReport} from '@components/ShowContextMenuContext'; import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; @@ -22,6 +23,7 @@ import ControlSelection from '@libs/ControlSelection'; import * as CurrencyUtils from '@libs/CurrencyUtils'; import * as DeviceCapabilities from '@libs/DeviceCapabilities'; import * as IOUUtils from '@libs/IOUUtils'; +import * as Localize from '@libs/Localize'; import * as OptionsListUtils from '@libs/OptionsListUtils'; import * as ReceiptUtils from '@libs/ReceiptUtils'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; @@ -30,9 +32,7 @@ import * as TransactionUtils from '@libs/TransactionUtils'; import * as PaymentMethods from '@userActions/PaymentMethods'; import * as Report from '@userActions/Report'; import CONST from '@src/CONST'; -import * as Localize from '@src/libs/Localize'; import PropTypes from './moneyRequestPreviewPropTypes'; -import ReportActionItemImages from './ReportActionItemImages'; export default function MoneyRequestPreview(props) { const theme = useTheme(); diff --git a/src/components/ReportActionItem/MoneyRequestPreviewWrapper.tsx b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewWrapper.tsx similarity index 91% rename from src/components/ReportActionItem/MoneyRequestPreviewWrapper.tsx rename to src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewWrapper.tsx index e290f1056071..f00411adc158 100644 --- a/src/components/ReportActionItem/MoneyRequestPreviewWrapper.tsx +++ b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewWrapper.tsx @@ -5,6 +5,7 @@ import React from 'react'; import {withOnyx} from 'react-native-onyx'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import ONYXKEYS from '@src/ONYXKEYS'; +import type {ReportAction} from '@src/types/onyx'; import MoneyRequestPreview from './MoneyRequestPreview'; import type {MoneyRequestPreviewOnyxProps, MoneyRequestPreviewProps} from './moneyRequestPreviewProps'; @@ -31,7 +32,7 @@ export default withOnyx( key: ONYXKEYS.SESSION, }, transaction: { - key: ({action}) => `${ONYXKEYS.COLLECTION.TRANSACTION}${ReportActionsUtils.isMoneyRequestAction(action) ? action?.originalMessage?.IOUTransactionID : 0 ?? 0}`, + key: ({action}) => `${ONYXKEYS.COLLECTION.TRANSACTION}${ReportActionsUtils.isMoneyRequestAction(action as ReportAction) ? action?.originalMessage?.IOUTransactionID : 0 ?? 0}`, }, walletTerms: { key: ONYXKEYS.WALLET_TERMS, diff --git a/src/components/ReportActionItem/MoneyRequestPreview/index.ts b/src/components/ReportActionItem/MoneyRequestPreview/index.ts new file mode 100644 index 000000000000..7102d862765a --- /dev/null +++ b/src/components/ReportActionItem/MoneyRequestPreview/index.ts @@ -0,0 +1,3 @@ +import MoneyRequestPreviewWrapper from './MoneyRequestPreviewWrapper'; + +export default MoneyRequestPreviewWrapper; diff --git a/src/components/ReportActionItem/moneyRequestPreviewPropTypes.ts b/src/components/ReportActionItem/MoneyRequestPreview/moneyRequestPreviewPropTypes.ts similarity index 96% rename from src/components/ReportActionItem/moneyRequestPreviewPropTypes.ts rename to src/components/ReportActionItem/MoneyRequestPreview/moneyRequestPreviewPropTypes.ts index 27612727e7b6..22c1366119f3 100644 --- a/src/components/ReportActionItem/moneyRequestPreviewPropTypes.ts +++ b/src/components/ReportActionItem/MoneyRequestPreview/moneyRequestPreviewPropTypes.ts @@ -29,7 +29,7 @@ const propTypes = { /** Extra styles to pass to View wrapper */ // eslint-disable-next-line react/forbid-prop-types - containerStyles: PropTypes.arrayOf(PropTypes.object), + containerStyles: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.object), PropTypes.object, PropTypes.bool]), /* Onyx Props */ diff --git a/src/components/ReportActionItem/MoneyRequestPreview/moneyRequestPreviewProps.ts b/src/components/ReportActionItem/MoneyRequestPreview/moneyRequestPreviewProps.ts new file mode 100644 index 000000000000..070e38667ee8 --- /dev/null +++ b/src/components/ReportActionItem/MoneyRequestPreview/moneyRequestPreviewProps.ts @@ -0,0 +1,72 @@ +import type {Ref} from 'react'; +// eslint-disable-next-line no-restricted-imports +import type {Text as RNText, StyleProp, ViewStyle} from 'react-native'; +import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; +import type CONST from '@src/CONST'; +import type {PersonalDetailsList, Report, Session, Transaction, TransactionViolation, WalletTerms} from '@src/types/onyx'; +import type {OriginalMessageIOU} from '@src/types/onyx/OriginalMessage'; +import type {ReportActionBase} from '@src/types/onyx/ReportAction'; + +// typescript version of reportActionFragmentPropType +type MoneyRequestFragment = { + /** The type of the action item fragment. Used to render a corresponding component */ + type: string; + + /** The text content of the fragment. */ + text: string; + + /** Used to apply additional styling. Style refers to a predetermined constant and not a class name. e.g. 'normal' or 'strong' */ + style?: string; + + /** ID of a report */ + reportID?: string; + + /** ID of a policy */ + policyID?: string; + + /** The target of a link fragment e.g. '_blank' */ + target?: string; + + /** The destination of a link fragment e.g. 'https://www.expensify.com' */ + href?: string; + + /** An additional avatar url - not the main avatar url but used within a message. */ + iconUrl?: string; + + /** Fragment edited flag */ + isEdited?: boolean; +}; + +// We need to override some of these to match the more general reportActionPropType +type MoneyRequestAction = Omit & { + person: MoneyRequestFragment[]; + reportActionID: string; + created: string; + actionName: (typeof CONST.REPORT.ACTIONS.TYPE)[keyof typeof CONST.REPORT.ACTIONS.TYPE]; + originalMessage: OriginalMessageIOU; +}; + +type MoneyRequestPreviewOnyxProps = { + chatReport: OnyxEntry; + iouReport: OnyxEntry; + personalDetails: OnyxEntry; + session: OnyxEntry; + transaction: OnyxEntry; + transactionViolations: OnyxCollection; + walletTerms: OnyxEntry; +}; +type MoneyRequestPreviewProps = { + action: MoneyRequestAction; + chatReportID: string; + checkIfContextMenuActive: () => void; + containerStyles: StyleProp; + contextMenuAnchor?: Ref; + iouReportID: string; + isBillSplit: boolean; + isHovered: boolean; + isWhisper: boolean; + onPreviewPressed: (() => void) | null; + shouldShowPendingConversionMessage: boolean; +} & MoneyRequestPreviewOnyxProps; + +export type {MoneyRequestPreviewProps, MoneyRequestPreviewOnyxProps}; diff --git a/src/components/ReportActionItem/moneyRequestPreviewProps.ts b/src/components/ReportActionItem/moneyRequestPreviewProps.ts deleted file mode 100644 index 620d93c23399..000000000000 --- a/src/components/ReportActionItem/moneyRequestPreviewProps.ts +++ /dev/null @@ -1,32 +0,0 @@ -import type {Ref} from 'react'; -// eslint-disable-next-line no-restricted-imports -import type {Text as RNText, StyleProp, ViewStyle} from 'react-native'; -import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; -import type {PersonalDetailsList, Report, Session, Transaction, TransactionViolation, WalletTerms} from '@src/types/onyx'; -import type {OriginalMessageIOU} from '@src/types/onyx/OriginalMessage'; -import type {ReportActionBase} from '@src/types/onyx/ReportAction'; - -type MoneyRequestPreviewOnyxProps = { - chatReport: OnyxEntry; - iouReport: OnyxEntry; - personalDetails: OnyxEntry; - session: OnyxEntry; - transaction: OnyxEntry; - transactionViolations: OnyxCollection; - walletTerms: OnyxEntry; -}; -type MoneyRequestPreviewProps = { - action: OnyxEntry; - chatReportID: string; - checkIfContextMenuActive: () => void; - containerStyles: StyleProp; - contextMenuAnchor?: Ref; - iouReportID: string; - isBillSplit: boolean; - isHovered: boolean; - isWhisper: boolean; - onPreviewPressed: (() => void) | null; - shouldShowPendingConversionMessage: boolean; -} & MoneyRequestPreviewOnyxProps; - -export type {MoneyRequestPreviewProps, MoneyRequestPreviewOnyxProps}; diff --git a/src/languages/es.ts b/src/languages/es.ts index 9dd1a5145d98..394333ebb23a 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -2596,7 +2596,7 @@ export default { perDayLimit: ({formattedLimit}: ViolationsPerDayLimitParams) => `Importe supera el límite diario de la categoría${formattedLimit ? ` de ${formattedLimit}/persona` : ''}`, receiptNotSmartScanned: 'Recibo no verificado. Por favor, confirma su exactitud', receiptRequired: ({category, formattedLimit}: ViolationsReceiptRequiredParams) => - `Recibo obligatorio${category || formattedLimit ? ` para importes sobre${category ? ` el limite de la categoría de` : ''} ${formattedLimit}` : ''}`, + `Recibo obligatorio${!category && !formattedLimit ? '' : ` para importes sobre${category ? ` el limite de la categoría de` : ''} ${formattedLimit}`}`, reviewRequired: 'Revisión requerida', rter: ({brokenBankConnection, isAdmin, email, isTransactionOlderThan7Days, member}: ViolationsRterParams) => { if (brokenBankConnection) { From eea8acf7602f7014e3ffb06d3111e0499fe52f0c Mon Sep 17 00:00:00 2001 From: Trevor Coleman Date: Mon, 29 Jan 2024 13:32:05 -0500 Subject: [PATCH 31/53] WIP --- .../{MoneyRequestPreview => }/MoneyRequestPreview.js | 4 ++-- .../MoneyRequestPreview/MoneyRequestPreviewWrapper.tsx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) rename src/components/ReportActionItem/{MoneyRequestPreview => }/MoneyRequestPreview.js (99%) diff --git a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreview.js b/src/components/ReportActionItem/MoneyRequestPreview.js similarity index 99% rename from src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreview.js rename to src/components/ReportActionItem/MoneyRequestPreview.js index e486f7575a04..3479ff8a70d2 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreview.js +++ b/src/components/ReportActionItem/MoneyRequestPreview.js @@ -11,7 +11,6 @@ import MultipleAvatars from '@components/MultipleAvatars'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; import PressableWithFeedback from '@components/Pressable/PressableWithoutFeedback'; import RenderHTML from '@components/RenderHTML'; -import ReportActionItemImages from '@components/ReportActionItem/ReportActionItemImages'; import {showContextMenuForReport} from '@components/ShowContextMenuContext'; import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; @@ -32,7 +31,8 @@ import * as TransactionUtils from '@libs/TransactionUtils'; import * as PaymentMethods from '@userActions/PaymentMethods'; import * as Report from '@userActions/Report'; import CONST from '@src/CONST'; -import PropTypes from './moneyRequestPreviewPropTypes'; +import PropTypes from './MoneyRequestPreview/moneyRequestPreviewPropTypes'; +import ReportActionItemImages from './ReportActionItemImages'; export default function MoneyRequestPreview(props) { const theme = useTheme(); diff --git a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewWrapper.tsx b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewWrapper.tsx index f00411adc158..2f6f71fbe1ba 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewWrapper.tsx +++ b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewWrapper.tsx @@ -6,7 +6,7 @@ import {withOnyx} from 'react-native-onyx'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import ONYXKEYS from '@src/ONYXKEYS'; import type {ReportAction} from '@src/types/onyx'; -import MoneyRequestPreview from './MoneyRequestPreview'; +import MoneyRequestPreview from '.'; import type {MoneyRequestPreviewOnyxProps, MoneyRequestPreviewProps} from './moneyRequestPreviewProps'; // We should not render the component if there is no iouReport and it's not a split. @@ -32,7 +32,7 @@ export default withOnyx( key: ONYXKEYS.SESSION, }, transaction: { - key: ({action}) => `${ONYXKEYS.COLLECTION.TRANSACTION}${ReportActionsUtils.isMoneyRequestAction(action as ReportAction) ? action?.originalMessage?.IOUTransactionID : 0 ?? 0}`, + key: ({action}) => `${ONYXKEYS.COLLECTION.TRANSACTION}${ReportActionsUtils.isMoneyRequestAction(action) ? action?.originalMessage?.IOUTransactionID : 0 ?? 0}`, }, walletTerms: { key: ONYXKEYS.WALLET_TERMS, From 58f7ed50876990988b1a2972bac4500bf893cb44 Mon Sep 17 00:00:00 2001 From: Trevor Coleman Date: Mon, 29 Jan 2024 15:46:28 -0500 Subject: [PATCH 32/53] feat(Violations): use types from merged branch and rename components --- .../ReportActionItem/MoneyRequestAction.tsx | 4 +- .../MoneyRequestPreviewContent.tsx} | 104 ++-------------- .../MoneyRequestPreviewWrapper.tsx | 11 +- .../MoneyRequestPreview/index.ts | 3 - .../moneyRequestPreviewProps.ts | 116 +++++++++--------- 5 files changed, 73 insertions(+), 165 deletions(-) rename src/components/ReportActionItem/{MoneyRequestPreview.tsx => MoneyRequestPreview/MoneyRequestPreviewContent.tsx} (80%) delete mode 100644 src/components/ReportActionItem/MoneyRequestPreview/index.ts diff --git a/src/components/ReportActionItem/MoneyRequestAction.tsx b/src/components/ReportActionItem/MoneyRequestAction.tsx index 51c814c1b2c6..27ab4c5a5ce3 100644 --- a/src/components/ReportActionItem/MoneyRequestAction.tsx +++ b/src/components/ReportActionItem/MoneyRequestAction.tsx @@ -18,7 +18,7 @@ import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type * as OnyxTypes from '@src/types/onyx'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; -import MoneyRequestPreview from './MoneyRequestPreview'; +import MoneyRequestPreviewContent from './MoneyRequestPreview/MoneyRequestPreviewContent'; type MoneyRequestActionOnyxProps = { /** Chat report associated with iouReport */ @@ -121,7 +121,7 @@ function MoneyRequestAction({ return isDeletedParentAction || isReversedTransaction ? ( ${translate(isReversedTransaction ? 'parentReportAction.reversedTransaction' : 'parentReportAction.deletedRequest')}`} /> ) : ( - ; - - /** Chat report associated with iouReport */ - chatReport: OnyxEntry; - - /** IOU report data object */ - iouReport: OnyxEntry; - - /** Session info for the currently logged in user. */ - session: OnyxEntry; - - /** The transaction attached to the action.message.iouTransactionID */ - transaction: OnyxEntry; - - /** Information about the user accepting the terms for payments */ - walletTerms: OnyxEntry; -}; - -type MoneyRequestPreviewProps = MoneyRequestPreviewOnyxProps & { - /** The active IOUReport, used for Onyx subscription */ - // The iouReportID is used inside withOnyx HOC - // eslint-disable-next-line react/no-unused-prop-types - iouReportID: string; - - /** The associated chatReport */ - chatReportID: string; - - /** The ID of the current report */ - reportID: string; - - /** Callback for the preview pressed */ - onPreviewPressed: (event?: GestureResponderEvent | KeyboardEvent) => void; - - /** All the data of the action, used for showing context menu */ - action: OnyxTypes.ReportAction; - - /** Popover context menu anchor, used for showing context menu */ - contextMenuAnchor?: ContextMenuAnchor; - - /** Callback for updating context menu active state, used for showing context menu */ - checkIfContextMenuActive?: () => void; - - /** Extra styles to pass to View wrapper */ - containerStyles?: StyleProp; - - /** True if this is this IOU is a split instead of a 1:1 request */ - isBillSplit: boolean; - - /** True if the IOU Preview card is hovered */ - isHovered?: boolean; - - /** Whether or not an IOU report contains money requests in a different currency - * that are either created or cancelled offline, and thus haven't been converted to the report's currency yet - */ - shouldShowPendingConversionMessage?: boolean; - - /** Whether a message is a whisper */ - isWhisper?: boolean; -}; +import {isEmptyObject} from '@src/types/utils/EmptyObject'; +import type {MoneyRequestPreviewProps} from './moneyRequestPreviewProps'; -function MoneyRequestPreview({ +function MoneyRequestPreviewContent({ iouReport, isBillSplit, session, @@ -360,28 +294,6 @@ function MoneyRequestPreview({ ); } -MoneyRequestPreview.displayName = 'MoneyRequestPreview'; +MoneyRequestPreviewContent.displayName = 'MoneyRequestPreview'; -export default withOnyx({ - personalDetails: { - key: ONYXKEYS.PERSONAL_DETAILS_LIST, - }, - chatReport: { - key: ({chatReportID}) => `${ONYXKEYS.COLLECTION.REPORT}${chatReportID}`, - }, - iouReport: { - key: ({iouReportID}) => `${ONYXKEYS.COLLECTION.REPORT}${iouReportID}`, - }, - session: { - key: ONYXKEYS.SESSION, - }, - transaction: { - key: ({action}) => { - const originalMessage = action?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU ? action.originalMessage : undefined; - return `${ONYXKEYS.COLLECTION.TRANSACTION}${originalMessage?.IOUTransactionID ?? 0}`; - }, - }, - walletTerms: { - key: ONYXKEYS.WALLET_TERMS, - }, -})(MoneyRequestPreview); +export default MoneyRequestPreviewContent; diff --git a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewWrapper.tsx b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewWrapper.tsx index 2f6f71fbe1ba..4cebd503482e 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewWrapper.tsx +++ b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewWrapper.tsx @@ -5,18 +5,17 @@ import React from 'react'; import {withOnyx} from 'react-native-onyx'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {ReportAction} from '@src/types/onyx'; -import MoneyRequestPreview from '.'; +import MoneyRequestPreviewContent from './MoneyRequestPreviewContent'; import type {MoneyRequestPreviewOnyxProps, MoneyRequestPreviewProps} from './moneyRequestPreviewProps'; // We should not render the component if there is no iouReport and it's not a split. // Moved outside of the component scope to allow for easier use of hooks in the main component. -function MoneyRequestPreviewWrapper(props: MoneyRequestPreviewProps) { +function MoneyRequestPreview(props: MoneyRequestPreviewProps) { // eslint-disable-next-line react/jsx-props-no-spreading - return lodashIsEmpty(props.iouReport) && !props.isBillSplit ? null : ; + return lodashIsEmpty(props.iouReport) && !props.isBillSplit ? null : ; } -MoneyRequestPreviewWrapper.displayName = 'MoneyRequestPreviewWrapper'; +MoneyRequestPreview.displayName = 'MoneyRequestPreview'; export default withOnyx({ personalDetails: { @@ -40,4 +39,4 @@ export default withOnyx( transactionViolations: { key: ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS, }, -})(MoneyRequestPreviewWrapper); +})(MoneyRequestPreview); diff --git a/src/components/ReportActionItem/MoneyRequestPreview/index.ts b/src/components/ReportActionItem/MoneyRequestPreview/index.ts deleted file mode 100644 index 7102d862765a..000000000000 --- a/src/components/ReportActionItem/MoneyRequestPreview/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import MoneyRequestPreviewWrapper from './MoneyRequestPreviewWrapper'; - -export default MoneyRequestPreviewWrapper; diff --git a/src/components/ReportActionItem/MoneyRequestPreview/moneyRequestPreviewProps.ts b/src/components/ReportActionItem/MoneyRequestPreview/moneyRequestPreviewProps.ts index 070e38667ee8..1d58dbc35c74 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview/moneyRequestPreviewProps.ts +++ b/src/components/ReportActionItem/MoneyRequestPreview/moneyRequestPreviewProps.ts @@ -1,72 +1,72 @@ -import type {Ref} from 'react'; -// eslint-disable-next-line no-restricted-imports -import type {Text as RNText, StyleProp, ViewStyle} from 'react-native'; -import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; -import type CONST from '@src/CONST'; -import type {PersonalDetailsList, Report, Session, Transaction, TransactionViolation, WalletTerms} from '@src/types/onyx'; -import type {OriginalMessageIOU} from '@src/types/onyx/OriginalMessage'; -import type {ReportActionBase} from '@src/types/onyx/ReportAction'; +import type {GestureResponderEvent, StyleProp, ViewStyle} from 'react-native'; +import type {OnyxCollection} from 'react-native-onyx'; +import type {OnyxEntry} from 'react-native-onyx/lib/types'; +import type {ContextMenuAnchor} from '@pages/home/report/ContextMenu/ReportActionContextMenu'; +import type * as OnyxTypes from '@src/types/onyx'; -// typescript version of reportActionFragmentPropType -type MoneyRequestFragment = { - /** The type of the action item fragment. Used to render a corresponding component */ - type: string; - - /** The text content of the fragment. */ - text: string; - - /** Used to apply additional styling. Style refers to a predetermined constant and not a class name. e.g. 'normal' or 'strong' */ - style?: string; +type MoneyRequestPreviewOnyxProps = { + /** All of the personal details for everyone */ + personalDetails: OnyxEntry; - /** ID of a report */ - reportID?: string; + /** Chat report associated with iouReport */ + chatReport: OnyxEntry; - /** ID of a policy */ - policyID?: string; + /** IOU report data object */ + iouReport: OnyxEntry; - /** The target of a link fragment e.g. '_blank' */ - target?: string; + /** Session info for the currently logged in user. */ + session: OnyxEntry; - /** The destination of a link fragment e.g. 'https://www.expensify.com' */ - href?: string; + /** The transaction attached to the action.message.iouTransactionID */ + transaction: OnyxEntry; - /** An additional avatar url - not the main avatar url but used within a message. */ - iconUrl?: string; + /** The transaction violations attached to the action.message.iouTransactionID */ + transactionViolations: OnyxCollection; - /** Fragment edited flag */ - isEdited?: boolean; + /** Information about the user accepting the terms for payments */ + walletTerms: OnyxEntry; }; -// We need to override some of these to match the more general reportActionPropType -type MoneyRequestAction = Omit & { - person: MoneyRequestFragment[]; - reportActionID: string; - created: string; - actionName: (typeof CONST.REPORT.ACTIONS.TYPE)[keyof typeof CONST.REPORT.ACTIONS.TYPE]; - originalMessage: OriginalMessageIOU; -}; +type MoneyRequestPreviewProps = MoneyRequestPreviewOnyxProps & { + /** The active IOUReport, used for Onyx subscription */ + // The iouReportID is used inside withOnyx HOC + // eslint-disable-next-line react/no-unused-prop-types + iouReportID: string; -type MoneyRequestPreviewOnyxProps = { - chatReport: OnyxEntry; - iouReport: OnyxEntry; - personalDetails: OnyxEntry; - session: OnyxEntry; - transaction: OnyxEntry; - transactionViolations: OnyxCollection; - walletTerms: OnyxEntry; -}; -type MoneyRequestPreviewProps = { - action: MoneyRequestAction; + /** The associated chatReport */ chatReportID: string; - checkIfContextMenuActive: () => void; - containerStyles: StyleProp; - contextMenuAnchor?: Ref; - iouReportID: string; + + /** The ID of the current report */ + reportID: string; + + /** Callback for the preview pressed */ + onPreviewPressed: (event?: GestureResponderEvent | KeyboardEvent) => void; + + /** All the data of the action, used for showing context menu */ + action: OnyxTypes.ReportAction; + + /** Popover context menu anchor, used for showing context menu */ + contextMenuAnchor?: ContextMenuAnchor; + + /** Callback for updating context menu active state, used for showing context menu */ + checkIfContextMenuActive?: () => void; + + /** Extra styles to pass to View wrapper */ + containerStyles?: StyleProp; + + /** True if this is this IOU is a split instead of a 1:1 request */ isBillSplit: boolean; - isHovered: boolean; - isWhisper: boolean; - onPreviewPressed: (() => void) | null; - shouldShowPendingConversionMessage: boolean; -} & MoneyRequestPreviewOnyxProps; + + /** True if the IOU Preview card is hovered */ + isHovered?: boolean; + + /** Whether or not an IOU report contains money requests in a different currency + * that are either created or cancelled offline, and thus haven't been converted to the report's currency yet + */ + shouldShowPendingConversionMessage?: boolean; + + /** Whether a message is a whisper */ + isWhisper?: boolean; +}; export type {MoneyRequestPreviewProps, MoneyRequestPreviewOnyxProps}; From 3982df6872a9803ae41b875ad8f563705482971e Mon Sep 17 00:00:00 2001 From: Trevor Coleman Date: Mon, 29 Jan 2024 17:26:00 -0500 Subject: [PATCH 33/53] feat(Violations): rename component & file --- .../ReportActionItem/MoneyRequestAction.tsx | 4 +- ...neyRequestPreviewWrapper.tsx => index.tsx} | 0 .../moneyRequestPreviewPropTypes.ts | 103 ------------------ 3 files changed, 2 insertions(+), 105 deletions(-) rename src/components/ReportActionItem/MoneyRequestPreview/{MoneyRequestPreviewWrapper.tsx => index.tsx} (100%) delete mode 100644 src/components/ReportActionItem/MoneyRequestPreview/moneyRequestPreviewPropTypes.ts diff --git a/src/components/ReportActionItem/MoneyRequestAction.tsx b/src/components/ReportActionItem/MoneyRequestAction.tsx index 27ab4c5a5ce3..51c814c1b2c6 100644 --- a/src/components/ReportActionItem/MoneyRequestAction.tsx +++ b/src/components/ReportActionItem/MoneyRequestAction.tsx @@ -18,7 +18,7 @@ import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type * as OnyxTypes from '@src/types/onyx'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; -import MoneyRequestPreviewContent from './MoneyRequestPreview/MoneyRequestPreviewContent'; +import MoneyRequestPreview from './MoneyRequestPreview'; type MoneyRequestActionOnyxProps = { /** Chat report associated with iouReport */ @@ -121,7 +121,7 @@ function MoneyRequestAction({ return isDeletedParentAction || isReversedTransaction ? ( ${translate(isReversedTransaction ? 'parentReportAction.reversedTransaction' : 'parentReportAction.deletedRequest')}`} /> ) : ( - {}, - containerStyles: [], - walletTerms: {}, - chatReport: {}, - isHovered: false, - personalDetails: {}, - session: { - email: null, - }, - transaction: {}, - shouldShowPendingConversionMessage: false, - isWhisper: false, - transactionViolations: {}, -}; - -export default { - propTypes, - defaultProps, -}; From afc6324b2197abb1d4627f626db2b4c7c3a1dcf5 Mon Sep 17 00:00:00 2001 From: Trevor Coleman Date: Mon, 29 Jan 2024 17:59:15 -0500 Subject: [PATCH 34/53] feat(Violations): restore violations logic --- .../MoneyRequestPreviewContent.tsx | 18 ++++++++++++------ .../MoneyRequestPreview/index.tsx | 2 +- .../{moneyRequestPreviewProps.ts => types.ts} | 0 src/libs/ReportUtils.ts | 6 ++++-- src/libs/TransactionUtils.ts | 4 ++-- 5 files changed, 19 insertions(+), 11 deletions(-) rename src/components/ReportActionItem/MoneyRequestPreview/{moneyRequestPreviewProps.ts => types.ts} (100%) diff --git a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx index 1679393093e6..3d5caaeb47e7 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx +++ b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx @@ -29,13 +29,14 @@ import * as ReceiptUtils from '@libs/ReceiptUtils'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; import * as TransactionUtils from '@libs/TransactionUtils'; +import ViolationsUtils from '@libs/Violations/ViolationsUtils'; import * as PaymentMethods from '@userActions/PaymentMethods'; import * as Report from '@userActions/Report'; 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 './moneyRequestPreviewProps'; +import type {MoneyRequestPreviewProps} from './types'; function MoneyRequestPreviewContent({ iouReport, @@ -55,6 +56,7 @@ function MoneyRequestPreviewContent({ shouldShowPendingConversionMessage = false, isHovered = false, isWhisper = false, + transactionViolations, }: MoneyRequestPreviewProps) { const theme = useTheme(); const styles = useThemeStyles(); @@ -63,10 +65,6 @@ function MoneyRequestPreviewContent({ const {isSmallScreenWidth, windowWidth} = useWindowDimensions(); const parser = new ExpensiMark(); - if (isEmptyObject(iouReport) && !isBillSplit) { - return null; - } - const sessionAccountID = session?.accountID; const managerID = iouReport?.managerID ?? -1; const ownerAccountID = iouReport?.ownerAccountID ?? -1; @@ -87,6 +85,7 @@ function MoneyRequestPreviewContent({ const requestMerchant = truncate(merchant, {length: CONST.REQUEST_PREVIEW.MAX_LENGTH}); const hasReceipt = TransactionUtils.hasReceipt(transaction); const isScanning = hasReceipt && TransactionUtils.isReceiptBeingScanned(transaction); + const hasViolations = transaction && TransactionUtils.hasViolation(transaction, transactionViolations); const hasFieldErrors = TransactionUtils.hasMissingSmartscanFields(transaction); const isDistanceRequest = TransactionUtils.isDistanceRequest(transaction); const isExpensifyCardTransaction = TransactionUtils.isExpensifyCardTransaction(transaction); @@ -140,7 +139,14 @@ function MoneyRequestPreviewContent({ } let message = translate('iou.cash'); - if (ReportUtils.isPaidGroupPolicyExpenseReport(iouReport) && ReportUtils.isReportApproved(iouReport) && !ReportUtils.isSettled(iouReport?.reportID)) { + if (hasViolations && transaction) { + const violations = TransactionUtils.getTransactionViolations(transaction, transactionViolations); + if (violations?.[0]) { + const violationMessage = ViolationsUtils.getViolationTranslation(violations?.[0], translate); + const isTooLong = violations.filter((v) => v.type === 'violation').length > 1 || violationMessage.length > 15; + message += ` • ${isTooLong ? translate('violations.reviewRequired') : violationMessage}`; + } + } else if (ReportUtils.isPaidGroupPolicyExpenseReport(iouReport) && ReportUtils.isReportApproved(iouReport) && !ReportUtils.isSettled(iouReport)) { message += ` • ${translate('iou.approved')}`; } else if (iouReport?.isWaitingOnBankAccount) { message += ` • ${translate('iou.pending')}`; diff --git a/src/components/ReportActionItem/MoneyRequestPreview/index.tsx b/src/components/ReportActionItem/MoneyRequestPreview/index.tsx index 4cebd503482e..81e36a423f66 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview/index.tsx +++ b/src/components/ReportActionItem/MoneyRequestPreview/index.tsx @@ -6,7 +6,7 @@ import {withOnyx} from 'react-native-onyx'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import ONYXKEYS from '@src/ONYXKEYS'; import MoneyRequestPreviewContent from './MoneyRequestPreviewContent'; -import type {MoneyRequestPreviewOnyxProps, MoneyRequestPreviewProps} from './moneyRequestPreviewProps'; +import type {MoneyRequestPreviewOnyxProps, MoneyRequestPreviewProps} from './types'; // We should not render the component if there is no iouReport and it's not a split. // Moved outside of the component scope to allow for easier use of hooks in the main component. diff --git a/src/components/ReportActionItem/MoneyRequestPreview/moneyRequestPreviewProps.ts b/src/components/ReportActionItem/MoneyRequestPreview/types.ts similarity index 100% rename from src/components/ReportActionItem/MoneyRequestPreview/moneyRequestPreviewProps.ts rename to src/components/ReportActionItem/MoneyRequestPreview/types.ts diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index be447cf2ea9d..21937e7606df 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -691,10 +691,12 @@ function hasParticipantInArray(report: Report, policyMemberAccountIDs: number[]) /** * Whether the Money Request report is settled */ -function isSettled(reportID: string | undefined): boolean { - if (!allReports) { +function isSettled(reportOrID: Report | OnyxEntry | string | undefined): boolean { + if (!allReports || !reportOrID) { return false; } + const reportID = typeof reportOrID === 'string' ? reportOrID : reportOrID?.reportID; + const report: Report | EmptyObject = allReports[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`] ?? {}; if (isEmptyObject(report) || report.isWaitingOnBankAccount) { return false; diff --git a/src/libs/TransactionUtils.ts b/src/libs/TransactionUtils.ts index ffbe414c58dd..97a1c3e95d6b 100644 --- a/src/libs/TransactionUtils.ts +++ b/src/libs/TransactionUtils.ts @@ -531,8 +531,8 @@ function getRecentTransactions(transactions: Record, size = 2): /** * Checks if any violations for the provided transaction are of type 'violation' */ -function hasViolation(transaction: Transaction | string, transactionViolations: OnyxCollection): boolean { - const transactionID = typeof transaction === 'string' ? transaction : transaction.transactionID; +function hasViolation(transactionOrID: Transaction | string, transactionViolations: OnyxCollection): boolean { + const transactionID = typeof transactionOrID === 'string' ? transactionOrID : transactionOrID.transactionID; return Boolean(transactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transactionID]?.some((violation: TransactionViolation) => violation.type === 'violation')); } From 1d734de4ccfc0c091376b7595c62b40981603b07 Mon Sep 17 00:00:00 2001 From: Trevor Coleman Date: Mon, 29 Jan 2024 18:09:03 -0500 Subject: [PATCH 35/53] feat(Violations): handle null case --- .../MoneyRequestPreview/MoneyRequestPreviewContent.tsx | 5 +++-- src/libs/TransactionUtils.ts | 5 ++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx index 3d5caaeb47e7..47e3b30ecb99 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx +++ b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx @@ -85,8 +85,9 @@ function MoneyRequestPreviewContent({ const requestMerchant = truncate(merchant, {length: CONST.REQUEST_PREVIEW.MAX_LENGTH}); const hasReceipt = TransactionUtils.hasReceipt(transaction); const isScanning = hasReceipt && TransactionUtils.isReceiptBeingScanned(transaction); - const hasViolations = transaction && TransactionUtils.hasViolation(transaction, transactionViolations); + const hasViolations = TransactionUtils.hasViolation(transaction, transactionViolations); const hasFieldErrors = TransactionUtils.hasMissingSmartscanFields(transaction); + const shouldShowRBR = hasViolations || hasFieldErrors; const isDistanceRequest = TransactionUtils.isDistanceRequest(transaction); const isExpensifyCardTransaction = TransactionUtils.isExpensifyCardTransaction(transaction); const isSettled = ReportUtils.isSettled(iouReport?.reportID); @@ -213,7 +214,7 @@ function MoneyRequestPreviewContent({ {getPreviewHeaderText() + (isSettled && !iouReport?.isCancelledIOU ? ` • ${getSettledMessage()}` : '')} - {!isSettled && hasFieldErrors && ( + {!isSettled && shouldShowRBR && ( , size = 2): /** * Checks if any violations for the provided transaction are of type 'violation' */ -function hasViolation(transactionOrID: Transaction | string, transactionViolations: OnyxCollection): boolean { +function hasViolation(transactionOrID: Transaction | OnyxEntry | string, transactionViolations: OnyxCollection): boolean { + if (!transactionOrID) { + return false; + } const transactionID = typeof transactionOrID === 'string' ? transactionOrID : transactionOrID.transactionID; return Boolean(transactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transactionID]?.some((violation: TransactionViolation) => violation.type === 'violation')); } From 7a03998f753b3bfcdaa35b9a0c3e50d3f2343638 Mon Sep 17 00:00:00 2001 From: Trevor Coleman Date: Mon, 29 Jan 2024 18:11:33 -0500 Subject: [PATCH 36/53] feat(Violations): remove nullish coalescing from narrowed type --- .../MoneyRequestPreview/MoneyRequestPreviewContent.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx index 47e3b30ecb99..32f4e29eced3 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx +++ b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx @@ -143,7 +143,7 @@ function MoneyRequestPreviewContent({ if (hasViolations && transaction) { const violations = TransactionUtils.getTransactionViolations(transaction, transactionViolations); if (violations?.[0]) { - const violationMessage = ViolationsUtils.getViolationTranslation(violations?.[0], translate); + const violationMessage = ViolationsUtils.getViolationTranslation(violations[0], translate); const isTooLong = violations.filter((v) => v.type === 'violation').length > 1 || violationMessage.length > 15; message += ` • ${isTooLong ? translate('violations.reviewRequired') : violationMessage}`; } From ddf8c630eaaa4a75a035c3c0915902c96d5e38be Mon Sep 17 00:00:00 2001 From: Trevor Coleman Date: Tue, 30 Jan 2024 13:40:09 -0500 Subject: [PATCH 37/53] feat(Violations): resolve type errors and import issues --- src/components/ReportActionItem/MoneyRequestAction.tsx | 2 +- .../ReportActionItem/MoneyRequestPreview/types.ts | 3 +-- src/components/ReportActionItem/MoneyRequestView.tsx | 4 ++-- src/components/ReportActionItem/ReportActionItemImage.tsx | 2 +- src/libs/OptionsListUtils.ts | 5 +++-- src/libs/TransactionUtils.ts | 7 ++++++- .../report/ContextMenu/PopoverReportActionContextMenu.tsx | 2 +- src/types/onyx/Transaction.ts | 2 +- 8 files changed, 16 insertions(+), 11 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestAction.tsx b/src/components/ReportActionItem/MoneyRequestAction.tsx index 51c814c1b2c6..ff29bf5b0ee8 100644 --- a/src/components/ReportActionItem/MoneyRequestAction.tsx +++ b/src/components/ReportActionItem/MoneyRequestAction.tsx @@ -1,7 +1,7 @@ import React from 'react'; import type {StyleProp, ViewStyle} from 'react-native'; import {withOnyx} from 'react-native-onyx'; -import type {OnyxEntry} from 'react-native-onyx/lib/types'; +import type {OnyxEntry} from 'react-native-onyx'; import RenderHTML from '@components/RenderHTML'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; diff --git a/src/components/ReportActionItem/MoneyRequestPreview/types.ts b/src/components/ReportActionItem/MoneyRequestPreview/types.ts index 1d58dbc35c74..17dd42b2f794 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview/types.ts +++ b/src/components/ReportActionItem/MoneyRequestPreview/types.ts @@ -1,6 +1,5 @@ import type {GestureResponderEvent, StyleProp, ViewStyle} from 'react-native'; -import type {OnyxCollection} from 'react-native-onyx'; -import type {OnyxEntry} from 'react-native-onyx/lib/types'; +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'; diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index 877094ca509a..223599924348 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -137,10 +137,10 @@ function MoneyRequestView({ // Flags for showing categories and tags // transactionCategory can be an empty string // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - const shouldShowCategory = isPolicyExpenseChat && (transactionCategory || OptionsListUtils.hasEnabledOptions(Object.values(policyCategories ?? {}))); + const shouldShowCategory = isPolicyExpenseChat && (transactionCategory || OptionsListUtils.hasEnabledOptions(policyCategories ?? {})); // transactionTag can be an empty string // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - const shouldShowTag = isPolicyExpenseChat && (transactionTag || OptionsListUtils.hasEnabledOptions(Object.values(policyTagsList))); + const shouldShowTag = isPolicyExpenseChat && (transactionTag || OptionsListUtils.hasEnabledOptions(policyTagsList)); const shouldShowBillable = isPolicyExpenseChat && (!!transactionBillable || !(policy?.disabledFields?.defaultBillable ?? true)); const {getViolationsForField} = useViolations(transactionViolations ?? []); diff --git a/src/components/ReportActionItem/ReportActionItemImage.tsx b/src/components/ReportActionItem/ReportActionItemImage.tsx index c014a71a4789..089402d51bf2 100644 --- a/src/components/ReportActionItem/ReportActionItemImage.tsx +++ b/src/components/ReportActionItem/ReportActionItemImage.tsx @@ -3,7 +3,7 @@ import React from 'react'; import type {ReactElement} from 'react'; import {View} from 'react-native'; import type {ImageSourcePropType} from 'react-native'; -import type {OnyxEntry} from 'react-native-onyx/lib/types'; +import type {OnyxEntry} from 'react-native-onyx'; import AttachmentModal from '@components/AttachmentModal'; import EReceiptThumbnail from '@components/EReceiptThumbnail'; import Image from '@components/Image'; diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts index be24cfb36f59..65a6293f3bab 100644 --- a/src/libs/OptionsListUtils.ts +++ b/src/libs/OptionsListUtils.ts @@ -19,6 +19,7 @@ import type { PolicyCategories, PolicyCategory, PolicyTag, + PolicyTags, Report, ReportAction, ReportActions, @@ -782,8 +783,8 @@ function getEnabledCategoriesCount(options: PolicyCategories): number { /** * Verifies that there is at least one enabled option */ -function hasEnabledOptions(options: PolicyCategory[] | PolicyTag[]): boolean { - return options.some((option) => option.enabled); +function hasEnabledOptions(options: PolicyCategories | PolicyTags): boolean { + return Object.values(options).some((option) => option.enabled); } /** diff --git a/src/libs/TransactionUtils.ts b/src/libs/TransactionUtils.ts index bd3d08425b7d..58b65ab7fb5f 100644 --- a/src/libs/TransactionUtils.ts +++ b/src/libs/TransactionUtils.ts @@ -539,7 +539,12 @@ function hasViolation(transactionOrID: Transaction | OnyxEntry | st return Boolean(transactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transactionID]?.some((violation: TransactionViolation) => violation.type === 'violation')); } -function getTransactionViolations({transactionID}: Transaction, transactionViolations: OnyxCollection): TransactionViolation[] | null { +function getTransactionViolations(transactionOrID: OnyxEntry | string, transactionViolations: OnyxCollection): TransactionViolation[] | null { + if (!transactionOrID) { + return null; + } + const transactionID = typeof transactionOrID === 'string' ? transactionOrID : transactionOrID.transactionID; + return transactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transactionID] ?? null; } diff --git a/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.tsx b/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.tsx index f37eabaa7c1d..272548214ece 100644 --- a/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.tsx +++ b/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.tsx @@ -67,7 +67,7 @@ function PopoverReportActionContextMenu(_props: never, ref: ForwardedRef(null); const anchorRef = useRef(null); const dimensionsEventListener = useRef(null); - const contextMenuAnchorRef = useRef(null); + const contextMenuAnchorRef = useRef(null); const contextMenuTargetNode = useRef(null); const onPopoverShow = useRef(() => {}); diff --git a/src/types/onyx/Transaction.ts b/src/types/onyx/Transaction.ts index 3a652ba0ef8c..65dd9d6edaa9 100644 --- a/src/types/onyx/Transaction.ts +++ b/src/types/onyx/Transaction.ts @@ -78,7 +78,7 @@ type Transaction = { routes?: Routes; transactionID: string; tag: string; - pendingFields?: Partial<{[K in TransactionPendingFieldsKey]: ValueOf}>; + pendingFields?: Partial<{[K in keyof Transaction | keyof Comment]: ValueOf}>; /** Card Transactions */ From 3aea37c9c6d4a8356c3bbe58ce734a4e86795c62 Mon Sep 17 00:00:00 2001 From: Trevor Coleman Date: Tue, 30 Jan 2024 18:35:07 -0500 Subject: [PATCH 38/53] feat(Violations): pass ReceiptError properly down to DotIndicatorMessage --- src/components/DotIndicatorMessage.tsx | 20 +++++++---- src/components/MessagesRow.tsx | 3 +- src/components/OfflineWithFeedback.tsx | 14 ++++---- .../ReportActionItem/MoneyRequestView.tsx | 35 +++++++++++++++++-- src/libs/ErrorUtils.ts | 21 ++++++++++- src/libs/Violations/ViolationsUtils.ts | 2 +- src/types/onyx/Transaction.ts | 2 +- 7 files changed, 75 insertions(+), 22 deletions(-) diff --git a/src/components/DotIndicatorMessage.tsx b/src/components/DotIndicatorMessage.tsx index d18704fdfb05..ac4dcb4cd30a 100644 --- a/src/components/DotIndicatorMessage.tsx +++ b/src/components/DotIndicatorMessage.tsx @@ -7,14 +7,14 @@ import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import fileDownload from '@libs/fileDownload'; import * as Localize from '@libs/Localize'; +import type {MaybePhraseKey} from '@libs/Localize'; import CONST from '@src/CONST'; +import type {ReceiptError} from '@src/types/onyx/Transaction'; import Icon from './Icon'; import * as Expensicons from './Icon/Expensicons'; import {PressableWithoutFeedback} from './Pressable'; import Text from './Text'; -type ReceiptError = {error?: string; source: string; filename: string}; - type DotIndicatorMessageProps = { /** * In most cases this should just be errors from onxyData @@ -23,7 +23,7 @@ type DotIndicatorMessageProps = { * timestamp: 'message', * } */ - messages: Record; + messages: Record; /** The type of message, 'error' shows a red dot, 'success' shows a green dot */ type: 'error' | 'success'; @@ -36,11 +36,17 @@ type DotIndicatorMessageProps = { }; /** Check if the error includes a receipt. */ -function isReceiptError(message: string | ReceiptError): message is ReceiptError { +function isReceiptError(message: unknown): message is ReceiptError { if (typeof message === 'string') { return false; } - return (message?.error ?? '') === CONST.IOU.RECEIPT_ERROR; + if (Array.isArray(message)) { + return false; + } + if (Object.keys(message as Record).length === 0) { + return false; + } + return ((message as Record)?.error ?? '') === CONST.IOU.RECEIPT_ERROR; } function DotIndicatorMessage({messages = {}, style, type, textStyles}: DotIndicatorMessageProps) { @@ -53,12 +59,12 @@ function DotIndicatorMessage({messages = {}, style, type, textStyles}: DotIndica } // Fetch the keys, sort them, and map through each key to get the corresponding message - const sortedMessages = Object.keys(messages) + const sortedMessages: Array = Object.keys(messages) .sort() .map((key) => messages[key]); // Removing duplicates using Set and transforming the result into an array - const uniqueMessages = [...new Set(sortedMessages)].map((message) => Localize.translateIfPhraseKey(message)); + const uniqueMessages: Array = [...new Set(sortedMessages)].map((message) => (isReceiptError(message) ? message : Localize.translateIfPhraseKey(message))); const isErrorMessage = type === 'error'; diff --git a/src/components/MessagesRow.tsx b/src/components/MessagesRow.tsx index cfec6fd292e9..7c764ec94fcd 100644 --- a/src/components/MessagesRow.tsx +++ b/src/components/MessagesRow.tsx @@ -6,6 +6,7 @@ import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import type * as Localize from '@libs/Localize'; import CONST from '@src/CONST'; +import type {ReceiptError} from '@src/types/onyx/Transaction'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; import DotIndicatorMessage from './DotIndicatorMessage'; import Icon from './Icon'; @@ -15,7 +16,7 @@ import Tooltip from './Tooltip'; type MessagesRowProps = { /** The messages to display */ - messages: Record; + messages: Record; /** The type of message, 'error' shows a red dot, 'success' shows a green dot */ type: 'error' | 'success'; diff --git a/src/components/OfflineWithFeedback.tsx b/src/components/OfflineWithFeedback.tsx index 1a8f313af267..9086c0c21ea7 100644 --- a/src/components/OfflineWithFeedback.tsx +++ b/src/components/OfflineWithFeedback.tsx @@ -1,13 +1,15 @@ -import React, {useCallback} from 'react'; +import React, {useCallback, useMemo} from 'react'; import type {ImageStyle, StyleProp, TextStyle, ViewStyle} from 'react-native'; import {View} from 'react-native'; import useNetwork from '@hooks/useNetwork'; import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; +import {removeErrorsWithNullMessage} from '@libs/ErrorUtils'; import mapChildrenFlat from '@libs/mapChildrenFlat'; import shouldRenderOffscreen from '@libs/shouldRenderOffscreen'; import CONST from '@src/CONST'; import type * as OnyxCommon from '@src/types/onyx/OnyxCommon'; +import type {ReceiptErrors} from '@src/types/onyx/Transaction'; import type ChildrenProps from '@src/types/utils/ChildrenProps'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; import MessagesRow from './MessagesRow'; @@ -26,7 +28,7 @@ type OfflineWithFeedbackProps = ChildrenProps & { shouldHideOnDelete?: boolean; /** The errors to display */ - errors?: OnyxCommon.Errors | null; + errors?: OnyxCommon.Errors | ReceiptErrors | null; /** Whether we should show the error messages */ shouldShowErrorMessages?: boolean; @@ -58,11 +60,6 @@ type OfflineWithFeedbackProps = ChildrenProps & { type StrikethroughProps = Partial & {style: Array}; -function omitBy(obj: Record | undefined | null, predicate: (value: T) => boolean) { - // eslint-disable-next-line @typescript-eslint/naming-convention, @typescript-eslint/no-unused-vars - return Object.fromEntries(Object.entries(obj ?? {}).filter(([_, value]) => !predicate(value))); -} - function OfflineWithFeedback({ pendingAction, canDismissError = true, @@ -84,7 +81,8 @@ function OfflineWithFeedback({ const hasErrors = !isEmptyObject(errors ?? {}); // Some errors have a null message. This is used to apply opacity only and to avoid showing redundant messages. - const errorMessages = omitBy(errors, (e) => e === null); + const errorMessages = useMemo(() => removeErrorsWithNullMessage(errors), [errors]); + const hasErrorMessages = !isEmptyObject(errorMessages); const isOfflinePendingAction = !!isOffline && !!pendingAction; const isUpdateOrDeleteError = hasErrors && (pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE || pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE); diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index 760ed13d3831..7ebc33518d88 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -1,4 +1,4 @@ -import React, {useCallback} from 'react'; +import React, {useCallback, useMemo} from 'react'; import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import type {OnyxEntry} from 'react-native-onyx'; @@ -34,7 +34,8 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type * as OnyxTypes from '@src/types/onyx'; -import type {TransactionPendingFieldsKey} from '@src/types/onyx/Transaction'; +import type {Errors} from '@src/types/onyx/OnyxCommon'; +import type {ReceiptError, ReceiptErrors, TransactionPendingFieldsKey} from '@src/types/onyx/Transaction'; import ReportActionItemImage from './ReportActionItemImage'; type MoneyRequestViewTransactionOnyxProps = { @@ -198,6 +199,31 @@ function MoneyRequestView({ const pendingAction = transaction?.pendingAction; const getPendingFieldAction = (fieldPath: TransactionPendingFieldsKey) => transaction?.pendingFields?.[fieldPath] ?? pendingAction; + const errors: Errors | null = useMemo(() => { + if (!transaction) { + return null; + } + + const transactionErrors = transaction.errors; + + if (transactionErrors) { + const errorKeys = Object.keys(transactionErrors) as Array; + + if (typeof transactionErrors[errorKeys[0]] === 'string') { + // transactionErrors is of type "Errors" + return transactionErrors as Errors; + } + // transactionErrors is of type "ReceiptErrors" + return errorKeys.reduce((acc, key) => { + const receiptError: ReceiptError = (transactionErrors as ReceiptErrors)[key]; + acc[key] = receiptError.error ?? ''; + return acc; + }, {} as Errors); + } + + return null; + }, [transaction]); + return ( @@ -205,9 +231,12 @@ function MoneyRequestView({ {hasReceipt && ( { + if (!transaction) { + return; + } Transaction.clearError(transaction.transactionID); }} > diff --git a/src/libs/ErrorUtils.ts b/src/libs/ErrorUtils.ts index 2466a262b4b9..39ec7f5768de 100644 --- a/src/libs/ErrorUtils.ts +++ b/src/libs/ErrorUtils.ts @@ -2,6 +2,7 @@ import CONST from '@src/CONST'; import type {TranslationFlatObject, TranslationPaths} from '@src/languages/types'; import type {ErrorFields, Errors} from '@src/types/onyx/OnyxCommon'; import type Response from '@src/types/onyx/Response'; +import type {ReceiptErrors} from '@src/types/onyx/Transaction'; import DateUtils from './DateUtils'; import * as Localize from './Localize'; @@ -119,4 +120,22 @@ function addErrorMessage(errors: ErrorsList, inpu } } -export {getAuthenticateErrorMessage, getMicroSecondOnyxError, getMicroSecondOnyxErrorObject, getLatestErrorMessage, getLatestErrorField, getEarliestErrorField, addErrorMessage}; +function removeErrorsWithNullMessage(errors: Errors | ReceiptErrors | null | undefined) { + if (!errors) { + return errors; + } + const nonNullEntries = Object.entries(errors).filter(([, message]) => message !== null); + + return Object.fromEntries(nonNullEntries); +} + +export { + getAuthenticateErrorMessage, + getMicroSecondOnyxError, + getMicroSecondOnyxErrorObject, + getLatestErrorMessage, + getLatestErrorField, + getEarliestErrorField, + addErrorMessage, + removeErrorsWithNullMessage, +}; diff --git a/src/libs/Violations/ViolationsUtils.ts b/src/libs/Violations/ViolationsUtils.ts index 20c29b0fb208..de4b03720677 100644 --- a/src/libs/Violations/ViolationsUtils.ts +++ b/src/libs/Violations/ViolationsUtils.ts @@ -106,7 +106,7 @@ const ViolationsUtils = { surcharge = 0, invoiceMarkup = 0, maxAge = 0, - tagName = 'etiqueta', + tagName, taxName, } = violation.data ?? {}; diff --git a/src/types/onyx/Transaction.ts b/src/types/onyx/Transaction.ts index dc777267b78f..1724d6c27b2f 100644 --- a/src/types/onyx/Transaction.ts +++ b/src/types/onyx/Transaction.ts @@ -103,4 +103,4 @@ type Transaction = { }; export default Transaction; -export type {WaypointCollection, Comment, Receipt, Waypoint, ReceiptError, TransactionPendingFieldsKey}; +export type {WaypointCollection, Comment, Receipt, Waypoint, ReceiptError, ReceiptErrors, TransactionPendingFieldsKey}; From cfdfb2d610946e58322a09a4282b298ff5a65b7c Mon Sep 17 00:00:00 2001 From: Trevor Coleman Date: Tue, 30 Jan 2024 19:17:51 -0500 Subject: [PATCH 39/53] feat(Violations): removed unused imports --- src/libs/OptionsListUtils.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts index 65a6293f3bab..3cf6785fcc0b 100644 --- a/src/libs/OptionsListUtils.ts +++ b/src/libs/OptionsListUtils.ts @@ -17,8 +17,6 @@ import type { PersonalDetailsList, Policy, PolicyCategories, - PolicyCategory, - PolicyTag, PolicyTags, Report, ReportAction, From 4075baaa6482639b2eb2c5e794d13c2a654f5488 Mon Sep 17 00:00:00 2001 From: Trevor Coleman Date: Tue, 30 Jan 2024 19:18:53 -0500 Subject: [PATCH 40/53] feat(Violations): handle null case of OnyxEntry. --- src/libs/ReceiptUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/ReceiptUtils.ts b/src/libs/ReceiptUtils.ts index cbffb5b454b3..444b9b0f3954 100644 --- a/src/libs/ReceiptUtils.ts +++ b/src/libs/ReceiptUtils.ts @@ -37,7 +37,7 @@ function getThumbnailAndImageURIs(transaction: OnyxEntry, receiptPa } // URI to image, i.e. blob:new.expensify.com/9ef3a018-4067-47c6-b29f-5f1bd35f213d or expensify.com/receipts/w_e616108497ef940b7210ec6beb5a462d01a878f4.jpg // If there're errors, we need to display them in preview. We can store many files in errors, but we just need to get the last one - const errors = _.findLast(transaction.errors) as ReceiptError | undefined; + const errors = _.findLast(transaction?.errors) as ReceiptError | undefined; const path = errors?.source ?? transaction?.receipt?.source ?? receiptPath ?? ''; // filename of uploaded image or last part of remote URI const filename = errors?.filename ?? transaction?.filename ?? receiptFileName ?? ''; From 9f5e655ab101714b7a3c8ee4a8c1fbe5b6d3e242 Mon Sep 17 00:00:00 2001 From: Trevor Coleman Date: Wed, 31 Jan 2024 13:45:52 -0500 Subject: [PATCH 41/53] nullishly coalesce --- src/libs/SidebarUtils.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index c61c6d940a48..5c2a2b748da3 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -56,11 +56,7 @@ let currentUserAccountID: number | undefined; Onyx.connect({ key: ONYXKEYS.SESSION, callback: (session) => { - if (!session) { - return; - } - - currentUserAccountID = session.accountID; + currentUserAccountID = session?.accountID; }, }); From 8cd7fcd2b6fdfb8403d29c7a22427d3e8c6249f7 Mon Sep 17 00:00:00 2001 From: Trevor Coleman Date: Thu, 1 Feb 2024 12:57:27 -0500 Subject: [PATCH 42/53] feat(Violations): move isReceiptError to ErrorUtils.ts --- src/components/DotIndicatorMessage.tsx | 17 ++--------------- .../ReportActionItem/MoneyRequestView.tsx | 14 +++++++++----- src/libs/ErrorUtils.ts | 17 ++++++++++++++++- 3 files changed, 27 insertions(+), 21 deletions(-) diff --git a/src/components/DotIndicatorMessage.tsx b/src/components/DotIndicatorMessage.tsx index ac4dcb4cd30a..81297c74f63f 100644 --- a/src/components/DotIndicatorMessage.tsx +++ b/src/components/DotIndicatorMessage.tsx @@ -5,9 +5,10 @@ import {View} from 'react-native'; import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; +import {isReceiptError} from '@libs/ErrorUtils'; import fileDownload from '@libs/fileDownload'; -import * as Localize from '@libs/Localize'; import type {MaybePhraseKey} from '@libs/Localize'; +import * as Localize from '@libs/Localize'; import CONST from '@src/CONST'; import type {ReceiptError} from '@src/types/onyx/Transaction'; import Icon from './Icon'; @@ -35,20 +36,6 @@ type DotIndicatorMessageProps = { textStyles?: StyleProp; }; -/** Check if the error includes a receipt. */ -function isReceiptError(message: unknown): message is ReceiptError { - if (typeof message === 'string') { - return false; - } - if (Array.isArray(message)) { - return false; - } - if (Object.keys(message as Record).length === 0) { - return false; - } - return ((message as Record)?.error ?? '') === CONST.IOU.RECEIPT_ERROR; -} - function DotIndicatorMessage({messages = {}, style, type, textStyles}: DotIndicatorMessageProps) { const theme = useTheme(); const styles = useThemeStyles(); diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index 7ebc33518d88..e935e1955ed3 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -20,6 +20,7 @@ import type {ViolationField} from '@hooks/useViolations'; import useWindowDimensions from '@hooks/useWindowDimensions'; import * as CardUtils from '@libs/CardUtils'; import * as CurrencyUtils from '@libs/CurrencyUtils'; +import {isReceiptError} from '@libs/ErrorUtils'; import Navigation from '@libs/Navigation/Navigation'; import * as OptionsListUtils from '@libs/OptionsListUtils'; import * as PolicyUtils from '@libs/PolicyUtils'; @@ -35,7 +36,7 @@ import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type * as OnyxTypes from '@src/types/onyx'; import type {Errors} from '@src/types/onyx/OnyxCommon'; -import type {ReceiptError, ReceiptErrors, TransactionPendingFieldsKey} from '@src/types/onyx/Transaction'; +import type {ReceiptErrors, TransactionPendingFieldsKey} from '@src/types/onyx/Transaction'; import ReportActionItemImage from './ReportActionItemImage'; type MoneyRequestViewTransactionOnyxProps = { @@ -207,15 +208,18 @@ function MoneyRequestView({ const transactionErrors = transaction.errors; if (transactionErrors) { - const errorKeys = Object.keys(transactionErrors) as Array; + const errorKeys = Object.keys(transactionErrors); - if (typeof transactionErrors[errorKeys[0]] === 'string') { - // transactionErrors is of type "Errors" + const firstError = transactionErrors[errorKeys[0]]; + + if (!isReceiptError(firstError) && typeof firstError === 'string') { + // transactionErrors is of type "Errors", but we need to assert it here + // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion return transactionErrors as Errors; } // transactionErrors is of type "ReceiptErrors" return errorKeys.reduce((acc, key) => { - const receiptError: ReceiptError = (transactionErrors as ReceiptErrors)[key]; + const receiptError = (transactionErrors as ReceiptErrors)[key]; acc[key] = receiptError.error ?? ''; return acc; }, {} as Errors); diff --git a/src/libs/ErrorUtils.ts b/src/libs/ErrorUtils.ts index 39ec7f5768de..1152a60562b6 100644 --- a/src/libs/ErrorUtils.ts +++ b/src/libs/ErrorUtils.ts @@ -2,7 +2,7 @@ import CONST from '@src/CONST'; import type {TranslationFlatObject, TranslationPaths} from '@src/languages/types'; import type {ErrorFields, Errors} from '@src/types/onyx/OnyxCommon'; import type Response from '@src/types/onyx/Response'; -import type {ReceiptErrors} from '@src/types/onyx/Transaction'; +import type {ReceiptError, ReceiptErrors} from '@src/types/onyx/Transaction'; import DateUtils from './DateUtils'; import * as Localize from './Localize'; @@ -129,6 +129,20 @@ function removeErrorsWithNullMessage(errors: Errors | ReceiptErrors | null | und return Object.fromEntries(nonNullEntries); } +/** Check if the error includes a receipt. */ +function isReceiptError(message: unknown): message is ReceiptError { + if (typeof message === 'string') { + return false; + } + if (Array.isArray(message)) { + return false; + } + if (Object.keys(message as Record).length === 0) { + return false; + } + return ((message as Record)?.error ?? '') === CONST.IOU.RECEIPT_ERROR; +} + export { getAuthenticateErrorMessage, getMicroSecondOnyxError, @@ -138,4 +152,5 @@ export { getEarliestErrorField, addErrorMessage, removeErrorsWithNullMessage, + isReceiptError, }; From 36f335d36dc9221c0b1e7eed9e3dfb54eb36ecbe Mon Sep 17 00:00:00 2001 From: Trevor Coleman Date: Tue, 6 Feb 2024 12:38:05 -0500 Subject: [PATCH 43/53] feat(Violations): handle null case --- src/libs/ErrorUtils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libs/ErrorUtils.ts b/src/libs/ErrorUtils.ts index 692e0fe2756d..ad8bae9eea4f 100644 --- a/src/libs/ErrorUtils.ts +++ b/src/libs/ErrorUtils.ts @@ -39,7 +39,7 @@ function getAuthenticateErrorMessage(response: Response): keyof TranslationFlatO * Method used to get an error object with microsecond as the key. * @param error - error key or message to be saved */ -function getMicroSecondOnyxError(error: string): Errors { +function getMicroSecondOnyxError(error: string | null): Errors { return {[DateUtils.getMicroseconds()]: error}; } @@ -64,7 +64,7 @@ function getLatestErrorMessage(onyxData: T const key = Object.keys(errors).sort().reverse()[0]; - return errors[key]; + return errors[key] ?? ''; } function getLatestErrorMessageField(onyxData: TOnyxData): Errors { From 7164120ab9687df1b5f08c17b5775fc66c69455e Mon Sep 17 00:00:00 2001 From: Trevor Coleman Date: Tue, 6 Feb 2024 12:38:56 -0500 Subject: [PATCH 44/53] feat(Violations): remove unused imports --- src/components/OfflineWithFeedback.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/OfflineWithFeedback.tsx b/src/components/OfflineWithFeedback.tsx index b298449b1215..975d154b885b 100644 --- a/src/components/OfflineWithFeedback.tsx +++ b/src/components/OfflineWithFeedback.tsx @@ -1,10 +1,9 @@ -import React, {useCallback, useMemo} from 'react'; +import React, {useCallback} from 'react'; import type {ImageStyle, StyleProp, TextStyle, ViewStyle} from 'react-native'; import {View} from 'react-native'; import useNetwork from '@hooks/useNetwork'; import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; -import {removeErrorsWithNullMessage} from '@libs/ErrorUtils'; import mapChildrenFlat from '@libs/mapChildrenFlat'; import shouldRenderOffscreen from '@libs/shouldRenderOffscreen'; import CONST from '@src/CONST'; From e9058ade37a3d88f6abe196ae1fec546e559d19a Mon Sep 17 00:00:00 2001 From: Trevor Coleman Date: Tue, 13 Feb 2024 16:31:08 -0500 Subject: [PATCH 45/53] fix imports --- src/components/ReportActionItem/MoneyRequestView.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index 23ba0a1c084d..bf32f8344240 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -36,8 +36,7 @@ import type {TranslationPaths} from '@src/languages/types'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type * as OnyxTypes from '@src/types/onyx'; -import type {Errors} from '@src/types/onyx/OnyxCommon'; -import type {ReceiptErrors, TransactionPendingFieldsKey} from '@src/types/onyx/Transaction'; +import type {TransactionPendingFieldsKey} from '@src/types/onyx/Transaction'; import ReportActionItemImage from './ReportActionItemImage'; type MoneyRequestViewTransactionOnyxProps = { From 512e599bc0808d83e1dce82f24ec49c08a319e7b Mon Sep 17 00:00:00 2001 From: Trevor Coleman Date: Tue, 13 Feb 2024 16:35:53 -0500 Subject: [PATCH 46/53] fix(Violations): remove unneeded default value --- src/languages/en.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index 9deae59dbc3a..2e631aad839a 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -2268,7 +2268,7 @@ export default { maxAge: ({maxAge}: ViolationsMaxAgeParams) => `Date older than ${maxAge} days`, missingCategory: 'Missing category', missingComment: 'Description required for selected category', - missingTag: ({tagName}: ViolationsMissingTagParams = {}) => `Missing ${tagName ?? 'tag'}`, + missingTag: ({tagName}: ViolationsMissingTagParams) => `Missing ${tagName ?? 'tag'}`, modifiedAmount: 'Amount greater than scanned receipt', modifiedDate: 'Date differs from scanned receipt', nonExpensiworksExpense: 'Non-Expensiworks expense', From f0b80e7aabfbe1056fe653f3c99e587b2d13b7fc Mon Sep 17 00:00:00 2001 From: Trevor Coleman Date: Wed, 14 Feb 2024 12:49:15 -0500 Subject: [PATCH 47/53] fix(Violations): remove unused import --- src/components/ReportActionItem/MoneyRequestView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index bf32f8344240..5c17eb6f44cf 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -1,4 +1,4 @@ -import React, {useCallback, useMemo} from 'react'; +import React, {useCallback} from 'react'; import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import type {OnyxEntry} from 'react-native-onyx'; From c1a1b2fd2bca64affd94e4b936d45dbcb20798e5 Mon Sep 17 00:00:00 2001 From: Trevor Coleman Date: Thu, 15 Feb 2024 09:07:05 -0500 Subject: [PATCH 48/53] prettier --- src/libs/ReportActionsUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts index 8e7fb084010c..eeebbdf7dbdb 100644 --- a/src/libs/ReportActionsUtils.ts +++ b/src/libs/ReportActionsUtils.ts @@ -6,7 +6,7 @@ import Onyx from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {ActionName, ChangeLog, OriginalMessageIOU, OriginalMessageActionableMentionWhisper, OriginalMessageReimbursementDequeued} from '@src/types/onyx/OriginalMessage'; +import type {ActionName, ChangeLog, OriginalMessageActionableMentionWhisper, OriginalMessageIOU, OriginalMessageReimbursementDequeued} from '@src/types/onyx/OriginalMessage'; import type Report from '@src/types/onyx/Report'; import type {Message, ReportActionBase, ReportActions} from '@src/types/onyx/ReportAction'; import type ReportAction from '@src/types/onyx/ReportAction'; From c0547319a0c7603942cb205ccf1b08c83e2d9dd2 Mon Sep 17 00:00:00 2001 From: Trevor Coleman Date: Fri, 16 Feb 2024 17:43:08 -0500 Subject: [PATCH 49/53] fix(Violations): revert translation --- src/languages/en.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index 69e09283e97b..080c93400428 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -2303,7 +2303,8 @@ export default { overLimitAttendee: ({formattedLimit}: ViolationsOverLimitParams) => `Amount over ${formattedLimit}/person limit`, perDayLimit: ({formattedLimit}: ViolationsPerDayLimitParams) => `Amount over daily ${formattedLimit}/person category limit`, receiptNotSmartScanned: 'Receipt not verified. Please confirm accuracy.', - receiptRequired: (params: ViolationsReceiptRequiredParams) => `Receipt required${params ? ` over ${params.formattedLimit}${params.category ? ` category limit` : ''}` : ''}`, + receiptRequired: ({formattedLimit, category}: ViolationsReceiptRequiredParams) => + `Receipt required${formattedLimit && category ? ` over ${formattedLimit}${category ? ` category limit` : ''}` : ''}`, reviewRequired: 'Review required', rter: ({brokenBankConnection, email, isAdmin, isTransactionOlderThan7Days, member}: ViolationsRterParams) => { if (brokenBankConnection) { From 01f627943fc0676e7d2f452c620bb32ef7d07186 Mon Sep 17 00:00:00 2001 From: Trevor Coleman Date: Fri, 16 Feb 2024 17:50:16 -0500 Subject: [PATCH 50/53] fix(Violations): move comment --- .../ReportActionItem/MoneyRequestPreview/index.tsx | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestPreview/index.tsx b/src/components/ReportActionItem/MoneyRequestPreview/index.tsx index 81e36a423f66..e84a6fa9d25c 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview/index.tsx +++ b/src/components/ReportActionItem/MoneyRequestPreview/index.tsx @@ -1,5 +1,3 @@ -// We should not render the component if there is no iouReport and it's not a split. -// Moved outside of the component scope to allow memoization of values later. import lodashIsEmpty from 'lodash/isEmpty'; import React from 'react'; import {withOnyx} from 'react-native-onyx'; @@ -8,9 +6,9 @@ import ONYXKEYS from '@src/ONYXKEYS'; import MoneyRequestPreviewContent from './MoneyRequestPreviewContent'; import type {MoneyRequestPreviewOnyxProps, MoneyRequestPreviewProps} from './types'; -// We should not render the component if there is no iouReport and it's not a split. -// Moved outside of the component scope to allow for easier use of hooks in the main component. function MoneyRequestPreview(props: MoneyRequestPreviewProps) { + // We should not render the component if there is no iouReport and it's not a split. + // Moved outside of the component scope to allow for easier use of hooks in the main component. // eslint-disable-next-line react/jsx-props-no-spreading return lodashIsEmpty(props.iouReport) && !props.isBillSplit ? null : ; } From 9f21c3ee96b2a0b7c54aec0d57edcd47f342ab31 Mon Sep 17 00:00:00 2001 From: Trevor Coleman Date: Fri, 16 Feb 2024 17:50:23 -0500 Subject: [PATCH 51/53] fix(Violations): refactor --- .../ReportActionItem/MoneyRequestPreview/index.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/components/ReportActionItem/MoneyRequestPreview/index.tsx b/src/components/ReportActionItem/MoneyRequestPreview/index.tsx index e84a6fa9d25c..1c502c53f99e 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview/index.tsx +++ b/src/components/ReportActionItem/MoneyRequestPreview/index.tsx @@ -29,7 +29,11 @@ export default withOnyx( key: ONYXKEYS.SESSION, }, transaction: { - key: ({action}) => `${ONYXKEYS.COLLECTION.TRANSACTION}${ReportActionsUtils.isMoneyRequestAction(action) ? action?.originalMessage?.IOUTransactionID : 0 ?? 0}`, + key: ({action}) => { + const isMoneyRequestAction = ReportActionsUtils.isMoneyRequestAction(action); + const transactionID = isMoneyRequestAction ? action?.originalMessage?.IOUTransactionID : 0; + return `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`; + }, }, walletTerms: { key: ONYXKEYS.WALLET_TERMS, From 79b0598a6cee617dadc25106ffd31c956b05cfb8 Mon Sep 17 00:00:00 2001 From: Trevor Coleman Date: Fri, 16 Feb 2024 18:08:15 -0500 Subject: [PATCH 52/53] fix(Violations): respace comment --- src/libs/ErrorUtils.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/libs/ErrorUtils.ts b/src/libs/ErrorUtils.ts index ee6183af82e3..20313ee8912d 100644 --- a/src/libs/ErrorUtils.ts +++ b/src/libs/ErrorUtils.ts @@ -151,7 +151,9 @@ function addErrorMessage(errors: Errors, inputID? } } -/** Check if the error includes a receipt. */ +/** + * Check if the error includes a receipt. + */ function isReceiptError(message: unknown): message is ReceiptError { if (typeof message === 'string') { return false; From e3699d3a2bdac73c36001c4b6019c5143546bd22 Mon Sep 17 00:00:00 2001 From: Trevor Coleman Date: Fri, 16 Feb 2024 18:29:10 -0500 Subject: [PATCH 53/53] fix(Violations): harmonize translations --- src/languages/en.ts | 3 +-- src/languages/es.ts | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index 080c93400428..a86cf18818b0 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -2303,8 +2303,7 @@ export default { overLimitAttendee: ({formattedLimit}: ViolationsOverLimitParams) => `Amount over ${formattedLimit}/person limit`, perDayLimit: ({formattedLimit}: ViolationsPerDayLimitParams) => `Amount over daily ${formattedLimit}/person category limit`, receiptNotSmartScanned: 'Receipt not verified. Please confirm accuracy.', - receiptRequired: ({formattedLimit, category}: ViolationsReceiptRequiredParams) => - `Receipt required${formattedLimit && category ? ` over ${formattedLimit}${category ? ` category limit` : ''}` : ''}`, + receiptRequired: (params: ViolationsReceiptRequiredParams) => `Receipt required${params ? ` over ${params.formattedLimit}${params.category ? ' category limit' : ''}` : ''}`, reviewRequired: 'Review required', rter: ({brokenBankConnection, email, isAdmin, isTransactionOlderThan7Days, member}: ViolationsRterParams) => { if (brokenBankConnection) { diff --git a/src/languages/es.ts b/src/languages/es.ts index 486e7d36f33d..b304f840fc8e 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -2793,8 +2793,8 @@ export default { overLimitAttendee: ({formattedLimit}: ViolationsOverLimitParams) => `Importe supera el límite${formattedLimit ? ` de ${formattedLimit}/persona` : ''}`, perDayLimit: ({formattedLimit}: ViolationsPerDayLimitParams) => `Importe supera el límite diario de la categoría${formattedLimit ? ` de ${formattedLimit}/persona` : ''}`, receiptNotSmartScanned: 'Recibo no verificado. Por favor, confirma su exactitud', - receiptRequired: ({category, formattedLimit}: ViolationsReceiptRequiredParams) => - `Recibo obligatorio${!category && !formattedLimit ? '' : ` para importes sobre${category ? ` el limite de la categoría de` : ''} ${formattedLimit}`}`, + receiptRequired: (params: ViolationsReceiptRequiredParams) => + `Recibo obligatorio${params ? ` para importes sobre${params.formattedLimit ? ` ${params.formattedLimit}` : ''}${params.category ? ' el límite de la categoría' : ''}` : ''}`, reviewRequired: 'Revisión requerida', rter: ({brokenBankConnection, isAdmin, email, isTransactionOlderThan7Days, member}: ViolationsRterParams) => { if (brokenBankConnection) {