From 2ec7bc4da9a50372200236aba39f98726c14b407 Mon Sep 17 00:00:00 2001
From: Aries <urchinzhou@gmail.com>
Date: Wed, 23 Apr 2025 16:11:03 +0800
Subject: [PATCH] Optimize/search filters (#7761)

* optimize ui

* optimize filters

* update date filter

* optimize

---------

Co-authored-by: zhouwenxuan <aries@Mac.local>
---
 .../search-filters/filter-by-creator.js       |  22 +--
 .../search/search-filters/filter-by-date.js   | 130 ++++++++--------
 .../search/search-filters/filter-by-suffix.js |  64 +++++---
 .../search/search-filters/filter-by-text.js   |   8 +-
 .../search/search-filters/index.css           |  42 ++++-
 .../components/search/search-filters/index.js |   8 +-
 frontend/src/components/search/search.js      | 143 ++++++++++--------
 frontend/src/css/search.css                   |   4 +
 8 files changed, 246 insertions(+), 175 deletions(-)

diff --git a/frontend/src/components/search/search-filters/filter-by-creator.js b/frontend/src/components/search/search-filters/filter-by-creator.js
index bc998f4f65..ec503139a4 100644
--- a/frontend/src/components/search/search-filters/filter-by-creator.js
+++ b/frontend/src/components/search/search-filters/filter-by-creator.js
@@ -11,11 +11,12 @@ import ModalPortal from '../../modal-portal';
 import toaster from '../../toast';
 import { SEARCH_FILTERS_KEY } from '../../../constants';
 
-const FilterByCreator = ({ creatorList, onSelect }) => {
+const FilterByCreator = ({ creatorList, onChange }) => {
   const [isOpen, setIsOpen] = useState(false);
   const [options, setOptions] = useState([]);
   const [selectedOptions, setSelectedOptions] = useState(creatorList || []);
   const [searchValue, setSearchValue] = useState('');
+  const [inputFocus, setInputFocus] = useState(false);
 
   const toggle = useCallback((e) => {
     setIsOpen(!isOpen);
@@ -28,7 +29,7 @@ const FilterByCreator = ({ creatorList, onSelect }) => {
     });
   }, [options, searchValue]);
 
-  const onSelectOption = useCallback((e) => {
+  const onChangeOption = useCallback((e) => {
     e.preventDefault();
     e.stopPropagation();
     const name = Utils.getEventData(e, 'toggle') ?? e.currentTarget.getAttribute('data-toggle');
@@ -40,17 +41,17 @@ const FilterByCreator = ({ creatorList, onSelect }) => {
       updated = updated.filter((option) => option.name !== name);
     }
     setSelectedOptions(updated);
-    onSelect(SEARCH_FILTERS_KEY.CREATOR_LIST, updated);
+    onChange(SEARCH_FILTERS_KEY.CREATOR_LIST, updated);
     if (displayOptions.length === 1) {
       setSearchValue('');
     }
-  }, [selectedOptions, displayOptions, options, onSelect]);
+  }, [selectedOptions, displayOptions, options, onChange]);
 
   const handleCancel = useCallback((e, name) => {
     const updated = selectedOptions.filter((option) => option.name !== name);
     setSelectedOptions(updated);
-    onSelect(SEARCH_FILTERS_KEY.CREATOR_LIST, updated);
-  }, [selectedOptions, onSelect]);
+    onChange(SEARCH_FILTERS_KEY.CREATOR_LIST, updated);
+  }, [selectedOptions, onChange]);
 
   const handleInputChange = useCallback((e) => {
     const v = e.target.value;
@@ -100,7 +101,7 @@ const FilterByCreator = ({ creatorList, onSelect }) => {
         </DropdownToggle>
         <ModalPortal>
           <DropdownMenu className="search-filter-menu filter-by-creator-menu">
-            <div className="input-container">
+            <div className={classNames('input-container', { 'focus': inputFocus })}>
               {selectedOptions.map((option) => (
                 <UserItem
                   key={option.name}
@@ -114,8 +115,11 @@ const FilterByCreator = ({ creatorList, onSelect }) => {
                   type="text"
                   placeholder={selectedOptions.length ? '' : gettext('Search user')}
                   value={searchValue}
+                  autoFocus
                   onChange={handleInputChange}
                   onKeyDown={handleInputKeyDown}
+                  onFocus={() => setInputFocus(true)}
+                  onBlur={() => setInputFocus(false)}
                 />
               </div>
             </div>
@@ -126,7 +130,7 @@ const FilterByCreator = ({ creatorList, onSelect }) => {
                 tabIndex="-1"
                 data-toggle={option.name}
                 onMouseDown={(e) => e.preventDefault()}
-                onClick={onSelectOption}
+                onClick={onChangeOption}
                 toggle={false}
               >
                 {isOpen && <UserItem user={option} />}
@@ -142,7 +146,7 @@ const FilterByCreator = ({ creatorList, onSelect }) => {
 
 FilterByCreator.propTypes = {
   creatorList: PropTypes.array.isRequired,
-  onSelect: PropTypes.func.isRequired,
+  onChange: PropTypes.func.isRequired,
 };
 
 export default FilterByCreator;
diff --git a/frontend/src/components/search/search-filters/filter-by-date.js b/frontend/src/components/search/search-filters/filter-by-date.js
index 0acb0f09de..2b7cf6d046 100644
--- a/frontend/src/components/search/search-filters/filter-by-date.js
+++ b/frontend/src/components/search/search-filters/filter-by-date.js
@@ -1,4 +1,4 @@
-import React, { useCallback, useMemo, useState } from 'react';
+import React, { useCallback, useEffect, useMemo, useState } from 'react';
 import PropTypes from 'prop-types';
 import { Dropdown, DropdownItem, DropdownMenu, DropdownToggle } from 'reactstrap';
 import dayjs from 'dayjs';
@@ -7,14 +7,19 @@ import { Utils } from '../../../utils/utils';
 import Picker from '../../date-and-time-picker';
 import ModalPortal from '../../modal-portal';
 import { SEARCH_FILTERS_KEY, SEARCH_FILTER_BY_DATE_OPTION_KEY, SEARCH_FILTER_BY_DATE_TYPE_KEY } from '../../../constants';
+import classNames from 'classnames';
 
 const DATE_INPUT_WIDTH = 118;
 
-const FilterByDate = ({ date, onSelect }) => {
+const FilterByDate = ({ date, onChange }) => {
   const [value, setValue] = useState(date.value);
   const [isOpen, setIsOpen] = useState(false);
   const [isTypeOpen, setIsTypeOpen] = useState(false);
   const [isCustomDate, setIsCustomDate] = useState(date.value === SEARCH_FILTER_BY_DATE_OPTION_KEY.CUSTOM);
+  const [time, setTime] = useState({
+    from: date.from,
+    to: date.to,
+  });
   const [type, setType] = useState(date.type);
 
   const typeLabel = useMemo(() => {
@@ -28,6 +33,11 @@ const FilterByDate = ({ date, onSelect }) => {
     }
   }, [type]);
 
+  const label = useMemo(() => {
+    if (!value || value.length === 0) return gettext('Date');
+    return typeLabel;
+  }, [typeLabel, value]);
+
   const typeOptions = useMemo(() => {
     return [
       {
@@ -40,28 +50,6 @@ const FilterByDate = ({ date, onSelect }) => {
     ];
   }, []);
 
-  const label = useMemo(() => {
-    if (!value || value.length === 0) return gettext('Date');
-    const formatDate = (date) => dayjs(date).format('YYYY-MM-DD');
-    const today = dayjs();
-    const prefix = `${typeLabel}: `;
-
-    switch (value) {
-      case SEARCH_FILTER_BY_DATE_OPTION_KEY.TODAY:
-        return `${prefix}${formatDate(today)}`;
-      case SEARCH_FILTER_BY_DATE_OPTION_KEY.LAST_7_DAYS:
-        return `${prefix}${formatDate(today.subtract(6, 'day'))} - ${formatDate(today)}`;
-      case SEARCH_FILTER_BY_DATE_OPTION_KEY.LAST_30_DAYS:
-        return `${prefix}${formatDate(today.subtract(29, 'day'))} - ${formatDate(today)}`;
-      case SEARCH_FILTER_BY_DATE_OPTION_KEY.CUSTOM:
-        return date.start && date.end
-          ? `${prefix}${formatDate(date.start)} - ${formatDate(date.end)}`
-          : gettext('Select date range');
-      default:
-        return gettext('Date');
-    }
-  }, [date, value, typeLabel]);
-
   const options = useMemo(() => {
     return [
       {
@@ -90,96 +78,102 @@ const FilterByDate = ({ date, onSelect }) => {
     const option = Utils.getEventData(e, 'toggle') ?? e.currentTarget.getAttribute('data-toggle');
     if (option === type) return;
     setType(option);
-    onSelect(SEARCH_FILTERS_KEY.DATE, {
-      ...date,
-      type: option,
-    });
-  }, [type, onSelect, date]);
+  }, [type]);
 
   const onClearDate = useCallback(() => {
     setValue('');
     setIsCustomDate(false);
-    onSelect(SEARCH_FILTERS_KEY.DATE, '');
-  }, [onSelect]);
+    setTime({
+      from: null,
+      to: null,
+    });
+    setIsOpen(false);
+  }, []);
 
   const onOptionClick = useCallback((e) => {
     const option = Utils.getEventData(e, 'toggle') ?? e.currentTarget.getAttribute('data-toggle');
     if (option === value) return;
     const today = dayjs().endOf('day');
-    setIsCustomDate(option === SEARCH_FILTER_BY_DATE_OPTION_KEY.CUSTOM);
+    const isCustomOption = option === SEARCH_FILTER_BY_DATE_OPTION_KEY.CUSTOM;
+    setIsCustomDate(isCustomOption);
     setValue(option);
+    setIsOpen(isCustomOption);
     switch (option) {
       case SEARCH_FILTER_BY_DATE_OPTION_KEY.TODAY: {
-        onSelect(SEARCH_FILTERS_KEY.DATE, {
-          value: option,
-          start: dayjs().startOf('day').unix(),
-          end: today.unix()
+        setTime({
+          from: dayjs().startOf('day').unix(),
+          to: today.unix()
         });
         break;
       }
       case SEARCH_FILTER_BY_DATE_OPTION_KEY.LAST_7_DAYS: {
-        onSelect(SEARCH_FILTERS_KEY.DATE, {
-          value: option,
-          start: dayjs().subtract(6, 'day').startOf('day').unix(),
-          end: today.unix()
+        setTime({
+          from: dayjs().subtract(6, 'day').startOf('day').unix(),
+          to: today.unix()
         });
         break;
       }
       case SEARCH_FILTER_BY_DATE_OPTION_KEY.LAST_30_DAYS: {
-        onSelect(SEARCH_FILTERS_KEY.DATE, {
-          value: option,
-          start: dayjs().subtract(30, 'day').startOf('day').unix(),
-          end: today.unix()
+        setTime({
+          from: dayjs().subtract(30, 'day').startOf('day').unix(),
+          to: today.unix()
         });
         break;
       }
       case SEARCH_FILTER_BY_DATE_OPTION_KEY.CUSTOM: {
-        onSelect(SEARCH_FILTERS_KEY.DATE, {
-          value: option,
-          start: null,
-          end: null,
+        setTime({
+          from: null,
+          to: null,
         });
         break;
       }
     }
-  }, [value, onSelect]);
+  }, [value]);
 
   const disabledStartDate = useCallback((startDate) => {
     if (!startDate) return false;
     const today = dayjs();
-    const endValue = date.end;
+    const endValue = time.to;
 
     if (!endValue) {
       return startDate.isAfter(today);
     }
     return endValue.isBefore(startDate) || startDate.isAfter(today);
-  }, [date]);
+  }, [time]);
 
   const disabledEndDate = useCallback((endDate) => {
     if (!endDate) return false;
     const today = dayjs();
-    const startValue = date.start;
+    const startValue = time.from;
     if (!startValue) {
       return endDate.isAfter(today);
     }
     return endDate.isBefore(startValue) || endDate.isAfter(today);
-  }, [date]);
+  }, [time]);
 
-  const onChangeCustomDate = useCallback((customDate) => {
-    const newDate = {
-      ...date,
-      ...customDate,
-    };
-    onSelect(SEARCH_FILTERS_KEY.DATE, newDate);
-  }, [date, onSelect]);
+  useEffect(() => {
+    if (!isOpen) {
+      if (type !== date.type || time.from !== date.from || time.to !== date.to) {
+        onChange(SEARCH_FILTERS_KEY.DATE, {
+          type,
+          value,
+          from: time.from,
+          to: time.to,
+        });
+      }
+    }
+  }, [isOpen, date, time, type, value, onChange]);
 
   return (
     <div className="search-filter filter-by-date-container">
       <Dropdown isOpen={isOpen} toggle={toggle}>
-        <DropdownToggle tag="div" className="search-filter-toggle" onClick={toggle}>
+        <DropdownToggle tag="div" className={classNames('search-filter-toggle', {
+          'active': isOpen && value,
+          'highlighted': value,
+        })} onClick={toggle}>
           <div className="filter-label" style={{ maxWidth: 300 }} title={label}>{label}</div>
           <i
-            className="sf3-font sf3-font-down sf3-font pl-1"
+            className="sf3-font sf3-font-down pl-1"
             onClick={(e) => {
               e.stopPropagation();
               toggle();
@@ -193,7 +187,7 @@ const FilterByDate = ({ date, onSelect }) => {
                 <DropdownToggle tag="div" className="search-filter-toggle filter-by-date-type-toggle">
                   <div className="filter-label">{typeLabel}</div>
                   <i
-                    className="sf3-font sf3-font-down sf3-font pl-1"
+                    className="sf3-font sf3-font-down pl-1"
                     onClick={(e) => {
                       e.stopPropagation();
                       toggleType();
@@ -241,8 +235,8 @@ const FilterByDate = ({ date, onSelect }) => {
                   <Picker
                     showHourAndMinute={false}
                     disabledDate={disabledStartDate}
-                    value={date.start}
-                    onChange={(value) => onChangeCustomDate({ start: value })}
+                    value={time.from}
+                    onChange={(value) => setTime({ ...time, from: value })}
                     inputWidth={DATE_INPUT_WIDTH}
                   />
                 </div>
@@ -251,8 +245,8 @@ const FilterByDate = ({ date, onSelect }) => {
                   <Picker
                     showHourAndMinute={false}
                     disabledDate={disabledEndDate}
-                    value={date.end}
-                    onChange={(value) => onChangeCustomDate({ end: value })}
+                    value={time.to}
+                    onChange={(value) => setTime({ ...time, to: value })}
                     inputWidth={DATE_INPUT_WIDTH}
                   />
                 </div>
@@ -272,7 +266,7 @@ FilterByDate.propTypes = {
     start: PropTypes.oneOfType([PropTypes.number, PropTypes.object]),
     end: PropTypes.oneOfType([PropTypes.number, PropTypes.object]),
   }),
-  onSelect: PropTypes.func.isRequired,
+  onChange: PropTypes.func.isRequired,
 };
 
 export default FilterByDate;
diff --git a/frontend/src/components/search/search-filters/filter-by-suffix.js b/frontend/src/components/search/search-filters/filter-by-suffix.js
index 13e081d1c2..f2ac8badc5 100644
--- a/frontend/src/components/search/search-filters/filter-by-suffix.js
+++ b/frontend/src/components/search/search-filters/filter-by-suffix.js
@@ -1,4 +1,4 @@
-import React, { useCallback, useRef, useState } from 'react';
+import React, { useCallback, useEffect, useRef, useState } from 'react';
 import { Dropdown, DropdownMenu, DropdownToggle } from 'reactstrap';
 import PropTypes from 'prop-types';
 import classNames from 'classnames';
@@ -6,18 +6,16 @@ import { gettext } from '../../../utils/constants';
 import ModalPortal from '../../modal-portal';
 import { SEARCH_FILTERS_KEY } from '../../../constants';
 
-const FilterBySuffix = ({ suffixes, onSelect }) => {
+const FilterBySuffix = ({ suffixes, onChange }) => {
   const [isOpen, setIsOpen] = useState(false);
-  const [inputValue, setInputValue] = useState(suffixes.join(', '));
+  const [inputValue, setInputValue] = useState(suffixes);
   const inputRef = useRef(null);
 
   const toggle = useCallback(() => setIsOpen(!isOpen), [isOpen]);
 
   const handleInput = useCallback((e) => {
     setInputValue(e.target.value);
-    const suffixes = e.target.value.split(',').map(suffix => suffix.trim()).filter(Boolean);
-    onSelect(SEARCH_FILTERS_KEY.SUFFIXES, suffixes);
-  }, [onSelect]);
+  }, []);
 
   const handleKeyDown = useCallback((e) => {
     e.stopPropagation();
@@ -26,30 +24,48 @@ const FilterBySuffix = ({ suffixes, onSelect }) => {
     }
   }, []);
 
+  const handleClearInput = useCallback(() => {
+    setInputValue('');
+    setIsOpen(false);
+  }, []);
+
+  useEffect(() => {
+    if (!isOpen && inputValue !== suffixes) {
+      onChange(SEARCH_FILTERS_KEY.SUFFIXES, inputValue.replace(/\./g, ''));
+    }
+  }, [isOpen, inputValue, suffixes, onChange]);
+
   return (
     <div className="search-filter filter-by-suffix-container">
       <Dropdown isOpen={isOpen} toggle={toggle}>
         <DropdownToggle tag="div" className={classNames('search-filter-toggle', {
-          'active': isOpen && suffixes.length > 0,
-          'highlighted': suffixes.length > 0,
+          'active': isOpen && inputValue.length > 0,
+          'highlighted': inputValue.length > 0,
         })} onClick={toggle}>
           <div className="filter-label" title={gettext('File suffix')}>{gettext('File suffix')}</div>
-          <i className="sf3-font sf3-font-down sf3-font pl-1" />
+          <i className="sf3-font sf3-font-down pl-1" />
         </DropdownToggle>
         <ModalPortal>
-          <DropdownMenu className="search-filter-menu filter-by-suffix-menu">
-            <div className="input-container">
-              <input
-                ref={inputRef}
-                type="text"
-                placeholder={gettext('Seperate multiple suffixes by ","(like .sdoc, .pdf)')}
-                value={inputValue}
-                autoFocus
-                width={120}
-                onChange={handleInput}
-                onKeyDown={handleKeyDown}
-              />
-            </div>
+          <DropdownMenu className="search-filter-menu filter-by-suffix-menu p-4">
+            <input
+              ref={inputRef}
+              type="text"
+              className="form-control"
+              placeholder={gettext('Seperate multiple suffixes by ","(like sdoc, pdf)')}
+              value={inputValue}
+              autoFocus
+              onChange={handleInput}
+              onKeyDown={handleKeyDown}
+            />
+            {inputValue.length > 0 && (
+              <button
+                type="button"
+                className="clear-icon-right sf3-font sf3-font-x-01"
+                onClick={handleClearInput}
+                aria-label={gettext('Clear')}
+              >
+              </button>
+            )}
           </DropdownMenu>
         </ModalPortal>
       </Dropdown>
@@ -58,8 +74,8 @@ const FilterBySuffix = ({ suffixes, onSelect }) => {
 };
 
 FilterBySuffix.propTypes = {
-  suffixes: PropTypes.array.isRequired,
-  onSelect: PropTypes.func.isRequired,
+  suffixes: PropTypes.string.isRequired,
+  onChange: PropTypes.func.isRequired,
 };
 
 export default FilterBySuffix;
diff --git a/frontend/src/components/search/search-filters/filter-by-text.js b/frontend/src/components/search/search-filters/filter-by-text.js
index 930802dfa7..dbb405667f 100644
--- a/frontend/src/components/search/search-filters/filter-by-text.js
+++ b/frontend/src/components/search/search-filters/filter-by-text.js
@@ -6,7 +6,7 @@ import { Utils } from '../../../utils/utils';
 import { gettext } from '../../../utils/constants';
 import { SEARCH_FILTERS_KEY } from '../../../constants';
 
-const FilterByText = ({ searchFilenameOnly, onSelect }) => {
+const FilterByText = ({ searchFilenameOnly, onChange }) => {
   const [isOpen, setIsOpen] = useState(false);
   const [value, setValue] = useState(searchFilenameOnly ? SEARCH_FILTERS_KEY.SEARCH_FILENAME_ONLY : SEARCH_FILTERS_KEY.SEARCH_FILENAME_AND_CONTENT);
 
@@ -27,8 +27,8 @@ const FilterByText = ({ searchFilenameOnly, onSelect }) => {
     const option = Utils.getEventData(e, 'toggle') ?? e.currentTarget.getAttribute('data-toggle');
     setValue(option);
     const isSearchFilenameOnly = option === SEARCH_FILTERS_KEY.SEARCH_FILENAME_ONLY;
-    onSelect(SEARCH_FILTERS_KEY.SEARCH_FILENAME_ONLY, isSearchFilenameOnly);
-  }, [onSelect]);
+    onChange(SEARCH_FILTERS_KEY.SEARCH_FILENAME_ONLY, isSearchFilenameOnly);
+  }, [onChange]);
 
   const label = options.find((option) => option.key === value).label;
 
@@ -59,7 +59,7 @@ const FilterByText = ({ searchFilenameOnly, onSelect }) => {
 
 FilterByText.propTypes = {
   searchFilenameOnly: PropTypes.bool.isRequired,
-  onSelect: PropTypes.func.isRequired,
+  onChange: PropTypes.func.isRequired,
 };
 
 export default FilterByText;
diff --git a/frontend/src/components/search/search-filters/index.css b/frontend/src/components/search/search-filters/index.css
index bc09d89ba7..804d3c842e 100644
--- a/frontend/src/components/search/search-filters/index.css
+++ b/frontend/src/components/search/search-filters/index.css
@@ -32,6 +32,10 @@
   background-color: #efefef;
 }
 
+.search-filter-toggle .sf3-font-down {
+  color: #666;
+}
+
 .search-filters-container .search-filter .filter-label {
   width: fit-content;
   display: inline-block;
@@ -57,6 +61,28 @@
 
 .search-filter-menu.filter-by-suffix-menu {
   width: 400px;
+  position: relative;
+}
+
+.search-filter-menu.filter-by-suffix-menu .clear-icon-right {
+  width: 20px;
+  height: 20px;
+  position: absolute;
+  top: 16px;
+  right: 16px;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  color: #666;
+  margin: 9px;
+  border: 0;
+  border-radius: 3px;
+  background-color: transparent;
+  padding: 0;
+}
+
+.search-filter-menu.filter-by-suffix-menu .clear-icon-right:hover {
+  background-color: #efefef;
 }
 
 .search-filters-container .search-filters-dropdown-item {
@@ -91,6 +117,14 @@
   gap: 4px;
 }
 
+.search-filter-menu .input-container.focus {
+  background-color: #fff;
+  border-color: #1991eb;
+  box-shadow: 0 0 0 2px rgba(70, 127, 207, .25);
+  color: #495057;
+  outline: 0;
+}
+
 .search-filter-menu .input-container .search-input-wrapper {
   display: flex;
   align-items: center;
@@ -150,6 +184,10 @@
   align-items: center;
 }
 
+.filter-by-date-menu .filter-by-date-menu-toolbar .filter-by-date-type-toggle .filter-label {
+  color: #7d7d7d;
+}
+
 .filter-by-date-menu .filter-by-date-menu-toolbar .delete-btn {
   display: flex;
   align-items: center;
@@ -184,12 +222,12 @@
   font-size: 14px;
 }
 
-.search-filters-container .search-filter-toggle.active,
 .search-filters-container .search-filter-toggle.active:hover,
 .search-filters-container .search-filter-toggle.highlighted:hover {
   background-color: rgba(255, 152, 0, 0.2);
 }
 
-.search-filters-container .search-filter-toggle.highlighted {
+.search-filters-container .search-filter-toggle.highlighted,
+.search-filter-toggle.highlighted .sf3-font-down {
   color: #ff9800;
 }
diff --git a/frontend/src/components/search/search-filters/index.js b/frontend/src/components/search/search-filters/index.js
index 3a704e2543..c0cfe807a2 100644
--- a/frontend/src/components/search/search-filters/index.js
+++ b/frontend/src/components/search/search-filters/index.js
@@ -10,10 +10,10 @@ import './index.css';
 const SearchFilters = ({ filters, onChange }) => {
   return (
     <div className="search-filters-container">
-      <FilterBySuffix suffixes={filters.suffixes} onSelect={onChange} />
-      <FilterByText searchFilenameOnly={filters.search_filename_only} onSelect={onChange} />
-      <FilterByCreator creatorList={filters.creator_list} onSelect={onChange} />
-      <FilterByDate date={filters.date} onSelect={onChange} />
+      <FilterBySuffix suffixes={filters.suffixes} onChange={onChange} />
+      <FilterByText searchFilenameOnly={filters.search_filename_only} onChange={onChange} />
+      <FilterByCreator creatorList={filters.creator_list} onChange={onChange} />
+      <FilterByDate date={filters.date} onChange={onChange} />
     </div>
   );
 };
diff --git a/frontend/src/components/search/search.js b/frontend/src/components/search/search.js
index ce66ff5196..e8093a1086 100644
--- a/frontend/src/components/search/search.js
+++ b/frontend/src/components/search/search.js
@@ -12,7 +12,7 @@ import { Utils } from '../../utils/utils';
 import toaster from '../toast';
 import Loading from '../loading';
 import { SEARCH_MASK, SEARCH_CONTAINER } from '../../constants/zIndexes';
-import { PRIVATE_FILE_TYPE, SEARCH_FILTER_BY_DATE_OPTION_KEY, SEARCH_FILTER_BY_DATE_TYPE_KEY, SEARCH_FILTERS_SHOW_KEY } from '../../constants';
+import { PRIVATE_FILE_TYPE, SEARCH_FILTER_BY_DATE_OPTION_KEY, SEARCH_FILTER_BY_DATE_TYPE_KEY, SEARCH_FILTERS_KEY, SEARCH_FILTERS_SHOW_KEY } from '../../constants';
 import SearchFilters from './search-filters';
 import SearchTags from './search-tags';
 import IconBtn from '../icon-btn';
@@ -61,10 +61,10 @@ class Search extends Component {
         date: {
           type: SEARCH_FILTER_BY_DATE_TYPE_KEY.CREATE_TIME,
           value: '',
-          start: null,
-          end: null,
+          from: null,
+          to: null,
         },
-        suffixes: [],
+        suffixes: '',
       },
     };
     this.highlightRef = null;
@@ -530,7 +530,7 @@ class Search extends Component {
           start: null,
           end: null,
         },
-        suffixes: [],
+        suffixes: '',
       }
     });
   }
@@ -558,7 +558,7 @@ class Search extends Component {
       }
     }
 
-    const filteredItems = this.filterResults(resultItems);
+    const filteredItems = this.filterByCreator(resultItems);
     if (isLoading) {
       return <Loading />;
     }
@@ -668,72 +668,34 @@ class Search extends Component {
   }
 
   searchRepo = () => {
-    const { value, filters } = this.state;
+    const { value } = this.state;
     const queryData = {
       q: value,
       search_repo: this.props.repoID,
       search_ftypes: 'all',
-      search_filename_only: filters.search_filename_only,
     };
-    this.getSearchResult(queryData);
+    this.getSearchResult(this.buildSearchParams(queryData));
   };
 
   searchFolder = () => {
-    const { value, filters } = this.state;
+    const { value } = this.state;
     const queryData = {
       q: value,
       search_repo: this.props.repoID,
       search_ftypes: 'all',
       search_path: this.props.path,
-      search_filename_only: filters.search_filename_only,
     };
-    this.getSearchResult(queryData);
+    this.getSearchResult(this.buildSearchParams(queryData));
   };
 
   searchAllRepos = () => {
-    const { value, filters } = this.state;
+    const { value } = this.state;
     const queryData = {
       q: value,
       search_repo: 'all',
       search_ftypes: 'all',
-      search_filename_only: filters.search_filename_only,
     };
-    this.getSearchResult(queryData);
-  };
-
-  filterResults = (results) => {
-    const { filters } = this.state;
-    return results.filter(item => {
-      if (filters.creator_list && filters.creator_list.length > 0) {
-        if (!filters.creator_list.some(creator => creator.email === item.repo_owner_email)) {
-          return false;
-        }
-      }
-
-      let startDate = filters.date.start;
-      let endDate = filters.date.end;
-      if (filters.date.value === SEARCH_FILTER_BY_DATE_OPTION_KEY.CUSTOM) {
-        startDate = filters.date.start?.unix();
-        endDate = filters.date.end?.unix();
-      }
-
-      if (startDate && item.mtime < startDate) {
-        return false;
-      }
-      if (endDate && item.mtime > endDate) {
-        return false;
-      }
-
-      if (filters.suffixes && filters.suffixes.length > 0) {
-        const pathParts = item.path.split('.');
-        const suffix = pathParts.length > 1 ? `.${pathParts.pop()}` : '';
-        if (!filters.suffixes.includes(suffix)) {
-          return false;
-        }
-      }
-
-      return true;
-    });
+    this.getSearchResult(this.buildSearchParams(queryData));
   };
 
   renderResults = (resultItems, isVisited) => {
@@ -785,26 +747,79 @@ class Search extends Component {
     this.setState({ isFiltersShow: !isFiltersShow });
   }
 
+  buildSearchParams = (baseParams) => {
+    const { filters } = this.state;
+    const params = { ...baseParams };
+
+    if (filters.search_filename_only) {
+      params.search_filename_only = filters.search_filename_only;
+    }
+
+    if (filters.date.value) {
+      const isCustom = filters.date.value === SEARCH_FILTER_BY_DATE_OPTION_KEY.CUSTOM;
+      params.time_from = isCustom ? filters.date.start?.unix() : filters.date.from;
+      params.time_to = isCustom ? filters.date.end?.unix() : filters.date.to;
+    }
+
+    if (filters.suffixes) {
+      params.input_fexts = filters.suffixes;
+      params.search_ftypes = 'custom';
+    }
+
+    if (filters.creator_list.length > 0) {
+      params.creator_emails = filters.creator_list.map(c => c.email).join(',');
+    }
+
+    return params;
+  };
+
   handleFiltersChange = (key, value) => {
-    const newFilters = { ...this.state.filters, [key]: value};
-    if (newFilters.search_filename_only !== this.state.filters.search_filename_only) {
-      this.setState({ filters: newFilters }, () => {
-        const newQueryData = {
-          ...this.queryData,
-          search_filename_only: newFilters.search_filename_only,
-        }
-        this.getSearchResult(newQueryData);
-      });
+    const newFilters = { ...this.state.filters, [key]: value };
+    const hasActiveFilter = newFilters.suffixes || newFilters.creator_list.length > 0 || newFilters.date.value;
+    this.setState({ filters: newFilters, isFilterControllerActive: hasActiveFilter });
+
+    // build query data
+    if (!this.state.value || !this.state.isResultGotten) return;
+    const queryUpdates = {};
+
+    if (key === SEARCH_FILTERS_KEY.CREATOR_LIST) return;
+    if (key === SEARCH_FILTERS_KEY.SEARCH_FILENAME_ONLY) {
+      queryUpdates.search_filename_only = value;
+    }
+    if (key === SEARCH_FILTERS_KEY.SUFFIXES) {
+      queryUpdates.search_ftypes = 'custom';
+      queryUpdates.input_fexts = value;
+      if (!value) {
+        queryUpdates.search_ftypes = 'all';
+      }
+    }
+    if (key === SEARCH_FILTERS_KEY.DATE) {
+      const date = value;
+      const isCustom = date.value === SEARCH_FILTER_BY_DATE_OPTION_KEY.CUSTOM;
+      queryUpdates.time_from = isCustom ? value.from?.unix() : value.from;
+      queryUpdates.time_to = isCustom ? value.to?.unix() : value.to;
     }
 
-    let isFilterControllerActive = false;
-    if (newFilters.creator_list.length > 0 || newFilters.date || newFilters.suffixes.length > 0) {
-      isFilterControllerActive = true;
-    }
+    const newQueryData = {
+      ...this.queryData,
+      ...queryUpdates,
+    };
 
-    this.setState({ filters: newFilters, isFilterControllerActive }, () => this.forceUpdate());
+    this.getSearchResult(newQueryData);
   }
 
+  filterByCreator = (results) => {
+    const { filters } = this.state;
+    return results.filter(item => {
+      if (filters.creator_list && filters.creator_list.length > 0) {
+        if (!filters.creator_list.some(creator => creator.email === item.repo_owner_email)) {
+          return false;
+        }
+      }
+      return true;
+    });
+  };
+
   handleSelectTag = (tag) => {
     this.props.onSelectTag(tag);
     this.resetToDefault();
diff --git a/frontend/src/css/search.css b/frontend/src/css/search.css
index 23110aa764..e8c1e5bef4 100644
--- a/frontend/src/css/search.css
+++ b/frontend/src/css/search.css
@@ -53,6 +53,10 @@
   color: #ff9800;
 }
 
+.search-container.show .input-icon .search-icon-right.search-filter-controller.active:hover {
+  background-color: rgba(255, 152, 0, 0.2);
+}
+
 .search-icon-left {
   display: flex;
 }