1
- import { useMemo } from 'react' ;
1
+ import { SetStateAction , useMemo } from 'react' ;
2
+ import { useState } from "react" ;
2
3
import OpenInNewIcon from "@mui/icons-material/OpenInNew" ;
4
+ import Grid from '@mui/material/Grid' ;
5
+ import Button from '@mui/material/Button' ;
3
6
import type {
4
7
DecodedValueMap ,
5
8
QueryParamConfigMap ,
@@ -9,11 +12,13 @@ import { useDebounce } from "usehooks-ts";
9
12
import {
10
13
useMaterialReactTable ,
11
14
MaterialReactTable ,
15
+ MRT_TableHeadCellFilterContainer ,
12
16
type MRT_ColumnDef ,
13
17
type MRT_PaginationState ,
14
18
type MRT_Updater ,
15
19
type MRT_ColumnFiltersState ,
16
20
type MRT_TableOptions ,
21
+ type MRT_TableInstance ,
17
22
} from 'material-react-table' ;
18
23
import { type Theme } from "@mui/material/styles" ;
19
24
import { parse } from "date-fns" ;
@@ -31,6 +36,10 @@ import {
31
36
RunStatuses ,
32
37
} from "../../lib/paddles.d" ;
33
38
import useDefaultTableOptions from "../../lib/table" ;
39
+ import Typography from '@mui/material/Typography' ;
40
+ import Box from '@mui/material/Box' ;
41
+ import Badge from '@mui/material/Badge' ;
42
+ import Menu from '@mui/material/Menu' ;
34
43
35
44
36
45
const DEFAULT_PAGE_SIZE = 25 ;
@@ -45,7 +54,6 @@ const _columns: MRT_ColumnDef<Run>[] = [
45
54
header : "link" ,
46
55
maxSize : 12 ,
47
56
enableColumnFilter : false ,
48
- enableColumnActions : false ,
49
57
Cell : ( { row } ) => {
50
58
return (
51
59
< IconLink to = { `/runs/${ row . original . name } ` } >
@@ -62,14 +70,14 @@ const _columns: MRT_ColumnDef<Run>[] = [
62
70
return row . original . status . replace ( "finished " , "" ) ;
63
71
} ,
64
72
filterSelectOptions : Object . values ( RunStatuses ) ,
65
- size : 40 ,
66
- enableColumnActions : false ,
73
+ maxSize : 25 ,
67
74
} ,
68
75
{
69
76
accessorKey : "user" ,
70
77
header : "user" ,
71
78
maxSize : 45 ,
72
79
enableColumnActions : false ,
80
+ enableColumnFilter : false ,
73
81
} ,
74
82
{
75
83
id : "scheduled" ,
@@ -81,23 +89,23 @@ const _columns: MRT_ColumnDef<Run>[] = [
81
89
const date_ : string [ ] = row . original . scheduled . split ( " " ) ;
82
90
return < div > { date_ [ 0 ] } < br /> { date_ [ 1 ] } </ div >
83
91
} ,
84
- size : 50 ,
92
+ size : 35 ,
85
93
} ,
86
94
{
87
95
id : "started" ,
88
96
header : "started" ,
89
97
accessorFn : ( row : Run ) => formatDate ( row . started ) ,
90
98
enableColumnFilter : false ,
91
99
sortingFn : "datetime" ,
92
- size : 125 ,
100
+ size : 35 ,
93
101
} ,
94
102
{
95
103
id : "posted" ,
96
104
header : "updated" ,
97
105
accessorFn : ( row : Run ) => formatDate ( row . posted ) ,
98
106
enableColumnFilter : false ,
99
107
sortingFn : "datetime" ,
100
- size : 125 ,
108
+ maxSize : 35 ,
101
109
} ,
102
110
{
103
111
id : "runtime" ,
@@ -115,16 +123,16 @@ const _columns: MRT_ColumnDef<Run>[] = [
115
123
{
116
124
accessorKey : "suite" ,
117
125
header : "suite" ,
118
- size : 70 ,
126
+ size : 50 ,
119
127
} ,
120
128
{
121
129
accessorKey : "branch" ,
122
130
header : "branch" ,
123
- maxSize : 75 ,
131
+ maxSize : 70 ,
124
132
} ,
125
133
{
126
134
accessorKey : "machine_type" ,
127
- header : "machine_type " ,
135
+ header : "machine type " ,
128
136
size : 30 ,
129
137
} ,
130
138
{
@@ -200,6 +208,9 @@ type RunListProps = {
200
208
}
201
209
202
210
export default function RunList ( props : RunListProps ) {
211
+ const [ openFilterMenu , setOpenFilterMenu ] = useState < boolean > ( false ) ;
212
+ const [ dropMenuAnchorEl , setDropMenuAnchor ] = useState < null | HTMLElement > ( null ) ;
213
+
203
214
const { params, setter, tableOptions } = props ;
204
215
const options = useDefaultTableOptions < Run > ( ) ;
205
216
const debouncedParams = useDebounce ( params , 500 ) ;
@@ -220,6 +231,15 @@ export default function RunList(props: RunListProps) {
220
231
pageIndex : params . page || 0 ,
221
232
pageSize : params . pageSize || DEFAULT_PAGE_SIZE ,
222
233
} ;
234
+ const toggleFilterMenu = ( event : { currentTarget : SetStateAction < HTMLElement | null > ; } ) => {
235
+ if ( dropMenuAnchorEl ) {
236
+ setDropMenuAnchor ( null ) ;
237
+ setOpenFilterMenu ( false ) ;
238
+ } else {
239
+ setDropMenuAnchor ( event . currentTarget ) ;
240
+ setOpenFilterMenu ( true ) ;
241
+ }
242
+ }
223
243
const onColumnFiltersChange = ( updater : MRT_Updater < MRT_ColumnFiltersState > ) => {
224
244
if ( ! ( updater instanceof Function ) ) return ;
225
245
const result : RunListParams = { pageSize : pagination . pageSize } ;
@@ -267,12 +287,19 @@ export default function RunList(props: RunListProps) {
267
287
data : data ,
268
288
manualPagination : true ,
269
289
manualFiltering : true ,
290
+ enableColumnActions : false ,
270
291
onPaginationChange,
271
292
rowCount : Infinity ,
272
293
muiPaginationProps : {
273
294
showLastButton : false ,
274
295
} ,
275
296
onColumnFiltersChange,
297
+ columnFilterDisplayMode : 'custom' ,
298
+ enableColumnFilters : false ,
299
+ muiFilterTextFieldProps : ( { column } ) => ( {
300
+ label : column . columnDef . header ,
301
+ placeholder : '' ,
302
+ } ) ,
276
303
initialState : {
277
304
...options . initialState ,
278
305
columnVisibility : {
@@ -297,8 +324,115 @@ export default function RunList(props: RunListProps) {
297
324
if ( category ) return { className : category } ;
298
325
return { } ;
299
326
} ,
327
+ renderTopToolbarCustomActions : ( { table } ) => (
328
+ < Box sx = { { padding : '4px' } } >
329
+ < Badge
330
+ color = "primary"
331
+ overlap = "circular"
332
+ badgeContent = { table . getState ( ) . columnFilters . reduce ( ( count , filter ) => ( filter . value ? count + 1 : count ) , 0 ) }
333
+ >
334
+ < Button
335
+ id = "filter-button"
336
+ onClick = { toggleFilterMenu }
337
+ >
338
+ Filters
339
+ </ Button >
340
+ </ Badge >
341
+ </ Box >
342
+ ) ,
300
343
...tableOptions ,
301
344
} ) ;
345
+
302
346
if ( query . isError ) return null ;
303
- return < MaterialReactTable table = { table } />
347
+ return (
348
+ < div >
349
+ < div >
350
+ < Typography variant = "body2" gutterBottom color = "gray" textAlign = { "right" } >
351
+ { table . getState ( ) . columnFilters . map ( ( column ) => {
352
+ let filterValue = column . value ;
353
+ if ( column . id == "scheduled" ) {
354
+ const parsedDate = new Date ( column . value as string ) ;
355
+ filterValue = ( parsedDate . toISOString ( ) . split ( 'T' ) [ 0 ] )
356
+ }
357
+ return ( column . value ? `${ column . id } : '${ filterValue } ' ` : "" )
358
+ } ) }
359
+ </ Typography >
360
+ < Menu
361
+ id = "filter-menu"
362
+ anchorEl = { dropMenuAnchorEl }
363
+ open = { openFilterMenu }
364
+ onClose = { toggleFilterMenu }
365
+ MenuListProps = { {
366
+ 'aria-labelledby' : 'filter-button' ,
367
+ } }
368
+ >
369
+ < FilterMenu isOpen = { openFilterMenu } table = { table } />
370
+ </ Menu >
371
+ </ div >
372
+ < MaterialReactTable table = { table } />
373
+ </ div >
374
+ )
375
+ }
376
+
377
+
378
+ // FilterMenu
379
+
380
+ type FilterMenuProps = {
381
+ isOpen : boolean ;
382
+ table : MRT_TableInstance < Run > ;
383
+ } ;
384
+
385
+
386
+ const FILTER_SECTIONS = [ "run" , "build" , "result" ]
387
+ const FILTER_SECTIONS_COLUMNS = [
388
+ [ "scheduled" , "suite" , "machine_type" , "user" ] ,
389
+ [ "branch" , "sha1" ] ,
390
+ [ "status" ] ,
391
+ ]
392
+
393
+ function FilterMenu ( { isOpen, table} : FilterMenuProps ) {
394
+ if ( ! isOpen ) {
395
+ return null ;
396
+ }
397
+
398
+ return (
399
+ < Box
400
+ sx = { {
401
+ padding : '1em' ,
402
+ margin : '0px 0.5em' ,
403
+ border : '2px dashed grey' ,
404
+ borderRadius : '8px' ,
405
+ } }
406
+ >
407
+ { FILTER_SECTIONS_COLUMNS . map ( ( _ , sectionIndex ) => (
408
+ < Box
409
+ key = { `section-${ sectionIndex } ` }
410
+ sx = { {
411
+ marginBottom : '1em' ,
412
+ marginLeft : '0.5em' ,
413
+ } }
414
+ >
415
+ < Typography variant = "body2" gutterBottom color = "gray" >
416
+ Filter by { FILTER_SECTIONS [ sectionIndex ] } details...
417
+ </ Typography >
418
+ < Grid container spacing = { 1 } alignItems = "center" >
419
+ { table . getLeafHeaders ( ) . map ( ( header ) => {
420
+ if ( FILTER_SECTIONS_COLUMNS [ sectionIndex ] . includes ( header . id ) ) {
421
+ return (
422
+ < Grid item xs = { 2 } key = { header . id } marginLeft = { "1.2em" } >
423
+ < MRT_TableHeadCellFilterContainer
424
+ header = { header }
425
+ table = { table }
426
+ style = { { backgroundColor : 'transparent' , width : '100%' } }
427
+ />
428
+ </ Grid >
429
+ ) ;
430
+ }
431
+ return null ;
432
+ } ) }
433
+ </ Grid >
434
+ </ Box >
435
+ ) ) }
436
+ </ Box >
437
+ )
304
438
}
0 commit comments