@@ -2,8 +2,9 @@ import { css } from "@emotion/css";
2
2
import Autocomplete from "@mui/material/Autocomplete" ;
3
3
import CircularProgress from "@mui/material/CircularProgress" ;
4
4
import TextField from "@mui/material/TextField" ;
5
+ import { organizationMembers } from "api/queries/organizations" ;
5
6
import { users } from "api/queries/users" ;
6
- import type { User } from "api/typesGenerated" ;
7
+ import type { OrganizationMemberWithUserData , User } from "api/typesGenerated" ;
7
8
import { Avatar } from "components/Avatar/Avatar" ;
8
9
import { AvatarData } from "components/AvatarData/AvatarData" ;
9
10
import { useDebouncedFunction } from "hooks/debounce" ;
@@ -16,45 +17,99 @@ import {
16
17
import { useQuery } from "react-query" ;
17
18
import { prepareQuery } from "utils/filters" ;
18
19
19
- export type UserAutocompleteProps = {
20
- value : User | null ;
21
- onChange : ( user : User | null ) => void ;
22
- label ?: string ;
20
+ // The common properties between users and org members that we need.
21
+ export type SelectedUser = {
22
+ avatar_url : string ;
23
+ email : string ;
24
+ username : string ;
25
+ } ;
26
+
27
+ export type CommonAutocompleteProps < T extends SelectedUser > = {
23
28
className ?: string ;
24
- size ?: ComponentProps < typeof TextField > [ "size" ] ;
29
+ label ?: string ;
30
+ onChange : ( user : T | null ) => void ;
25
31
required ?: boolean ;
32
+ size ?: ComponentProps < typeof TextField > [ "size" ] ;
33
+ value : T | null ;
26
34
} ;
27
35
36
+ export type UserAutocompleteProps = CommonAutocompleteProps < User > ;
37
+
28
38
export const UserAutocomplete : FC < UserAutocompleteProps > = ( {
29
39
value,
30
- onChange,
31
- label,
32
- className,
33
- size = "small" ,
34
- required,
40
+ ...props
35
41
} ) => {
36
- const [ autoComplete , setAutoComplete ] = useState < {
37
- value : string ;
38
- open : boolean ;
39
- } > ( {
40
- value : value ?. email ?? "" ,
41
- open : false ,
42
- } ) ;
42
+ const [ filter , setFilter ] = useState < string > ( ) ;
43
+
43
44
const usersQuery = useQuery ( {
44
45
...users ( {
45
- q : prepareQuery ( encodeURI ( autoComplete . value ) ) ,
46
+ q : filter !== undefined ? prepareQuery ( encodeURI ( filter ) ) : "" ,
46
47
limit : 25 ,
47
48
} ) ,
48
- enabled : autoComplete . open ,
49
+ enabled : filter !== undefined ,
49
50
keepPreviousData : true ,
50
51
} ) ;
52
+ return (
53
+ < InnerAutocomplete < User >
54
+ users = { usersQuery . data ?. users }
55
+ value = { value }
56
+ setFilter = { setFilter }
57
+ { ...props }
58
+ />
59
+ ) ;
60
+ } ;
61
+
62
+ export type MemberAutocompleteProps =
63
+ CommonAutocompleteProps < OrganizationMemberWithUserData > & {
64
+ organizationId : string ;
65
+ } ;
66
+
67
+ export const MemberAutocomplete : FC < MemberAutocompleteProps > = ( {
68
+ value,
69
+ organizationId,
70
+ ...props
71
+ } ) => {
72
+ const [ filter , setFilter ] = useState < string > ( ) ;
73
+
74
+ // Currently this queries all members, as there is no pagination.
75
+ const membersQuery = useQuery ( {
76
+ ...organizationMembers ( organizationId ) ,
77
+ enabled : filter !== undefined ,
78
+ keepPreviousData : true ,
79
+ } ) ;
80
+ return (
81
+ < InnerAutocomplete < OrganizationMemberWithUserData >
82
+ users = { membersQuery . data }
83
+ value = { value }
84
+ setFilter = { setFilter }
85
+ { ...props }
86
+ />
87
+ ) ;
88
+ } ;
89
+
90
+ type InnerAutocompleteProps < T extends SelectedUser > =
91
+ CommonAutocompleteProps < T > & {
92
+ /** Filter is undefined if the autocomplete is closed. */
93
+ setFilter : ( filter : string | undefined ) => void ;
94
+ /** Users are undefined if not loaded. */
95
+ users : readonly T [ ] | undefined ;
96
+ } ;
97
+
98
+ const InnerAutocomplete = < T extends SelectedUser > ( {
99
+ className,
100
+ label,
101
+ onChange,
102
+ required,
103
+ setFilter,
104
+ size = "small" ,
105
+ users,
106
+ value,
107
+ } : InnerAutocompleteProps < T > ) => {
108
+ const [ open , setOpen ] = useState ( false ) ;
51
109
52
110
const { debounced : debouncedInputOnChange } = useDebouncedFunction (
53
111
( event : ChangeEvent < HTMLInputElement > ) => {
54
- setAutoComplete ( ( state ) => ( {
55
- ...state ,
56
- value : event . target . value ,
57
- } ) ) ;
112
+ setFilter ( event . target . value ?? "" ) ;
58
113
} ,
59
114
750 ,
60
115
) ;
@@ -63,24 +118,20 @@ export const UserAutocomplete: FC<UserAutocompleteProps> = ({
63
118
< Autocomplete
64
119
noOptionsText = "No users found"
65
120
className = { className }
66
- options = { usersQuery . data ?. users ?? [ ] }
67
- loading = { usersQuery . isLoading }
121
+ options = { users ?? [ ] }
122
+ loading = { users !== undefined }
68
123
value = { value }
69
124
data-testid = "user-autocomplete"
70
- open = { autoComplete . open }
125
+ open = { open }
71
126
isOptionEqualToValue = { ( a , b ) => a . username === b . username }
72
127
getOptionLabel = { ( option ) => option . email }
73
128
onOpen = { ( ) => {
74
- setAutoComplete ( ( state ) => ( {
75
- ...state ,
76
- open : true ,
77
- } ) ) ;
129
+ setOpen ( true ) ;
130
+ setFilter ( value ?. email ?? "" ) ;
78
131
} }
79
132
onClose = { ( ) => {
80
- setAutoComplete ( {
81
- value : value ?. email ?? "" ,
82
- open : false ,
83
- } ) ;
133
+ setOpen ( false ) ;
134
+ setFilter ( undefined ) ;
84
135
} }
85
136
onChange = { ( _ , newValue ) => {
86
137
onChange ( newValue ) ;
@@ -117,9 +168,7 @@ export const UserAutocomplete: FC<UserAutocompleteProps> = ({
117
168
) ,
118
169
endAdornment : (
119
170
< >
120
- { usersQuery . isFetching && autoComplete . open && (
121
- < CircularProgress size = { 16 } />
122
- ) }
171
+ { users === undefined && open && < CircularProgress size = { 16 } /> }
123
172
{ params . InputProps . endAdornment }
124
173
</ >
125
174
) ,
0 commit comments