Skip to content

Commit c9a90e0

Browse files
authored
Merge pull request #54451 from abzokhattab/add-toggle-for-enable/disable-instead-of-label
Add toggle for enable/disable instead of label
2 parents 4e5562b + a5d13c8 commit c9a90e0

7 files changed

+176
-51
lines changed

src/CONST.ts

+1
Original file line numberDiff line numberDiff line change
@@ -866,6 +866,7 @@ const CONST = {
866866
EMPTY_ARRAY,
867867
EMPTY_OBJECT,
868868
DEFAULT_NUMBER_ID: 0,
869+
EMPTY_STRING: '',
869870
USE_EXPENSIFY_URL,
870871
EXPENSIFY_URL,
871872
GOOGLE_MEET_URL_ANDROID: 'https://meet.google.com',

src/pages/workspace/categories/WorkspaceCategoriesPage.tsx

+18-4
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,12 @@ import * as Expensicons from '@components/Icon/Expensicons';
1414
import * as Illustrations from '@components/Icon/Illustrations';
1515
import LottieAnimations from '@components/LottieAnimations';
1616
import ScreenWrapper from '@components/ScreenWrapper';
17-
import ListItemRightCaretWithLabel from '@components/SelectionList/ListItemRightCaretWithLabel';
1817
import TableListItem from '@components/SelectionList/TableListItem';
1918
import type {ListItem} from '@components/SelectionList/types';
2019
import SelectionListWithModal from '@components/SelectionListWithModal';
2120
import CustomListHeader from '@components/SelectionListWithModal/CustomListHeader';
2221
import TableListItemSkeleton from '@components/Skeletons/TableRowSkeleton';
22+
import Switch from '@components/Switch';
2323
import Text from '@components/Text';
2424
import TextLink from '@components/TextLink';
2525
import useAutoTurnSelectionModeOffWhenHasNoActiveOption from '@hooks/useAutoTurnSelectionModeOffWhenHasNoActiveOption';
@@ -42,8 +42,8 @@ import type {FullScreenNavigatorParamList} from '@libs/Navigation/types';
4242
import * as PolicyUtils from '@libs/PolicyUtils';
4343
import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper';
4444
import * as Modal from '@userActions/Modal';
45-
import {deleteWorkspaceCategories, setWorkspaceCategoryEnabled} from '@userActions/Policy/Category';
4645
import * as Category from '@userActions/Policy/Category';
46+
import {deleteWorkspaceCategories, setWorkspaceCategoryEnabled} from '@userActions/Policy/Category';
4747
import CONST from '@src/CONST';
4848
import ONYXKEYS from '@src/ONYXKEYS';
4949
import ROUTES from '@src/ROUTES';
@@ -105,6 +105,13 @@ function WorkspaceCategoriesPage({route}: WorkspaceCategoriesPageProps) {
105105
setSelectedCategories({});
106106
}, [isFocused]);
107107

108+
const updateWorkspaceRequiresCategory = useCallback(
109+
(value: boolean, categoryName: string) => {
110+
Category.setWorkspaceCategoryEnabled(policyId, {[categoryName]: {name: categoryName, enabled: value}});
111+
},
112+
[policyId],
113+
);
114+
108115
const categoryList = useMemo<PolicyOption[]>(() => {
109116
const categories = lodashSortBy(Object.values(policyCategories ?? {}), 'name', localeCompare) as PolicyCategory[];
110117
return categories.reduce<PolicyOption[]>((acc, value) => {
@@ -121,12 +128,19 @@ function WorkspaceCategoriesPage({route}: WorkspaceCategoriesPageProps) {
121128
isDisabled,
122129
pendingAction: value.pendingAction,
123130
errors: value.errors ?? undefined,
124-
rightElement: <ListItemRightCaretWithLabel labelText={value.enabled ? translate('workspace.common.enabled') : translate('workspace.common.disabled')} />,
131+
rightElement: (
132+
<Switch
133+
isOn={value.enabled}
134+
disabled={isDisabled}
135+
accessibilityLabel={translate('workspace.categories.enableCategory')}
136+
onToggle={(newValue: boolean) => updateWorkspaceRequiresCategory(newValue, value.name)}
137+
/>
138+
),
125139
});
126140

127141
return acc;
128142
}, []);
129-
}, [policyCategories, isOffline, selectedCategories, canSelectMultiple, translate]);
143+
}, [policyCategories, isOffline, selectedCategories, canSelectMultiple, translate, updateWorkspaceRequiresCategory]);
130144

131145
useAutoTurnSelectionModeOffWhenHasNoActiveOption(categoryList);
132146

src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx

