New User List for Integration selector (#6806)
* WIP New User List for int selector * user selection * searchusers by term * Remove old user list related code on selector * Fix linting issues * Submitting user list selection * currentUserId * multiselect showing after the bar * useState instead of reducer * Remoce empty line * add callbacks * Fix lint
This commit is contained in:
parent
0e5d63a7c3
commit
fc44e8f90b
5 changed files with 281 additions and 190 deletions
150
app/components/server_user_list/index.tsx
Normal file
150
app/components/server_user_list/index.tsx
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react';
|
||||
|
||||
import {fetchProfiles, searchProfiles} from '@actions/remote/user';
|
||||
import UserList from '@components/user_list';
|
||||
import {General} from '@constants';
|
||||
import {useServerUrl} from '@context/server';
|
||||
import {debounce} from '@helpers/api/general';
|
||||
import {filterProfilesMatchingTerm} from '@utils/user';
|
||||
|
||||
type Props = {
|
||||
currentTeamId: string;
|
||||
currentUserId: string;
|
||||
teammateNameDisplay: string;
|
||||
tutorialWatched: boolean;
|
||||
handleSelectProfile: (user: UserProfile) => void;
|
||||
term: string;
|
||||
}
|
||||
|
||||
function handleIdSelection(currentIds: {[id: string]: UserProfile}, user: UserProfile) {
|
||||
const newSelectedIds = {...currentIds};
|
||||
const wasSelected = currentIds[user.id];
|
||||
|
||||
if (wasSelected) {
|
||||
Reflect.deleteProperty(newSelectedIds, user.id);
|
||||
} else {
|
||||
newSelectedIds[user.id] = user;
|
||||
}
|
||||
|
||||
return newSelectedIds;
|
||||
}
|
||||
|
||||
export default function ServerUserList({
|
||||
currentTeamId,
|
||||
currentUserId,
|
||||
teammateNameDisplay,
|
||||
tutorialWatched,
|
||||
handleSelectProfile,
|
||||
term,
|
||||
}: Props) {
|
||||
const serverUrl = useServerUrl();
|
||||
|
||||
const next = useRef(true);
|
||||
const page = useRef(-1);
|
||||
const mounted = useRef(false);
|
||||
|
||||
const [profiles, setProfiles] = useState<UserProfile[]>([]);
|
||||
const [searchResults, setSearchResults] = useState<UserProfile[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [selectedIds, setSelectedIds] = useState<{[id: string]: UserProfile}>({});
|
||||
const selectedCount = Object.keys(selectedIds).length;
|
||||
|
||||
const isSearch = Boolean(term);
|
||||
|
||||
const loadedProfiles = ({users}: {users?: UserProfile[]}) => {
|
||||
if (mounted.current) {
|
||||
if (users && !users.length) {
|
||||
next.current = false;
|
||||
}
|
||||
|
||||
page.current += 1;
|
||||
setLoading(false);
|
||||
setProfiles((current) => {
|
||||
if (users?.length) {
|
||||
return [...current, ...users];
|
||||
}
|
||||
|
||||
return current;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const getProfiles = useCallback(debounce(() => {
|
||||
if (next.current && !loading && !term && mounted.current) {
|
||||
setLoading(true);
|
||||
fetchProfiles(serverUrl, page.current + 1, General.PROFILE_CHUNK_SIZE).then(loadedProfiles);
|
||||
}
|
||||
}, 100), [loading, isSearch, serverUrl, currentTeamId]);
|
||||
|
||||
const onHandleSelectProfile = useCallback((user: UserProfile) => {
|
||||
handleSelectProfile(user);
|
||||
setSelectedIds((current) => handleIdSelection(current, user));
|
||||
}, [handleSelectProfile]);
|
||||
|
||||
const searchUsers = useCallback(async (searchTerm: string) => {
|
||||
const lowerCasedTerm = searchTerm.toLowerCase();
|
||||
setLoading(true);
|
||||
const results = await searchProfiles(serverUrl, lowerCasedTerm, {allow_inactive: true});
|
||||
|
||||
let data: UserProfile[] = [];
|
||||
if (results.data) {
|
||||
data = results.data;
|
||||
}
|
||||
|
||||
setSearchResults(data);
|
||||
setLoading(false);
|
||||
}, [serverUrl, currentTeamId]);
|
||||
|
||||
useEffect(() => {
|
||||
searchUsers(term);
|
||||
}, [term]);
|
||||
|
||||
useEffect(() => {
|
||||
mounted.current = true;
|
||||
getProfiles();
|
||||
return () => {
|
||||
mounted.current = false;
|
||||
};
|
||||
}, []);
|
||||
|
||||
const data = useMemo(() => {
|
||||
if (term) {
|
||||
const exactMatches: UserProfile[] = [];
|
||||
const filterByTerm = (p: UserProfile) => {
|
||||
if (selectedCount > 0 && p.id === currentUserId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (p.username === term || p.username.startsWith(term)) {
|
||||
exactMatches.push(p);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
const results = filterProfilesMatchingTerm(searchResults, term).filter(filterByTerm);
|
||||
return [...exactMatches, ...results];
|
||||
}
|
||||
return profiles;
|
||||
}, [term, isSearch && selectedCount, isSearch && searchResults, profiles]);
|
||||
|
||||
return (
|
||||
<UserList
|
||||
currentUserId={currentUserId}
|
||||
handleSelectProfile={onHandleSelectProfile}
|
||||
loading={loading}
|
||||
profiles={data}
|
||||
selectedIds={selectedIds}
|
||||
showNoResults={!loading && page.current !== -1}
|
||||
teammateNameDisplay={teammateNameDisplay}
|
||||
fetchMore={getProfiles}
|
||||
term={term}
|
||||
testID='create_direct_message.user_list'
|
||||
tutorialWatched={tutorialWatched}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
@ -9,7 +9,7 @@ import {Preferences} from '@constants';
|
|||
import {renderWithEverything} from '@test/intl-test-helper';
|
||||
import TestHelper from '@test/test_helper';
|
||||
|
||||
import CustomList, {FLATLIST} from '.';
|
||||
import CustomList from '.';
|
||||
|
||||
describe('components/integration_selector/custom_list', () => {
|
||||
let database: Database;
|
||||
|
|
@ -42,7 +42,6 @@ describe('components/integration_selector/custom_list', () => {
|
|||
<CustomList
|
||||
data={[channel]}
|
||||
key='custom_list'
|
||||
listType={FLATLIST}
|
||||
loading={false}
|
||||
theme={Preferences.THEMES.denim}
|
||||
testID='ChannelListRow'
|
||||
|
|
|
|||
|
|
@ -2,24 +2,18 @@
|
|||
// See LICENSE.txt for license information.
|
||||
import React, {useCallback} from 'react';
|
||||
import {
|
||||
Text, Platform, FlatList, RefreshControl, View, SectionList,
|
||||
Platform, FlatList, RefreshControl, View,
|
||||
} from 'react-native';
|
||||
|
||||
import {makeStyleSheetFromTheme, changeOpacity} from '@utils/theme';
|
||||
import {typography} from '@utils/typography';
|
||||
|
||||
export const FLATLIST = 'flat';
|
||||
export const SECTIONLIST = 'section';
|
||||
const INITIAL_BATCH_TO_RENDER = 15;
|
||||
|
||||
type UserProfileSection = {
|
||||
id: string;
|
||||
data: UserProfile[];
|
||||
};
|
||||
type DataType = DialogOption[] | Channel[] | UserProfile[] | UserProfileSection[];
|
||||
type DataType = DialogOption[] | Channel[];
|
||||
type ListItemProps = {
|
||||
id: string;
|
||||
item: DialogOption | Channel | UserProfile;
|
||||
item: DialogOption | Channel;
|
||||
selected: boolean;
|
||||
selectable?: boolean;
|
||||
enabled: boolean;
|
||||
|
|
@ -29,14 +23,13 @@ type ListItemProps = {
|
|||
type Props = {
|
||||
data: DataType;
|
||||
canRefresh?: boolean;
|
||||
listType?: string;
|
||||
loading?: boolean;
|
||||
loadingComponent?: React.ReactElement<any, string> | null;
|
||||
noResults: () => JSX.Element | null;
|
||||
refreshing?: boolean;
|
||||
onRefresh?: () => void;
|
||||
onLoadMore: () => void;
|
||||
onRowPress: (item: UserProfile | Channel | DialogOption) => void;
|
||||
onRowPress: (item: Channel | DialogOption) => void;
|
||||
renderItem: (props: ListItemProps) => JSX.Element;
|
||||
selectable?: boolean;
|
||||
theme?: object;
|
||||
|
|
@ -111,7 +104,7 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
|||
});
|
||||
|
||||
function CustomList({
|
||||
data, shouldRenderSeparator, listType, loading, loadingComponent, noResults,
|
||||
data, shouldRenderSeparator, loading, loadingComponent, noResults,
|
||||
onLoadMore, onRowPress, selectable, renderItem, theme,
|
||||
canRefresh = true, testID, refreshing = false, onRefresh,
|
||||
}: Props) {
|
||||
|
|
@ -156,76 +149,35 @@ function CustomList({
|
|||
return loadingComponent;
|
||||
}, [loading, loadingComponent]);
|
||||
|
||||
const renderSectionHeader = useCallback(({section}: any) => {
|
||||
return (
|
||||
<View style={style.sectionWrapper}>
|
||||
<View style={style.sectionContainer}>
|
||||
<Text style={style.sectionText}>{section.id}</Text>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}, [style]);
|
||||
|
||||
const renderSectionList = () => {
|
||||
return (
|
||||
<SectionList
|
||||
contentContainerStyle={style.container}
|
||||
keyExtractor={keyExtractor}
|
||||
initialNumToRender={INITIAL_BATCH_TO_RENDER}
|
||||
ItemSeparatorComponent={renderSeparator}
|
||||
ListEmptyComponent={renderEmptyList()}
|
||||
ListFooterComponent={renderFooter}
|
||||
maxToRenderPerBatch={INITIAL_BATCH_TO_RENDER + 1}
|
||||
onEndReached={onLoadMore}
|
||||
removeClippedSubviews={true}
|
||||
renderItem={renderListItem}
|
||||
renderSectionHeader={renderSectionHeader}
|
||||
scrollEventThrottle={60}
|
||||
sections={data as UserProfileSection[]}
|
||||
style={style.list}
|
||||
stickySectionHeadersEnabled={false}
|
||||
testID={testID}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const renderFlatList = () => {
|
||||
let refreshControl;
|
||||
if (canRefresh) {
|
||||
refreshControl = (
|
||||
<RefreshControl
|
||||
refreshing={refreshing}
|
||||
onRefresh={onRefresh}
|
||||
/>);
|
||||
}
|
||||
|
||||
return (
|
||||
<FlatList
|
||||
contentContainerStyle={style.container}
|
||||
data={data}
|
||||
keyboardShouldPersistTaps='always'
|
||||
keyExtractor={keyExtractor}
|
||||
initialNumToRender={INITIAL_BATCH_TO_RENDER}
|
||||
ItemSeparatorComponent={renderSeparator}
|
||||
ListEmptyComponent={renderEmptyList()}
|
||||
ListFooterComponent={renderFooter}
|
||||
maxToRenderPerBatch={INITIAL_BATCH_TO_RENDER + 1}
|
||||
onEndReached={onLoadMore}
|
||||
refreshControl={refreshControl}
|
||||
removeClippedSubviews={true}
|
||||
renderItem={renderListItem}
|
||||
scrollEventThrottle={60}
|
||||
style={style.list}
|
||||
testID={testID}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
if (listType === FLATLIST) {
|
||||
return renderFlatList();
|
||||
let refreshControl;
|
||||
if (canRefresh) {
|
||||
refreshControl = (
|
||||
<RefreshControl
|
||||
refreshing={refreshing}
|
||||
onRefresh={onRefresh}
|
||||
/>);
|
||||
}
|
||||
|
||||
return renderSectionList();
|
||||
return (
|
||||
<FlatList
|
||||
contentContainerStyle={style.container}
|
||||
data={data}
|
||||
keyboardShouldPersistTaps='always'
|
||||
keyExtractor={keyExtractor}
|
||||
initialNumToRender={INITIAL_BATCH_TO_RENDER}
|
||||
ItemSeparatorComponent={renderSeparator}
|
||||
ListEmptyComponent={renderEmptyList()}
|
||||
ListFooterComponent={renderFooter}
|
||||
maxToRenderPerBatch={INITIAL_BATCH_TO_RENDER + 1}
|
||||
onEndReached={onLoadMore}
|
||||
refreshControl={refreshControl}
|
||||
removeClippedSubviews={true}
|
||||
renderItem={renderListItem}
|
||||
scrollEventThrottle={60}
|
||||
style={style.list}
|
||||
testID={testID}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default CustomList;
|
||||
|
|
|
|||
|
|
@ -3,13 +3,14 @@
|
|||
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
|
||||
import withObservables from '@nozbe/with-observables';
|
||||
|
||||
import {observeCurrentTeamId} from '@queries/servers/system';
|
||||
import {observeCurrentTeamId, observeCurrentUserId} from '@queries/servers/system';
|
||||
|
||||
import IntegrationSelector from './integration_selector';
|
||||
|
||||
import type {WithDatabaseArgs} from '@typings/database/database';
|
||||
|
||||
const withTeamId = withObservables([], ({database}: WithDatabaseArgs) => ({
|
||||
currentUserId: observeCurrentUserId(database),
|
||||
currentTeamId: observeCurrentTeamId(database),
|
||||
}));
|
||||
|
||||
|
|
|
|||
|
|
@ -7,12 +7,10 @@ import {View} from 'react-native';
|
|||
import {SafeAreaView} from 'react-native-safe-area-context';
|
||||
|
||||
import {fetchChannels, searchChannels} from '@actions/remote/channel';
|
||||
import {fetchProfiles, searchProfiles} from '@actions/remote/user';
|
||||
import ServerUserList from '@app/components/server_user_list';
|
||||
import {t} from '@app/i18n';
|
||||
import FormattedText from '@components/formatted_text';
|
||||
import SearchBar from '@components/search';
|
||||
import {createProfilesSections} from '@components/user_list';
|
||||
import UserListRow from '@components/user_list_row';
|
||||
import {General, View as ViewConstants} from '@constants';
|
||||
import {useServerUrl} from '@context/server';
|
||||
import {useTheme} from '@context/theme';
|
||||
|
|
@ -25,20 +23,16 @@ import {
|
|||
import {filterChannelsMatchingTerm} from '@utils/channel';
|
||||
import {changeOpacity, getKeyboardAppearanceFromTheme, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {typography} from '@utils/typography';
|
||||
import {filterProfilesMatchingTerm} from '@utils/user';
|
||||
|
||||
import ChannelListRow from './channel_list_row';
|
||||
import CustomList, {FLATLIST, SECTIONLIST} from './custom_list';
|
||||
import CustomList from './custom_list';
|
||||
import OptionListRow from './option_list_row';
|
||||
import SelectedOptions from './selected_options';
|
||||
|
||||
type DataType = DialogOption[] | Channel[] | UserProfile[];
|
||||
type Selection = DialogOption | Channel | UserProfile | DataType;
|
||||
type DataType = DialogOption | Channel | UserProfile;
|
||||
type DataTypeList = DialogOption[] | Channel[] | UserProfile[];
|
||||
type Selection = DataType | DataTypeList;
|
||||
type MultiselectSelectedMap = Dictionary<DialogOption> | Dictionary<Channel> | Dictionary<UserProfile>;
|
||||
type UserProfileSection = {
|
||||
id: string;
|
||||
data: UserProfile[];
|
||||
};
|
||||
|
||||
const VALID_DATASOURCES = [
|
||||
ViewConstants.DATA_SOURCE_CHANNELS,
|
||||
|
|
@ -50,7 +44,7 @@ const close = () => {
|
|||
popTopScreen();
|
||||
};
|
||||
|
||||
const extractItemKey = (dataSource: string, item: Selection): string => {
|
||||
const extractItemKey = (dataSource: string, item: DataType): string => {
|
||||
switch (dataSource) {
|
||||
case ViewConstants.DATA_SOURCE_USERS: {
|
||||
const typedItem = item as UserProfile;
|
||||
|
|
@ -79,16 +73,14 @@ const toggleFromMap = <T extends DialogOption | Channel | UserProfile>(current:
|
|||
return newMap;
|
||||
};
|
||||
|
||||
const filterSearchData = (source: string, searchData: DataType, searchTerm: string) => {
|
||||
const filterSearchData = (source: string, searchData: DataTypeList, searchTerm: string) => {
|
||||
if (!searchData) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const lowerCasedTerm = searchTerm.toLowerCase();
|
||||
|
||||
if (source === ViewConstants.DATA_SOURCE_USERS) {
|
||||
return filterProfilesMatchingTerm(searchData as UserProfile[], lowerCasedTerm);
|
||||
} else if (source === ViewConstants.DATA_SOURCE_CHANNELS) {
|
||||
if (source === ViewConstants.DATA_SOURCE_CHANNELS) {
|
||||
return filterChannelsMatchingTerm(searchData as Channel[], lowerCasedTerm);
|
||||
} else if (source === ViewConstants.DATA_SOURCE_DYNAMIC) {
|
||||
return searchData;
|
||||
|
|
@ -97,11 +89,26 @@ const filterSearchData = (source: string, searchData: DataType, searchTerm: stri
|
|||
return (searchData as DialogOption[]).filter((option) => option.text && option.text.includes(lowerCasedTerm));
|
||||
};
|
||||
|
||||
const handleIdSelection = (dataSource: string, currentIds: {[id: string]: DataType}, item: DataType) => {
|
||||
const newSelectedIds = {...currentIds};
|
||||
const key = extractItemKey(dataSource, item);
|
||||
const wasSelected = currentIds[key];
|
||||
|
||||
if (wasSelected) {
|
||||
Reflect.deleteProperty(newSelectedIds, key);
|
||||
} else {
|
||||
newSelectedIds[key] = item;
|
||||
}
|
||||
|
||||
return newSelectedIds;
|
||||
};
|
||||
|
||||
export type Props = {
|
||||
getDynamicOptions?: (userInput?: string) => Promise<DialogOption[]>;
|
||||
options?: PostActionOption[];
|
||||
currentTeamId: string;
|
||||
data?: DataType;
|
||||
currentUserId: string;
|
||||
data?: DataTypeList;
|
||||
dataSource: string;
|
||||
handleSelect: (opt: Selection) => void;
|
||||
isMultiselect?: boolean;
|
||||
|
|
@ -154,7 +161,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
|||
|
||||
function IntegrationSelector(
|
||||
{dataSource, data, isMultiselect = false, selected, handleSelect,
|
||||
currentTeamId, componentId, getDynamicOptions, options, teammateNameDisplay}: Props) {
|
||||
currentTeamId, currentUserId, componentId, getDynamicOptions, options, teammateNameDisplay}: Props) {
|
||||
const serverUrl = useServerUrl();
|
||||
const theme = useTheme();
|
||||
const searchTimeoutId = useRef<NodeJS.Timeout | null>(null);
|
||||
|
|
@ -162,12 +169,17 @@ function IntegrationSelector(
|
|||
const intl = useIntl();
|
||||
|
||||
// HOOKS
|
||||
const [integrationData, setIntegrationData] = useState<DataType>(data || []);
|
||||
const [integrationData, setIntegrationData] = useState<DataTypeList>(data || []);
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const [term, setTerm] = useState<string>('');
|
||||
const [searchResults, setSearchResults] = useState<DataType>([]);
|
||||
const [searchResults, setSearchResults] = useState<DataTypeList>([]);
|
||||
|
||||
// Channels and DialogOptions, will be removed
|
||||
const [multiselectSelected, setMultiselectSelected] = useState<MultiselectSelectedMap>({});
|
||||
const [customListData, setCustomListData] = useState<DataType | UserProfileSection[]>([]);
|
||||
|
||||
// Users selection and in the future Channels and DialogOptions
|
||||
const [selectedIds, setSelectedIds] = useState<{[id: string]: DataType}>({});
|
||||
const [customListData, setCustomListData] = useState<DataTypeList>([]);
|
||||
|
||||
const page = useRef<number>(-1);
|
||||
const next = useRef<boolean>(VALID_DATASOURCES.includes(dataSource));
|
||||
|
|
@ -199,24 +211,20 @@ function IntegrationSelector(
|
|||
return;
|
||||
}
|
||||
|
||||
const itemKey = extractItemKey(dataSource, item);
|
||||
|
||||
switch (dataSource) {
|
||||
case ViewConstants.DATA_SOURCE_USERS: {
|
||||
setMultiselectSelected((current) => toggleFromMap(current, itemKey, item as UserProfile));
|
||||
return;
|
||||
}
|
||||
case ViewConstants.DATA_SOURCE_CHANNELS: {
|
||||
const itemKey = extractItemKey(dataSource, item as Channel);
|
||||
setMultiselectSelected((current) => toggleFromMap(current, itemKey, item as Channel));
|
||||
return;
|
||||
}
|
||||
default: {
|
||||
const itemKey = extractItemKey(dataSource, item as DialogOption);
|
||||
setMultiselectSelected((current) => toggleFromMap(current, itemKey, item as DialogOption));
|
||||
}
|
||||
}
|
||||
}, [isMultiselect, dataSource, handleSelect]);
|
||||
|
||||
const handleRemoveOption = useCallback((item: UserProfile | Channel | DialogOption) => {
|
||||
const handleRemoveOption = useCallback((item: Channel | DialogOption) => {
|
||||
const itemKey = extractItemKey(dataSource, item);
|
||||
setMultiselectSelected((current) => {
|
||||
const multiselectSelectedItems = {...current};
|
||||
|
|
@ -242,32 +250,13 @@ function IntegrationSelector(
|
|||
}
|
||||
}, 100), [loading, term, serverUrl, currentTeamId, integrationData]);
|
||||
|
||||
const getProfiles = useCallback(debounce(async () => {
|
||||
if (next.current && !loading && !term) {
|
||||
setLoading(true);
|
||||
page.current += 1;
|
||||
|
||||
const {users: profiles} = await fetchProfiles(serverUrl, page.current);
|
||||
|
||||
setLoading(false);
|
||||
|
||||
if (profiles && profiles.length > 0) {
|
||||
setIntegrationData([...integrationData as UserProfile[], ...profiles]);
|
||||
} else {
|
||||
next.current = false;
|
||||
}
|
||||
}
|
||||
}, 100), [loading, term, integrationData]);
|
||||
|
||||
const loadMore = useCallback(async () => {
|
||||
if (dataSource === ViewConstants.DATA_SOURCE_USERS) {
|
||||
await getProfiles();
|
||||
} else if (dataSource === ViewConstants.DATA_SOURCE_CHANNELS) {
|
||||
if (dataSource === ViewConstants.DATA_SOURCE_CHANNELS) {
|
||||
await getChannels();
|
||||
}
|
||||
|
||||
// dynamic options are not paged so are not reloaded on scroll
|
||||
}, [getProfiles, getChannels, dataSource]);
|
||||
}, [getChannels, dataSource]);
|
||||
|
||||
const searchDynamicOptions = useCallback(async (searchTerm = '') => {
|
||||
if (options && options !== integrationData && !searchTerm) {
|
||||
|
|
@ -288,10 +277,25 @@ function IntegrationSelector(
|
|||
}
|
||||
}, [options, getDynamicOptions, integrationData]);
|
||||
|
||||
const handleSelectProfile = useCallback((user: UserProfile): void => {
|
||||
if (!isMultiselect) {
|
||||
handleSelect(user);
|
||||
close();
|
||||
}
|
||||
|
||||
setSelectedIds((current) => handleIdSelection(dataSource, current, user));
|
||||
}, [isMultiselect, handleIdSelection, handleSelect, close, dataSource]);
|
||||
|
||||
const onHandleMultiselectSubmit = useCallback(() => {
|
||||
handleSelect(Object.values(multiselectSelected));
|
||||
if (dataSource === ViewConstants.DATA_SOURCE_USERS) {
|
||||
// New multiselect
|
||||
handleSelect(Object.values(selectedIds) as UserProfile[]);
|
||||
} else {
|
||||
// Legacy multiselect
|
||||
handleSelect(Object.values(multiselectSelected));
|
||||
}
|
||||
close();
|
||||
}, [multiselectSelected, handleSelect]);
|
||||
}, [multiselectSelected, selectedIds, handleSelect]);
|
||||
|
||||
const onSearch = useCallback((text: string) => {
|
||||
if (!text) {
|
||||
|
|
@ -313,15 +317,7 @@ function IntegrationSelector(
|
|||
|
||||
setLoading(true);
|
||||
|
||||
if (dataSource === ViewConstants.DATA_SOURCE_USERS) {
|
||||
const {data: userData} = await searchProfiles(
|
||||
serverUrl, text.toLowerCase(),
|
||||
{team_id: currentTeamId, allow_inactive: true});
|
||||
|
||||
if (userData) {
|
||||
setSearchResults(userData);
|
||||
}
|
||||
} else if (dataSource === ViewConstants.DATA_SOURCE_CHANNELS) {
|
||||
if (dataSource === ViewConstants.DATA_SOURCE_CHANNELS) {
|
||||
const isSearch = true;
|
||||
const {channels: receivedChannels} = await searchChannels(
|
||||
serverUrl, text, currentTeamId, isSearch);
|
||||
|
|
@ -350,9 +346,7 @@ function IntegrationSelector(
|
|||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (dataSource === ViewConstants.DATA_SOURCE_USERS) {
|
||||
getProfiles();
|
||||
} else if (dataSource === ViewConstants.DATA_SOURCE_CHANNELS) {
|
||||
if (dataSource === ViewConstants.DATA_SOURCE_CHANNELS) {
|
||||
getChannels();
|
||||
} else {
|
||||
// Static and dynamic option search
|
||||
|
|
@ -361,16 +355,12 @@ function IntegrationSelector(
|
|||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
let listData: (DataType | UserProfileSection[]) = integrationData;
|
||||
let listData: DataTypeList = integrationData;
|
||||
|
||||
if (term) {
|
||||
listData = searchResults;
|
||||
}
|
||||
|
||||
if (dataSource === ViewConstants.DATA_SOURCE_USERS) {
|
||||
listData = createProfilesSections(listData as UserProfile[]);
|
||||
}
|
||||
|
||||
if (dataSource === ViewConstants.DATA_SOURCE_DYNAMIC) {
|
||||
listData = (integrationData as DialogOption[]).filter((option) => option.text && option.text.toLowerCase().includes(term));
|
||||
}
|
||||
|
|
@ -411,12 +401,6 @@ function IntegrationSelector(
|
|||
|
||||
let text;
|
||||
switch (dataSource) {
|
||||
case ViewConstants.DATA_SOURCE_USERS:
|
||||
text = {
|
||||
id: t('mobile.integration_selector.loading_users'),
|
||||
defaultMessage: 'Loading Users...',
|
||||
};
|
||||
break;
|
||||
case ViewConstants.DATA_SOURCE_CHANNELS:
|
||||
text = {
|
||||
id: t('mobile.integration_selector.loading_channels'),
|
||||
|
|
@ -485,26 +469,8 @@ function IntegrationSelector(
|
|||
);
|
||||
}, [multiselectSelected, theme, isMultiselect]);
|
||||
|
||||
const renderUserItem = useCallback((itemProps: any): JSX.Element => {
|
||||
const itemSelected = Boolean(multiselectSelected[itemProps.item.id]);
|
||||
|
||||
return (
|
||||
<UserListRow
|
||||
key={itemProps.id}
|
||||
{...itemProps}
|
||||
theme={theme}
|
||||
selectable={isMultiselect}
|
||||
user={itemProps.item}
|
||||
teammateNameDisplay={teammateNameDisplay}
|
||||
selected={itemSelected}
|
||||
/>
|
||||
);
|
||||
}, [multiselectSelected, theme, isMultiselect, teammateNameDisplay]);
|
||||
|
||||
const getRenderItem = (): (itemProps: any) => JSX.Element => {
|
||||
switch (dataSource) {
|
||||
case ViewConstants.DATA_SOURCE_USERS:
|
||||
return renderUserItem;
|
||||
case ViewConstants.DATA_SOURCE_CHANNELS:
|
||||
return renderChannelItem;
|
||||
default:
|
||||
|
|
@ -513,7 +479,12 @@ function IntegrationSelector(
|
|||
};
|
||||
|
||||
const renderSelectedOptions = useCallback((): React.ReactElement<string> | null => {
|
||||
const selectedItems: Channel[] | DialogOption[] | UserProfile[] = Object.values(multiselectSelected);
|
||||
let selectedItems: Channel[] | DialogOption[] | UserProfile[] = Object.values(multiselectSelected);
|
||||
|
||||
if (dataSource === ViewConstants.DATA_SOURCE_USERS) {
|
||||
// New multiselect
|
||||
selectedItems = Object.values(selectedIds) as UserProfile[];
|
||||
}
|
||||
|
||||
if (!selectedItems.length) {
|
||||
return null;
|
||||
|
|
@ -530,9 +501,38 @@ function IntegrationSelector(
|
|||
<View style={style.separator}/>
|
||||
</>
|
||||
);
|
||||
}, [multiselectSelected, style, theme]);
|
||||
}, [multiselectSelected, selectedIds, style, theme]);
|
||||
|
||||
const renderDataTypeList = () => {
|
||||
switch (dataSource) {
|
||||
case ViewConstants.DATA_SOURCE_USERS:
|
||||
return (
|
||||
<ServerUserList
|
||||
currentTeamId={currentTeamId}
|
||||
currentUserId={currentUserId}
|
||||
teammateNameDisplay={teammateNameDisplay}
|
||||
term={term}
|
||||
tutorialWatched={true}
|
||||
handleSelectProfile={handleSelectProfile}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
return (
|
||||
<CustomList
|
||||
data={customListData as (Channel[] | DialogOption[])}
|
||||
key='custom_list'
|
||||
loading={loading}
|
||||
loadingComponent={renderLoading()}
|
||||
noResults={renderNoResults}
|
||||
onLoadMore={loadMore}
|
||||
onRowPress={handleSelectItem}
|
||||
renderItem={getRenderItem()}
|
||||
theme={theme}
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const listType = dataSource === ViewConstants.DATA_SOURCE_USERS ? SECTIONLIST : FLATLIST;
|
||||
const selectedOptionsComponent = renderSelectedOptions();
|
||||
|
||||
return (
|
||||
|
|
@ -555,18 +555,7 @@ function IntegrationSelector(
|
|||
|
||||
{selectedOptionsComponent}
|
||||
|
||||
<CustomList
|
||||
data={customListData as DataType}
|
||||
key='custom_list'
|
||||
listType={listType}
|
||||
loading={loading}
|
||||
loadingComponent={renderLoading()}
|
||||
noResults={renderNoResults}
|
||||
onLoadMore={loadMore}
|
||||
onRowPress={handleSelectItem}
|
||||
renderItem={getRenderItem()}
|
||||
theme={theme}
|
||||
/>
|
||||
{renderDataTypeList()}
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue