SearchControl.tsx 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. import React, {
  2. useCallback, useEffect, useState,
  3. } from 'react';
  4. import { useTranslation } from 'next-i18next';
  5. import { SORT_AXIS, SORT_ORDER } from '~/interfaces/search';
  6. import type { ISearchConditions, ISearchConfigurations } from '~/stores/search';
  7. import SearchForm from '../SearchForm';
  8. import SearchOptionModal from './SearchOptionModal';
  9. import SortControl from './SortControl';
  10. import styles from './SearchControl.module.scss';
  11. type Props = {
  12. isSearchServiceReachable: boolean,
  13. isEnableSort: boolean,
  14. isEnableFilter: boolean,
  15. initialSearchConditions: Partial<ISearchConditions>,
  16. onSearchInvoked?: (keyword: string, configurations: Partial<ISearchConfigurations>) => void,
  17. allControl: React.ReactNode,
  18. }
  19. const SearchControl = React.memo((props: Props): JSX.Element => {
  20. const {
  21. isSearchServiceReachable,
  22. isEnableSort,
  23. isEnableFilter,
  24. initialSearchConditions,
  25. onSearchInvoked,
  26. allControl,
  27. } = props;
  28. const keywordOnInit = initialSearchConditions.keyword ?? '';
  29. const [keyword, setKeyword] = useState(keywordOnInit);
  30. const [sort, setSort] = useState<SORT_AXIS>(initialSearchConditions.sort ?? SORT_AXIS.RELATION_SCORE);
  31. const [order, setOrder] = useState<SORT_ORDER>(initialSearchConditions.order ?? SORT_ORDER.DESC);
  32. const [includeUserPages, setIncludeUserPages] = useState(initialSearchConditions.includeUserPages ?? false);
  33. const [includeTrashPages, setIncludeTrashPages] = useState(initialSearchConditions.includeTrashPages ?? false);
  34. const [isFileterOptionModalShown, setIsFileterOptionModalShown] = useState(false);
  35. const { t } = useTranslation('');
  36. const searchFormSubmittedHandler = useCallback((input: string) => {
  37. setKeyword(input);
  38. onSearchInvoked?.(input, {
  39. sort, order, includeUserPages, includeTrashPages,
  40. });
  41. }, [includeTrashPages, includeUserPages, onSearchInvoked, order, sort]);
  42. const changeSortHandler = useCallback((nextSort: SORT_AXIS, nextOrder: SORT_ORDER) => {
  43. setSort(nextSort);
  44. setOrder(nextOrder);
  45. onSearchInvoked?.(keyword, {
  46. sort: nextSort, order: nextOrder, includeUserPages, includeTrashPages,
  47. });
  48. }, [includeTrashPages, includeUserPages, keyword, onSearchInvoked]);
  49. const changeIncludeUserPagesHandler = useCallback((include: boolean) => {
  50. setIncludeUserPages(include);
  51. onSearchInvoked?.(keyword, {
  52. sort, order, includeUserPages: include, includeTrashPages,
  53. });
  54. }, [includeTrashPages, keyword, onSearchInvoked, order, sort]);
  55. const changeIncludeTrashPagesHandler = useCallback((include: boolean) => {
  56. setIncludeTrashPages(include);
  57. onSearchInvoked?.(keyword, {
  58. sort, order, includeUserPages, includeTrashPages: include,
  59. });
  60. }, [includeUserPages, keyword, onSearchInvoked, order, sort]);
  61. useEffect(() => {
  62. setKeyword(keywordOnInit);
  63. }, [keywordOnInit]);
  64. return (
  65. <div className="shadow-sm">
  66. <div className="grw-search-page-nav d-flex py-3 align-items-center">
  67. <div className="flex-grow-1 mx-4">
  68. <SearchForm
  69. isSearchServiceReachable={isSearchServiceReachable}
  70. keywordOnInit={keyword}
  71. disableIncrementalSearch
  72. onSubmit={searchFormSubmittedHandler}
  73. />
  74. </div>
  75. </div>
  76. {/* TODO: replace the following elements deleteAll button , relevance button and include specificPath button component */}
  77. <div className="search-control d-flex align-items-center py-md-2 py-3 px-md-4 px-3 border-bottom border-gray">
  78. {/* sort option */}
  79. {isEnableSort && (
  80. <div className="flex-grow-1">
  81. <SortControl
  82. sort={sort}
  83. order={order}
  84. onChange={changeSortHandler}
  85. />
  86. </div>
  87. )}
  88. {/* filter option */}
  89. {isEnableFilter && (
  90. <>
  91. <div className="d-lg-none">
  92. <button
  93. type="button"
  94. className="btn"
  95. onClick={() => setIsFileterOptionModalShown(true)}
  96. >
  97. <span className="material-symbols-outlined">
  98. tune
  99. </span>
  100. </button>
  101. </div>
  102. <div className="d-none d-lg-flex align-items-center search-control-include-options">
  103. <div className="px-2 py-1">
  104. <div className="form-check form-check-succsess">
  105. <input
  106. className="form-check-input me-2"
  107. type="checkbox"
  108. id="flexCheckDefault"
  109. defaultChecked={includeUserPages}
  110. onChange={e => changeIncludeUserPagesHandler(e.target.checked)}
  111. />
  112. <label
  113. className="form-label form-check-label mb-0 d-flex align-items-center text-secondary with-no-font-weight"
  114. htmlFor="flexCheckDefault"
  115. >
  116. {t('Include Subordinated Target Page', { target: '/user' })}
  117. </label>
  118. </div>
  119. </div>
  120. <div className="px-2 py-1">
  121. <div className="form-check form-check-succsess">
  122. <input
  123. className="form-check-input me-2"
  124. type="checkbox"
  125. id="flexCheckChecked"
  126. checked={includeTrashPages}
  127. onChange={e => changeIncludeTrashPagesHandler(e.target.checked)}
  128. />
  129. <label
  130. className="form-label form-check-label mb-0 d-flex align-items-center text-secondary with-no-font-weight"
  131. htmlFor="flexCheckChecked"
  132. >
  133. {t('Include Subordinated Target Page', { target: '/trash' })}
  134. </label>
  135. </div>
  136. </div>
  137. </div>
  138. </>
  139. )}
  140. <div className="d-flex">
  141. <div className="btn-group">
  142. {/* TODO: imprv to delete all result UI */}
  143. {/* <button className={`btn btn-sm rounded ${styles['btn-delete']}`} type="button" data-bs-toggle="dropdown" aria-expanded="false">
  144. <span className="material-symbols-outlined ">delete</span>
  145. <span className="material-symbols-outlined ">expand_more</span>
  146. </button> */}
  147. {/* <ul className="dropdown-menu"> */}
  148. {allControl}
  149. {/* </ul> */}
  150. </div>
  151. </div>
  152. </div>
  153. <SearchOptionModal
  154. isOpen={isFileterOptionModalShown || false}
  155. onClose={() => setIsFileterOptionModalShown(false)}
  156. includeUserPages={includeUserPages}
  157. includeTrashPages={includeTrashPages}
  158. onIncludeUserPagesSwitched={setIncludeUserPages}
  159. onIncludeTrashPagesSwitched={setIncludeTrashPages}
  160. />
  161. </div>
  162. );
  163. });
  164. SearchControl.displayName = 'SearchControl';
  165. export default SearchControl;