Skip to content
This repository has been archived by the owner on Apr 14, 2024. It is now read-only.

Commit

Permalink
feat: фильтрация на странице поиска (#64)
Browse files Browse the repository at this point in the history
Co-authored-by: Ruslan Kutliakhmetov <[email protected]>
  • Loading branch information
DieWerkself and ko22009 authored Nov 19, 2023
1 parent 8f9896f commit fba891c
Show file tree
Hide file tree
Showing 24 changed files with 361 additions and 238 deletions.
1 change: 1 addition & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ module.exports = {
'plugin:@tanstack/eslint-plugin-query/recommended',
],
rules: {
'@typescript-eslint/no-floating-promises': 'off',
'@typescript-eslint/no-unused-vars': [
'warn',
{
Expand Down
2 changes: 2 additions & 0 deletions src/entities/project/filter/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './ui';
export * from './model';
41 changes: 41 additions & 0 deletions src/entities/project/filter/model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { create } from 'zustand';
import { persist } from 'zustand/middleware';

import { SelectOptions } from '~/shared/types';

export interface FilterType {
specs: string[];
skills: SelectOptions[];
date: string;
}

export const defaultFilter: FilterType = {
specs: [],
skills: [],
date: '',
};

interface FilterStore {
filter: FilterType;
updateFilter: (filter: Partial<FilterType>) => void;
removeFilter: () => void;
}

export const useFilterStore = create(
persist<FilterStore>(
(set) => ({
filter: defaultFilter,
updateFilter: (value: Partial<FilterType>) => {
set((state) => ({
filter: { ...state.filter, ...value },
}));
},
removeFilter: () => {
set(() => ({ filter: defaultFilter }));
},
}),
{
name: 'filter',
},
),
);
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
/* eslint-disable @typescript-eslint/no-floating-promises */
import {
Button,
IconButton,
Expand All @@ -16,23 +15,24 @@ import {
Stack,
Input,
} from '@chakra-ui/react';
import { useQueryClient } from '@tanstack/react-query';
import { useState } from 'react';
import { IoOptions } from 'react-icons/io5';

import { useIsMobile } from '~/shared/hooks';
import { stringToServerDate } from '~/shared/lib/stringToServerDate';
import { Counter } from '~/shared/ui/Counter';
import { FilterSpecialization } from '~/shared/ui/FilterSpecialization';
import { SearchSelect } from '~/shared/ui/SearchSelect';

export const Filter = () => {
const queryClient = useQueryClient();
import { useFilterStore } from '../model';

interface FilterProps {
totalItems?: number | null;
}

export const Filter = ({ totalItems = 0 }: FilterProps) => {
const { isOpen, onOpen, onClose } = useDisclosure();
const [userSpecs, setUserSpecs] = useState<string[]>([]);
const [selectedItems, setSelectedItems] = useState<{ value: string; label: string }[]>(
[],
);

const { filter, removeFilter, updateFilter } = useFilterStore();
const isMobile = useIsMobile();

return (
Expand All @@ -49,7 +49,11 @@ export const Filter = () => {
<>
<Text hidden={isMobile}>Все фильтры</Text>
<Icon as={IoOptions} fontSize="2xl" />
<Counter count={5} float borderBg="bg" />
<Counter
count={filter.skills.length + filter.specs.length + (filter.date ? 1 : 0)}
float
borderBg="bg"
/>
</>
}
></IconButton>
Expand All @@ -71,9 +75,7 @@ export const Filter = () => {
fontWeight="500"
colorScheme="purple"
onClick={() => {
setUserSpecs([]);
setSelectedItems([]);
queryClient.invalidateQueries(['skills']);
removeFilter();
}}
>
Сбросить
Expand All @@ -85,16 +87,24 @@ export const Filter = () => {
<Heading variant="h2" mb={3}>
Специализация
</Heading>
<FilterSpecialization userSpecs={userSpecs} setUserSpecs={setUserSpecs} />
<FilterSpecialization
userSpecs={filter.specs}
setUserSpecs={(values) => {
updateFilter({ specs: values });
}}
/>
</Box>
<Box>
<Stack gap={1} mb={4}>
<Heading variant="h2" mb={3}>
Профессиональные навыки
</Heading>
<SearchSelect
selectedItems={selectedItems}
setSelectedItems={setSelectedItems}
isSearchFilter={true}
selectedItems={filter.skills}
setSelectedItems={(values) => {
updateFilter({ skills: values });
}}
/>
</Stack>
</Box>
Expand All @@ -108,13 +118,18 @@ export const Filter = () => {
color="gray.500"
placeholder="Выберите дату"
type="date"
value={filter.date.split('T', 1)[0]}
onChange={(e) => {
const value = e.target.value;
updateFilter({ date: value ? stringToServerDate(value) : value });
}}
/>
</Box>
</Stack>
</Container>
<Container maxW="md" py={6} bg="bg" position="sticky" bottom="0" mt="auto">
<Button fontSize="sm" fontWeight="600" w="full">
Показать 43 проекта
<Button fontSize="sm" fontWeight="600" w="full" onClick={onClose}>
Показать {totalItems ?? 0} проектов
</Button>
</Container>
</ModalContent>
Expand Down
1 change: 1 addition & 0 deletions src/entities/project/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ export * from './contacts';
export * from './avatars-group';
export * from './api';
export * from './info';
export * from './filter';
1 change: 0 additions & 1 deletion src/features/project/filter/index.ts

This file was deleted.

1 change: 0 additions & 1 deletion src/features/project/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
export * from './filter';
export * from './search';
export * from './add';
11 changes: 6 additions & 5 deletions src/features/project/search/SearchProject.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { SearchInput } from '~/shared/ui/SearchInput';

export const SearchProject = () => {
const handleSumbit = (value: string) => {
console.log(value);
};
return <SearchInput onChange={handleSumbit} placeholder="Найти проект" />;
interface SearchProjectProps {
onChange: (value: string) => void;
}

export const SearchProject = ({ onChange }: SearchProjectProps) => {
return <SearchInput onChange={onChange} placeholder="Найти проект" />;
};
169 changes: 169 additions & 0 deletions src/pages/projects/ui/ProjectsBase.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
import {
Flex,
SimpleGrid,
Heading,
Container,
Box,
Skeleton,
Image,
Text,
Button,
} from '@chakra-ui/react';
import { QueryFunctionContext, QueryKey, useInfiniteQuery } from '@tanstack/react-query';
import React, { useEffect, useRef, useState } from 'react';
import { Link, generatePath, useNavigate } from 'react-router-dom';

import { ProjectCard } from '~/widgets/project-card';

import { AddProject } from '~/features/project';

import { AvatarsGroup } from '~/entities/project';

import { useApi } from '~/shared/hooks';
import { PATHS } from '~/shared/lib/router';
import { STag } from '~/shared/ui/STag';

import NotAuth from './NotAuth.svg';

interface ProjectPageProps {
userId: string;
}

export const ProjectsBase = ({ userId }: ProjectPageProps) => {
const targetRef = useRef<HTMLDivElement>(null);
const { projectsApi } = useApi();
const [isEmptyData, setEmptyData] = useState<boolean>(false);
const navigate = useNavigate();
const dummyAvatars = [
{ firstName: 'Alex', lastName: 'Gordon', img: 'https://bit.ly/ryan-florence' },
{ firstName: 'Игорь', lastName: 'Крутой', img: 'https://bit.ly/sage-adebayo' },
{ firstName: 'Джек', lastName: 'Воробей', img: 'https://bit.ly/kent-c-dodds' },
{ firstName: 'Кларк', lastName: 'Кент', img: 'https://bit.ly/prosper-baba' },
{ firstName: 'Джеймс', lastName: 'Бонд', img: 'https://bit.ly/code-beast' },
{ firstName: 'Бернд', lastName: 'Шнайдер', img: 'https://bit.ly/dan-abramov' },
];

const { data, isLoading, fetchNextPage, isFetchingNextPage } = useInfiniteQuery({
queryKey: ['getAllProjects', userId],
queryFn: ({ pageParam = 1 }: QueryFunctionContext<QueryKey, number>) =>
projectsApi.getAllProjects({ page: pageParam, owner_id: userId }),
// getNextPageParam: (lastPage) => lastPage.page + 1,
onSuccess: (response) => {
setEmptyData(!response.pages[0].data.length);
},
staleTime: 5000,
});

useEffect(() => {
const options = {
root: null,
rootMargin: '0px',
threshold: 1.0,
};

const observer = new IntersectionObserver((entries: IntersectionObserverEntry[]) => {
const [entry] = entries;
if (entry.isIntersecting) {
// eslint-disable-next-line @typescript-eslint/no-floating-promises
fetchNextPage();
}
}, options);

if (targetRef.current) observer.observe(targetRef.current);

return () => {
if (targetRef.current) observer.unobserve(targetRef.current);
};
}, [data]);

return (
<Container maxW="md" mb={4}>
<Flex justifyContent="space-between" alignItems="center" my={4} h={42}>
<Heading variant="h1" as="h1">
Проекты
</Heading>
<Flex gap={4} alignItems="baseline">
<AddProject />
</Flex>
</Flex>

{isLoading ? (
<>
<Skeleton height="200px" borderRadius="2xl" mb={3} />
<Skeleton height="200px" borderRadius="2xl" mb={3} />
<Skeleton height="200px" borderRadius="2xl" mb={3} />
</>
) : (
<SimpleGrid gap={4}>
{isEmptyData ? (
<Flex
bg="white"
my={6}
borderRadius="2xl"
p={5}
direction="column"
alignItems="center"
gap={5}
>
<Image src={NotAuth} />
<Text fontSize="md" fontWeight="medium" mt={1}>
Нет проектов
</Text>
<Text color="gray.700" textAlign="center">
Здесь будут отображаться все ваши проекты в качестве участника и
организатора
</Text>
<Button
type="button"
onClick={() => {
navigate(PATHS.addProject);
}}
fontSize="sm"
fontWeight="600"
w="full"
>
Создать свой проект
</Button>
</Flex>
) : (
<>
{data?.pages.map((group, i) => (
<React.Fragment key={i}>
{group.data.map((project) => {
return (
<Link
key={project.id}
to={generatePath(PATHS.project, { id: project.id })}
>
<ProjectCard
status={project.status}
title={project.name}
date={project.deadline}
description={project.description}
>
<Flex justifyContent="space-between" alignItems="center">
<STag mainTags={['Организатор']} />
<AvatarsGroup avatars={dummyAvatars} />
</Flex>
</ProjectCard>
</Link>
);
})}
</React.Fragment>
))}
</>
)}

{isFetchingNextPage && (
<>
<Skeleton height="200px" borderRadius="2xl" mb={3} />
<Skeleton height="200px" borderRadius="2xl" mb={3} />
<Skeleton height="200px" borderRadius="2xl" mb={3} />
</>
)}
<Box ref={targetRef}></Box>
</SimpleGrid>
)}
</Container>
);
};
Loading

0 comments on commit fba891c

Please sign in to comment.