diff --git a/src/components/SelectionListRadio/BaseSelectionListRadio.js b/src/components/SelectionListRadio/BaseSelectionListRadio.js
index 2b8f373b987a..ac425771dd96 100644
--- a/src/components/SelectionListRadio/BaseSelectionListRadio.js
+++ b/src/components/SelectionListRadio/BaseSelectionListRadio.js
@@ -11,6 +11,7 @@ import CONST from '../../CONST';
import variables from '../../styles/variables';
import {propTypes as selectionListRadioPropTypes, defaultProps as selectionListRadioDefaultProps} from './selectionListRadioPropTypes';
import RadioListItem from './RadioListItem';
+import CheckboxListItem from './CheckboxListItem';
import useKeyboardShortcut from '../../hooks/useKeyboardShortcut';
import SafeAreaConsumer from '../SafeAreaConsumer';
import withKeyboardState, {keyboardStatePropTypes} from '../withKeyboardState';
@@ -44,6 +45,8 @@ function BaseSelectionListRadio(props) {
let offset = 0;
const itemLayouts = [{length: 0, offset}];
+ let selectedCount = 0;
+
_.each(props.sections, (section, sectionIndex) => {
// We're not rendering any section header, but we need to push to the array
// because React Native accounts for it in getItemLayout
@@ -51,16 +54,16 @@ function BaseSelectionListRadio(props) {
itemLayouts.push({length: sectionHeaderHeight, offset});
offset += sectionHeaderHeight;
- _.each(section.data, (option, optionIndex) => {
+ _.each(section.data, (item, optionIndex) => {
// Add item to the general flattened array
allOptions.push({
- ...option,
+ ...item,
sectionIndex,
index: optionIndex,
});
// If disabled, add to the disabled indexes array
- if (section.isDisabled || option.isDisabled) {
+ if (section.isDisabled || item.isDisabled) {
disabledOptionsIndexes.push(disabledIndex);
}
disabledIndex += 1;
@@ -69,6 +72,10 @@ function BaseSelectionListRadio(props) {
const fullItemHeight = variables.optionRowHeight;
itemLayouts.push({length: fullItemHeight, offset});
offset += fullItemHeight;
+
+ if (item.isSelected) {
+ selectedCount++;
+ }
});
// We're not rendering any section footer, but we need to push to the array
@@ -80,6 +87,12 @@ function BaseSelectionListRadio(props) {
// because React Native accounts for it in getItemLayout
itemLayouts.push({length: 0, offset});
+ if (selectedCount > 1 && !props.canSelectMultiple) {
+ throw new Error(
+ 'Dev error: SelectionList - multiple items are selected but prop `canSelectMultiple` is false. Please enable `canSelectMultiple` or make your list have only 1 item with `isSelected: true`.',
+ );
+ }
+
return {
allOptions,
disabledOptionsIndexes,
@@ -159,6 +172,16 @@ function BaseSelectionListRadio(props) {
const renderItem = ({item, index, section}) => {
const isFocused = focusedIndex === index + lodashGet(section, 'indexOffset', 0);
+ if (props.canSelectMultiple) {
+ return (
+
+ );
+ }
+
return (
{},
+};
+
+function CheckboxListItem(props) {
+ // TODO: REVIEW ERRORS
+
+ const errors = {};
+
+ return (
+ <>
+ props.onSelectRow(props.item)}
+ accessibilityLabel={props.item.text}
+ accessibilityRole="checkbox"
+ accessibilityState={{checked: props.item.isSelected}}
+ hoverDimmingValue={1}
+ hoverStyle={styles.hoveredComponentBG}
+ focusStyle={styles.hoveredComponentBG}
+ >
+ props.onSelectRow(props.item)}
+ />
+
+
+
+
+ {props.item.text}
+
+
+ {Boolean(props.item.alternateText) && (
+
+ {props.item.alternateText}
+
+ )}
+
+
+
+ {props.item.isAdmin && (
+
+ {props.translate('common.admin')}
+
+ )}
+
+ {!_.isEmpty(errors[item.accountID]) && (
+
+ )}
+ >
+ );
+}
+
+CheckboxListItem.displayName = 'CheckboxListItem';
+CheckboxListItem.propTypes = propTypes;
+CheckboxListItem.defaultProps = defaultProps;
+
+export default withLocalize(CheckboxListItem);
diff --git a/src/components/SelectionListRadio/selectionListRadioPropTypes.js b/src/components/SelectionListRadio/selectionListRadioPropTypes.js
index 14e41b195d7b..39081def712b 100644
--- a/src/components/SelectionListRadio/selectionListRadioPropTypes.js
+++ b/src/components/SelectionListRadio/selectionListRadioPropTypes.js
@@ -33,6 +33,9 @@ const propTypes = {
}),
).isRequired,
+ /** Whether this is a multi-select list */
+ canSelectMultiple: PropTypes.bool,
+
/** Callback to fire when a row is tapped */
onSelectRow: PropTypes.func,
@@ -71,6 +74,7 @@ const propTypes = {
};
const defaultProps = {
+ canSelectMultiple: false,
onSelectRow: () => {},
textInputLabel: '',
textInputPlaceholder: '',
diff --git a/src/pages/workspace/WorkspaceMembersPage.js b/src/pages/workspace/WorkspaceMembersPage.js
index 2a5cecb1cf94..abf29731ca6d 100644
--- a/src/pages/workspace/WorkspaceMembersPage.js
+++ b/src/pages/workspace/WorkspaceMembersPage.js
@@ -38,6 +38,7 @@ import PressableWithFeedback from '../../components/Pressable/PressableWithFeedb
import usePrevious from '../../hooks/usePrevious';
import Log from '../../libs/Log';
import * as PersonalDetailsUtils from '../../libs/PersonalDetailsUtils';
+import SelectionListRadio from '../../components/SelectionListRadio';
const propTypes = {
/** The personal details of the person who is logged in */
@@ -249,9 +250,9 @@ function WorkspaceMembersPage(props) {
// Add or remove the user if the checkbox is enabled
if (_.contains(selectedEmployees, Number(accountID))) {
- removeUser(accountID);
+ removeUser(Number(accountID));
} else {
- addUser(accountID);
+ addUser(Number(accountID));
}
},
[selectedEmployees, addUser, removeUser],
@@ -395,6 +396,77 @@ function WorkspaceMembersPage(props) {
const policyID = lodashGet(props.route, 'params.policyID');
const policyName = lodashGet(props.policy, 'name');
+ const getListData = () => {
+ let result = [];
+
+ _.each(props.policyMembers, (policyMember, accountID) => {
+ if (isDeletedPolicyMember(policyMember)) {
+ return;
+ }
+
+ const details = props.personalDetails[accountID];
+
+ if (!details) {
+ Log.hmmm(`[WorkspaceMembersPage] no personal details found for policy member with accountID: ${accountID}`);
+ return;
+ }
+
+ // If search value is provided, filter out members that don't match the search value
+ if (searchValue.trim()) {
+ let memberDetails = '';
+ if (details.login) {
+ memberDetails += ` ${details.login.toLowerCase()}`;
+ }
+ if (details.firstName) {
+ memberDetails += ` ${details.firstName.toLowerCase()}`;
+ }
+ if (details.lastName) {
+ memberDetails += ` ${details.lastName.toLowerCase()}`;
+ }
+ if (details.displayName) {
+ memberDetails += ` ${details.displayName.toLowerCase()}`;
+ }
+ if (details.phoneNumber) {
+ memberDetails += ` ${details.phoneNumber.toLowerCase()}`;
+ }
+
+ if (!OptionsListUtils.isSearchStringMatch(searchValue.trim(), memberDetails)) {
+ return;
+ }
+ }
+
+ // If this policy is owned by Expensify then show all support (expensify.com or team.expensify.com) emails
+ // We don't want to show guides as policy members unless the user is a guide. Some customers get confused when they
+ // see random people added to their policy, but guides having access to the policies help set them up.
+ if (PolicyUtils.isExpensifyTeam(details.login || details.displayName)) {
+ if (policyOwner && currentUserLogin && !PolicyUtils.isExpensifyTeam(policyOwner) && !PolicyUtils.isExpensifyTeam(currentUserLogin)) {
+ return;
+ }
+ }
+
+ result.push({
+ keyForList: accountID,
+ isSelected: _.contains(selectedEmployees, Number(accountID)),
+ text: props.formatPhoneNumber(details.displayName),
+ alternateText: props.formatPhoneNumber(details.login),
+ isAdmin: props.session.email === details.login || policyMember.role === 'admin',
+ avatar: {
+ source: UserUtils.getAvatar(details.avatar, accountID),
+ name: details.login,
+ type: CONST.ICON_TYPE_AVATAR,
+ },
+ });
+ });
+
+ result = _.sortBy(result, (value) => value.text.toLowerCase());
+
+ return result;
+ };
+
+ const data2 = getListData();
+
+ const headerMessage = searchValue.trim() && !data2.length ? props.translate('common.noResultsFound') : '';
+
return (
-
- */}
+ {/* */}
+ {/* */}
+ {/* {data.length > 0 ? ( */}
+
+
+ _.contains(selectedEmployees, Number(accountID)))}
+ onPress={() => toggleAllUsers(removableMembers)}
+ />
+
+ {props.translate('workspace.people.selectAll')}
+
+
+
+ toggleUser(item.keyForList)}
/>
+
+ {/* item.login} */}
+ {/* showsVerticalScrollIndicator */}
+ {/* style={[styles.ph5, styles.pb5]} */}
+ {/* contentContainerStyle={safeAreaPaddingBottomStyle} */}
+ {/* keyboardShouldPersistTaps="handled" */}
+ {/* /> */}
- {data.length > 0 ? (
-
-
- toggleAllUsers(removableMembers)}
- accessibilityRole={CONST.ACCESSIBILITY_ROLE.CHECKBOX}
- accessibilityState={{
- checked: !_.isEmpty(removableMembers) && _.every(_.keys(removableMembers), (accountID) => _.contains(selectedEmployees, Number(accountID))),
- }}
- accessibilityLabel={props.translate('workspace.people.selectAll')}
- hoverDimmingValue={1}
- pressDimmingValue={0.7}
- >
- _.contains(selectedEmployees, Number(accountID)))}
- onPress={() => toggleAllUsers(removableMembers)}
- accessibilityLabel={props.translate('workspace.people.selectAll')}
- />
-
-
- {props.translate('workspace.people.selectAll')}
-
-
- item.login}
- showsVerticalScrollIndicator
- style={[styles.ph5, styles.pb5]}
- contentContainerStyle={safeAreaPaddingBottomStyle}
- keyboardShouldPersistTaps="handled"
- />
-
- ) : (
-
- {props.translate('workspace.common.memberNotFound')}
-
- )}
+ {/* ) : ( */}
+ {/* */}
+ {/* {props.translate('workspace.common.memberNotFound')} */}
+ {/* */}
+ {/* )} */}
)}