import React, { useCallback, useEffect, useMemo, useRef, useState, } from 'react'; import { useTranslation } from 'next-i18next'; import { useRouter } from 'next/router'; import { ISelectableAll, ISelectableAndIndeterminatable } from '~/client/interfaces/selectable-all'; import { IFormattedSearchResult } from '~/interfaces/search'; import { useIsSearchServiceReachable, useShowPageLimitationL } from '~/stores/context'; import { ISearchConditions, ISearchConfigurations, useSWRxSearch } from '~/stores/search'; import { NotAvailableForGuest } from './NotAvailableForGuest'; import { NotAvailableForReadOnlyUser } from './NotAvailableForReadOnlyUser'; import PaginationWrapper from './PaginationWrapper'; import { OperateAllControl } from './SearchPage/OperateAllControl'; import SearchControl from './SearchPage/SearchControl'; import { IReturnSelectedPageIds, SearchPageBase, usePageDeleteModalForBulkDeletion } from './SearchPage/SearchPageBase'; // TODO: replace with "customize:showPageLimitationS" const INITIAL_PAGIONG_SIZE = 20; /** * SearchResultListHead */ type SearchResultListHeadProps = { searchResult: IFormattedSearchResult, searchingKeyword: string, offset: number, pagingSize: number, onPagingSizeChanged: (size: number) => void, } const SearchResultListHead = React.memo((props: SearchResultListHeadProps): JSX.Element => { const { t } = useTranslation(); const { searchResult, searchingKeyword, offset, pagingSize, onPagingSizeChanged, } = props; const { took, total, hitsCount } = searchResult.meta; const leftNum = offset + 1; const rightNum = offset + hitsCount; if (total === 0) { return (
0 {t('search_result.page_number_unit')}
); } return (
{t('search_result.result_meta')} {`${searchingKeyword}`} {`${leftNum}-${rightNum}`} / {total} { took != null && ( // blackout 70px rectangle in VRT ({took}ms) ) }
); }); SearchResultListHead.displayName = 'SearchResultListHead'; export const SearchPage = (): JSX.Element => { const { t } = useTranslation(); const { data: showPageLimitationL } = useShowPageLimitationL(); const router = useRouter(); // parse URL Query const queries = router.query.q; const initQ = (Array.isArray(queries) ? queries.join(' ') : queries) ?? ''; const [keyword, setKeyword] = useState(initQ); const [offset, setOffset] = useState(0); const [limit, setLimit] = useState(showPageLimitationL ?? INITIAL_PAGIONG_SIZE); const [configurationsByControl, setConfigurationsByControl] = useState>({}); const selectAllControlRef = useRef(null); const searchPageBaseRef = useRef(null); const { data: isSearchServiceReachable } = useIsSearchServiceReachable(); const { data, conditions, mutate } = useSWRxSearch(keyword, null, { ...configurationsByControl, offset, limit, }); const searchInvokedHandler = useCallback((_keyword: string, newConfigurations: Partial) => { setKeyword(_keyword); setOffset(0); setConfigurationsByControl(newConfigurations); }, []); const selectAllCheckboxChangedHandler = useCallback((isChecked: boolean) => { const instance = searchPageBaseRef.current; if (instance == null) { return; } if (isChecked) { instance.selectAll(); } else { instance.deselectAll(); } }, []); const selectedPagesByCheckboxesChangedHandler = useCallback((selectedCount: number, totalCount: number) => { const instance = selectAllControlRef.current; if (instance == null) { return; } if (selectedCount === 0) { instance.deselect(); } else if (selectedCount === totalCount) { instance.select(); } else { instance.setIndeterminate(); } }, []); const pagingSizeChangedHandler = useCallback((pagingSize: number) => { setOffset(0); setLimit(pagingSize); mutate(); }, [mutate]); const pagingNumberChangedHandler = useCallback((activePage: number) => { setOffset((activePage - 1) * limit); mutate(); }, [limit, mutate]); const initialSearchConditions: Partial = useMemo(() => { return { keyword: initQ, limit: INITIAL_PAGIONG_SIZE, }; }, [initQ]); // for bulk deletion const deleteAllButtonClickedHandler = usePageDeleteModalForBulkDeletion(data, searchPageBaseRef, () => mutate()); // push state useEffect(() => { const newUrl = new URL('/_search', 'http://example.com'); newUrl.searchParams.append('q', keyword); window.history.pushState('', `Search - ${keyword}`, `${newUrl.pathname}${newUrl.search}`); }, [keyword]); const hitsCount = data?.meta.hitsCount; const allControl = useMemo(() => { const isDisabled = hitsCount === 0; return ( ); }, [deleteAllButtonClickedHandler, hitsCount, selectAllCheckboxChangedHandler, t]); const searchControl = useMemo(() => { if (!isSearchServiceReachable) { return <>; } return ( ); }, [allControl, initialSearchConditions, isSearchServiceReachable, searchInvokedHandler]); const searchResultListHead = useMemo(() => { if (data == null) { return <>; } return ( ); }, [data, keyword, limit, offset, pagingSizeChangedHandler]); const searchPager = useMemo(() => { // when pager is not needed if (data == null || data.meta.hitsCount === data.meta.total) { return <>; } const { total } = data.meta; const { offset, limit } = conditions; return ( ); }, [conditions, data, pagingNumberChangedHandler]); return ( ); };