Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Find filters #3

Merged
merged 2 commits into from
Dec 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"@radix-ui/react-dialog": "^1.1.2",
"@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-label": "^2.1.0",
"@radix-ui/react-select": "^2.1.2",
"@radix-ui/react-slot": "^1.1.0",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
Expand Down
156 changes: 156 additions & 0 deletions src/components/ui/select.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
import * as React from "react"
import * as SelectPrimitive from "@radix-ui/react-select"
import { cn } from "@/lib/utils"
import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "@radix-ui/react-icons"

const Select = SelectPrimitive.Root

const SelectGroup = SelectPrimitive.Group

const SelectValue = SelectPrimitive.Value

const SelectTrigger = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
>(({ className, children, ...props }, ref) => (
<SelectPrimitive.Trigger
ref={ref}
className={cn(
"flex h-9 w-full items-center justify-between whitespace-nowrap rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
className
)}
{...props}
>
{children}
<SelectPrimitive.Icon asChild>
<ChevronDownIcon className="h-4 w-4 opacity-50" />
</SelectPrimitive.Icon>
</SelectPrimitive.Trigger>
))
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName

const SelectScrollUpButton = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>
>(({ className, ...props }, ref) => (
<SelectPrimitive.ScrollUpButton
ref={ref}
className={cn(
"flex cursor-default items-center justify-center py-1",
className
)}
{...props}
>
<ChevronUpIcon className="h-4 w-4" />
</SelectPrimitive.ScrollUpButton>
))
SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName

const SelectScrollDownButton = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>
>(({ className, ...props }, ref) => (
<SelectPrimitive.ScrollDownButton
ref={ref}
className={cn(
"flex cursor-default items-center justify-center py-1",
className
)}
{...props}
>
<ChevronDownIcon className="h-4 w-4" />
</SelectPrimitive.ScrollDownButton>
))
SelectScrollDownButton.displayName =
SelectPrimitive.ScrollDownButton.displayName

const SelectContent = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
>(({ className, children, position = "popper", ...props }, ref) => (
<SelectPrimitive.Portal>
<SelectPrimitive.Content
ref={ref}
className={cn(
"relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
position === "popper" &&
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
className
)}
position={position}
{...props}
>
<SelectScrollUpButton />
<SelectPrimitive.Viewport
className={cn(
"p-1",
position === "popper" &&
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"
)}
>
{children}
</SelectPrimitive.Viewport>
<SelectScrollDownButton />
</SelectPrimitive.Content>
</SelectPrimitive.Portal>
))
SelectContent.displayName = SelectPrimitive.Content.displayName

const SelectLabel = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
>(({ className, ...props }, ref) => (
<SelectPrimitive.Label
ref={ref}
className={cn("px-2 py-1.5 text-sm font-semibold", className)}
{...props}
/>
))
SelectLabel.displayName = SelectPrimitive.Label.displayName

const SelectItem = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
>(({ className, children, ...props }, ref) => (
<SelectPrimitive.Item
ref={ref}
className={cn(
"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className
)}
{...props}
>
<span className="absolute right-2 flex h-3.5 w-3.5 items-center justify-center">
<SelectPrimitive.ItemIndicator>
<CheckIcon className="h-4 w-4" />
</SelectPrimitive.ItemIndicator>
</span>
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
</SelectPrimitive.Item>
))
SelectItem.displayName = SelectPrimitive.Item.displayName

const SelectSeparator = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
>(({ className, ...props }, ref) => (
<SelectPrimitive.Separator
ref={ref}
className={cn("-mx-1 my-1 h-px bg-muted", className)}
{...props}
/>
))
SelectSeparator.displayName = SelectPrimitive.Separator.displayName

export {
Select,
SelectGroup,
SelectValue,
SelectTrigger,
SelectContent,
SelectLabel,
SelectItem,
SelectSeparator,
SelectScrollUpButton,
SelectScrollDownButton,
}
118 changes: 91 additions & 27 deletions src/pages/find.page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,38 @@ import { getAllPeople } from '@/shared/api/people.api';
import { searchPeople } from '@/shared/api/people.api';
import { Person } from '@/shared/interfaces/person.interface';
import { useSelectedStore } from '@/shared/store/selected.store';
import { getAllDepartments, getAllDivisions } from '@/shared/api/additional.api';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; // Импорт компонентов из ShadCN UI

