@@ -2,15 +2,33 @@ import { Badge } from "@/shared/ui/badge";
22import { Card , CardContent } from "@/shared/ui/card" ;
33import { Input } from "@/shared/ui/input" ;
44import type { JobFile , JobsMeta } from "@/domains/jobs/domain/job.types" ;
5- import { useMemo , useState , type Dispatch , type ReactNode , type SetStateAction } from "react" ;
6- import { FiBriefcase , FiCheck , FiFileText , FiFilter , FiSearch , FiSlash , FiTag } from "react-icons/fi" ;
5+ import {
6+ useMemo ,
7+ useState ,
8+ type FormEvent ,
9+ type Dispatch ,
10+ type ReactNode ,
11+ type SetStateAction ,
12+ } from "react" ;
13+ import {
14+ FiBriefcase ,
15+ FiCheck ,
16+ FiFileText ,
17+ FiFilter ,
18+ FiPlus ,
19+ FiSearch ,
20+ FiSlash ,
21+ FiTag ,
22+ } from "react-icons/fi" ;
723import { KeywordsModal } from "./KeywordsModal" ;
824import { Button } from "@/shared/ui/button" ;
925interface JobsFiltersCardProps {
1026 search : string ;
1127 setSearch : Dispatch < SetStateAction < string > > ;
1228 keywordFilter : string [ ] ;
1329 setKeywordFilter : Dispatch < SetStateAction < string [ ] > > ;
30+ onRemoveFilter : ( filterToRemove : string ) => void ;
31+ onClearFilters : ( ) => void ;
1432 keywords : string [ ] ;
1533 selectedFile : string ;
1634 setSelectedFile : Dispatch < SetStateAction < string > > ;
@@ -23,17 +41,20 @@ function getSelectedFilters(search: string, keywordFilter: string[]) {
2341 const searchTerms = search
2442 . split ( / [ , ; / ] + / )
2543 . map ( ( item ) => item . trim ( ) )
26- . filter ( Boolean )
27- . slice ( 0 , 5 ) ;
44+ . filter ( Boolean ) ;
2845
29- return Array . from ( new Set ( [ ...keywordFilter . filter ( Boolean ) , ...searchTerms ] ) ) ;
46+ return Array . from (
47+ new Set ( [ ...keywordFilter . filter ( Boolean ) , ...searchTerms ] ) ,
48+ ) ;
3049}
3150
3251export function JobsFiltersCard ( {
3352 search,
3453 setSearch,
3554 keywordFilter,
3655 setKeywordFilter,
56+ onRemoveFilter,
57+ onClearFilters,
3758 keywords,
3859 selectedFile,
3960 setSelectedFile,
@@ -42,8 +63,12 @@ export function JobsFiltersCard({
4263 actions,
4364} : JobsFiltersCardProps ) {
4465 const [ seeKeywordsModal , setSeeKeywordsModal ] = useState ( false ) ;
66+ const [ searchTerm , setSearchTerm ] = useState ( "" ) ;
4567
46- const selectedFilters = useMemo ( ( ) => getSelectedFilters ( search , keywordFilter ) , [ search , keywordFilter ] ) ;
68+ const selectedFilters = useMemo (
69+ ( ) => getSelectedFilters ( search , keywordFilter ) ,
70+ [ search , keywordFilter ] ,
71+ ) ;
4772
4873 const handleManageFilters = ( ) => {
4974 setSeeKeywordsModal ( true ) ;
@@ -59,39 +84,54 @@ export function JobsFiltersCard({
5984 return ;
6085 }
6186
62- setKeywordFilter ( ( current ) => ( current . includes ( value ) ? current : [ ...current , value ] ) ) ;
63- } ;
64-
65- const handleRemoveFilter = ( filterToRemove : string ) => {
66- setKeywordFilter ( ( current ) => current . filter ( ( item ) => item !== filterToRemove ) ) ;
67- setSearch ( ( current ) =>
68- current
69- . split ( / [ , ; / ] + / )
70- . map ( ( item ) => item . trim ( ) )
71- . filter ( ( item ) => item && item !== filterToRemove )
72- . join ( ", " ) ,
87+ setKeywordFilter ( ( current ) =>
88+ current . includes ( value ) ? current : [ ...current , value ] ,
7389 ) ;
7490 } ;
7591
76- const handleClearFilters = ( ) => {
77- setSearch ( "" ) ;
78- setKeywordFilter ( [ ] ) ;
92+ const handleAddSearchTerm = ( event : FormEvent < HTMLFormElement > ) => {
93+ event . preventDefault ( ) ;
94+
95+ const nextTerm = searchTerm . trim ( ) ;
96+
97+ if ( ! nextTerm ) {
98+ return ;
99+ }
100+
101+ setSearch ( ( current ) => {
102+ const currentTerms = getSelectedFilters ( current , [ ] ) ;
103+
104+ if ( currentTerms . includes ( nextTerm ) ) {
105+ return current ;
106+ }
107+
108+ return [ ...currentTerms , nextTerm ] . join ( ", " ) ;
109+ } ) ;
110+ setSearchTerm ( "" ) ;
79111 } ;
80112
81113 return (
82114 < >
83115 < Card className = "rounded-[24px] border border-slate-200 bg-white/95 shadow-[0_18px_45px_rgba(15,23,42,0.08)] backdrop-blur dark:border-[#2b3f58] dark:bg-[#131d31]/95 dark:shadow-[0_18px_45px_rgba(3,8,20,0.25)]" >
84116 < CardContent className = "flex flex-col gap-4 p-5 md:p-6" >
85117 < div className = "flex flex-col md:flex-row gap-3 md:items-center" >
86- < div className = "relative flex-1" >
118+ < form className = "relative flex-1" onSubmit = { handleAddSearchTerm } >
87119 < FiSearch className = "pointer-events-none absolute left-4 top-1/2 h-4 w-4 -translate-y-1/2 text-slate-500 dark:text-slate-400" />
88120 < Input
89- value = { search }
90- onChange = { ( event ) => setSearch ( event . target . value ) }
91- className = "h-14 w-full rounded-2xl border border-slate-300 bg-white pl-11 text-base text-slate-900 placeholder:text-slate-500 focus-visible:ring-[#14AE5C]/40 dark:border-[#35506f] dark:bg-[#091224] dark:text-slate-100 dark:placeholder:text-slate-400"
121+ value = { searchTerm }
122+ onChange = { ( event ) => setSearchTerm ( event . target . value ) }
123+ className = "h-14 w-full rounded-2xl border border-slate-300 bg-white pl-11 pr-14 text-base text-slate-900 placeholder:text-slate-500 focus-visible:ring-[#14AE5C]/40 dark:border-[#35506f] dark:bg-[#091224] dark:text-slate-100 dark:placeholder:text-slate-400"
92124 placeholder = "Buscar por título, empresa, local ou link"
93125 />
94- </ div >
126+ < button
127+ type = "submit"
128+ aria-label = "Adicionar filtro de busca"
129+ className = "absolute right-2 top-1/2 flex h-10 w-10 -translate-y-1/2 items-center justify-center rounded-xl bg-[#0c6b35] text-white transition-colors hover:bg-[#0a5b2d] disabled:cursor-not-allowed disabled:opacity-50"
130+ disabled = { ! searchTerm . trim ( ) }
131+ >
132+ < FiPlus className = "h-4 w-4" />
133+ </ button >
134+ </ form >
95135 < div className = "flex items-center gap-2" > { actions } </ div >
96136 </ div >
97137
@@ -124,15 +164,18 @@ export function JobsFiltersCard({
124164 ) ) }
125165 </ select >
126166
127- < div >
128- < Badge variant = "secondary" className = "gap-1.5 rounded-full border border-slate-300 bg-slate-100 px-3 py-1 text-xs text-slate-700 dark:border-[#35506f] dark:bg-[#24324c] dark:text-slate-100" >
167+ < div >
168+ < Badge
169+ variant = "secondary"
170+ className = "gap-1.5 rounded-full border border-slate-300 bg-slate-100 px-3 py-1 text-xs text-slate-700 dark:border-[#35506f] dark:bg-[#24324c] dark:text-slate-100"
171+ >
129172 < FiFileText className = "h-3.5 w-3.5" />
130- { meta . file || "Sem arquivo" }
173+ { meta . file || "Sem arquivo" }
131174 </ Badge >
132175
133176 < Badge className = "gap-1.5 rounded-full bg-[#0c6b35] px-3 py-1 text-xs text-white" >
134177 < FiBriefcase className = "h-3.5 w-3.5" />
135- { meta . total } vagas
178+ { meta . total } vagas
136179 </ Badge >
137180 </ div >
138181 </ div >
@@ -151,7 +194,7 @@ export function JobsFiltersCard({
151194 variant = "outline"
152195 aria-label = "Limpar filtros"
153196 className = "h-10 w-10 rounded-md border-[#0c6b35] bg-[#0c6b35] p-0 text-white shadow-sm hover:bg-[#0a5b2d]"
154- onClick = { handleClearFilters }
197+ onClick = { onClearFilters }
155198 >
156199 < FiSlash className = "h-4 w-4" />
157200 </ Button >
@@ -160,7 +203,9 @@ export function JobsFiltersCard({
160203
161204 < div className = "rounded-2xl border border-slate-200 bg-slate-50 px-3 py-3 dark:border-[#2b3f58] dark:bg-[#12203a]/45" >
162205 < div className = "flex flex-wrap items-center gap-2" >
163- < span className = "text-[11px] font-medium uppercase tracking-[0.18em] text-slate-500 dark:text-slate-400" > Filtros selecionados</ span >
206+ < span className = "text-[11px] font-medium uppercase tracking-[0.18em] text-slate-500 dark:text-slate-400" >
207+ Filtros selecionados
208+ </ span >
164209 { selectedFilters . length > 0 ? (
165210 selectedFilters . map ( ( filter ) => (
166211 < Badge
@@ -174,20 +219,24 @@ export function JobsFiltersCard({
174219 type = "button"
175220 aria-label = { `Remover filtro ${ filter } ` }
176221 className = "ml-1 rounded-full p-0.5 text-[#0c6b35] hover:bg-[#14AE5C]/20 dark:text-[#8df0af]"
177- onClick = { ( ) => handleRemoveFilter ( filter ) }
222+ onClick = { ( ) => onRemoveFilter ( filter ) }
178223 >
179224 < FiSlash className = "h-3 w-3" />
180225 </ button >
181226 </ Badge >
182227 ) )
183228 ) : (
184- < span className = "text-sm text-slate-500 dark:text-slate-400" > Use o botão Filtrar para adicionar ou remover palavras-chave.</ span >
229+ < span className = "text-sm text-slate-500 dark:text-slate-400" >
230+ Use o botão Filtrar para adicionar ou remover palavras-chave.
231+ </ span >
185232 ) }
186233 </ div >
187234 </ div >
188235 </ CardContent >
189236 </ Card >
190- { seeKeywordsModal && < KeywordsModal onClose = { ( ) => setSeeKeywordsModal ( false ) } /> }
237+ { seeKeywordsModal && (
238+ < KeywordsModal onClose = { ( ) => setSeeKeywordsModal ( false ) } />
239+ ) }
191240 </ >
192241 ) ;
193242}
0 commit comments