@droppii-org/chat-mobile 0.2.80 → 0.2.81

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/lib/module/config/api-endpoints.js +2 -1
  2. package/lib/module/config/api-endpoints.js.map +1 -1
  3. package/lib/module/hooks/contacts/useSearchContacts.js +50 -0
  4. package/lib/module/hooks/contacts/useSearchContacts.js.map +1 -0
  5. package/lib/module/hooks/query-keys.js +4 -0
  6. package/lib/module/hooks/query-keys.js.map +1 -1
  7. package/lib/module/screens/SearchConversation/ContactResultItem.js +119 -0
  8. package/lib/module/screens/SearchConversation/ContactResultItem.js.map +1 -0
  9. package/lib/module/screens/SearchConversation/SearchAllResult.js +26 -20
  10. package/lib/module/screens/SearchConversation/SearchAllResult.js.map +1 -1
  11. package/lib/module/screens/SearchConversation/SearchCommunityResult.js +79 -0
  12. package/lib/module/screens/SearchConversation/SearchCommunityResult.js.map +1 -0
  13. package/lib/module/screens/SearchConversation/SearchContactsResult.js +117 -0
  14. package/lib/module/screens/SearchConversation/SearchContactsResult.js.map +1 -0
  15. package/lib/module/screens/SearchConversation/SearchEmpty.js +2 -0
  16. package/lib/module/screens/SearchConversation/SearchEmpty.js.map +1 -1
  17. package/lib/module/screens/SearchConversation/SearchMessagesResult.js +1 -8
  18. package/lib/module/screens/SearchConversation/SearchMessagesResult.js.map +1 -1
  19. package/lib/module/screens/SearchConversation/SearchResultItem.js +2 -0
  20. package/lib/module/screens/SearchConversation/SearchResultItem.js.map +1 -1
  21. package/lib/module/screens/SearchConversation/TabResult.js +24 -7
  22. package/lib/module/screens/SearchConversation/TabResult.js.map +1 -1
  23. package/lib/module/screens/SearchConversation/index.js +6 -2
  24. package/lib/module/screens/SearchConversation/index.js.map +1 -1
  25. package/lib/module/screens/SearchConversation/types.js +1 -0
  26. package/lib/module/screens/SearchConversation/types.js.map +1 -1
  27. package/lib/module/screens/chat-detail/messageToolbox/MessageToolboxWrapper.js +11 -1
  28. package/lib/module/screens/chat-detail/messageToolbox/MessageToolboxWrapper.js.map +1 -1
  29. package/lib/module/services/endpoints.js +2 -1
  30. package/lib/module/services/endpoints.js.map +1 -1
  31. package/lib/module/translation/resources/i18n.js +6 -0
  32. package/lib/module/translation/resources/i18n.js.map +1 -1
  33. package/lib/module/types/chat.js +8 -0
  34. package/lib/module/types/chat.js.map +1 -1
  35. package/lib/module/utils/dateTimeUtils.js +10 -0
  36. package/lib/module/utils/dateTimeUtils.js.map +1 -1
  37. package/lib/module/utils/highlightSearch.js +6 -2
  38. package/lib/module/utils/highlightSearch.js.map +1 -1
  39. package/lib/module/utils/messageUtils.js +10 -2
  40. package/lib/module/utils/messageUtils.js.map +1 -1
  41. package/lib/typescript/src/config/api-endpoints.d.ts +1 -0
  42. package/lib/typescript/src/config/api-endpoints.d.ts.map +1 -1
  43. package/lib/typescript/src/hooks/contacts/useSearchContacts.d.ts +221 -0
  44. package/lib/typescript/src/hooks/contacts/useSearchContacts.d.ts.map +1 -0
  45. package/lib/typescript/src/hooks/query-keys.d.ts +4 -0
  46. package/lib/typescript/src/hooks/query-keys.d.ts.map +1 -1
  47. package/lib/typescript/src/screens/SearchConversation/ContactResultItem.d.ts +9 -0
  48. package/lib/typescript/src/screens/SearchConversation/ContactResultItem.d.ts.map +1 -0
  49. package/lib/typescript/src/screens/SearchConversation/SearchAllResult.d.ts +3 -2
  50. package/lib/typescript/src/screens/SearchConversation/SearchAllResult.d.ts.map +1 -1
  51. package/lib/typescript/src/screens/SearchConversation/SearchCommunityResult.d.ts +7 -0
  52. package/lib/typescript/src/screens/SearchConversation/SearchCommunityResult.d.ts.map +1 -0
  53. package/lib/typescript/src/screens/SearchConversation/SearchContactsResult.d.ts +8 -0
  54. package/lib/typescript/src/screens/SearchConversation/SearchContactsResult.d.ts.map +1 -0
  55. package/lib/typescript/src/screens/SearchConversation/SearchEmpty.d.ts.map +1 -1
  56. package/lib/typescript/src/screens/SearchConversation/SearchMessagesResult.d.ts.map +1 -1
  57. package/lib/typescript/src/screens/SearchConversation/SearchResultItem.d.ts.map +1 -1
  58. package/lib/typescript/src/screens/SearchConversation/TabResult.d.ts +4 -2
  59. package/lib/typescript/src/screens/SearchConversation/TabResult.d.ts.map +1 -1
  60. package/lib/typescript/src/screens/SearchConversation/index.d.ts +4 -2
  61. package/lib/typescript/src/screens/SearchConversation/index.d.ts.map +1 -1
  62. package/lib/typescript/src/screens/SearchConversation/types.d.ts +2 -1
  63. package/lib/typescript/src/screens/SearchConversation/types.d.ts.map +1 -1
  64. package/lib/typescript/src/screens/chat-detail/messageToolbox/MessageToolboxWrapper.d.ts.map +1 -1
  65. package/lib/typescript/src/services/endpoints.d.ts +1 -0
  66. package/lib/typescript/src/services/endpoints.d.ts.map +1 -1
  67. package/lib/typescript/src/translation/resources/i18n.d.ts.map +1 -1
  68. package/lib/typescript/src/types/chat.d.ts +27 -0
  69. package/lib/typescript/src/types/chat.d.ts.map +1 -1
  70. package/lib/typescript/src/utils/dateTimeUtils.d.ts +2 -0
  71. package/lib/typescript/src/utils/dateTimeUtils.d.ts.map +1 -1
  72. package/lib/typescript/src/utils/highlightSearch.d.ts.map +1 -1
  73. package/lib/typescript/src/utils/messageUtils.d.ts.map +1 -1
  74. package/package.json +3 -1
  75. package/src/build-ignore.d.ts +2 -0
  76. package/src/config/api-endpoints.ts +2 -0
  77. package/src/hooks/contacts/useSearchContacts.ts +66 -0
  78. package/src/hooks/query-keys.ts +6 -0
  79. package/src/screens/SearchConversation/ContactResultItem.tsx +128 -0
  80. package/src/screens/SearchConversation/SearchAllResult.tsx +38 -24
  81. package/src/screens/SearchConversation/SearchCommunityResult.tsx +96 -0
  82. package/src/screens/SearchConversation/SearchContactsResult.tsx +155 -0
  83. package/src/screens/SearchConversation/SearchEmpty.tsx +2 -0
  84. package/src/screens/SearchConversation/SearchMessagesResult.tsx +1 -6
  85. package/src/screens/SearchConversation/SearchResultItem.tsx +6 -1
  86. package/src/screens/SearchConversation/TabResult.tsx +40 -5
  87. package/src/screens/SearchConversation/index.tsx +10 -2
  88. package/src/screens/SearchConversation/types.tsx +1 -0
  89. package/src/screens/chat-detail/messageToolbox/MessageToolboxWrapper.tsx +22 -4
  90. package/src/services/endpoints.ts +1 -0
  91. package/src/translation/resources/i18n.ts +6 -0
  92. package/src/types/chat.ts +30 -0
  93. package/src/utils/dateTimeUtils.ts +15 -0
  94. package/src/utils/highlightSearch.ts +12 -2
  95. package/src/utils/messageUtils.ts +15 -9
