Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[TS migration] Migrate 'TagPicker' component to TypeScript #36580

Merged
merged 29 commits into from
Mar 26, 2024
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
32268d4
ref: move TagPicker to TS
kubabutkiewicz Feb 15, 2024
0674554
Merge branch 'main' of github.com:kubabutkiewicz/expensify-app into t…
kubabutkiewicz Feb 21, 2024
e154fea
fix: missing value
kubabutkiewicz Feb 21, 2024
f4e1f1f
Merge branch 'main' of github.com:kubabutkiewicz/expensify-app into t…
kubabutkiewicz Feb 21, 2024
4e01f47
Merge branch 'main' of github.com:kubabutkiewicz/expensify-app into t…
kubabutkiewicz Feb 27, 2024
797d11a
Merge branch 'main' of github.com:kubabutkiewicz/expensify-app into t…
kubabutkiewicz Feb 29, 2024
4e3edb2
fix: typecheck
kubabutkiewicz Feb 29, 2024
ff6f32b
Merge branch 'main' of github.com:kubabutkiewicz/expensify-app into t…
kubabutkiewicz Mar 4, 2024
77e3d04
fix: resolve comments
kubabutkiewicz Mar 4, 2024
36b6803
Merge branch 'main' of github.com:kubabutkiewicz/expensify-app into t…
kubabutkiewicz Mar 5, 2024
6aa6a28
fix: remove unnecessary field from PolicyTag type
kubabutkiewicz Mar 5, 2024
89ddddd
Merge branch 'main' of github.com:kubabutkiewicz/expensify-app into t…
kubabutkiewicz Mar 7, 2024
68c5327
fix: changed prop name to tagListName
kubabutkiewicz Mar 7, 2024
af2e187
Merge branch 'main' of github.com:kubabutkiewicz/expensify-app into t…
kubabutkiewicz Mar 8, 2024
c82c0d1
fix: changed prop name
kubabutkiewicz Mar 8, 2024
f62fef6
fix: reverted unnecessary changes
kubabutkiewicz Mar 8, 2024
954c153
Merge branch 'main' of github.com:kubabutkiewicz/expensify-app into t…
kubabutkiewicz Mar 11, 2024
4ec1840
Merge branch 'main' of github.com:kubabutkiewicz/expensify-app into t…
kubabutkiewicz Mar 12, 2024
075f940
fix: adjust descriptions
kubabutkiewicz Mar 12, 2024
576ac31
fix: adjusted tagIndex prop comment
kubabutkiewicz Mar 12, 2024
961dba6
Merge branch 'main' of github.com:kubabutkiewicz/expensify-app into t…
kubabutkiewicz Mar 12, 2024
9f3e889
Merge branch 'main' of github.com:kubabutkiewicz/expensify-app into t…
kubabutkiewicz Mar 13, 2024
f5e43db
fix: rerun pipeline
kubabutkiewicz Mar 13, 2024
c6e10e4
fix: remove log
kubabutkiewicz Mar 13, 2024
4ae787f
Merge branch 'main' of github.com:kubabutkiewicz/expensify-app into t…
kubabutkiewicz Mar 14, 2024
82a6481
Merge branch 'main' of github.com:kubabutkiewicz/expensify-app into t…
kubabutkiewicz Mar 25, 2024
8aa8001
fix: rename prop name
kubabutkiewicz Mar 26, 2024
4e4b6da
Merge branch 'main' of github.com:kubabutkiewicz/expensify-app into t…
kubabutkiewicz Mar 26, 2024
9e084c5
Merge branch 'main' of github.com:kubabutkiewicz/expensify-app into t…
kubabutkiewicz Mar 26, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 tags attached to a policy */
policyTags: OnyxEntry<PolicyTagList>;
kubabutkiewicz marked this conversation as resolved.
Show resolved Hide resolved

/** 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;

/** The index of a tag list */
tagIndex: number;
kubabutkiewicz marked this conversation as resolved.
Show resolved Hide resolved
};

function TagPicker({selectedTag, tagListName, policyTags, tagIndex, 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 policyRecentlyUsedTagsList = useMemo(() => policyRecentlyUsedTags?.[tagListName] ?? [], [policyRecentlyUsedTags, tagListName]);
const policyTagList = PolicyUtils.getTagList(policyTags, tagIndex);
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}]}
flodnv marked this conversation as resolved.
Show resolved Hide resolved
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.

