diff --git a/src/libs/API/parameters/SearchForRoomsToMentionParams.ts b/src/libs/API/parameters/SearchForRoomsToMentionParams.ts new file mode 100644 index 000000000000..6a4efe7aed6b --- /dev/null +++ b/src/libs/API/parameters/SearchForRoomsToMentionParams.ts @@ -0,0 +1,6 @@ +type SearchForRoomsToMentionParams = { + query: string; + policyID: string; +}; + +export default SearchForRoomsToMentionParams; diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts index 61a0d6870cd5..2d7948076548 100644 --- a/src/libs/API/parameters/index.ts +++ b/src/libs/API/parameters/index.ts @@ -48,6 +48,7 @@ export type {default as RequestAccountValidationLinkParams} from './RequestAccou export type {default as ResolveActionableMentionWhisperParams} from './ResolveActionableMentionWhisperParams'; export type {default as RevealExpensifyCardDetailsParams} from './RevealExpensifyCardDetailsParams'; export type {default as SearchForReportsParams} from './SearchForReportsParams'; +export type {default as SearchForRoomsToMentionParams} from './SearchForRoomsToMentionParams'; export type {default as SendPerformanceTimingParams} from './SendPerformanceTimingParams'; export type {default as SetContactMethodAsDefaultParams} from './SetContactMethodAsDefaultParams'; export type {default as SignInUserWithLinkParams} from './SignInUserWithLinkParams'; diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index cb904b0031f3..7a40400b3826 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -442,6 +442,7 @@ const READ_COMMANDS = { GET_REPORT_PRIVATE_NOTE: 'GetReportPrivateNote', OPEN_ROOM_MEMBERS_PAGE: 'OpenRoomMembersPage', SEARCH_FOR_REPORTS: 'SearchForReports', + SEARCH_FOR_ROOMS_TO_MENTION: 'SearchForRoomsToMention', SEND_PERFORMANCE_TIMING: 'SendPerformanceTiming', GET_ROUTE: 'GetRoute', GET_ROUTE_FOR_DRAFT: 'GetRouteForDraft', @@ -484,6 +485,7 @@ type ReadCommandParameters = { [READ_COMMANDS.GET_REPORT_PRIVATE_NOTE]: Parameters.GetReportPrivateNoteParams; [READ_COMMANDS.OPEN_ROOM_MEMBERS_PAGE]: Parameters.OpenRoomMembersPageParams; [READ_COMMANDS.SEARCH_FOR_REPORTS]: Parameters.SearchForReportsParams; + [READ_COMMANDS.SEARCH_FOR_ROOMS_TO_MENTION]: Parameters.SearchForRoomsToMentionParams; [READ_COMMANDS.SEND_PERFORMANCE_TIMING]: Parameters.SendPerformanceTimingParams; [READ_COMMANDS.GET_ROUTE]: Parameters.GetRouteParams; [READ_COMMANDS.GET_ROUTE_FOR_DRAFT]: Parameters.GetRouteParams; diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index 8b0dbf8a37a9..9de059acd1d7 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -34,6 +34,7 @@ import type { RemoveFromRoomParams, ResolveActionableMentionWhisperParams, SearchForReportsParams, + SearchForRoomsToMentionParams, SetNameValuePairParams, TogglePinnedChatParams, UpdateCommentParams, @@ -3426,7 +3427,7 @@ function savePrivateNotesDraft(reportID: string, note: string) { Onyx.merge(`${ONYXKEYS.COLLECTION.PRIVATE_NOTES_DRAFT}${reportID}`, note); } -function searchForReports(searchInput: string) { +function searchForReports(searchInput: string, policyID?: string) { // We do not try to make this request while offline because it sets a loading indicator optimistically if (isNetworkOffline) { Onyx.set(ONYXKEYS.IS_SEARCHING_FOR_REPORTS, false); @@ -3449,12 +3450,16 @@ function searchForReports(searchInput: string) { }, ]; - const parameters: SearchForReportsParams = {searchInput}; + const searchForRoomToMentionParams: SearchForRoomsToMentionParams = {query: searchInput, policyID: policyID ?? ''}; + const searchForReportsParams: SearchForReportsParams = {searchInput}; - API.read(READ_COMMANDS.SEARCH_FOR_REPORTS, parameters, {successData, failureData}); + API.read(policyID ? READ_COMMANDS.SEARCH_FOR_ROOMS_TO_MENTION : READ_COMMANDS.SEARCH_FOR_REPORTS, policyID ? searchForRoomToMentionParams : searchForReportsParams, { + successData, + failureData, + }); } -function searchInServer(searchInput: string) { +function searchInServer(searchInput: string, policyID?: string) { if (isNetworkOffline || !searchInput.trim().length) { Onyx.set(ONYXKEYS.IS_SEARCHING_FOR_REPORTS, false); return; @@ -3464,7 +3469,7 @@ function searchInServer(searchInput: string) { // we want to show the loading state right away. Otherwise, we will see a flashing UI where the client options are sorted and // tell the user there are no options, then we start searching, and tell them there are no options again. Onyx.set(ONYXKEYS.IS_SEARCHING_FOR_REPORTS, true); - searchForReports(searchInput); + searchForReports(searchInput, policyID); } function updateLastVisitTime(reportID: string) { diff --git a/src/pages/home/report/ReportActionCompose/SuggestionMention.tsx b/src/pages/home/report/ReportActionCompose/SuggestionMention.tsx index 9884d005b3f4..dbd550e1cd7c 100644 --- a/src/pages/home/report/ReportActionCompose/SuggestionMention.tsx +++ b/src/pages/home/report/ReportActionCompose/SuggestionMention.tsx @@ -1,8 +1,8 @@ import Str from 'expensify-common/lib/str'; import lodashSortBy from 'lodash/sortBy'; -import type {ForwardedRef, RefAttributes} from 'react'; +import type {ForwardedRef} from 'react'; import React, {forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState} from 'react'; -import {withOnyx} from 'react-native-onyx'; +import {useOnyx} from 'react-native-onyx'; import type {OnyxCollection} from 'react-native-onyx'; import * as Expensicons from '@components/Icon/Expensicons'; import type {Mention} from '@components/MentionSuggestions'; @@ -10,6 +10,7 @@ import MentionSuggestions from '@components/MentionSuggestions'; import {usePersonalDetails} from '@components/OnyxProvider'; import useArrowKeyFocusManager from '@hooks/useArrowKeyFocusManager'; import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; +import useDebounce from '@hooks/useDebounce'; import useLocalize from '@hooks/useLocalize'; import * as LoginUtils from '@libs/LoginUtils'; import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; @@ -17,6 +18,7 @@ import * as ReportUtils from '@libs/ReportUtils'; import * as SuggestionsUtils from '@libs/SuggestionUtils'; import * as UserUtils from '@libs/UserUtils'; import {isValidRoomName} from '@libs/ValidationUtils'; +import * as ReportUserActions from '@userActions/Report'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {PersonalDetailsList, Report} from '@src/types/onyx'; @@ -31,11 +33,6 @@ type SuggestionValues = { prefixType: string; }; -type RoomMentionOnyxProps = { - /** All reports shared with the user */ - reports: OnyxCollection; -}; - /** * Check if this piece of string looks like a mention */ @@ -50,24 +47,15 @@ const defaultSuggestionsValues: SuggestionValues = { }; function SuggestionMention( - { - value, - selection, - setSelection, - updateComment, - isAutoSuggestionPickerLarge, - measureParentContainer, - isComposerFocused, - reports, - isGroupPolicyReport, - policyID, - }: SuggestionProps & RoomMentionOnyxProps, + {value, selection, setSelection, updateComment, isAutoSuggestionPickerLarge, measureParentContainer, isComposerFocused, isGroupPolicyReport, policyID}: SuggestionProps, ref: ForwardedRef, ) { const personalDetails = usePersonalDetails() ?? CONST.EMPTY_OBJECT; const {translate, formatPhoneNumber} = useLocalize(); const [suggestionValues, setSuggestionValues] = useState(defaultSuggestionsValues); + const [reports] = useOnyx(ONYXKEYS.COLLECTION.REPORT); + const currentUserPersonalDetails = useCurrentUserPersonalDetails(); const isMentionSuggestionsMenuVisible = !!suggestionValues.suggestedMentions.length && suggestionValues.shouldShowSuggestionMenu; @@ -90,6 +78,21 @@ function SuggestionMention( // Used to decide whether to block the suggestions list from showing to prevent flickering const shouldBlockCalc = useRef(false); + /** + * Search for reports suggestions in server. + * + * The function is debounced to not perform requests on every keystroke. + */ + const debouncedSearchInServer = useDebounce( + useCallback(() => { + const foundSuggestionsCount = suggestionValues.suggestedMentions.length; + if (suggestionValues.prefixType === '#' && foundSuggestionsCount < 5) { + ReportUserActions.searchInServer(value, policyID); + } + }, [suggestionValues.suggestedMentions.length, suggestionValues.prefixType, policyID, value]), + CONST.TIMING.SEARCH_OPTION_LIST_DEBOUNCE_TIME, + ); + const formatLoginPrivateDomain = useCallback( (displayText = '', userLogin = '') => { if (userLogin !== displayText) { @@ -137,6 +140,7 @@ function SuggestionMention( setSuggestionValues((prevState) => ({ ...prevState, suggestedMentions: [], + shouldShowSuggestionMenu: false, })); }, [ @@ -323,10 +327,11 @@ function SuggestionMention( const shouldDisplayRoomMentionsSuggestions = isGroupPolicyReport && (isValidRoomName(suggestionWord.toLowerCase()) || prefix === ''); if (prefixType === '#' && shouldDisplayRoomMentionsSuggestions) { - // filter reports by room name and current policy - const filteredRoomMentions = getRoomMentionOptions(prefix, reports); - nextState.suggestedMentions = filteredRoomMentions; - nextState.shouldShowSuggestionMenu = !!filteredRoomMentions.length; + // Filter reports by room name and current policy + nextState.suggestedMentions = getRoomMentionOptions(prefix, reports); + + // Even if there are no reports, we should show the suggestion menu - to perform live search + nextState.shouldShowSuggestionMenu = true; } setSuggestionValues((prevState) => ({ @@ -342,6 +347,10 @@ function SuggestionMention( calculateMentionSuggestion(selection.end); }, [selection, calculateMentionSuggestion]); + useEffect(() => { + debouncedSearchInServer(); + }, [suggestionValues.suggestedMentions.length, suggestionValues.prefixType, policyID, value, debouncedSearchInServer]); + const updateShouldShowSuggestionMenuToFalse = useCallback(() => { setSuggestionValues((prevState) => { if (prevState.shouldShowSuggestionMenu) { @@ -390,8 +399,4 @@ function SuggestionMention( SuggestionMention.displayName = 'SuggestionMention'; -export default withOnyx, RoomMentionOnyxProps>({ - reports: { - key: ONYXKEYS.COLLECTION.REPORT, - }, -})(forwardRef(SuggestionMention)); +export default forwardRef(SuggestionMention);