From 7d7c4dad4bcd283490af0176538a1d36d95f86ae Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Tue, 23 Jan 2024 19:38:17 +0100 Subject: [PATCH 01/41] extend routes --- src/ROUTES.ts | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 37003a09a0c..df26c7cf27e 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -156,8 +156,9 @@ const ROUTES = { getRoute: (reportID: string) => `r/${reportID}` as const, }, EDIT_REQUEST: { - route: 'r/:threadReportID/edit/:field', - getRoute: (threadReportID: string, field: ValueOf) => `r/${threadReportID}/edit/${field}` as const, + route: 'r/:threadReportID/edit/:field/:tagIndex?', + getRoute: (threadReportID: string, field: ValueOf, tagIndex?: number) => + `r/${threadReportID}/edit/${field}${tagIndex !== undefined ? `/${tagIndex}` : ''}` as const, }, EDIT_CURRENCY_REQUEST: { route: 'r/:threadReportID/edit/currency', @@ -279,8 +280,8 @@ const ROUTES = { getRoute: (iouType: string, reportID = '') => `${iouType}/new/category/${reportID}` as const, }, MONEY_REQUEST_TAG: { - route: ':iouType/new/tag/:reportID?', - getRoute: (iouType: string, reportID = '') => `${iouType}/new/tag/${reportID}` as const, + route: ':iouType/new/tag/:tagIndex/:reportID?', + getRoute: (iouType: string, tagIndex: number, reportID = '') => `${iouType}/new/tag/${tagIndex}/${reportID}` as const, }, MONEY_REQUEST_MERCHANT: { route: ':iouType/new/merchant/:reportID?', @@ -365,9 +366,9 @@ const ROUTES = { getUrlWithBackToParam(`${action}/${iouType}/scan/${transactionID}/${reportID}`, backTo), }, MONEY_REQUEST_STEP_TAG: { - route: 'create/:iouType/tag/:transactionID/:reportID', - getRoute: (iouType: ValueOf, transactionID: string, reportID: string, backTo = '') => - getUrlWithBackToParam(`create/${iouType}/tag/${transactionID}/${reportID}`, backTo), + route: 'create/:iouType/tag/:tagIndex/:transactionID/:reportID', + getRoute: (iouType: ValueOf, tagIndex: number, transactionID: string, reportID: string, backTo = '') => + getUrlWithBackToParam(`create/${iouType}/tag/${tagIndex}/${transactionID}/${reportID}`, backTo), }, MONEY_REQUEST_STEP_WAYPOINT: { route: ':action/:iouType/waypoint/:transactionID/:reportID/:pageIndex', From 6f723e2fa83b5abf47440018702a54c4aaa1f3bf Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Tue, 23 Jan 2024 19:38:25 +0100 Subject: [PATCH 02/41] clarify types --- src/types/onyx/PolicyTag.ts | 10 ++++++++-- src/types/onyx/index.ts | 3 ++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/types/onyx/PolicyTag.ts b/src/types/onyx/PolicyTag.ts index 58a21dcf4df..8765cff5c57 100644 --- a/src/types/onyx/PolicyTag.ts +++ b/src/types/onyx/PolicyTag.ts @@ -10,6 +10,12 @@ type PolicyTag = { 'GL Code': string; }; -type PolicyTags = Record; +type PolicyTagList = { + name: string; + required: boolean; + tags: PolicyTags; +}; + +type PolicyTags = Record; -export type {PolicyTag, PolicyTags}; +export type {PolicyTag, PolicyTags, PolicyTagList}; diff --git a/src/types/onyx/index.ts b/src/types/onyx/index.ts index e6d6c27fc81..e5c730c1541 100644 --- a/src/types/onyx/index.ts +++ b/src/types/onyx/index.ts @@ -31,7 +31,7 @@ import type {PolicyCategories, PolicyCategory} from './PolicyCategory'; import type {PolicyMembers} from './PolicyMember'; import type PolicyMember from './PolicyMember'; import type PolicyReportField from './PolicyReportField'; -import type {PolicyTag, PolicyTags} from './PolicyTag'; +import type {PolicyTag, PolicyTagList, PolicyTags} from './PolicyTag'; import type PrivatePersonalDetails from './PrivatePersonalDetails'; import type RecentlyUsedCategories from './RecentlyUsedCategories'; import type RecentlyUsedReportFields from './RecentlyUsedReportFields'; @@ -105,6 +105,7 @@ export type { PolicyMembers, PolicyTag, PolicyTags, + PolicyTagList, PrivatePersonalDetails, RecentWaypoint, RecentlyUsedCategories, From 430a72b05fa07dada9aa195064a1ef0b56ce1573 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Tue, 23 Jan 2024 19:38:56 +0100 Subject: [PATCH 03/41] improve getTag of TransactionUtils --- src/libs/TransactionUtils.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/libs/TransactionUtils.ts b/src/libs/TransactionUtils.ts index 1229e700d29..6296024dc0f 100644 --- a/src/libs/TransactionUtils.ts +++ b/src/libs/TransactionUtils.ts @@ -364,7 +364,11 @@ function getBillable(transaction: OnyxEntry): boolean { /** * Return the tag from the transaction. This "tag" field has no "modified" complement. */ -function getTag(transaction: OnyxEntry): string { +function getTag(transaction: OnyxEntry, tagIndex?: number): string { + if (tagIndex !== undefined) { + return transaction?.tag?.split(':')[tagIndex] ?? ''; + } + return transaction?.tag ?? ''; } From 7881a0c5a86efdbe41cf16af8c4a5ca560eeb11f Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Tue, 23 Jan 2024 19:39:18 +0100 Subject: [PATCH 04/41] Implement new logic of PolicyUtils --- src/libs/PolicyUtils.ts | 40 +++++++++++++++++++--------------------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts index ec7346e26f8..164a46ecfab 100644 --- a/src/libs/PolicyUtils.ts +++ b/src/libs/PolicyUtils.ts @@ -3,7 +3,7 @@ import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {PersonalDetailsList, Policy, PolicyMembers, PolicyTag, PolicyTags} from '@src/types/onyx'; +import type {PersonalDetailsList, Policy, PolicyMembers, PolicyTagList, PolicyTags} from '@src/types/onyx'; import type {EmptyObject} from '@src/types/utils/EmptyObject'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; @@ -157,42 +157,40 @@ function getIneligibleInvitees(policyMembers: OnyxEntry, personal } /** - * Gets the tag from policy tags, defaults to the first if no key is provided. + * Gets a tag name from policy tags based on a tag index. */ -function getTag(policyTags: OnyxEntry, tagKey?: keyof typeof policyTags): PolicyTag | undefined | EmptyObject { - if (isEmptyObject(policyTags)) { - return {}; +function getTagListName(policyTags: OnyxEntry, tagIndex: number) { + if (Object.keys(policyTags ?? {})?.length === 0) { + return ''; } - const policyTagKey = tagKey ?? Object.keys(policyTags ?? {})[0]; + const policyTagKeys = Object.keys(policyTags ?? {})[tagIndex] ?? []; - return policyTags?.[policyTagKey] ?? {}; + return policyTags?.[policyTagKeys]?.name ?? ''; } /** - * Gets the first tag name from policy tags. + * Gets all tag lists of a policy */ -function getTagListName(policyTags: OnyxEntry) { - if (Object.keys(policyTags ?? {})?.length === 0) { - return ''; +function getTagLists(policyTags: OnyxCollection): PolicyTagList[] { + if (isEmptyObject(policyTags)) { + return []; } - const policyTagKeys = Object.keys(policyTags ?? {})[0] ?? []; - - return policyTags?.[policyTagKeys]?.name ?? ''; + return Object.values(policyTags).filter((policyTagList): policyTagList is PolicyTagList => policyTagList !== null); } /** - * Gets the tags of a policy for a specific key. Defaults to the first tag if no key is provided. + * Gets a tag list of a policy by a tag index */ -function getTagList(policyTags: OnyxCollection, tagKey: string) { - if (Object.keys(policyTags ?? {})?.length === 0) { +function getTagList(policyTags: OnyxCollection, tagIndex: number): PolicyTagList | EmptyObject { + const tagLists = getTagLists(policyTags); + + if (tagLists.length === 0) { return {}; } - const policyTagKey = tagKey ?? Object.keys(policyTags ?? {})[0]; - - return policyTags?.[policyTagKey]?.tags ?? {}; + return tagLists[tagIndex]; } function isPendingDeletePolicy(policy: OnyxEntry): boolean { @@ -218,7 +216,7 @@ export { isPolicyAdmin, getMemberAccountIDsForWorkspace, getIneligibleInvitees, - getTag, + getTagLists, getTagListName, getTagList, isPendingDeletePolicy, From 8a26e3779b2146c02ec51dcbf9c6cdc80ebcd0fb Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Tue, 23 Jan 2024 19:39:56 +0100 Subject: [PATCH 05/41] Implement new logic of IOU --- src/libs/actions/IOU.js | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index 7ee752a1f0e..db65aa77549 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -231,17 +231,28 @@ function resetMoneyRequestCategory_temporaryForRefactor(transactionID) { /* * @param {String} transactionID + * @param {String} reportTags * @param {String} tag + * @param {Number} tagIndex */ -function setMoneyRequestTag_temporaryForRefactor(transactionID, tag) { - Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`, {tag}); +function setMoneyRequestTag_temporaryForRefactor(transactionID, reportTags, tag, tagIndex) { + const splittedReportTags = reportTags.split(CONST.COLON); + splittedReportTags[tagIndex] = tag; + + Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`, { + tag: splittedReportTags.join(CONST.COLON), + }); } /* * @param {String} transactionID + * @param {String} reportTags + * @param {String} tag */ -function resetMoneyRequestTag_temporaryForRefactor(transactionID) { - Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`, {tag: null}); +function resetMoneyRequestTag_temporaryForRefactor(transactionID, reportTags, tag) { + Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`, { + tag: reportTags.replace(tag, '').replace(/:*$/, ''), + }); } /** @@ -743,8 +754,9 @@ function getMoneyRequestInformation( ); const optimisticPolicyRecentlyUsedCategories = Policy.buildOptimisticPolicyRecentlyUsedCategories(iouReport.policyID, category); - + // TODO: Adapt const optimisticPolicyRecentlyUsedTags = Policy.buildOptimisticPolicyRecentlyUsedTags(iouReport.policyID, tag); + // const optimisticPolicyRecentlyUsedTags = {}; // If there is an existing transaction (which is the case for distance requests), then the data from the existing transaction // needs to be manually merged into the optimistic transaction. This is because buildOnyxDataForMoneyRequest() uses `Onyx.set()` for the transaction From 02ae4cc330e5b07391a271fa60c3c9d2ee492ab2 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Tue, 23 Jan 2024 19:40:38 +0100 Subject: [PATCH 06/41] Implement new logic to components --- ...oraryForRefactorRequestConfirmationList.js | 45 +++++++++---------- .../ReportActionItem/MoneyRequestView.js | 43 +++++++++--------- src/components/TagPicker/index.js | 10 ++--- .../TagPicker/tagPickerPropTypes.js | 3 ++ src/libs/ModifiedExpenseMessage.ts | 2 +- src/pages/EditRequestPage.js | 23 ++++++---- src/pages/EditRequestTagPage.js | 6 ++- src/pages/iou/MoneyRequestTagPage.js | 2 +- .../step/IOURequestStepConfirmation.js | 1 - .../iou/request/step/IOURequestStepTag.js | 40 ++++++++++------- 10 files changed, 97 insertions(+), 78 deletions(-) diff --git a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js index 36d424ea28f..dd067901483 100755 --- a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js +++ b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js @@ -78,9 +78,6 @@ const propTypes = { /** IOU category */ iouCategory: PropTypes.string, - /** IOU tag */ - iouTag: PropTypes.string, - /** IOU isBillable */ iouIsBillable: PropTypes.bool, @@ -175,7 +172,6 @@ const defaultProps = { onSelectParticipant: () => {}, iouType: CONST.IOU.TYPE.REQUEST, iouCategory: '', - iouTag: '', iouIsBillable: false, onToggleBillable: () => {}, payeePersonalDetails: null, @@ -215,7 +211,6 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ iouCurrencyCode, iouIsBillable, iouMerchant, - iouTag, iouType, isDistanceRequest, isEditingSplitBill, @@ -268,13 +263,11 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ const shouldShowDate = shouldShowSmartScanFields || isDistanceRequest; const shouldShowMerchant = shouldShowSmartScanFields && !isDistanceRequest; - // Fetches the first tag list of the policy - const policyTag = PolicyUtils.getTag(policyTags); - const policyTagList = lodashGet(policyTag, 'tags', {}); - const policyTagListName = lodashGet(policyTag, 'name', translate('common.tag')); + const policyTagList = useMemo(() => PolicyUtils.getTagLists(policyTags), [policyTags]); + const policyTagValueList = useMemo(() => _.flatten(_.map(policyTagList, ({tags}) => _.values(tags))), [policyTagList]); // A flag for showing the tags field - const shouldShowTags = isPolicyExpenseChat && OptionsListUtils.hasEnabledOptions(_.values(policyTagList)); + const shouldShowTags = isPolicyExpenseChat && OptionsListUtils.hasEnabledOptions(policyTagValueList); // A flag for showing tax rate const shouldShowTax = isPolicyExpenseChat && policy && policy.isTaxTrackingEnabled; @@ -796,21 +789,23 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ rightLabel={canUseViolations && Boolean(policy.requiresCategory) ? translate('common.required') : ''} /> )} - {shouldShowTags && ( - - Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_TAG.getRoute(iouType, transaction.transactionID, reportID, Navigation.getActiveRouteWithoutParams())) - } - style={[styles.moneyRequestMenuItem]} - disabled={didConfirm} - interactive={!isReadOnly} - rightLabel={canUseViolations && Boolean(policy.requiresTag) ? translate('common.required') : ''} - /> - )} + {shouldShowTags && + _.map(policyTagList, ({name}, index) => ( + + Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_TAG.getRoute(iouType, index, transaction.transactionID, reportID, Navigation.getActiveRouteWithoutParams())) + } + style={[styles.moneyRequestMenuItem]} + disabled={didConfirm} + interactive={!isReadOnly} + rightLabel={canUseViolations && Boolean(policy.requiresTag) ? translate('common.required') : ''} + /> + ))} {shouldShowTax && ( PolicyUtils.getTagLists(policyTags), [policyTags]); + const policyTagValueList = useMemo(() => _.flatten(_.map(policyTagList, ({tags}) => _.values(tags))), [policyTagList]); // Flags for showing categories and tags const shouldShowCategory = isPolicyExpenseChat && (transactionCategory || OptionsListUtils.hasEnabledOptions(lodashValues(policyCategories))); - const shouldShowTag = isPolicyExpenseChat && (transactionTag || OptionsListUtils.hasEnabledOptions(lodashValues(policyTagsList))); + const shouldShowTag = isPolicyExpenseChat && (transactionTag || OptionsListUtils.hasEnabledOptions(lodashValues(policyTagValueList))); const shouldShowBillable = isPolicyExpenseChat && (transactionBillable || !lodashGet(policy, 'disabledFields.defaultBillable', true)); const {getViolationsForField} = useViolations(transactionViolations); @@ -346,20 +346,24 @@ function MoneyRequestView({report, parentReport, parentReportActions, policyCate {canUseViolations && } )} - {shouldShowTag && ( - - Navigation.navigate(ROUTES.EDIT_REQUEST.getRoute(report.reportID, CONST.EDIT_REQUEST_FIELD.TAG))} - brickRoadIndicator={hasViolations('tag') ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : ''} - /> - {canUseViolations && } - - )} + {shouldShowTag && + _.map(policyTagList, ({name}, index) => ( + + Navigation.navigate(ROUTES.EDIT_REQUEST.getRoute(report.reportID, CONST.EDIT_REQUEST_FIELD.TAG, index))} + brickRoadIndicator={hasViolations('tag') ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : ''} + /> + {canUseViolations && } + + ))} {isCardTransaction && ( )} - {shouldShowBillable && ( <> diff --git a/src/components/TagPicker/index.js b/src/components/TagPicker/index.js index e258472eae9..9989447f911 100644 --- a/src/components/TagPicker/index.js +++ b/src/components/TagPicker/index.js @@ -12,15 +12,15 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import {defaultProps, propTypes} from './tagPickerPropTypes'; -function TagPicker({selectedTag, tag, policyTags, policyRecentlyUsedTags, shouldShowDisabledAndSelectedOption, insets, onSubmit}) { +function TagPicker({selectedTag, tag, tagIndex, policyTags, policyRecentlyUsedTags, shouldShowDisabledAndSelectedOption, insets, onSubmit}) { const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); const {translate} = useLocalize(); const [searchValue, setSearchValue] = useState(''); const policyRecentlyUsedTagsList = lodashGet(policyRecentlyUsedTags, tag, []); - const policyTagList = PolicyUtils.getTagList(policyTags, tag); - const policyTagsCount = _.size(_.filter(policyTagList, (policyTag) => policyTag.enabled)); + const policyTagList = PolicyUtils.getTagList(policyTags, tagIndex); + const policyTagsCount = _.size(_.filter(policyTagList.tags, (policyTag) => policyTag.enabled)); const isTagsCountBelowThreshold = policyTagsCount < CONST.TAG_LIST_THRESHOLD; const shouldShowTextInput = !isTagsCountBelowThreshold; @@ -41,10 +41,10 @@ function TagPicker({selectedTag, tag, policyTags, policyRecentlyUsedTags, should const enabledTags = useMemo(() => { if (!shouldShowDisabledAndSelectedOption) { - return policyTagList; + return policyTagList.tags; } const selectedNames = _.map(selectedOptions, (s) => s.name); - const tags = [...selectedOptions, ..._.filter(policyTagList, (policyTag) => policyTag.enabled && !selectedNames.includes(policyTag.name))]; + const tags = [...selectedOptions, ..._.filter(policyTagList.tags, (policyTag) => policyTag.enabled && !selectedNames.includes(policyTag.name))]; return tags; }, [selectedOptions, policyTagList, shouldShowDisabledAndSelectedOption]); diff --git a/src/components/TagPicker/tagPickerPropTypes.js b/src/components/TagPicker/tagPickerPropTypes.js index b98f7f6ef8e..d74bad59a55 100644 --- a/src/components/TagPicker/tagPickerPropTypes.js +++ b/src/components/TagPicker/tagPickerPropTypes.js @@ -12,6 +12,9 @@ const propTypes = { /** The name of tag list we are getting tags for */ tag: PropTypes.string.isRequired, + // TODO: Comment + tagIndex: PropTypes.number.isRequired, + /** Callback to submit the selected tag */ onSubmit: PropTypes.func.isRequired, diff --git a/src/libs/ModifiedExpenseMessage.ts b/src/libs/ModifiedExpenseMessage.ts index ff5ad932719..a754bf85743 100644 --- a/src/libs/ModifiedExpenseMessage.ts +++ b/src/libs/ModifiedExpenseMessage.ts @@ -102,7 +102,7 @@ function getForReportAction(reportAction: ReportAction): string { const reportActionOriginalMessage = reportAction.originalMessage as ExpenseOriginalMessage | undefined; const policyID = ReportUtils.getReportPolicyID(reportAction.reportID) ?? ''; const policyTags = allPolicyTags?.[`${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`] ?? {}; - const policyTagListName = PolicyUtils.getTagListName(policyTags) || Localize.translateLocal('common.tag'); + const policyTagListName = PolicyUtils.getTagListName(policyTags, 0) || Localize.translateLocal('common.tag'); const removalFragments: string[] = []; const setFragments: string[] = []; diff --git a/src/pages/EditRequestPage.js b/src/pages/EditRequestPage.js index 606d3da1ddb..f9f6561d07f 100644 --- a/src/pages/EditRequestPage.js +++ b/src/pages/EditRequestPage.js @@ -1,8 +1,9 @@ import lodashGet from 'lodash/get'; import lodashValues from 'lodash/values'; import PropTypes from 'prop-types'; -import React, {useCallback, useEffect} from 'react'; +import React, {useCallback, useEffect, useMemo} from 'react'; import {withOnyx} from 'react-native-onyx'; +import _ from 'underscore'; import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; import categoryPropTypes from '@components/categoryPropTypes'; import ScreenWrapper from '@components/ScreenWrapper'; @@ -40,6 +41,9 @@ const propTypes = { /** reportID for the "transaction thread" */ threadReportID: PropTypes.string, + + // TODO: Comment + tagIndex: PropTypes.string, }), }).isRequired, @@ -77,16 +81,18 @@ function EditRequestPage({report, route, policyCategories, policyTags, parentRep comment: transactionDescription, merchant: transactionMerchant, category: transactionCategory, - tag: transactionTag, + // tag: reportTags, } = ReportUtils.getTransactionDetails(transaction); const defaultCurrency = lodashGet(route, 'params.currency', '') || transactionCurrency; const fieldToEdit = lodashGet(route, ['params', 'field'], ''); + const rawTagIndex = lodashGet(route, ['params', 'tagIndex'], undefined); + const tagIndex = +rawTagIndex; - // For now, it always defaults to the first tag of the policy - const policyTag = PolicyUtils.getTag(policyTags); - const policyTagList = lodashGet(policyTag, 'tags', {}); - const tagListName = PolicyUtils.getTagListName(policyTags); + const transactionTag = TransactionUtils.getTag(transaction, +tagIndex); + const policyTagList = useMemo(() => PolicyUtils.getTagLists(policyTags), [policyTags]); + const policyTagValueList = useMemo(() => _.flatten(_.map(policyTagList, ({tags}) => _.values(tags))), [policyTagList]); + const policyTagListName = PolicyUtils.getTagListName(policyTags, +tagIndex); // A flag for verifying that the current report is a sub-report of a workspace chat const isPolicyExpenseChat = ReportUtils.isGroupPolicy(report); @@ -95,7 +101,7 @@ function EditRequestPage({report, route, policyCategories, policyTags, parentRep const shouldShowCategories = isPolicyExpenseChat && (transactionCategory || OptionsListUtils.hasEnabledOptions(lodashValues(policyCategories))); // A flag for showing the tags page - const shouldShowTags = isPolicyExpenseChat && (transactionTag || OptionsListUtils.hasEnabledOptions(lodashValues(policyTagList))); + const shouldShowTags = isPolicyExpenseChat && (transactionTag || OptionsListUtils.hasEnabledOptions(policyTagValueList)); // Decides whether to allow or disallow editing a money request useEffect(() => { @@ -251,7 +257,8 @@ function EditRequestPage({report, route, policyCategories, policyTags, parentRep return ( diff --git a/src/pages/EditRequestTagPage.js b/src/pages/EditRequestTagPage.js index 8ecc4a95306..398b6adc39d 100644 --- a/src/pages/EditRequestTagPage.js +++ b/src/pages/EditRequestTagPage.js @@ -18,11 +18,14 @@ const propTypes = { /** The tag name to which the default tag belongs to */ tagName: PropTypes.string.isRequired, + // TODO: Comment + tagIndex: PropTypes.number.isRequired, + /** Callback to fire when the Save button is pressed */ onSubmit: PropTypes.func.isRequired, }; -function EditRequestTagPage({defaultTag, policyID, tagName, onSubmit}) { +function EditRequestTagPage({defaultTag, policyID, tagName, tagIndex, onSubmit}) { const styles = useThemeStyles(); const {translate} = useLocalize(); @@ -46,6 +49,7 @@ function EditRequestTagPage({defaultTag, policyID, tagName, onSubmit}) { { Navigation.goBack(ROUTES.MONEY_REQUEST_CONFIRMATION.getRoute(iouType, report.reportID)); diff --git a/src/pages/iou/request/step/IOURequestStepConfirmation.js b/src/pages/iou/request/step/IOURequestStepConfirmation.js index 9df2564ae38..5021c1996a7 100644 --- a/src/pages/iou/request/step/IOURequestStepConfirmation.js +++ b/src/pages/iou/request/step/IOURequestStepConfirmation.js @@ -349,7 +349,6 @@ function IOURequestStepConfirmation({ iouIsBillable={transaction.billable} onToggleBillable={setBillable} iouCategory={transaction.category} - iouTag={transaction.tag} onConfirm={createTransaction} onSendMoney={sendMoney} onSelectParticipant={addNewParticipant} diff --git a/src/pages/iou/request/step/IOURequestStepTag.js b/src/pages/iou/request/step/IOURequestStepTag.js index 7e2ccbe1a9d..f272042ff16 100644 --- a/src/pages/iou/request/step/IOURequestStepTag.js +++ b/src/pages/iou/request/step/IOURequestStepTag.js @@ -1,6 +1,5 @@ import React from 'react'; import {withOnyx} from 'react-native-onyx'; -import _ from 'underscore'; import TagPicker from '@components/TagPicker'; import tagPropTypes from '@components/tagPropTypes'; import Text from '@components/Text'; @@ -10,6 +9,7 @@ import useThemeStyles from '@hooks/useThemeStyles'; import compose from '@libs/compose'; import Navigation from '@libs/Navigation/Navigation'; import * as PolicyUtils from '@libs/PolicyUtils'; +import * as TransactionUtils from '@libs/TransactionUtils'; import reportPropTypes from '@pages/reportPropTypes'; import * as IOU from '@userActions/IOU'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -44,16 +44,17 @@ function IOURequestStepTag({ policyTags, report, route: { - params: {transactionID, backTo}, + params: {tagIndex: rawTagIndex, transactionID, backTo}, }, - transaction: {tag}, + transaction, }) { const styles = useThemeStyles(); const {translate} = useLocalize(); - // Fetches the first tag list of the policy - const tagListKey = _.first(_.keys(policyTags)); - const policyTagListName = PolicyUtils.getTagListName(policyTags) || translate('common.tag'); + const tagIndex = +rawTagIndex; + const reportTags = TransactionUtils.getTag(transaction); + const tag = TransactionUtils.getTag(transaction, tagIndex); + const policyTagListName = PolicyUtils.getTagListName(policyTags, tagIndex); const navigateBack = () => { Navigation.goBack(backTo || ROUTES.HOME); @@ -64,10 +65,10 @@ function IOURequestStepTag({ * @param {String} selectedTag.searchText */ const updateTag = (selectedTag) => { - if (selectedTag.searchText === tag) { - IOU.resetMoneyRequestTag_temporaryForRefactor(transactionID); + if (tag === selectedTag.searchText) { + IOU.resetMoneyRequestTag_temporaryForRefactor(transactionID, reportTags, selectedTag.searchText); } else { - IOU.setMoneyRequestTag_temporaryForRefactor(transactionID, selectedTag.searchText); + IOU.setMoneyRequestTag_temporaryForRefactor(transactionID, reportTags, selectedTag.searchText, tagIndex); } navigateBack(); }; @@ -79,13 +80,20 @@ function IOURequestStepTag({ shouldShowWrapper testID={IOURequestStepTag.displayName} > - {translate('iou.tagSelection', {tagName: policyTagListName})} - + {({insets}) => ( + <> + {translate('iou.tagSelection', {tagName: policyTagListName})} + + + + )} ); } From 58ad1d3e5f06fb2bb8e3be4fccd9b544c57526c4 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Wed, 24 Jan 2024 16:01:45 +0100 Subject: [PATCH 07/41] Implement new logic for recently used tags --- src/libs/actions/IOU.js | 2 -- src/libs/actions/Policy.ts | 24 ++++++++++++++++-------- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index ef20a3f13a8..b856193ef90 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -754,9 +754,7 @@ function getMoneyRequestInformation( ); const optimisticPolicyRecentlyUsedCategories = Policy.buildOptimisticPolicyRecentlyUsedCategories(iouReport.policyID, category); - // TODO: Adapt const optimisticPolicyRecentlyUsedTags = Policy.buildOptimisticPolicyRecentlyUsedTags(iouReport.policyID, tag); - // const optimisticPolicyRecentlyUsedTags = {}; // If there is an existing transaction (which is the case for distance requests), then the data from the existing transaction // needs to be manually merged into the optimistic transaction. This is because buildOnyxDataForMoneyRequest() uses `Onyx.set()` for the transaction diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts index cbbc00dd42f..0a97dd054ed 100644 --- a/src/libs/actions/Policy.ts +++ b/src/libs/actions/Policy.ts @@ -1588,20 +1588,28 @@ function buildOptimisticPolicyRecentlyUsedCategories(policyID: string, category: return lodashUnion([category], policyRecentlyUsedCategories); } -function buildOptimisticPolicyRecentlyUsedTags(policyID: string, tag: string): RecentlyUsedTags { - if (!policyID || !tag) { +function buildOptimisticPolicyRecentlyUsedTags(policyID: string, reportTags: string): RecentlyUsedTags { + if (!policyID || !reportTags) { return {}; } + const splittedReportTags = reportTags.split(CONST.COLON); const policyTags = allPolicyTags?.[`${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`] ?? {}; - // For now it only uses the first tag of the policy, since multi-tags are not yet supported - const tagListKey = Object.keys(policyTags)[0]; + const policyTagKeys = Object.keys(policyTags); const policyRecentlyUsedTags = allRecentlyUsedTags?.[`${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_TAGS}${policyID}`] ?? {}; + const newOptimisticPolicyRecentlyUsedTags: Record = {}; - return { - ...policyRecentlyUsedTags, - [tagListKey]: lodashUnion([tag], policyRecentlyUsedTags?.[tagListKey] ?? []), - }; + splittedReportTags.forEach((tag, index) => { + if (!tag) { + return; + } + + const tagListKey = policyTagKeys[index]; + const prevRecentlyUsedTags = policyRecentlyUsedTags[tagListKey] ?? []; + newOptimisticPolicyRecentlyUsedTags[tagListKey] = lodashUnion([tag], prevRecentlyUsedTags); + }); + + return newOptimisticPolicyRecentlyUsedTags; } /** From c3f51167fd7b9e115eff84886d4a3041b832eac8 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Wed, 24 Jan 2024 16:50:41 +0100 Subject: [PATCH 08/41] integrate insertTagIntoReportTagsSting --- src/libs/IOUUtils.ts | 16 +++++++++++++++- src/libs/actions/IOU.js | 11 ++++------- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/src/libs/IOUUtils.ts b/src/libs/IOUUtils.ts index 11dd0f5badd..701f8d5e689 100644 --- a/src/libs/IOUUtils.ts +++ b/src/libs/IOUUtils.ts @@ -127,4 +127,18 @@ function isValidMoneyRequestType(iouType: string): boolean { return moneyRequestType.includes(iouType); } -export {calculateAmount, updateIOUOwnerAndTotal, isIOUReportPendingCurrencyConversion, isValidMoneyRequestType, navigateToStartMoneyRequestStep, navigateToStartStepIfScanFileCannotBeRead}; +function insertTagIntoReportTagsSting(reportTags: string, tag: string, tagIndex: number): string { + const splittedReportTags = reportTags.split(CONST.COLON); + splittedReportTags[tagIndex] = tag; + return splittedReportTags.join(CONST.COLON).replace(/:*$/, ''); +} + +export { + calculateAmount, + updateIOUOwnerAndTotal, + isIOUReportPendingCurrencyConversion, + isValidMoneyRequestType, + navigateToStartMoneyRequestStep, + navigateToStartStepIfScanFileCannotBeRead, + insertTagIntoReportTagsSting, +}; diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index b856193ef90..2775d23aefc 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -236,22 +236,19 @@ function resetMoneyRequestCategory_temporaryForRefactor(transactionID) { * @param {Number} tagIndex */ function setMoneyRequestTag_temporaryForRefactor(transactionID, reportTags, tag, tagIndex) { - const splittedReportTags = reportTags.split(CONST.COLON); - splittedReportTags[tagIndex] = tag; - Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`, { - tag: splittedReportTags.join(CONST.COLON), + tag: IOUUtils.insertTagIntoReportTagsSting(reportTags, tag, tagIndex), }); } /* * @param {String} transactionID * @param {String} reportTags - * @param {String} tag + * @param {Number} tagIndex */ -function resetMoneyRequestTag_temporaryForRefactor(transactionID, reportTags, tag) { +function resetMoneyRequestTag_temporaryForRefactor(transactionID, reportTags, tagIndex) { Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`, { - tag: reportTags.replace(tag, '').replace(/:*$/, ''), + tag: IOUUtils.insertTagIntoReportTagsSting(reportTags, '', tagIndex), }); } From be603aacca037c3e6421dea8308dc546f88158da Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Wed, 24 Jan 2024 16:51:02 +0100 Subject: [PATCH 09/41] remove fallback value --- 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 c1252b5c5db..c532a07305c 100644 --- a/src/components/ReportActionItem/MoneyRequestView.js +++ b/src/components/ReportActionItem/MoneyRequestView.js @@ -354,7 +354,7 @@ function MoneyRequestView({report, parentReport, parentReportActions, policyCate > Date: Wed, 24 Jan 2024 16:51:25 +0100 Subject: [PATCH 10/41] integrate editing --- src/pages/EditRequestPage.js | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/pages/EditRequestPage.js b/src/pages/EditRequestPage.js index 52fb54e1908..0d4c51fc2f4 100644 --- a/src/pages/EditRequestPage.js +++ b/src/pages/EditRequestPage.js @@ -11,6 +11,7 @@ import tagPropTypes from '@components/tagPropTypes'; import transactionPropTypes from '@components/transactionPropTypes'; import compose from '@libs/compose'; import * as CurrencyUtils from '@libs/CurrencyUtils'; +import * as IOUUtils from '@libs/IOUUtils'; import Navigation from '@libs/Navigation/Navigation'; import * as OptionsListUtils from '@libs/OptionsListUtils'; import * as PolicyUtils from '@libs/PolicyUtils'; @@ -81,7 +82,7 @@ function EditRequestPage({report, route, policyCategories, policyTags, parentRep comment: transactionDescription, merchant: transactionMerchant, category: transactionCategory, - // tag: reportTags, + tag: reportTags, } = ReportUtils.getTransactionDetails(transaction); const defaultCurrency = lodashGet(route, 'params.currency', '') || transactionCurrency; @@ -89,10 +90,10 @@ function EditRequestPage({report, route, policyCategories, policyTags, parentRep const rawTagIndex = lodashGet(route, ['params', 'tagIndex'], undefined); const tagIndex = +rawTagIndex; - const transactionTag = TransactionUtils.getTag(transaction, +tagIndex); + const tag = TransactionUtils.getTag(transaction, tagIndex); const policyTagList = useMemo(() => PolicyUtils.getTagLists(policyTags), [policyTags]); const policyTagValueList = useMemo(() => _.flatten(_.map(policyTagList, ({tags}) => _.values(tags))), [policyTagList]); - const policyTagListName = PolicyUtils.getTagListName(policyTags, +tagIndex); + const policyTagListName = PolicyUtils.getTagListName(policyTags, tagIndex); // A flag for verifying that the current report is a sub-report of a workspace chat const isPolicyExpenseChat = ReportUtils.isGroupPolicy(report); @@ -101,7 +102,7 @@ function EditRequestPage({report, route, policyCategories, policyTags, parentRep const shouldShowCategories = isPolicyExpenseChat && (transactionCategory || OptionsListUtils.hasEnabledOptions(lodashValues(policyCategories))); // A flag for showing the tags page - const shouldShowTags = isPolicyExpenseChat && (transactionTag || OptionsListUtils.hasEnabledOptions(policyTagValueList)); + const shouldShowTags = isPolicyExpenseChat && (reportTags || OptionsListUtils.hasEnabledOptions(policyTagValueList)); // Decides whether to allow or disallow editing a money request useEffect(() => { @@ -166,14 +167,14 @@ function EditRequestPage({report, route, policyCategories, policyTags, parentRep const saveTag = useCallback( ({tag: newTag}) => { let updatedTag = newTag; - if (newTag === transactionTag) { + if (newTag === tag) { // In case the same tag has been selected, reset the tag. updatedTag = ''; } - IOU.updateMoneyRequestTag(transaction.transactionID, report.reportID, updatedTag); + IOU.updateMoneyRequestTag(transaction.transactionID, report.reportID, IOUUtils.insertTagIntoReportTagsSting(reportTags, updatedTag, tagIndex)); Navigation.dismissModal(); }, - [transactionTag, transaction.transactionID, report.reportID], + [tag, reportTags, tagIndex, transaction.transactionID, report.reportID], ); const saveCategory = useCallback( @@ -253,7 +254,7 @@ function EditRequestPage({report, route, policyCategories, policyTags, parentRep if (fieldToEdit === CONST.EDIT_REQUEST_FIELD.TAG && shouldShowTags) { return ( Date: Wed, 24 Jan 2024 16:51:34 +0100 Subject: [PATCH 11/41] use updated reset --- src/pages/iou/request/step/IOURequestStepTag.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/iou/request/step/IOURequestStepTag.js b/src/pages/iou/request/step/IOURequestStepTag.js index f272042ff16..76609d2a818 100644 --- a/src/pages/iou/request/step/IOURequestStepTag.js +++ b/src/pages/iou/request/step/IOURequestStepTag.js @@ -66,7 +66,7 @@ function IOURequestStepTag({ */ const updateTag = (selectedTag) => { if (tag === selectedTag.searchText) { - IOU.resetMoneyRequestTag_temporaryForRefactor(transactionID, reportTags, selectedTag.searchText); + IOU.resetMoneyRequestTag_temporaryForRefactor(transactionID, reportTags, tagIndex); } else { IOU.setMoneyRequestTag_temporaryForRefactor(transactionID, reportTags, selectedTag.searchText, tagIndex); } From 66f2ab79cfe39b851e87df270a117f50b6dd384b Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Wed, 24 Jan 2024 16:53:00 +0100 Subject: [PATCH 12/41] todo added --- src/libs/Violations/ViolationsUtils.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libs/Violations/ViolationsUtils.ts b/src/libs/Violations/ViolationsUtils.ts index c0558d7487c..9998939f7b7 100644 --- a/src/libs/Violations/ViolationsUtils.ts +++ b/src/libs/Violations/ViolationsUtils.ts @@ -53,7 +53,8 @@ const ViolationsUtils = { if (policyRequiresTags) { const hasTagOutOfPolicyViolation = transactionViolations.some((violation) => violation.name === 'tagOutOfPolicy'); const hasMissingTagViolation = transactionViolations.some((violation) => violation.name === 'missingTag'); - const isTagInPolicy = Boolean(policyTags[transaction.tag]?.enabled); + // TODO: Implement + const isTagInPolicy = false; // Boolean(policyTags[transaction.tag]?.enabled); // Add 'tagOutOfPolicy' violation if tag is not in policy if (!hasTagOutOfPolicyViolation && transaction.tag && !isTagInPolicy) { From 3e6afa8e9050f77b970147c33510ef13c6c28114 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Fri, 26 Jan 2024 19:04:38 +0100 Subject: [PATCH 13/41] prettify routes --- src/ROUTES.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ROUTES.ts b/src/ROUTES.ts index d1b24c14a72..b21109436ca 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -167,7 +167,7 @@ const ROUTES = { EDIT_REQUEST: { route: 'r/:threadReportID/edit/:field/:tagIndex?', getRoute: (threadReportID: string, field: ValueOf, tagIndex?: number) => - `r/${threadReportID}/edit/${field}${tagIndex !== undefined ? `/${tagIndex}` : ''}` as const, + `r/${threadReportID}/edit/${field}${typeof tagIndex === 'number' ? `/${tagIndex}` : ''}` as const, }, EDIT_CURRENCY_REQUEST: { route: 'r/:threadReportID/edit/currency', From eab5b730ecdb63c88c5e454c7ffed72b1317e7ca Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Fri, 26 Jan 2024 19:04:45 +0100 Subject: [PATCH 14/41] clarify type --- src/types/onyx/PolicyTag.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types/onyx/PolicyTag.ts b/src/types/onyx/PolicyTag.ts index 8765cff5c57..539a73953b0 100644 --- a/src/types/onyx/PolicyTag.ts +++ b/src/types/onyx/PolicyTag.ts @@ -13,7 +13,7 @@ type PolicyTag = { type PolicyTagList = { name: string; required: boolean; - tags: PolicyTags; + tags: Record; }; type PolicyTags = Record; From a2005386f76e8b69763a5ae9e1cd5bbfdbf98a22 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Fri, 26 Jan 2024 19:06:24 +0100 Subject: [PATCH 15/41] integrate hasEnabledTags --- ...oraryForRefactorRequestConfirmationList.js | 7 +++-- src/libs/OptionsListUtils.ts | 27 +++++++++++++++++-- src/libs/PolicyUtils.ts | 9 ++++--- src/pages/EditRequestPage.js | 6 ++--- 4 files changed, 36 insertions(+), 13 deletions(-) diff --git a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js index 4de2db1a9ef..9d543a95883 100755 --- a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js +++ b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js @@ -264,11 +264,10 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ const shouldShowDate = shouldShowSmartScanFields || isDistanceRequest; const shouldShowMerchant = shouldShowSmartScanFields && !isDistanceRequest; - const policyTagList = useMemo(() => PolicyUtils.getTagLists(policyTags), [policyTags]); - const policyTagValueList = useMemo(() => _.flatten(_.map(policyTagList, ({tags}) => _.values(tags))), [policyTagList]); + const policyTagLists = useMemo(() => PolicyUtils.getTagLists(policyTags), [policyTags]); // A flag for showing the tags field - const shouldShowTags = isPolicyExpenseChat && OptionsListUtils.hasEnabledOptions(policyTagValueList); + const shouldShowTags = useMemo(() => isPolicyExpenseChat && OptionsListUtils.hasEnabledTags(policyTagLists), [isPolicyExpenseChat, policyTagLists]); // A flag for showing tax rate const shouldShowTax = isPolicyExpenseChat && policy && policy.isTaxTrackingEnabled; @@ -804,7 +803,7 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ /> )} {shouldShowTags && - _.map(policyTagList, ({name}, index) => ( + _.map(policyTagLists, ({name}, index) => ( option.enabled); } @@ -1130,6 +1144,14 @@ function getTagListSections(tags: Tag[], recentlyUsedTags: string[], selectedOpt return tagSections; } +/** + * Checks if there is one enabled tag at least + */ +function hasEnabledTags(policyTagLists: PolicyTagList[]) { + const policyTagValueList = policyTagLists.map(({tags}) => Object.values(tags)).flat(); + return hasEnabledOptions(policyTagValueList); +} + type PolicyTaxRateWithDefault = { name: string; defaultExternalID: string; @@ -1989,6 +2011,7 @@ export { hasEnabledOptions, sortCategories, getCategoryOptionTree, + hasEnabledTags, formatMemberForList, formatSectionsFromSearchTerm, transformedTaxRates, diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts index e3630d90672..55e17e00579 100644 --- a/src/libs/PolicyUtils.ts +++ b/src/libs/PolicyUtils.ts @@ -4,7 +4,6 @@ import type {ValueOf} from 'type-fest'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {PersonalDetailsList, Policy, PolicyMembers, PolicyTagList, PolicyTags} from '@src/types/onyx'; -import type {EmptyObject} from '@src/types/utils/EmptyObject'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; type MemberEmailsToAccountIDs = Record; @@ -183,11 +182,15 @@ function getTagLists(policyTags: OnyxCollection): PolicyTagList[] /** * Gets a tag list of a policy by a tag index */ -function getTagList(policyTags: OnyxCollection, tagIndex: number): PolicyTagList | EmptyObject { +function getTagList(policyTags: OnyxCollection, tagIndex: number): PolicyTagList { const tagLists = getTagLists(policyTags); if (tagLists.length === 0) { - return {}; + return { + name: '', + required: false, + tags: {}, + }; } return tagLists[tagIndex]; diff --git a/src/pages/EditRequestPage.js b/src/pages/EditRequestPage.js index 0d4c51fc2f4..559693f18ab 100644 --- a/src/pages/EditRequestPage.js +++ b/src/pages/EditRequestPage.js @@ -3,7 +3,6 @@ import lodashValues from 'lodash/values'; import PropTypes from 'prop-types'; import React, {useCallback, useEffect, useMemo} from 'react'; import {withOnyx} from 'react-native-onyx'; -import _ from 'underscore'; import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; import categoryPropTypes from '@components/categoryPropTypes'; import ScreenWrapper from '@components/ScreenWrapper'; @@ -91,9 +90,8 @@ function EditRequestPage({report, route, policyCategories, policyTags, parentRep const tagIndex = +rawTagIndex; const tag = TransactionUtils.getTag(transaction, tagIndex); - const policyTagList = useMemo(() => PolicyUtils.getTagLists(policyTags), [policyTags]); - const policyTagValueList = useMemo(() => _.flatten(_.map(policyTagList, ({tags}) => _.values(tags))), [policyTagList]); const policyTagListName = PolicyUtils.getTagListName(policyTags, tagIndex); + const policyTagLists = useMemo(() => PolicyUtils.getTagLists(policyTags), [policyTags]); // A flag for verifying that the current report is a sub-report of a workspace chat const isPolicyExpenseChat = ReportUtils.isGroupPolicy(report); @@ -102,7 +100,7 @@ function EditRequestPage({report, route, policyCategories, policyTags, parentRep const shouldShowCategories = isPolicyExpenseChat && (transactionCategory || OptionsListUtils.hasEnabledOptions(lodashValues(policyCategories))); // A flag for showing the tags page - const shouldShowTags = isPolicyExpenseChat && (reportTags || OptionsListUtils.hasEnabledOptions(policyTagValueList)); + const shouldShowTags = useMemo(() => isPolicyExpenseChat && (reportTags || OptionsListUtils.hasEnabledTags(policyTagLists)), [isPolicyExpenseChat, policyTagLists, reportTags]); // Decides whether to allow or disallow editing a money request useEffect(() => { From f8e2512a77831ea453215418cfe5717385013d22 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Sat, 27 Jan 2024 11:43:05 +0100 Subject: [PATCH 16/41] integrate violations for tags --- .../ReportActionItem/MoneyRequestView.js | 18 ++++- src/hooks/useViolations.ts | 17 ++++- src/libs/Violations/ViolationsUtils.ts | 70 +++++++++++++------ 3 files changed, 81 insertions(+), 24 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestView.js b/src/components/ReportActionItem/MoneyRequestView.js index c532a07305c..478a4b8b6cf 100644 --- a/src/components/ReportActionItem/MoneyRequestView.js +++ b/src/components/ReportActionItem/MoneyRequestView.js @@ -175,7 +175,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]); + const hasViolations = useCallback((field, data) => canUseViolations && getViolationsForField(field, data).length > 0, [canUseViolations, getViolationsForField]); let amountDescription = `${translate('iou.amount')}`; @@ -359,9 +359,21 @@ 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, index))} - brickRoadIndicator={hasViolations('tag') ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : ''} + brickRoadIndicator={ + hasViolations('tag', { + tagName: name, + }) + ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR + : '' + } /> - {canUseViolations && } + {canUseViolations && ( + + )} ))} {isCardTransaction && ( diff --git a/src/hooks/useViolations.ts b/src/hooks/useViolations.ts index 76d48158237..3b34d4e6f19 100644 --- a/src/hooks/useViolations.ts +++ b/src/hooks/useViolations.ts @@ -60,7 +60,22 @@ function useViolations(violations: TransactionViolation[]) { return violationGroups ?? new Map(); }, [violations]); - const getViolationsForField = useCallback((field: ViolationField) => violationsByField.get(field) ?? [], [violationsByField]); + const getViolationsForField = useCallback( + (field: ViolationField, data: TransactionViolation['data']) => { + const currentViolations = violationsByField.get(field); + + if (!currentViolations) { + return []; + } + + if (data?.tagName) { + return currentViolations.filter((violation) => violation.data?.tagName === data.tagName); + } + + return currentViolations; + }, + [violationsByField], + ); return { getViolationsForField, diff --git a/src/libs/Violations/ViolationsUtils.ts b/src/libs/Violations/ViolationsUtils.ts index 9998939f7b7..299dffd0b2d 100644 --- a/src/libs/Violations/ViolationsUtils.ts +++ b/src/libs/Violations/ViolationsUtils.ts @@ -1,6 +1,7 @@ import reject from 'lodash/reject'; import Onyx from 'react-native-onyx'; import type {Phrase, PhraseParameters} from '@libs/Localize'; +import CONST from '@src/CONST'; import type {TranslationPaths} from '@src/languages/types'; import ONYXKEYS from '@src/ONYXKEYS'; import type {PolicyCategories, PolicyTags, Transaction, TransactionViolation} from '@src/types/onyx'; @@ -51,30 +52,59 @@ const ViolationsUtils = { } if (policyRequiresTags) { - const hasTagOutOfPolicyViolation = transactionViolations.some((violation) => violation.name === 'tagOutOfPolicy'); - const hasMissingTagViolation = transactionViolations.some((violation) => violation.name === 'missingTag'); - // TODO: Implement - const isTagInPolicy = false; // Boolean(policyTags[transaction.tag]?.enabled); + const selectedTags = transaction.tag.split(CONST.COLON); + const policyTagKeys = Object.keys(policyTags); - // Add 'tagOutOfPolicy' violation if tag is not in policy - if (!hasTagOutOfPolicyViolation && transaction.tag && !isTagInPolicy) { - newTransactionViolations.push({name: 'tagOutOfPolicy', type: 'violation', userMessage: ''}); - } + policyTagKeys.forEach((key, index) => { + const hasTagOutOfPolicyViolation = transactionViolations.some((violation) => violation.name === 'tagOutOfPolicy' && violation.data?.tagName === key); + const hasMissingTagViolation = transactionViolations.some((violation) => violation.name === 'missingTag' && violation.data?.tagName === key); + const selectedTag = selectedTags[index]; + const isTagInPolicy = Boolean(policyTags[key]?.tags[selectedTag]?.enabled); - // Remove 'tagOutOfPolicy' violation if tag is in policy - if (hasTagOutOfPolicyViolation && transaction.tag && isTagInPolicy) { - newTransactionViolations = reject(newTransactionViolations, {name: 'tagOutOfPolicy'}); - } + // Add 'tagOutOfPolicy' violation if tag is not in policy + if (!hasTagOutOfPolicyViolation && selectedTag && !isTagInPolicy) { + newTransactionViolations.push({ + name: 'tagOutOfPolicy', + type: 'violation', + userMessage: '', + data: { + tagName: key, + }, + }); + } - // Remove 'missingTag' violation if tag is valid according to policy - if (hasMissingTagViolation && isTagInPolicy) { - newTransactionViolations = reject(newTransactionViolations, {name: 'missingTag'}); - } + // Remove 'tagOutOfPolicy' violation if tag is in policy + if (hasTagOutOfPolicyViolation && selectedTag && isTagInPolicy) { + newTransactionViolations = reject(newTransactionViolations, { + name: 'tagOutOfPolicy', + data: { + tagName: key, + }, + }); + } - // Add 'missingTag violation' if tag is required and not set - if (!hasMissingTagViolation && !transaction.tag && policyRequiresTags) { - newTransactionViolations.push({name: 'missingTag', type: 'violation', userMessage: ''}); - } + // Remove 'missingTag' violation if tag is valid according to policy + if (hasMissingTagViolation && isTagInPolicy) { + newTransactionViolations = reject(newTransactionViolations, { + name: 'missingTag', + data: { + tagName: key, + }, + }); + } + + // Add 'missingTag violation' if tag is required and not set + if (!hasMissingTagViolation && !selectedTag && policyRequiresTags) { + newTransactionViolations.push({ + name: 'missingTag', + type: 'violation', + userMessage: '', + data: { + tagName: key, + }, + }); + } + }); } return { From cf53298842cfff87f28b8d918a095445f1012ece Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Mon, 29 Jan 2024 14:33:27 +0100 Subject: [PATCH 17/41] integrate getCountOfEnabledTagsOfList --- src/components/TagPicker/index.js | 2 +- src/libs/PolicyUtils.ts | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/components/TagPicker/index.js b/src/components/TagPicker/index.js index 9989447f911..94b91c66f15 100644 --- a/src/components/TagPicker/index.js +++ b/src/components/TagPicker/index.js @@ -20,7 +20,7 @@ function TagPicker({selectedTag, tag, tagIndex, policyTags, policyRecentlyUsedTa const policyRecentlyUsedTagsList = lodashGet(policyRecentlyUsedTags, tag, []); const policyTagList = PolicyUtils.getTagList(policyTags, tagIndex); - const policyTagsCount = _.size(_.filter(policyTagList.tags, (policyTag) => policyTag.enabled)); + const policyTagsCount = PolicyUtils.getCountOfEnabledTagsOfList(policyTagList); const isTagsCountBelowThreshold = policyTagsCount < CONST.TAG_LIST_THRESHOLD; const shouldShowTextInput = !isTagsCountBelowThreshold; diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts index 55e17e00579..9b327b11e54 100644 --- a/src/libs/PolicyUtils.ts +++ b/src/libs/PolicyUtils.ts @@ -203,6 +203,13 @@ function getCleanedTagName(tag: string) { return tag?.replace(/\\{1,2}:/g, ':'); } +/** + * Gets a count of enabled tags of a policy + */ +function getCountOfEnabledTagsOfList(policyTagList: PolicyTagList) { + return Object.values(policyTagList.tags).filter((policyTag) => policyTag.enabled).length; +} + function isPendingDeletePolicy(policy: OnyxEntry): boolean { return policy?.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE; } @@ -230,6 +237,7 @@ export { getTagListName, getTagList, getCleanedTagName, + getCountOfEnabledTagsOfList, isPendingDeletePolicy, isPolicyMember, isPaidGroupPolicy, From 0e13b3c619ebdc11adc2f488b07f3ee08bd690e8 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Mon, 29 Jan 2024 14:33:41 +0100 Subject: [PATCH 18/41] use hasEnabledTags --- src/components/ReportActionItem/MoneyRequestView.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestView.js b/src/components/ReportActionItem/MoneyRequestView.js index 478a4b8b6cf..6a61272c919 100644 --- a/src/components/ReportActionItem/MoneyRequestView.js +++ b/src/components/ReportActionItem/MoneyRequestView.js @@ -167,11 +167,10 @@ function MoneyRequestView({report, parentReport, parentReportActions, policyCate const isPolicyExpenseChat = ReportUtils.isGroupPolicy(report); const policyTagList = useMemo(() => PolicyUtils.getTagLists(policyTags), [policyTags]); - const policyTagValueList = useMemo(() => _.flatten(_.map(policyTagList, ({tags}) => _.values(tags))), [policyTagList]); // Flags for showing categories and tags const shouldShowCategory = isPolicyExpenseChat && (transactionCategory || OptionsListUtils.hasEnabledOptions(lodashValues(policyCategories))); - const shouldShowTag = isPolicyExpenseChat && (transactionTag || OptionsListUtils.hasEnabledOptions(lodashValues(policyTagValueList))); + const shouldShowTag = useMemo(() => isPolicyExpenseChat && (transactionTag || OptionsListUtils.hasEnabledTags(policyTagList)), [isPolicyExpenseChat, policyTagList, transactionTag]); const shouldShowBillable = isPolicyExpenseChat && (transactionBillable || !lodashGet(policy, 'disabledFields.defaultBillable', true)); const {getViolationsForField} = useViolations(transactionViolations); From 9b13a171eed529e40c843c068b74e6ee9af8951e Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Mon, 29 Jan 2024 14:34:32 +0100 Subject: [PATCH 19/41] clarify comment --- src/components/TagPicker/tagPickerPropTypes.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/TagPicker/tagPickerPropTypes.js b/src/components/TagPicker/tagPickerPropTypes.js index d74bad59a55..cbdc73f5d05 100644 --- a/src/components/TagPicker/tagPickerPropTypes.js +++ b/src/components/TagPicker/tagPickerPropTypes.js @@ -12,7 +12,7 @@ const propTypes = { /** The name of tag list we are getting tags for */ tag: PropTypes.string.isRequired, - // TODO: Comment + /** The index of a tag list */ tagIndex: PropTypes.number.isRequired, /** Callback to submit the selected tag */ From d6ccb1943b0038beb4b71bb48b973b2a314dd133 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Mon, 29 Jan 2024 14:35:50 +0100 Subject: [PATCH 20/41] minor improvement --- src/hooks/useViolations.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/hooks/useViolations.ts b/src/hooks/useViolations.ts index 3b34d4e6f19..db55d960998 100644 --- a/src/hooks/useViolations.ts +++ b/src/hooks/useViolations.ts @@ -62,11 +62,7 @@ function useViolations(violations: TransactionViolation[]) { const getViolationsForField = useCallback( (field: ViolationField, data: TransactionViolation['data']) => { - const currentViolations = violationsByField.get(field); - - if (!currentViolations) { - return []; - } + const currentViolations = violationsByField.get(field) ?? []; if (data?.tagName) { return currentViolations.filter((violation) => violation.data?.tagName === data.tagName); From 8540bf625ffc40e65c167d174ec748f008ea30da Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Mon, 29 Jan 2024 14:39:39 +0100 Subject: [PATCH 21/41] clarify comment --- src/libs/IOUUtils.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/libs/IOUUtils.ts b/src/libs/IOUUtils.ts index 93b262eaaf3..c35817b0875 100644 --- a/src/libs/IOUUtils.ts +++ b/src/libs/IOUUtils.ts @@ -99,9 +99,18 @@ function isValidMoneyRequestType(iouType: string): boolean { return moneyRequestType.includes(iouType); } +/** + * Inserts a newly selected tag into the already existed report tags like a string + * + * @param reportTags - currently selected tags for a report + * @param tag - a newly selected tag, that should be added to the reportTags + * @param tagIndex - the index of a tag list + * @returns + */ function insertTagIntoReportTagsSting(reportTags: string, tag: string, tagIndex: number): string { const splittedReportTags = reportTags.split(CONST.COLON); splittedReportTags[tagIndex] = tag; + return splittedReportTags.join(CONST.COLON).replace(/:*$/, ''); } From 258caa79329bcb562bd0f900d0e5d0489e3b1baf Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Mon, 29 Jan 2024 14:43:44 +0100 Subject: [PATCH 22/41] clarify comment --- src/libs/OptionsListUtils.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts index a1aafecdef2..57b32a11b8a 100644 --- a/src/libs/OptionsListUtils.ts +++ b/src/libs/OptionsListUtils.ts @@ -1145,10 +1145,11 @@ function getTagListSections(tags: Tag[], recentlyUsedTags: string[], selectedOpt } /** - * Checks if there is one enabled tag at least + * Verifies that there is at least one enabled tag */ function hasEnabledTags(policyTagLists: PolicyTagList[]) { const policyTagValueList = policyTagLists.map(({tags}) => Object.values(tags)).flat(); + return hasEnabledOptions(policyTagValueList); } From 00c5448f801154f1ed699eb620ef75cfd615b8bf Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Mon, 29 Jan 2024 14:54:58 +0100 Subject: [PATCH 23/41] minor improvement --- src/libs/PolicyUtils.ts | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts index 9b327b11e54..5478bb3fdc7 100644 --- a/src/libs/PolicyUtils.ts +++ b/src/libs/PolicyUtils.ts @@ -156,16 +156,17 @@ function getIneligibleInvitees(policyMembers: OnyxEntry, personal } /** - * Gets a tag name from policy tags based on a tag index. + * Gets a tag name of policy tags based on a tag index. */ function getTagListName(policyTags: OnyxEntry, tagIndex: number) { - if (Object.keys(policyTags ?? {})?.length === 0) { + if (isEmptyObject(policyTags)) { return ''; } - const policyTagKeys = Object.keys(policyTags ?? {})[tagIndex] ?? []; + const policyTagKeys = Object.keys(policyTags ?? {}); + const policyTagKey = policyTagKeys[tagIndex] ?? ''; - return policyTags?.[policyTagKeys]?.name ?? ''; + return policyTags?.[policyTagKey]?.name ?? ''; } /** @@ -185,15 +186,13 @@ function getTagLists(policyTags: OnyxCollection): PolicyTagList[] function getTagList(policyTags: OnyxCollection, tagIndex: number): PolicyTagList { const tagLists = getTagLists(policyTags); - if (tagLists.length === 0) { - return { + return ( + tagLists[tagIndex] ?? { name: '', required: false, tags: {}, - }; - } - - return tagLists[tagIndex]; + } + ); } /** From 9d9d5bd57473b3a7ffc6d764b8fb5fd07da86687 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Mon, 29 Jan 2024 14:58:38 +0100 Subject: [PATCH 24/41] clarify comment --- src/libs/TransactionUtils.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/libs/TransactionUtils.ts b/src/libs/TransactionUtils.ts index 060d77c8d4d..04ba862745d 100644 --- a/src/libs/TransactionUtils.ts +++ b/src/libs/TransactionUtils.ts @@ -362,11 +362,12 @@ function getBillable(transaction: OnyxEntry): boolean { } /** - * Return the tag from the transaction. This "tag" field has no "modified" complement. + * Return the tag from the transaction. When the tagIndex is passed, return the tag based on the index. + * This "tag" field has no "modified" complement. */ function getTag(transaction: OnyxEntry, tagIndex?: number): string { if (tagIndex !== undefined) { - return transaction?.tag?.split(':')[tagIndex] ?? ''; + return transaction?.tag?.split(CONST.COLON)[tagIndex] ?? ''; } return transaction?.tag ?? ''; From 9614236fdc542859319261c0eddb8bb380981543 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Mon, 29 Jan 2024 15:05:17 +0100 Subject: [PATCH 25/41] minor improvement --- src/libs/actions/Policy.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts index dd643164adc..4cc48d2fefa 100644 --- a/src/libs/actions/Policy.ts +++ b/src/libs/actions/Policy.ts @@ -1534,20 +1534,18 @@ function buildOptimisticPolicyRecentlyUsedTags(policyID: string, reportTags: str return {}; } - const splittedReportTags = reportTags.split(CONST.COLON); const policyTags = allPolicyTags?.[`${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`] ?? {}; const policyTagKeys = Object.keys(policyTags); const policyRecentlyUsedTags = allRecentlyUsedTags?.[`${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_TAGS}${policyID}`] ?? {}; - const newOptimisticPolicyRecentlyUsedTags: Record = {}; + const newOptimisticPolicyRecentlyUsedTags: RecentlyUsedTags = {}; - splittedReportTags.forEach((tag, index) => { + reportTags.split(CONST.COLON).forEach((tag, index) => { if (!tag) { return; } const tagListKey = policyTagKeys[index]; - const prevRecentlyUsedTags = policyRecentlyUsedTags[tagListKey] ?? []; - newOptimisticPolicyRecentlyUsedTags[tagListKey] = lodashUnion([tag], prevRecentlyUsedTags); + newOptimisticPolicyRecentlyUsedTags[tagListKey] = lodashUnion([tag], policyRecentlyUsedTags[tagListKey] ?? []); }); return newOptimisticPolicyRecentlyUsedTags; From d134ce71106a392d994109cfb6531ba9f620fc17 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Mon, 29 Jan 2024 15:21:40 +0100 Subject: [PATCH 26/41] minor improvement --- src/pages/EditRequestPage.js | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/pages/EditRequestPage.js b/src/pages/EditRequestPage.js index 559693f18ab..3acdc830f32 100644 --- a/src/pages/EditRequestPage.js +++ b/src/pages/EditRequestPage.js @@ -42,7 +42,7 @@ const propTypes = { /** reportID for the "transaction thread" */ threadReportID: PropTypes.string, - // TODO: Comment + /** The index of a tag list */ tagIndex: PropTypes.string, }), }).isRequired, @@ -81,13 +81,12 @@ function EditRequestPage({report, route, policyCategories, policyTags, parentRep comment: transactionDescription, merchant: transactionMerchant, category: transactionCategory, - tag: reportTags, + tag: transactionTag, } = ReportUtils.getTransactionDetails(transaction); const defaultCurrency = lodashGet(route, 'params.currency', '') || transactionCurrency; const fieldToEdit = lodashGet(route, ['params', 'field'], ''); - const rawTagIndex = lodashGet(route, ['params', 'tagIndex'], undefined); - const tagIndex = +rawTagIndex; + const tagIndex = Number(lodashGet(route, ['params', 'tagIndex'], undefined)); const tag = TransactionUtils.getTag(transaction, tagIndex); const policyTagListName = PolicyUtils.getTagListName(policyTags, tagIndex); @@ -100,7 +99,7 @@ function EditRequestPage({report, route, policyCategories, policyTags, parentRep const shouldShowCategories = isPolicyExpenseChat && (transactionCategory || OptionsListUtils.hasEnabledOptions(lodashValues(policyCategories))); // A flag for showing the tags page - const shouldShowTags = useMemo(() => isPolicyExpenseChat && (reportTags || OptionsListUtils.hasEnabledTags(policyTagLists)), [isPolicyExpenseChat, policyTagLists, reportTags]); + const shouldShowTags = useMemo(() => isPolicyExpenseChat && (transactionTag || OptionsListUtils.hasEnabledTags(policyTagLists)), [isPolicyExpenseChat, policyTagLists, transactionTag]); // Decides whether to allow or disallow editing a money request useEffect(() => { @@ -169,10 +168,10 @@ function EditRequestPage({report, route, policyCategories, policyTags, parentRep // In case the same tag has been selected, reset the tag. updatedTag = ''; } - IOU.updateMoneyRequestTag(transaction.transactionID, report.reportID, IOUUtils.insertTagIntoReportTagsSting(reportTags, updatedTag, tagIndex)); + IOU.updateMoneyRequestTag(transaction.transactionID, report.reportID, IOUUtils.insertTagIntoReportTagsSting(transactionTag, updatedTag, tagIndex)); Navigation.dismissModal(); }, - [tag, reportTags, tagIndex, transaction.transactionID, report.reportID], + [tag, transactionTag, tagIndex, transaction.transactionID, report.reportID], ); const saveCategory = useCallback( From b179b66a469e1e9247acd094dfc175da9ee56883 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Mon, 29 Jan 2024 15:22:08 +0100 Subject: [PATCH 27/41] clarify comment --- src/pages/EditRequestTagPage.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/EditRequestTagPage.js b/src/pages/EditRequestTagPage.js index 398b6adc39d..080d3d0acbf 100644 --- a/src/pages/EditRequestTagPage.js +++ b/src/pages/EditRequestTagPage.js @@ -18,7 +18,7 @@ const propTypes = { /** The tag name to which the default tag belongs to */ tagName: PropTypes.string.isRequired, - // TODO: Comment + /** The index of a tag list */ tagIndex: PropTypes.number.isRequired, /** Callback to fire when the Save button is pressed */ From 54c10dd3d0367f724697463ea634a3993e2c0170 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Mon, 29 Jan 2024 15:25:41 +0100 Subject: [PATCH 28/41] minor improvement --- src/pages/iou/request/step/IOURequestStepTag.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/pages/iou/request/step/IOURequestStepTag.js b/src/pages/iou/request/step/IOURequestStepTag.js index 76609d2a818..d284ce7ccc4 100644 --- a/src/pages/iou/request/step/IOURequestStepTag.js +++ b/src/pages/iou/request/step/IOURequestStepTag.js @@ -51,10 +51,10 @@ function IOURequestStepTag({ const styles = useThemeStyles(); const {translate} = useLocalize(); - const tagIndex = +rawTagIndex; - const reportTags = TransactionUtils.getTag(transaction); - const tag = TransactionUtils.getTag(transaction, tagIndex); + const tagIndex = Number(rawTagIndex); const policyTagListName = PolicyUtils.getTagListName(policyTags, tagIndex); + const transactionTag = TransactionUtils.getTag(transaction); + const tag = TransactionUtils.getTag(transaction, tagIndex); const navigateBack = () => { Navigation.goBack(backTo || ROUTES.HOME); @@ -66,9 +66,9 @@ function IOURequestStepTag({ */ const updateTag = (selectedTag) => { if (tag === selectedTag.searchText) { - IOU.resetMoneyRequestTag_temporaryForRefactor(transactionID, reportTags, tagIndex); + IOU.resetMoneyRequestTag_temporaryForRefactor(transactionID, transactionTag, tagIndex); } else { - IOU.setMoneyRequestTag_temporaryForRefactor(transactionID, reportTags, selectedTag.searchText, tagIndex); + IOU.setMoneyRequestTag_temporaryForRefactor(transactionID, transactionTag, selectedTag.searchText, tagIndex); } navigateBack(); }; From 2544666108ced40bded72319debeb124d5073b86 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Mon, 29 Jan 2024 15:29:33 +0100 Subject: [PATCH 29/41] minor improvement --- src/libs/Violations/ViolationsUtils.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/libs/Violations/ViolationsUtils.ts b/src/libs/Violations/ViolationsUtils.ts index 299dffd0b2d..5b81f3dae12 100644 --- a/src/libs/Violations/ViolationsUtils.ts +++ b/src/libs/Violations/ViolationsUtils.ts @@ -56,15 +56,15 @@ const ViolationsUtils = { const policyTagKeys = Object.keys(policyTags); policyTagKeys.forEach((key, index) => { - const hasTagOutOfPolicyViolation = transactionViolations.some((violation) => violation.name === 'tagOutOfPolicy' && violation.data?.tagName === key); - const hasMissingTagViolation = transactionViolations.some((violation) => violation.name === 'missingTag' && violation.data?.tagName === key); + const hasTagOutOfPolicyViolation = transactionViolations.some((violation) => violation.name === CONST.VIOLATIONS.TAG_OUT_OF_POLICY && violation.data?.tagName === key); + const hasMissingTagViolation = transactionViolations.some((violation) => violation.name === CONST.VIOLATIONS.MISSING_TAG && violation.data?.tagName === key); const selectedTag = selectedTags[index]; const isTagInPolicy = Boolean(policyTags[key]?.tags[selectedTag]?.enabled); // Add 'tagOutOfPolicy' violation if tag is not in policy if (!hasTagOutOfPolicyViolation && selectedTag && !isTagInPolicy) { newTransactionViolations.push({ - name: 'tagOutOfPolicy', + name: CONST.VIOLATIONS.TAG_OUT_OF_POLICY, type: 'violation', userMessage: '', data: { @@ -76,7 +76,7 @@ const ViolationsUtils = { // Remove 'tagOutOfPolicy' violation if tag is in policy if (hasTagOutOfPolicyViolation && selectedTag && isTagInPolicy) { newTransactionViolations = reject(newTransactionViolations, { - name: 'tagOutOfPolicy', + name: CONST.VIOLATIONS.TAG_OUT_OF_POLICY, data: { tagName: key, }, @@ -86,7 +86,7 @@ const ViolationsUtils = { // Remove 'missingTag' violation if tag is valid according to policy if (hasMissingTagViolation && isTagInPolicy) { newTransactionViolations = reject(newTransactionViolations, { - name: 'missingTag', + name: CONST.VIOLATIONS.MISSING_TAG, data: { tagName: key, }, @@ -96,7 +96,7 @@ const ViolationsUtils = { // Add 'missingTag violation' if tag is required and not set if (!hasMissingTagViolation && !selectedTag && policyRequiresTags) { newTransactionViolations.push({ - name: 'missingTag', + name: CONST.VIOLATIONS.MISSING_TAG, type: 'violation', userMessage: '', data: { @@ -113,6 +113,7 @@ const ViolationsUtils = { value: newTransactionViolations, }; }, + /** * Gets the translated message for each violation type. * From bc2689a777005716d6a6c5c077714f1c1cadeb10 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Mon, 29 Jan 2024 15:58:58 +0100 Subject: [PATCH 30/41] rename variable --- src/components/ReportActionItem/MoneyRequestView.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestView.js b/src/components/ReportActionItem/MoneyRequestView.js index 6a61272c919..212044d636a 100644 --- a/src/components/ReportActionItem/MoneyRequestView.js +++ b/src/components/ReportActionItem/MoneyRequestView.js @@ -166,11 +166,11 @@ function MoneyRequestView({report, parentReport, parentReportActions, policyCate // if the policy of the report is either Collect or Control, then this report must be tied to workspace chat const isPolicyExpenseChat = ReportUtils.isGroupPolicy(report); - const policyTagList = useMemo(() => PolicyUtils.getTagLists(policyTags), [policyTags]); + const policyTagLists = useMemo(() => PolicyUtils.getTagLists(policyTags), [policyTags]); // Flags for showing categories and tags const shouldShowCategory = isPolicyExpenseChat && (transactionCategory || OptionsListUtils.hasEnabledOptions(lodashValues(policyCategories))); - const shouldShowTag = useMemo(() => isPolicyExpenseChat && (transactionTag || OptionsListUtils.hasEnabledTags(policyTagList)), [isPolicyExpenseChat, policyTagList, transactionTag]); + const shouldShowTag = useMemo(() => isPolicyExpenseChat && (transactionTag || OptionsListUtils.hasEnabledTags(policyTagLists)), [isPolicyExpenseChat, policyTagLists, transactionTag]); const shouldShowBillable = isPolicyExpenseChat && (transactionBillable || !lodashGet(policy, 'disabledFields.defaultBillable', true)); const {getViolationsForField} = useViolations(transactionViolations); @@ -346,7 +346,7 @@ function MoneyRequestView({report, parentReport, parentReportActions, policyCate )} {shouldShowTag && - _.map(policyTagList, ({name}, index) => ( + _.map(policyTagLists, ({name}, index) => ( Date: Mon, 29 Jan 2024 16:25:59 +0100 Subject: [PATCH 31/41] integrate multiple levels of tags to split bill --- src/ROUTES.ts | 5 +- .../MoneyRequestConfirmationList.js | 48 +++++++++---------- src/libs/actions/IOU.js | 14 ++++-- src/pages/EditSplitBillPage.js | 5 ++ src/pages/iou/MoneyRequestTagPage.js | 21 ++++---- 5 files changed, 53 insertions(+), 40 deletions(-) diff --git a/src/ROUTES.ts b/src/ROUTES.ts index b21109436ca..608c2c9a489 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -218,8 +218,9 @@ const ROUTES = { getRoute: (reportID: string, reportActionID: string) => `r/${reportID}/split/${reportActionID}` as const, }, EDIT_SPLIT_BILL: { - route: `r/:reportID/split/:reportActionID/edit/:field`, - getRoute: (reportID: string, reportActionID: string, field: ValueOf) => `r/${reportID}/split/${reportActionID}/edit/${field}` as const, + route: `r/:reportID/split/:reportActionID/edit/:field/:tagIndex?`, + getRoute: (reportID: string, reportActionID: string, field: ValueOf, tagIndex?: number) => + `r/${reportID}/split/${reportActionID}/edit/${field}${typeof tagIndex === 'number' ? `/${tagIndex}` : ''}` as const, }, EDIT_SPLIT_BILL_CURRENCY: { route: 'r/:reportID/split/:reportActionID/edit/currency', diff --git a/src/components/MoneyRequestConfirmationList.js b/src/components/MoneyRequestConfirmationList.js index d967d04ab94..0838561f483 100755 --- a/src/components/MoneyRequestConfirmationList.js +++ b/src/components/MoneyRequestConfirmationList.js @@ -241,12 +241,10 @@ function MoneyRequestConfirmationList(props) { const shouldShowDate = shouldShowAllFields && !isTypeSend && !isSplitWithScan; const shouldShowMerchant = shouldShowAllFields && !isTypeSend && !props.isDistanceRequest && !isSplitWithScan; - // Fetches the first tag list of the policy - const policyTag = PolicyUtils.getTag(props.policyTags); - const policyTagList = lodashGet(policyTag, 'tags', {}); - const policyTagListName = lodashGet(policyTag, 'name', translate('common.tag')); + const policyTagLists = useMemo(() => PolicyUtils.getTagLists(props.policyTags), [props.policyTags]); + // A flag for showing the tags field - const shouldShowTags = props.isPolicyExpenseChat && (props.iouTag || OptionsListUtils.hasEnabledOptions(_.values(policyTagList))); + const shouldShowTags = props.isPolicyExpenseChat && (props.iouTag || OptionsListUtils.hasEnabledTags(policyTagLists)); // A flag for showing tax fields - tax rate and tax amount const shouldShowTax = props.isPolicyExpenseChat && props.policy.isTaxTrackingEnabled; @@ -755,25 +753,27 @@ function MoneyRequestConfirmationList(props) { rightLabel={canUseViolations && Boolean(props.policy.requiresCategory) ? translate('common.required') : ''} /> )} - {shouldShowTags && ( - { - if (props.isEditingSplitBill) { - Navigation.navigate(ROUTES.EDIT_SPLIT_BILL.getRoute(props.reportID, props.reportActionID, CONST.EDIT_REQUEST_FIELD.TAG)); - return; - } - Navigation.navigate(ROUTES.MONEY_REQUEST_TAG.getRoute(props.iouType, props.reportID)); - }} - style={[styles.moneyRequestMenuItem]} - disabled={didConfirm} - interactive={!props.isReadOnly} - rightLabel={canUseViolations && Boolean(props.policy.requiresTag) ? translate('common.required') : ''} - /> - )} + {shouldShowTags && + _.map(policyTagLists, ({name}, index) => ( + { + if (props.isEditingSplitBill) { + Navigation.navigate(ROUTES.EDIT_SPLIT_BILL.getRoute(props.reportID, props.reportActionID, CONST.EDIT_REQUEST_FIELD.TAG, index)); + return; + } + Navigation.navigate(ROUTES.MONEY_REQUEST_TAG.getRoute(props.iouType, index, props.reportID)); + }} + style={[styles.moneyRequestMenuItem]} + disabled={didConfirm} + interactive={!props.isReadOnly} + rightLabel={canUseViolations && Boolean(props.policy.requiresTag) ? translate('common.required') : ''} + /> + ))} {shouldShowTax && ( { setDraftSplitTransaction({tag: transactionChanges.tag.trim()}); diff --git a/src/pages/iou/MoneyRequestTagPage.js b/src/pages/iou/MoneyRequestTagPage.js index 9309c8d788e..9192b75cec8 100644 --- a/src/pages/iou/MoneyRequestTagPage.js +++ b/src/pages/iou/MoneyRequestTagPage.js @@ -2,7 +2,6 @@ import lodashGet from 'lodash/get'; import PropTypes from 'prop-types'; import React from 'react'; import {withOnyx} from 'react-native-onyx'; -import _ from 'underscore'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import ScreenWrapper from '@components/ScreenWrapper'; import TagPicker from '@components/TagPicker'; @@ -13,6 +12,7 @@ import useThemeStyles from '@hooks/useThemeStyles'; import compose from '@libs/compose'; import Navigation from '@libs/Navigation/Navigation'; import * as PolicyUtils from '@libs/PolicyUtils'; +import * as TransactionUtils from '@libs/TransactionUtils'; import reportPropTypes from '@pages/reportPropTypes'; import * as IOU from '@userActions/IOU'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -54,20 +54,20 @@ function MoneyRequestTagPage({route, report, policyTags, iou}) { const {translate} = useLocalize(); const iouType = lodashGet(route, 'params.iouType', ''); - - // Fetches the first tag list of the policy - const tagListKey = _.first(_.keys(policyTags)); - const policyTagListName = PolicyUtils.getTagListName(policyTags, 0) || translate('common.tag'); + const tagIndex = Number(lodashGet(route, 'params.tagIndex', undefined)); + const policyTagListName = PolicyUtils.getTagListName(policyTags, tagIndex); + const transactionTag = TransactionUtils.getTag(iou); + const tag = TransactionUtils.getTag(iou, tagIndex); const navigateBack = () => { Navigation.goBack(ROUTES.MONEY_REQUEST_CONFIRMATION.getRoute(iouType, report.reportID)); }; const updateTag = (selectedTag) => { - if (selectedTag.searchText === iou.tag) { - IOU.resetMoneyRequestTag(); + if (tag === selectedTag.searchText) { + IOU.resetMoneyRequestTag(transactionTag, tagIndex); } else { - IOU.setMoneyRequestTag(selectedTag.searchText); + IOU.setMoneyRequestTag(transactionTag, selectedTag.searchText, tagIndex); } navigateBack(); }; @@ -87,8 +87,9 @@ function MoneyRequestTagPage({route, report, policyTags, iou}) { {translate('iou.tagSelection', {tagName: policyTagListName})} From 3a95869505bf5165a039eb416d026d4f6d603c99 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Mon, 29 Jan 2024 16:54:30 +0100 Subject: [PATCH 32/41] integrate multiple levels of tags for modified expense message --- src/libs/ModifiedExpenseMessage.ts | 37 ++++++++++++++++++++---------- 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/src/libs/ModifiedExpenseMessage.ts b/src/libs/ModifiedExpenseMessage.ts index 49e9ae82662..649ef4b2433 100644 --- a/src/libs/ModifiedExpenseMessage.ts +++ b/src/libs/ModifiedExpenseMessage.ts @@ -102,8 +102,6 @@ function getForReportAction(reportAction: OnyxEntry): string { } const reportActionOriginalMessage = reportAction?.originalMessage as ExpenseOriginalMessage | undefined; const policyID = ReportUtils.getReportPolicyID(reportAction?.reportID) ?? ''; - const policyTags = allPolicyTags?.[`${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`] ?? {}; - const policyTagListName = PolicyUtils.getTagListName(policyTags, 0) || Localize.translateLocal('common.tag'); const removalFragments: string[] = []; const setFragments: string[] = []; @@ -188,16 +186,31 @@ function getForReportAction(reportAction: OnyxEntry): string { const hasModifiedTag = reportActionOriginalMessage && 'oldTag' in reportActionOriginalMessage && 'tag' in reportActionOriginalMessage; if (hasModifiedTag) { - buildMessageFragmentForValue( - PolicyUtils.getCleanedTagName(reportActionOriginalMessage?.tag ?? ''), - PolicyUtils.getCleanedTagName(reportActionOriginalMessage?.oldTag ?? ''), - policyTagListName, - true, - setFragments, - removalFragments, - changeFragments, - policyTagListName === Localize.translateLocal('common.tag'), - ); + const policyTags = allPolicyTags?.[`${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`] ?? {}; + const transactionTag = reportActionOriginalMessage?.tag ?? ''; + const oldTransactionTag = reportActionOriginalMessage?.oldTag ?? ''; + const splittedTag = transactionTag.split(CONST.COLON); + const splittedOldTag = oldTransactionTag.split(CONST.COLON); + + Object.keys(policyTags).forEach((policyTagKey, index) => { + const policyTagListName = PolicyUtils.getTagListName(policyTags, index) || Localize.translateLocal('common.tag'); + + const newTag = splittedTag[index] ?? ''; + const oldTag = splittedOldTag[index] ?? ''; + + if (newTag !== oldTag) { + buildMessageFragmentForValue( + PolicyUtils.getCleanedTagName(newTag), + PolicyUtils.getCleanedTagName(oldTag), + policyTagListName, + true, + setFragments, + removalFragments, + changeFragments, + policyTagListName === Localize.translateLocal('common.tag'), + ); + } + }); } const hasModifiedBillable = reportActionOriginalMessage && 'oldBillable' in reportActionOriginalMessage && 'billable' in reportActionOriginalMessage; From 4619e03d4b8d692dba0deb661467eb5a0df7c38e Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Tue, 30 Jan 2024 15:19:18 +0100 Subject: [PATCH 33/41] update violations tests for tags. --- src/libs/Violations/ViolationsUtils.ts | 10 +++++++++- tests/unit/ViolationUtilsTest.js | 17 +++++++++++++---- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/src/libs/Violations/ViolationsUtils.ts b/src/libs/Violations/ViolationsUtils.ts index 5b81f3dae12..787cb70d7e9 100644 --- a/src/libs/Violations/ViolationsUtils.ts +++ b/src/libs/Violations/ViolationsUtils.ts @@ -52,9 +52,17 @@ const ViolationsUtils = { } if (policyRequiresTags) { - const selectedTags = transaction.tag.split(CONST.COLON); + const selectedTags = transaction.tag?.split(CONST.COLON) ?? []; const policyTagKeys = Object.keys(policyTags); + if (policyTagKeys.length === 0) { + newTransactionViolations.push({ + name: CONST.VIOLATIONS.TAG_OUT_OF_POLICY, + type: 'violation', + userMessage: '', + }); + } + policyTagKeys.forEach((key, index) => { const hasTagOutOfPolicyViolation = transactionViolations.some((violation) => violation.name === CONST.VIOLATIONS.TAG_OUT_OF_POLICY && violation.data?.tagName === key); const hasMissingTagViolation = transactionViolations.some((violation) => violation.name === CONST.VIOLATIONS.MISSING_TAG && violation.data?.tagName === key); diff --git a/tests/unit/ViolationUtilsTest.js b/tests/unit/ViolationUtilsTest.js index f0b53443831..616ae11eda1 100644 --- a/tests/unit/ViolationUtilsTest.js +++ b/tests/unit/ViolationUtilsTest.js @@ -128,7 +128,16 @@ describe('getViolationsOnyxData', () => { describe('policyRequiresTags', () => { beforeEach(() => { policyRequiresTags = true; - policyTags = {Lunch: {enabled: true}, Dinner: {enabled: true}}; + policyTags = { + Meals: { + name: 'Meals', + required: true, + tags: { + Lunch: {name: 'Lunch', enabled: true}, + Dinner: {name: 'Dinner', enabled: true}, + }, + }, + }; transaction.tag = 'Lunch'; }); @@ -143,7 +152,7 @@ describe('getViolationsOnyxData', () => { const result = ViolationsUtils.getViolationsOnyxData(transaction, transactionViolations, policyRequiresTags, policyTags, policyRequiresCategories, policyCategories); - expect(result.value).toEqual(expect.arrayContaining([missingTagViolation])); + expect(result.value).toEqual(expect.arrayContaining([{...missingTagViolation, data: {tagName: 'Meals'}}])); }); it('should add a tagOutOfPolicy violation when policy requires tags and tag is not in the policy', () => { @@ -163,7 +172,7 @@ describe('getViolationsOnyxData', () => { const result = ViolationsUtils.getViolationsOnyxData(transaction, transactionViolations, policyRequiresTags, policyTags, policyRequiresCategories, policyCategories); - expect(result.value).toEqual(expect.arrayContaining([tagOutOfPolicyViolation, ...transactionViolations])); + expect(result.value).toEqual(expect.arrayContaining([{...tagOutOfPolicyViolation, data: {tagName: 'Meals'}}, ...transactionViolations])); }); it('should add missingTag violation to existing violations if transaction does not have a tag', () => { @@ -175,7 +184,7 @@ describe('getViolationsOnyxData', () => { const result = ViolationsUtils.getViolationsOnyxData(transaction, transactionViolations, policyRequiresTags, policyTags, policyRequiresCategories, policyCategories); - expect(result.value).toEqual(expect.arrayContaining([missingTagViolation, ...transactionViolations])); + expect(result.value).toEqual(expect.arrayContaining([{...missingTagViolation, data: {tagName: 'Meals'}}, ...transactionViolations])); }); }); From ef0ca5dee489bff88bb8028df820e2b7dd684c02 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Mon, 5 Feb 2024 15:11:12 +0100 Subject: [PATCH 34/41] use const --- src/libs/PolicyUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts index 97d3bbab256..8d67a89877a 100644 --- a/src/libs/PolicyUtils.ts +++ b/src/libs/PolicyUtils.ts @@ -196,7 +196,7 @@ function getTagList(policyTags: OnyxCollection, tagIndex: number) * Cleans up escaping of colons (used to create multi-level tags, e.g. "Parent: Child") in the tag name we receive from the backend */ function getCleanedTagName(tag: string) { - return tag?.replace(/\\{1,2}:/g, ':'); + return tag?.replace(/\\{1,2}:/g, CONST.COLON); } /** From bf8d517462fbc6e13593b1ca0409b484e4610049 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Mon, 5 Feb 2024 15:34:05 +0100 Subject: [PATCH 35/41] use new value --- src/pages/iou/request/step/IOURequestStepTag.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/iou/request/step/IOURequestStepTag.js b/src/pages/iou/request/step/IOURequestStepTag.js index aeaf7b9ebda..134e427b4e0 100644 --- a/src/pages/iou/request/step/IOURequestStepTag.js +++ b/src/pages/iou/request/step/IOURequestStepTag.js @@ -72,7 +72,7 @@ function IOURequestStepTag({ const isSelectedTag = selectedTag.searchText === tag; const updatedTag = IOUUtils.insertTagIntoReportTagsSting(transactionTag, isSelectedTag ? '' : selectedTag.searchText, tagIndex); if (isSplitBill) { - IOU.setDraftSplitTransaction(transactionID, {tag: selectedTag.searchText}); + IOU.setDraftSplitTransaction(transactionID, {tag: updatedTag}); navigateBack(); return; } From 996438dd509caf8c58f9a4c37764144b889ab20a Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Tue, 6 Feb 2024 12:01:16 +0100 Subject: [PATCH 36/41] back import --- src/libs/PolicyUtils.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts index 9c913263570..0982e0a73f5 100644 --- a/src/libs/PolicyUtils.ts +++ b/src/libs/PolicyUtils.ts @@ -4,6 +4,7 @@ import type {ValueOf} from 'type-fest'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {PersonalDetailsList, Policy, PolicyMembers, PolicyTagList, PolicyTags} from '@src/types/onyx'; +import type {EmptyObject} from '@src/types/utils/EmptyObject'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; type MemberEmailsToAccountIDs = Record; From 3d7cd9a3ede8b3b6ebbd71a816f0577716d3ff0d Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Thu, 8 Feb 2024 17:30:17 +0100 Subject: [PATCH 37/41] fix method name --- src/libs/IOUUtils.ts | 4 ++-- src/pages/EditRequestPage.js | 2 +- src/pages/iou/request/step/IOURequestStepTag.js | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/libs/IOUUtils.ts b/src/libs/IOUUtils.ts index ef05abce5c6..ddcbca7c14f 100644 --- a/src/libs/IOUUtils.ts +++ b/src/libs/IOUUtils.ts @@ -107,11 +107,11 @@ function isValidMoneyRequestType(iouType: string): boolean { * @param tagIndex - the index of a tag list * @returns */ -function insertTagIntoReportTagsSting(reportTags: string, tag: string, tagIndex: number): string { +function insertTagIntoReportTagsString(reportTags: string, tag: string, tagIndex: number): string { const splittedReportTags = reportTags.split(CONST.COLON); splittedReportTags[tagIndex] = tag; return splittedReportTags.join(CONST.COLON).replace(/:*$/, ''); } -export {calculateAmount, updateIOUOwnerAndTotal, isIOUReportPendingCurrencyConversion, isValidMoneyRequestType, navigateToStartMoneyRequestStep, insertTagIntoReportTagsSting}; +export {calculateAmount, updateIOUOwnerAndTotal, isIOUReportPendingCurrencyConversion, isValidMoneyRequestType, navigateToStartMoneyRequestStep, insertTagIntoReportTagsString}; diff --git a/src/pages/EditRequestPage.js b/src/pages/EditRequestPage.js index cf92ee694cd..79e4d9ded4f 100644 --- a/src/pages/EditRequestPage.js +++ b/src/pages/EditRequestPage.js @@ -166,7 +166,7 @@ function EditRequestPage({report, route, policyCategories, policyTags, parentRep // In case the same tag has been selected, reset the tag. updatedTag = ''; } - IOU.updateMoneyRequestTag(transaction.transactionID, report.reportID, IOUUtils.insertTagIntoReportTagsSting(transactionTag, updatedTag, tagIndex)); + IOU.updateMoneyRequestTag(transaction.transactionID, report.reportID, IOUUtils.insertTagIntoReportTagsString(transactionTag, updatedTag, tagIndex)); Navigation.dismissModal(); }, [tag, transactionTag, tagIndex, transaction.transactionID, report.reportID], diff --git a/src/pages/iou/request/step/IOURequestStepTag.js b/src/pages/iou/request/step/IOURequestStepTag.js index fc595edca74..7ac53b08ba7 100644 --- a/src/pages/iou/request/step/IOURequestStepTag.js +++ b/src/pages/iou/request/step/IOURequestStepTag.js @@ -70,7 +70,7 @@ function IOURequestStepTag({ */ const updateTag = (selectedTag) => { const isSelectedTag = selectedTag.searchText === tag; - const updatedTag = IOUUtils.insertTagIntoReportTagsSting(transactionTag, isSelectedTag ? '' : selectedTag.searchText, tagIndex); + const updatedTag = IOUUtils.insertTagIntoReportTagsString(transactionTag, isSelectedTag ? '' : selectedTag.searchText, tagIndex); if (isSplitBill && isEditing) { IOU.setDraftSplitTransaction(transactionID, {tag: updatedTag}); navigateBack(); From eeb4356f6d83388e203bd731f718cf05a1c5f517 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Thu, 8 Feb 2024 17:40:32 +0100 Subject: [PATCH 38/41] create a var for translation --- src/libs/ModifiedExpenseMessage.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/libs/ModifiedExpenseMessage.ts b/src/libs/ModifiedExpenseMessage.ts index 21b31b07601..bab8998382b 100644 --- a/src/libs/ModifiedExpenseMessage.ts +++ b/src/libs/ModifiedExpenseMessage.ts @@ -191,9 +191,10 @@ function getForReportAction(reportID: string | undefined, reportAction: OnyxEntr const oldTransactionTag = reportActionOriginalMessage?.oldTag ?? ''; const splittedTag = transactionTag.split(CONST.COLON); const splittedOldTag = oldTransactionTag.split(CONST.COLON); + const localizedTagListName = Localize.translateLocal('common.tag'); Object.keys(policyTags).forEach((policyTagKey, index) => { - const policyTagListName = PolicyUtils.getTagListName(policyTags, index) || Localize.translateLocal('common.tag'); + const policyTagListName = PolicyUtils.getTagListName(policyTags, index) || localizedTagListName; const newTag = splittedTag[index] ?? ''; const oldTag = splittedOldTag[index] ?? ''; @@ -207,7 +208,7 @@ function getForReportAction(reportID: string | undefined, reportAction: OnyxEntr setFragments, removalFragments, changeFragments, - policyTagListName === Localize.translateLocal('common.tag'), + policyTagListName === localizedTagListName, ); } }); From 11b6422608aebf2fd88cd1ec112d4c8f8ec4ae5a Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Thu, 8 Feb 2024 17:44:08 +0100 Subject: [PATCH 39/41] use Set instead of lodash --- src/libs/actions/Policy.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts index 6fab214abc8..189e33091a6 100644 --- a/src/libs/actions/Policy.ts +++ b/src/libs/actions/Policy.ts @@ -1545,7 +1545,7 @@ function buildOptimisticPolicyRecentlyUsedTags(policyID?: string, reportTags?: s } const tagListKey = policyTagKeys[index]; - newOptimisticPolicyRecentlyUsedTags[tagListKey] = lodashUnion([tag], policyRecentlyUsedTags[tagListKey] ?? []); + newOptimisticPolicyRecentlyUsedTags[tagListKey] = [...new Set([...tag, ...(policyRecentlyUsedTags[tagListKey] ?? [])])]; }); return newOptimisticPolicyRecentlyUsedTags; From 09514683eabd508d34ef2953e7457016cc792992 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Wed, 14 Feb 2024 15:23:20 +0100 Subject: [PATCH 40/41] fix conflicts --- ...poraryForRefactorRequestConfirmationList.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js index 97543819b4f..1cfbe249829 100755 --- a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js +++ b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js @@ -2,7 +2,7 @@ import {useIsFocused} from '@react-navigation/native'; import {format} from 'date-fns'; import lodashGet from 'lodash/get'; import PropTypes from 'prop-types'; -import React, {useCallback, useEffect, useMemo, useReducer, useRef, useState} from 'react'; +import React, {Fragment, useCallback, useEffect, useMemo, useReducer, useRef, useState} from 'react'; import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; @@ -271,7 +271,7 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ const shouldShowTags = useMemo(() => isPolicyExpenseChat && OptionsListUtils.hasEnabledTags(policyTagLists), [isPolicyExpenseChat, policyTagLists]); // A flag for showing tax rate - const shouldShowTax = isPolicyExpenseChat && policy && policy.isTaxTrackingEnabled; + const shouldShowTax = isPolicyExpenseChat && policy && lodashGet(policy, 'isTaxTrackingEnabled', false); // A flag for showing the billable field const shouldShowBillable = !lodashGet(policy, 'disabledFields.defaultBillable', true); @@ -309,8 +309,8 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ const isMerchantEmpty = !iouMerchant || iouMerchant === CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT; const isMerchantRequired = isPolicyExpenseChat && !isScanRequest && shouldShowMerchant; - const isCategoryRequired = canUseViolations && Boolean(policy.requiresCategory); - const isTagRequired = canUseViolations && Boolean(policy.requiresTag); + const isCategoryRequired = canUseViolations && Boolean(lodashGet(policy, 'requiresCategory', false)); + const isTagRequired = canUseViolations && Boolean(lodashGet(policy, 'requiresTag', false)); useEffect(() => { if ((!isMerchantRequired && isMerchantEmpty) || !merchantError) { @@ -753,14 +753,14 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ titleStyle={styles.flex1} disabled={didConfirm} interactive={!isReadOnly} - rightLabel={canUseViolations && Boolean(policy.requiresCategory) ? translate('common.required') : ''} + rightLabel={canUseViolations && Boolean(lodashGet(policy, 'requiresCategory', false)) ? translate('common.required') : ''} /> ), shouldShow: shouldShowCategories, isSupplementary: !isCategoryRequired, }, - { - item: _.map(policyTagLists, ({name}, index) => ( + ..._.map(policyTagLists, ({name}, index) => ({ + item: ( - )), + ), shouldShow: shouldShowTags, isSupplementary: !isTagRequired, - }, + })), { item: ( Date: Thu, 15 Feb 2024 11:33:09 +0100 Subject: [PATCH 41/41] fix onyx tags type --- src/ONYXKEYS.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 5755296f3bb..7f67d525400 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -435,7 +435,7 @@ type OnyxCollectionValuesMapping = { [ONYXKEYS.COLLECTION.POLICY]: OnyxTypes.Policy; [ONYXKEYS.COLLECTION.POLICY_DRAFTS]: OnyxTypes.Policy; [ONYXKEYS.COLLECTION.POLICY_CATEGORIES]: OnyxTypes.PolicyCategories; - [ONYXKEYS.COLLECTION.POLICY_TAGS]: OnyxTypes.PolicyTags; + [ONYXKEYS.COLLECTION.POLICY_TAGS]: OnyxTypes.PolicyTagList; [ONYXKEYS.COLLECTION.POLICY_MEMBERS]: OnyxTypes.PolicyMembers; [ONYXKEYS.COLLECTION.POLICY_MEMBERS_DRAFTS]: OnyxTypes.PolicyMember; [ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_CATEGORIES]: OnyxTypes.RecentlyUsedCategories;