From ddf2b6eda3ff1c18d5ba079395f7ce515bb480da Mon Sep 17 00:00:00 2001 From: Fedor Katurov Date: Tue, 29 Mar 2022 17:40:48 +0700 Subject: [PATCH] added lab search --- src/api/lab/index.ts | 4 +-- src/components/input/InputText/index.tsx | 32 ++++++++++++++++--- .../input/InputText/styles.module.scss | 6 ++++ src/components/input/SearchInput/index.tsx | 12 +++++++ src/components/lab/LabHead/index.tsx | 15 +++++++-- src/components/lab/LabHead/styles.module.scss | 12 +++++++ src/components/lab/LabNoResults/index.tsx | 19 +++++++++++ .../lab/LabNoResults/styles.module.scss | 16 ++++++++++ src/containers/lab/LabGrid/index.tsx | 7 +++- src/hooks/data/useDebouncedValue.ts | 12 +++++++ src/hooks/lab/useGetLabNodes.ts | 17 ++++++---- src/hooks/lab/useLab.ts | 5 ++- src/types/index.ts | 11 ------- src/types/lab/index.ts | 1 + src/utils/context/LabContextProvider.tsx | 4 +++ src/utils/providers/LabProvider.tsx | 4 +++ 16 files changed, 149 insertions(+), 28 deletions(-) create mode 100644 src/components/input/SearchInput/index.tsx create mode 100644 src/components/lab/LabNoResults/index.tsx create mode 100644 src/components/lab/LabNoResults/styles.module.scss create mode 100644 src/hooks/data/useDebouncedValue.ts diff --git a/src/api/lab/index.ts b/src/api/lab/index.ts index ab2de217..770cf37e 100644 --- a/src/api/lab/index.ts +++ b/src/api/lab/index.ts @@ -7,9 +7,9 @@ import { } from '~/types/lab'; import { api, cleanResult } from '~/utils/api'; -export const getLabNodes = ({ offset, limit, sort }: GetLabNodesRequest) => +export const getLabNodes = ({ offset, limit, sort, search }: GetLabNodesRequest) => api - .get(API.LAB.NODES, { params: { offset, limit, sort } }) + .get(API.LAB.NODES, { params: { offset, limit, sort, search } }) .then(cleanResult); export const getLabStats = () => api.get(API.LAB.STATS).then(cleanResult); diff --git a/src/components/input/InputText/index.tsx b/src/components/input/InputText/index.tsx index 577e0128..ed3ff34f 100644 --- a/src/components/input/InputText/index.tsx +++ b/src/components/input/InputText/index.tsx @@ -1,4 +1,13 @@ -import React, { ChangeEvent, FC, useCallback, useState } from 'react'; +import React, { + ChangeEvent, + DetailedHTMLProps, + FC, + InputHTMLAttributes, + ReactElement, + ReactNode, + useCallback, + useState, +} from 'react'; import classNames from 'classnames'; @@ -6,11 +15,21 @@ import { Icon } from '~/components/input/Icon'; import { InputWrapper } from '~/components/input/InputWrapper'; import { useTranslatedError } from '~/hooks/data/useTranslatedError'; import { useFocusEvent } from '~/hooks/dom/useFocusEvent'; -import { IInputTextProps } from '~/types'; import styles from './styles.module.scss'; -const InputText: FC = ({ +export type InputTextProps = Omit< + DetailedHTMLProps, HTMLInputElement>, + 'prefix' +> & { + handler?: (value: string) => void; + title?: string; + error?: string; + suffix?: ReactNode; + prefix?: ReactNode; +}; + +const InputText: FC = ({ className = '', handler, required = false, @@ -43,7 +62,12 @@ const InputText: FC = ({ return ( -
+
{!!prefix &&
{prefix}
} {} + +const SearchInput: VFC = ({ ...props }) => ( + } /> +); + +export { SearchInput }; diff --git a/src/components/lab/LabHead/index.tsx b/src/components/lab/LabHead/index.tsx index d038b783..2cfa8fb9 100644 --- a/src/components/lab/LabHead/index.tsx +++ b/src/components/lab/LabHead/index.tsx @@ -1,6 +1,9 @@ import React, { FC } from 'react'; +import { Filler } from '~/components/containers/Filler'; import { Group } from '~/components/containers/Group'; +import { InputText } from '~/components/input/InputText'; +import { SearchInput } from '~/components/input/SearchInput'; import { HorizontalMenu } from '~/components/menu/HorizontalMenu'; import { LabNodesSort } from '~/types/lab'; import { useLabContext } from '~/utils/context/LabContextProvider'; @@ -12,10 +15,10 @@ interface IProps { } const LabHead: FC = ({ isLoading }) => { - const { sort, setSort } = useLabContext(); + const { sort, setSort, search, setSearch } = useLabContext(); return ( - +
= ({ isLoading }) => { Важные - + + + +
+ +
+
); }; diff --git a/src/components/lab/LabHead/styles.module.scss b/src/components/lab/LabHead/styles.module.scss index 65568e8d..a9505c24 100644 --- a/src/components/lab/LabHead/styles.module.scss +++ b/src/components/lab/LabHead/styles.module.scss @@ -2,4 +2,16 @@ .wrap { border-radius: $radius; + display: flex; + flex-direction: row; + + @include tablet { + flex-direction: column; + } +} + +.search { + @include tablet { + margin-top: $gap; + } } diff --git a/src/components/lab/LabNoResults/index.tsx b/src/components/lab/LabNoResults/index.tsx new file mode 100644 index 00000000..d2e1c568 --- /dev/null +++ b/src/components/lab/LabNoResults/index.tsx @@ -0,0 +1,19 @@ +import React, { VFC } from 'react'; + +import { Card } from '~/components/containers/Card'; +import { Button } from '~/components/input/Button'; + +import styles from './styles.module.scss'; + +interface LabNoResultsProps { + resetSearch: () => void; +} + +const LabNoResults: VFC = ({ resetSearch }) => ( + +
Здесь ничего нет
+ +
+); + +export { LabNoResults }; diff --git a/src/components/lab/LabNoResults/styles.module.scss b/src/components/lab/LabNoResults/styles.module.scss new file mode 100644 index 00000000..c38588d4 --- /dev/null +++ b/src/components/lab/LabNoResults/styles.module.scss @@ -0,0 +1,16 @@ +@import "src/styles/variables"; + +.wrap { + padding: $gap * 2; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + min-height: 33vh; +} + +.title { + text-transform: uppercase; + font: $font_20_semibold; + margin-bottom: $gap * 2; +} diff --git a/src/containers/lab/LabGrid/index.tsx b/src/containers/lab/LabGrid/index.tsx index 16467f28..18f590c5 100644 --- a/src/containers/lab/LabGrid/index.tsx +++ b/src/containers/lab/LabGrid/index.tsx @@ -3,6 +3,7 @@ import React, { FC } from 'react'; import Masonry from 'react-masonry-css'; import { InfiniteScroll } from '~/components/containers/InfiniteScroll'; +import { LabNoResults } from '~/components/lab/LabNoResults'; import { LabNode } from '~/components/lab/LabNode'; import { EMPTY_NODE, NODE_TYPES } from '~/constants/node'; import { useLabContext } from '~/utils/context/LabContextProvider'; @@ -30,7 +31,7 @@ const LoadingNode = () => ( ); const LabGrid: FC = () => { - const { isLoading, nodes, hasMore, loadMore } = useLabContext(); + const { isLoading, nodes, hasMore, loadMore, search, setSearch } = useLabContext(); if (isLoading) { return ( @@ -52,6 +53,10 @@ const LabGrid: FC = () => { ); } + if (search && !nodes.length) { + return setSearch('')} />; + } + return ( (val: T, delay = 300) => { + const [state, setState] = useState(val); + + useEffect(() => { + const timeout = setTimeout(() => setState(val), delay); + return () => clearTimeout(timeout); + }, [val, delay]); + + return state; +}; diff --git a/src/hooks/lab/useGetLabNodes.ts b/src/hooks/lab/useGetLabNodes.ts index 64c86b5b..06dea212 100644 --- a/src/hooks/lab/useGetLabNodes.ts +++ b/src/hooks/lab/useGetLabNodes.ts @@ -4,15 +4,17 @@ import useSWRInfinite, { SWRInfiniteKeyLoader } from 'swr/infinite'; import { getLabNodes } from '~/api/lab'; import { useAuth } from '~/hooks/auth/useAuth'; +import { useDebouncedValue } from '~/hooks/data/useDebouncedValue'; import { useLabStore } from '~/store/lab/useLabStore'; import { INode } from '~/types'; import { GetLabNodesRequest, ILabNode, LabNodesSort } from '~/types/lab'; import { flatten, uniqBy } from '~/utils/ramda'; -const getKey: (isUser: boolean, sort?: LabNodesSort) => SWRInfiniteKeyLoader = (isUser, sort) => ( - index, - prev: ILabNode[] -) => { +const getKey: (isUser: boolean, sort?: LabNodesSort, search?: string) => SWRInfiniteKeyLoader = ( + isUser, + sort, + search +) => (index, prev: ILabNode[]) => { if (!isUser) return null; if (index > 0 && (!prev?.length || prev.length < 20)) return null; @@ -20,6 +22,7 @@ const getKey: (isUser: boolean, sort?: LabNodesSort) => SWRInfiniteKeyLoader = ( limit: 20, offset: index * 20, sort: sort || LabNodesSort.New, + search: search || '', }; return JSON.stringify(props); @@ -33,12 +36,13 @@ const parseKey = (key: string): GetLabNodesRequest => { } }; -export const useGetLabNodes = (sort?: LabNodesSort) => { +export const useGetLabNodes = (sort?: LabNodesSort, search?: string) => { const labStore = useLabStore(); const { isUser } = useAuth(); + const searchDebounced = useDebouncedValue(search); const { data, isValidating, size, setSize, mutate } = useSWRInfinite( - getKey(isUser, sort), + getKey(isUser, sort, searchDebounced), async (key: string) => { const result = await getLabNodes(parseKey(key)); return result.nodes; @@ -46,6 +50,7 @@ export const useGetLabNodes = (sort?: LabNodesSort) => { { fallbackData: [labStore.nodes], onSuccess: data => labStore.setNodes(flatten(data)), + dedupingInterval: 300, } ); diff --git a/src/hooks/lab/useLab.ts b/src/hooks/lab/useLab.ts index 84d9ed93..a5bad038 100644 --- a/src/hooks/lab/useLab.ts +++ b/src/hooks/lab/useLab.ts @@ -6,8 +6,9 @@ import { LabNodesSort } from '~/types/lab'; export const useLab = () => { const [sort, setSort] = useState(LabNodesSort.New); + const [search, setSearch] = useState(''); - const { nodes, isLoading, loadMore, hasMore } = useGetLabNodes(sort); + const { nodes, isLoading, loadMore, hasMore } = useGetLabNodes(sort, search); const { tags, heroes, updates, isLoading: isLoadingStats } = useGetLabStats(); return { @@ -21,5 +22,7 @@ export const useLab = () => { updates, sort, setSort, + search, + setSearch, }; }; diff --git a/src/types/index.ts b/src/types/index.ts index 602fdd67..19675458 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -15,17 +15,6 @@ export interface ITag { readonly updated_at: string; } -export type IInputTextProps = DetailedHTMLProps< - InputHTMLAttributes, - HTMLInputElement -> & { - handler?: (value: string) => void; - title?: string; - error?: string; - suffix?: ReactElement; - prefix?: ReactElement; -}; - export type IIcon = string; export type ValueOf = T[keyof T]; diff --git a/src/types/lab/index.ts b/src/types/lab/index.ts index 758d6f6b..2f240760 100644 --- a/src/types/lab/index.ts +++ b/src/types/lab/index.ts @@ -30,6 +30,7 @@ export type GetLabNodesRequest = { offset?: number; after?: string; sort?: LabNodesSort; + search?: string; }; export interface ILabNode { diff --git a/src/utils/context/LabContextProvider.tsx b/src/utils/context/LabContextProvider.tsx index 8c9163cc..20d3e913 100644 --- a/src/utils/context/LabContextProvider.tsx +++ b/src/utils/context/LabContextProvider.tsx @@ -15,6 +15,8 @@ export interface LabContextProps { updates: IFlowNode[]; sort: LabNodesSort; setSort: (sort: LabNodesSort) => void; + search: string; + setSearch: (val: string) => void; } const defaultValues: LabContextProps = { @@ -28,6 +30,8 @@ const defaultValues: LabContextProps = { updates: [], sort: LabNodesSort.New, setSort: () => {}, + search: '', + setSearch: () => {}, }; const LabContext = createContext(defaultValues); diff --git a/src/utils/providers/LabProvider.tsx b/src/utils/providers/LabProvider.tsx index e722c9fd..c3701be3 100644 --- a/src/utils/providers/LabProvider.tsx +++ b/src/utils/providers/LabProvider.tsx @@ -17,6 +17,8 @@ const LabProvider: FC = ({ children }) => { updates, sort, setSort, + search, + setSearch, } = useLab(); return ( @@ -31,6 +33,8 @@ const LabProvider: FC = ({ children }) => { updates={updates} sort={sort} setSort={setSort} + search={search} + setSearch={setSearch} > {children}