@@ -3,114 +3,42 @@ import { Button } from '@components/ui/button'
33import { Input } from '@components/ui/input'
44import { UserAvatar } from '@components/features/message/UserAvatar'
55import { Search , UserPlus , X } from 'lucide-react'
6- import type { RavenUser } from '@raven/types/Raven/RavenUser'
76import type { UserFields } from '@raven/types/common/UserFields'
87import { ScrollArea } from '@components/ui/scroll-area'
8+ import { db , UserData } from '../../../../db/db'
9+ import { useLiveQuery } from 'dexie-react-hooks' ;
10+ import { useDebounce } from '@raven/lib/hooks/useDebounce' ;
11+ import _ from '@lib/translate'
12+ import useCurrentRavenUser from "@raven/lib/hooks/useCurrentRavenUser"
913
1014interface AddMembersStepProps {
11- selectedUsers : RavenUser [ ]
12- onSelectUsers : ( users : RavenUser [ ] ) => void
15+ selectedUsers : UserData [ ]
16+ onSelectUsers : ( users : UserData [ ] ) => void
1317}
1418
1519export const AddMembersStep = ( { selectedUsers, onSelectUsers } : AddMembersStepProps ) => {
20+
21+ const { myProfile } = useCurrentRavenUser ( )
1622 const [ searchQuery , setSearchQuery ] = useState ( '' )
23+ const debouncedText = useDebounce ( searchQuery , 200 )
24+ const filterText = searchQuery === '' ? '' : debouncedText
25+
26+ const filteredUsers = useLiveQuery ( ( ) => db . users
27+ . where ( 'enabled' )
28+ . equals ( 1 )
29+ . and ( ( user ) => user . name !== myProfile ?. name )
30+ . and ( ( user ) => user . name . toLowerCase ( ) . includes ( filterText . toLowerCase ( ) ) || user . full_name . toLowerCase ( ) . includes ( filterText . toLowerCase ( ) ) )
31+ . and ( ( user ) => ! selectedUsers . some ( ( selected ) => selected . name === user . name ) )
32+ . toArray ( ) ,
33+ [ filterText , selectedUsers ] )
34+
1735 const [ announcement , setAnnouncement ] = useState ( '' )
1836 const searchInputRef = useRef < HTMLInputElement > ( null )
1937
20- // TODO: Replace with actual data from API
21- const mockAvailableUsers : RavenUser [ ] = [
22- {
23- name : 'michael.brown@company.com' ,
24- full_name : 'Michael Brown' ,
25- user_image :
26- 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=150&h=150&fit=crop&crop=face' ,
27- type : 'User' ,
28- availability_status : 'Available' ,
29- custom_status : '' ,
30- enabled : 1 ,
31- first_name : 'Michael' ,
32- creation : '2024-01-01' ,
33- modified : '2024-01-01' ,
34- owner : 'admin' ,
35- modified_by : 'admin' ,
36- docstatus : 0 ,
37- } ,
38- {
39- name : 'emily.davis@company.com' ,
40- full_name : 'Emily Davis' ,
41- user_image :
42- 'https://images.unsplash.com/photo-1438761681033-6461ffad8d80?w=150&h=150&fit=crop&crop=face' ,
43- type : 'User' ,
44- availability_status : 'Away' ,
45- custom_status : 'On lunch break' ,
46- enabled : 1 ,
47- first_name : 'Emily' ,
48- creation : '2024-01-01' ,
49- modified : '2024-01-01' ,
50- owner : 'admin' ,
51- modified_by : 'admin' ,
52- docstatus : 0 ,
53- } ,
54- {
55- name : 'daniel.wilson@company.com' ,
56- full_name : 'Daniel Wilson' ,
57- user_image :
58- 'https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?w=150&h=150&fit=crop&crop=face' ,
59- type : 'User' ,
60- availability_status : 'Available' ,
61- custom_status : '' ,
62- enabled : 1 ,
63- first_name : 'Daniel' ,
64- creation : '2024-01-01' ,
65- modified : '2024-01-01' ,
66- owner : 'admin' ,
67- modified_by : 'admin' ,
68- docstatus : 0 ,
69- } ,
70- {
71- name : 'olivia.moore@company.com' ,
72- full_name : 'Olivia Moore' ,
73- user_image :
74- 'https://images.unsplash.com/photo-1544005313-94ddf0286df2?w=150&h=150&fit=crop&crop=face' ,
75- type : 'User' ,
76- availability_status : 'Do not disturb' ,
77- custom_status : 'In a call' ,
78- enabled : 1 ,
79- first_name : 'Olivia' ,
80- creation : '2024-01-01' ,
81- modified : '2024-01-01' ,
82- owner : 'admin' ,
83- modified_by : 'admin' ,
84- docstatus : 0 ,
85- } ,
86- {
87- name : 'david.lee@company.com' ,
88- full_name : 'David Lee' ,
89- user_image : undefined ,
90- type : 'User' ,
91- availability_status : 'Available' ,
92- custom_status : '' ,
93- enabled : 1 ,
94- first_name : 'David' ,
95- creation : '2024-01-01' ,
96- modified : '2024-01-01' ,
97- owner : 'admin' ,
98- modified_by : 'admin' ,
99- docstatus : 0 ,
100- } ,
101- ]
102-
103- const filteredUsers = mockAvailableUsers . filter (
104- ( user ) =>
105- ! selectedUsers . some ( ( selected ) => selected . name === user . name ) &&
106- ( user . full_name . toLowerCase ( ) . includes ( searchQuery . toLowerCase ( ) ) ||
107- user . name . toLowerCase ( ) . includes ( searchQuery . toLowerCase ( ) ) )
108- )
109-
110- const handleSelectUser = ( user : RavenUser ) => {
38+ const handleSelectUser = ( user : UserData ) => {
11139 onSelectUsers ( [ ...selectedUsers , user ] )
11240 setSearchQuery ( '' )
113- setAnnouncement ( `${ user . full_name } added to channel` )
41+ setAnnouncement ( _ ( `${ user . full_name } added to channel` ) )
11442 // Return focus to search input after selection
11543 setTimeout ( ( ) => {
11644 searchInputRef . current ?. focus ( )
@@ -125,7 +53,7 @@ export const AddMembersStep = ({ selectedUsers, onSelectUsers }: AddMembersStepP
12553 }
12654 }
12755
128- const handleKeyDownOnUser = ( e : React . KeyboardEvent , user : RavenUser ) => {
56+ const handleKeyDownOnUser = ( e : React . KeyboardEvent , user : UserData ) => {
12957 if ( e . key === 'Enter' || e . key === ' ' ) {
13058 e . preventDefault ( )
13159 handleSelectUser ( user )
@@ -140,7 +68,7 @@ export const AddMembersStep = ({ selectedUsers, onSelectUsers }: AddMembersStepP
14068 }
14169
14270 return (
143- < div className = "space-y-5 h-full overflow-y-auto px-6" >
71+ < div className = "flex flex-col h-full min-h-0 px-6 gap-4 " >
14472 { /* Screen reader announcements */ }
14573 < div
14674 role = "status"
@@ -152,84 +80,87 @@ export const AddMembersStep = ({ selectedUsers, onSelectUsers }: AddMembersStepP
15280 </ div >
15381
15482 { /* Description */ }
155- < div className = "text-sm text-muted-foreground" id = "search-description" >
156- Search and select team members to add to this channel. You can skip this step and invite members later.
83+ < div className = "text-sm text-muted-foreground shrink-0 " id = "search-description" >
84+ { _ ( ' Search and select team members to add to this channel. You can skip this step and invite members later.' ) }
15785 </ div >
15886
15987 { /* Selected Users as Badges */ }
16088 { selectedUsers . length > 0 && (
161- < div className = "space-y-2" >
162- < div className = "flex items-center justify-between" >
89+ < div className = "space-y-2 shrink-0 min-h-0 flex flex-col " >
90+ < div className = "flex items-center justify-between shrink-0 " >
16391 < span className = "text-xs font-medium text-muted-foreground" >
164- Selected ({ selectedUsers . length } )
92+ { _ ( ' Selected' ) } ({ selectedUsers . length } )
16593 </ span >
16694 < Button
16795 variant = "ghost"
16896 size = "sm"
16997 onClick = { ( ) => {
17098 onSelectUsers ( [ ] )
171- setAnnouncement ( 'All members removed from selection' )
99+ setAnnouncement ( _ ( 'All members removed from selection' ) )
172100 } }
173101 className = "h-6 text-xs px-3"
174102 type = "button"
175103 aria-label = { `Clear all ${ selectedUsers . length } selected members` }
176104 >
177- Clear all
105+ { _ ( ' Clear all' ) }
178106 </ Button >
179107 </ div >
180- < div className = "flex flex-wrap gap-2" role = "list" aria-label = "Selected members" >
181- { selectedUsers . map ( ( user ) => (
182- < button
183- key = { user . name }
184- type = "button"
185- className = "flex items-center gap-1 px-2 py-1 cursor-pointer hover:bg-muted transition-colors rounded-md bg-secondary text-secondary-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2"
186- onClick = { ( ) => handleRemoveSelectedUser ( user . name ) }
187- onKeyDown = { ( e ) => handleKeyDownOnBadge ( e , user . name ) }
188- aria-label = { `Remove ${ user . full_name } from selection` }
189- role = "listitem"
190- >
191- < span className = "text-xs" > { user . full_name } </ span >
192- < X className = "h-3 w-3" aria-hidden = "true" />
193- </ button >
194- ) ) }
195- </ div >
108+ < ScrollArea className = "max-h-15 min-h-0 shrink-0" >
109+ < div className = "flex flex-wrap gap-2 p-0.5" role = "list" aria-label = "Selected members" >
110+ { selectedUsers . map ( ( user ) => (
111+ < button
112+ key = { user . name }
113+ type = "button"
114+ className = "flex items-center gap-1 px-2 py-1 cursor-pointer hover:bg-muted transition-colors rounded-md bg-secondary text-secondary-foreground focus:shadow-md"
115+ onClick = { ( ) => handleRemoveSelectedUser ( user . name ) }
116+ onKeyDown = { ( e ) => handleKeyDownOnBadge ( e , user . name ) }
117+ aria-label = { _ ( `Remove ${ user . full_name } from selection` ) }
118+ role = "listitem"
119+ >
120+ < span className = "text-xs" > { user . full_name } </ span >
121+ < X className = "h-3 w-3" aria-hidden = "true" />
122+ </ button >
123+ ) ) }
124+ </ div >
125+ </ ScrollArea >
196126 </ div >
197127 ) }
198128
199129 { /* Search */ }
200- < div >
130+ < div className = "shrink-0" >
201131 < div className = "relative" >
202132 < Search className = "absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" aria-hidden = "true" />
203133 < Input
204134 ref = { searchInputRef }
205- placeholder = " Search by name or email..."
135+ placeholder = { _ ( ' Search by name or email...' ) }
206136 value = { searchQuery }
207137 onChange = { ( e ) => setSearchQuery ( e . target . value ) }
208138 className = "pl-9"
209139 type = "text"
210- aria-label = " Search team members"
140+ aria-label = { _ ( ' Search team members' ) }
211141 aria-describedby = "search-description"
212142 />
213143 </ div >
214144 </ div >
215145
216146 { /* Available Users */ }
217- < ScrollArea className = "h-[320px] " >
218- < div className = "space-y-0.5 pr-4 " role = "list" aria-label = "Available team members" >
219- { filteredUsers . map ( ( user ) => (
220- < button
147+ < ScrollArea className = "flex-1 min-h-10 " >
148+ < div className = "space-y-1 p- 0.5" role = "list" aria-label = "Available team members" >
149+ { filteredUsers ? .map ( ( user ) => (
150+ < Button
221151 key = { user . name }
222152 type = "button"
223- className = "flex items-center gap-2.5 px-2 py-2 rounded-md hover:bg-accent/50 transition-colors cursor-pointer group w-full text-left focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-1"
153+ variant = 'ghost'
154+ className = "flex items-center gap-2.5 hover:bg-accent/50 transition-colors cursor-pointer group w-full text-left h-12"
224155 onClick = { ( ) => handleSelectUser ( user ) }
225156 onKeyDown = { ( e ) => handleKeyDownOnUser ( e , user ) }
226- aria-label = { `Add ${ user . full_name } (${ user . name } ) to channel` }
157+ aria-label = { _ ( `Add ${ user . full_name } (${ user . name } ) to channel` ) }
227158 role = "listitem"
228159 >
229160 < UserAvatar
230161 user = { user as UserFields }
231162 size = "sm"
232- className = "flex- shrink-0"
163+ className = "shrink-0"
233164 showStatusIndicator = { false }
234165 />
235166 < div className = "flex-1 min-w-0" >
@@ -240,23 +171,23 @@ export const AddMembersStep = ({ selectedUsers, onSelectUsers }: AddMembersStepP
240171 { user . name }
241172 </ div >
242173 </ div >
243- < UserPlus className = "h-3.5 w-3.5 text-muted-foreground/60 group-hover:text-muted-foreground flex- shrink-0 transition-colors" aria-hidden = "true" />
244- </ button >
174+ < UserPlus className = "h-3.5 w-3.5 text-muted-foreground/60 group-hover:text-muted-foreground shrink-0 transition-colors mr-2 " aria-hidden = "true" />
175+ </ Button >
245176 ) ) }
246177 </ div >
247178
248- { filteredUsers . length === 0 && searchQuery && (
179+ { filteredUsers ? .length === 0 && filterText && (
249180 < div className = "text-center py-8 pr-4" >
250181 < p className = "text-sm text-muted-foreground" >
251- No users found matching your search.
182+ { _ ( ' No users found matching your search.' ) }
252183 </ p >
253184 </ div >
254185 ) }
255186
256- { filteredUsers . length === 0 && ! searchQuery && selectedUsers . length > 0 && (
187+ { filteredUsers ? .length === 0 && ! filterText && selectedUsers . length > 0 && (
257188 < div className = "text-center py-8 pr-4" >
258189 < p className = "text-sm text-muted-foreground" >
259- All available users have been selected.
190+ { _ ( ' All available users have been selected.' ) }
260191 </ p >
261192 </ div >
262193 ) }
0 commit comments