Skip to content

Commit

Permalink
Merge pull request #36580 from kubabutkiewicz/ts-migration/TagPicker/…
Browse files Browse the repository at this point in the history
…component

[TS migration] Migrate 'TagPicker' component to TypeScript
  • Loading branch information
flodnv authored Mar 26, 2024
2 parents 4eb0705 + 9e084c5 commit f7a697a
Show file tree
Hide file tree
Showing 9 changed files with 100 additions and 96 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import lodashGet from 'lodash/get';
import React, {useMemo, useState} from 'react';
import type {OnyxEntry} from 'react-native-onyx';
import {withOnyx} from 'react-native-onyx';
import _ from 'underscore';
import type {EdgeInsets} from 'react-native-safe-area-context';
import OptionsSelector from '@components/OptionsSelector';
import useLocalize from '@hooks/useLocalize';
import useStyleUtils from '@hooks/useStyleUtils';
Expand All @@ -10,22 +10,64 @@ import * as OptionsListUtils from '@libs/OptionsListUtils';
import * as PolicyUtils from '@libs/PolicyUtils';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import {defaultProps, propTypes} from './tagPickerPropTypes';
import type {PolicyTag, PolicyTagList, PolicyTags, RecentlyUsedTags} from '@src/types/onyx';

function TagPicker({selectedTag, tag, tagIndex, policyTags, policyRecentlyUsedTags, shouldShowDisabledAndSelectedOption, insets, onSubmit}) {
type SelectedTagOption = {
name: string;
enabled: boolean;
accountID: number | null;
};

type TagPickerOnyxProps = {
/** Collection of tag list on a policy */
policyTags: OnyxEntry<PolicyTagList>;

/** List of recently used tags */
policyRecentlyUsedTags: OnyxEntry<RecentlyUsedTags>;
};

type TagPickerProps = TagPickerOnyxProps & {
/** The policyID we are getting tags for */
// It's used in withOnyx HOC.
// eslint-disable-next-line react/no-unused-prop-types
policyID: string;

/** The selected tag of the money request */
selectedTag: string;

/** The name of tag list we are getting tags for */
tagListName: string;

/** Callback to submit the selected tag */
onSubmit: () => void;

/**
* Safe area insets required for reflecting the portion of the view,
* that is not covered by navigation bars, tab bars, toolbars, and other ancestor views.
*/
insets: EdgeInsets;

/** Should show the selected option that is disabled? */
shouldShowDisabledAndSelectedOption?: boolean;

/** Indicates which tag list index was selected */
tagListIndex: number;
};

function TagPicker({selectedTag, tagListName, policyTags, tagListIndex, policyRecentlyUsedTags, shouldShowDisabledAndSelectedOption = false, insets, onSubmit}: TagPickerProps) {
const styles = useThemeStyles();
const StyleUtils = useStyleUtils();
const {translate} = useLocalize();
const [searchValue, setSearchValue] = useState('');

const policyRecentlyUsedTagsList = lodashGet(policyRecentlyUsedTags, tag, []);
const policyTagList = PolicyUtils.getTagList(policyTags, tagIndex);
const policyRecentlyUsedTagsList = useMemo(() => policyRecentlyUsedTags?.[tagListName] ?? [], [policyRecentlyUsedTags, tagListName]);
const policyTagList = PolicyUtils.getTagList(policyTags, tagListIndex);
const policyTagsCount = PolicyUtils.getCountOfEnabledTagsOfList(policyTagList.tags);
const isTagsCountBelowThreshold = policyTagsCount < CONST.TAG_LIST_THRESHOLD;

const shouldShowTextInput = !isTagsCountBelowThreshold;

const selectedOptions = useMemo(() => {
const selectedOptions: SelectedTagOption[] = useMemo(() => {
if (!selectedTag) {
return [];
}
Expand All @@ -39,26 +81,27 @@ function TagPicker({selectedTag, tag, tagIndex, policyTags, policyRecentlyUsedTa
];
}, [selectedTag]);

const enabledTags = useMemo(() => {
const enabledTags: PolicyTags | Array<PolicyTag | SelectedTagOption> = useMemo(() => {
if (!shouldShowDisabledAndSelectedOption) {
return policyTagList.tags;
}
const selectedNames = _.map(selectedOptions, (s) => s.name);
const tags = [...selectedOptions, ..._.filter(policyTagList.tags, (policyTag) => policyTag.enabled && !selectedNames.includes(policyTag.name))];
return tags;
const selectedNames = selectedOptions.map((s) => s.name);

return [...selectedOptions, ...Object.values(policyTagList.tags).filter((policyTag) => policyTag.enabled && !selectedNames.includes(policyTag.name))];
}, [selectedOptions, policyTagList, shouldShowDisabledAndSelectedOption]);

const sections = useMemo(
() => OptionsListUtils.getFilteredOptions({}, {}, [], searchValue, selectedOptions, [], false, false, false, {}, [], true, enabledTags, policyRecentlyUsedTagsList, false).tagOptions,
[searchValue, enabledTags, selectedOptions, policyRecentlyUsedTagsList],
);

const headerMessage = OptionsListUtils.getHeaderMessageForNonUserList(lodashGet(sections, '[0].data.length', 0) > 0, searchValue);
const headerMessage = OptionsListUtils.getHeaderMessageForNonUserList((sections?.[0]?.data?.length ?? 0) > 0, searchValue);

const selectedOptionKey = lodashGet(_.filter(lodashGet(sections, '[0].data', []), (policyTag) => policyTag.searchText === selectedTag)[0], 'keyForList');
const selectedOptionKey = sections[0]?.data?.filter((policyTag) => policyTag.searchText === selectedTag)?.[0]?.keyForList;

return (
<OptionsSelector
// @ts-expect-error TODO: Remove this once OptionsSelector (https://github.com/Expensify/App/issues/25125) is migrated to TypeScript.
contentContainerStyles={[{paddingBottom: StyleUtils.getSafeAreaMargins(insets).marginBottom}]}
optionHoveredStyle={styles.hoveredComponentBG}
sectionHeaderStyle={styles.mt5}
Expand All @@ -81,14 +124,14 @@ function TagPicker({selectedTag, tag, tagIndex, policyTags, policyRecentlyUsedTa
}

TagPicker.displayName = 'TagPicker';
TagPicker.propTypes = propTypes;
TagPicker.defaultProps = defaultProps;

export default withOnyx({
export default withOnyx<TagPickerProps, TagPickerOnyxProps>({
policyTags: {
key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`,
},
policyRecentlyUsedTags: {
key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_TAGS}${policyID}`,
},
})(TagPicker);

export type {SelectedTagOption};
44 changes: 0 additions & 44 deletions src/components/TagPicker/tagPickerPropTypes.js

This file was deleted.

26 changes: 14 additions & 12 deletions src/libs/OptionsListUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import lodashSet from 'lodash/set';
import lodashSortBy from 'lodash/sortBy';
import Onyx from 'react-native-onyx';
import type {OnyxCollection, OnyxEntry} from 'react-native-onyx';
import type {SelectedTagOption} from '@components/TagPicker';
import CONST from '@src/CONST';
import type {TranslationPaths} from '@src/languages/types';
import ONYXKEYS from '@src/ONYXKEYS';
Expand All @@ -19,6 +20,7 @@ import type {
PolicyCategories,
PolicyTag,
PolicyTagList,
PolicyTags,
Report,
ReportAction,
ReportActions,
Expand Down Expand Up @@ -54,12 +56,6 @@ import * as TaskUtils from './TaskUtils';
import * as TransactionUtils from './TransactionUtils';
import * as UserUtils from './UserUtils';

type Tag = {
enabled: boolean;
name: string;
accountID: number | null;
};

type Option = Partial<ReportUtils.OptionData>;

/**
Expand Down Expand Up @@ -131,7 +127,7 @@ type GetOptionsConfig = {
categories?: PolicyCategories;
recentlyUsedCategories?: string[];
includeTags?: boolean;
tags?: Record<string, Tag>;
tags?: PolicyTags | Array<SelectedTagOption | PolicyTag>;
recentlyUsedTags?: string[];
canInviteUser?: boolean;
includeSelectedOptions?: boolean;
Expand Down Expand Up @@ -914,7 +910,7 @@ function sortCategories(categories: Record<string, Category>): Category[] {
/**
* Sorts tags alphabetically by name.
*/
function sortTags(tags: Record<string, Tag> | Tag[]) {
function sortTags(tags: Record<string, PolicyTag | SelectedTagOption> | Array<PolicyTag | SelectedTagOption>) {
let sortedTags;

if (Array.isArray(tags)) {
Expand Down Expand Up @@ -1095,7 +1091,7 @@ function getCategoryListSections(
*
* @param tags - an initial tag array
*/
function getTagsOptions(tags: Category[]): Option[] {
function getTagsOptions(tags: Array<Pick<PolicyTag, 'name' | 'enabled'>>): Option[] {
return tags.map((tag) => {
// This is to remove unnecessary escaping backslash in tag name sent from backend.
const cleanedName = PolicyUtils.getCleanedTagName(tag.name);
Expand All @@ -1112,7 +1108,13 @@ function getTagsOptions(tags: Category[]): Option[] {
/**
* Build the section list for tags
*/
function getTagListSections(tags: Tag[], recentlyUsedTags: string[], selectedOptions: Category[], searchInputValue: string, maxRecentReportsToShow: number) {
function getTagListSections(
tags: Array<PolicyTag | SelectedTagOption>,
recentlyUsedTags: string[],
selectedOptions: SelectedTagOption[],
searchInputValue: string,
maxRecentReportsToShow: number,
) {
const tagSections = [];
const sortedTags = sortTags(tags);
const selectedOptionNames = selectedOptions.map((selectedOption) => selectedOption.name);
Expand Down Expand Up @@ -1424,7 +1426,7 @@ function getOptions(
}

if (includeTags) {
const tagOptions = getTagListSections(Object.values(tags), recentlyUsedTags, selectedOptions as Category[], searchInputValue, maxRecentReportsToShow);
const tagOptions = getTagListSections(Object.values(tags), recentlyUsedTags, selectedOptions as SelectedTagOption[], searchInputValue, maxRecentReportsToShow);

return {
recentReports: [],
Expand Down Expand Up @@ -1851,7 +1853,7 @@ function getFilteredOptions(
categories: PolicyCategories = {},
recentlyUsedCategories: string[] = [],
includeTags = false,
tags: Record<string, Tag> = {},
tags: PolicyTags | Array<PolicyTag | SelectedTagOption> = {},
recentlyUsedTags: string[] = [],
canInviteUser = true,
includeSelectedOptions = false,
Expand Down
14 changes: 7 additions & 7 deletions src/pages/EditRequestPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ const propTypes = {
/** reportID for the "transaction thread" */
threadReportID: PropTypes.string,

/** The index of a tag list */
/** Indicates which tag list index was selected */
tagIndex: PropTypes.string,
}),
}).isRequired,
Expand Down Expand Up @@ -78,10 +78,10 @@ function EditRequestPage({report, route, policy, policyCategories, policyTags, p

const defaultCurrency = lodashGet(route, 'params.currency', '') || transactionCurrency;
const fieldToEdit = lodashGet(route, ['params', 'field'], '');
const tagIndex = Number(lodashGet(route, ['params', 'tagIndex'], undefined));
const tagListIndex = Number(lodashGet(route, ['params', 'tagIndex'], undefined));

const tag = TransactionUtils.getTag(transaction, tagIndex);
const policyTagListName = PolicyUtils.getTagListName(policyTags, tagIndex);
const tag = TransactionUtils.getTag(transaction, tagListIndex);
const policyTagListName = PolicyUtils.getTagListName(policyTags, tagListIndex);
const policyTagLists = useMemo(() => PolicyUtils.getTagLists(policyTags), [policyTags]);

// A flag for verifying that the current report is a sub-report of a workspace chat
Expand Down Expand Up @@ -129,14 +129,14 @@ function EditRequestPage({report, route, policy, policyCategories, policyTags, p
IOU.updateMoneyRequestTag(
transaction.transactionID,
report.reportID,
IOUUtils.insertTagIntoTransactionTagsString(transactionTag, updatedTag, tagIndex),
IOUUtils.insertTagIntoTransactionTagsString(transactionTag, updatedTag, tagListIndex),
policy,
policyTags,
policyCategories,
);
Navigation.dismissModal();
},
[tag, transaction.transactionID, report.reportID, transactionTag, tagIndex, policy, policyTags, policyCategories],
[tag, transaction.transactionID, report.reportID, transactionTag, tagListIndex, policy, policyTags, policyCategories],
);

if (fieldToEdit === CONST.EDIT_REQUEST_FIELD.AMOUNT) {
Expand All @@ -159,7 +159,7 @@ function EditRequestPage({report, route, policy, policyCategories, policyTags, p
<EditRequestTagPage
defaultTag={tag}
tagName={policyTagListName}
tagIndex={tagIndex}
tagListIndex={tagListIndex}
policyID={lodashGet(report, 'policyID', '')}
onSubmit={saveTag}
/>
Expand Down
18 changes: 9 additions & 9 deletions src/pages/EditRequestTagPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,21 @@ const propTypes = {
/** The policyID we are getting tags for */
policyID: PropTypes.string.isRequired,

/** The tag name to which the default tag belongs to */
tagName: PropTypes.string,
/** The tag list name to which the default tag belongs to */
tagListName: PropTypes.string,

/** The index of a tag list */
tagIndex: PropTypes.number.isRequired,
/** Indicates which tag list index was selected */
tagListIndex: PropTypes.number.isRequired,

/** Callback to fire when the Save button is pressed */
onSubmit: PropTypes.func.isRequired,
};

const defaultProps = {
tagName: '',
tagListName: '',
};

function EditRequestTagPage({defaultTag, policyID, tagName, tagIndex, onSubmit}) {
function EditRequestTagPage({defaultTag, policyID, tagListName, tagListIndex, onSubmit}) {
const styles = useThemeStyles();
const {translate} = useLocalize();

Expand All @@ -46,14 +46,14 @@ function EditRequestTagPage({defaultTag, policyID, tagName, tagIndex, onSubmit})
{({insets}) => (
<>
<HeaderWithBackButton
title={tagName || translate('common.tag')}
title={tagListName || translate('common.tag')}
onBackButtonPress={Navigation.goBack}
/>
<Text style={[styles.ph5, styles.pv3]}>{translate('iou.tagSelection')}</Text>
<TagPicker
selectedTag={defaultTag}
tag={tagName}
tagIndex={tagIndex}
tagListName={tagListName}
tagListIndex={tagListIndex}
policyID={policyID}
shouldShowDisabledAndSelectedOption
insets={insets}
Expand Down
2 changes: 1 addition & 1 deletion src/pages/EditSplitBillPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ function EditSplitBillPage({route, transaction, draftTransaction, report}: EditS
<EditRequestTagPage
defaultTag={transactionTag ?? ''}
policyID={report?.policyID ?? ''}
tagIndex={Number(tagIndex)}
tagListIndex={Number(tagIndex)}
onSubmit={(transactionChanges) => {
setDraftSplitTransaction({tag: transactionChanges.tag.trim()});
}}
Expand Down
3 changes: 3 additions & 0 deletions src/pages/iou/request/step/IOURequestStepRoutePropTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,8 @@ export default PropTypes.shape({

/** A path to go to when the user presses the back button */
backTo: PropTypes.string,

/** Indicates which tag list index was selected */
tagIndex: PropTypes.string,
}),
});
Loading

0 comments on commit f7a697a

Please sign in to comment.