2 changes: 1 addition & 1 deletion src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -654,7 +654,7 @@ export default {
`changed the distance to ${newDistanceToDisplay} (previously ${oldDistanceToDisplay}), which updated the amount to ${newAmountToDisplay} (previously ${oldAmountToDisplay})`,
threadRequestReportName: ({formattedAmount, comment}: ThreadRequestReportNameParams) => `${formattedAmount} ${comment ? `for ${comment}` : 'request'}`,
threadSentMoneyReportName: ({formattedAmount, comment}: ThreadSentMoneyReportNameParams) => `${formattedAmount} sent${comment ? ` for ${comment}` : ''}`,
tagSelection: ({tagName}: TagSelectionParams) => `Select a ${tagName} to add additional organization to your money.`,
tagSelection: ({tagListName}: TagSelectionParams) => `Select a ${tagListName} to add additional organization to your money.`,
categorySelection: 'Select a category to add additional organization to your money.',
error: {
invalidCategoryLength: 'The length of the category chosen exceeds the maximum allowed (255). Please choose a different or shorten the category name first.',
Expand Down
2 changes: 1 addition & 1 deletion src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -649,7 +649,7 @@ export default {
`cambió la distancia a ${newDistanceToDisplay} (previamente ${oldDistanceToDisplay}), lo que cambió el importe a ${newAmountToDisplay} (previamente ${oldAmountToDisplay})`,
threadRequestReportName: ({formattedAmount, comment}: ThreadRequestReportNameParams) => `${comment ? `${formattedAmount} para ${comment}` : `Solicitud de ${formattedAmount}`}`,
threadSentMoneyReportName: ({formattedAmount, comment}: ThreadSentMoneyReportNameParams) => `${formattedAmount} enviado${comment ? ` para ${comment}` : ''}`,
tagSelection: ({tagName}: TagSelectionParams) => `Seleccione una ${tagName} para organizar mejor tu dinero.`,
tagSelection: ({tagListName}: TagSelectionParams) => `Seleccione una ${tagListName} para organizar mejor tu dinero.`,
categorySelection: 'Seleccione una categoría para organizar mejor tu dinero.',
error: {
invalidCategoryLength: 'El largo de la categoría escogida excede el máximo permitido (255). Por favor, escoge otra categoría o acorta la categoría primero.',
Expand Down
2 changes: 1 addition & 1 deletion src/languages/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ type UpdatedTheDistanceParams = {newDistanceToDisplay: string; oldDistanceToDisp

type FormattedMaxLengthParams = {formattedMaxLength: string};

type TagSelectionParams = {tagName: string};
type TagSelectionParams = {tagListName: string};

type WalletProgramParams = {walletProgram: string};

Expand Down
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 @@ -53,12 +55,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 @@ -130,7 +126,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 @@ -882,7 +878,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 @@ -1056,7 +1052,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 @@ -1073,7 +1069,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 @@ -1385,7 +1387,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 @@ -1810,7 +1812,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
12 changes: 6 additions & 6 deletions src/pages/EditRequestTagPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const propTypes = {
policyID: PropTypes.string.isRequired,

/** The tag name to which the default tag belongs to */
kubabutkiewicz marked this conversation as resolved.
Show resolved Hide resolved
tagName: PropTypes.string,
tagListName: PropTypes.string,

/** The index of a tag list */
tagIndex: PropTypes.number.isRequired,
Expand All @@ -26,10 +26,10 @@ const propTypes = {
};

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

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

Expand All @@ -46,13 +46,13 @@ 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', {tagName: tagName || translate('common.tag')})}</Text>
<Text style={[styles.ph5, styles.pv3]}>{translate('iou.tagSelection', {tagListName: tagListName || translate('common.tag')})}</Text>
<TagPicker
selectedTag={defaultTag}
tag={tagName}
tagListName={tagListName}
tagIndex={tagIndex}
policyID={policyID}
shouldShowDisabledAndSelectedOption
Expand Down
4 changes: 2 additions & 2 deletions src/pages/iou/request/step/IOURequestStepTag.js
Original file line number Diff line number Diff line change
Expand Up @@ -134,10 +134,10 @@ function IOURequestStepTag({
>
{({insets}) => (
<>
<Text style={[styles.ph5, styles.pv3]}>{translate('iou.tagSelection', {tagName: policyTagListName})}</Text>
<Text style={[styles.ph5, styles.pv3]}>{translate('iou.tagSelection', {tagListName: policyTagListName})}</Text>
<TagPicker
policyID={report.policyID}
tag={policyTagListName}
tagListName={policyTagListName}
tagIndex={tagIndex}
selectedTag={tag}
insets={insets}
Expand Down
4 changes: 2 additions & 2 deletions src/types/onyx/PolicyTag.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ type PolicyTag = {

/** "General Ledger code" that corresponds to this tag in an accounting system. Similar to an ID. */
// eslint-disable-next-line @typescript-eslint/naming-convention
'GL Code': string;
'GL Code'?: string;
};

type PolicyTags = Record<string, PolicyTag>;
Expand All @@ -21,7 +21,7 @@ type PolicyTagList<T extends string = string> = Record<
/** Flag that determines if tags are required */
required: boolean;

/** Nested tags */
/** List of tags */
tags: PolicyTags;

/** Index by which the tag appears in the hierarchy of tags */
Expand Down
Loading