+35-8
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@ import HeaderWithBackButton from '@components/HeaderWithBackButton';
88
import * as Expensicons from '@components/Icon/Expensicons';
99
import * as Illustrations from '@components/Icon/Illustrations';
1010
import ScreenWrapper from '@components/ScreenWrapper';
11-
import ListItemRightCaretWithLabel from '@components/SelectionList/ListItemRightCaretWithLabel';
1211
import TableListItem from '@components/SelectionList/TableListItem';
1312
import type {ListItem} from '@components/SelectionList/types';
1413
import SelectionListWithModal from '@components/SelectionListWithModal';
1514
import CustomListHeader from '@components/SelectionListWithModal/CustomListHeader';
15+
import Switch from '@components/Switch';
1616
import Text from '@components/Text';
1717
import useLocalize from '@hooks/useLocalize';
1818
import useMobileSelectionMode from '@hooks/useMobileSelectionMode';
@@ -74,11 +74,11 @@ function PolicyDistanceRatesPage({
7474
const dismissError = useCallback(
7575
(item: RateForList) => {
7676
if (customUnitRates[item.value].errors) {
77-
DistanceRate.clearDeleteDistanceRateError(policyID, customUnit?.customUnitID ?? '', item.value);
77+
DistanceRate.clearDeleteDistanceRateError(policyID, customUnit?.customUnitID ?? CONST.EMPTY_STRING, item.value);
7878
return;
7979
}
8080

81-
DistanceRate.clearCreateDistanceRateItemAndError(policyID, customUnit?.customUnitID ?? '', item.value);
81+
DistanceRate.clearCreateDistanceRateItemAndError(policyID, customUnit?.customUnitID ?? CONST.EMPTY_STRING, item.value);
8282
},
8383
[customUnit?.customUnitID, customUnitRates, policyID],
8484
);
@@ -98,16 +98,36 @@ function PolicyDistanceRatesPage({
9898
setSelectedDistanceRates([]);
9999
}, [isFocused]);
100100

101+
const updateDistanceRateEnabled = useCallback(
102+
(value: boolean, rateID: string) => {
103+
if (!customUnit) {
104+
return;
105+
}
106+
const rate = customUnit?.rates?.[rateID];
107+
// Rates can be disabled or deleted as long as in the remaining rates there is always at least one enabled rate and there are no pending delete action
108+
const canDisableOrDeleteRate = Object.values(customUnit?.rates ?? {}).some(
109+
(distanceRate: Rate) => distanceRate?.enabled && rateID !== distanceRate?.customUnitRateID && distanceRate?.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE,
110+
);
111+
112+
if (!rate?.enabled || canDisableOrDeleteRate) {
113+
DistanceRate.setPolicyDistanceRatesEnabled(policyID, customUnit, [{...rate, enabled: value}]);
114+
} else {
115+
setIsWarningModalVisible(true);
116+
}
117+
},
118+
[customUnit, policyID],
119+
);
120+
101121
const distanceRatesList = useMemo<RateForList[]>(
102122
() =>
103123
Object.values(customUnitRates)
104124
.sort((rateA, rateB) => (rateA?.rate ?? 0) - (rateB?.rate ?? 0))
105125
.map((value) => ({
106-
value: value.customUnitRateID ?? '',
126+
value: value.customUnitRateID ?? CONST.EMPTY_STRING,
107127
text: `${CurrencyUtils.convertAmountToDisplayString(value.rate, value.currency ?? CONST.CURRENCY.USD)} / ${translate(
108128
`common.${customUnit?.attributes?.unit ?? CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES}`,
109129
)}`,
110-
keyForList: value.customUnitRateID ?? '',
130+
keyForList: value.customUnitRateID ?? CONST.EMPTY_STRING,
111131
isSelected: selectedDistanceRates.find((rate) => rate.customUnitRateID === value.customUnitRateID) !== undefined && canSelectMultiple,
112132
isDisabled: value.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE,
113133
pendingAction:
@@ -119,9 +139,16 @@ function PolicyDistanceRatesPage({
119139
value.pendingFields?.taxClaimablePercentage ??
120140
(policy?.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD ? policy?.pendingAction : undefined),
121141
errors: value.errors ?? undefined,
122-
rightElement: <ListItemRightCaretWithLabel labelText={value.enabled ? translate('workspace.common.enabled') : translate('workspace.common.disabled')} />,
142+
rightElement: (
143+
<Switch
144+
isOn={!!value?.enabled}
145+
accessibilityLabel={translate('workspace.distanceRates.trackTax')}
146+
onToggle={(newValue: boolean) => updateDistanceRateEnabled(newValue, value.customUnitRateID)}
147+
disabled={value.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE}
148+
/>
149+
),
123150
})),
124-
[customUnit?.attributes?.unit, customUnitRates, selectedDistanceRates, translate, policy?.pendingAction, canSelectMultiple],
151+
[customUnitRates, translate, customUnit, selectedDistanceRates, canSelectMultiple, policy?.pendingAction, updateDistanceRateEnabled],
125152
);
126153

127154
const addRate = () => {
@@ -170,7 +197,7 @@ function PolicyDistanceRatesPage({
170197
DistanceRate.deletePolicyDistanceRates(
171198
policyID,
172199
customUnit,
173-
selectedDistanceRates.map((rate) => rate.customUnitRateID ?? ''),
200+
selectedDistanceRates.map((rate) => rate.customUnitRateID ?? CONST.EMPTY_STRING),
174201
);
175202
setSelectedDistanceRates([]);
176203
setIsDeleteModalVisible(false);

src/pages/workspace/reportFields/ReportFieldsListValuesPage.tsx

+22-4
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, {useMemo, useState} from 'react';
1+
import React, {useCallback, useMemo, useState} from 'react';
22
import {View} from 'react-native';
33
import {useOnyx} from 'react-native-onyx';
44
import Button from '@components/Button';
@@ -10,12 +10,12 @@ import HeaderWithBackButton from '@components/HeaderWithBackButton';
1010
import * as Expensicons from '@components/Icon/Expensicons';
1111
import * as Illustrations from '@components/Icon/Illustrations';
1212
import ScreenWrapper from '@components/ScreenWrapper';
13-
import ListItemRightCaretWithLabel from '@components/SelectionList/ListItemRightCaretWithLabel';
1413
import TableListItem from '@components/SelectionList/TableListItem';
1514
import type {ListItem} from '@components/SelectionList/types';
1615
import SelectionListWithModal from '@components/SelectionListWithModal';
1716
import CustomListHeader from '@components/SelectionListWithModal/CustomListHeader';
1817
import TableListItemSkeleton from '@components/Skeletons/TableRowSkeleton';
18+
import Switch from '@components/Switch';
1919
import Text from '@components/Text';
2020
import useLocalize from '@hooks/useLocalize';
2121
import useMobileSelectionMode from '@hooks/useMobileSelectionMode';
@@ -90,6 +90,18 @@ function ReportFieldsListValuesPage({
9090
return [reportFieldValues, reportFieldDisabledValues];
9191
}, [formDraft?.disabledListValues, formDraft?.listValues, policy?.fieldList, reportFieldID]);
9292

93+
const updateReportFieldListValueEnabled = useCallback(
94+
(value: boolean, valueIndex: number) => {
95+
if (reportFieldID) {
96+
ReportField.updateReportFieldListValueEnabled(policyID, reportFieldID, [Number(valueIndex)], value);
97+
return;
98+
}
99+
100+
ReportField.setReportFieldsListValueEnabled([valueIndex], value);
101+
},
102+
[policyID, reportFieldID],
103+
);
104+
93105
const listValuesSections = useMemo(() => {
94106
const data = listValues
95107
.map<ValueListItem>((value, index) => ({
@@ -99,11 +111,17 @@ function ReportFieldsListValuesPage({
99111
keyForList: value,
100112
isSelected: selectedValues[value] && canSelectMultiple,
101113
enabled: !disabledListValues.at(index) ?? true,
102-
rightElement: <ListItemRightCaretWithLabel labelText={disabledListValues.at(index) ? translate('workspace.common.disabled') : translate('workspace.common.enabled')} />,
114+
rightElement: (
115+
<Switch
116+
isOn={!disabledListValues.at(index) ?? true}
117+
accessibilityLabel={translate('workspace.distanceRates.trackTax')}
118+
onToggle={(newValue: boolean) => updateReportFieldListValueEnabled(newValue, index)}
119+
/>
120+
),
103121
}))
104122
.sort((a, b) => localeCompare(a.value, b.value));
105123
return [{data, isDisabled: false}];
106-
}, [canSelectMultiple, disabledListValues, listValues, selectedValues, translate]);
124+
}, [canSelectMultiple, disabledListValues, listValues, selectedValues, translate, updateReportFieldListValueEnabled]);
107125

108126
const shouldShowEmptyState = Object.values(listValues ?? {}).length <= 0;
109127
const selectedValuesArray = Object.keys(selectedValues).filter((key) => selectedValues[key]);

src/pages/workspace/tags/WorkspaceTagsPage.tsx

+51-18
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,11 @@ import * as Illustrations from '@components/Icon/Illustrations';
1515
import LottieAnimations from '@components/LottieAnimations';
1616
import type {PopoverMenuItem} from '@components/PopoverMenu';
1717
import ScreenWrapper from '@components/ScreenWrapper';
18-
import ListItemRightCaretWithLabel from '@components/SelectionList/ListItemRightCaretWithLabel';
1918
import TableListItem from '@components/SelectionList/TableListItem';
2019
import SelectionListWithModal from '@components/SelectionListWithModal';
2120
import CustomListHeader from '@components/SelectionListWithModal/CustomListHeader';
2221
import TableListItemSkeleton from '@components/Skeletons/TableRowSkeleton';
22+
import Switch from '@components/Switch';
2323
import Text from '@components/Text';
2424
import TextLink from '@components/TextLink';
2525
import useEnvironment from '@hooks/useEnvironment';
@@ -103,23 +103,49 @@ function WorkspaceTagsPage({route}: WorkspaceTagsPageProps) {
103103
: undefined;
104104
};
105105

106+
const updateWorkspaceTagEnabled = useCallback(
107+
(value: boolean, tagName: string) => {
108+
Tag.setWorkspaceTagEnabled(policyID, {[tagName]: {name: tagName, enabled: value}}, 0);
109+
},
110+
[policyID],
111+
);
112+
113+
const updateWorkspaceRequiresTag = useCallback(
114+
(value: boolean, orderWeight: number) => {
115+
Tag.setPolicyTagsRequired(policyID, value, orderWeight);
116+
},
117+
[policyID],
118+
);
106119
const tagList = useMemo<TagListItem[]>(() => {
107120
if (isMultiLevelTags) {
108-
return policyTagLists.map((policyTagList) => ({
109-
value: policyTagList.name,
110-
orderWeight: policyTagList.orderWeight,
111-
text: PolicyUtils.getCleanedTagName(policyTagList.name),
112-
keyForList: String(policyTagList.orderWeight),
113-
isSelected: selectedTags[policyTagList.name] && canSelectMultiple,
114-
pendingAction: getPendingAction(policyTagList),
115-
enabled: true,
116-
required: policyTagList.required,
117-
rightElement: (
118-
<ListItemRightCaretWithLabel
119-
labelText={policyTagList.required && !!Object.values(policyTagList?.tags ?? {}).some((tag) => tag.enabled) ? translate('common.required') : undefined}
120-
/>
121-
),
122-
}));
121+
return policyTagLists.map((policyTagList) => {
122+
const areTagsEnabled = !!Object.values(policyTagList?.tags ?? {}).some((tag) => tag.enabled);
123+
const isSwitchDisabled = !policyTagList.required && !areTagsEnabled;
124+
const isSwitchEnabled = policyTagList.required && areTagsEnabled;
125+
126+
if (policyTagList.required && !areTagsEnabled) {
127+
updateWorkspaceRequiresTag(false, policyTagList.orderWeight);
128+
}
129+
130+
return {
131+
value: policyTagList.name,
132+
orderWeight: policyTagList.orderWeight,
133+
text: PolicyUtils.getCleanedTagName(policyTagList.name),
134+
keyForList: String(policyTagList.orderWeight),
135+
isSelected: selectedTags[policyTagList.name] && canSelectMultiple,
136+
pendingAction: getPendingAction(policyTagList),
137+
enabled: true,
138+
required: policyTagList.required,
139+
rightElement: (
140+
<Switch
141+
isOn={isSwitchEnabled}
142+
accessibilityLabel={translate('workspace.tags.requiresTag')}
143+
onToggle={(newValue: boolean) => updateWorkspaceRequiresTag(newValue, policyTagList.orderWeight)}
144+
disabled={isSwitchDisabled}
145+
/>
146+
),
147+
};
148+
});
123149
}
124150
const sortedTags = lodashSortBy(Object.values(policyTagLists.at(0)?.tags ?? {}), 'name', localeCompare) as PolicyTag[];
125151
return sortedTags.map((tag) => ({
@@ -131,9 +157,16 @@ function WorkspaceTagsPage({route}: WorkspaceTagsPageProps) {
131157
errors: tag.errors ?? undefined,
132158
enabled: tag.enabled,
133159
isDisabled: tag.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE,
134-
rightElement: <ListItemRightCaretWithLabel labelText={tag.enabled ? translate('workspace.common.enabled') : translate('workspace.common.disabled')} />,
160+
rightElement: (
161+
<Switch
162+
isOn={tag.enabled}
163+
disabled={tag.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE}
164+
accessibilityLabel={translate('workspace.tags.enableTag')}
165+
onToggle={(newValue: boolean) => updateWorkspaceTagEnabled(newValue, tag.name)}
166+
/>
167+
),
135168
}));
136-
}, [isMultiLevelTags, policyTagLists, selectedTags, canSelectMultiple, translate]);
169+
}, [isMultiLevelTags, policyTagLists, selectedTags, canSelectMultiple, translate, updateWorkspaceRequiresTag, updateWorkspaceTagEnabled]);
137170

138171
const tagListKeyedByName = useMemo(
139172
() =>

0 commit comments

Comments
 (0)