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

Feat/workspace categories settings #37133

Merged
5 changes: 5 additions & 0 deletions src/ROUTES.ts
Original file line number Diff line number Diff line change
Expand Up @@ -526,6 +526,11 @@ const ROUTES = {
route: 'workspace/:policyID/categories',
getRoute: (policyID: string) => `workspace/${policyID}/categories` as const,
},
WORKSPACE_CATEGORIES_SETTINGS: {
route: 'workspace/:policyID/categories/settings',
getRoute: (policyID: string) => `workspace/${policyID}/categories/settings` as const,
},

// Referral program promotion
REFERRAL_DETAILS_MODAL: {
route: 'referral/:contentType',
Expand Down
1 change: 1 addition & 0 deletions src/SCREENS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,7 @@ const SCREENS = {
DESCRIPTION: 'Workspace_Profile_Description',
SHARE: 'Workspace_Profile_Share',
NAME: 'Workspace_Profile_Name',
CATEGORIES_SETTINGS: 'Categories_Settings',
},

EDIT_REQUEST: {
Expand Down
2 changes: 2 additions & 0 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1739,6 +1739,8 @@ export default {
collect: 'Collect',
},
categories: {
requiresCategory: 'Members must categorize all spend',
enableCategory: 'Enable category',
subtitle: 'Get a better overview of where money is being spent. Use our default categories or add your own.',
emptyCategories: {
title: "You haven't created any categories",
Expand Down
2 changes: 2 additions & 0 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1763,6 +1763,8 @@ export default {
collect: 'Recolectar',
},
categories: {
requiresCategory: 'Los miembros deben categorizar todos los gastos',
enableCategory: 'Activar categoría',
subtitle: 'Obtén una visión general de dónde te gastas el dinero. Utiliza las categorías predeterminadas o añade las tuyas propias.',
emptyCategories: {
title: 'No has creado ninguna categoría',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
type SetWorkspaceRequiresCategoryParams = {
policyID: string;
requiresCategory: boolean;
};

export default SetWorkspaceRequiresCategoryParams;
1 change: 1 addition & 0 deletions src/libs/API/parameters/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ export type {default as UnHoldMoneyRequestParams} from './UnHoldMoneyRequestPara
export type {default as CancelPaymentParams} from './CancelPaymentParams';
export type {default as AcceptACHContractForBankAccount} from './AcceptACHContractForBankAccount';
export type {default as UpdateWorkspaceDescriptionParams} from './UpdateWorkspaceDescriptionParams';
export type {default as SetWorkspaceRequiresCategoryParams} from './SetWorkspaceRequiresCategoryParams';
export type {default as SetWorkspaceAutoReportingParams} from './SetWorkspaceAutoReportingParams';
export type {default as SetWorkspaceApprovalModeParams} from './SetWorkspaceApprovalModeParams';
export type {default as SwitchToOldDotParams} from './SwitchToOldDotParams';
2 changes: 2 additions & 0 deletions src/libs/API/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ const WRITE_COMMANDS = {
UPDATE_WORKSPACE_DESCRIPTION: 'UpdateWorkspaceDescription',
CREATE_WORKSPACE: 'CreateWorkspace',
CREATE_WORKSPACE_FROM_IOU_PAYMENT: 'CreateWorkspaceFromIOUPayment',
SET_WORKSPACE_REQUIRES_CATEGORY: 'SetWorkspaceRequiresCategory',
CREATE_TASK: 'CreateTask',
CANCEL_TASK: 'CancelTask',
EDIT_TASK_ASSIGNEE: 'EditTaskAssignee',
Expand Down Expand Up @@ -255,6 +256,7 @@ type WriteCommandParameters = {
[WRITE_COMMANDS.UPDATE_WORKSPACE_CUSTOM_UNIT_AND_RATE]: Parameters.UpdateWorkspaceCustomUnitAndRateParams;
[WRITE_COMMANDS.CREATE_WORKSPACE]: Parameters.CreateWorkspaceParams;
[WRITE_COMMANDS.CREATE_WORKSPACE_FROM_IOU_PAYMENT]: Parameters.CreateWorkspaceFromIOUPaymentParams;
[WRITE_COMMANDS.SET_WORKSPACE_REQUIRES_CATEGORY]: Parameters.SetWorkspaceRequiresCategoryParams;
[WRITE_COMMANDS.CREATE_TASK]: Parameters.CreateTaskParams;
[WRITE_COMMANDS.CANCEL_TASK]: Parameters.CancelTaskParams;
[WRITE_COMMANDS.EDIT_TASK_ASSIGNEE]: Parameters.EditTaskAssigneeParams;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,7 @@ const SettingsModalStackNavigator = createModalStackNavigator<SettingsNavigatorP
[SCREENS.WORKSPACE.DESCRIPTION]: () => require('../../../pages/workspace/WorkspaceProfileDescriptionPage').default as React.ComponentType,
[SCREENS.WORKSPACE.SHARE]: () => require('../../../pages/workspace/WorkspaceProfileSharePage').default as React.ComponentType,
[SCREENS.WORKSPACE.CURRENCY]: () => require('../../../pages/workspace/WorkspaceProfileCurrencyPage').default as React.ComponentType,
[SCREENS.WORKSPACE.CATEGORIES_SETTINGS]: () => require('../../../pages/workspace/categories/WorkspaceCategoriesSettingsPage').default as React.ComponentType,
[SCREENS.REIMBURSEMENT_ACCOUNT]: () => require('../../../pages/ReimbursementAccount/ReimbursementAccountPage').default as React.ComponentType,
[SCREENS.GET_ASSISTANCE]: () => require('../../../pages/GetAssistancePage').default as React.ComponentType,
[SCREENS.SETTINGS.TWO_FACTOR_AUTH]: () => require('../../../pages/settings/Security/TwoFactorAuth/TwoFactorAuthPage').default as React.ComponentType,
Expand Down
3 changes: 3 additions & 0 deletions src/libs/Navigation/linkingConfig/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,9 @@ const config: LinkingOptions<RootStackParamList>['config'] = {
[SCREENS.WORKSPACE.INVITE_MESSAGE]: {
path: ROUTES.WORKSPACE_INVITE_MESSAGE.route,
},
[SCREENS.WORKSPACE.CATEGORIES_SETTINGS]: {
path: ROUTES.WORKSPACE_CATEGORIES_SETTINGS.route,
},
[SCREENS.REIMBURSEMENT_ACCOUNT]: {
path: ROUTES.BANK_ACCOUNT_WITH_STEP_TO_OPEN.route,
exact: true,
Expand Down
3 changes: 3 additions & 0 deletions src/libs/Navigation/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,9 @@ type SettingsNavigatorParamList = {
[SCREENS.WORKSPACE.INVITE_MESSAGE]: {
policyID: string;
};
[SCREENS.WORKSPACE.CATEGORIES_SETTINGS]: {
policyID: string;
};
[SCREENS.GET_ASSISTANCE]: {
backTo: Routes;
};
Expand Down
56 changes: 56 additions & 0 deletions src/libs/actions/Policy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ import type {
} from '@src/types/onyx';
import type {Errors} from '@src/types/onyx/OnyxCommon';
import type {Attributes, CustomUnit, Rate, Unit} from '@src/types/onyx/Policy';
import type {OnyxData} from '@src/types/onyx/Request';
import type {EmptyObject} from '@src/types/utils/EmptyObject';
import {isEmptyObject} from '@src/types/utils/EmptyObject';

Expand Down Expand Up @@ -2178,6 +2179,60 @@ function createWorkspaceFromIOUPayment(iouReport: Report | EmptyObject): string
return policyID;
}

const setWorkspaceRequiresCategory = (policyID: string, requiresCategory: boolean) => {
const onyxData: OnyxData = {
optimisticData: [
{
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
value: {
requiresCategory,
errors: {
requiresCategory: null,
},
pendingFields: {
requiresCategory: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE,
},
},
},
],
successData: [
{
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
value: {
errors: {
requiresCategory: null,
},
pendingFields: {
requiresCategory: null,
},
},
},
],
failureData: [
{
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
value: {
requiresCategory: !requiresCategory,
errors: ErrorUtils.getMicroSecondOnyxError('workspace.categories.genericFailureMessage'),
pendingFields: {
requiresCategory: null,
},
},
},
],
};

const parameters = {
policyID,
requiresCategory,
};

API.write('SetWorkspaceRequiresCategory', parameters, onyxData);
};

export {
removeMembers,
addMembersToWorkspace,
Expand Down Expand Up @@ -2221,4 +2276,5 @@ export {
setWorkspaceAutoReporting,
setWorkspaceApprovalMode,
updateWorkspaceDescription,
setWorkspaceRequiresCategory,
};
24 changes: 23 additions & 1 deletion src/pages/workspace/categories/WorkspaceCategoriesPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import React, {useMemo, useState} from 'react';
import {View} from 'react-native';
import {withOnyx} from 'react-native-onyx';
import type {OnyxEntry} from 'react-native-onyx';
import Button from '@components/Button';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
import Icon from '@components/Icon';
import * as Expensicons from '@components/Icon/Expensicons';
Expand All @@ -16,10 +17,12 @@ import useLocalize from '@hooks/useLocalize';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import useWindowDimensions from '@hooks/useWindowDimensions';
import Navigation from '@libs/Navigation/Navigation';
import type {CentralPaneNavigatorParamList} from '@navigation/types';
import AdminPolicyAccessOrNotFoundWrapper from '@pages/workspace/AdminPolicyAccessOrNotFoundWrapper';
import PaidPolicyAccessOrNotFoundWrapper from '@pages/workspace/PaidPolicyAccessOrNotFoundWrapper';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import type SCREENS from '@src/SCREENS';
import type * as OnyxTypes from '@src/types/onyx';

Expand Down Expand Up @@ -86,6 +89,22 @@ function WorkspaceCategoriesPage({policyCategories, route}: WorkspaceCategoriesP
</View>
);

const navigateToCategorySettings = () => {
Navigation.navigate(ROUTES.WORKSPACE_CATEGORIES_SETTINGS.getRoute(route.params.policyID));
};

const settingsButton = (
<View style={[styles.w100, styles.flexRow, isSmallScreenWidth && styles.mb3]}>
<Button
medium
onPress={navigateToCategorySettings}
icon={Expensicons.Gear}
text={translate('common.settings')}
style={[isSmallScreenWidth && styles.w50]}
/>
</View>
);

return (
<AdminPolicyAccessOrNotFoundWrapper policyID={route.params.policyID}>
<PaidPolicyAccessOrNotFoundWrapper policyID={route.params.policyID}>
Expand All @@ -99,7 +118,10 @@ function WorkspaceCategoriesPage({policyCategories, route}: WorkspaceCategoriesP
icon={Illustrations.FolderOpen}
title={translate('workspace.common.categories')}
shouldShowBackButton={isSmallScreenWidth}
/>
>
{!isSmallScreenWidth && settingsButton}
</HeaderWithBackButton>
{isSmallScreenWidth && <View style={[styles.pl5, styles.pr5]}>{settingsButton}</View>}
<View style={[styles.ph5, styles.pb5]}>
<Text style={[styles.textNormal, styles.colorMuted]}>{translate('workspace.categories.subtitle')}</Text>
</View>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import type {StackScreenProps} from '@react-navigation/stack';
import React from 'react';
import {View} from 'react-native';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
import OfflineWithFeedback from '@components/OfflineWithFeedback';
import ScreenWrapper from '@components/ScreenWrapper';
import Switch from '@components/Switch';
import Text from '@components/Text';
import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
import {setWorkspaceRequiresCategory} from '@libs/actions/Policy';
import type {SettingsNavigatorParamList} from '@navigation/types';
import AdminPolicyAccessOrNotFoundWrapper from '@pages/workspace/AdminPolicyAccessOrNotFoundWrapper';
import PaidPolicyAccessOrNotFoundWrapper from '@pages/workspace/PaidPolicyAccessOrNotFoundWrapper';
import type SCREENS from '@src/SCREENS';
import type * as OnyxCommon from '@src/types/onyx/OnyxCommon';

type WorkspaceCategoriesSettingsPageProps = StackScreenProps<SettingsNavigatorParamList, typeof SCREENS.WORKSPACE.CATEGORIES_SETTINGS>;

function WorkspaceCategoriesSettingsPage({route}: WorkspaceCategoriesSettingsPageProps) {
const styles = useThemeStyles();
const {translate} = useLocalize();

const updateWorkspaceRequiresCategory = (value: boolean) => {
setWorkspaceRequiresCategory(route.params.policyID, value);
};

return (
<AdminPolicyAccessOrNotFoundWrapper policyID={route.params.policyID}>
<PaidPolicyAccessOrNotFoundWrapper policyID={route.params.policyID}>
{({policy}) => (
<ScreenWrapper
includeSafeAreaPaddingBottom={false}
style={[styles.defaultModalContainer]}
testID={WorkspaceCategoriesSettingsPage.displayName}
>
<HeaderWithBackButton title={translate('common.settings')} />
<OfflineWithFeedback
errors={policy?.errorFields?.requiresCategory}
pendingAction={policy?.pendingFields?.requiresCategory as OnyxCommon.PendingAction}
errorRowStyles={styles.mh5}
>
<View style={[styles.mt2, styles.mh4]}>
<View style={[styles.flexRow, styles.mb5, styles.mr2, styles.alignItemsCenter, styles.justifyContentBetween]}>
<Text style={[styles.textNormal, styles.colorMuted]}>{translate('workspace.categories.requiresCategory')}</Text>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The text does not wrap properly which caused #38370

<Switch
isOn={policy?.requiresCategory ?? false}
accessibilityLabel={translate('workspace.categories.requiresCategory')}
onToggle={updateWorkspaceRequiresCategory}
/>
</View>
</View>
</OfflineWithFeedback>
</ScreenWrapper>
)}
</PaidPolicyAccessOrNotFoundWrapper>
</AdminPolicyAccessOrNotFoundWrapper>
);
}

WorkspaceCategoriesSettingsPage.displayName = 'WorkspaceCategoriesSettingsPage';

export default WorkspaceCategoriesSettingsPage;
Loading