Skip to content

Commit

Permalink
Merge pull request #3 from shampsdev/find-filters
Browse files Browse the repository at this point in the history
Find filters
  • Loading branch information
AlexDyakonov authored Dec 1, 2024
2 parents 64f2e88 + aabc218 commit cdfe6e3
Show file tree
Hide file tree
Showing 5 changed files with 419 additions and 27 deletions.
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

0 comments on commit cdfe6e3

Please sign in to comment.