5
5
Toolbar ,
6
6
ToolbarContent ,
7
7
ToolbarItem ,
8
+ ToolbarFilter ,
8
9
Pagination ,
9
10
Drawer ,
10
11
DrawerContent ,
@@ -14,6 +15,7 @@ import {
14
15
import { Table , Thead , Tr , Th , ThProps , Tbody , Td } from '@patternfly/react-table' ;
15
16
import { fetch } from 'cross-fetch'
16
17
import { DetailsDrawer } from './DetailsDrawer' ;
18
+ import { ImageTableFilter } from './ImageTableFilter' ;
17
19
interface ImageData {
18
20
name : string ;
19
21
version : string ;
@@ -24,7 +26,8 @@ interface ImageData {
24
26
date : string ;
25
27
selflink : URL ;
26
28
virt : string ;
27
- } ;
29
+ }
30
+
28
31
29
32
export const ImageTable : React . FunctionComponent = ( ) => {
30
33
const [ search , setSearch ] = React . useState ( '' ) ;
@@ -35,15 +38,137 @@ export const ImageTable: React.FunctionComponent = () => {
35
38
const [ perPage , setPerPage ] = React . useState ( 20 ) ;
36
39
const [ isDrawerExpanded , setIsExpanded ] = React . useState ( false ) ;
37
40
const [ selectedImage , setSelectedImage ] = React . useState ( { } ) ;
41
+
42
+ const [ providerSelections , setProviderSelections ] = React . useState < string [ ] > ( [ ] ) ;
43
+ const [ regionSelections , setRegionSelections ] = React . useState < string [ ] > ( [ ] ) ;
44
+ const [ architectureSelections , setArchitectureSelections ] = React . useState < string [ ] > ( [ ] ) ;
45
+ const [ filterConfig , setFilterConfig ] = React . useState < object [ ] > ( [ ] ) ;
46
+
38
47
const drawerRef = React . useRef < HTMLDivElement > ( ) ;
39
48
49
+ const onFilter = ( image : ImageData ) => {
50
+ const matchesProviderValue = providerSelections . includes ( image . provider . toLowerCase ( ) ) ;
51
+ const matchesRegionValue = regionSelections . includes ( image . region . toLowerCase ( ) ) ;
52
+ const matchesArchitectureValue = architectureSelections . includes ( image . arch . toLowerCase ( ) ) ;
53
+
54
+ return (
55
+ ( providerSelections . length === 0 || matchesProviderValue )
56
+ && ( regionSelections . length === 0 || matchesRegionValue )
57
+ && ( architectureSelections . length === 0 || matchesArchitectureValue )
58
+ ) ;
59
+ } ;
60
+
61
+ const onFilterSelect = ( _ : React . MouseEvent | undefined , itemId : string | number | undefined ) => {
62
+ if ( ! itemId || typeof itemId !== 'string' ) {
63
+ return ;
64
+ }
65
+
66
+ const itemIdElements = itemId . split ( '/' ) ;
67
+ const category = itemIdElements [ 0 ] ;
68
+ const item = itemIdElements [ 1 ] ;
69
+ let selections : string [ ] = [ ] ;
70
+
71
+ switch ( category ) {
72
+ case 'provider' :
73
+ selections = providerSelections . includes ( item )
74
+ ? providerSelections . filter ( ( fil : string ) => fil !== item )
75
+ : [ item , ...providerSelections ]
76
+ setProviderSelections ( selections ) ;
77
+ break ;
78
+ case 'region' :
79
+ selections = regionSelections . includes ( item )
80
+ ? regionSelections . filter ( ( fil : string ) => fil !== item )
81
+ : [ item , ...regionSelections ]
82
+ setRegionSelections ( selections ) ;
83
+ break ;
84
+ case 'architecture' :
85
+ selections = architectureSelections . includes ( item )
86
+ ? architectureSelections . filter ( ( fil : string ) => fil !== item )
87
+ : [ item , ...architectureSelections ]
88
+ setArchitectureSelections ( selections ) ;
89
+ break ;
90
+ default :
91
+ break ;
92
+ }
93
+ setPage ( 1 ) ;
94
+ } ;
95
+
96
+ const onFilterDelete = ( type : string , id : string ) => {
97
+ switch ( type ) {
98
+ case 'provider' :
99
+ if ( id === 'all' ) {
100
+ setProviderSelections ( [ ] ) ;
101
+ } else {
102
+ setProviderSelections ( providerSelections . filter ( ( fil : string ) => fil !== id ) ) ;
103
+ }
104
+ break ;
105
+ case 'region' :
106
+ if ( id === 'all' ) {
107
+ setRegionSelections ( [ ] ) ;
108
+ } else {
109
+ setRegionSelections ( regionSelections . filter ( ( fil : string ) => fil !== id ) ) ;
110
+ }
111
+ break ;
112
+ case 'architecture' :
113
+ if ( id === 'all' ) {
114
+ setArchitectureSelections ( [ ] ) ;
115
+ } else {
116
+ setArchitectureSelections ( architectureSelections . filter ( ( fil : string ) => fil !== id ) ) ;
117
+ }
118
+ break ;
119
+ default :
120
+ setProviderSelections ( [ ] ) ;
121
+ setRegionSelections ( [ ] ) ;
122
+ setArchitectureSelections ( [ ] ) ;
123
+ break ;
124
+ }
125
+ setPage ( 1 ) ;
126
+ } ;
127
+
128
+ const isFilterSelected = ( category : string , itemId : string ) : boolean => {
129
+ let filterSelected = false ;
130
+ switch ( category ) {
131
+ case 'provider' :
132
+ filterSelected = providerSelections . includes ( itemId ) ;
133
+ break ;
134
+ case 'region' :
135
+ filterSelected = regionSelections . includes ( itemId ) ;
136
+ break ;
137
+ case 'architecture' :
138
+ filterSelected = architectureSelections . includes ( itemId ) ;
139
+ break ;
140
+ default :
141
+ break ;
142
+ }
143
+ return filterSelected ;
144
+ } ;
145
+
146
+ const getSelectedFiltersByCategory = ( category : string ) : string [ ] => {
147
+ let filters = [ ] as string [ ] ;
148
+ switch ( category ) {
149
+ case 'provider' :
150
+ filters = providerSelections ;
151
+ break ;
152
+ case 'region' :
153
+ filters = regionSelections ;
154
+ break ;
155
+ case 'architecture' :
156
+ filters = architectureSelections ;
157
+ break ;
158
+ default :
159
+ break ;
160
+ }
161
+ return filters ;
162
+ } ;
163
+
164
+
40
165
const onDrawerExpand = ( ) => {
41
166
drawerRef . current && drawerRef . current . focus ( ) ;
42
167
} ;
43
168
44
169
const onDrawerOpenClick = ( details : object ) => {
45
170
setIsExpanded ( true ) ;
46
- setSelectedImage ( details )
171
+ setSelectedImage ( details ) ;
47
172
} ;
48
173
49
174
const onDrawerCloseClick = ( ) => {
@@ -56,21 +181,35 @@ export const ImageTable: React.FunctionComponent = () => {
56
181
region : 'Region' ,
57
182
arch : 'Architecture' ,
58
183
date : 'Release Date'
59
- }
184
+ } ;
60
185
61
186
useEffect ( ( ) => {
62
- loadImageData ( )
187
+ loadImageData ( ) ;
63
188
} , [ ] )
64
189
65
190
const loadImageData = ( ) => {
66
191
fetch ( 'https://imagedirectory.cloud/images/v2/all' , {
67
192
method : 'get' ,
68
193
} )
69
194
. then ( res => res . json ( ) )
70
- . then ( data => {
71
- setImageData ( data )
195
+ . then ( ( data ) => {
196
+ setImageData ( data ) ;
197
+ setFilterConfig ( [
198
+ {
199
+ category : 'provider' ,
200
+ data : [ ...new Set ( data . map ( item => item [ 'provider' ] ) ) ]
201
+ } ,
202
+ {
203
+ category : 'region' ,
204
+ data : [ ...new Set ( data . map ( item => item [ 'region' ] ) ) ]
205
+ } ,
206
+ {
207
+ category : 'architecture' ,
208
+ data : [ ...new Set ( data . map ( item => item [ 'arch' ] . toLowerCase ( ) ) ) ]
209
+ }
210
+ ] ) ;
72
211
} )
73
- }
212
+ } ;
74
213
75
214
const handleSearch = ( event ) => {
76
215
setIsExpanded ( false ) ;
@@ -109,11 +248,12 @@ export const ImageTable: React.FunctionComponent = () => {
109
248
setPage ( newPage ) ;
110
249
} ;
111
250
112
- const searchedImageData = imageData . filter ( ( item : ImageData ) =>
251
+ const filteredImageData = imageData . filter ( onFilter ) ;
252
+ const searchedImageData = filteredImageData . filter ( ( item : ImageData ) =>
113
253
item . name . toLowerCase ( ) . includes ( search . toLowerCase ( ) )
114
254
) ;
115
255
116
- const paginatedImageData = searchedImageData . slice ( ( page - 1 ) * perPage , page * perPage )
256
+ const paginatedImageData = searchedImageData . slice ( ( page - 1 ) * perPage , page * perPage ) ;
117
257
118
258
let sortedImageData = paginatedImageData ;
119
259
if ( activeSortIndex !== undefined ) {
@@ -147,7 +287,9 @@ export const ImageTable: React.FunctionComponent = () => {
147
287
return (
148
288
< React . Fragment >
149
289
< Title headingLevel = 'h1' > Browse Images</ Title >
150
- < Toolbar id = "toolbar-top" >
290
+ < Toolbar
291
+ id = "toolbar-top"
292
+ clearAllFilters = { ( ) => onFilterDelete ( '' , '' ) } >
151
293
< ToolbarContent >
152
294
< ToolbarItem variant = "search-filter" >
153
295
< SearchInput
@@ -156,6 +298,29 @@ export const ImageTable: React.FunctionComponent = () => {
156
298
placeholder = 'Search by name'
157
299
/>
158
300
</ ToolbarItem >
301
+ { filterConfig . map ( ( filter : object ) => {
302
+ const category = filter [ 'category' ] ;
303
+ const data = filter [ 'data' ] ;
304
+ return (
305
+ < ToolbarItem key = { category } >
306
+ < ToolbarFilter
307
+ chips = { getSelectedFiltersByCategory ( category ) }
308
+ deleteChip = { ( category , chip ) => onFilterDelete ( category as string , chip as string ) }
309
+ deleteChipGroup = { ( ) => onFilterDelete ( category , 'all' ) }
310
+ categoryName = { category }
311
+ >
312
+ < ImageTableFilter
313
+ onFilterSelect = { onFilterSelect }
314
+ category = { category }
315
+ filters = { data }
316
+ isFilterSelected = { isFilterSelected }
317
+ totalFilterCount = { getSelectedFiltersByCategory ( category ) . length } />
318
+ </ ToolbarFilter >
319
+ </ ToolbarItem >
320
+ )
321
+ }
322
+ ) }
323
+
159
324
< ToolbarItem alignment = { {
160
325
default : 'alignRight'
161
326
} } >
0 commit comments