@@ -4,21 +4,29 @@ import {
4
4
Badge ,
5
5
Box ,
6
6
Center ,
7
+ Grid ,
7
8
Group ,
9
+ Input ,
10
+ Pagination ,
8
11
ScrollArea ,
9
12
Select ,
10
13
Stack ,
11
14
Text ,
15
+ TextInput ,
12
16
Title ,
17
+ createStyles ,
13
18
useMantineTheme ,
14
19
} from '@mantine/core'
20
+ import { useInputState } from '@mantine/hooks'
15
21
import { showNotification } from '@mantine/notifications'
16
22
import {
23
+ mdiAccountGroupOutline ,
17
24
mdiAccountOutline ,
18
25
mdiBadgeAccountHorizontalOutline ,
19
26
mdiCheck ,
20
27
mdiClose ,
21
28
mdiEmailOutline ,
29
+ mdiIdentifier ,
22
30
mdiPhoneOutline ,
23
31
mdiStar ,
24
32
} from '@mdi/js'
@@ -44,43 +52,76 @@ const iconProps = {
44
52
color : 'gray' ,
45
53
}
46
54
55
+ const useGridStyles = createStyles ( ( theme ) => ( {
56
+ root : {
57
+ flexDirection : 'row' ,
58
+ flexGrow : 1 ,
59
+ gap : 0 ,
60
+ } ,
61
+
62
+ col : {
63
+ display : 'flex' ,
64
+ flexDirection : 'row' ,
65
+ alignItems : 'center' ,
66
+ justifyContent : 'flex-start' ,
67
+ gap : theme . spacing . xs ,
68
+ boxSizing : 'border-box' ,
69
+ padding : `0 ${ theme . spacing . xs } ` ,
70
+ height : '1.5rem' ,
71
+ } ,
72
+
73
+ input : {
74
+ userSelect : 'none' ,
75
+ lineHeight : 1 ,
76
+ fontSize : '1rem' ,
77
+ } ,
78
+ } ) )
79
+
47
80
const MemberItem : FC < MemberItemProps > = ( props ) => {
48
81
const { user, isCaptain, isRegistered } = props
49
82
const theme = useMantineTheme ( )
50
83
51
84
const { t } = useTranslation ( )
85
+ const { classes } = useGridStyles ( )
52
86
53
87
return (
54
88
< Group noWrap spacing = "xl" position = "apart" >
55
89
< Group noWrap w = "calc(100% - 16rem)" miw = "500px" >
56
90
< Avatar alt = "avatar" src = { user . avatar } >
57
91
{ user . userName ?. slice ( 0 , 1 ) ?? 'U' }
58
92
</ Avatar >
59
- < Group noWrap >
60
- < Stack spacing = { 2 } w = "15rem" >
61
- < Group noWrap spacing = "xs" >
62
- < Icon path = { mdiAccountOutline } { ...iconProps } />
63
- < Group noWrap >
64
- < Text fw = { 700 } > { user . userName } </ Text >
65
- < Text > { ! user . realName ? '' : user . realName } </ Text >
66
- </ Group >
67
- </ Group >
68
- < Group noWrap spacing = "xs" >
69
- < Icon path = { mdiBadgeAccountHorizontalOutline } { ...iconProps } />
70
- < Text > { ! user . stdNumber ? t ( 'admin.placeholder.empty' ) : user . stdNumber } </ Text >
71
- </ Group >
72
- </ Stack >
73
- < Stack spacing = { 2 } >
74
- < Group noWrap spacing = "xs" >
75
- < Icon path = { mdiEmailOutline } { ...iconProps } />
76
- < Text > { ! user . email ? t ( 'admin.placeholder.empty' ) : user . email } </ Text >
77
- </ Group >
78
- < Group noWrap spacing = "xs" >
79
- < Icon path = { mdiPhoneOutline } { ...iconProps } />
80
- < Text > { ! user . phone ? t ( 'admin.placeholder.empty' ) : user . phone } </ Text >
81
- </ Group >
82
- </ Stack >
83
- </ Group >
93
+ < Grid className = { classes . root } >
94
+ < Grid . Col span = { 3 } className = { classes . col } >
95
+ < Icon path = { mdiIdentifier } { ...iconProps } />
96
+ < Text fw = { 700 } > { user . userName } </ Text >
97
+ </ Grid . Col >
98
+ < Grid . Col span = { 3 } className = { classes . col } >
99
+ < Icon path = { mdiBadgeAccountHorizontalOutline } { ...iconProps } />
100
+ < Input
101
+ variant = "unstyled"
102
+ value = { user . stdNumber || t ( 'admin.placeholder.empty' ) }
103
+ readOnly
104
+ classNames = { { input : classes . input } }
105
+ />
106
+ </ Grid . Col >
107
+ < Grid . Col span = { 6 } className = { classes . col } >
108
+ < Icon path = { mdiEmailOutline } { ...iconProps } />
109
+ < Text > { user . email || t ( 'admin.placeholder.empty' ) } </ Text >
110
+ </ Grid . Col >
111
+ < Grid . Col span = { 6 } className = { classes . col } >
112
+ < Icon path = { mdiAccountOutline } { ...iconProps } />
113
+ < Input
114
+ variant = "unstyled"
115
+ value = { user . realName || t ( 'admin.placeholder.empty' ) }
116
+ readOnly
117
+ classNames = { { input : classes . input } }
118
+ />
119
+ </ Grid . Col >
120
+ < Grid . Col span = { 6 } className = { classes . col } >
121
+ < Icon path = { mdiPhoneOutline } { ...iconProps } />
122
+ < Text > { user . phone || t ( 'admin.placeholder.empty' ) } </ Text >
123
+ </ Grid . Col >
124
+ </ Grid >
84
125
</ Group >
85
126
< Group noWrap position = "right" >
86
127
{ isCaptain && (
@@ -176,17 +217,22 @@ const ParticipationItem: FC<ParticipationItemProps> = (props) => {
176
217
)
177
218
}
178
219
220
+ const PART_NUM_PER_PAGE = 10
221
+
179
222
const GameTeamReview : FC = ( ) => {
180
223
const navigate = useNavigate ( )
181
224
const { id } = useParams ( )
182
225
const numId = parseInt ( id ?? '-1' )
183
226
const [ disabled , setDisabled ] = useState ( false )
184
227
const [ selectedStatus , setSelectedStatus ] = useState < ParticipationStatus | null > ( null )
228
+ const [ selectedOrg , setSelectedOrg ] = useState < string | null > ( null )
185
229
const [ participations , setParticipations ] = useState < ParticipationInfoModel [ ] > ( )
230
+ const [ search , setSearch ] = useInputState ( '' )
186
231
const { classes } = useAccordionStyles ( )
187
232
const participationStatusMap = useParticipationStatusMap ( )
188
233
189
234
const { t } = useTranslation ( )
235
+ const [ activePage , setPage ] = useState ( 1 )
190
236
191
237
const setParticipationStatus = async ( id : number , status : ParticipationStatus ) => {
192
238
setDisabled ( true )
@@ -207,6 +253,10 @@ const GameTeamReview: FC = () => {
207
253
}
208
254
}
209
255
256
+ useEffect ( ( ) => {
257
+ setPage ( 1 )
258
+ } , [ selectedStatus , selectedOrg , search ] )
259
+
210
260
useEffect ( ( ) => {
211
261
if ( numId < 0 ) {
212
262
showNotification ( {
@@ -223,21 +273,57 @@ const GameTeamReview: FC = () => {
223
273
} )
224
274
} , [ ] )
225
275
276
+ const orgs = Array . from ( new Set ( participations ?. map ( ( p ) => p . organization ?? '' ) ?? [ ] ) ) . filter (
277
+ ( org ) => ! ! org
278
+ )
279
+
280
+ const filteredParticipations = participations ?. filter (
281
+ ( participation ) =>
282
+ ( selectedStatus === null || participation . status === selectedStatus ) &&
283
+ ( selectedOrg === null || participation . organization === selectedOrg ) &&
284
+ ( search === '' || participation . team ?. name ?. toLowerCase ( ) . includes ( search . toLowerCase ( ) ) )
285
+ )
286
+
287
+ const pagedParticipations = filteredParticipations ?. slice (
288
+ ( activePage - 1 ) * PART_NUM_PER_PAGE ,
289
+ activePage * PART_NUM_PER_PAGE
290
+ )
291
+
226
292
return (
227
293
< WithGameEditTab
228
294
headProps = { { position : 'apart' } }
229
295
isLoading = { ! participations }
230
296
head = {
231
- < Select
232
- placeholder = { t ( 'admin.content.show_all' ) }
233
- clearable
234
- data = { Array . from ( participationStatusMap , ( v ) => ( { value : v [ 0 ] , label : v [ 1 ] . title } ) ) }
235
- value = { selectedStatus }
236
- onChange = { ( value : ParticipationStatus ) => setSelectedStatus ( value ) }
237
- />
297
+ < Group position = "apart" noWrap w = "100%" >
298
+ < TextInput
299
+ w = "20rem"
300
+ placeholder = { t ( 'admin.placeholder.teams.search' ) }
301
+ value = { search }
302
+ onChange = { setSearch }
303
+ rightSection = { < Icon path = { mdiAccountGroupOutline } size = { 1 } /> }
304
+ />
305
+ < Group position = "right" noWrap >
306
+ { orgs . length && (
307
+ < Select
308
+ placeholder = { t ( 'admin.content.show_all' ) }
309
+ clearable
310
+ data = { orgs . map ( ( org ) => ( { value : org , label : org } ) ) }
311
+ value = { selectedOrg }
312
+ onChange = { ( value : string ) => setSelectedOrg ( value ) }
313
+ />
314
+ ) }
315
+ < Select
316
+ placeholder = { t ( 'admin.content.show_all' ) }
317
+ clearable
318
+ data = { Array . from ( participationStatusMap , ( v ) => ( { value : v [ 0 ] , label : v [ 1 ] . title } ) ) }
319
+ value = { selectedStatus }
320
+ onChange = { ( value : ParticipationStatus ) => setSelectedStatus ( value ) }
321
+ />
322
+ </ Group >
323
+ </ Group >
238
324
}
239
325
>
240
- < ScrollArea type = "auto " pos = "relative" h = "calc(100vh - 180px)" offsetScrollbars >
326
+ < ScrollArea type = "never " pos = "relative" h = "calc(100vh - 250px)" >
241
327
{ ! participations || participations . length === 0 ? (
242
328
< Center h = "calc(100vh - 200px)" >
243
329
< Stack spacing = { 0 } >
@@ -252,20 +338,23 @@ const GameTeamReview: FC = () => {
252
338
classNames = { classes }
253
339
className = { classes . root }
254
340
>
255
- { participations ?. map (
256
- ( participation ) =>
257
- ( selectedStatus === null || participation . status === selectedStatus ) && (
258
- < ParticipationItem
259
- key = { participation . id }
260
- participation = { participation }
261
- disabled = { disabled }
262
- setParticipationStatus = { setParticipationStatus }
263
- />
264
- )
265
- ) }
341
+ { pagedParticipations ?. map ( ( participation ) => (
342
+ < ParticipationItem
343
+ key = { participation . id }
344
+ participation = { participation }
345
+ disabled = { disabled }
346
+ setParticipationStatus = { setParticipationStatus }
347
+ />
348
+ ) ) }
266
349
</ Accordion >
267
350
) }
268
351
</ ScrollArea >
352
+ < Pagination
353
+ position = "right"
354
+ value = { activePage }
355
+ onChange = { setPage }
356
+ total = { ( filteredParticipations ?. length ?? 0 ) / PART_NUM_PER_PAGE + 1 }
357
+ />
269
358
</ WithGameEditTab >
270
359
)
271
360
}
0 commit comments