export const FindPage = () => {
const [searchTerm, setSearchTerm] = useState('');
const [people, setPeople] = useState<Person[]>([]);
const [allPeople, setAllPeople] = useState<Person[]>([]);
const [departments, setDepartments] = useState<string[]>([]);
const [divisions, setDivisions] = useState<string[]>([]);
const [selectedDepartment, setSelectedDepartment] = useState<string>('');
const [selectedDivision, setSelectedDivision] = useState<string>('');
const [loading, setLoading] = useState<boolean>(false);
const [error, setError] = useState<string>('');

const { setSelected } = useSelectedStore();

useEffect(() => {
const fetchDepartmentsAndDivisions = async () => {
try {
const fetchedDepartments = await getAllDepartments();
const fetchedDivisions = await getAllDivisions();
setDepartments(fetchedDepartments);
setDivisions(fetchedDivisions);
} catch (err) {
setError('Не удалось загрузить фильтры');
console.error(err);
}
};

fetchDepartmentsAndDivisions();
}, []);

useEffect(() => {
const fetchAllPeople = async () => {
setLoading(true);
Expand All @@ -36,14 +58,9 @@ export const FindPage = () => {
};

const handleSearchClick = async () => {
if (searchTerm.trim() === '') {
setPeople(allPeople);
return;
}

setLoading(true);
try {
const results = await searchPeople(searchTerm);
const results = searchTerm.trim() === '' ? allPeople : await searchPeople(searchTerm);
setPeople(results);
} catch (err) {
setError('Не удалось выполнить поиск');
Expand All @@ -59,54 +76,101 @@ export const FindPage = () => {
}
};

const handleFilterChange = () => {
const filteredPeople = allPeople.filter((person) => {
const matchesDepartment =
selectedDepartment && selectedDepartment !== 'Все департаменты'
? person.department === selectedDepartment
: true;
const matchesDivision =
selectedDivision && selectedDivision !== 'Все подразделения'
? person.division === selectedDivision
: true;
return matchesDepartment && matchesDivision;
});
setPeople(filteredPeople);
};

useEffect(() => {
handleFilterChange();
}, [selectedDepartment, selectedDivision, allPeople]);

return (
<div className='p-5 pt-20'>
<div className='mt-10 w-full max-w-screen-xl mx-auto p-5'>
<div className='flex justify-center gap-3'>
<input
type='search'
value={searchTerm}
onChange={handleSearchChange}
onKeyDown={handleKeyDown}
placeholder='Введите запрос...'
className='w-96 p-3 border rounded-xl focus:outline-none focus:ring-2 focus:ring-blue-500'
/>
<div className="p-5 pt-20">
<div className="mt-10 w-full max-w-screen-xl mx-auto p-5">
<div className="flex justify-center gap-3">
<input
type="search"
value={searchTerm}
onChange={handleSearchChange}
onKeyDown={handleKeyDown}
placeholder="Введите запрос..."
className="w-full max-w-[700px] p-3 border rounded-xl focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
<button
onClick={handleSearchClick}
className='p-3 bg-blue-500 text-white rounded-xl hover:bg-blue-600 focus:outline-none transform active:scale-95 transition-transform duration-200'
className="p-3 bg-blue-500 text-white rounded-xl hover:bg-blue-600 focus:outline-none transform active:scale-95 transition-transform duration-200"
>
Поиск
</button>
</div>

<div className='mt-10'>
<div className="flex justify-center gap-4 mt-5">
<Select value={selectedDepartment} onValueChange={setSelectedDepartment}>
<SelectTrigger className="w-96">
<SelectValue placeholder="Все департаменты" />
</SelectTrigger>
<SelectContent>
<SelectItem value="Все департаменты">Все департаменты</SelectItem>
{departments.map((dept) => (
<SelectItem key={dept} value={dept}>
{dept}
</SelectItem>
))}
</SelectContent>
</Select>

<Select value={selectedDivision} onValueChange={setSelectedDivision}>
<SelectTrigger className="w-96">
<SelectValue placeholder="Все подразделения" />
</SelectTrigger>
<SelectContent>
<SelectItem value="Все подразделения">Все подразделения</SelectItem>
{divisions.map((div) => (
<SelectItem key={div} value={div}>
{div}
</SelectItem>
))}
</SelectContent>
</Select>
</div>

<div className="mt-10">
{loading ? (
<div>Загрузка...</div>
) : error ? (
<div>{error}</div>
) : (
<div className='grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6'>
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6">
{people.length > 0 ? (
people.map((person) => (
<div
key={person.id}
onClick={() => setSelected(person)}
className='bg-white p-4 rounded-xl border flex flex-col items-center justify-center text-center space-y-3'
className="bg-white p-4 rounded-xl border flex flex-col items-center justify-center text-center space-y-3"
>
<img
src={person.image}
alt={`${person.name} ${person.surname}`}
className='w-18 h-20 rounded-full mb-3'
className="w-18 h-20 rounded-full mb-3"
/>
<h3 className='text-lg font-semibold'>
<h3 className="text-lg font-semibold">
{person.name} {person.surname}
</h3>
<p className='text-sm text-gray-600'>{person.jobtitle}</p>
<p className="text-sm text-gray-600">{person.jobtitle}</p>

{person.division && (
<p className='text-sm text-gray-500 mt-1'>
Отдел: {person.division}
</p>
<p className="text-sm text-gray-500 mt-1">Отдел: {person.division}</p>
)}
</div>
))
Expand Down
38 changes: 38 additions & 0 deletions src/shared/api/additional.api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { API_URL } from '../constants';

export const getAllDepartments = async (): Promise<string[]> => {
try {
const response = await fetch(
`${API_URL}/persons/departments`
);

if (!response.ok) {
throw new Error('Failed to fetch departments');
}

const departments: string[] = await response.json();
return departments;
} catch (error) {
console.error('Error fetching departments:', error);
throw error;
}
};

export const getAllDivisions = async (): Promise<string[]> => {
try {
const response = await fetch(
`${API_URL}/persons/divisions`
);

if (!response.ok) {
throw new Error('Failed to fetch divisions');
}

const divisions: string[] = await response.json();
return divisions;
} catch (error) {
console.error('Error fetching divisions:', error);
throw error;
}
};

Loading