From ce112efd159fe78ed7171711f91361d66fd8c4c6 Mon Sep 17 00:00:00 2001 From: tony max Date: Tue, 30 Jun 2026 22:55:33 -0300 Subject: [PATCH] feat: implement discover filters component, hook, and selection logic for profile filtering --- .../discover/components/DiscoverFilters.tsx | 175 ++++++++++++++---- .../discover/hooks/useDiscoverFilters.ts | 5 +- .../discover/model/discover.selectors.ts | 17 +- 3 files changed, 159 insertions(+), 38 deletions(-) diff --git a/src/features/discover/components/DiscoverFilters.tsx b/src/features/discover/components/DiscoverFilters.tsx index 1bfec71..9c3012c 100644 --- a/src/features/discover/components/DiscoverFilters.tsx +++ b/src/features/discover/components/DiscoverFilters.tsx @@ -1,4 +1,6 @@ -import { Filter, Search } from "lucide-react"; +import { Filter, Search, X } from "lucide-react"; +import { motion, AnimatePresence } from "motion/react"; +import { useState, useRef, useEffect, useMemo } from "react"; import { ROLE_OPTIONS, STATUS_OPTIONS } from "../constants/discover.constants"; @@ -7,7 +9,8 @@ interface DiscoverFiltersProps { selectedRole: string; selectedStatus: string; selectedTag: string; - popularTags: string[]; + topTags: string[]; + allTags: string[]; setSearchQuery: (value: string) => void; setSelectedRole: (value: string) => void; setSelectedStatus: (value: string) => void; @@ -20,13 +23,43 @@ export function DiscoverFilters({ selectedRole, selectedStatus, selectedTag, - popularTags, + topTags, + allTags, setSearchQuery, setSelectedRole, setSelectedStatus, setSelectedTag, clearSelectedTag, }: DiscoverFiltersProps) { + const [tagSearch, setTagSearch] = useState(""); + const [isTagDropdownOpen, setIsTagDropdownOpen] = useState(false); + const dropdownRef = useRef(null); + const inputRef = useRef(null); + + const trimmedTagSearch = tagSearch.trim(); + + const visibleTags = useMemo(() => { + if (!trimmedTagSearch) return allTags; + return allTags.filter((tag) => tag.toLowerCase().includes(trimmedTagSearch.toLowerCase())); + }, [allTags, trimmedTagSearch]); + + useEffect(() => { + const handleClickOutside = (e: MouseEvent) => { + if (dropdownRef.current && !dropdownRef.current.contains(e.target as Node)) { + setIsTagDropdownOpen(false); + setTagSearch(""); + } + }; + document.addEventListener("mousedown", handleClickOutside); + return () => document.removeEventListener("mousedown", handleClickOutside); + }, []); + + const handleTagSelect = (tag: string) => { + setSelectedTag(tag === selectedTag ? "" : tag); + setTagSearch(""); + setIsTagDropdownOpen(false); + }; + return (

@@ -87,42 +120,122 @@ export function DiscoverFilters({

- {popularTags.length > 0 && ( -
- - Filtrar por Paixão: - +
+ + Filtrar por Skills: + - {popularTags.map((tag) => { - const isSelected = selectedTag === tag; + {/* ── Top 3 Tags (Quick Filters) ── */} + {topTags.map((tag) => { + const isSelected = selectedTag === tag; - return ( + return ( + + ); + })} + + {/* ── Search Bar (Autocomplete) ── */} +
+
+ + { + setTagSearch(e.target.value); + setIsTagDropdownOpen(true); + }} + onFocus={() => setIsTagDropdownOpen(true)} + placeholder="BUSCAR MAIS SKILLS..." + className="w-full pl-9 pr-8 py-2 bg-neo-bg border-2 border-neo-black font-black text-[11px] uppercase tracking-widest focus:bg-white transition-all outline-none" + /> + {tagSearch && ( - ); - })} + )} +
- {selectedTag && ( - - )} + + {isTagDropdownOpen && ( + + {visibleTags.length > 0 ? ( + visibleTags.map((tag) => { + const isSelected = selectedTag === tag; + return ( + + ); + }) + ) : ( +
+

+ Nenhuma skill encontrada. +

+
+ )} +
+ )} +
- )} + + {/* Selected tag indicator if it's not in the top 3 */} + {selectedTag && !topTags.includes(selectedTag) && ( + + )} + + {selectedTag && ( + + )} +
); } diff --git a/src/features/discover/hooks/useDiscoverFilters.ts b/src/features/discover/hooks/useDiscoverFilters.ts index 54a3251..625b857 100644 --- a/src/features/discover/hooks/useDiscoverFilters.ts +++ b/src/features/discover/hooks/useDiscoverFilters.ts @@ -17,7 +17,7 @@ export function useDiscoverFilters(profiles: Profile[]) { clearSelectedTag, } = useDiscoverFiltersStore(); - const popularTags = useMemo(() => getPopularTags(profiles), [profiles]); + const { topTags, allTags } = useMemo(() => getPopularTags(profiles), [profiles]); const filteredProfiles = useMemo( () => @@ -35,7 +35,8 @@ export function useDiscoverFilters(profiles: Profile[]) { selectedRole, selectedStatus, selectedTag, - popularTags, + topTags, + allTags, filteredProfiles, setSearchQuery, setSelectedRole, diff --git a/src/features/discover/model/discover.selectors.ts b/src/features/discover/model/discover.selectors.ts index 62bdfa5..5e26084 100644 --- a/src/features/discover/model/discover.selectors.ts +++ b/src/features/discover/model/discover.selectors.ts @@ -6,19 +6,26 @@ export function sortProfiles(profiles: Profile[], currentUserId?: string): Profi return sortByCurrentUserAndName(profiles, currentUserId); } -export function getPopularTags(profiles: Profile[]): string[] { +export function getPopularTags(profiles: Profile[]): { topTags: string[]; allTags: string[] } { const counts: Record = {}; profiles.forEach((profile) => { profile.canvas?.loves?.forEach((tag) => { counts[tag] = (counts[tag] || 0) + 1; }); + profile.canvas?.comfort?.forEach((tag) => { + counts[tag] = (counts[tag] || 0) + 1; + }); }); - return Object.entries(counts) - .sort((a, b) => b[1] - a[1]) - .slice(0, 8) - .map(([tag]) => tag); + const sortedByCount = Object.entries(counts).sort((a, b) => b[1] - a[1]); + + return { + topTags: sortedByCount.slice(0, 3).map(([tag]) => tag), + allTags: Object.keys(counts).sort((a, b) => + a.localeCompare(b, "pt-BR", { sensitivity: "base" }), + ), + }; } export function filterProfiles(