From a4844ce6fed3f67c0b0c563d178dbf88fecf4016 Mon Sep 17 00:00:00 2001 From: Trevor Coleman Date: Wed, 10 Jan 2024 17:21:00 -0500 Subject: [PATCH 01/25] feat(Violations): create useMoneyRequestViewErrors and move violations into errors prop of inputs --- .../MoneyRequestView.js | 40 ++++++----- .../MoneyRequestView/index.js | 3 + .../useMoneyRequestViewErrors.ts | 70 +++++++++++++++++++ src/hooks/useViolations.ts | 12 ++-- 4 files changed, 102 insertions(+), 23 deletions(-) rename src/components/ReportActionItem/{ => MoneyRequestView}/MoneyRequestView.js (94%) create mode 100644 src/components/ReportActionItem/MoneyRequestView/index.js create mode 100644 src/components/ReportActionItem/MoneyRequestView/useMoneyRequestViewErrors.ts diff --git a/src/components/ReportActionItem/MoneyRequestView.js b/src/components/ReportActionItem/MoneyRequestView/MoneyRequestView.js similarity index 94% rename from src/components/ReportActionItem/MoneyRequestView.js rename to src/components/ReportActionItem/MoneyRequestView/MoneyRequestView.js index 036b64af1e4b..c09617a53bb7 100644 --- a/src/components/ReportActionItem/MoneyRequestView.js +++ b/src/components/ReportActionItem/MoneyRequestView/MoneyRequestView.js @@ -9,6 +9,7 @@ import * as Expensicons from '@components/Icon/Expensicons'; import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; import ReceiptEmptyState from '@components/ReceiptEmptyState'; +import ReportActionItemImage from '@components/ReportActionItem/ReportActionItemImage'; import SpacerView from '@components/SpacerView'; import Switch from '@components/Switch'; import tagPropTypes from '@components/tagPropTypes'; @@ -26,13 +27,13 @@ import useWindowDimensions from '@hooks/useWindowDimensions'; import * as CardUtils from '@libs/CardUtils'; import compose from '@libs/compose'; import * as CurrencyUtils from '@libs/CurrencyUtils'; -import Navigation from '@libs/Navigation/Navigation'; import * as OptionsListUtils from '@libs/OptionsListUtils'; import * as PolicyUtils from '@libs/PolicyUtils'; 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 Navigation from '@navigation/Navigation'; import AnimatedEmptyStateBackground from '@pages/home/report/AnimatedEmptyStateBackground'; import reportActionPropTypes from '@pages/home/report/reportActionPropTypes'; import iouReportPropTypes from '@pages/iouReportPropTypes'; @@ -42,7 +43,7 @@ import * as IOU from '@userActions/IOU'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; -import ReportActionItemImage from './ReportActionItemImage'; +import useMoneyRequestViewErrors from './useMoneyRequestViewErrors'; const violationNames = lodashValues(CONST.VIOLATIONS); @@ -228,6 +229,14 @@ function MoneyRequestView({report, parentReport, parentReportActions, policyCate const pendingAction = lodashGet(transaction, 'pendingAction'); const getPendingFieldAction = (fieldPath) => lodashGet(transaction, fieldPath) || pendingAction; + const {getErrorForField} = useMoneyRequestViewErrors({ + transactionViolations, + hasErrors, + isEmptyMerchant, + transactionDate, + transactionAmount, + }); + return ( @@ -274,9 +283,8 @@ function MoneyRequestView({report, parentReport, parentReportActions, policyCate shouldShowRightIcon={canEditAmount} onPress={() => Navigation.navigate(ROUTES.EDIT_REQUEST.getRoute(report.reportID, CONST.EDIT_REQUEST_FIELD.AMOUNT))} brickRoadIndicator={hasViolations('amount') || (hasErrors && transactionAmount === 0) ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : ''} - error={hasErrors && transactionAmount === 0 ? translate('common.error.enterAmount') : ''} + error={getErrorForField('amount')} /> - {canUseViolations && } Navigation.navigate(ROUTES.EDIT_REQUEST.getRoute(report.reportID, CONST.EDIT_REQUEST_FIELD.DESCRIPTION))} wrapperStyle={[styles.pv2, styles.taskDescriptionMenuItem]} brickRoadIndicator={hasViolations('comment') ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : ''} + error={getErrorForField('comment')} numberOfLinesTitle={0} /> - {canUseViolations && } {isDistanceRequest ? ( @@ -314,9 +322,8 @@ function MoneyRequestView({report, parentReport, parentReportActions, policyCate titleStyle={styles.flex1} onPress={() => Navigation.navigate(ROUTES.EDIT_REQUEST.getRoute(report.reportID, CONST.EDIT_REQUEST_FIELD.MERCHANT))} brickRoadIndicator={hasViolations('merchant') || (hasErrors && isEmptyMerchant) ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : ''} - error={hasErrors && isEmptyMerchant ? translate('common.error.enterMerchant') : ''} + error={getErrorForField('merchant')} /> - {canUseViolations && } )} @@ -328,9 +335,8 @@ function MoneyRequestView({report, parentReport, parentReportActions, policyCate titleStyle={styles.flex1} onPress={() => Navigation.navigate(ROUTES.EDIT_REQUEST.getRoute(report.reportID, CONST.EDIT_REQUEST_FIELD.DATE))} brickRoadIndicator={hasViolations('date') || (hasErrors && transactionDate === '') ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : ''} - error={hasErrors && transactionDate === '' ? translate('common.error.enterDate') : ''} + error={getErrorForField('date')} /> - {canUseViolations && } {shouldShowCategory && ( @@ -342,8 +348,8 @@ function MoneyRequestView({report, parentReport, parentReportActions, policyCate titleStyle={styles.flex1} onPress={() => Navigation.navigate(ROUTES.EDIT_REQUEST.getRoute(report.reportID, CONST.EDIT_REQUEST_FIELD.CATEGORY))} brickRoadIndicator={hasViolations('category') ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : ''} + error={getErrorForField('category')} /> - {canUseViolations && } )} {shouldShowTag && ( @@ -356,8 +362,8 @@ function MoneyRequestView({report, parentReport, parentReportActions, policyCate titleStyle={styles.flex1} onPress={() => Navigation.navigate(ROUTES.EDIT_REQUEST.getRoute(report.reportID, CONST.EDIT_REQUEST_FIELD.TAG))} brickRoadIndicator={hasViolations('tag') ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : ''} + error={getErrorForField('tag')} /> - {canUseViolations && } )} {isCardTransaction && ( @@ -379,13 +385,13 @@ function MoneyRequestView({report, parentReport, parentReportActions, policyCate isOn={transactionBillable} onToggle={saveBillable} /> + {hasViolations('billable') && ( + + )} - {hasViolations('billable') && ( - - )} )} diff --git a/src/components/ReportActionItem/MoneyRequestView/index.js b/src/components/ReportActionItem/MoneyRequestView/index.js new file mode 100644 index 000000000000..e6ce263b1831 --- /dev/null +++ b/src/components/ReportActionItem/MoneyRequestView/index.js @@ -0,0 +1,3 @@ +import MoneyRequestView from './MoneyRequestView'; + +export default MoneyRequestView; diff --git a/src/components/ReportActionItem/MoneyRequestView/useMoneyRequestViewErrors.ts b/src/components/ReportActionItem/MoneyRequestView/useMoneyRequestViewErrors.ts new file mode 100644 index 000000000000..108287d36a00 --- /dev/null +++ b/src/components/ReportActionItem/MoneyRequestView/useMoneyRequestViewErrors.ts @@ -0,0 +1,70 @@ +import {useCallback} from 'react'; +import useLocalize from '@hooks/useLocalize'; +import usePermissions from '@hooks/usePermissions'; +import type {MoneyRequestField} from '@hooks/useViolations'; +import useViolations from '@hooks/useViolations'; +import ViolationsUtils from '@libs/ViolationsUtils'; +import type {TranslationPaths} from '@src/languages/types'; +import type {TransactionViolation} from '@src/types/onyx'; + +type FieldsWithErrors = Exclude; + +type FieldCheck = { + isError: boolean; + translationPath: TranslationPaths; +}; + +type FieldChecks = Partial>; + +type UseMoneyRequestViewErrorsParams = { + transactionViolations: TransactionViolation[]; + hasErrors: boolean; + isEmptyMerchant: boolean; + transactionDate: string; + transactionAmount: number; +}; + +function useMoneyRequestViewErrors(params: UseMoneyRequestViewErrorsParams) { + const {transactionViolations, hasErrors, isEmptyMerchant, transactionDate, transactionAmount} = params; + + const {translate} = useLocalize(); + const {canUseViolations} = usePermissions(); + const {getViolationsForField} = useViolations(transactionViolations); + + const getErrorForField = useCallback( + (field: Exclude) => { + const fieldChecks: FieldChecks = { + amount: { + isError: transactionAmount === 0, + translationPath: 'common.error.enterAmount', + }, + merchant: { + isError: isEmptyMerchant, + translationPath: 'common.error.enterMerchant', + }, + date: { + isError: transactionDate === '', + translationPath: 'common.error.enterDate', + }, + }; + + const {isError, translationPath} = fieldChecks[field] ?? {}; + + if (hasErrors && isError && translationPath) { + return translate(translationPath); + } + + if (canUseViolations) { + const violations = getViolationsForField(field); + return ViolationsUtils.getViolationTranslation(violations[0], translate); + } + + return ''; + }, + [canUseViolations, hasErrors, getViolationsForField, translate, transactionAmount, isEmptyMerchant, transactionDate], + ); + + return {getErrorForField}; +} + +export default useMoneyRequestViewErrors; diff --git a/src/hooks/useViolations.ts b/src/hooks/useViolations.ts index 76d48158237b..5b6f0f96c5da 100644 --- a/src/hooks/useViolations.ts +++ b/src/hooks/useViolations.ts @@ -4,12 +4,12 @@ import type {TransactionViolation, ViolationName} from '@src/types/onyx'; /** * Names of Fields where violations can occur. */ -type ViolationField = 'amount' | 'billable' | 'category' | 'comment' | 'date' | 'merchant' | 'receipt' | 'tag' | 'tax'; +type MoneyRequestField = 'amount' | 'billable' | 'category' | 'comment' | 'date' | 'merchant' | 'receipt' | 'tag' | 'tax'; /** * Map from Violation Names to the field where that violation can occur. */ -const violationFields: Record = { +const violationFields: Record = { allTagLevelsRequired: 'tag', autoReportedRejectedExpense: 'merchant', billableExpense: 'billable', @@ -45,11 +45,11 @@ const violationFields: Record = { taxRequired: 'tax', }; -type ViolationsMap = Map; +type ViolationsMap = Map; function useViolations(violations: TransactionViolation[]) { const violationsByField = useMemo((): ViolationsMap => { - const violationGroups = new Map(); + const violationGroups = new Map(); for (const violation of violations) { const field = violationFields[violation.name]; @@ -60,7 +60,7 @@ function useViolations(violations: TransactionViolation[]) { return violationGroups ?? new Map(); }, [violations]); - const getViolationsForField = useCallback((field: ViolationField) => violationsByField.get(field) ?? [], [violationsByField]); + const getViolationsForField = useCallback((field: MoneyRequestField) => violationsByField.get(field) ?? [], [violationsByField]); return { getViolationsForField, @@ -68,4 +68,4 @@ function useViolations(violations: TransactionViolation[]) { } export default useViolations; -export type {ViolationField}; +export type {MoneyRequestField}; From 6dc9f5c4a52d07163ee6b280a7577e0657a4ee5a Mon Sep 17 00:00:00 2001 From: Trevor Coleman Date: Wed, 10 Jan 2024 17:34:28 -0500 Subject: [PATCH 02/25] feat(Violations): move hasViolations into useMoneyRequestViewErrors, and update brickRoadIndicatorProps --- .../MoneyRequestView/MoneyRequestView.js | 15 +++++++-------- .../MoneyRequestView/useMoneyRequestViewErrors.ts | 5 +++-- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestView/MoneyRequestView.js b/src/components/ReportActionItem/MoneyRequestView/MoneyRequestView.js index c09617a53bb7..2b6b8baa156d 100644 --- a/src/components/ReportActionItem/MoneyRequestView/MoneyRequestView.js +++ b/src/components/ReportActionItem/MoneyRequestView/MoneyRequestView.js @@ -176,7 +176,6 @@ function MoneyRequestView({report, parentReport, parentReportActions, policyCate const shouldShowBillable = isPolicyExpenseChat && (transactionBillable || !lodashGet(policy, 'disabledFields.defaultBillable', true)); const {getViolationsForField} = useViolations(transactionViolations); - const hasViolations = useCallback((field) => canUseViolations && getViolationsForField(field).length > 0, [canUseViolations, getViolationsForField]); let amountDescription = `${translate('iou.amount')}`; @@ -282,7 +281,7 @@ function MoneyRequestView({report, parentReport, parentReportActions, policyCate interactive={canEditAmount} shouldShowRightIcon={canEditAmount} onPress={() => Navigation.navigate(ROUTES.EDIT_REQUEST.getRoute(report.reportID, CONST.EDIT_REQUEST_FIELD.AMOUNT))} - brickRoadIndicator={hasViolations('amount') || (hasErrors && transactionAmount === 0) ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : ''} + brickRoadIndicator={getErrorForField('comment') ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : ''} error={getErrorForField('amount')} /> @@ -296,7 +295,7 @@ function MoneyRequestView({report, parentReport, parentReportActions, policyCate titleStyle={styles.flex1} onPress={() => Navigation.navigate(ROUTES.EDIT_REQUEST.getRoute(report.reportID, CONST.EDIT_REQUEST_FIELD.DESCRIPTION))} wrapperStyle={[styles.pv2, styles.taskDescriptionMenuItem]} - brickRoadIndicator={hasViolations('comment') ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : ''} + brickRoadIndicator={getErrorForField('comment') ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : ''} error={getErrorForField('comment')} numberOfLinesTitle={0} /> @@ -321,7 +320,7 @@ function MoneyRequestView({report, parentReport, parentReportActions, policyCate shouldShowRightIcon={canEditMerchant} titleStyle={styles.flex1} onPress={() => Navigation.navigate(ROUTES.EDIT_REQUEST.getRoute(report.reportID, CONST.EDIT_REQUEST_FIELD.MERCHANT))} - brickRoadIndicator={hasViolations('merchant') || (hasErrors && isEmptyMerchant) ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : ''} + brickRoadIndicator={getErrorForField('merchant') ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : ''} error={getErrorForField('merchant')} /> @@ -334,7 +333,7 @@ function MoneyRequestView({report, parentReport, parentReportActions, policyCate shouldShowRightIcon={canEditDate} titleStyle={styles.flex1} onPress={() => Navigation.navigate(ROUTES.EDIT_REQUEST.getRoute(report.reportID, CONST.EDIT_REQUEST_FIELD.DATE))} - brickRoadIndicator={hasViolations('date') || (hasErrors && transactionDate === '') ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : ''} + brickRoadIndicator={getErrorForField('date') ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : ''} error={getErrorForField('date')} /> @@ -347,7 +346,7 @@ function MoneyRequestView({report, parentReport, parentReportActions, policyCate shouldShowRightIcon={canEdit} titleStyle={styles.flex1} onPress={() => Navigation.navigate(ROUTES.EDIT_REQUEST.getRoute(report.reportID, CONST.EDIT_REQUEST_FIELD.CATEGORY))} - brickRoadIndicator={hasViolations('category') ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : ''} + brickRoadIndicator={getErrorForField('comment') ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : ''} error={getErrorForField('category')} /> @@ -361,7 +360,7 @@ function MoneyRequestView({report, parentReport, parentReportActions, policyCate shouldShowRightIcon={canEdit} titleStyle={styles.flex1} onPress={() => Navigation.navigate(ROUTES.EDIT_REQUEST.getRoute(report.reportID, CONST.EDIT_REQUEST_FIELD.TAG))} - brickRoadIndicator={hasViolations('tag') ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : ''} + brickRoadIndicator={getErrorForField('comment') ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : ''} error={getErrorForField('tag')} /> @@ -385,7 +384,7 @@ function MoneyRequestView({report, parentReport, parentReportActions, policyCate isOn={transactionBillable} onToggle={saveBillable} /> - {hasViolations('billable') && ( + {getErrorForField('comment') && ( canUseViolations && getViolationsForField(field).length > 0, [canUseViolations, getViolationsForField]); const getErrorForField = useCallback( (field: Exclude) => { @@ -54,14 +55,14 @@ function useMoneyRequestViewErrors(params: UseMoneyRequestViewErrorsParams) { return translate(translationPath); } - if (canUseViolations) { + if (canUseViolations && hasViolations(field)) { const violations = getViolationsForField(field); return ViolationsUtils.getViolationTranslation(violations[0], translate); } return ''; }, - [canUseViolations, hasErrors, getViolationsForField, translate, transactionAmount, isEmptyMerchant, transactionDate], + [transactionAmount, isEmptyMerchant, transactionDate, hasErrors, canUseViolations, hasViolations, translate, getViolationsForField], ); return {getErrorForField}; From 36b7dbfbbdf7bd4af91cbd07fe49dc44da760431 Mon Sep 17 00:00:00 2001 From: Trevor Coleman Date: Wed, 10 Jan 2024 17:40:11 -0500 Subject: [PATCH 03/25] feat(Violations): add comment --- .../MoneyRequestView/useMoneyRequestViewErrors.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestView/useMoneyRequestViewErrors.ts b/src/components/ReportActionItem/MoneyRequestView/useMoneyRequestViewErrors.ts index cb39b282db9b..043287f5bffa 100644 --- a/src/components/ReportActionItem/MoneyRequestView/useMoneyRequestViewErrors.ts +++ b/src/components/ReportActionItem/MoneyRequestView/useMoneyRequestViewErrors.ts @@ -7,6 +7,9 @@ import ViolationsUtils from '@libs/ViolationsUtils'; import type {TranslationPaths} from '@src/languages/types'; import type {TransactionViolation} from '@src/types/onyx'; +// receipt can display more than one violation so we can't use the error prop +// so we exclude it from this type so it will throw an error if someone tries to +// use it type FieldsWithErrors = Exclude; type FieldCheck = { @@ -24,9 +27,7 @@ type UseMoneyRequestViewErrorsParams = { transactionAmount: number; }; -function useMoneyRequestViewErrors(params: UseMoneyRequestViewErrorsParams) { - const {transactionViolations, hasErrors, isEmptyMerchant, transactionDate, transactionAmount} = params; - +function useMoneyRequestViewErrors({transactionViolations, hasErrors, isEmptyMerchant, transactionDate, transactionAmount}: UseMoneyRequestViewErrorsParams) { const {translate} = useLocalize(); const {canUseViolations} = usePermissions(); const {getViolationsForField} = useViolations(transactionViolations); From 02fe40d1ea1c57083afd68459380fe2eef57ae57 Mon Sep 17 00:00:00 2001 From: Trevor Coleman Date: Thu, 11 Jan 2024 10:52:56 -0500 Subject: [PATCH 04/25] feat(Violations): add comment --- .../MoneyRequestView/useMoneyRequestViewErrors.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/components/ReportActionItem/MoneyRequestView/useMoneyRequestViewErrors.ts b/src/components/ReportActionItem/MoneyRequestView/useMoneyRequestViewErrors.ts index 043287f5bffa..a71bc8b763e6 100644 --- a/src/components/ReportActionItem/MoneyRequestView/useMoneyRequestViewErrors.ts +++ b/src/components/ReportActionItem/MoneyRequestView/useMoneyRequestViewErrors.ts @@ -35,6 +35,7 @@ function useMoneyRequestViewErrors({transactionViolations, hasErrors, isEmptyMer const getErrorForField = useCallback( (field: Exclude) => { + // Checks applied when creating a new money request const fieldChecks: FieldChecks = { amount: { isError: transactionAmount === 0, @@ -52,10 +53,12 @@ function useMoneyRequestViewErrors({transactionViolations, hasErrors, isEmptyMer const {isError, translationPath} = fieldChecks[field] ?? {}; + // Display form errors when first creating the money request if (hasErrors && isError && translationPath) { return translate(translationPath); } + // Show violations if there are any if (canUseViolations && hasViolations(field)) { const violations = getViolationsForField(field); return ViolationsUtils.getViolationTranslation(violations[0], translate); From d3fecfc85a06bdfac023d25d66cc681664ca9256 Mon Sep 17 00:00:00 2001 From: Trevor Coleman Date: Thu, 11 Jan 2024 11:13:50 -0500 Subject: [PATCH 05/25] feat(Violations): move getErrorForField into MoneyRequestView --- .../MoneyRequestView.js | 48 +++++++++--- .../MoneyRequestView/index.js | 3 - .../useMoneyRequestViewErrors.ts | 75 ------------------- 3 files changed, 39 insertions(+), 87 deletions(-) rename src/components/ReportActionItem/{MoneyRequestView => }/MoneyRequestView.js (92%) delete mode 100644 src/components/ReportActionItem/MoneyRequestView/index.js delete mode 100644 src/components/ReportActionItem/MoneyRequestView/useMoneyRequestViewErrors.ts diff --git a/src/components/ReportActionItem/MoneyRequestView/MoneyRequestView.js b/src/components/ReportActionItem/MoneyRequestView.js similarity index 92% rename from src/components/ReportActionItem/MoneyRequestView/MoneyRequestView.js rename to src/components/ReportActionItem/MoneyRequestView.js index 2b6b8baa156d..12e72ccbd884 100644 --- a/src/components/ReportActionItem/MoneyRequestView/MoneyRequestView.js +++ b/src/components/ReportActionItem/MoneyRequestView.js @@ -9,7 +9,6 @@ import * as Expensicons from '@components/Icon/Expensicons'; import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; import ReceiptEmptyState from '@components/ReceiptEmptyState'; -import ReportActionItemImage from '@components/ReportActionItem/ReportActionItemImage'; import SpacerView from '@components/SpacerView'; import Switch from '@components/Switch'; import tagPropTypes from '@components/tagPropTypes'; @@ -33,6 +32,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 ViolationsUtils from '@libs/ViolationsUtils'; import Navigation from '@navigation/Navigation'; import AnimatedEmptyStateBackground from '@pages/home/report/AnimatedEmptyStateBackground'; import reportActionPropTypes from '@pages/home/report/reportActionPropTypes'; @@ -43,7 +43,7 @@ import * as IOU from '@userActions/IOU'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; -import useMoneyRequestViewErrors from './useMoneyRequestViewErrors'; +import ReportActionItemImage from './ReportActionItemImage'; const violationNames = lodashValues(CONST.VIOLATIONS); @@ -176,6 +176,7 @@ function MoneyRequestView({report, parentReport, parentReportActions, policyCate const shouldShowBillable = isPolicyExpenseChat && (transactionBillable || !lodashGet(policy, 'disabledFields.defaultBillable', true)); const {getViolationsForField} = useViolations(transactionViolations); + const hasViolations = useCallback((field) => canUseViolations && getViolationsForField(field).length > 0, [canUseViolations, getViolationsForField]); let amountDescription = `${translate('iou.amount')}`; @@ -228,13 +229,42 @@ function MoneyRequestView({report, parentReport, parentReportActions, policyCate const pendingAction = lodashGet(transaction, 'pendingAction'); const getPendingFieldAction = (fieldPath) => lodashGet(transaction, fieldPath) || pendingAction; - const {getErrorForField} = useMoneyRequestViewErrors({ - transactionViolations, - hasErrors, - isEmptyMerchant, - transactionDate, - transactionAmount, - }); + const getErrorForField = useCallback( + (field) => { + // Checks applied when creating a new money request + // NOTE: receipt field can return multiple violations, so we need to handle it separately + const fieldChecks = { + amount: { + isError: transactionAmount === 0, + translationPath: 'common.error.enterAmount', + }, + merchant: { + isError: isEmptyMerchant, + translationPath: 'common.error.enterMerchant', + }, + date: { + isError: transactionDate === '', + translationPath: 'common.error.enterDate', + }, + }; + + const {isError, translationPath} = fieldChecks[field] ? fieldChecks[field] : {}; + + // Display form errors when first creating the money request + if (hasErrors && isError && translationPath) { + return translate(translationPath); + } + + // Show violations if there are any + if (canUseViolations && hasViolations(field)) { + const violations = getViolationsForField(field); + return ViolationsUtils.getViolationTranslation(violations[0], translate); + } + + return ''; + }, + [transactionAmount, isEmptyMerchant, transactionDate, hasErrors, canUseViolations, hasViolations, translate, getViolationsForField], + ); return ( diff --git a/src/components/ReportActionItem/MoneyRequestView/index.js b/src/components/ReportActionItem/MoneyRequestView/index.js deleted file mode 100644 index e6ce263b1831..000000000000 --- a/src/components/ReportActionItem/MoneyRequestView/index.js +++ /dev/null @@ -1,3 +0,0 @@ -import MoneyRequestView from './MoneyRequestView'; - -export default MoneyRequestView; diff --git a/src/components/ReportActionItem/MoneyRequestView/useMoneyRequestViewErrors.ts b/src/components/ReportActionItem/MoneyRequestView/useMoneyRequestViewErrors.ts deleted file mode 100644 index a71bc8b763e6..000000000000 --- a/src/components/ReportActionItem/MoneyRequestView/useMoneyRequestViewErrors.ts +++ /dev/null @@ -1,75 +0,0 @@ -import {useCallback} from 'react'; -import useLocalize from '@hooks/useLocalize'; -import usePermissions from '@hooks/usePermissions'; -import type {MoneyRequestField} from '@hooks/useViolations'; -import useViolations from '@hooks/useViolations'; -import ViolationsUtils from '@libs/ViolationsUtils'; -import type {TranslationPaths} from '@src/languages/types'; -import type {TransactionViolation} from '@src/types/onyx'; - -// receipt can display more than one violation so we can't use the error prop -// so we exclude it from this type so it will throw an error if someone tries to -// use it -type FieldsWithErrors = Exclude; - -type FieldCheck = { - isError: boolean; - translationPath: TranslationPaths; -}; - -type FieldChecks = Partial>; - -type UseMoneyRequestViewErrorsParams = { - transactionViolations: TransactionViolation[]; - hasErrors: boolean; - isEmptyMerchant: boolean; - transactionDate: string; - transactionAmount: number; -}; - -function useMoneyRequestViewErrors({transactionViolations, hasErrors, isEmptyMerchant, transactionDate, transactionAmount}: UseMoneyRequestViewErrorsParams) { - const {translate} = useLocalize(); - const {canUseViolations} = usePermissions(); - const {getViolationsForField} = useViolations(transactionViolations); - const hasViolations = useCallback((field: MoneyRequestField) => canUseViolations && getViolationsForField(field).length > 0, [canUseViolations, getViolationsForField]); - - const getErrorForField = useCallback( - (field: Exclude) => { - // Checks applied when creating a new money request - const fieldChecks: FieldChecks = { - amount: { - isError: transactionAmount === 0, - translationPath: 'common.error.enterAmount', - }, - merchant: { - isError: isEmptyMerchant, - translationPath: 'common.error.enterMerchant', - }, - date: { - isError: transactionDate === '', - translationPath: 'common.error.enterDate', - }, - }; - - const {isError, translationPath} = fieldChecks[field] ?? {}; - - // Display form errors when first creating the money request - if (hasErrors && isError && translationPath) { - return translate(translationPath); - } - - // Show violations if there are any - if (canUseViolations && hasViolations(field)) { - const violations = getViolationsForField(field); - return ViolationsUtils.getViolationTranslation(violations[0], translate); - } - - return ''; - }, - [transactionAmount, isEmptyMerchant, transactionDate, hasErrors, canUseViolations, hasViolations, translate, getViolationsForField], - ); - - return {getErrorForField}; -} - -export default useMoneyRequestViewErrors; From e12dc9f553315df832a3f932467a2d42a044ef60 Mon Sep 17 00:00:00 2001 From: Trevor Coleman Date: Mon, 15 Jan 2024 14:13:41 -0500 Subject: [PATCH 06/25] feat(Violations): fix import --- src/components/ReportActionItem/MoneyRequestView.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ReportActionItem/MoneyRequestView.js b/src/components/ReportActionItem/MoneyRequestView.js index fbb36a77b3cf..8ccad72fb608 100644 --- a/src/components/ReportActionItem/MoneyRequestView.js +++ b/src/components/ReportActionItem/MoneyRequestView.js @@ -32,7 +32,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 ViolationsUtils from '@libs/ViolationsUtils'; +import ViolationsUtils from '@libs/Violations/ViolationsUtils'; import Navigation from '@navigation/Navigation'; import AnimatedEmptyStateBackground from '@pages/home/report/AnimatedEmptyStateBackground'; import reportActionPropTypes from '@pages/home/report/reportActionPropTypes'; From 32efefc05add52493a0d097fcd1fa58d8222df44 Mon Sep 17 00:00:00 2001 From: Trevor Coleman Date: Mon, 15 Jan 2024 14:15:22 -0500 Subject: [PATCH 07/25] feat(Violations): update translation params to match violation data object --- src/languages/en.ts | 12 ++++++------ src/languages/es.ts | 11 ++++++----- src/languages/types.ts | 10 +++++----- src/libs/Violations/ViolationsUtils.ts | 13 +++++-------- src/types/onyx/TransactionViolation.ts | 3 +-- 5 files changed, 23 insertions(+), 26 deletions(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index b6fa37560536..02a4bef7c36d 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -83,7 +83,6 @@ import type { ViolationsInvoiceMarkupParams, ViolationsMaxAgeParams, ViolationsMissingTagParams, - ViolationsOverAutoApprovalLimitParams, ViolationsOverCategoryLimitParams, ViolationsOverLimitParams, ViolationsPerDayLimitParams, @@ -2078,7 +2077,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 over ${formattedLimit}`, categoryOutOfPolicy: 'Category no longer valid', conversionSurcharge: ({surcharge}: ViolationsConversionSurchargeParams) => `Applied ${surcharge}% conversion surcharge`, customUnitOutOfPolicy: 'Unit no longer valid', @@ -2093,13 +2092,14 @@ 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}`, + overAutoApprovalLimit: ({formattedLimit}: ViolationsOverLimitParams) => `Expense exceeds auto approval limit of ${formattedLimit}`, 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`, + overLimit: ({formattedLimit}: ViolationsOverLimitParams) => `Amount over ${formattedLimit}/person limit`, + overLimitAttendee: ({formattedLimit}: ViolationsOverLimitParams) => `Amount over ${formattedLimit}/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: ({formattedLimit, category}: ViolationsReceiptRequiredParams = {}) => + `Receipt required${formattedLimit ? ` over ${formattedLimit}${category ? ' category limit' : ''}` : ''}`, rter: ({brokenBankConnection, email, isAdmin, isTransactionOlderThan7Days, member}: ViolationsRterParams) => { if (brokenBankConnection) { return isAdmin diff --git a/src/languages/es.ts b/src/languages/es.ts index 271f0787bfde..ac46044f0a9f 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -2565,7 +2565,7 @@ 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`, customUnitOutOfPolicy: 'Unidad ya no es válida', @@ -2580,13 +2580,14 @@ 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}`, + overAutoApprovalLimit: ({formattedLimit}: ViolationsOverAutoApprovalLimitParams) => `Importe supera el límite de aprobación automática de ${formattedLimit}`, 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`, + overLimit: ({formattedLimit}: ViolationsOverLimitParams) => `Importe supera el límite de ${formattedLimit}/persona`, + overLimitAttendee: ({formattedLimit}: ViolationsOverLimitParams) => `Importe supera el límite de ${formattedLimit}/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: ({formattedLimit, category}: ViolationsReceiptRequiredParams = {}) => + `Recibo obligatorio${formattedLimit ? ` para importes sobre ${category ? 'el limite de la categoría de ' : ''}${formattedLimit}` : ``}`, rter: ({brokenBankConnection, isAdmin, email, isTransactionOlderThan7Days, member}: ViolationsRterParams) => { if (brokenBankConnection) { return isAdmin diff --git a/src/languages/types.ts b/src/languages/types.ts index 35a5110abf79..ee068dffb346 100644 --- a/src/languages/types.ts +++ b/src/languages/types.ts @@ -217,7 +217,7 @@ type WalletProgramParams = {walletProgram: string}; type ViolationsAutoReportedRejectedExpenseParams = {rejectedBy: string; rejectReason: string}; -type ViolationsCashExpenseWithNoReceiptParams = {amount: string}; +type ViolationsCashExpenseWithNoReceiptParams = {formattedLimit: string}; type ViolationsConversionSurchargeParams = {surcharge?: number}; @@ -227,15 +227,15 @@ type ViolationsMaxAgeParams = {maxAge: number}; type ViolationsMissingTagParams = {tagName?: string}; -type ViolationsOverAutoApprovalLimitParams = {formattedLimitAmount: string}; +type ViolationsOverAutoApprovalLimitParams = {formattedLimit: string}; type ViolationsOverCategoryLimitParams = {categoryLimit: string}; -type ViolationsOverLimitParams = {amount: string}; +type ViolationsOverLimitParams = {formattedLimit: string}; -type ViolationsPerDayLimitParams = {limit: string}; +type ViolationsPerDayLimitParams = {limit?: string}; -type ViolationsReceiptRequiredParams = {amount: string; category?: string}; +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 c0558d7487cf..b259953ddf2d 100644 --- a/src/libs/Violations/ViolationsUtils.ts +++ b/src/libs/Violations/ViolationsUtils.ts @@ -104,7 +104,7 @@ const ViolationsUtils = { case 'billableExpense': return translate('violations.billableExpense'); case 'cashExpenseWithNoReceipt': - return translate('violations.cashExpenseWithNoReceipt', {amount: violation.data?.amount ?? ''}); + return translate('violations.cashExpenseWithNoReceipt', {formattedLimit: violation.data?.formattedLimit ?? ''}); case 'categoryOutOfPolicy': return translate('violations.categoryOutOfPolicy'); case 'conversionSurcharge': @@ -134,22 +134,19 @@ const ViolationsUtils = { case 'nonExpensiworksExpense': return translate('violations.nonExpensiworksExpense'); case 'overAutoApprovalLimit': - return translate('violations.overAutoApprovalLimit', {formattedLimitAmount: violation.data?.formattedLimitAmount ?? ''}); + return translate('violations.overAutoApprovalLimit', {formattedLimit: violation.data?.formattedLimit ?? ''}); case 'overCategoryLimit': return translate('violations.overCategoryLimit', {categoryLimit: violation.data?.categoryLimit ?? ''}); case 'overLimit': - return translate('violations.overLimit', {amount: violation.data?.amount ?? ''}); + return translate('violations.overLimit', {formattedLimit: violation.data?.formattedLimit ?? ''}); case 'overLimitAttendee': - return translate('violations.overLimitAttendee', {amount: violation.data?.amount ?? ''}); + return translate('violations.overLimitAttendee', {formattedLimit: violation.data?.formattedLimit ?? ''}); case 'perDayLimit': return translate('violations.perDayLimit', {limit: violation.data?.limit ?? ''}); case 'receiptNotSmartScanned': return translate('violations.receiptNotSmartScanned'); case 'receiptRequired': - return translate('violations.receiptRequired', { - amount: violation.data?.amount ?? '0', - category: violation.data?.category ?? '', - }); + return translate('violations.receiptRequired', violation.data ?? undefined); case 'rter': return translate('violations.rter', { brokenBankConnection: violation.data?.brokenBankConnection ?? false, diff --git a/src/types/onyx/TransactionViolation.ts b/src/types/onyx/TransactionViolation.ts index 20603adc7cfa..45fac467beed 100644 --- a/src/types/onyx/TransactionViolation.ts +++ b/src/types/onyx/TransactionViolation.ts @@ -13,12 +13,11 @@ type TransactionViolation = { data?: { rejectedBy?: string; rejectReason?: string; - amount?: string; + formattedLimit?: string; surcharge?: number; invoiceMarkup?: number; maxAge?: number; tagName?: string; - formattedLimitAmount?: string; categoryLimit?: string; limit?: string; category?: string; From 70e45a89a36dcd59a8ec62e50afc0b7948e9582d Mon Sep 17 00:00:00 2001 From: Trevor Coleman Date: Mon, 15 Jan 2024 14:38:13 -0500 Subject: [PATCH 08/25] feat(Violations): change param names to match violation data prop --- src/languages/en.ts | 2 +- src/languages/es.ts | 2 +- src/languages/types.ts | 2 +- src/libs/Violations/ViolationsUtils.ts | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index 02a4bef7c36d..4a8dc88fa149 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -2093,7 +2093,7 @@ export default { modifiedDate: 'Date differs from scanned receipt', nonExpensiworksExpense: 'Non-Expensiworks expense', overAutoApprovalLimit: ({formattedLimit}: ViolationsOverLimitParams) => `Expense exceeds auto approval limit of ${formattedLimit}`, - overCategoryLimit: ({categoryLimit}: ViolationsOverCategoryLimitParams) => `Amount over ${categoryLimit}/person category limit`, + 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: ({limit}: ViolationsPerDayLimitParams) => `Amount over daily ${limit}/person category limit`, diff --git a/src/languages/es.ts b/src/languages/es.ts index ac46044f0a9f..6a95ef07279e 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -2581,7 +2581,7 @@ export default { modifiedDate: 'Fecha difiere del recibo escaneado', nonExpensiworksExpense: 'Gasto no es de Expensiworks', overAutoApprovalLimit: ({formattedLimit}: ViolationsOverAutoApprovalLimitParams) => `Importe supera el límite de aprobación automática de ${formattedLimit}`, - overCategoryLimit: ({categoryLimit}: ViolationsOverCategoryLimitParams) => `Importe supera el límite para la categoría de ${categoryLimit}/persona`, + overCategoryLimit: ({formattedLimit}: ViolationsOverCategoryLimitParams) => `Importe supera el límite para la categoría de ${formattedLimit}/persona`, overLimit: ({formattedLimit}: ViolationsOverLimitParams) => `Importe supera el límite de ${formattedLimit}/persona`, overLimitAttendee: ({formattedLimit}: ViolationsOverLimitParams) => `Importe supera el límite de ${formattedLimit}/persona`, perDayLimit: ({limit}: ViolationsPerDayLimitParams) => `Importe supera el límite diario de la categoría de ${limit}/persona`, diff --git a/src/languages/types.ts b/src/languages/types.ts index ee068dffb346..e363176788bf 100644 --- a/src/languages/types.ts +++ b/src/languages/types.ts @@ -229,7 +229,7 @@ type ViolationsMissingTagParams = {tagName?: string}; type ViolationsOverAutoApprovalLimitParams = {formattedLimit: string}; -type ViolationsOverCategoryLimitParams = {categoryLimit: string}; +type ViolationsOverCategoryLimitParams = {formattedLimit: string}; type ViolationsOverLimitParams = {formattedLimit: string}; diff --git a/src/libs/Violations/ViolationsUtils.ts b/src/libs/Violations/ViolationsUtils.ts index b259953ddf2d..441baf3d8cef 100644 --- a/src/libs/Violations/ViolationsUtils.ts +++ b/src/libs/Violations/ViolationsUtils.ts @@ -136,7 +136,7 @@ const ViolationsUtils = { case 'overAutoApprovalLimit': return translate('violations.overAutoApprovalLimit', {formattedLimit: violation.data?.formattedLimit ?? ''}); case 'overCategoryLimit': - return translate('violations.overCategoryLimit', {categoryLimit: violation.data?.categoryLimit ?? ''}); + return translate('violations.overCategoryLimit', {formattedLimit: violation.data?.formattedLimit ?? ''}); case 'overLimit': return translate('violations.overLimit', {formattedLimit: violation.data?.formattedLimit ?? ''}); case 'overLimitAttendee': From df51b442787b0d8bb38e43807eeacc4f0eb27c48 Mon Sep 17 00:00:00 2001 From: Trevor Coleman Date: Mon, 15 Jan 2024 14:58:22 -0500 Subject: [PATCH 09/25] feat(Violations): change param names to match violation data prop --- src/languages/en.ts | 4 ++-- src/languages/es.ts | 2 +- src/languages/types.ts | 2 +- src/libs/Violations/ViolationsUtils.ts | 7 ++++--- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index 4a8dc88fa149..5ed115229a62 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -2077,7 +2077,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: ({formattedLimit}: ViolationsCashExpenseWithNoReceiptParams) => `Receipt required over ${formattedLimit}`, + 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', @@ -2088,7 +2088,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', diff --git a/src/languages/es.ts b/src/languages/es.ts index 6a95ef07279e..7e5655d7d71d 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -2565,7 +2565,7 @@ 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: ({formattedLimit}: ViolationsCashExpenseWithNoReceiptParams) => `Recibo obligatorio para montos mayores a ${formattedLimit}`, + 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`, customUnitOutOfPolicy: 'Unidad ya no es válida', diff --git a/src/languages/types.ts b/src/languages/types.ts index e363176788bf..fbf11fb093cb 100644 --- a/src/languages/types.ts +++ b/src/languages/types.ts @@ -217,7 +217,7 @@ type WalletProgramParams = {walletProgram: string}; type ViolationsAutoReportedRejectedExpenseParams = {rejectedBy: string; rejectReason: string}; -type ViolationsCashExpenseWithNoReceiptParams = {formattedLimit: string}; +type ViolationsCashExpenseWithNoReceiptParams = {formattedLimit?: string}; type ViolationsConversionSurchargeParams = {surcharge?: number}; diff --git a/src/libs/Violations/ViolationsUtils.ts b/src/libs/Violations/ViolationsUtils.ts index 441baf3d8cef..eabb82666769 100644 --- a/src/libs/Violations/ViolationsUtils.ts +++ b/src/libs/Violations/ViolationsUtils.ts @@ -1,7 +1,7 @@ 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 type {TranslationPaths, ViolationsOverLimitParams} from '@src/languages/types'; import ONYXKEYS from '@src/ONYXKEYS'; import type {PolicyCategories, PolicyTags, Transaction, TransactionViolation} from '@src/types/onyx'; @@ -93,6 +93,7 @@ const ViolationsUtils = { violation: TransactionViolation, translate: (phraseKey: TKey, ...phraseParameters: PhraseParameters>) => string, ): string { + console.log(violation.name, violation.data); switch (violation.name) { case 'allTagLevelsRequired': return translate('violations.allTagLevelsRequired'); @@ -104,7 +105,7 @@ const ViolationsUtils = { case 'billableExpense': return translate('violations.billableExpense'); case 'cashExpenseWithNoReceipt': - return translate('violations.cashExpenseWithNoReceipt', {formattedLimit: violation.data?.formattedLimit ?? ''}); + return translate('violations.cashExpenseWithNoReceipt', violation.data ?? undefined); case 'categoryOutOfPolicy': return translate('violations.categoryOutOfPolicy'); case 'conversionSurcharge': @@ -126,7 +127,7 @@ const ViolationsUtils = { case 'missingComment': return translate('violations.missingComment'); case 'missingTag': - return translate('violations.missingTag', {tagName: violation.data?.tagName}); + return translate('violations.missingTag', violation.data ?? undefined); case 'modifiedAmount': return translate('violations.modifiedAmount'); case 'modifiedDate': From 394caca60317f62c1589cb5fe6f07bec04c58ad7 Mon Sep 17 00:00:00 2001 From: Trevor Coleman Date: Mon, 15 Jan 2024 14:58:47 -0500 Subject: [PATCH 10/25] feat(Violations): fixes --- src/libs/Violations/ViolationsUtils.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/libs/Violations/ViolationsUtils.ts b/src/libs/Violations/ViolationsUtils.ts index eabb82666769..7d121bc2d767 100644 --- a/src/libs/Violations/ViolationsUtils.ts +++ b/src/libs/Violations/ViolationsUtils.ts @@ -1,7 +1,7 @@ import reject from 'lodash/reject'; import Onyx from 'react-native-onyx'; import type {Phrase, PhraseParameters} from '@libs/Localize'; -import type {TranslationPaths, ViolationsOverLimitParams} from '@src/languages/types'; +import type {TranslationPaths} from '@src/languages/types'; import ONYXKEYS from '@src/ONYXKEYS'; import type {PolicyCategories, PolicyTags, Transaction, TransactionViolation} from '@src/types/onyx'; @@ -93,7 +93,6 @@ const ViolationsUtils = { violation: TransactionViolation, translate: (phraseKey: TKey, ...phraseParameters: PhraseParameters>) => string, ): string { - console.log(violation.name, violation.data); switch (violation.name) { case 'allTagLevelsRequired': return translate('violations.allTagLevelsRequired'); From 2a624f90daf64ac48c3f0d16f3224617d731d85e Mon Sep 17 00:00:00 2001 From: Trevor Coleman Date: Mon, 22 Jan 2024 15:21:47 -0500 Subject: [PATCH 11/25] feat(Violations): filter violations by type --- src/hooks/useViolations.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/hooks/useViolations.ts b/src/hooks/useViolations.ts index 5b6f0f96c5da..5cd958b20c3f 100644 --- a/src/hooks/useViolations.ts +++ b/src/hooks/useViolations.ts @@ -49,14 +49,15 @@ type ViolationsMap = Map; function useViolations(violations: TransactionViolation[]) { const violationsByField = useMemo((): ViolationsMap => { + const filteredViolations = violations.filter((v) => v.type === 'violation'); const violationGroups = new Map(); - - for (const violation of violations) { - const field = violationFields[violation.name]; - const existingViolations = violationGroups.get(field) ?? []; - violationGroups.set(field, [...existingViolations, violation]); + for (const violation of filteredViolations) { + if (violation.type === 'violation') { + const field = violationFields[violation.name]; + const existingViolations = violationGroups.get(field) ?? []; + violationGroups.set(field, [...existingViolations, violation]); + } } - return violationGroups ?? new Map(); }, [violations]); From e37a63a00e643a3441c2a51a03500411efd10e31 Mon Sep 17 00:00:00 2001 From: Trevor Coleman Date: Mon, 22 Jan 2024 15:32:15 -0500 Subject: [PATCH 12/25] feat(Violations): fix dependency array --- src/components/ReportActionItem/MoneyRequestView.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ReportActionItem/MoneyRequestView.js b/src/components/ReportActionItem/MoneyRequestView.js index 76d5aa41f772..d271ff174b90 100644 --- a/src/components/ReportActionItem/MoneyRequestView.js +++ b/src/components/ReportActionItem/MoneyRequestView.js @@ -263,7 +263,7 @@ function MoneyRequestView({report, parentReport, parentReportActions, policyCate return ''; }, - [transactionAmount, isEmptyMerchant, transactionDate, hasErrors, canUseViolations, hasViolations, translate, getViolationsForField], + [transactionAmount, isPolicyExpenseChat, isEmptyMerchant, transactionDate, hasErrors, canUseViolations, hasViolations, translate, getViolationsForField], ); return ( From ef959ef48d92735faa7a317bb264ae6332111af6 Mon Sep 17 00:00:00 2001 From: Trevor Coleman Date: Mon, 22 Jan 2024 15:35:02 -0500 Subject: [PATCH 13/25] feat(Violations): handle default case for missing tag --- src/languages/es.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/languages/es.ts b/src/languages/es.ts index e9accef936dc..050cd8e9c6bd 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -2576,7 +2576,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: ({tagName}: ViolationsMissingTagParams = {}) => `Falta ${tagName ?? 'etiqueta'}`, modifiedAmount: 'Importe superior al del recibo escaneado', modifiedDate: 'Fecha difiere del recibo escaneado', nonExpensiworksExpense: 'Gasto no es de Expensiworks', From 8be0e64fdafd410b8dc423c2f2f82b91e91c3580 Mon Sep 17 00:00:00 2001 From: Trevor Coleman Date: Wed, 24 Jan 2024 12:04:35 -0500 Subject: [PATCH 14/25] feat(Violations): simplify --- src/components/ReportActionItem/MoneyRequestView.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ReportActionItem/MoneyRequestView.js b/src/components/ReportActionItem/MoneyRequestView.js index d271ff174b90..d2a8ea80ae33 100644 --- a/src/components/ReportActionItem/MoneyRequestView.js +++ b/src/components/ReportActionItem/MoneyRequestView.js @@ -248,7 +248,7 @@ function MoneyRequestView({report, parentReport, parentReportActions, policyCate }, }; - const {isError, translationPath} = fieldChecks[field] ? fieldChecks[field] : {}; + const {isError, translationPath} = fieldChecks[field] || {}; // Display form errors when first creating the money request if (hasErrors && isError && translationPath) { From 0adddd000f04df0fbd7667f2a686601149a1bb2c Mon Sep 17 00:00:00 2001 From: Trevor Coleman Date: Wed, 24 Jan 2024 12:04:55 -0500 Subject: [PATCH 15/25] feat(Violations): fix fields --- src/components/ReportActionItem/MoneyRequestView.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestView.js b/src/components/ReportActionItem/MoneyRequestView.js index d2a8ea80ae33..c6c19a8e24ab 100644 --- a/src/components/ReportActionItem/MoneyRequestView.js +++ b/src/components/ReportActionItem/MoneyRequestView.js @@ -311,7 +311,7 @@ function MoneyRequestView({report, parentReport, parentReportActions, policyCate interactive={canEditAmount} shouldShowRightIcon={canEditAmount} onPress={() => Navigation.navigate(ROUTES.EDIT_REQUEST.getRoute(report.reportID, CONST.EDIT_REQUEST_FIELD.AMOUNT))} - brickRoadIndicator={getErrorForField('comment') ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : ''} + brickRoadIndicator={getErrorForField('amount') ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : ''} error={getErrorForField('amount')} /> @@ -376,7 +376,7 @@ function MoneyRequestView({report, parentReport, parentReportActions, policyCate shouldShowRightIcon={canEdit} titleStyle={styles.flex1} onPress={() => Navigation.navigate(ROUTES.EDIT_REQUEST.getRoute(report.reportID, CONST.EDIT_REQUEST_FIELD.CATEGORY))} - brickRoadIndicator={getErrorForField('comment') ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : ''} + brickRoadIndicator={getErrorForField('category') ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : ''} error={getErrorForField('category')} /> @@ -390,7 +390,7 @@ function MoneyRequestView({report, parentReport, parentReportActions, policyCate shouldShowRightIcon={canEdit} titleStyle={styles.flex1} onPress={() => Navigation.navigate(ROUTES.EDIT_REQUEST.getRoute(report.reportID, CONST.EDIT_REQUEST_FIELD.TAG))} - brickRoadIndicator={getErrorForField('comment') ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : ''} + brickRoadIndicator={getErrorForField('tag') ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : ''} error={getErrorForField('tag')} /> @@ -414,7 +414,7 @@ function MoneyRequestView({report, parentReport, parentReportActions, policyCate isOn={transactionBillable} onToggle={saveBillable} /> - {getErrorForField('comment') && ( + {getErrorForField('billable') && ( Date: Wed, 24 Jan 2024 12:07:12 -0500 Subject: [PATCH 16/25] feat(Violations): reword comment --- src/components/ReportActionItem/MoneyRequestView.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ReportActionItem/MoneyRequestView.js b/src/components/ReportActionItem/MoneyRequestView.js index c6c19a8e24ab..cb44622585ab 100644 --- a/src/components/ReportActionItem/MoneyRequestView.js +++ b/src/components/ReportActionItem/MoneyRequestView.js @@ -250,7 +250,7 @@ function MoneyRequestView({report, parentReport, parentReportActions, policyCate const {isError, translationPath} = fieldChecks[field] || {}; - // Display form errors when first creating the money request + // Return form errors if there are any if (hasErrors && isError && translationPath) { return translate(translationPath); } From 01c552ce6968e907193470fadf86b7348f643c2c Mon Sep 17 00:00:00 2001 From: Trevor Coleman Date: Wed, 24 Jan 2024 12:07:25 -0500 Subject: [PATCH 17/25] feat(Violations): remove redundant check --- src/hooks/useViolations.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/hooks/useViolations.ts b/src/hooks/useViolations.ts index 5cd958b20c3f..291831fea1f7 100644 --- a/src/hooks/useViolations.ts +++ b/src/hooks/useViolations.ts @@ -52,11 +52,9 @@ function useViolations(violations: TransactionViolation[]) { const filteredViolations = violations.filter((v) => v.type === 'violation'); const violationGroups = new Map(); for (const violation of filteredViolations) { - if (violation.type === 'violation') { - const field = violationFields[violation.name]; - const existingViolations = violationGroups.get(field) ?? []; - violationGroups.set(field, [...existingViolations, violation]); - } + const field = violationFields[violation.name]; + const existingViolations = violationGroups.get(field) ?? []; + violationGroups.set(field, [...existingViolations, violation]); } return violationGroups ?? new Map(); }, [violations]); From 498634a8e63c903bef3e4bda898fcf97bf7d774f Mon Sep 17 00:00:00 2001 From: Trevor Coleman Date: Wed, 24 Jan 2024 12:08:29 -0500 Subject: [PATCH 18/25] feat(Violations): add space --- 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 c07462908d84..02c34d4b5005 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -2085,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: ({formattedLimit}: ViolationsCashExpenseWithNoReceiptParams = {}) => `Receipt required${formattedLimit ? `over ${formattedLimit}` : ''}`, + 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', From 015764e317d0a3c861286120db0b480690328c98 Mon Sep 17 00:00:00 2001 From: Trevor Coleman Date: Wed, 24 Jan 2024 12:10:41 -0500 Subject: [PATCH 19/25] feat(Violations): change empty string to single quotes --- src/languages/es.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/languages/es.ts b/src/languages/es.ts index 49948bc76004..1c82d7c92771 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -2595,7 +2595,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: ({formattedLimit, category}: ViolationsReceiptRequiredParams = {}) => - `Recibo obligatorio${formattedLimit ? ` para importes sobre ${category ? 'el limite de la categoría de ' : ''}${formattedLimit}` : ``}`, + `Recibo obligatorio${formattedLimit ? ` para importes sobre ${category ? 'el limite de la categoría de ' : ''}${formattedLimit}` : ''}`, rter: ({brokenBankConnection, isAdmin, email, isTransactionOlderThan7Days, member}: ViolationsRterParams) => { if (brokenBankConnection) { return isAdmin From b2ba0b61276264ac30085f6ebc5ea3824792a198 Mon Sep 17 00:00:00 2001 From: Trevor Coleman Date: Wed, 24 Jan 2024 13:19:31 -0500 Subject: [PATCH 20/25] feat(Violations): make translations consistent --- src/languages/en.ts | 4 +- src/languages/es.ts | 15 +++---- src/languages/types.ts | 8 ++-- src/libs/Violations/ViolationsUtils.ts | 57 +++++++++++++++++--------- 4 files changed, 51 insertions(+), 33 deletions(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index 02c34d4b5005..fa49d1935af9 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -2085,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: ({formattedLimit}: ViolationsCashExpenseWithNoReceiptParams = {}) => `Receipt required${formattedLimit ? ` over ${formattedLimit}` : ''}`, + 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', @@ -2104,7 +2104,7 @@ export default { 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: ({limit}: ViolationsPerDayLimitParams) => `Amount over daily ${limit}/person category 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 ? ` over ${formattedLimit}${category ? ' category limit' : ''}` : ''}`, diff --git a/src/languages/es.ts b/src/languages/es.ts index 1c82d7c92771..bf24a5279fed 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: ({formattedLimit}: ViolationsCashExpenseWithNoReceiptParams = {}) => `Recibo obligatorio para montos mayores a ${formattedLimit}`, + 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: ({formattedLimit}: ViolationsOverAutoApprovalLimitParams) => `Importe supera el límite de aprobación automática de ${formattedLimit}`, - overCategoryLimit: ({formattedLimit}: ViolationsOverCategoryLimitParams) => `Importe supera el límite para la categoría de ${formattedLimit}/persona`, - overLimit: ({formattedLimit}: ViolationsOverLimitParams) => `Importe supera el límite de ${formattedLimit}/persona`, - overLimitAttendee: ({formattedLimit}: ViolationsOverLimitParams) => `Importe supera el límite de ${formattedLimit}/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: ({formattedLimit, category}: ViolationsReceiptRequiredParams = {}) => `Recibo obligatorio${formattedLimit ? ` para importes sobre ${category ? 'el limite de la categoría de ' : ''}${formattedLimit}` : ''}`, diff --git a/src/languages/types.ts b/src/languages/types.ts index 769b43cc131f..a0998e6d795c 100644 --- a/src/languages/types.ts +++ b/src/languages/types.ts @@ -225,13 +225,13 @@ type ViolationsMaxAgeParams = {maxAge: number}; type ViolationsMissingTagParams = {tagName?: string}; -type ViolationsOverAutoApprovalLimitParams = {formattedLimit: string}; +type ViolationsOverAutoApprovalLimitParams = {formattedLimit?: string}; -type ViolationsOverCategoryLimitParams = {formattedLimit: string}; +type ViolationsOverCategoryLimitParams = {formattedLimit?: string}; -type ViolationsOverLimitParams = {formattedLimit: string}; +type ViolationsOverLimitParams = {formattedLimit?: string}; -type ViolationsPerDayLimitParams = {limit?: string}; +type ViolationsPerDayLimitParams = {formattedLimit?: string}; type ViolationsReceiptRequiredParams = {formattedLimit?: string; category?: string}; diff --git a/src/libs/Violations/ViolationsUtils.ts b/src/libs/Violations/ViolationsUtils.ts index 7d121bc2d767..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', violation.data ?? undefined); + 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', violation.data ?? undefined); + return translate('violations.missingTag', {tagName}); case 'modifiedAmount': return translate('violations.modifiedAmount'); case 'modifiedDate': @@ -134,37 +151,37 @@ const ViolationsUtils = { case 'nonExpensiworksExpense': return translate('violations.nonExpensiworksExpense'); case 'overAutoApprovalLimit': - return translate('violations.overAutoApprovalLimit', {formattedLimit: violation.data?.formattedLimit ?? ''}); + return translate('violations.overAutoApprovalLimit', {formattedLimit}); case 'overCategoryLimit': - return translate('violations.overCategoryLimit', {formattedLimit: violation.data?.formattedLimit ?? ''}); + return translate('violations.overCategoryLimit', {formattedLimit}); case 'overLimit': - return translate('violations.overLimit', {formattedLimit: violation.data?.formattedLimit ?? ''}); + return translate('violations.overLimit', {formattedLimit}); case 'overLimitAttendee': - return translate('violations.overLimitAttendee', {formattedLimit: violation.data?.formattedLimit ?? ''}); + 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', violation.data ?? undefined); + 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 581f8b078c5158301599bc2bde3ce429c037ac80 Mon Sep 17 00:00:00 2001 From: Trevor Coleman Date: Wed, 24 Jan 2024 17:31:06 -0500 Subject: [PATCH 21/25] feat(Violations): rename vars --- .../ReportActionItem/MoneyRequestView.js | 2 +- src/hooks/useViolations.ts | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestView.js b/src/components/ReportActionItem/MoneyRequestView.js index cb44622585ab..2884e9cd0482 100644 --- a/src/components/ReportActionItem/MoneyRequestView.js +++ b/src/components/ReportActionItem/MoneyRequestView.js @@ -255,7 +255,7 @@ function MoneyRequestView({report, parentReport, parentReportActions, policyCate return translate(translationPath); } - // Show violations if there are any + // Return violations if there are any if (canUseViolations && hasViolations(field)) { const violations = getViolationsForField(field); return ViolationsUtils.getViolationTranslation(violations[0], translate); diff --git a/src/hooks/useViolations.ts b/src/hooks/useViolations.ts index 291831fea1f7..70e66ab65a08 100644 --- a/src/hooks/useViolations.ts +++ b/src/hooks/useViolations.ts @@ -4,12 +4,12 @@ import type {TransactionViolation, ViolationName} from '@src/types/onyx'; /** * Names of Fields where violations can occur. */ -type MoneyRequestField = 'amount' | 'billable' | 'category' | 'comment' | 'date' | 'merchant' | 'receipt' | 'tag' | 'tax'; +type ViolationField = 'amount' | 'billable' | 'category' | 'comment' | 'date' | 'merchant' | 'receipt' | 'tag' | 'tax'; /** * Map from Violation Names to the field where that violation can occur. */ -const violationFields: Record = { +const violationFields: Record = { allTagLevelsRequired: 'tag', autoReportedRejectedExpense: 'merchant', billableExpense: 'billable', @@ -45,12 +45,12 @@ const violationFields: Record = { taxRequired: 'tax', }; -type ViolationsMap = Map; +type ViolationsMap = Map; function useViolations(violations: TransactionViolation[]) { const violationsByField = useMemo((): ViolationsMap => { - const filteredViolations = violations.filter((v) => v.type === 'violation'); - const violationGroups = new Map(); + const filteredViolations = violations.filter((violation) => violation.type === 'violation'); + const violationGroups = new Map(); for (const violation of filteredViolations) { const field = violationFields[violation.name]; const existingViolations = violationGroups.get(field) ?? []; @@ -59,7 +59,7 @@ function useViolations(violations: TransactionViolation[]) { return violationGroups ?? new Map(); }, [violations]); - const getViolationsForField = useCallback((field: MoneyRequestField) => violationsByField.get(field) ?? [], [violationsByField]); + const getViolationsForField = useCallback((field: ViolationField) => violationsByField.get(field) ?? [], [violationsByField]); return { getViolationsForField, @@ -67,4 +67,4 @@ function useViolations(violations: TransactionViolation[]) { } export default useViolations; -export type {MoneyRequestField}; +export type {ViolationField}; From c92ee98845091f7bd40eff87fba2323d89380c6e Mon Sep 17 00:00:00 2001 From: Trevor Coleman Date: Wed, 24 Jan 2024 17:35:25 -0500 Subject: [PATCH 22/25] feat(Violations): return empty string if variables are missing --- src/libs/Violations/ViolationsUtils.ts | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/libs/Violations/ViolationsUtils.ts b/src/libs/Violations/ViolationsUtils.ts index 5b94116e36e6..4a36e32f03bc 100644 --- a/src/libs/Violations/ViolationsUtils.ts +++ b/src/libs/Violations/ViolationsUtils.ts @@ -100,8 +100,8 @@ const ViolationsUtils = { isTransactionOlderThan7Days = false, member, category, - rejectedBy = '', - rejectReason = '', + rejectedBy, + rejectReason, formattedLimit, surcharge, invoiceMarkup, @@ -114,10 +114,12 @@ const ViolationsUtils = { case 'allTagLevelsRequired': return translate('violations.allTagLevelsRequired'); case 'autoReportedRejectedExpense': - return translate('violations.autoReportedRejectedExpense', { - rejectedBy, - rejectReason, - }); + return rejectReason && rejectedBy + ? translate('violations.autoReportedRejectedExpense', { + rejectedBy, + rejectReason, + }) + : ''; case 'billableExpense': return translate('violations.billableExpense'); case 'cashExpenseWithNoReceipt': From abf3255f1ad48b300aebc95bed0824ce52427d03 Mon Sep 17 00:00:00 2001 From: Trevor Coleman Date: Fri, 2 Feb 2024 22:26:25 -0500 Subject: [PATCH 23/25] feat(Violations): restore type --- src/components/ReportActionItem/MoneyRequestView.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index 132d6ff5441b..3f6f6f646a42 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -32,6 +32,7 @@ import AnimatedEmptyStateBackground from '@pages/home/report/AnimatedEmptyStateB import * as IOU from '@userActions/IOU'; import * as Transaction from '@userActions/Transaction'; import CONST from '@src/CONST'; +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'; @@ -200,10 +201,10 @@ function MoneyRequestView({ const getPendingFieldAction = (fieldPath: TransactionPendingFieldsKey) => transaction?.pendingFields?.[fieldPath] ?? pendingAction; const getErrorForField = useCallback( - (field) => { + (field: ViolationField) => { // Checks applied when creating a new money request // NOTE: receipt field can return multiple violations, so we need to handle it separately - const fieldChecks = { + const fieldChecks: Partial> = { amount: { isError: transactionAmount === 0, translationPath: 'common.error.enterAmount', From 011a08a41aea4f63e045fd5f6d5798a0ef5374ec Mon Sep 17 00:00:00 2001 From: Trevor Coleman Date: Tue, 6 Feb 2024 11:43:38 -0500 Subject: [PATCH 24/25] feat(Violations): add deps to array --- src/components/ReportActionItem/MoneyRequestView.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index e08e529d1153..cbbd198939b3 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -219,7 +219,7 @@ function MoneyRequestView({ }, }; - const {isError, translationPath} = fieldChecks[field] || {}; + const {isError, translationPath} = fieldChecks[field] ?? {}; // Return form errors if there are any if (hasErrors && isError && translationPath) { @@ -234,7 +234,7 @@ function MoneyRequestView({ return ''; }, - [transactionAmount, isPolicyExpenseChat, isEmptyMerchant, transactionDate, hasErrors, canUseViolations, hasViolations, translate, getViolationsForField], + [transactionAmount, isSettled, isCancelled, isPolicyExpenseChat, isEmptyMerchant, transactionDate, hasErrors, canUseViolations, hasViolations, translate, getViolationsForField], ); return ( From 519897aecec5072110ae9876d0c7e3edfe2fd795 Mon Sep 17 00:00:00 2001 From: Trevor Coleman Date: Tue, 6 Feb 2024 12:06:19 -0500 Subject: [PATCH 25/25] feat(Violations): replace tag field --- src/components/ReportActionItem/MoneyRequestView.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index cbbd198939b3..2d69f0811ac4 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -376,6 +376,7 @@ function MoneyRequestView({ Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_TAG.getRoute(CONST.IOU.ACTION.EDIT, CONST.IOU.TYPE.REQUEST, transaction?.transactionID ?? '', report.reportID)) } brickRoadIndicator={getErrorForField('tag') ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined} + error={getErrorForField('tag')} /> )}