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

fix: categorize all expense switch is not disabled #39036

Merged
merged 13 commits into from
Apr 3, 2024
2 changes: 1 addition & 1 deletion src/libs/OptionsListUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -827,7 +827,7 @@ function getSearchValueForPhoneOrEmail(searchTerm: string) {
* Verifies that there is at least one enabled option
*/
function hasEnabledOptions(options: PolicyCategories | PolicyTag[]): boolean {
return Object.values(options).some((option) => option.enabled);
return Object.values(options).some((option) => option.enabled && option.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE);
}

/**
Expand Down
109 changes: 90 additions & 19 deletions src/libs/actions/Policy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ import getIsNarrowLayout from '@libs/getIsNarrowLayout';
import Log from '@libs/Log';
import Navigation from '@libs/Navigation/Navigation';
import * as NumberUtils from '@libs/NumberUtils';
import * as OptionsListUtils from '@libs/OptionsListUtils';
import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils';
import * as PhoneNumber from '@libs/PhoneNumber';
import * as PolicyUtils from '@libs/PolicyUtils';
Expand Down Expand Up @@ -2872,27 +2873,29 @@ function createWorkspaceFromIOUPayment(iouReport: Report | EmptyObject): string

function setWorkspaceCategoryEnabled(policyID: string, categoriesToUpdate: Record<string, {name: string; enabled: boolean}>) {
const policyCategories = allPolicyCategories?.[`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyID}`] ?? {};
const policy = allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${policyID}`];
const optimisticPolicyCategoriesData = {
...Object.keys(categoriesToUpdate).reduce<PolicyCategories>((acc, key) => {
acc[key] = {
...policyCategories[key],
...categoriesToUpdate[key],
errors: null,
pendingFields: {
enabled: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE,
},
pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE,
};

return acc;
}, {}),
};
const shouldDisableRequiresCategory = !OptionsListUtils.hasEnabledOptions({...policyCategories, ...optimisticPolicyCategoriesData});
const onyxData: OnyxData = {
optimisticData: [
{
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyID}`,
value: {
...Object.keys(categoriesToUpdate).reduce<PolicyCategories>((acc, key) => {
acc[key] = {
...policyCategories[key],
...categoriesToUpdate[key],
errors: null,
pendingFields: {
enabled: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE,
},
pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE,
};

return acc;
}, {}),
},
value: optimisticPolicyCategoriesData,
},
],
successData: [
Expand Down Expand Up @@ -2938,6 +2941,37 @@ function setWorkspaceCategoryEnabled(policyID: string, categoriesToUpdate: Recor
},
],
};
if (shouldDisableRequiresCategory) {
Copy link
Contributor

Choose a reason for hiding this comment

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

can we do the same for deleteWorkspaceCategories?

function deleteWorkspaceCategories(policyID: string, categoryNamesToDelete: string[]) {

Screen.Recording.2024-04-02.at.9.55.11.at.night.mov

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I updated to do the same when deleting category

Copy link
Contributor

Choose a reason for hiding this comment

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

Nice catch!

onyxData.optimisticData?.push({
tienifr marked this conversation as resolved.
Show resolved Hide resolved
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
value: {
requiresCategory: false,
pendingFields: {
requiresCategory: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE,
},
},
});
onyxData.successData?.push({
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
value: {
pendingFields: {
requiresCategory: null,
},
},
});
onyxData.failureData?.push({
tienifr marked this conversation as resolved.
Show resolved Hide resolved
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
value: {
requiresCategory: policy?.requiresCategory,
pendingFields: {
requiresCategory: null,
},
},
});
}

const parameters = {
policyID,
Expand Down Expand Up @@ -3437,15 +3471,21 @@ function clearCategoryErrors(policyID: string, categoryName: string) {
}

function deleteWorkspaceCategories(policyID: string, categoryNamesToDelete: string[]) {
const policy = allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${policyID}`];
const policyCategories = allPolicyCategories?.[`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyID}`] ?? {};
const optimisticPolicyCategoriesData = categoryNamesToDelete.reduce<Record<string, Partial<PolicyCategory>>>((acc, categoryName) => {
acc[categoryName] = {pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE};
return acc;
}, {});
const shouldDisableRequiresCategory = !OptionsListUtils.hasEnabledOptions(
Object.values(policyCategories).filter((category) => !categoryNamesToDelete.includes(category.name) && category.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE),
);
const onyxData: OnyxData = {
optimisticData: [
{
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyID}`,
value: categoryNamesToDelete.reduce<Record<string, Partial<PolicyCategory>>>((acc, categoryName) => {
acc[categoryName] = {pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE};
return acc;
}, {}),
value: optimisticPolicyCategoriesData,
},
],
successData: [
Expand All @@ -3472,6 +3512,37 @@ function deleteWorkspaceCategories(policyID: string, categoryNamesToDelete: stri
},
],
};
if (shouldDisableRequiresCategory) {
onyxData.optimisticData?.push({
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
value: {
requiresCategory: false,
pendingFields: {
requiresCategory: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE,
},
},
});
onyxData.successData?.push({
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
value: {
pendingFields: {
requiresCategory: null,
},
},
});
onyxData.failureData?.push({
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
value: {
requiresCategory: policy?.requiresCategory,
pendingFields: {
requiresCategory: null,
},
},
});
}

const parameters = {
policyID,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import type {StackScreenProps} from '@react-navigation/stack';
import React from 'react';
import {View} from 'react-native';
import type {OnyxEntry} from 'react-native-onyx';
import {withOnyx} from 'react-native-onyx';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
import OfflineWithFeedback from '@components/OfflineWithFeedback';
import ScreenWrapper from '@components/ScreenWrapper';
Expand All @@ -9,23 +11,32 @@ import Text from '@components/Text';
import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
import {setWorkspaceRequiresCategory} from '@libs/actions/Policy';
import * as OptionsListUtils from '@libs/OptionsListUtils';
import type {SettingsNavigatorParamList} from '@navigation/types';
import AdminPolicyAccessOrNotFoundWrapper from '@pages/workspace/AdminPolicyAccessOrNotFoundWrapper';
import FeatureEnabledAccessOrNotFoundWrapper from '@pages/workspace/FeatureEnabledAccessOrNotFoundWrapper';
import PaidPolicyAccessOrNotFoundWrapper from '@pages/workspace/PaidPolicyAccessOrNotFoundWrapper';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import type SCREENS from '@src/SCREENS';
import type * as OnyxTypes from '@src/types/onyx';

type WorkspaceCategoriesSettingsPageProps = StackScreenProps<SettingsNavigatorParamList, typeof SCREENS.WORKSPACE.CATEGORIES_SETTINGS>;
type WorkspaceCategoriesSettingsPageOnyxProps = {
/** Collection of categories attached to a policy */
policyCategories: OnyxEntry<OnyxTypes.PolicyCategories>;
};

