11import { Autocomplete } from '@mui/material' ;
2- import React , { useEffect , useRef , useState } from 'react' ;
3- import { Box , Chip , Grid , TextField , Tooltip , Typography } from '../../base' ;
2+ import React , { useCallback , useEffect , useState } from 'react' ;
3+ import { Box , Chip , CircularProgress , Grid , TextField , Tooltip , Typography } from '../../base' ;
44import { iconLarge , iconSmall } from '../../constants/iconsSizes' ;
55import { CloseIcon , OrgIcon } from '../../icons' ;
66
@@ -10,95 +10,112 @@ interface Option {
1010}
1111
1212interface InputFieldSearchProps {
13- defaultData ?: Option [ ] ;
13+ data : Option [ ] ;
14+ setFilterData : ( data : Option [ ] ) => void ;
1415 label ?: string ;
1516 fetchSuggestions : ( value : string ) => void ;
16- setFilterData : ( data : Option [ ] ) => void ;
1717 isLoading : boolean ;
1818 type : string ;
1919 disabled ?: boolean ;
20+ selectedData : Option [ ] ;
21+ searchValue : string ;
22+ setSearchValue : ( value : string ) => void ;
2023}
2124
2225const InputFieldSearch : React . FC < InputFieldSearchProps > = ( {
23- defaultData = [ ] ,
26+ data ,
2427 label,
2528 fetchSuggestions,
2629 setFilterData,
2730 isLoading,
2831 type,
29- disabled
32+ disabled,
33+ selectedData,
34+ searchValue,
35+ setSearchValue
3036} ) => {
31- const [ data , setData ] = useState < Option [ ] > ( [ ] ) ;
3237 const [ error , setError ] = useState ( '' ) ;
33- const [ inputValue , setInputValue ] = useState ( '' ) ;
3438 const [ open , setOpen ] = useState ( false ) ;
35- const [ showAllUsers , setShowAllUsers ] = useState ( false ) ;
36- const [ selectedOption , setSelectedOption ] = useState < Option | undefined > ( undefined ) ;
37- const isFirstRender = useRef ( true ) ;
39+ const [ showAllItems , setShowAllItems ] = useState ( false ) ;
40+ const [ localSelectedData , setLocalSelectedData ] = useState < Option [ ] > ( selectedData ) ;
3841
42+ // Sync local state with prop changes
3943 useEffect ( ( ) => {
40- if ( ! isFirstRender . current ) {
41- setFilterData ( data ) ;
42- } else {
43- isFirstRender . current = false ;
44- }
45- } , [ data , setFilterData ] ) ;
44+ setLocalSelectedData ( selectedData ) ;
45+ } , [ selectedData ] ) ;
4646
47- const handleDelete = ( id : string ) => {
48- setData ( ( prevData ) => prevData . filter ( ( item ) => item . id !== id ) ) ;
49- } ;
47+ const handleDelete = useCallback (
48+ ( id : string ) => {
49+ const newData = localSelectedData . filter ( ( item ) => item . id !== id ) ;
50+ setLocalSelectedData ( newData ) ;
51+ setFilterData ( newData ) ;
52+ } ,
53+ [ localSelectedData , setFilterData ]
54+ ) ;
5055
51- const handleAdd = ( event : React . SyntheticEvent , value : Option | null ) => {
52- if ( ! value ) return ;
56+ const handleAdd = useCallback (
57+ ( _event : React . SyntheticEvent , value : Option | null ) => {
58+ if ( ! value ) return ;
5359
54- setData ( ( prevData ) => {
55- const isDuplicate = prevData . some ( ( item ) => item . id === value . id ) ;
60+ // Check for duplicates
61+ const isDuplicate = localSelectedData . some ( ( item ) => item . id === value . id ) ;
5662 if ( isDuplicate ) {
5763 setError ( `${ type } already selected` ) ;
58- return prevData ;
64+ return ;
5965 }
6066
67+ // Update both local and parent state
68+ const newData = [ ...localSelectedData , value ] ;
69+ setLocalSelectedData ( newData ) ;
70+ setFilterData ( newData ) ;
6171 setError ( '' ) ;
62- return [ ...prevData , value ] ;
63- } ) ;
64- setSelectedOption ( undefined ) ;
65- setInputValue ( '' ) ;
66- } ;
67-
68- const handleInputChange = ( event : React . SyntheticEvent , value : string ) => {
69- setInputValue ( value ) ;
70- if ( value === '' ) {
72+ setSearchValue ( '' ) ;
7173 setOpen ( false ) ;
72- } else {
73- const encodedValue = encodeURIComponent ( value ) ;
74- fetchSuggestions ( encodedValue ) ;
75- setError ( '' ) ;
76- setOpen ( true ) ;
77- }
78- } ;
74+ } ,
75+ [ localSelectedData , setFilterData , type , setSearchValue ]
76+ ) ;
77+
78+ const handleInputChange = useCallback (
79+ ( _event : React . SyntheticEvent , value : string ) => {
80+ setSearchValue ( value ) ;
81+ if ( value === '' ) {
82+ setOpen ( false ) ;
83+ } else {
84+ const encodedValue = encodeURIComponent ( value ) ;
85+ fetchSuggestions ( encodedValue ) ;
86+ setError ( '' ) ;
87+ setOpen ( true ) ;
88+ }
89+ } ,
90+ [ fetchSuggestions , setSearchValue ]
91+ ) ;
7992
8093 return (
81- < >
82- < Autocomplete < Option , false , true , false >
94+ < Box sx = { { width : '100%' } } >
95+ < Autocomplete
8396 id = { `${ type } -search-field` }
84- sx = { { width : 'auto ' } }
85- options = { defaultData }
86- getOptionLabel = { ( option : Option ) => option . name }
97+ style = { { width : '100% ' } }
98+ options = { data }
99+ getOptionLabel = { ( ) => searchValue }
87100 isOptionEqualToValue = { ( option : Option , value : Option ) => option . id === value . id }
88101 noOptionsText = { isLoading ? 'Loading...' : `No ${ type } found` }
89102 loading = { isLoading }
90103 open = { open }
104+ onClose = { ( ) => setOpen ( false ) }
91105 disabled = { disabled }
92- value = { selectedOption }
93- inputValue = { inputValue }
106+ value = { undefined }
107+ inputValue = { searchValue }
94108 onChange = { handleAdd }
95109 onInputChange = { handleInputChange }
96- ffilterOptions = { ( options ) => options }
110+ ffilterOptions = { ( x ) => x }
97111 disableClearable
98112 includeInputInList
99113 filterSelectedOptions
100114 disableListWrap
101115 clearOnBlur
116+ popupIcon = { null }
117+ blurOnSelect
118+ forcePopupIcon = { false }
102119 renderInput = { ( params ) => (
103120 < TextField
104121 { ...params }
@@ -109,50 +126,55 @@ const InputFieldSearch: React.FC<InputFieldSearchProps> = ({
109126 InputProps = { {
110127 ...params . InputProps ,
111128 endAdornment : (
112- < React . Fragment > { isLoading ? < div color = "inherit" /> : null } </ React . Fragment >
129+ < React . Fragment >
130+ { isLoading ? < CircularProgress color = "inherit" size = { 20 } /> : null }
131+ </ React . Fragment >
113132 )
114133 } }
115134 />
116135 ) }
117136 renderOption = { ( props , option : Option ) => (
118- < li { ...props } >
119- < Grid container alignItems = "center" >
120- < Grid item >
121- < Box sx = { { color : 'text.secondary' , mr : 2 } } >
122- < OrgIcon { ...iconLarge } />
123- </ Box >
137+ < li { ...props } key = { option . id } >
138+ < Box component = "li" sx = { { '& > img' : { mr : 2 , flexShrink : 0 } } } >
139+ < Grid container alignItems = "center" >
140+ < Grid item >
141+ < Box sx = { { color : 'text.secondary' , mr : 2 } } >
142+ < OrgIcon { ...iconLarge } />
143+ </ Box >
144+ </ Grid >
145+ < Grid item xs >
146+ < Typography variant = "body2" > { option . name } </ Typography >
147+ </ Grid >
124148 </ Grid >
125- < Grid item xs >
126- < Typography variant = "body2" > { option . name } </ Typography >
127- </ Grid >
128- </ Grid >
149+ </ Box >
129150 </ li >
130151 ) }
131152 />
153+
132154 < Box
133155 sx = { {
134156 display : 'flex' ,
135157 flexWrap : 'wrap' ,
136158 gap : 0.5 ,
137- mt : data ?. length > 0 ? '0.5rem' : ''
159+ mt : localSelectedData ?. length > 0 ? '0.5rem' : ''
138160 } }
139161 >
140- { ! showAllUsers && data ?. length > 0 && (
162+ { ! showAllItems && localSelectedData ?. length > 0 && (
141163 < Chip
142- key = { data [ data . length - 1 ] ?. id }
164+ key = { localSelectedData [ localSelectedData . length - 1 ] ?. id }
143165 avatar = { < OrgIcon { ...iconSmall } /> }
144- label = { data [ data . length - 1 ] ?. name }
166+ label = { localSelectedData [ localSelectedData . length - 1 ] ?. name }
145167 size = "small"
146- onDelete = { ( ) => handleDelete ( data [ data . length - 1 ] ?. id ) }
168+ onDelete = { ( ) => handleDelete ( localSelectedData [ localSelectedData . length - 1 ] ?. id ) }
147169 deleteIcon = {
148- < Tooltip title = " Remove member" >
170+ < Tooltip title = { ` Remove ${ type } ` } >
149171 < CloseIcon style = { iconSmall } />
150172 </ Tooltip >
151173 }
152174 />
153175 ) }
154- { showAllUsers &&
155- data ?. map ( ( obj ) => (
176+ { showAllItems &&
177+ localSelectedData ?. map ( ( obj ) => (
156178 < Chip
157179 key = { obj . id }
158180 avatar = { < OrgIcon { ...iconSmall } /> }
@@ -166,23 +188,23 @@ const InputFieldSearch: React.FC<InputFieldSearchProps> = ({
166188 }
167189 />
168190 ) ) }
169- { data ?. length > 1 && (
191+ { localSelectedData ?. length > 1 && (
170192 < Typography
171- onClick = { ( ) => setShowAllUsers ( ! showAllUsers ) }
193+ onClick = { ( ) => setShowAllItems ( ! showAllItems ) }
172194 sx = { {
173195 cursor : 'pointer' ,
174- color : 'white ' ,
196+ color : 'primary.main ' ,
175197 fontWeight : '600' ,
176198 '&:hover' : {
177- color : 'black '
199+ color : 'primary.dark '
178200 }
179201 } }
180202 >
181- { showAllUsers ? '(hide)' : `(+${ data ?. length - 1 } )` }
203+ { showAllItems ? '(hide)' : `(+${ localSelectedData ?. length - 1 } )` }
182204 </ Typography >
183205 ) }
184206 </ Box >
185- </ >
207+ </ Box >
186208 ) ;
187209} ;
188210
0 commit comments