@@ -0,0 +1,66 @@
1
+ import { useInfiniteQuery } from '@tanstack/react-query';
2
+ import { useMemo } from 'react';
3
+ import { contactQueryKeys } from '../query-keys';
4
+ import { apiInstance } from '../../services/apis';
5
+ import { ENDPOINTS } from '../../services/endpoints';
6
+ import type {
7
+ ContactCategory,
8
+ DChatApplicationType,
9
+ IContactSearchItem,
10
+ IContactSearchRequest,
11
+ } from '../../types/chat';
12
+ import type { BaseResponse } from '../../types/auth';
13
+ import { PAGE_SIZE } from '../../constants';
14
+
15
+ interface IUseSearchContactsProps {
16
+ query: string;
17
+ applicationType: DChatApplicationType;
18
+ type?: ContactCategory;
19
+ pageSize?: number;
20
+ }
21
+
22
+ export const useSearchContacts = ({
23
+ query,
24
+ applicationType,
25
+ type,
26
+ pageSize = PAGE_SIZE,
27
+ }: IUseSearchContactsProps) => {
28
+ const { data, ...rest } = useInfiniteQuery({
29
+ queryKey: [contactQueryKeys.search(query.trim(), type), pageSize],
30
+ queryFn: async ({ pageParam = 1 }) => {
31
+ const params: IContactSearchRequest = {
32
+ page: pageParam,
33
+ pageSize,
34
+ applicationType,
35
+ searchTerm: query.trim(),
36
+ type,
37
+ };
38
+ const res = await apiInstance?.post<BaseResponse<IContactSearchItem[]>>(
39
+ ENDPOINTS.chatService.searchContacts(),
40
+ params
41
+ );
42
+ return res?.data;
43
+ },
44
+ getNextPageParam: (lastPage) => {
45
+ const currentPage = lastPage?.pageable?.pageNumber || 1;
46
+ const dataLength = lastPage?.data?.length || 0;
47
+ return dataLength < pageSize ? undefined : currentPage + 1;
48
+ },
49
+ initialPageParam: 1,
50
+ enabled: !!query.trim() && !!applicationType,
51
+ });
52
+
53
+ const dataFlatten = useMemo<IContactSearchItem[]>(() => {
54
+ if (!query.trim() || !data) return [];
55
+ return (
56
+ data?.pages
57
+ ?.flatMap?.((page) => page?.data)
58
+ ?.filter((item): item is IContactSearchItem => item !== undefined) || []
59
+ );
60
+ }, [data, query]);
61
+
62
+ return {
63
+ dataFlatten,
64
+ ...rest,
65
+ };
66
+ };
@@ -62,6 +62,12 @@ export const messageQueryKeys = {
62
62
  [...messageQueryKeys.all, 'search', query, conversationId] as const,
63
63
  };
