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};