From 704ad687b994315e1ce6603412a468d1e905953b Mon Sep 17 00:00:00 2001 From: Dmitriy Semenov Date: Mon, 25 Mar 2024 17:46:31 +0300 Subject: [PATCH] =?UTF-8?q?refactor:=20=D1=81=D1=82=D1=80=D0=B0=D0=BD?= =?UTF-8?q?=D0=B8=D1=86=D0=B0=20=D0=BF=D0=BE=D0=B8=D1=81=D0=BA=D0=B0=20?= =?UTF-8?q?=D0=B4=D0=BB=D1=8F=20=D0=B4=D0=B5=D0=BA=D1=81=D1=82=D0=BE=D0=BF?= =?UTF-8?q?=D0=B0=20(#100)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/providers/router/Layout.tsx | 6 +- src/app/providers/router/router.paths.ts | 1 + src/entities/project/filter/ui/Filter.tsx | 206 ++++++++++++------ src/features/filter/FilterSpecialization.tsx | 11 +- .../filter/FilterSpecializationModal.tsx | 180 ++++++++------- src/features/project/filter/FilterAuth.tsx | 22 +- src/features/project/filter/FilterUser.tsx | 1 + src/pages/search/ui/SearchPage.desktop.tsx | 128 +++++++++-- src/pages/search/ui/SearchPage.tsx | 32 +-- src/shared/hooks/index.ts | 1 + src/shared/hooks/useInfinityScroll.tsx | 38 ++++ src/widgets/project-card/ui/ProjectCard.tsx | 1 + 12 files changed, 428 insertions(+), 199 deletions(-) create mode 100644 src/shared/hooks/useInfinityScroll.tsx diff --git a/src/app/providers/router/Layout.tsx b/src/app/providers/router/Layout.tsx index a56d5a80..50e9fc7c 100644 --- a/src/app/providers/router/Layout.tsx +++ b/src/app/providers/router/Layout.tsx @@ -47,9 +47,9 @@ export const Layout = () => { ) : ( - + {/* */} {/* ВОТ ТУТ ДЛЯ ДЕСКТОПА РАСКОММЕНТИРУЙ, А BlankPage ЗАКОММЕНТЬ */} - {/* {!isNotFoundPage && } + {!isNotFoundPage && } { flexDirection="column" > - */} + )} diff --git a/src/app/providers/router/router.paths.ts b/src/app/providers/router/router.paths.ts index 99db0a43..b921655a 100644 --- a/src/app/providers/router/router.paths.ts +++ b/src/app/providers/router/router.paths.ts @@ -80,6 +80,7 @@ export const routerPaths = [ path: PATHS.search, view: { base: SearchPage, + desktop: SearchPageDesktop, }, }, { diff --git a/src/entities/project/filter/ui/Filter.tsx b/src/entities/project/filter/ui/Filter.tsx index d6b75b6a..12414587 100644 --- a/src/entities/project/filter/ui/Filter.tsx +++ b/src/entities/project/filter/ui/Filter.tsx @@ -14,6 +14,9 @@ import { Container, Stack, Input, + Drawer, + DrawerOverlay, + DrawerContent, } from '@chakra-ui/react'; import { useEffect } from 'react'; import { IoOptions } from 'react-icons/io5'; @@ -74,90 +77,159 @@ export const Filter = ({ } /> - - - - - - - - + {!isMobile ? ( + + + + + + Фильтры + + + + + + Специализация + + { + updateFilter({ specs: values }); + }} + /> + + + + + Профессиональные навыки + + { + updateFilter({ skills: values }); + }} + /> + + + + + + - - - - - - Специализация - - { - updateFilter({ specs: values }); + + + + ) : ( + + + + + + + + + Фильтры + + + + + + + - Профессиональные навыки + Специализация - { - updateFilter({ skills: values }); + { + updateFilter({ specs: values }); }} /> - - - - Дата начала проекта - { - const value = e.target.value; - updateFilter({ date: value ? stringToServerDate(value) : value }); - }} - /> - - - - - - - - + + + + + Профессиональные навыки + + { + updateFilter({ skills: values }); + }} + /> + + + + Дата начала проекта + { + const value = e.target.value; + updateFilter({ date: value ? stringToServerDate(value) : value }); + }} + /> + + + + + + + + + )} ); }; diff --git a/src/features/filter/FilterSpecialization.tsx b/src/features/filter/FilterSpecialization.tsx index cc2ea636..b061aa05 100644 --- a/src/features/filter/FilterSpecialization.tsx +++ b/src/features/filter/FilterSpecialization.tsx @@ -7,6 +7,7 @@ import { Tag, TagLabel, IconButton, + useDisclosure, } from '@chakra-ui/react'; import { useState } from 'react'; @@ -27,7 +28,7 @@ export const FilterSpecialization = ({ setUserSpecs, doubleChecked, }: FilterSpecializationProps) => { - const [specFilter, setSpecFilter] = useState(false); + const { isOpen, onOpen, onClose } = useDisclosure(); const [searchText, setSearchText] = useState(''); const { data: specGroup } = useGetSpecsGroups(); @@ -50,7 +51,7 @@ export const FilterSpecialization = ({ readOnly placeholder="Например, Фронтенд разработчик" onClick={() => { - setSpecFilter(true); + onOpen(); }} /> @@ -89,11 +90,11 @@ export const FilterSpecialization = ({ { setUserSpecs([]); }} diff --git a/src/features/filter/FilterSpecializationModal.tsx b/src/features/filter/FilterSpecializationModal.tsx index 7bae2ea6..cb8433ee 100644 --- a/src/features/filter/FilterSpecializationModal.tsx +++ b/src/features/filter/FilterSpecializationModal.tsx @@ -3,6 +3,9 @@ import { Box, Button, Container, + Drawer, + DrawerContent, + DrawerOverlay, Flex, Heading, Icon, @@ -15,13 +18,14 @@ import { useEffect, useRef, useState } from 'react'; import { FiChevronLeft } from 'react-icons/fi'; import type { GetSpecGroupsDataResponse, GetSpecsDataResponse } from '~/shared/api'; +import { useIsMobile } from '~/shared/hooks'; import { SearchInput } from '~/shared/ui/SearchInput'; import { GroupItem } from './GroupItem'; interface FilterSpecializationModalProps { - isVisible: boolean; - changeVisible: (status: boolean) => void; + isOpen: boolean; + onClose: () => void; state?: GetSpecsDataResponse; stateGroup?: GetSpecGroupsDataResponse; resetSpec: () => void; @@ -35,8 +39,8 @@ interface FilterSpecializationModalProps { export const FilterSpecializationModal = (props: FilterSpecializationModalProps) => { const { - isVisible, - changeVisible, + isOpen, + onClose, state, stateGroup, resetSpec, @@ -53,6 +57,7 @@ export const FilterSpecializationModal = (props: FilterSpecializationModalProps) const [filteredGroupsState, setFilteredGroupsState] = useState([]); const [filteredState, setFilteredState] = useState([]); + const isMobile = useIsMobile(); useEffect(() => { if (state && stateGroup) { @@ -75,88 +80,107 @@ export const FilterSpecializationModal = (props: FilterSpecializationModalProps) setFilteredGroupsState([...activeSections, ...inactiveSections]); setFilteredState([...activeCheckbox, ...inactiveCheckbox]); } - }, [isVisible, state]); + }, [isOpen, state]); - return ( + const getMainFilterSpecsBody = () => ( + <> + + + + + { + onClose(); + }} + variant="ghost" + aria-label="Close" + minW="fit-content" + mr={2} + icon={} + /> + + Специализация + + + + + { + setSearchText(value); + }} + value={searchText} + /> + + + + {filteredGroupsState.map((group) => ( + + ))} + + + + {filteredGroupsState.length > 0 && ( + + )} + + + ); + + return isMobile ? ( { - changeVisible(false); + onClose(); setSearchText(''); }} size="full" - isOpen={isVisible} + isOpen={isOpen} > - - - - - - { - changeVisible(false); - }} - variant="ghost" - aria-label="Close" - minW="fit-content" - mr={2} - icon={} - /> - - Специализация - - - - - { - setSearchText(value); - }} - value={searchText} - /> - - - - {filteredGroupsState.map((group) => ( - - ))} - - - - {filteredGroupsState.length > 0 && ( - - )} - - + {getMainFilterSpecsBody()} + ) : ( + { + onClose(); + setSearchText(''); + }} + placement="right" + isOpen={isOpen} + size="sm" + > + + + {getMainFilterSpecsBody()} + + ); }; diff --git a/src/features/project/filter/FilterAuth.tsx b/src/features/project/filter/FilterAuth.tsx index b7f4e724..9875dcd3 100644 --- a/src/features/project/filter/FilterAuth.tsx +++ b/src/features/project/filter/FilterAuth.tsx @@ -1,8 +1,10 @@ +import { IconButton, Spinner } from '@chakra-ui/react'; import React from 'react'; import { Filter } from '~/entities/project'; import { useGetProfile } from '~/entities/user'; +import { useIsMobile } from '~/shared/hooks'; import type { FilterSpecOptions } from '~/shared/types'; interface FilterAuthProps { @@ -18,6 +20,7 @@ export const FilterAuth = ({ FilterSpec, }: FilterAuthProps & FilterSpecOptions) => { const { isLoading: isLoadingUser, data: user } = useGetProfile(userId); + const isMobile = useIsMobile(); return !isLoadingUser ? ( - ) : null; + ) : ( + + {/* + */} + + + } + /> + ); }; diff --git a/src/features/project/filter/FilterUser.tsx b/src/features/project/filter/FilterUser.tsx index f9083a39..351d8e14 100644 --- a/src/features/project/filter/FilterUser.tsx +++ b/src/features/project/filter/FilterUser.tsx @@ -15,6 +15,7 @@ export const FilterUser = ({ totalItems, FilterSpec, }: FilterUserProps & FilterSpecOptions) => { + console.log(userId); return !userId ? ( ) : ( diff --git a/src/pages/search/ui/SearchPage.desktop.tsx b/src/pages/search/ui/SearchPage.desktop.tsx index 7d638d76..c834fa9f 100644 --- a/src/pages/search/ui/SearchPage.desktop.tsx +++ b/src/pages/search/ui/SearchPage.desktop.tsx @@ -1,17 +1,61 @@ -import { Flex, SimpleGrid, Heading } from '@chakra-ui/react'; -import { useState } from 'react'; +/* eslint-disable @typescript-eslint/no-misused-promises */ +import { Flex, SimpleGrid, Skeleton, Box } from '@chakra-ui/react'; +import { useQuery } from '@tanstack/react-query'; +import React, { useRef, useState } from 'react'; +import { Link, generatePath } from 'react-router-dom'; import { ProjectCard } from '~/widgets/project-card'; -import { SearchProject } from '~/features/project'; +import { FilterSpecialization } from '~/features/filter'; +import { FilterUser, SearchProject } from '~/features/project'; -import { Filter } from '~/entities/project'; +import { DummyNotFound } from '~/entities/dummy'; +import { useFilterStore } from '~/entities/project'; +import { useGetSpecs } from '~/entities/storage'; +import { useApi, useAuth, useInfinityScroll } from '~/shared/hooks'; +import { PATHS } from '~/shared/lib/router'; import { STag } from '~/shared/ui/STag'; +import { useGetAllPositions } from '../api/useGetAllPositions'; + export const SearchPageDesktop = () => { + const user = useAuth(); + const { storageApi } = useApi(); + const targetRef = useRef(null); + const [searchText, setSearchText] = useState(''); + const { filter } = useFilterStore(); + + const { + data: positions, + isLoading, + fetchNextPage, + isFetchingNextPage, + hasNextPage, + } = useGetAllPositions({ + date: filter.date, + skills: filter.skills, + specs: filter.specs, + searchText, + }); + + const { data: allSpecs } = useGetSpecs(); + + const { data: allSkills } = useQuery({ + queryKey: ['skills'], + queryFn: () => storageApi.getSkills({ per_page: 2000 }), + staleTime: Infinity, + }); + + useInfinityScroll({ + hasNextPage, + targetRef, + isFetchingNextPage, + fetchNextPage, + }); + const handleSumbit = (value: string) => { setSearchText(value); }; @@ -20,29 +64,69 @@ export const SearchPageDesktop = () => { <> - + {/* Поиск - + */} - {/* */} + - - {/* {data.map((project) => { - return ( - - - - ); - })} */} - + {isLoading || !positions ? ( + + + + + + + ) : ( + <> + {!positions.pages[0].total_items ? ( + + ) : ( + + {positions.pages.map((group, i) => ( + + {group.data.map((position) => { + return ( + + + id === position.specialization_id) + .map(({ name }) => (name ? name : ''))} + tags={allSkills + ?.filter(({ value }) => position.skills.includes(value)) + .map(({ label }) => label)} + /> + + + ); + })} + + ))} + + )} + {isFetchingNextPage ? ( + + + + + + + ) : ( + + )} + + )} ); diff --git a/src/pages/search/ui/SearchPage.tsx b/src/pages/search/ui/SearchPage.tsx index f3530dbe..3f27f8f1 100644 --- a/src/pages/search/ui/SearchPage.tsx +++ b/src/pages/search/ui/SearchPage.tsx @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-misused-promises */ import { Flex, SimpleGrid, @@ -8,7 +9,7 @@ import { Box, } from '@chakra-ui/react'; import { useQuery } from '@tanstack/react-query'; -import React, { useEffect, useRef, useState } from 'react'; +import React, { useRef, useState } from 'react'; import { Link, generatePath } from 'react-router-dom'; import { ProjectCard } from '~/widgets/project-card'; @@ -21,7 +22,7 @@ import { useFilterStore } from '~/entities/project'; import { useGetSpecs } from '~/entities/storage'; import { Avatar, NotAuthAvatar } from '~/entities/user'; -import { useApi, useAuth, useLayoutRefs } from '~/shared/hooks'; +import { useApi, useAuth, useInfinityScroll, useLayoutRefs } from '~/shared/hooks'; import { PATHS } from '~/shared/lib/router'; import { STag } from '~/shared/ui/STag'; @@ -47,7 +48,6 @@ export const SearchPage = () => { date: filter.date, skills: filter.skills, specs: filter.specs, - searchText, }); @@ -59,26 +59,12 @@ export const SearchPage = () => { staleTime: Infinity, }); - useEffect(() => { - const options = { - root: null, - rootMargin: '0px', - threshold: 1.0, - }; - - const observer = new IntersectionObserver((entries: IntersectionObserverEntry[]) => { - const [entry] = entries; - if (entry.isIntersecting) { - if (hasNextPage) fetchNextPage(); - } - }, options); - - if (targetRef.current) observer.observe(targetRef.current); - - return () => { - if (targetRef.current) observer.unobserve(targetRef.current); - }; - }, [positions]); + useInfinityScroll({ + hasNextPage, + targetRef, + isFetchingNextPage, + fetchNextPage, + }); const handleSumbit = (value: string) => { setSearchText(value); diff --git a/src/shared/hooks/index.ts b/src/shared/hooks/index.ts index 2cb460d0..abdb059a 100644 --- a/src/shared/hooks/index.ts +++ b/src/shared/hooks/index.ts @@ -4,3 +4,4 @@ export * from './useHorizontalScroll'; export * from './useApi'; export * from './useAuth'; export * from './useWindowSizes'; +export * from './useInfinityScroll'; diff --git a/src/shared/hooks/useInfinityScroll.tsx b/src/shared/hooks/useInfinityScroll.tsx new file mode 100644 index 00000000..c4cbebe0 --- /dev/null +++ b/src/shared/hooks/useInfinityScroll.tsx @@ -0,0 +1,38 @@ +import type { RefObject } from 'react'; +import { useEffect } from 'react'; + +interface useInfinityScrollProps { + hasNextPage?: boolean; + fetchNextPage: () => void; + targetRef: RefObject; + isFetchingNextPage: boolean; +} + +export function useInfinityScroll({ + hasNextPage, + fetchNextPage, + targetRef, + isFetchingNextPage, +}: useInfinityScrollProps) { + useEffect(() => { + const options = { + root: null, + rootMargin: '0px', + threshold: 1.0, + }; + + const observer = new IntersectionObserver((entries: IntersectionObserverEntry[]) => { + const [entry] = entries; + if (entry.isIntersecting) { + if (hasNextPage) fetchNextPage(); + } + }, options); + + if (targetRef.current) observer.observe(targetRef.current); + + return () => { + if (targetRef.current) observer.unobserve(targetRef.current); + }; + }, [hasNextPage, isFetchingNextPage]); + return; +} diff --git a/src/widgets/project-card/ui/ProjectCard.tsx b/src/widgets/project-card/ui/ProjectCard.tsx index b1aabed1..123f5381 100644 --- a/src/widgets/project-card/ui/ProjectCard.tsx +++ b/src/widgets/project-card/ui/ProjectCard.tsx @@ -27,6 +27,7 @@ export const ProjectCard = ({ info, children }: ProjectCardProps) => { _active={{ boxShadow: '2xl' }} boxShadow="none" alignContent="center" + height="100%" >