Skip to content

Commit 32cf24b

Browse files
authored
Merge pull request Expensify#64929 from ZhenjaHorbach/add-multi-tags-for-edit-split
Multi tags for Split Expenses
2 parents 790f520 + 3e7f965 commit 32cf24b

4 files changed

Lines changed: 295 additions & 67 deletions

File tree

src/components/MoneyRequestConfirmationListFooter.tsx

Lines changed: 12 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,8 @@ import useThemeStyles from '@hooks/useThemeStyles';
1313
import {convertToDisplayString} from '@libs/CurrencyUtils';
1414
import DistanceRequestUtils from '@libs/DistanceRequestUtils';
1515
import Navigation from '@libs/Navigation/Navigation';
16-
import {hasEnabledOptions} from '@libs/OptionsListUtils';
1716
import {getDestinationForDisplay, getSubratesFields, getSubratesForDisplay, getTimeDifferenceIntervals, getTimeForDisplay} from '@libs/PerDiemRequestUtils';
18-
import {canSendInvoice, getPerDiemCustomUnit, hasDependentTags as hasDependentTagsPolicyUtils, isMultiLevelTags as isMultiLevelTagsPolicyUtils, isPaidGroupPolicy} from '@libs/PolicyUtils';
17+
import {canSendInvoice, getPerDiemCustomUnit, isPaidGroupPolicy} from '@libs/PolicyUtils';
1918
import type {ThumbnailAndImageURI} from '@libs/ReceiptUtils';
2019
import {getThumbnailAndImageURIs} from '@libs/ReceiptUtils';
2120
import {
@@ -26,7 +25,7 @@ import {
2625
isReportOutstanding,
2726
populateOptimisticReportFormula,
2827
} from '@libs/ReportUtils';
29-
import {hasEnabledTags} from '@libs/TagsOptionsListUtils';
28+
import {getTagVisibility, hasEnabledTags} from '@libs/TagsOptionsListUtils';
3029
import {
3130
getTagForDisplay,
3231
getTaxAmount,
@@ -58,14 +57,6 @@ import ReceiptEmptyState from './ReceiptEmptyState';
5857
import ReceiptImage from './ReceiptImage';
5958
import {ShowContextMenuContext} from './ShowContextMenuContext';
6059

61-
type TagVisibility = {
62-
/** Flag indicating if the tag is required */
63-
isTagRequired: boolean;
64-
65-
/** Flag indicating if the tag should be shown */
66-
shouldShow: boolean;
67-
};
68-
6960
type MoneyRequestConfirmationListFooterProps = {
7061
/** The action to perform */
7162
action: IOUAction;
@@ -267,8 +258,6 @@ function MoneyRequestConfirmationListFooter({
267258
const [currentUserLogin] = useOnyx(ONYXKEYS.SESSION, {selector: (session) => session?.email, canBeMissing: true});
268259

269260
const shouldShowTags = useMemo(() => isPolicyExpenseChat && hasEnabledTags(policyTagLists), [isPolicyExpenseChat, policyTagLists]);
270-
const hasDependentTags = useMemo(() => hasDependentTagsPolicyUtils(policy, policyTags), [policy, policyTags]);
271-
const isMultilevelTags = useMemo(() => isMultiLevelTagsPolicyUtils(policyTags), [policyTags]);
272261
const shouldShowAttendees = useMemo(() => shouldShowAttendeesTransactionUtils(iouType, policy), [iouType, policy]);
273262

274263
const hasPendingWaypoints = transaction && isFetchingWaypointsFromServer(transaction);
@@ -358,28 +347,16 @@ function MoneyRequestConfirmationListFooter({
358347
[],
359348
);
360349

361-
const tagVisibility: TagVisibility[] = policyTagLists.map(({tags, required}, index) => {
362-
const isTagRequired = required ?? false;
363-
let shouldShow = false;
364-
365-
if (shouldShowTags) {
366-
if (hasDependentTags) {
367-
if (index === 0) {
368-
shouldShow = true;
369-
} else {
370-
const prevTagValue = getTagForDisplay(transaction, index - 1);
371-
shouldShow = !!prevTagValue;
372-
}
373-
} else {
374-
shouldShow = !isMultilevelTags || hasEnabledOptions(tags);
375-
}
376-
}
377-
378-
return {
379-
isTagRequired,
380-
shouldShow,
381-
};
382-
});
350+
const tagVisibility = useMemo(
351+
() =>
352+
getTagVisibility({
353+
shouldShowTags,
354+
policy,
355+
policyTags,
356+
transaction,
357+
}),
358+
[shouldShowTags, policy, policyTags, transaction],
359+
);
383360

384361
const previousTagsVisibility = usePrevious(tagVisibility.map((v) => v.shouldShow)) ?? [];
385362

src/libs/TagsOptionsListUtils.ts

Lines changed: 56 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
1+
import type {OnyxEntry} from 'react-native-onyx';
12
import CONST from '@src/CONST';
2-
import type {PolicyTag, PolicyTagLists, PolicyTags} from '@src/types/onyx';
3+
import type {Policy, PolicyTag, PolicyTagLists, PolicyTags, Transaction} from '@src/types/onyx';
34
import type {PendingAction} from '@src/types/onyx/OnyxCommon';
45
import localeCompare from './LocaleCompare';
56
import {translateLocal} from './Localize';
67
import {hasEnabledOptions} from './OptionsListUtils';
78
import type {Option} from './OptionsListUtils';
8-
import {getCleanedTagName} from './PolicyUtils';
9+
import {getCleanedTagName, getTagLists, hasDependentTags as hasDependentTagsPolicyUtils, isMultiLevelTags as isMultiLevelTagsPolicyUtils} from './PolicyUtils';
910
import tokenizedSearch from './tokenizedSearch';
11+
import {getTagForDisplay} from './TransactionUtils';
1012

1113
type SelectedTagOption = {
1214
name: string;
@@ -16,6 +18,14 @@ type SelectedTagOption = {
1618
pendingAction?: PendingAction;
1719
};
1820

21+
type TagVisibility = {
22+
/** Flag indicating if the tag is required */
23+
isTagRequired: boolean;
24+
25+
/** Flag indicating if the tag should be shown */
26+
shouldShow: boolean;
27+
};
28+
1929
/**
2030
* Transforms the provided tags into option objects.
2131
*
@@ -166,5 +176,47 @@ function sortTags(tags: Record<string, PolicyTag | SelectedTagOption> | Array<Po
166176
return Object.values(tags ?? {}).sort((a, b) => localeCompare(a.name, b.name)) as PolicyTag[];
167177
}
168178

169-
export {getTagsOptions, getTagListSections, hasEnabledTags, sortTags};
170-
export type {SelectedTagOption};
179+
/**
180+
* Calculate tag visibility for each tag list
181+
*/
182+
function getTagVisibility({
183+
shouldShowTags,
184+
policy,
185+
policyTags,
186+
transaction,
187+
}: {
188+
shouldShowTags: boolean;
189+
policy: Policy | undefined;
190+
policyTags: OnyxEntry<PolicyTagLists>;
191+
transaction: Transaction | undefined;
192+
}): TagVisibility[] {
193+
const hasDependentTags = hasDependentTagsPolicyUtils(policy, policyTags);
194+
const isMultilevelTags = isMultiLevelTagsPolicyUtils(policyTags);
195+
const policyTagLists = getTagLists(policyTags);
196+
197+
return policyTagLists.map(({tags, required}, index) => {
198+
const isTagRequired = required ?? false;
199+
let shouldShow = false;
200+
201+
if (shouldShowTags) {
202+
if (hasDependentTags) {
203+
if (index === 0) {
204+
shouldShow = true;
205+
} else {
206+
const prevTagValue = getTagForDisplay(transaction, index - 1);
207+
shouldShow = !!prevTagValue;
208+
}
209+
} else {
210+
shouldShow = !isMultilevelTags || hasEnabledOptions(tags);
211+
}
212+
}
213+
214+
return {
215+
isTagRequired,
216+
shouldShow,
217+
};
218+
});
219+
}
220+
221+
export {getTagsOptions, getTagListSections, hasEnabledTags, sortTags, getTagVisibility};
222+
export type {SelectedTagOption, TagVisibility};

src/pages/iou/SplitExpenseEditPage.tsx

Lines changed: 61 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import ScrollView from '@components/ScrollView';
1010
import useLocalize from '@hooks/useLocalize';
1111
import useOnyx from '@hooks/useOnyx';
1212
import usePolicy from '@hooks/usePolicy';
13+
import usePrevious from '@hooks/usePrevious';
1314
import useThemeStyles from '@hooks/useThemeStyles';
1415
import {removeSplitExpenseField, updateSplitExpenseField} from '@libs/actions/IOU';
1516
import {convertToDisplayString} from '@libs/CurrencyUtils';
@@ -22,8 +23,8 @@ import {getTagLists} from '@libs/PolicyUtils';
2223
import {isSplitAction} from '@libs/ReportSecondaryActionUtils';
2324
import type {TransactionDetails} from '@libs/ReportUtils';
2425
import {getParsedComment, getReportOrDraftReport, getTransactionDetails} from '@libs/ReportUtils';
25-
import {hasEnabledTags} from '@libs/TagsOptionsListUtils';
26-
import {getTag} from '@libs/TransactionUtils';
26+
import {getTagVisibility, hasEnabledTags} from '@libs/TagsOptionsListUtils';
27+
import {getTag, getTagForDisplay} from '@libs/TransactionUtils';
2728
import CONST from '@src/CONST';
2829
import ONYXKEYS from '@src/ONYXKEYS';
2930
import ROUTES from '@src/ROUTES';
@@ -56,13 +57,29 @@ function SplitExpenseEditPage({route}: SplitExpensePageProps) {
5657
const currentAmount = transactionDetailsAmount >= 0 ? Math.abs(Number(splitExpenseDraftTransactionDetails?.amount)) : Number(splitExpenseDraftTransactionDetails?.amount);
5758
const currentDescription = getParsedComment(Parser.htmlToMarkdown(splitExpenseDraftTransactionDetails?.comment ?? ''));
5859

60+
const shouldShowCategory = !!policy?.areCategoriesEnabled && !!policyCategories;
61+
5962
const transactionTag = getTag(splitExpenseDraftTransaction);
6063
const policyTagLists = useMemo(() => getTagLists(policyTags), [policyTags]);
6164

62-
const shouldShowTag = !!policy?.areTagsEnabled && !!(transactionTag || hasEnabledTags(policyTagLists));
63-
const shouldShowCategory = !!policy?.areCategoriesEnabled && !!policyCategories;
6465
const isSplitAvailable = report && transaction && isSplitAction(report, [transaction], policy);
6566

67+
const isCategoryRequired = !!policy?.requiresCategory;
68+
69+
const shouldShowTags = !!policy?.areTagsEnabled && !!(transactionTag || hasEnabledTags(policyTagLists));
70+
const tagVisibility = useMemo(
71+
() =>
72+
getTagVisibility({
73+
shouldShowTags,
74+
policy,
75+
policyTags,
76+
transaction: splitExpenseDraftTransaction,
77+
}),
78+
[shouldShowTags, policy, policyTags, splitExpenseDraftTransaction],
79+
);
80+
81+
const previousTagsVisibility = usePrevious(tagVisibility.map((v) => v.shouldShow)) ?? [];
82+
6683
return (
6784
<ScreenWrapper testID={SplitExpenseEditPage.displayName}>
6885
<FullPageNotFoundView shouldShow={!reportID || isEmptyObject(splitExpenseDraftTransaction) || !isSplitAvailable}>
@@ -103,6 +120,7 @@ function SplitExpenseEditPage({route}: SplitExpensePageProps) {
103120
description={translate('common.category')}
104121
title={splitExpenseDraftTransactionDetails?.category}
105122
numberOfLinesTitle={2}
123+
rightLabel={isCategoryRequired ? translate('common.required') : ''}
106124
onPress={() => {
107125
Navigation.navigate(
108126
ROUTES.MONEY_REQUEST_STEP_CATEGORY.getRoute(
@@ -118,29 +136,45 @@ function SplitExpenseEditPage({route}: SplitExpensePageProps) {
118136
titleStyle={styles.flex1}
119137
/>
120138
)}
121-
{shouldShowTag && (
122-
<MenuItemWithTopDescription
123-
shouldShowRightIcon
124-
key={translate('workspace.common.tags')}
125-
description={translate('workspace.common.tags')}
126-
title={transactionTag}
127-
numberOfLinesTitle={2}
128-
onPress={() => {
129-
Navigation.navigate(
130-
ROUTES.MONEY_REQUEST_STEP_TAG.getRoute(
131-
CONST.IOU.ACTION.EDIT,
132-
CONST.IOU.TYPE.SPLIT_EXPENSE,
133-
0,
134-
CONST.IOU.OPTIMISTIC_TRANSACTION_ID,
135-
reportID,
136-
Navigation.getActiveRoute(),
137-
),
138-
);
139-
}}
140-
style={[styles.moneyRequestMenuItem]}
141-
titleStyle={styles.flex1}
142-
/>
143-
)}
139+
{shouldShowTags &&
140+
policyTagLists.map(({name}, index) => {
141+
const tagVisibilityItem = tagVisibility.at(index);
142+
const shouldShow = tagVisibilityItem?.shouldShow ?? false;
143+
const isTagRequired = tagVisibilityItem?.isTagRequired ?? false;
144+
const prevShouldShow = previousTagsVisibility.at(index) ?? false;
145+
146+
if (!shouldShow) {
147+
return null;
148+
}
149+
150+
return (
151+
<MenuItemWithTopDescription
152+
shouldShowRightIcon
153+
key={name}
154+
highlighted={!getTagForDisplay(splitExpenseDraftTransaction, index) && !prevShouldShow}
155+
title={getTagForDisplay(splitExpenseDraftTransaction, index)}
156+
description={name}
157+
shouldShowBasicTitle
158+
shouldShowDescriptionOnTop
159+
numberOfLinesTitle={2}
160+
rightLabel={isTagRequired ? translate('common.required') : ''}
161+
onPress={() => {
162+
Navigation.navigate(
163+
ROUTES.MONEY_REQUEST_STEP_TAG.getRoute(
164+
CONST.IOU.ACTION.EDIT,
165+
CONST.IOU.TYPE.SPLIT_EXPENSE,
166+
index,
167+
CONST.IOU.OPTIMISTIC_TRANSACTION_ID,
168+
reportID,
169+
Navigation.getActiveRoute(),
170+
),
171+
);
172+
}}
173+
style={[styles.moneyRequestMenuItem]}
174+
titleStyle={styles.flex1}
175+
/>
176+
);
177+
})}
144178
<MenuItemWithTopDescription
145179
shouldShowRightIcon
146180
key={translate('common.date')}

0 commit comments

Comments
 (0)