diff --git a/plugin-hrm-form/src/components/search/index.tsx b/plugin-hrm-form/src/components/search/index.tsx index 6c5a04cd09..ff5a1de663 100644 --- a/plugin-hrm-form/src/components/search/index.tsx +++ b/plugin-hrm-form/src/components/search/index.tsx @@ -16,8 +16,8 @@ /* eslint-disable react/prop-types */ /* eslint-disable react/no-multi-comp */ -import React, { useState, useEffect } from 'react'; -import { connect } from 'react-redux'; +import React, { useState, useEffect, useRef } from 'react'; +import { connect, useSelector } from 'react-redux'; import { bindActionCreators } from 'redux'; import Dialog from '@material-ui/core/Dialog'; import DialogContent from '@material-ui/core/DialogContent'; @@ -28,8 +28,16 @@ import ContactDetails from '../contact/ContactDetails'; import Case from '../case'; import ProfileRouter, { isProfileRoute } from '../profile/ProfileRouter'; import { SearchParams } from '../../states/search/types'; +import selectSearchStateForTask from '../../states/search/selectSearchStateForTask'; import { CustomITask } from '../../types/types'; -import { newCreateSearchForm, handleSearchFormChange, searchCases, searchContacts } from '../../states/search/actions'; +import { + newCreateSearchForm, + handleSearchFormChange, + searchCases, + searchContacts, + searchV2Contacts, + searchContactsByIds, +} from '../../states/search/actions'; import { RootState } from '../../states'; import { namespace } from '../../states/storeNamespaces'; import { getCurrentTopmostRouteForTask } from '../../states/routing/getRoute'; @@ -83,6 +91,8 @@ const mapDispatchToProps = (dispatch, ownProps) => { ), ), searchContacts: (context: string) => searchContacts(dispatch)(taskId, context), + searchContactsByIds: (context: string) => searchContactsByIds(dispatch)(taskId, context), + searchV2Contacts: (context: string) => searchV2Contacts(dispatch)(taskId, context), searchCases: (context: string) => searchCases(dispatch)(taskId, context), closeModal: () => dispatch(newCloseModalAction(taskId)), handleNewCreateSearch: (context: string) => dispatch(newCreateSearchForm(taskId, context)), @@ -95,6 +105,8 @@ const Search: React.FC = ({ task, currentIsCaller, searchContacts, + searchContactsByIds, + searchV2Contacts, searchCases, handleSearchFormChange, searchContactsResults, @@ -109,6 +121,15 @@ const Search: React.FC = ({ const [mockedMessage, setMockedMessage] = useState(''); const [searchParams, setSearchParams] = useState({}); + const [contactsOffset, setContactsOffset] = useState(0); + const prevContactsOffset = useRef(-1); + + const searchState = useSelector((state: RootState) => selectSearchStateForTask(state, task.taskSid, searchContext)); + const { + isRequesting, + searchContactsResult: { searchMatchIds: contactSearchMatchIds, currentPageIds: contactsCurrentPageIds }, + } = searchState; + useEffect(() => { if (!form) { handleNewCreateSearch(searchContext); @@ -117,8 +138,18 @@ const Search: React.FC = ({ const closeDialog = () => setMockedMessage(''); - const handleSearchContacts = (newSearchParams: SearchParams, newOffset) => - searchContacts(searchContext)({ ...form, ...newSearchParams }, CONTACTS_PER_PAGE, newOffset); + const handleSearchContacts = (newSearchParams: SearchParams, newOffset: number) => { + // TODO: handle legacy + // if (enable_search_v2) { + searchV2Contacts(searchContext)({ + searchParams: { ...form, ...newSearchParams }, + limit: CONTACTS_PER_PAGE, + offset: newOffset, + }); + // } else { + // searchContacts(searchContext)({ ...form, ...newSearchParams }, CONTACTS_PER_PAGE, newOffset); + // } + }; const handleSearchCases = (newSearchParams, newOffset) => searchCases(searchContext)({ ...form, ...newSearchParams }, CASES_PER_PAGE, newOffset); @@ -137,7 +168,42 @@ const Search: React.FC = ({ } }; - const setOffsetAndHandleSearchContacts = newOffset => handleSearchContacts(searchParams, newOffset); + const setOffsetAndHandleSearchContacts = setContactsOffset; + + // On page change, trigger a new search + useEffect(() => { + console.log('>>>>>>>>>>> triggered effect'); + const handlePageChange = (newSearchParams: SearchParams, newOffset: number) => { + // if (enable_search_v2) { + + const targetIds = contactSearchMatchIds?.slice(newOffset, newOffset + CONTACTS_PER_PAGE); + console.log('>>>>>>>>>>> contactsOffset', contactsOffset); + console.log('>>>>>>>>>>> prevContactsOffset', prevContactsOffset.current); + console.log('>>>>>>>>>>> isRequesting', isRequesting); + console.log('>>>>>>>>>>> targetIds', targetIds); + // const targetsChanged: boolean = + // Boolean(targetIds) && targetIds.every((id, index) => contactsCurrentPageIds[index] !== id); + // if (targetIds && targetsChanged && !isRequesting) { + if (targetIds && prevContactsOffset.current !== newOffset) { + console.log('>>>>>>>>>>> inner if condition of effect reached'); + prevContactsOffset.current = contactsOffset; + searchContactsByIds(searchContext)(targetIds, CONTACTS_PER_PAGE, newOffset); + } + // } else { + // searchContacts(searchContext)({ ...form, ...newSearchParams }, CONTACTS_PER_PAGE, newOffset); + // } + }; + + handlePageChange(searchParams, contactsOffset); + }, [ + contactSearchMatchIds, + contactsCurrentPageIds, + contactsOffset, + isRequesting, + searchContactsByIds, + searchContext, + searchParams, + ]); const setOffsetAndHandleSearchCases = newOffset => handleSearchCases(searchParams, newOffset); diff --git a/plugin-hrm-form/src/services/ContactService.ts b/plugin-hrm-form/src/services/ContactService.ts index 286decba0b..526121876e 100644 --- a/plugin-hrm-form/src/services/ContactService.ts +++ b/plugin-hrm-form/src/services/ContactService.ts @@ -75,6 +75,49 @@ export async function searchContacts( }; } +export async function searchContactsByIds( + ids: string[], + limit, + offset, +): Promise<{ + count: number; + contacts: Contact[]; +}> { + const queryParams = getQueryParams({ limit, offset }); + + console.log('>>>>>>>>>>>> searchContactsByIds with ids', ids); + + const options = { + method: 'POST', + body: JSON.stringify({ ids }), + }; + const response = await fetchHrmApi(`/contacts/searchByIds${queryParams}`, options); + console.log('>>>>>>>>>>>> response.contacts', response.contacts); + return { + ...response, + contacts: response.contacts.map(convertApiContactToFlexContact), + }; +} + +export async function searchContactsV2({ + searchParams, +}: { + searchParams: SearchParams; +}): Promise<{ + count: number; + contacts: Contact[]; +}> { + const options = { + method: 'POST', + body: JSON.stringify({}), + }; + const response = await fetchHrmApi(`/contacts/searchV2`, options); + return { + ...response, + contacts: response.contacts.map(convertApiContactToFlexContact), + }; +} + type HandleTwilioTaskResponse = { channelSid?: string; serviceSid?: string; diff --git a/plugin-hrm-form/src/states/search/actions.ts b/plugin-hrm-form/src/states/search/actions.ts index 21c799182c..bcb0c0fbea 100644 --- a/plugin-hrm-form/src/states/search/actions.ts +++ b/plugin-hrm-form/src/states/search/actions.ts @@ -20,11 +20,16 @@ import { endOfDay, formatISO, parseISO, startOfDay } from 'date-fns'; import * as t from './types'; import { SearchParams } from './types'; -import { searchContacts as searchContactsApiCall } from '../../services/ContactService'; +import { + searchContacts as searchContactsApiCall, + searchContactsByIds as searchContactsByIdsApiCall, + searchContactsV2 as searchV2ContactsApiCall, +} from '../../services/ContactService'; import { searchCases as searchCasesApiCall } from '../../services/CaseService'; import { updateDefinitionVersion } from '../configuration/actions'; import { getCasesMissingVersions, getContactsMissingVersions } from '../../utils/definitionVersions'; import { getNumberFromTask } from '../../utils'; +import { Contact } from '../../types/types'; // Action creators export const handleSearchFormChange = (taskId: string, context: string) => ( @@ -48,8 +53,17 @@ export const newCreateSearchForm = (taskId: string, context: string): t.SearchAc } as t.SearchActionType; }; -export const searchContacts = (dispatch: Dispatch) => (taskId: string, context: string) => async ( - searchParams: SearchParams, +const searchContactsActionWithSearchService = ( + searchService: ( + searchParams: T, + limit: any, + offset: any, + ) => Promise<{ + count: number; + contacts: Contact[]; + }>, +) => (dispatch: Dispatch) => (taskId: string, context: string) => async ( + searchParams: T, limit: number, offset: number, dispatchedFromPreviousContacts?: boolean, @@ -57,16 +71,7 @@ export const searchContacts = (dispatch: Dispatch) => (taskId: string, cont try { dispatch({ type: t.SEARCH_CONTACTS_REQUEST, taskId, context }); - const { dateFrom, dateTo, ...rest } = searchParams ?? {}; - const searchParamsToSubmit: SearchParams = rest; - if (dateFrom) { - searchParamsToSubmit.dateFrom = formatISO(startOfDay(parseISO(dateFrom))); - } - if (dateTo) { - searchParamsToSubmit.dateTo = formatISO(endOfDay(parseISO(dateTo))); - } - - const searchResultRaw = await searchContactsApiCall(searchParamsToSubmit, limit, offset); + const searchResultRaw = await searchService(searchParams, limit, offset); const searchResult = { ...searchResultRaw, contacts: searchResultRaw.contacts }; const definitions = await getContactsMissingVersions(searchResultRaw.contacts); @@ -78,6 +83,11 @@ export const searchContacts = (dispatch: Dispatch) => (taskId: string, cont } }; +/** + * Legacy search actions + */ +export const searchContacts = searchContactsActionWithSearchService(searchContactsApiCall); + export const searchCases = (dispatch: Dispatch) => (taskId: string, context: string) => async ( searchParams: any, limit: number, @@ -118,6 +128,44 @@ export const searchCases = (dispatch: Dispatch) => (taskId: string, context } }; +/** + * Search v2 actions + */ +export const searchContactsByIds = searchContactsActionWithSearchService(searchContactsByIdsApiCall); + +export const searchV2Contacts = (dispatch: Dispatch) => (taskId: string, context: string) => async ({ + searchParams, + limit, + offset, + dispatchedFromPreviousContacts, +}: { + searchParams: SearchParams; + limit: number; + offset: number; + dispatchedFromPreviousContacts?: boolean; +}) => { + console.log('>>>>>>>>>>>>> searchV2Contacts callewd') + try { + dispatch({ type: t.SEARCH_CONTACTS_REQUEST, taskId, context }); + + const { dateFrom, dateTo, ...rest } = searchParams ?? {}; + const searchParamsToSubmit: SearchParams = rest; + if (dateFrom) { + searchParamsToSubmit.dateFrom = formatISO(startOfDay(parseISO(dateFrom))); + } + if (dateTo) { + searchParamsToSubmit.dateTo = formatISO(endOfDay(parseISO(dateTo))); + } + + const searchResultRaw = await searchV2ContactsApiCall({ searchParams: searchParamsToSubmit }); + const searchMatchIds = searchResultRaw.contacts.map(c => c.id); + + dispatch({ type: t.SEARCH_V2_CONTACTS_SUCCESS, searchMatchIds, taskId, dispatchedFromPreviousContacts, context }); + } catch (error) { + dispatch({ type: t.SEARCH_CONTACTS_FAILURE, error, taskId, dispatchedFromPreviousContacts, context }); + } +}; + export const viewPreviousContacts = (dispatch: Dispatch) => (task: ITask) => () => { const contactNumber = getNumberFromTask(task); const taskId = task.taskSid; diff --git a/plugin-hrm-form/src/states/search/reducer.ts b/plugin-hrm-form/src/states/search/reducer.ts index d0159f4cc9..8265107164 100644 --- a/plugin-hrm-form/src/states/search/reducer.ts +++ b/plugin-hrm-form/src/states/search/reducer.ts @@ -68,8 +68,8 @@ export const newTaskEntry: SearchStateTaskEntry = { [ContactDetailsSections.EXTERNAL_REPORT]: false, [ContactDetailsSections.RECORDING]: false, }, - searchContactsResult: { count: 0, ids: [] }, - searchCasesResult: { count: 0, ids: [] }, + searchContactsResult: { count: 0, currentPageIds: [] }, + searchCasesResult: { count: 0, currentPageIds: [] }, isRequesting: false, isRequestingCases: false, caseRefreshRequired: false, @@ -223,9 +223,14 @@ export function reduce( } = action; const task = state.tasks[taskId]; const searchContext = state.tasks[taskId][context]; + const { searchContactsResult } = state.tasks[taskId][context]; const newContactsResult = { - ids: contacts.map(c => c.id), - count: searchResult.count, + ...searchContactsResult, + currentPageIds: contacts.map(c => c.id), + // if v2 is enabled, preserve count, else re-compute after every search + count: searchContactsResult.searchMatchIds?.length + ? searchContactsResult.searchMatchIds.length + : searchResult.count, }; const previousContactCounts = dispatchedFromPreviousContacts ? { ...searchContext.previousContactCounts, contacts: searchResult.count } @@ -247,6 +252,33 @@ export function reduce( }, }; } + case t.SEARCH_V2_CONTACTS_SUCCESS: { + const { searchMatchIds, taskId, dispatchedFromPreviousContacts, context } = action; + const task = state.tasks[taskId]; + const searchContext = state.tasks[taskId][context]; + const newContactsResult = { + searchMatchIds, + count: searchMatchIds.length, + currentPageIds: [], + }; + const previousContactCounts = dispatchedFromPreviousContacts + ? { ...searchContext.previousContactCounts, contacts: searchMatchIds.length } + : searchContext.previousContactCounts; + return { + ...state, + tasks: { + ...state.tasks, + [taskId]: { + ...task, + [context]: { + ...searchContext, + searchContactsResult: newContactsResult, + previousContactCounts, + }, + }, + }, + }; + } case t.SEARCH_CONTACTS_FAILURE: { const task = state.tasks[action.taskId]; const context = state.tasks[action.taskId][action.context]; @@ -292,7 +324,7 @@ export function reduce( const task = state.tasks[taskId]; const context = state.tasks[action.taskId][action.context]; const newCasesResult = { - ids: cases.map(c => c.id), + currentPageIds: cases.map(c => c.id), count, }; const previousContactCounts = dispatchedFromPreviousContacts diff --git a/plugin-hrm-form/src/states/search/selectCasesForSearchResults.ts b/plugin-hrm-form/src/states/search/selectCasesForSearchResults.ts index cf5b62d895..3878b9f466 100644 --- a/plugin-hrm-form/src/states/search/selectCasesForSearchResults.ts +++ b/plugin-hrm-form/src/states/search/selectCasesForSearchResults.ts @@ -29,7 +29,7 @@ const selectCasesForSearchResults = (state: RootState, taskSid: string, context: } return { count: resultReferences.count, - cases: resultReferences.ids + cases: resultReferences.currentPageIds .map(id => state[namespace].connectedCase.cases[id]?.connectedCase) .filter(c => Boolean(c)), }; diff --git a/plugin-hrm-form/src/states/search/selectContactsForSearchResults.ts b/plugin-hrm-form/src/states/search/selectContactsForSearchResults.ts index 439e539c1e..34fbf3d8ae 100644 --- a/plugin-hrm-form/src/states/search/selectContactsForSearchResults.ts +++ b/plugin-hrm-form/src/states/search/selectContactsForSearchResults.ts @@ -30,7 +30,7 @@ const selectContactsForSearchResults = (state: RootState, taskSid: string, conte } return { count: resultReferences.count, - contacts: resultReferences.ids + contacts: resultReferences.currentPageIds .map(id => state[namespace].activeContacts.existingContacts[id]?.savedContact) .filter(c => Boolean(c)), }; diff --git a/plugin-hrm-form/src/states/search/types.ts b/plugin-hrm-form/src/states/search/types.ts index 2d7b6d8d9d..3d47116cd0 100644 --- a/plugin-hrm-form/src/states/search/types.ts +++ b/plugin-hrm-form/src/states/search/types.ts @@ -22,6 +22,7 @@ export const CHANGE_SEARCH_PAGE = 'CHANGE_SEARCH_PAGE'; export const VIEW_CONTACT_DETAILS = 'VIEW_CONTACT_DETAILS'; export const SEARCH_CONTACTS_REQUEST = 'SEARCH_CONTACTS_REQUEST'; export const SEARCH_CONTACTS_SUCCESS = 'SEARCH_CONTACTS_SUCCESS'; +export const SEARCH_V2_CONTACTS_SUCCESS = 'SEARCH_V2_CONTACTS_SUCCESS'; export const SEARCH_CONTACTS_FAILURE = 'SEARCH_CONTACTS_FAILURE'; export const SEARCH_CASES_REQUEST = 'SEARCH_CASES_REQUEST'; export const SEARCH_CASES_SUCCESS = 'SEARCH_CASES_SUCCESS'; @@ -74,6 +75,14 @@ export type SearchContactsSuccessAction = { context: string; }; +export type SearchV2ContactsSuccessAction = { + type: typeof SEARCH_V2_CONTACTS_SUCCESS; + searchMatchIds: string[]; + taskId: string; + dispatchedFromPreviousContacts?: boolean; + context: string; +}; + type SearchContactsFailureAction = { type: typeof SEARCH_CONTACTS_FAILURE; error: any; @@ -105,6 +114,7 @@ export type SearchActionType = | SearchFormChangeAction | SearchContactsRequestAction | SearchContactsSuccessAction + | SearchV2ContactsSuccessAction | SearchContactsFailureAction | SearchCasesRequestAction | SearchCasesSuccessAction @@ -114,7 +124,9 @@ export type SearchActionType = export type SearchResultReferences = { count: number; - ids: string[]; + currentPageIds: string[]; + // TODO: this should not be optional, just let it be empty array for legacy search + searchMatchIds?: string[]; }; export type PreviousContactCounts = {