[FIX] Add search and fix pagination for omnichannels departments (#3621)
* [FIX] Search and pagination for omnichannels departments * pagination complete * minor tweak * renamed a param and workaround for a ux bug * fix style of flatlist and search as header scrollable * stick the header * Merge branch 'fix.forward-department-list' of https://github.com/RocketChat/Rocket.Chat.ReactNative into fix.forward-department-list * refactor pagination * fix value type * refactor render search * refactor layout * make ts happy
This commit is contained in:
parent
a58b27e4f1
commit
9ec598407d
|
@ -821,7 +821,6 @@ const RocketChat = {
|
|||
return sdk.methodCallWrapper(method, ...params);
|
||||
},
|
||||
getUserInfo,
|
||||
|
||||
getUidDirectMessage(room) {
|
||||
const { id: userId } = reduxStore.getState().login.user;
|
||||
|
||||
|
|
|
@ -421,9 +421,18 @@ export const getDepartmentInfo = (departmentId: string): any =>
|
|||
// RC 2.2.0
|
||||
sdk.get(`livechat/department/${departmentId}?includeAgents=false`);
|
||||
|
||||
export const getDepartments = () =>
|
||||
export const getDepartments = (args?: { count: number; offset: number; text: string }) => {
|
||||
let params;
|
||||
if (args) {
|
||||
params = {
|
||||
text: args.text,
|
||||
count: args.count,
|
||||
offset: args.offset
|
||||
};
|
||||
}
|
||||
// RC 2.2.0
|
||||
sdk.get('livechat/department');
|
||||
return sdk.get('livechat/department', params);
|
||||
};
|
||||
|
||||
export const usersAutoComplete = (selector: any) =>
|
||||
// RC 2.4.0
|
||||
|
|
|
@ -102,8 +102,10 @@ export type ChatsStackParamList = {
|
|||
PickerView: {
|
||||
title: string;
|
||||
data: IOptionsField[];
|
||||
value?: any; // TODO: Change
|
||||
onChangeText?: ((text: string) => IOptionsField[]) | ((term?: string) => Promise<any>);
|
||||
value?: string;
|
||||
onSearch?: (text?: string) => Promise<any>;
|
||||
onEndReached?: (text: string, offset?: number) => Promise<any>;
|
||||
total?: number;
|
||||
goBack?: boolean;
|
||||
onChangeValue: Function;
|
||||
};
|
||||
|
|
|
@ -13,8 +13,9 @@ import RocketChat from '../lib/rocketchat';
|
|||
import OrSeparator from '../containers/OrSeparator';
|
||||
import Input from '../containers/UIKit/MultiSelect/Input';
|
||||
import { forwardRoom as forwardRoomAction } from '../actions/room';
|
||||
import { ILivechatDepartment } from './definition/ILivechatDepartment';
|
||||
import { IRoom } from '../definitions';
|
||||
import { ChatsStackParamList } from '../stacks/types';
|
||||
import { IOptionsField } from './NotificationPreferencesView/options';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
|
@ -23,14 +24,6 @@ const styles = StyleSheet.create({
|
|||
}
|
||||
});
|
||||
|
||||
// TODO: Refactor when migrate room
|
||||
interface IRoom {
|
||||
departmentId?: any;
|
||||
servedBy?: {
|
||||
_id: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface ITransferData {
|
||||
roomId: string;
|
||||
userId?: string;
|
||||
|
@ -41,12 +34,6 @@ interface IUser {
|
|||
username: string;
|
||||
_id: string;
|
||||
}
|
||||
|
||||
interface IParsedData {
|
||||
label: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
interface IForwardLivechatViewProps {
|
||||
navigation: StackNavigationProp<ChatsStackParamList, 'ForwardLivechatView'>;
|
||||
route: RouteProp<ChatsStackParamList, 'ForwardLivechatView'>;
|
||||
|
@ -54,22 +41,31 @@ interface IForwardLivechatViewProps {
|
|||
forwardRoom: (rid: string, transferData: ITransferData) => void;
|
||||
}
|
||||
|
||||
const COUNT_DEPARTMENT = 50;
|
||||
|
||||
const ForwardLivechatView = ({ forwardRoom, navigation, route, theme }: IForwardLivechatViewProps) => {
|
||||
const [departments, setDepartments] = useState<IParsedData[]>([]);
|
||||
const [departments, setDepartments] = useState<IOptionsField[]>([]);
|
||||
const [departmentId, setDepartment] = useState('');
|
||||
const [users, setUsers] = useState<IParsedData[]>([]);
|
||||
const [departmentTotal, setDepartmentTotal] = useState(0);
|
||||
const [users, setUsers] = useState<IOptionsField[]>([]);
|
||||
const [userId, setUser] = useState();
|
||||
const [room, setRoom] = useState<IRoom>({});
|
||||
const [room, setRoom] = useState<IRoom>({} as IRoom);
|
||||
|
||||
const rid = route.params?.rid;
|
||||
|
||||
const getDepartments = async () => {
|
||||
const getDepartments = async (text = '', offset = 0) => {
|
||||
try {
|
||||
const result: any = await RocketChat.getDepartments();
|
||||
const result = await RocketChat.getDepartments({ count: COUNT_DEPARTMENT, text, offset });
|
||||
if (result.success) {
|
||||
setDepartments(
|
||||
result.departments.map((department: ILivechatDepartment) => ({ label: department.name, value: department._id }))
|
||||
);
|
||||
const parsedDepartments: IOptionsField[] = result.departments.map(department => ({
|
||||
label: department.name,
|
||||
value: department._id
|
||||
}));
|
||||
if (!text && !offset) {
|
||||
setDepartments(parsedDepartments);
|
||||
setDepartmentTotal(result?.total);
|
||||
}
|
||||
return { data: parsedDepartments, total: result?.total, offset: result?.offset };
|
||||
}
|
||||
} catch {
|
||||
// do nothing
|
||||
|
@ -80,26 +76,27 @@ const ForwardLivechatView = ({ forwardRoom, navigation, route, theme }: IForward
|
|||
try {
|
||||
const { servedBy: { _id: agentId } = {} } = room;
|
||||
const _id = agentId && { $ne: agentId };
|
||||
const result: any = await RocketChat.usersAutoComplete({
|
||||
const result = await RocketChat.usersAutoComplete({
|
||||
conditions: { _id, status: { $ne: 'offline' }, statusLivechat: 'available' },
|
||||
term
|
||||
});
|
||||
if (result.success) {
|
||||
const parsedUsers = result.items.map((user: IUser) => ({ label: user.username, value: user._id }));
|
||||
setUsers(parsedUsers);
|
||||
return parsedUsers;
|
||||
if (!term) {
|
||||
setUsers(parsedUsers);
|
||||
}
|
||||
return { data: parsedUsers };
|
||||
}
|
||||
} catch {
|
||||
// do nothing
|
||||
}
|
||||
return [];
|
||||
};
|
||||
|
||||
const getRoom = async () => {
|
||||
try {
|
||||
const result = await RocketChat.getRoomInfo(rid);
|
||||
if (result.success) {
|
||||
setRoom(result.room);
|
||||
setRoom(result.room as IRoom);
|
||||
}
|
||||
} catch {
|
||||
// do nothing
|
||||
|
@ -148,6 +145,9 @@ const ForwardLivechatView = ({ forwardRoom, navigation, route, theme }: IForward
|
|||
value: room?.departmentId,
|
||||
data: departments,
|
||||
onChangeValue: setDepartment,
|
||||
onSearch: getDepartments,
|
||||
onEndReached: getDepartments,
|
||||
total: departmentTotal,
|
||||
goBack: false
|
||||
});
|
||||
};
|
||||
|
@ -157,7 +157,7 @@ const ForwardLivechatView = ({ forwardRoom, navigation, route, theme }: IForward
|
|||
title: I18n.t('Forward_to_user'),
|
||||
data: users,
|
||||
onChangeValue: setUser,
|
||||
onChangeText: getUsers,
|
||||
onSearch: getUsers,
|
||||
goBack: false
|
||||
});
|
||||
};
|
||||
|
|
|
@ -24,6 +24,9 @@ const styles = StyleSheet.create({
|
|||
paddingVertical: 56,
|
||||
...sharedStyles.textSemibold,
|
||||
...sharedStyles.textAlignCenter
|
||||
},
|
||||
listNoHeader: {
|
||||
marginTop: 32
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -34,9 +37,16 @@ interface IItem {
|
|||
theme: string;
|
||||
}
|
||||
|
||||
interface IRenderSearch {
|
||||
hasSearch: boolean;
|
||||
onChangeText: (text: string) => void;
|
||||
}
|
||||
|
||||
interface IPickerViewState {
|
||||
data: IOptionsField[];
|
||||
value: string;
|
||||
total: number;
|
||||
searchText: string;
|
||||
}
|
||||
|
||||
interface IPickerViewProps {
|
||||
|
@ -54,8 +64,22 @@ const Item = React.memo(({ item, selected, onItemPress, theme }: IItem) => (
|
|||
/>
|
||||
));
|
||||
|
||||
const RenderSearch = ({ hasSearch, onChangeText }: IRenderSearch) => {
|
||||
if (!hasSearch) {
|
||||
return <List.Separator style={styles.listNoHeader} />;
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<View style={styles.search}>
|
||||
<SearchBox onChangeText={onChangeText} />
|
||||
</View>
|
||||
<List.Separator />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
class PickerView extends React.PureComponent<IPickerViewProps, IPickerViewState> {
|
||||
private onSearch?: ((text: string) => IOptionsField[]) | ((term?: string | undefined) => Promise<any>);
|
||||
private onSearch?: (text?: string) => Promise<any>;
|
||||
|
||||
static navigationOptions = ({ route }: IPickerViewProps) => ({
|
||||
title: route.params?.title ?? I18n.t('Select_an_option')
|
||||
|
@ -64,10 +88,10 @@ class PickerView extends React.PureComponent<IPickerViewProps, IPickerViewState>
|
|||
constructor(props: IPickerViewProps) {
|
||||
super(props);
|
||||
const data = props.route.params?.data ?? [];
|
||||
const value = props.route.params?.value;
|
||||
this.state = { data, value };
|
||||
|
||||
this.onSearch = props.route.params?.onChangeText;
|
||||
const value = props.route.params?.value ?? '';
|
||||
const total = props.route.params?.total ?? 0;
|
||||
this.state = { data, value, total, searchText: '' };
|
||||
this.onSearch = props.route.params?.onSearch;
|
||||
}
|
||||
|
||||
onChangeValue = (value: string) => {
|
||||
|
@ -80,28 +104,26 @@ class PickerView extends React.PureComponent<IPickerViewProps, IPickerViewState>
|
|||
}
|
||||
};
|
||||
|
||||
onChangeText = debounce(
|
||||
async (text: string) => {
|
||||
if (this.onSearch) {
|
||||
const data = await this.onSearch(text);
|
||||
this.setState({ data });
|
||||
onChangeText = debounce(async (searchText: string) => {
|
||||
if (this.onSearch) {
|
||||
const data = await this.onSearch(searchText);
|
||||
if (data?.data) {
|
||||
this.setState({ ...data, searchText });
|
||||
}
|
||||
},
|
||||
300,
|
||||
true
|
||||
);
|
||||
|
||||
renderSearch() {
|
||||
if (!this.onSearch) {
|
||||
return null;
|
||||
}
|
||||
}, 500);
|
||||
|
||||
return (
|
||||
<View style={styles.search}>
|
||||
<SearchBox onChangeText={this.onChangeText} />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
onEndReached = async () => {
|
||||
const { route } = this.props;
|
||||
const { data, total, searchText } = this.state;
|
||||
const onEndReached = route.params?.onEndReached;
|
||||
if (onEndReached && data.length < total) {
|
||||
const val = await onEndReached(searchText, data.length);
|
||||
if (val?.data) {
|
||||
this.setState({ ...val, data: [...data, ...val.data] });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const { data, value } = this.state;
|
||||
|
@ -109,7 +131,6 @@ class PickerView extends React.PureComponent<IPickerViewProps, IPickerViewState>
|
|||
|
||||
return (
|
||||
<SafeAreaView>
|
||||
{this.renderSearch()}
|
||||
<FlatList
|
||||
data={data}
|
||||
keyExtractor={item => item.value as string}
|
||||
|
@ -121,13 +142,14 @@ class PickerView extends React.PureComponent<IPickerViewProps, IPickerViewState>
|
|||
onItemPress={() => this.onChangeValue(item.value as string)}
|
||||
/>
|
||||
)}
|
||||
onEndReached={() => this.onEndReached()}
|
||||
onEndReachedThreshold={0.5}
|
||||
ItemSeparatorComponent={List.Separator}
|
||||
ListHeaderComponent={List.Separator}
|
||||
ListHeaderComponent={<RenderSearch hasSearch={!!this.onSearch} onChangeText={this.onChangeText} />}
|
||||
ListFooterComponent={List.Separator}
|
||||
ListEmptyComponent={() => (
|
||||
<Text style={[styles.noResult, { color: themes[theme].titleText }]}>{I18n.t('No_results_found')}</Text>
|
||||
)}
|
||||
contentContainerStyle={[List.styles.contentContainerStyleFlatList]}
|
||||
/>
|
||||
</SafeAreaView>
|
||||
);
|
||||
|
|
Loading…
Reference in New Issue