From b670e718d424f5d7cf19e5ad6d94b5a436aa7878 Mon Sep 17 00:00:00 2001 From: Sagar naik Date: Mon, 2 Dec 2024 20:22:02 +0530 Subject: [PATCH] fix: global search fixes (#1832) --- .../GlobalSearch/GlobalSearchBar.res | 117 +++-- .../GlobalSearch/GlobalSearchBarHelper.res | 474 ++++++++++++------ .../GlobalSearch/GlobalSearchBarUtils.res | 80 ++- .../GlobalSearchResults/SearchResultsPage.res | 3 +- 4 files changed, 473 insertions(+), 201 deletions(-) diff --git a/src/screens/Analytics/GlobalSearch/GlobalSearchBar.res b/src/screens/Analytics/GlobalSearch/GlobalSearchBar.res index 6be146e6d..35c4984fe 100644 --- a/src/screens/Analytics/GlobalSearch/GlobalSearchBar.res +++ b/src/screens/Analytics/GlobalSearch/GlobalSearchBar.res @@ -16,10 +16,11 @@ let make = () => { let (localSearchText, setLocalSearchText) = React.useState(_ => "") let (selectedOption, setSelectedOption) = React.useState(_ => ""->getDefaultOption) let (allOptions, setAllOptions) = React.useState(_ => []) + let (selectedFilter, setSelectedFilter) = React.useState(_ => None) + let (allFilters, setAllFilters) = React.useState(_ => []) let (categorieSuggestionResponse, setCategorieSuggestionResponse) = React.useState(_ => Dict.make()->JSON.Encode.object ) - let {userHasAccess} = GroupACLHooks.useUserGroupACLHook() let (searchResults, setSearchResults) = React.useState(_ => []) let merchentDetails = HSwitchUtils.useMerchantDetailsValue() let isReconEnabled = merchentDetails.recon_status === Active @@ -32,7 +33,7 @@ let make = () => { let redirectOnSelect = element => { mixpanelEvent(~eventName="global_search_redirect") - let redirectLink = element.redirect_link->JSON.Decode.string->Option.getOr("/search") + let redirectLink = element.redirect_link->JSON.Decode.string->Option.getOr(defaultRoute) if redirectLink->isNonEmptyString { setShowModal(_ => false) GlobalVars.appendDashboardPath(~url=redirectLink)->RescriptReactRouter.push @@ -63,13 +64,13 @@ let make = () => { let getSearchResults = async results => { try { - let url = getURL(~entityName=GLOBAL_SEARCH, ~methodType=Post) + let local_results = [] + let url = getURL(~entityName=GLOBAL_SEARCH, ~methodType=Post) let body = searchText->generateQuery let response = await fetchDetails(url, body->JSON.Encode.object, Post) - let local_results = [] results->Array.forEach((item: resultType) => { switch item.section { | Local => local_results->Array.pushMany(item.results) @@ -87,15 +88,11 @@ let make = () => { let values = response->getRemoteResults results->Array.pushMany(values) + let defaultItem = searchText->getDefaultResult - if results->Array.length > 0 { - let defaultItem = searchText->getDefaultResult - let arr = [defaultItem]->Array.concat(results) + let finalResults = results->Array.length > 0 ? [defaultItem]->Array.concat(results) : [] - setSearchResults(_ => arr) - } else { - setSearchResults(_ => []) - } + setSearchResults(_ => finalResults) setState(_ => Loaded) } catch { | _ => setState(_ => Loaded) @@ -112,7 +109,7 @@ let make = () => { React.useEffect(_ => { let results = [] - if searchText->String.length > 0 && activeFilter->LogicUtils.isEmptyString { + if searchText->isNonEmptyString && searchText->getSearchValidation { setState(_ => Loading) let localResults: resultType = searchText->getLocalMatchedResults(hswitchTabs) @@ -123,14 +120,10 @@ let make = () => { if isShowRemoteResults { getSearchResults(results)->ignore } else { - if results->Array.length > 0 { - let defaultItem = searchText->getDefaultResult - let arr = [defaultItem]->Array.concat(results) + let defaultItem = searchText->getDefaultResult + let finalResults = results->Array.length > 0 ? [defaultItem]->Array.concat(results) : [] - setSearchResults(_ => arr) - } else { - setSearchResults(_ => []) - } + setSearchResults(_ => finalResults) setState(_ => Loaded) } } else { @@ -141,31 +134,40 @@ let make = () => { None }, [searchText]) + let setFilterText = value => { + setActiveFilter(_ => value) + } + React.useEffect(_ => { setSearchText(_ => "") setLocalSearchText(_ => "") - setActiveFilter(_ => "") + setFilterText("") + setSelectedFilter(_ => None) None }, [showModal]) + let onKeyPress = event => { + open ReactEvent.Keyboard + let metaKey = event->metaKey + let keyPressed = event->key + let ctrlKey = event->ctrlKey + let cmdKey = Window.Navigator.platform->String.includes("Mac") + + if ( + (cmdKey && metaKey && keyPressed == global_search_activate_key) || + (ctrlKey && keyPressed == global_search_activate_key) + ) { + setShowModal(_ => true) + } + + event->preventDefault + } + React.useEffect(() => { if userHasAccess(~groupAccess=AnalyticsView) === Access { getCategoryOptions()->ignore } - let onKeyPress = event => { - let metaKey = event->ReactEvent.Keyboard.metaKey - let keyPressed = event->ReactEvent.Keyboard.key - let ctrlKey = event->ReactEvent.Keyboard.ctrlKey - - if Window.Navigator.platform->String.includes("Mac") && metaKey && keyPressed == "k" { - event->ReactEvent.Keyboard.preventDefault - setShowModal(_ => true) - } else if ctrlKey && keyPressed == "k" { - event->ReactEvent.Keyboard.preventDefault - setShowModal(_ => true) - } - } Window.addEventListener("keydown", onKeyPress) Some(() => Window.removeEventListener("keydown", onKeyPress)) }, []) @@ -178,15 +180,38 @@ let make = () => { setSearchText(_ => value) }, ~wait=500) + let onFilterClicked = category => { + let newFilter = category.categoryType->getcategoryFromVariant + let lastString = searchText->getEndChar + if activeFilter->isNonEmptyString && lastString !== filterSeparator { + let end = searchText->String.length - activeFilter->String.length + let newText = searchText->String.substring(~start=0, ~end) + setLocalSearchText(_ => `${newText} ${newFilter}:`) + setFilterText(newFilter) + } else if lastString !== filterSeparator { + setLocalSearchText(_ => `${searchText} ${newFilter}:`) + setFilterText(newFilter) + } + } + + let onSuggestionClicked = option => { + let value = activeFilter->String.split(filterSeparator)->getValueFromArray(1, "") + let key = if value->isNonEmptyString { + let end = searchText->String.length - (value->String.length + 1) + searchText->String.substring(~start=0, ~end) + } else { + searchText + } + let saparater = searchText->getEndChar == filterSeparator ? "" : filterSeparator + setLocalSearchText(_ => `${key}${saparater}${option}`) + setFilterText("") + } + React.useEffect(() => { setGlobalSearchText(localSearchText) None }, [localSearchText]) - let setFilterText = ReactDebounce.useDebounced(value => { - setActiveFilter(_ => value) - }, ~wait=500) - let leftIcon = switch state { | Loading =>
@@ -200,6 +225,8 @@ let make = () => {
} + let viewType = getViewType(~state, ~searchResults, ~searchText) +
@@ -214,9 +241,16 @@ let make = () => { allOptions selectedOption setSelectedOption + allFilters + selectedFilter + setSelectedFilter + viewType redirectOnSelect + activeFilter + onFilterClicked + onSuggestionClicked /> - {switch getViewType(~state, ~searchResults) { + {switch viewType { | Load =>
@@ -229,9 +263,12 @@ let make = () => { | EmptyResult => }} diff --git a/src/screens/Analytics/GlobalSearch/GlobalSearchBarHelper.res b/src/screens/Analytics/GlobalSearch/GlobalSearchBarHelper.res index e907662c0..787645459 100644 --- a/src/screens/Analytics/GlobalSearch/GlobalSearchBarHelper.res +++ b/src/screens/Analytics/GlobalSearch/GlobalSearchBarHelper.res @@ -1,24 +1,22 @@ open LogicUtils open GlobalSearchTypes module RenderedComponent = { + open String @react.component let make = (~ele, ~searchText) => { + let defaultStyle = "font-medium text-fs-14 text-lightgray_background opacity-50" + listOfMatchedText(ele, searchText) - ->Array.mapWithIndex((item, i) => { - if ( - String.toLowerCase(item) == String.toLowerCase(searchText) && String.length(searchText) > 0 - ) { - Int.toString} - className="border-searched_text_border bg-yellow-searched_text font-medium text-fs-14 text-lightgray_background opacity-50"> - {item->React.string} + ->Array.mapWithIndex((item, index) => { + let key = index->Int.toString + let element = item->React.string + + if item->toLowerCase == searchText->toLowerCase && searchText->isNonEmptyString { + + element } else { - Int.toString} - className="font-medium text-fs-14 text-lightgray_background opacity-50"> - {item->React.string} - + element } }) ->React.array @@ -65,7 +63,8 @@ module EmptyResult = { module OptionWrapper = { @react.component let make = (~index, ~value, ~children, ~selectedOption, ~redirectOnSelect) => { - let activeClass = value == selectedOption ? "bg-gray-100 rounded-lg p-2 group items-center" : "" + let activeClass = value == selectedOption ? "bg-gray-100 rounded-lg border" : "" +
value->redirectOnSelect} className={`flex ${activeClass} flex-row truncate hover:bg-gray-100 cursor-pointer hover:rounded-lg p-2 group items-center`} @@ -79,6 +78,8 @@ module ModalWrapper = { open FramerMotion.Motion @react.component let make = (~showModal, ~setShowModal, ~children) => { + let borderRadius = ["15px", "15px", "15px", "15px"] + {children}
@@ -106,9 +107,33 @@ module ShowMoreLink = { ~textStyleClass="", ~searchText, ) => { - 10}> + let totalCount = section.total_results + + let generateLink = (path, domain) => { + `${path}?query=${searchText}&domain=${domain}` + } + + let onClick = _ => { + let link = switch section.section { + | PaymentAttempts => generateLink("payment-attempts", "payment_attempts") + | SessionizerPaymentAttempts => + generateLink("payment-attempts", "sessionizer_payment_attempts") + | PaymentIntents => generateLink("payment-intents", "payment_intents") + | SessionizerPaymentIntents => generateLink("payment-intents", "sessionizer_payment_intents") + | Refunds => generateLink("refunds-global", "refunds") + | SessionizerPaymentRefunds => generateLink("refunds-global", "sessionizer_refunds") + | Disputes => generateLink("dispute-global", "disputes") + | SessionizerPaymentDisputes => generateLink("dispute-global", "sessionizer_disputes") + | Local + | Others + | Default => "" + } + GlobalVars.appendDashboardPath(~url=link)->RescriptReactRouter.push + cleanUpFunction() + } + + 10}> { - let totalCount = section.total_results let suffix = totalCount > 1 ? "s" : "" let linkText = `View ${totalCount->Int.toString} result${suffix}` @@ -122,27 +147,7 @@ module ShowMoreLink = { | Refunds | Disputes =>
{ - let link = switch section.section { - | PaymentAttempts => `payment-attempts?query=${searchText}&domain=payment_attempts` - | SessionizerPaymentAttempts => - `payment-attempts?query=${searchText}&domain=sessionizer_payment_attempts` - | PaymentIntents => `payment-intents?query=${searchText}&domain=payment_intents` - | SessionizerPaymentIntents => - `payment-intents?query=${searchText}&domain=sessionizer_payment_intents` - | Refunds => `refunds-global?query=${searchText}&domain=refunds` - | SessionizerPaymentRefunds => - `refunds-global?query=${searchText}&domain=sessionizer_refunds` - | Disputes => `dispute-global?query=${searchText}&domain=disputes` - | SessionizerPaymentDisputes => - `dispute-global?query=${searchText}&domain=sessionizer_disputes` - | Local - | Others - | Default => "" - } - GlobalVars.appendDashboardPath(~url=link)->RescriptReactRouter.push - cleanUpFunction() - }} + onClick className={`font-medium cursor-pointer underline underline-offset-2 opacity-50 ${textStyleClass}`}> {linkText->React.string}
@@ -212,30 +217,46 @@ module SearchResultsComponent = { } } -let sidebarScrollbarCss = ` - @supports (-webkit-appearance: none){ - .sidebar-scrollbar { - scrollbar-width: auto; - scrollbar-color: #8a8c8f; - } - - .sidebar-scrollbar::-webkit-scrollbar { - display: block; - overflow: scroll; - height: 4px; - width: 5px; - } - - .sidebar-scrollbar::-webkit-scrollbar-thumb { - background-color: #8a8c8f; - border-radius: 3px; - } - - .sidebar-scrollbar::-webkit-scrollbar-track { - display: none; - } +module FilterOption = { + @react.component + let make = (~onClick, ~value, ~placeholder=None, ~filter, ~selectedFilter=None) => { + let activeBg = "bg-gray-200" + let wrapperBg = "bg-gray-400/40" + let rounded = "rounded-lg" + + let (activeWrapperClass, activeClass) = switch selectedFilter { + | Some(val) => + filter == val + ? (`${activeBg} ${rounded} border`, `${wrapperBg} border`) + : ( + `hover:${activeBg} hover:${rounded} hover:border`, + `hover:${wrapperBg} hover:border ${activeBg}`, + ) + | None => ( + `hover:${activeBg} hover:${rounded} hover:border`, + `hover:${wrapperBg} hover:border ${activeBg}`, + ) + } + +
+
+ {value->React.string} +
+ Option.isSome}> +
{placeholder->Option.getOr("")->React.string}
+
+
+ } +} + +module NoResults = { + @react.component + let make = () => { +
{"No Results"->React.string}
+ } } - ` module FilterResultsComponent = { open GlobalSearchBarUtils @@ -244,20 +265,22 @@ module FilterResultsComponent = { let make = ( ~categorySuggestions: array, ~activeFilter, - ~setActiveFilter, ~searchText, - ~setLocalSearchText, + ~setAllFilters, + ~selectedFilter, + ~setSelectedFilter, + ~onFilterClicked, + ~onSuggestionClicked, ) => { - let filterKey = activeFilter->String.split(":")->getValueFromArray(0, "") + let filterKey = activeFilter->String.split(filterSeparator)->getValueFromArray(0, "") let filters = categorySuggestions->Array.filter(category => { - if !(activeFilter->isEmptyString) { - if searchText->String.charAt(searchText->String.length - 1) == ":" { - `${category.categoryType->getcategoryFromVariant}:` == `${filterKey}:` + if activeFilter->isNonEmptyString { + let categoryType = category.categoryType->getcategoryFromVariant + if searchText->getEndChar == filterSeparator { + `${categoryType}:` == `${filterKey}:` } else { - category.categoryType - ->getcategoryFromVariant - ->String.includes(filterKey) + categoryType->String.includes(filterKey) } } else { true @@ -272,6 +295,49 @@ module FilterResultsComponent = { } } + let updateAllFilters = () => { + if filters->Array.length == 1 { + switch filters->Array.get(0) { + | Some(filter) => + if filter.options->Array.length > 0 && filters->checkFilterKey { + let filterValue = activeFilter->String.split(filterSeparator)->getValueFromArray(1, "") + + let options = if filterValue->isNonEmptyString { + filter.options->Array.filter(option => option->String.includes(filterValue)) + } else { + filter.options + } + + let newFilters = options->Array.map(option => { + let value = { + categoryType: filter.categoryType, + options: [option], + placeholder: filter.placeholder, + } + + value + }) + setAllFilters(_ => newFilters) + } else { + setAllFilters(_ => filters) + } + | _ => () + } + } else { + setAllFilters(_ => filters) + } + } + + React.useEffect(() => { + updateAllFilters() + None + }, [activeFilter]) + + React.useEffect(() => { + setSelectedFilter(_ => None) + None + }, [filters->Array.length]) + Array.length > 0}>
{React.string(sidebarScrollbarCss)} {switch filters->Array.get(0) { | Some(value) => - value.options - ->Array.map(option => { -
{ - let saparater = - searchText->String.charAt(searchText->String.length - 1) == ":" ? "" : ":" - setLocalSearchText(_ => `${searchText}${saparater}${option}`) - setActiveFilter(_ => "") - }}> -
- - {`${value.categoryType - ->getcategoryFromVariant - ->String.toLocaleLowerCase} : ${option}`->React.string} - -
-
- }) - ->React.array - | _ => React.null + let filterValue = + activeFilter->String.split(filterSeparator)->getValueFromArray(1, "") + + let options = if filterValue->isNonEmptyString { + value.options->Array.filter(option => option->String.includes(filterValue)) + } else { + value.options + } + + if options->Array.length > 0 { + options + ->Array.map(option => { + let filter = { + categoryType: value.categoryType, + options: [option], + placeholder: value.placeholder, + } + + let itemValue = `${value.categoryType + ->getcategoryFromVariant + ->String.toLocaleLowerCase} : ${option}` + + option->onSuggestionClicked} + value=itemValue + filter + selectedFilter + /> + }) + ->React.array + } else { + + } + | _ => }}
Array.length === 1 && filters->checkFilterKey)}> {filters ->Array.map(category => { -
{ - let newFilter = category.categoryType->getcategoryFromVariant - let lastString = searchText->String.charAt(searchText->String.length - 1) - if activeFilter->isNonEmptyString && lastString !== ":" { - let end = searchText->String.length - activeFilter->String.length - let newText = searchText->String.substring(~start=0, ~end) - setLocalSearchText(_ => `${newText} ${newFilter}:`) - setActiveFilter(_ => newFilter) - } else if lastString !== ":" { - setLocalSearchText(_ => `${searchText} ${newFilter}:`) - setActiveFilter(_ => newFilter) - } - }}> -
- - {`${category.categoryType - ->getcategoryFromVariant - ->String.toLocaleLowerCase} : `->React.string} - -
-
{category.placeholder->React.string}
-
+ let itemValue = `${category.categoryType + ->getcategoryFromVariant + ->String.toLocaleLowerCase} : ` + category->onFilterClicked} + value=itemValue + placeholder={Some(category.placeholder)} + filter={category} + selectedFilter + /> }) ->React.array}
@@ -348,7 +413,9 @@ module FilterResultsComponent = { } module ModalSearchBox = { + open GlobalSearchBarUtils open FramerMotion.Motion + open ReactEvent.Keyboard @react.component let make = ( ~leftIcon, @@ -360,9 +427,23 @@ module ModalSearchBox = { ~selectedOption, ~setSelectedOption, ~redirectOnSelect, + ~allFilters, + ~selectedFilter, + ~setSelectedFilter, + ~viewType, + ~activeFilter, + ~onFilterClicked, + ~onSuggestionClicked, ) => { + let inputRef = React.useRef(Nullable.null) let (errorMessage, setErrorMessage) = React.useState(_ => "") + let tabKey = 9 + let arrowDown = 40 + let arrowUp = 38 + let enterKey = 13 + let spaceKey = 32 + let input: ReactFinalForm.fieldRenderPropsInput = { { name: "global_search", @@ -377,44 +458,142 @@ module ModalSearchBox = { } } + let getNextIndex = (selectedIndex, options) => { + let count = options->Array.length + selectedIndex == count - 1 ? 0 : Int.mod(selectedIndex + 1, count) + } + let getPrevIndex = (selectedIndex, options) => { + let count = options->Array.length + selectedIndex === 0 ? count - 1 : Int.mod(selectedIndex - 1, count) + } + + let tabKeyPressHandler = e => { + switch inputRef.current->Js.Nullable.toOption { + | Some(elem) => elem->MultipleFileUpload.focus + | None => () + } + + let keyPressed = e->keyCode + + switch viewType { + | EmptyResult | Load => () + | Results => { + let index = allOptions->Array.findIndex(item => { + item == selectedOption + }) + + if keyPressed == tabKey { + let newIndex = getNextIndex(index, allOptions) + switch allOptions->Array.get(newIndex) { + | Some(val) => setSelectedOption(_ => val) + | _ => () + } + } + } + | FiltersSugsestions => { + let index = allFilters->Array.findIndex(item => { + switch selectedFilter { + | Some(val) => item == val + | _ => false + } + }) + + if keyPressed == tabKey { + let newIndex = getNextIndex(index, allFilters) + switch allFilters->Array.get(newIndex) { + | Some(val) => setSelectedFilter(_ => val->Some) + | _ => () + } + } + } + } + } + + React.useEffect(() => { + Window.addEventListener("keydown", tabKeyPressHandler) + Some(() => Window.removeEventListener("keydown", tabKeyPressHandler)) + }, (selectedFilter, selectedOption)) + let handleKeyDown = e => { - open ReactEvent.Keyboard + let keyPressed = e->keyCode - let index = allOptions->Array.findIndex(item => { - item == selectedOption - }) + switch viewType { + | Results => { + let index = allOptions->Array.findIndex(item => { + item == selectedOption + }) - if e->keyCode == 40 { - let newIndex = - index == allOptions->Array.length - 1 ? 0 : Int.mod(index + 1, allOptions->Array.length) - switch allOptions->Array.get(newIndex) { - | Some(val) => setSelectedOption(_ => val) - | _ => () + if keyPressed == arrowDown { + let newIndex = getNextIndex(index, allOptions) + switch allOptions->Array.get(newIndex) { + | Some(val) => setSelectedOption(_ => val) + | _ => () + } + } else if keyPressed == arrowUp { + let newIndex = getPrevIndex(index, allOptions) + switch allOptions->Array.get(newIndex) { + | Some(val) => setSelectedOption(_ => val) + | _ => () + } + } else if keyPressed == enterKey { + selectedOption->redirectOnSelect + } } - } else if e->keyCode == 38 { - let newIndex = - index === 0 ? allOptions->Array.length - 1 : Int.mod(index - 1, allOptions->Array.length) - switch allOptions->Array.get(newIndex) { - | Some(val) => setSelectedOption(_ => val) - | _ => () + + | FiltersSugsestions => { + let index = allFilters->Array.findIndex(item => { + switch selectedFilter { + | Some(val) => item == val + | _ => false + } + }) + + if keyPressed == arrowDown { + let newIndex = getNextIndex(index, allFilters) + switch allFilters->Array.get(newIndex) { + | Some(val) => setSelectedFilter(_ => val->Some) + | _ => () + } + } else if keyPressed == arrowUp { + let newIndex = getPrevIndex(index, allFilters) + switch allFilters->Array.get(newIndex) { + | Some(val) => setSelectedFilter(_ => val->Some) + | _ => () + } + } else if keyPressed == enterKey { + switch selectedFilter { + | Some(filter) => + if activeFilter->String.includes(filterSeparator) { + switch filter.options->Array.get(0) { + | Some(val) => val->onSuggestionClicked + | _ => () + } + } else { + filter->onFilterClicked + } + | _ => () + } + } } - } else if e->keyCode == 13 && localSearchText->isNonEmptyString { - selectedOption->redirectOnSelect + + | EmptyResult | Load => () } - if e->keyCode === 32 { + if keyPressed == spaceKey { setFilterText("") } else { let values = localSearchText->String.split(" ") let filter = values->getValueFromArray(values->Array.length - 1, "") - setFilterText(filter) + if activeFilter !== filter { + setFilterText(filter) + } } } - let validateForm = _values => { + let validateForm = _ => { let errors = Dict.make() - let lastChar = localSearchText->String.charCodeAt(localSearchText->String.length - 1) - if localSearchText->GlobalSearchBarUtils.validateQuery && lastChar == 32.0 { + let lastChar = localSearchText->getEndChar + if lastChar == " " { setErrorMessage(_ => "Multiple free-text terms found") } else if !(localSearchText->GlobalSearchBarUtils.validateQuery) { setErrorMessage(_ => "") @@ -437,16 +616,19 @@ module ModalSearchBox = {
{leftIcon}
- ReactDOM.Ref.domRef} + autoComplete="off" autoFocus=true placeholder="Search" - autoComplete="off" + className="w-full pr-2 pl-2 text-jp-gray-900 text-opacity-75 focus:text-opacity-100 placeholder-jp-gray-900 focus:outline-none rounded h-10 text-lg font-normal placeholder-opacity-50 " + name={input.name} + label="No" + value=localSearchText + type_="text" + checked={false} + onChange=input.onChange onKeyUp=handleKeyDown - customStyle="bg-white border-none" - onActiveStyle="bg-white" - onHoverCss="bg-white" - inputStyle="!text-lg" />
{ + string->String.charAt(string->String.length - 1) +} + let matchInSearchOption = (searchOptions, searchText, name, link, ~sectionName) => { searchOptions ->Option.getOr([]) @@ -433,7 +441,7 @@ let generateFilter = (queryArray: array) => { queryArray->Array.forEach(query => { let keyValuePair = query - ->String.split(":") + ->String.split(filterSeparator) ->Array.filter(query => { query->String.trim->isNonEmptyString }) @@ -442,8 +450,14 @@ let generateFilter = (queryArray: array) => { let value = keyValuePair->getValueFromArray(1, "") switch filter->Dict.get(key) { - | Some(prevArr) => filter->Dict.set(key, prevArr->Array.concat([value])) - | _ => filter->Dict.set(key, [value]) + | Some(prevArr) => + if !(prevArr->Array.includes(value)) && value->isNonEmptyString { + filter->Dict.set(key, prevArr->Array.concat([value])) + } + | _ => + if value->isNonEmptyString { + filter->Dict.set(key, [value]) + } } }) @@ -467,7 +481,7 @@ let generateQuery = searchQuery => { query->String.trim->isNonEmptyString }) ->Array.forEach(query => { - if RegExp.test(%re("/^[^:\s]+:[^:\s]+$/"), query) { + if RegExp.test(%re("/^[^:\s]+:[^:\s]*$/"), query) { filters->Array.push(query) } else { queryText := query @@ -475,13 +489,15 @@ let generateQuery = searchQuery => { }) let body = { - let query = if filters->Array.length > 0 { - [("filters", filters->generateFilter->JSON.Encode.object)] + let filterObj = filters->generateFilter + let query = if filters->Array.length > 0 && filterObj->Dict.keysToArray->Array.length > 0 { + [("filters", filterObj->JSON.Encode.object)] } else { [] } - let query = query->Array.concat([("query", queryText.contents->JSON.Encode.string)]) + let query = + query->Array.concat([("query", queryText.contents->String.trim->JSON.Encode.string)]) query->Dict.fromArray } @@ -506,15 +522,53 @@ let validateQuery = searchQuery => { freeTextCount.contents > 1 } -let getViewType = (~state, ~searchResults) => { +let getViewType = (~state, ~searchResults, ~searchText) => { switch state { | Loading => Load - | Loaded => - if searchResults->Array.length > 0 { - Results - } else { - EmptyResult + | Loaded => { + let endChar = searchText->String.charAt(searchText->String.length - 1) + let isFilter = endChar == filterSeparator || endChar == " " + + if isFilter { + FiltersSugsestions + } else if searchResults->Array.length > 0 { + Results + } else { + EmptyResult + } } | Idle => FiltersSugsestions } } + +let getSearchValidation = query => { + let paylod = query->generateQuery + let query = paylod->getString("query", "")->String.trim + + !(paylod->getObj("filters", Dict.make())->isEmptyDict && query->isEmptyString) +} + +let sidebarScrollbarCss = ` + @supports (-webkit-appearance: none){ + .sidebar-scrollbar { + scrollbar-width: auto; + scrollbar-color: #8a8c8f; + } + + .sidebar-scrollbar::-webkit-scrollbar { + display: block; + overflow: scroll; + height: 4px; + width: 5px; + } + + .sidebar-scrollbar::-webkit-scrollbar-thumb { + background-color: #8a8c8f; + border-radius: 3px; + } + + .sidebar-scrollbar::-webkit-scrollbar-track { + display: none; + } +} + ` diff --git a/src/screens/Analytics/GlobalSearchResults/SearchResultsPage.res b/src/screens/Analytics/GlobalSearchResults/SearchResultsPage.res index 7b57545e0..c5339d611 100644 --- a/src/screens/Analytics/GlobalSearchResults/SearchResultsPage.res +++ b/src/screens/Analytics/GlobalSearchResults/SearchResultsPage.res @@ -95,12 +95,11 @@ let make = () => { let {globalSearch} = HyperswitchAtom.featureFlagAtom->Recoil.useRecoilValueFromAtom let {userHasAccess} = GroupACLHooks.useUserGroupACLHook() let isShowRemoteResults = globalSearch && userHasAccess(~groupAccess=OperationsView) === Access - let fallBackQuery = UrlUtils.useGetFilterDictFromUrl("")->LogicUtils.getString("query", "") let getSearchResults = async results => { try { let url = getURL(~entityName=GLOBAL_SEARCH, ~methodType=Post) - let query = searchText->isNonEmptyString ? searchText : fallBackQuery + let body = query->generateQuery let response = await fetchDetails(url, body->JSON.Encode.object, Post)