Skip to content

Commit

Permalink
feat(Violations): create useMoneyRequestViewErrors and move violation…
Browse files Browse the repository at this point in the history
…s into errors prop of inputs
  • Loading branch information
trevor-coleman committed Jan 10, 2024
1 parent ce67e8a commit a4844ce
Show file tree
Hide file tree
Showing 4 changed files with 102 additions and 23 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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';
Expand All @@ -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);

Expand Down Expand Up @@ -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 (
<View style={[StyleUtils.getReportWelcomeContainerStyle(isSmallScreenWidth)]}>
<AnimatedEmptyStateBackground />
Expand Down Expand Up @@ -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 && <ViolationMessages violations={getViolationsForField('amount')} />}
</OfflineWithFeedback>
<OfflineWithFeedback pendingAction={getPendingFieldAction('pendingFields.comment')}>
<MenuItemWithTopDescription
Expand All @@ -289,9 +297,9 @@ function MoneyRequestView({report, parentReport, parentReportActions, policyCate
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 : ''}
error={getErrorForField('comment')}
numberOfLinesTitle={0}
/>
{canUseViolations && <ViolationMessages violations={getViolationsForField('comment')} />}
</OfflineWithFeedback>
{isDistanceRequest ? (
<OfflineWithFeedback pendingAction={lodashGet(transaction, 'pendingFields.waypoints') || lodashGet(transaction, 'pendingAction')}>
Expand All @@ -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 && <ViolationMessages violations={getViolationsForField('merchant')} />}
</OfflineWithFeedback>
)}
<OfflineWithFeedback pendingAction={getPendingFieldAction('pendingFields.created')}>
Expand All @@ -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 && <ViolationMessages violations={getViolationsForField('date')} />}
</OfflineWithFeedback>
{shouldShowCategory && (
<OfflineWithFeedback pendingAction={lodashGet(transaction, 'pendingFields.category') || lodashGet(transaction, 'pendingAction')}>
Expand All @@ -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 && <ViolationMessages violations={getViolationsForField('category')} />}
</OfflineWithFeedback>
)}
{shouldShowTag && (
Expand All @@ -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 && <ViolationMessages violations={getViolationsForField('tag')} />}
</OfflineWithFeedback>
)}
{isCardTransaction && (
Expand All @@ -379,13 +385,13 @@ function MoneyRequestView({report, parentReport, parentReportActions, policyCate
isOn={transactionBillable}
onToggle={saveBillable}
/>
{hasViolations('billable') && (
<ViolationMessages
violations={getViolationsForField('billable')}
isLast
/>
)}
</View>
{hasViolations('billable') && (
<ViolationMessages
violations={getViolationsForField('billable')}
isLast
/>
)}
</>
)}
</View>
Expand Down
3 changes: 3 additions & 0 deletions src/components/ReportActionItem/MoneyRequestView/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import MoneyRequestView from './MoneyRequestView';

export default MoneyRequestView;
Original file line number Diff line number Diff line change
@@ -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<MoneyRequestField, 'receipt'>;

type FieldCheck = {
isError: boolean;
translationPath: TranslationPaths;
};

type FieldChecks = Partial<Record<FieldsWithErrors, FieldCheck>>;

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<MoneyRequestField, 'receipt'>) => {
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;
12 changes: 6 additions & 6 deletions src/hooks/useViolations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<ViolationName, ViolationField> = {
const violationFields: Record<ViolationName, MoneyRequestField> = {
allTagLevelsRequired: 'tag',
autoReportedRejectedExpense: 'merchant',
billableExpense: 'billable',
Expand Down Expand Up @@ -45,11 +45,11 @@ const violationFields: Record<ViolationName, ViolationField> = {
taxRequired: 'tax',
};

type ViolationsMap = Map<ViolationField, TransactionViolation[]>;
type ViolationsMap = Map<MoneyRequestField, TransactionViolation[]>;

function useViolations(violations: TransactionViolation[]) {
const violationsByField = useMemo((): ViolationsMap => {
const violationGroups = new Map<ViolationField, TransactionViolation[]>();
const violationGroups = new Map<MoneyRequestField, TransactionViolation[]>();

for (const violation of violations) {
const field = violationFields[violation.name];
Expand All @@ -60,12 +60,12 @@ 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,
};
}

export default useViolations;
export type {ViolationField};
export type {MoneyRequestField};

0 comments on commit a4844ce

Please sign in to comment.