64
64
 
65
+ export const contactQueryKeys = {
66
+ all: ['contacts'] as const,
67
+ search: (query: string, type?: string) =>
68
+ [...contactQueryKeys.all, 'search', query, type] as const,
69
+ };
70
+
65
71
  export const groupQueryKeys = {
66
72
  all: ['groups'] as const,
67
73
  public: () => [...groupQueryKeys.all, 'public'] as const,
@@ -0,0 +1,128 @@
1
+ import {
2
+ KColors,
3
+ KContainer,
4
+ KImage,
5
+ KLabel,
6
+ KSpacingValue,
7
+ } from '@droppii/libs';
8
+ import { memo, useCallback, useMemo } from 'react';
9
+ import { StyleSheet } from 'react-native';
10
+ import { PeerType } from '@droppii/openim-rn-client-sdk';
11
+ import { ContactCategory, type IContactSearchItem } from '../../types/chat';
12
+ import { AvatarSection, NamePrefixIcon } from '../../components/ThreadCard';
13
+ import { formatLastOnlineTime } from '../../utils/dateTimeUtils';
14
+ import {
15
+ highlightSearch,
16
+ type HighlightResult,
17
+ } from '../../utils/highlightSearch';
18
+ import { trans } from '../../translation';
19
+ import isEmpty from 'lodash/isEmpty';
20
+
21
+ const CONTACT_PEER_TYPE: Record<ContactCategory, PeerType> = {
22
+ [ContactCategory.Customer]: PeerType.Customer,
23
+ [ContactCategory.Seller]: PeerType.User,
24
+ [ContactCategory.Pdp]: PeerType.Bot,
25
+ [ContactCategory.Group]: PeerType.Group,
26
+ };
27
+
28
+ interface IContactResultItemProps {
29
+ item: IContactSearchItem;
30
+ onPressSearchContact?: (item: IContactSearchItem) => void;
31
+ searchTerm?: string;
32
+ }
33
+
34
+ const ContactResultItem = memo((props: IContactResultItemProps) => {
35
+ const { item, onPressSearchContact, searchTerm } = props || {};
36
+ const peerType = CONTACT_PEER_TYPE[item?.type];
37
+
38
+ const subtitle = useMemo(() => {
39
+ if (item?.type === ContactCategory.Group) {
40
+ const description = !isEmpty(item?.description?.trim())
41
+ ? item?.description
42
+ : trans('group:no_description');
43
+ return `${trans('group:member_count', { count: item?.memberCount ?? 0 })} • ${description}`;
44
+ }
45
+ if (item?.type === ContactCategory.Pdp) {
46
+ return '';
47
+ }
48
+ return formatLastOnlineTime(item?.lastOnlineTime ?? null);
49
+ }, [item?.type, item?.description, item?.memberCount, item?.lastOnlineTime]);
50
+
51
+ const highlightResult: HighlightResult | null = useMemo(() => {
52
+ if (isEmpty(searchTerm?.trim())) return null;
53
+ return highlightSearch(item?.targetName || '', searchTerm || '');
54
+ }, [item?.targetName, searchTerm]);
55
+
56
+ const handlePressItem = useCallback(() => {
57
+ onPressSearchContact?.(item);
58
+ }, [onPressSearchContact, item]);
59
+
60
+ return (
61
+ <KContainer.Touchable
62
+ row
63
+ justifyContent="space-between"
64
+ alignItems
65
+ paddingV={'0.75rem'}
66
+ style={styles.container}
67
+ onPress={handlePressItem}
68
+ brBW={1}
69
+ brBC={KColors.border.light}
70
+ >
71
+ <KContainer.View row alignItems flex>
72
+ <AvatarSection
73
+ avatar={item?.targetAvatar ?? null}
74
+ fullName={item?.targetName}
75
+ peerType={peerType}
76
+ size="nm"
77
+ />
78
+ <KContainer.View flex marginL={'0.5rem'}>
79
+ <KContainer.View row alignItems>
80
+ <NamePrefixIcon peerType={peerType} />
81
+ {highlightResult != null ? (
82
+ <KLabel.Text typo="TextMdMedium" numberOfLines={1} flex>
83
+ {highlightResult?.before || ''}
84
+ <KLabel.Text typo="TextMdMedium" color={KColors.primary.normal}>
85
+ {highlightResult?.match || ''}
86
+ </KLabel.Text>
87
+ <KLabel.Text typo="TextMdMedium">
88
+ {highlightResult?.after || ''}
89
+ </KLabel.Text>
90
+ </KLabel.Text>
91
+ ) : (
92
+ <KLabel.Text typo="TextMdMedium" numberOfLines={1} flex>
93
+ {item?.targetName || ''}
94
+ </KLabel.Text>
95
+ )}
96
+ </KContainer.View>
97
+ <KContainer.VisibleView visible={!!subtitle}>
98
+ <KLabel.Text
99
+ typo="TextXsNormal"
100
+ color={KColors.gray.light}
101
+ numberOfLines={1}
102
+ >
103
+ {subtitle}
104
+ </KLabel.Text>
105
+ </KContainer.VisibleView>
106
+ </KContainer.View>
107
+ </KContainer.View>
108
+
109
+ <KContainer.VisibleView visible={false}>
110
+ <KContainer.View center>
111
+ <KImage.VectorIcons
112
+ name="more-horizontal-b"
113
+ size={24}
114
+ color={KColors.gray.normal}
115
+ />
116
+ </KContainer.View>
117
+ </KContainer.VisibleView>
118
+ </KContainer.Touchable>
119
+ );
120
+ });
121
+
122
+ export default ContactResultItem;
123
+
124
+ const styles = StyleSheet.create({
125
+ container: {
126
+ gap: KSpacingValue['1rem'],
127
+ },
128
+ });
@@ -10,41 +10,56 @@ import {
10
10
  import { memo, useCallback, useMemo } from 'react';
11
11
  import { useChatContext } from '../../context';
12
12
  import { useSearchMessage } from '../../hooks/message/useSearchMessage';
13
+ import { useSearchContacts } from '../../hooks/contacts/useSearchContacts';
13
14
  import { SearchConversationTabResultType } from './types';
14
- import type { ISearchMessageItem } from '../../types/chat';
15
+ import type { IContactSearchItem, ISearchMessageItem } from '../../types/chat';
15
16
  import { trans } from '../../translation';
16
17
  import { ActivityIndicator, FlatList, StyleSheet } from 'react-native';
17
18
  import { SearchEmpty } from './SearchEmpty';
18
19
  import SearchResultItem from './SearchResultItem';
20
+ import ContactResultItem from './ContactResultItem';
19
21
 
20
22
  interface SearchAllResultProps {
21
23
  searchTerm: string;
22
24
  handleChangeTab: (key: SearchConversationTabResultType) => void;
23
25
  onPressItem?: (item: ISearchMessageItem) => void;
26
+ onPressSearchContact?: (item: IContactSearchItem) => void;
24
27
  }
25
28
 
26
29
  interface ISearchAllResultItem {
27
30
  type: SearchConversationTabResultType;
28
- data: ISearchMessageItem[];
31
+ data: (ISearchMessageItem | IContactSearchItem)[];
29
32
  title: string;
30
33
  }
31
34
 
32
35
  const PAGE_SIZE = 5;
33
36
 
34
37
  const SearchAllResult = memo(
35
- ({ searchTerm, handleChangeTab, onPressItem }: SearchAllResultProps) => {
38
+ ({
39
+ searchTerm,
40
+ handleChangeTab,
41
+ onPressItem,
42
+ onPressSearchContact,
43
+ }: SearchAllResultProps) => {
36
44
  const { applicationType } = useChatContext();
37
- const { dataFlatten: messagesData, isFetching } = useSearchMessage({
38
- query: searchTerm,
39
- applicationType,
40
- pageSize: PAGE_SIZE,
41
- });
45
+ const { dataFlatten: messagesData, isFetching: isFetchingMessages } =
46
+ useSearchMessage({
47
+ query: searchTerm,
48
+ applicationType,
49
+ pageSize: PAGE_SIZE,
50
+ });
51
+ const { dataFlatten: contactsData, isFetching: isFetchingContacts } =
52
+ useSearchContacts({
53
+ query: searchTerm,
54
+ applicationType,
55
+ });
56
+ const isFetching = isFetchingMessages || isFetchingContacts;
42
57
 
43
58
  const data: ISearchAllResultItem[] = useMemo(() => {
44
59
  const mData = [
45
60
  {
46
61
  type: SearchConversationTabResultType.Conversations,
47
- data: [],
62
+ data: contactsData,
48
63
  title: trans('contacts'),
49
64
  },
50
65
  {
@@ -54,7 +69,7 @@ const SearchAllResult = memo(
54
69
  },
55
70
  ]?.filter((item) => item.data.length > 0);
56
71
  return mData;
57
- }, [messagesData]);
72
+ }, [messagesData, contactsData]);
58
73
 
59
74
  const onChangeTab = useCallback(
60
75
  (key: SearchConversationTabResultType) => {
@@ -80,21 +95,24 @@ const SearchAllResult = memo(
80
95
  </KLabel.Text>
81
96
  <KImage.VectorIcons name="angle-right-o" size={20} />
82
97
  </KContainer.Touchable>
83
- {item?.data?.map((childItem) => {
84
- return (
85
- <KContainer.View
86
- style={styles.childItemContainer}
87
- key={`${item.type}-${childItem?.conversation?.conversationID}-${childItem?.chatLog?.clientMsgID}`}
88
- >
98
+ {item.type === SearchConversationTabResultType.Conversations
99
+ ? (item.data as IContactSearchItem[]).map((childItem) => (
100
+ <ContactResultItem
101
+ key={`${item.type}-${childItem?.id}`}
102
+ item={childItem}
103
+ onPressSearchContact={onPressSearchContact}
104
+ searchTerm={searchTerm}
105
+ />
106
+ ))
107
+ : (item.data as ISearchMessageItem[]).map((childItem) => (
89
108
  <SearchResultItem
109
+ key={`${item.type}-${childItem?.conversation?.conversationID}-${childItem?.chatLog?.clientMsgID}`}
90
110
  item={childItem}
91
111
  tabResultType={item.type}
92
112
  onPressItem={onPressItem}
93
113
  searchTerm={searchTerm}
94
114
  />
95
- </KContainer.View>
96
- );
97
- })}
115
+ ))}
98
116
  <KButton.Link
99
117
  label={trans('view_all')}
100
118
  icon={{
@@ -108,7 +126,7 @@ const SearchAllResult = memo(
108
126
  </KContainer.View>
109
127
  );
110
128
  },
111
- [onChangeTab, onPressItem, searchTerm]
129
+ [onChangeTab, onPressItem, onPressSearchContact, searchTerm]
112
130
  );
113
131
 
114
132
  const keyExtractor = useCallback((item: ISearchAllResultItem) => {
@@ -158,10 +176,6 @@ const styles = StyleSheet.create({
158
176
  borderBottomWidth: 1,
159
177
  borderBottomColor: KColors.border.light,
160
178
  },
161
- childItemContainer: {
162
- borderBottomWidth: 1,
163
- borderBottomColor: KColors.border.light,
164
- },
165
179
  viewAllButton: {
166
180
  height: 40,
167
181
  },
@@ -0,0 +1,96 @@
1
+ import { KColors, KContainer, KSpacingValue } from '@droppii/libs';
2
+ import { memo, useCallback, useMemo } from 'react';
3
+ import { ActivityIndicator, FlatList, StyleSheet } from 'react-native';
4
+ import isEmpty from 'lodash/isEmpty';
5
+ import type { SearchPublicGroupInfo } from '@droppii/openim-rn-client-sdk';
6
+ import { useFetchPublicGroup } from '../../hooks/groups/useFetchPublicGroup';
7
+ import { GroupItem } from '../../components/GroupItem';
8
+ import { SearchEmpty } from './SearchEmpty';
9
+ import { SearchConversationTabResultType } from './types';
10
+
11
+ interface ISearchCommunityResultProps {
12
+ searchTerm: string;
13
+ onPressGroup?: (groupId: string) => void;
14
+ }
15
+
16
+ const keyExtractor = (item: SearchPublicGroupInfo) => item.groupID;
17
+
18
+ const SearchCommunityResult = memo(
19
+ ({ searchTerm, onPressGroup }: ISearchCommunityResultProps) => {
20
+ const hasKeyword = !isEmpty(searchTerm?.trim());
21
+
22
+ const {
23
+ dataFlatten,
24
+ hasNextPage,
25
+ fetchNextPage,
26
+ isFetchingNextPage,
27
+ isFetching,
28
+ isRefetching,
29
+ refetch,
30
+ } = useFetchPublicGroup(searchTerm);
31
+
32
+ const data = useMemo(
33
+ () => (hasKeyword ? dataFlatten : []),
34
+ [hasKeyword, dataFlatten]
35
+ );
36
+
37
+ const renderItem = useCallback(
38
+ ({ item }: { item: SearchPublicGroupInfo }) => (
39
+ <GroupItem item={item} onPress={onPressGroup} />
40
+ ),
41
+ [onPressGroup]
42
+ );
43
+
44
+ const onEndReached = useCallback(() => {
45
+ if (hasKeyword && hasNextPage && !isFetchingNextPage) {
46
+ fetchNextPage();
47
+ }
48
+ }, [hasKeyword, hasNextPage, isFetchingNextPage, fetchNextPage]);
49
+
50
+ if (!hasKeyword) {
51
+ return (
52
+ <KContainer.View flex>
53
+ <SearchEmpty
54
+ searchTerm={searchTerm}
55
+ searchType={SearchConversationTabResultType.Community}
56
+ />
57
+ </KContainer.View>
58
+ );
59
+ }
60
+
61
+ return (
62
+ <KContainer.View flex>
63
+ {isFetching && !isFetchingNextPage && !isRefetching ? (
64
+ <KContainer.View flex center>
65
+ <ActivityIndicator color={KColors.primary.normal} size={'small'} />
66
+ </KContainer.View>
67
+ ) : (
68
+ <FlatList
69
+ data={data}
70
+ renderItem={renderItem}
71
+ keyExtractor={keyExtractor}
72
+ contentContainerStyle={styles.container}
73
+ showsVerticalScrollIndicator={false}
74
+ onEndReached={onEndReached}
75
+ ListEmptyComponent={
76
+ <SearchEmpty
77
+ searchTerm={searchTerm}
78
+ searchType={SearchConversationTabResultType.Community}
79
+ />
80
+ }
81
+ refreshing={isFetching}
82
+ onRefresh={refetch}
83
+ />
84
+ )}
85
+ </KContainer.View>
86
+ );
87
+ }
88
+ );
89
+
90
+ export default SearchCommunityResult;
91
+
92
+ const styles = StyleSheet.create({
93
+ container: {
94
+ paddingHorizontal: KSpacingValue['0.75rem'],
95
+ },
96
+ });
@@ -0,0 +1,155 @@
1
+ import {
2
+ KColors,
3
+ KContainer,
4
+ KSpacingValue,
5
+ KTagsChoiceSlideBar,
6
+ type KTagsChoiceSlideBarItemProps,
7
+ } from '@droppii/libs';
8
+ import { memo, useCallback, useMemo, useState } from 'react';
9
+ import { ActivityIndicator, FlatList, StyleSheet } from 'react-native';
10
+ import { useChatContext } from '../../context';
11
+ import { useSearchContacts } from '../../hooks/contacts/useSearchContacts';
12
+ import { ContactCategory, type IContactSearchItem } from '../../types/chat';
13
+ import { trans } from '../../translation';
14
+ import { SearchEmpty } from './SearchEmpty';
15
+ import ContactResultItem from './ContactResultItem';
16
+ import { SearchConversationTabResultType } from './types';
17
+
18
+ interface ISearchContactsResultProps {
19
+ searchTerm: string;
20
+ onPressSearchContact?: (item: IContactSearchItem) => void;
21
+ }
22
+
23
+ const FILTER_ITEMS: KTagsChoiceSlideBarItemProps[] = [
24
+ {
25
+ id: ContactCategory.Customer,
26
+ label: trans('contact_type:customer'),
27
+ allowDeselect: true,
28
+ },
29
+ {
30
+ id: ContactCategory.Pdp,
31
+ label: trans('contact_type:pdp'),
32
+ allowDeselect: true,
33
+ },
34
+ {
35
+ id: ContactCategory.Group,
36
+ label: trans('contact_type:group'),
37
+ allowDeselect: true,
38
+ },
39
+ {
40
+ id: ContactCategory.Seller,
41
+ label: trans('contact_type:seller'),
42
+ allowDeselect: true,
43
+ },
44
+ ];
45
+
46
+ const SearchContactsResult = memo(
47
+ ({ searchTerm, onPressSearchContact }: ISearchContactsResultProps) => {
48
+ const { applicationType } = useChatContext();
49
+ const [contactType, setContactType] = useState<
50
+ ContactCategory | undefined
51
+ >();
52
+
53
+ const {
54
+ dataFlatten,
55
+ hasNextPage,
56
+ fetchNextPage,
57
+ isFetchingNextPage,
58
+ isFetching,
59
+ isRefetching,
60
+ refetch,
61
+ } = useSearchContacts({
62
+ query: searchTerm,
63
+ applicationType,
64
+ type: contactType,
65
+ });
66
+
67
+ const onFilterChanged = useCallback(
68
+ (item?: KTagsChoiceSlideBarItemProps) => {
69
+ setContactType(item?.id as ContactCategory | undefined);
70
+ },
71
+ []
72
+ );
73
+
74
+ const renderItem = useCallback(
75
+ ({ item }: { item: IContactSearchItem }) => {
76
+ return (
77
+ <ContactResultItem
78
+ item={item}
79
+ onPressSearchContact={onPressSearchContact}
80
+ searchTerm={searchTerm}
81
+ />
82
+ );
83
+ },
84
+ [onPressSearchContact, searchTerm]
85
+ );
86
+
87
+ const keyExtractor = useCallback(
88
+ (item: IContactSearchItem) => item?.id,
89
+ []
90
+ );
91
+
92
+ const onEndReached = useCallback(() => {
93
+ if (hasNextPage && !isFetchingNextPage) {
94
+ fetchNextPage();
95
+ }
96
+ }, [hasNextPage, isFetchingNextPage, fetchNextPage]);
97
+
98
+ const ListHeaderComponent = useMemo(
99
+ () => (
100
+ <KContainer.View style={styles.filterContainer}>
101
+ <KTagsChoiceSlideBar
102
+ items={FILTER_ITEMS}
103
+ onChanged={onFilterChanged}
104
+ containerStyle={styles.filterContentContainer}
105
+ type="outline"
106
+ />
107
+ </KContainer.View>
108
+ ),
109
+ [onFilterChanged]
110
+ );
111
+
112
+ return (
113
+ <KContainer.View flex>
114
+ {ListHeaderComponent}
115
+ {isFetching && !isFetchingNextPage && !isRefetching ? (
116
+ <KContainer.View flex center>
117
+ <ActivityIndicator color={KColors.primary.normal} size={'small'} />
118
+ </KContainer.View>
119
+ ) : (
120
+ <FlatList
121
+ data={dataFlatten}
122
+ renderItem={renderItem}
123
+ keyExtractor={keyExtractor}
124
+ contentContainerStyle={styles.container}
125
+ showsVerticalScrollIndicator={false}
126
+ onEndReached={onEndReached}
127
+ ListEmptyComponent={
128
+ <SearchEmpty
129
+ searchTerm={searchTerm}
130
+ searchType={SearchConversationTabResultType.Conversations}
131
+ />
132
+ }
133
+ refreshing={isFetching}
134
+ onRefresh={refetch}
135
+ />
136
+ )}
137
+ </KContainer.View>
138
+ );
139
+ }
140
+ );
141
+
142
+ export default SearchContactsResult;
143
+
144
+ const styles = StyleSheet.create({
145
+ container: {
146
+ paddingHorizontal: KSpacingValue['0.75rem'],
147
+ },
148
+ filterContainer: {
149
+ borderBottomWidth: 1,
150
+ borderBottomColor: KColors.border.light,
151
+ },
152
+ filterContentContainer: {
153
+ padding: 12,
154
+ },
155
+ });
@@ -22,6 +22,8 @@ export const SearchEmpty = memo(
22
22
  return 'search_message:empty_hint';
23
23
  case SearchConversationTabResultType.Conversations:
24
24
  return 'search_conversation:empty_hint';
25
+ case SearchConversationTabResultType.Community:
26
+ return 'search_community:empty_hint';
25
27
  default:
26
28
  return 'search:empty_hint';
27
29
  }
@@ -1,4 +1,4 @@
1
- import { KColors, KContainer, KDivider, KSpacingValue } from '@droppii/libs';
1
+ import { KColors, KContainer, KSpacingValue } from '@droppii/libs';
2
2
  import { memo, useCallback } from 'react';
3
3
  import { useSearchMessage } from '../../hooks/message/useSearchMessage';
4
4
  import { useChatContext } from '../../context';
@@ -52,10 +52,6 @@ const SearchMessagesResult = memo(
52
52
  }
53
53
  }, [hasNextPage, isFetchingNextPage, fetchNextPage]);
54
54
 
55
- const ItemSeparatorComponent = useCallback(() => {
56
- return <KDivider size={'hairline'} vertical={false} />;
57
- }, []);
58
-
59
55
  return (
60
56
  <KContainer.View flex>
61
57
  {isFetching && !isFetchingNextPage && !isRefetching ? (
@@ -70,7 +66,6 @@ const SearchMessagesResult = memo(
70
66
  contentContainerStyle={styles.container}
71
67
  showsVerticalScrollIndicator={false}
72
68
  onEndReached={onEndReached}
73
- ItemSeparatorComponent={ItemSeparatorComponent}
74
69
  ListEmptyComponent={
75
70
  <SearchEmpty
76
71
  searchTerm={searchTerm}
@@ -68,7 +68,12 @@ const SearchResultItem = memo((props: ISearchResultItemProps) => {
68
68
  }, [onPressItem, item?.conversation, item?.chatLog]);
69
69
 
70
70
  return (
71
- <KContainer.Touchable style={styles.container} onPress={handlePressItem}>
71
+ <KContainer.Touchable
72
+ style={styles.container}
73
+ onPress={handlePressItem}
74
+ brBW={1}
75
+ brBC={KColors.border.light}
76
+ >
72
77
  <KContainer.View style={styles.leftContainer}>
73
78
  <AvatarSection
74
79
  avatar={item?.conversation?.faceURL ?? null}