Skip to content

Commit

Permalink
chore: wip
Browse files Browse the repository at this point in the history
  • Loading branch information
thiagobrez committed Jul 10, 2023
1 parent 43c4443 commit 717e22b
Show file tree
Hide file tree
Showing 4 changed files with 241 additions and 49 deletions.
29 changes: 26 additions & 3 deletions src/components/SelectionListRadio/BaseSelectionListRadio.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -44,23 +45,25 @@ 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
const sectionHeaderHeight = 0;
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;
Expand All @@ -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
Expand All @@ -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,
Expand Down Expand Up @@ -159,6 +172,16 @@ function BaseSelectionListRadio(props) {
const renderItem = ({item, index, section}) => {
const isFocused = focusedIndex === index + lodashGet(section, 'indexOffset', 0);

if (props.canSelectMultiple) {
return (
<CheckboxListItem
item={item}
isFocused={isFocused}
onSelectRow={props.onSelectRow}
/>
);
}

return (
<RadioListItem
item={item}
Expand Down
96 changes: 96 additions & 0 deletions src/components/SelectionListRadio/CheckboxListItem.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import React from 'react';
import {View} from 'react-native';
import PropTypes from 'prop-types';
import _ from 'underscore';
import PressableWithFeedback from '../Pressable/PressableWithFeedback';
import styles from '../../styles/styles';
import Text from '../Text';
import {radioListItemPropTypes} from './selectionListRadioPropTypes';
import Checkbox from '../Checkbox';
import FormHelpMessage from '../FormHelpMessage';
import {propTypes as item} from '../UserDetailsTooltip/userDetailsTooltipPropTypes';
import withLocalize, {withLocalizePropTypes} from '../withLocalize';

const propTypes = {
/** The section list item */
item: PropTypes.shape(radioListItemPropTypes),

/** Whether this item is focused (for arrow key controls) */
isFocused: PropTypes.bool,

/** Callback to fire when the item is pressed */
onSelectRow: PropTypes.func,

...withLocalizePropTypes,
};

const defaultProps = {
item: {},
isFocused: false,
onSelectRow: () => {},
};

function CheckboxListItem(props) {
// TODO: REVIEW ERRORS

const errors = {};

return (
<>
<PressableWithFeedback
// style={[styles.peopleRow, (_.isEmpty(props.item.errors) || errors[item.accountID]) && styles.peopleRowBorderBottom, hasError && styles.borderColorDanger]}
style={[styles.peopleRow]}
onPress={() => props.onSelectRow(props.item)}
accessibilityLabel={props.item.text}
accessibilityRole="checkbox"
accessibilityState={{checked: props.item.isSelected}}
hoverDimmingValue={1}
hoverStyle={styles.hoveredComponentBG}
focusStyle={styles.hoveredComponentBG}
>
<Checkbox
isChecked={props.item.isSelected}
onPress={() => props.onSelectRow(props.item)}
/>
<View style={styles.flex1}>
<View style={[styles.flex1, styles.justifyContentBetween, styles.sidebarLinkInner, styles.optionRow, props.isFocused && styles.sidebarLinkActive]}>
<View style={[styles.flex1, styles.alignItemsStart]}>
<Text
style={[
styles.optionDisplayName,
props.isFocused ? styles.sidebarLinkActiveText : styles.sidebarLinkText,
props.item.isSelected && styles.sidebarLinkTextBold,
]}
>
{props.item.text}
</Text>

{Boolean(props.item.alternateText) && (
<Text style={[props.isFocused ? styles.sidebarLinkActiveText : styles.sidebarLinkText, styles.optionAlternateText, styles.textLabelSupporting]}>
{props.item.alternateText}
</Text>
)}
</View>
</View>
</View>
{props.item.isAdmin && (
<View style={[styles.badge, styles.peopleBadge]}>
<Text style={[styles.peopleBadgeText]}>{props.translate('common.admin')}</Text>
</View>
)}
</PressableWithFeedback>
{!_.isEmpty(errors[item.accountID]) && (
<FormHelpMessage
isError
message={errors[item.accountID]}
/>
)}
</>
);
}

CheckboxListItem.displayName = 'CheckboxListItem';
CheckboxListItem.propTypes = propTypes;
CheckboxListItem.defaultProps = defaultProps;

export default withLocalize(CheckboxListItem);
Original file line number Diff line number Diff line change
Expand Up @@ -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,

Expand Down Expand Up @@ -71,6 +74,7 @@ const propTypes = {
};

const defaultProps = {
canSelectMultiple: false,
onSelectRow: () => {},
textInputLabel: '',
textInputPlaceholder: '',
Expand Down
161 changes: 115 additions & 46 deletions src/pages/workspace/WorkspaceMembersPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down Expand Up @@ -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],
Expand Down Expand Up @@ -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) {

This comment has been minimized.

Copy link
@rushatgabhane

rushatgabhane Mar 26, 2024

Member

Coming from #34852

Details about root cause here.

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 (
<ScreenWrapper
includeSafeAreaPaddingBottom={false}
Expand Down Expand Up @@ -442,53 +514,50 @@ function WorkspaceMembersPage(props) {
onPress={askForConfirmationToRemove}
/>
</View>
<View style={[styles.w100, styles.pv3, styles.ph5]}>
<TextInput
value={searchValue}
{/* <View style={[styles.w100, styles.pv3, styles.ph5]}> */}
{/* <TextInput */}
{/* value={searchValue} */}
{/* onChangeText={setSearchValue} */}
{/* label={props.translate('optionsSelector.findMember')} */}
{/* /> */}
{/* </View> */}
{/* {data.length > 0 ? ( */}
<View style={[styles.w100, styles.mt4, styles.flex1]}>
<View style={[styles.peopleRow, styles.ph5, styles.pb3]}>
<Checkbox
isChecked={!_.isEmpty(removableMembers) && _.every(_.keys(removableMembers), (accountID) => _.contains(selectedEmployees, Number(accountID)))}
onPress={() => toggleAllUsers(removableMembers)}
/>
<View style={[styles.flex1]}>
<Text style={[styles.textStrong, styles.ph5]}>{props.translate('workspace.people.selectAll')}</Text>
</View>
</View>

<SelectionListRadio
canSelectMultiple
sections={[{data: data2, indexOffset: 0, isDisabled: false}]}
textInputLabel={props.translate('optionsSelector.findMember')}
textInputValue={searchValue}
onChangeText={setSearchValue}
label={props.translate('optionsSelector.findMember')}
headerMessage={headerMessage}
onSelectRow={(item) => toggleUser(item.keyForList)}
/>

{/* <KeyboardDismissingFlatList */}
{/* renderItem={renderItem} */}
{/* data={data} */}
{/* keyExtractor={(item) => item.login} */}
{/* showsVerticalScrollIndicator */}
{/* style={[styles.ph5, styles.pb5]} */}
{/* contentContainerStyle={safeAreaPaddingBottomStyle} */}
{/* keyboardShouldPersistTaps="handled" */}
{/* /> */}
</View>
{data.length > 0 ? (
<View style={[styles.w100, styles.mt4, styles.flex1]}>
<View style={[styles.peopleRow, styles.ph5, styles.pb3]}>
<PressableWithFeedback
disabled={_.isEmpty(removableMembers)}
onPress={() => 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}
>
<Checkbox
disabled={_.isEmpty(removableMembers)}
isChecked={!_.isEmpty(removableMembers) && _.every(_.keys(removableMembers), (accountID) => _.contains(selectedEmployees, Number(accountID)))}
onPress={() => toggleAllUsers(removableMembers)}
accessibilityLabel={props.translate('workspace.people.selectAll')}
/>
</PressableWithFeedback>
<View style={[styles.flex1]}>
<Text style={[styles.textStrong, styles.ph5]}>{props.translate('workspace.people.selectAll')}</Text>
</View>
</View>
<KeyboardDismissingFlatList
renderItem={renderItem}
data={data}
keyExtractor={(item) => item.login}
showsVerticalScrollIndicator
style={[styles.ph5, styles.pb5]}
contentContainerStyle={safeAreaPaddingBottomStyle}
keyboardShouldPersistTaps="handled"
/>
</View>
) : (
<View style={[styles.ph5]}>
<Text style={[styles.textLabel, styles.colorMuted]}>{props.translate('workspace.common.memberNotFound')}</Text>
</View>
)}
{/* ) : ( */}
{/* <View style={[styles.ph5]}> */}
{/* <Text style={[styles.textLabel, styles.colorMuted]}>{props.translate('workspace.common.memberNotFound')}</Text> */}
{/* </View> */}
{/* )} */}
</View>
</FullPageNotFoundView>
)}
Expand Down

0 comments on commit 717e22b

Please sign in to comment.