Skip to content

Commit 4386ecc

Browse files
authored
Merge pull request Expensify#88576 from software-mansion-labs/feat/migrate-category-selector-to-navigation
2 parents d51eca5 + 036c7cd commit 4386ecc

13 files changed

Lines changed: 234 additions & 214 deletions

File tree

src/ROUTES.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,16 @@ const DYNAMIC_ROUTES = {
304304
path: 'imported',
305305
entryScreens: [SCREENS.WORKSPACE.CATEGORIES],
306306
},
307+
SPEND_CATEGORY_SELECTOR: {
308+
path: 'spend-category-selector/:groupID',
309+
entryScreens: [SCREENS.WORKSPACE.CATEGORIES_SETTINGS, SCREENS.SETTINGS_CATEGORIES.SETTINGS_CATEGORIES_SETTINGS],
310+
getRoute: (groupID: string) => `spend-category-selector/${groupID}` as const,
311+
},
312+
DEFAULT_CATEGORY_SELECTOR: {
313+
path: 'default-category-selector/:customUnitID',
314+
entryScreens: [SCREENS.WORKSPACE.DISTANCE_RATES_SETTINGS, SCREENS.WORKSPACE.PER_DIEM_SETTINGS],
315+
getRoute: (customUnitID: string) => `default-category-selector/${customUnitID}` as const,
316+
},
307317
WORKSPACE_INVITE: {
308318
path: 'invite',
309319
entryScreens: [SCREENS.WORKSPACE.PROFILE, SCREENS.WORKSPACE.MEMBERS],

src/SCREENS.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -788,6 +788,8 @@ const SCREENS = {
788788
CATEGORIES_SETTINGS: 'Categories_Settings',
789789
DYNAMIC_CATEGORIES_IMPORT: 'Dynamic_Categories_Import',
790790
DYNAMIC_CATEGORIES_IMPORTED: 'Dynamic_Categories_Imported',
791+
DYNAMIC_SPEND_CATEGORY_SELECTOR: 'Dynamic_Spend_Category_Selector',
792+
DYNAMIC_DEFAULT_CATEGORY_SELECTOR: 'Dynamic_Default_Category_Selector',
791793
MORE_FEATURES: 'Workspace_More_Features',
792794
MEMBER_DETAILS: 'Workspace_Member_Details',
793795
MEMBER_DETAILS_ROLE: 'Workspace_Member_Details_Role',

src/components/CategorySelector/CategorySelectorModal.tsx

Lines changed: 0 additions & 64 deletions
This file was deleted.

src/components/CategorySelector/index.tsx

Lines changed: 0 additions & 75 deletions
This file was deleted.
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import React from 'react';
2+
import type {StyleProp, ViewStyle} from 'react-native';
3+
import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription';
4+
import useThemeStyles from '@hooks/useThemeStyles';
5+
import {getDecodedCategoryName} from '@libs/CategoryUtils';
6+
import createDynamicRoute from '@libs/Navigation/helpers/dynamicRoutesUtils/createDynamicRoute';
7+
import Navigation from '@libs/Navigation/Navigation';
8+
import CONST from '@src/CONST';
9+
import {DYNAMIC_ROUTES} from '@src/ROUTES';
10+
11+
type CustomUnitDefaultCategorySelectorProps = {
12+
/** Currently selected category */
13+
defaultValue?: string;
14+
15+
/** Label to display on field */
16+
label: string;
17+
18+
/** Any additional styles to apply */
19+
wrapperStyle: StyleProp<ViewStyle>;
20+
21+
/** Whether item is focused or active */
22+
focused?: boolean;
23+
24+
/** The custom unit ID to update when selecting a category */
25+
customUnitID: string;
26+
};
27+
28+
function CustomUnitDefaultCategorySelector({defaultValue = '', wrapperStyle, label, focused, customUnitID}: CustomUnitDefaultCategorySelectorProps) {
29+
const styles = useThemeStyles();
30+
31+
const decodedCategoryName = getDecodedCategoryName(defaultValue);
32+
const descStyle = decodedCategoryName.length === 0 ? styles.textNormal : null;
33+
34+
const onPress = () => {
35+
Navigation.navigate(createDynamicRoute(DYNAMIC_ROUTES.DEFAULT_CATEGORY_SELECTOR.getRoute(customUnitID)));
36+
};
37+
38+
return (
39+
<MenuItemWithTopDescription
40+
shouldShowRightIcon
41+
title={decodedCategoryName}
42+
description={label}
43+
descriptionTextStyle={descStyle}
44+
onPress={onPress}
45+
wrapperStyle={wrapperStyle}
46+
focused={focused}
47+
sentryLabel={CONST.SENTRY_LABEL.WORKSPACE.RULES.CATEGORY_SELECTOR}
48+
/>
49+
);
50+
}
51+
52+
export default CustomUnitDefaultCategorySelector;

src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -528,6 +528,8 @@ const SettingsModalStackNavigator = createModalStackNavigator<SettingsNavigatorP
528528
[SCREENS.WORKSPACE.CATEGORIES_SETTINGS]: () => require<ReactComponentModule>('../../../../pages/workspace/categories/WorkspaceCategoriesSettingsPage').default,
529529
[SCREENS.WORKSPACE.DYNAMIC_CATEGORIES_IMPORT]: () => require<ReactComponentModule>('../../../../pages/workspace/categories/ImportCategoriesPage').default,
530530
[SCREENS.WORKSPACE.DYNAMIC_CATEGORIES_IMPORTED]: () => require<ReactComponentModule>('../../../../pages/workspace/categories/ImportedCategoriesPage').default,
531+
[SCREENS.WORKSPACE.DYNAMIC_SPEND_CATEGORY_SELECTOR]: () => require<ReactComponentModule>('../../../../pages/workspace/categories/DynamicSpendCategorySelectorPage').default,
532+
[SCREENS.WORKSPACE.DYNAMIC_DEFAULT_CATEGORY_SELECTOR]: () => require<ReactComponentModule>('../../../../pages/workspace/categories/DynamicDefaultCategorySelectorPage').default,
531533
[SCREENS.WORKSPACE.UPGRADE]: () => require<ReactComponentModule>('../../../../pages/workspace/upgrade/WorkspaceUpgradePage').default,
532534
[SCREENS.WORKSPACE.DOWNGRADE]: () => require<ReactComponentModule>('../../../../pages/workspace/downgrade/WorkspaceDowngradePage').default,
533535
[SCREENS.WORKSPACE.PAY_AND_DOWNGRADE]: () => require<ReactComponentModule>('../../../../pages/workspace/downgrade/PayAndDowngradePage').default,

src/libs/Navigation/linkingConfig/config.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1002,6 +1002,8 @@ const config: LinkingOptions<RootNavigatorParamList>['config'] = {
10021002
},
10031003
[SCREENS.WORKSPACE.DYNAMIC_CATEGORIES_IMPORT]: DYNAMIC_ROUTES.WORKSPACE_CATEGORIES_IMPORT.path,
10041004
[SCREENS.WORKSPACE.DYNAMIC_CATEGORIES_IMPORTED]: DYNAMIC_ROUTES.WORKSPACE_CATEGORIES_IMPORTED.path,
1005+
[SCREENS.WORKSPACE.DYNAMIC_SPEND_CATEGORY_SELECTOR]: DYNAMIC_ROUTES.SPEND_CATEGORY_SELECTOR.path,
1006+
[SCREENS.WORKSPACE.DYNAMIC_DEFAULT_CATEGORY_SELECTOR]: DYNAMIC_ROUTES.DEFAULT_CATEGORY_SELECTOR.path,
10051007
[SCREENS.WORKSPACE.WORKFLOWS_PAYER]: {
10061008
path: ROUTES.WORKSPACE_WORKFLOWS_PAYER.route,
10071009
},

src/libs/Navigation/types.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -445,6 +445,14 @@ type SettingsNavigatorParamList = {
445445
// eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
446446
backTo?: Routes;
447447
};
448+
[SCREENS.WORKSPACE.DYNAMIC_SPEND_CATEGORY_SELECTOR]: {
449+
policyID: string;
450+
groupID: string;
451+
};
452+
[SCREENS.WORKSPACE.DYNAMIC_DEFAULT_CATEGORY_SELECTOR]: {
453+
policyID: string;
454+
customUnitID: string;
455+
};
448456
[SCREENS.WORKSPACE.DYNAMIC_CATEGORIES_IMPORT]: {
449457
policyID: string;
450458
};
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import React from 'react';
2+
import CategoryPicker from '@components/CategoryPicker';
3+
import HeaderWithBackButton from '@components/HeaderWithBackButton';
4+
import ScreenWrapper from '@components/ScreenWrapper';
5+
import type {ListItem} from '@components/SelectionList/types';
6+
import useDynamicBackPath from '@hooks/useDynamicBackPath';
7+
import useLocalize from '@hooks/useLocalize';
8+
import useOnyx from '@hooks/useOnyx';
9+
import useThemeStyles from '@hooks/useThemeStyles';
10+
import getNonEmptyStringOnyxID from '@libs/getNonEmptyStringOnyxID';
11+
import Navigation from '@libs/Navigation/Navigation';
12+
import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types';
13+
import type {SettingsNavigatorParamList} from '@libs/Navigation/types';
14+
import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper';
15+
import {setPolicyCustomUnitDefaultCategory} from '@userActions/Policy/Category';
16+
import CONST from '@src/CONST';
17+
import ONYXKEYS from '@src/ONYXKEYS';
18+
import {DYNAMIC_ROUTES} from '@src/ROUTES';
19+
import type SCREENS from '@src/SCREENS';
20+
21+
type DynamicDefaultCategorySelectorPageProps = PlatformStackScreenProps<SettingsNavigatorParamList, typeof SCREENS.WORKSPACE.DYNAMIC_DEFAULT_CATEGORY_SELECTOR>;
22+
23+
function DynamicDefaultCategorySelectorPage({route}: DynamicDefaultCategorySelectorPageProps) {
24+
const {policyID, customUnitID} = route.params;
25+
const styles = useThemeStyles();
26+
const {translate} = useLocalize();
27+
const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${getNonEmptyStringOnyxID(policyID)}`);
28+
const currentCategory = policy?.customUnits?.[customUnitID]?.defaultCategory ?? '';
29+
const backPath = useDynamicBackPath(DYNAMIC_ROUTES.DEFAULT_CATEGORY_SELECTOR.path);
30+
31+
const onCategorySelected = (selectedCategory: ListItem) => {
32+
if (!selectedCategory.searchText) {
33+
return;
34+
}
35+
if (currentCategory === selectedCategory.searchText) {
36+
Navigation.goBack(backPath);
37+
return;
38+
}
39+
setPolicyCustomUnitDefaultCategory(policyID, customUnitID, currentCategory, selectedCategory.searchText);
40+
Navigation.goBack(backPath);
41+
};
42+
43+
return (
44+
<AccessOrNotFoundWrapper
45+
policyID={policyID}
46+
accessVariants={[CONST.POLICY.ACCESS_VARIANTS.ADMIN, CONST.POLICY.ACCESS_VARIANTS.PAID]}
47+
featureName={CONST.POLICY.MORE_FEATURES.ARE_CATEGORIES_ENABLED}
48+
>
49+
<ScreenWrapper
50+
style={styles.pb0}
51+
enableEdgeToEdgeBottomSafeAreaPadding
52+
shouldEnableKeyboardAvoidingView={false}
53+
testID="DynamicDefaultCategorySelectorPage"
54+
>
55+
<HeaderWithBackButton
56+
title={translate('workspace.common.defaultCategory')}
57+
shouldShowBackButton
58+
onBackButtonPress={() => Navigation.goBack(backPath)}
59+
/>
60+
<CategoryPicker
61+
policyID={policyID}
62+
selectedCategory={currentCategory}
63+
onSubmit={onCategorySelected}
64+
addBottomSafeAreaPadding
65+
/>
66+
</ScreenWrapper>
67+
</AccessOrNotFoundWrapper>
68+
);
69+
}
70+
71+
export default DynamicDefaultCategorySelectorPage;
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import React from 'react';
2+
import CategoryPicker from '@components/CategoryPicker';
3+
import HeaderWithBackButton from '@components/HeaderWithBackButton';
4+
import ScreenWrapper from '@components/ScreenWrapper';
5+
import type {ListItem} from '@components/SelectionList/types';
6+
import useDynamicBackPath from '@hooks/useDynamicBackPath';
7+
import useOnyx from '@hooks/useOnyx';
8+
import useThemeStyles from '@hooks/useThemeStyles';
9+
import getNonEmptyStringOnyxID from '@libs/getNonEmptyStringOnyxID';
10+
import Navigation from '@libs/Navigation/Navigation';
11+
import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types';
12+
import type {SettingsNavigatorParamList} from '@libs/Navigation/types';
13+
import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper';
14+
import {setWorkspaceDefaultSpendCategory} from '@userActions/Policy/Policy';
15+
import CONST from '@src/CONST';
16+
import ONYXKEYS from '@src/ONYXKEYS';
17+
import {DYNAMIC_ROUTES} from '@src/ROUTES';
18+
import type SCREENS from '@src/SCREENS';
19+
20+
type DynamicSpendCategorySelectorPageProps = PlatformStackScreenProps<SettingsNavigatorParamList, typeof SCREENS.WORKSPACE.DYNAMIC_SPEND_CATEGORY_SELECTOR>;
21+
22+
function DynamicSpendCategorySelectorPage({route}: DynamicSpendCategorySelectorPageProps) {
23+
const {policyID, groupID} = route.params;
24+
const styles = useThemeStyles();
25+
const backPath = useDynamicBackPath(DYNAMIC_ROUTES.SPEND_CATEGORY_SELECTOR.path);
26+
27+
const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${getNonEmptyStringOnyxID(policyID)}`);
28+
const label = groupID.charAt(0).toUpperCase() + groupID.slice(1);
29+
const currentCategory = policy?.mccGroup?.[groupID]?.category ?? '';
30+
31+
const onCategorySelected = (selectedCategory: ListItem) => {
32+
if (!selectedCategory.keyForList) {
33+
return;
34+
}
35+
if (currentCategory === selectedCategory.keyForList) {
36+
Navigation.goBack(backPath);
37+
return;
38+
}
39+
setWorkspaceDefaultSpendCategory(policyID, groupID, selectedCategory.keyForList, policy?.mccGroup);
40+
Navigation.goBack(backPath);
41+
};
42+
43+
return (
44+
<AccessOrNotFoundWrapper
45+
policyID={policyID}
46+
accessVariants={[CONST.POLICY.ACCESS_VARIANTS.ADMIN, CONST.POLICY.ACCESS_VARIANTS.PAID]}
47+
featureName={CONST.POLICY.MORE_FEATURES.ARE_CATEGORIES_ENABLED}
48+
>
49+
<ScreenWrapper
50+
style={styles.pb0}
51+
enableEdgeToEdgeBottomSafeAreaPadding
52+
shouldEnableKeyboardAvoidingView={false}
53+
testID="DynamicSpendCategorySelectorPage"
54+
>
55+
<HeaderWithBackButton
56+
title={label}
57+
shouldShowBackButton
58+
onBackButtonPress={() => Navigation.goBack(backPath)}
59+
/>
60+
<CategoryPicker
61+
policyID={policyID}
62+
selectedCategory={currentCategory}
63+
onSubmit={onCategorySelected}
64+
addBottomSafeAreaPadding
65+
/>
66+
</ScreenWrapper>
67+
</AccessOrNotFoundWrapper>
68+
);
69+
}
70+
71+
export default DynamicSpendCategorySelectorPage;

0 commit comments

Comments
 (0)