@@ -2,7 +2,10 @@ import { useState } from "react";
2
2
import { Controller , useForm } from "react-hook-form" ;
3
3
4
4
import dayjs from "@calcom/dayjs" ;
5
+ import { classNames } from "@calcom/lib" ;
6
+ import { useDebounce } from "@calcom/lib/hooks/useDebounce" ;
5
7
import { useHasTeamPlan } from "@calcom/lib/hooks/useHasPaidPlan" ;
8
+ import { useInViewObserver } from "@calcom/lib/hooks/useInViewObserver" ;
6
9
import { useLocale } from "@calcom/lib/hooks/useLocale" ;
7
10
import { trpc } from "@calcom/trpc/react" ;
8
11
import useMeQuery from "@calcom/trpc/react/hooks/useMeQuery" ;
@@ -18,6 +21,8 @@ import {
18
21
Switch ,
19
22
TextArea ,
20
23
UpgradeTeamsBadge ,
24
+ Label ,
25
+ Input ,
21
26
} from "@calcom/ui" ;
22
27
23
28
export type BookingRedirectForm = {
@@ -43,21 +48,39 @@ export const CreateOrEditOutOfOfficeEntryModal = ({
43
48
const { t } = useLocale ( ) ;
44
49
const utils = trpc . useUtils ( ) ;
45
50
46
- const { data : listMembers } = trpc . viewer . teams . legacyListMembers . useQuery ( { } ) ;
51
+ const [ searchText , setSearchText ] = useState ( "" ) ;
52
+ const debouncedSearch = useDebounce ( searchText , 500 ) ;
53
+
54
+ const members = trpc . viewer . teams . legacyListMembers . useInfiniteQuery (
55
+ { limit : 10 , searchText : debouncedSearch } ,
56
+ {
57
+ enabled : true ,
58
+ getNextPageParam : ( lastPage ) => lastPage . nextCursor ,
59
+ }
60
+ ) ;
47
61
48
62
const me = useMeQuery ( ) ;
49
63
50
64
const memberListOptions : {
51
65
value : number ;
52
66
label : string ;
67
+ avatarUrl : string | null ;
53
68
} [ ] =
54
- listMembers
69
+ members ?. data ?. pages
70
+ . flatMap ( ( page ) => page . members )
55
71
?. filter ( ( member ) => me ?. data ?. id !== member . id )
56
72
. map ( ( member ) => ( {
57
73
value : member . id ,
58
- label : member . name || "" ,
74
+ label : member . name || member . username || "" ,
75
+ avatarUrl : member . avatarUrl ,
59
76
} ) ) || [ ] ;
60
77
78
+ const { ref : observerRef } = useInViewObserver ( ( ) => {
79
+ if ( members . hasNextPage && ! members . isFetching ) {
80
+ members . fetchNextPage ( ) ;
81
+ }
82
+ } , document . querySelector ( '[role="dialog"]' ) ) ;
83
+
61
84
const { data : outOfOfficeReasonList } = trpc . viewer . outOfOfficeReasonList . useQuery ( ) ;
62
85
63
86
const reasonList = ( outOfOfficeReasonList || [ ] ) . map ( ( reason ) => ( {
@@ -74,6 +97,7 @@ export const CreateOrEditOutOfOfficeEntryModal = ({
74
97
setValue,
75
98
control,
76
99
register,
100
+ watch,
77
101
formState : { isSubmitting } ,
78
102
} = useForm < BookingRedirectForm > ( {
79
103
defaultValues : currentlyEditingOutOfOfficeEntry
@@ -89,6 +113,8 @@ export const CreateOrEditOutOfOfficeEntryModal = ({
89
113
} ,
90
114
} ) ;
91
115
116
+ const watchedTeamUserId = watch ( "toTeamUserId" ) ;
117
+
92
118
const createOrEditOutOfOfficeEntry = trpc . viewer . outOfOfficeCreateOrUpdate . useMutation ( {
93
119
onSuccess : ( ) => {
94
120
showToast (
@@ -209,28 +235,54 @@ export const CreateOrEditOutOfOfficeEntryModal = ({
209
235
</ div >
210
236
211
237
{ profileRedirect && (
212
- < div className = "mt-4" >
213
- < div className = "h-16" >
214
- < p className = "text-emphasis block text-sm font-medium" > { t ( "team_member" ) } </ p >
215
- < Controller
216
- control = { control }
217
- name = "toTeamUserId"
218
- render = { ( { field : { onChange, value } } ) => (
219
- < Select < Option >
220
- name = "toTeamUsername"
221
- data-testid = "team_username_select"
222
- value = { memberListOptions . find ( ( member ) => member . value === value ) }
223
- placeholder = { t ( "select_team_member" ) }
224
- isSearchable
225
- options = { memberListOptions }
226
- onChange = { ( selectedOption ) => {
227
- if ( selectedOption ?. value ) {
228
- onChange ( selectedOption . value ) ;
238
+ < div className = "mb-2" >
239
+ < Label className = "text-emphasis mt-6" > { t ( "select_team_member" ) } </ Label >
240
+ < div className = "mt-2" >
241
+ < Input
242
+ type = "text"
243
+ placeholder = { t ( "search" ) }
244
+ onChange = { ( e ) => setSearchText ( e . target . value ) }
245
+ value = { searchText }
246
+ />
247
+ < div className = "scroll-bar flex h-[150px] flex-col gap-0.5 overflow-y-scroll rounded-md border p-1" >
248
+ { memberListOptions . map ( ( member ) => (
249
+ < label
250
+ key = { member . value }
251
+ data-testid = { `team_username_select_${ member . value } ` }
252
+ tabIndex = { watchedTeamUserId === member . value ? - 1 : 0 }
253
+ role = "radio"
254
+ aria-checked = { watchedTeamUserId === member . value }
255
+ onKeyDown = { ( e ) => {
256
+ if ( e . key === "Enter" || e . key === " " ) {
257
+ e . preventDefault ( ) ;
258
+ setValue ( "toTeamUserId" , member . value , { shouldDirty : true } ) ;
229
259
}
230
260
} }
231
- />
232
- ) }
233
- />
261
+ className = { classNames (
262
+ "hover:bg-subtle focus:bg-subtle focus:ring-emphasis cursor-pointer items-center justify-between gap-0.5 rounded-sm py-2 outline-none focus:ring-2" ,
263
+ watchedTeamUserId === member . value && "bg-subtle"
264
+ ) } >
265
+ < div className = "flex flex-1 items-center space-x-3" >
266
+ < input
267
+ type = "radio"
268
+ className = "hidden"
269
+ checked = { watchedTeamUserId === member . value }
270
+ onChange = { ( ) => setValue ( "toTeamUserId" , member . value , { shouldDirty : true } ) }
271
+ />
272
+ < span className = "text-emphasis w-full px-2 text-sm" > { member . label } </ span >
273
+ </ div >
274
+ </ label >
275
+ ) ) }
276
+ < div className = "text-default text-center" ref = { observerRef } >
277
+ < Button
278
+ color = "minimal"
279
+ loading = { members . isFetchingNextPage }
280
+ disabled = { ! members . hasNextPage }
281
+ onClick = { ( ) => members . fetchNextPage ( ) } >
282
+ { members . hasNextPage ? t ( "load_more_results" ) : t ( "no_more_results" ) }
283
+ </ Button >
284
+ </ div >
285
+ </ div >
234
286
</ div >
235
287
</ div >
236
288
) }
0 commit comments