@@ -32,7 +32,7 @@ import {
32
32
ToolbarItem ,
33
33
TooltipProps ,
34
34
} from '@patternfly/react-core'
35
- import { FilterIcon } from '@patternfly/react-icons'
35
+ import { ExportIcon , FilterIcon } from '@patternfly/react-icons'
36
36
import CaretDownIcon from '@patternfly/react-icons/dist/js/icons/caret-down-icon'
37
37
import {
38
38
expandable ,
@@ -71,12 +71,15 @@ import {
71
71
} from 'react'
72
72
import { AcmButton } from '../AcmButton/AcmButton'
73
73
import { AcmEmptyState } from '../AcmEmptyState/AcmEmptyState'
74
+ import { AcmToastContext } from '../AcmAlert/AcmToast'
74
75
import { useTranslation } from '../../lib/acm-i18next'
75
76
import { usePaginationTitles } from '../../lib/paginationStrings'
76
77
import { filterLabelMargin , filterOption , filterOptionBadge } from './filterStyles'
77
78
import { AcmManageColumn } from './AcmManageColumn'
78
79
import { useNavigate , useLocation } from 'react-router-dom-v5-compat'
79
80
import { ParsedQuery , parse , stringify } from 'query-string'
81
+ import { IAlertContext } from '../AcmAlert/AcmAlert'
82
+ import { createDownloadFile } from '../../resources/utils'
80
83
81
84
type SortFn < T > = ( a : T , b : T ) => number
82
85
type CellFn < T > = ( item : T ) => ReactNode
@@ -98,6 +101,9 @@ export interface IAcmTableColumn<T> {
98
101
/** cell content, either on field name of using cell function */
99
102
cell : CellFn < T > | string
100
103
104
+ /** exported value as a string, supported export: CSV*/
105
+ exportContent ?: CellFn < T >
106
+
101
107
transforms ?: ITransform [ ]
102
108
103
109
cellTransforms ?: ITransform [ ]
@@ -480,6 +486,8 @@ export type AcmTableProps<T> = {
480
486
showColumManagement ?: boolean
481
487
nonZeroCount ?: boolean
482
488
indeterminateCount ?: boolean
489
+ showExportButton ?: boolean
490
+ exportFilePrefix ?: string
483
491
}
484
492
485
493
export function AcmTable < T > ( props : AcmTableProps < T > ) {
@@ -501,6 +509,8 @@ export function AcmTable<T>(props: AcmTableProps<T>) {
501
509
showColumManagement,
502
510
nonZeroCount,
503
511
indeterminateCount,
512
+ showExportButton,
513
+ exportFilePrefix,
504
514
} = props
505
515
506
516
const defaultSort = {
@@ -511,6 +521,7 @@ export function AcmTable<T>(props: AcmTableProps<T>) {
511
521
const initialSearch = props . initialSearch || ''
512
522
513
523
const { t } = useTranslation ( )
524
+ const toastContext = useContext ( AcmToastContext )
514
525
515
526
// State that can come from context or component state (perPage)
516
527
const [ statePerPage , stateSetPerPage ] = useState ( props . initialPerPage || DEFAULT_ITEMS_PER_PAGE )
@@ -816,6 +827,51 @@ export function AcmTable<T>(props: AcmTableProps<T>) {
816
827
}
817
828
} , [ page , actualPage , setPage ] )
818
829
830
+ const exportTable = useCallback (
831
+ ( toastContext : IAlertContext ) => {
832
+ toastContext . addAlert ( {
833
+ title : t ( 'Generating data. Download may take a moment to start.' ) ,
834
+ type : 'info' ,
835
+ autoClose : true ,
836
+ } )
837
+
838
+ const fileNamePrefix = exportFilePrefix ?? 'table-values'
839
+ const headerString : string [ ] = [ ]
840
+ const csvExportCellArray : string [ ] = [ ]
841
+
842
+ selectedSortedCols . forEach ( ( { header } ) => {
843
+ header && headerString . push ( header )
844
+ } )
845
+ csvExportCellArray . push ( headerString . join ( ',' ) )
846
+
847
+ sorted . forEach ( ( { item } ) => {
848
+ let contentString : string [ ] = [ ]
849
+ selectedSortedCols . forEach ( ( { header, exportContent } ) => {
850
+ if ( header ) {
851
+ // if callback and its output exists, add to array, else add "-"
852
+ exportContent && exportContent ( item )
853
+ ? contentString . push ( exportContent ( item ) as string )
854
+ : contentString . push ( '-' )
855
+ }
856
+ } )
857
+ contentString = [ contentString . join ( ',' ) ]
858
+ contentString [ 0 ] && csvExportCellArray . push ( contentString [ 0 ] )
859
+ } )
860
+
861
+ const exportString = csvExportCellArray . join ( '\n' )
862
+ const fileName = `${ fileNamePrefix } -${ Date . now ( ) } .csv`
863
+
864
+ createDownloadFile ( fileName , exportString , 'text/csv' )
865
+
866
+ toastContext . addAlert ( {
867
+ title : t ( 'Export successful' ) ,
868
+ type : 'success' ,
869
+ autoClose : true ,
870
+ } )
871
+ } ,
872
+ [ selectedSortedCols , sorted , exportFilePrefix , t ]
873
+ )
874
+
819
875
const paged = useMemo < ITableItem < T > [ ] > ( ( ) => {
820
876
const start = ( actualPage - 1 ) * perPage
821
877
return sorted . slice ( start , start + perPage )
@@ -1053,6 +1109,7 @@ export function AcmTable<T>(props: AcmTableProps<T>) {
1053
1109
const hasItems = ( items && items . length > 0 && filtered ) || nonZeroCount || indeterminateCount
1054
1110
const showToolbar = props . showToolbar !== false ? hasItems : false
1055
1111
const topToolbarStyle = items ? { } : { paddingBottom : 0 }
1112
+ const [ isExportMenuOpen , setIsExportMenuOpen ] = useState ( false )
1056
1113
1057
1114
const translatedPaginationTitles = usePaginationTitles ( )
1058
1115
@@ -1158,12 +1215,55 @@ export function AcmTable<T>(props: AcmTableProps<T>) {
1158
1215
< TableActions actions = { tableActions } selections = { selected } items = { items } keyFn = { keyFn } />
1159
1216
) }
1160
1217
{ customTableAction && < ToolbarItem > { customTableAction } </ ToolbarItem > }
1161
- { showColumManagement && (
1162
- < AcmManageColumn < T >
1163
- { ...{ selectedColIds, setSelectedColIds, requiredColIds, defaultColIds, setColOrderIds, colOrderIds } }
1164
- allCols = { columns . filter ( ( col ) => ! col . isActionCol ) }
1165
- />
1166
- ) }
1218
+ < ToolbarGroup spaceItems = { { default : 'spaceItemsNone' } } >
1219
+ { showColumManagement && (
1220
+ < ToolbarItem >
1221
+ < AcmManageColumn < T >
1222
+ { ...{
1223
+ selectedColIds,
1224
+ setSelectedColIds,
1225
+ requiredColIds,
1226
+ defaultColIds,
1227
+ setColOrderIds,
1228
+ colOrderIds,
1229
+ } }
1230
+ allCols = { columns . filter ( ( col ) => ! col . isActionCol ) }
1231
+ />
1232
+ </ ToolbarItem >
1233
+ ) }
1234
+ { showExportButton && (
1235
+ < ToolbarItem key = { `export-toolbar-item` } >
1236
+ < Dropdown
1237
+ onSelect = { ( event ) => {
1238
+ event ?. stopPropagation ( )
1239
+ setIsExportMenuOpen ( false )
1240
+ } }
1241
+ className = "export-dropdownMenu"
1242
+ toggle = {
1243
+ < DropdownToggle
1244
+ toggleIndicator = { null }
1245
+ onToggle = { ( value , event ) => {
1246
+ event . stopPropagation ( )
1247
+ setIsExportMenuOpen ( value )
1248
+ } }
1249
+ aria-label = "export-search-result"
1250
+ id = "export-search-result"
1251
+ >
1252
+ < ExportIcon />
1253
+ </ DropdownToggle >
1254
+ }
1255
+ isOpen = { isExportMenuOpen }
1256
+ isPlain
1257
+ dropdownItems = { [
1258
+ < DropdownItem key = "export-csv" onClick = { ( ) => exportTable ( toastContext ) } >
1259
+ { t ( 'Export as CSV' ) }
1260
+ </ DropdownItem > ,
1261
+ ] }
1262
+ position = { 'left' }
1263
+ />
1264
+ </ ToolbarItem >
1265
+ ) }
1266
+ </ ToolbarGroup >
1167
1267
{ additionalToolbarItems }
1168
1268
{ ( ! props . autoHidePagination || filtered . length > perPage ) && (
1169
1269
< ToolbarItem variant = "pagination" >
0 commit comments