function WorkspaceCategoriesSettingsPage({route}: WorkspaceCategoriesSettingsPageProps) {
type WorkspaceCategoriesSettingsPageProps = WorkspaceCategoriesSettingsPageOnyxProps & StackScreenProps<SettingsNavigatorParamList, typeof SCREENS.WORKSPACE.CATEGORIES_SETTINGS>;

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

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

const hasEnabledOptions = OptionsListUtils.hasEnabledOptions(policyCategories ?? {});
return (
<AdminPolicyAccessOrNotFoundWrapper policyID={route.params.policyID}>
<PaidPolicyAccessOrNotFoundWrapper policyID={route.params.policyID}>
Expand Down Expand Up @@ -53,7 +64,7 @@ function WorkspaceCategoriesSettingsPage({route}: WorkspaceCategoriesSettingsPag
isOn={policy?.requiresCategory ?? false}
accessibilityLabel={translate('workspace.categories.requiresCategory')}
onToggle={updateWorkspaceRequiresCategory}
disabled={!policy?.areCategoriesEnabled}
disabled={!policy?.areCategoriesEnabled || !hasEnabledOptions}
/>
</View>
</View>
Expand All @@ -69,4 +80,8 @@ function WorkspaceCategoriesSettingsPage({route}: WorkspaceCategoriesSettingsPag

WorkspaceCategoriesSettingsPage.displayName = 'WorkspaceCategoriesSettingsPage';

export default WorkspaceCategoriesSettingsPage;
export default withOnyx<WorkspaceCategoriesSettingsPageProps, WorkspaceCategoriesSettingsPageOnyxProps>({
policyCategories: {
Copy link
Contributor

Choose a reason for hiding this comment

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

Looks like we're back at it again 🙂 #37508 (comment)

Copy link
Contributor Author

@tienifr tienifr Apr 2, 2024

Choose a reason for hiding this comment

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

@getusha

Yeah. Previously, I said that:

It's much less performant since we need to connect to the entire policyCategories in Onyx. And anywhere we use such are categories enabled logic, there'll be this overhead and duplicated logic.

Meanwhile we can just use areCategoriesEnabled which is an existing field designed for this very purpose (more details #37508 (comment)). There's no overhead since the places that need this use case already have the policy object passed in.

It is because we can use areCategoriesEnabled in this case. But now, we only turn off the requiresCategories, so I think to address the issue, we still need to subscribe the policyCategories

key: ({route}) => `${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${route.params.policyID}`,
},
})(WorkspaceCategoriesSettingsPage);
6 changes: 3 additions & 3 deletions src/types/onyx/PolicyTag.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type * as OnyxCommon from './OnyxCommon';

type PolicyTag = {
type PolicyTag = OnyxCommon.OnyxValueWithOfflineFeedback<{
/** Name of a Tag */
name: string;

Expand All @@ -13,9 +13,9 @@ type PolicyTag = {

/** A list of errors keyed by microtime */
errors?: OnyxCommon.Errors | null;
};
}>;

type PolicyTags = Record<string, OnyxCommon.OnyxValueWithOfflineFeedback<PolicyTag>>;
type PolicyTags = Record<string, PolicyTag>;

type PolicyTagList<T extends string = string> = Record<
T,
Expand Down
Loading