Skip to content

Commit

Permalink
Merge pull request #37133 from ArekChr/feat/workspace_categories_sett…
Browse files Browse the repository at this point in the history
…ings

Feat/workspace categories settings
  • Loading branch information
luacmartins authored Feb 27, 2024
2 parents 13e2944 + b69d717 commit 04644d8
Show file tree
Hide file tree
Showing 13 changed files with 168 additions and 1 deletion.
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
6 changes: 6 additions & 0 deletions src/libs/API/parameters/SetWorkspaceRequiresCategoryParams.ts
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
1 change: 1 addition & 0 deletions src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,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
63 changes: 63 additions & 0 deletions src/pages/workspace/categories/WorkspaceCategoriesSettingsPage.tsx
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>
<Switch
isOn={policy?.requiresCategory ?? false}
accessibilityLabel={translate('workspace.categories.requiresCategory')}
onToggle={updateWorkspaceRequiresCategory}
/>
</View>
</View>
</OfflineWithFeedback>
</ScreenWrapper>
)}
</PaidPolicyAccessOrNotFoundWrapper>
</AdminPolicyAccessOrNotFoundWrapper>
);
}

WorkspaceCategoriesSettingsPage.displayName = 'WorkspaceCategoriesSettingsPage';

export default WorkspaceCategoriesSettingsPage;

0 comments on commit 04644d8

Please sign in to comment.