diff options
Diffstat (limited to 'sdnr/wt/odlux/framework/src/components/material-table')
4 files changed, 224 insertions, 138 deletions
diff --git a/sdnr/wt/odlux/framework/src/components/material-table/index.tsx b/sdnr/wt/odlux/framework/src/components/material-table/index.tsx index d89ced23a..3e31c5e03 100644 --- a/sdnr/wt/odlux/framework/src/components/material-table/index.tsx +++ b/sdnr/wt/odlux/framework/src/components/material-table/index.tsx @@ -33,19 +33,21 @@ import { EnhancedTableFilter } from './tableFilter'; import { ColumnModel, ColumnType } from './columnModel'; import { Omit } from '@material-ui/core'; import { SvgIconProps } from '@material-ui/core/SvgIcon/SvgIcon'; +import { replaceHyphen } from '../../utilities/yangHelper'; +import { string } from 'prop-types'; export { ColumnModel, ColumnType } from './columnModel'; -type propType = string | number | null | undefined | (string|number)[]; +type propType = string | number | null | undefined | (string | number)[]; type dataType = { [prop: string]: propType }; -type resultType<TData = dataType> = { page: number, rowCount: number, rows: TData[] }; +type resultType<TData = dataType> = { page: number, total: number, rows: TData[] }; -export type DataCallback<TData = dataType> = (page?: number, rowsPerPage?: number, orderBy?: string | null, order?: 'asc' | 'desc' | null, filter?: { [property: string]: string }) =>resultType<TData> | Promise<resultType<TData>>; +export type DataCallback<TData = dataType> = (page?: number, rowsPerPage?: number, orderBy?: string | null, order?: 'asc' | 'desc' | null, filter?: { [property: string]: string }) => resultType<TData> | Promise<resultType<TData>>; function desc(a: dataType, b: dataType, orderBy: string) { - if ((b[orderBy] || "") < (a[orderBy] || "") ) { + if ((b[orderBy] || "") < (a[orderBy] || "")) { return -1; } - if ((b[orderBy] || "") > (a[orderBy] || "") ) { + if ((b[orderBy] || "") > (a[orderBy] || "")) { return 1; } return 0; @@ -68,7 +70,7 @@ function getSorting(order: 'asc' | 'desc' | null, orderBy: string) { const styles = (theme: Theme) => createStyles({ root: { width: '100%', - marginTop: theme.spacing.unit * 3, + marginTop: theme.spacing(3), }, table: { minWidth: 1020, @@ -83,7 +85,7 @@ export type MaterialTableComponentState<TData = {}> = { orderBy: string | null; selected: any[] | null; rows: TData[]; - rowCount: number; + total: number; page: number; rowsPerPage: number; loading: boolean; @@ -95,18 +97,19 @@ export type TableApi = { forceRefresh?: () => Promise<void> }; type MaterialTableComponentBaseProps<TData> = WithStyles<typeof styles> & { columns: ColumnModel<TData>[]; - idProperty: keyof TData | ((data: TData) => React.Key ); + idProperty: keyof TData | ((data: TData) => React.Key); + tableId?: string; title?: string; enableSelection?: boolean; disableSorting?: boolean; disableFilter?: boolean; - customActionButtons?: { icon: React.ComponentType<SvgIconProps>, tooltip?: string, onClick: () => void }[]; + customActionButtons?: { icon: React.ComponentType<SvgIconProps>, tooltip?: string, onClick: () => void }[]; onHandleClick?(event: React.MouseEvent<HTMLTableRowElement>, rowData: TData): void; }; -type MaterialTableComponentPropsWithRows<TData={}> = MaterialTableComponentBaseProps<TData> & { rows: TData[]; asynchronus?: boolean; }; -type MaterialTableComponentPropsWithRequestData<TData={}> = MaterialTableComponentBaseProps<TData> & { onRequestData: DataCallback; tableApi?: TableApi; }; -type MaterialTableComponentPropsWithExternalState<TData={}> = MaterialTableComponentBaseProps<TData> & MaterialTableComponentState & { +type MaterialTableComponentPropsWithRows<TData = {}> = MaterialTableComponentBaseProps<TData> & { rows: TData[]; asynchronus?: boolean; }; +type MaterialTableComponentPropsWithRequestData<TData = {}> = MaterialTableComponentBaseProps<TData> & { onRequestData: DataCallback; tableApi?: TableApi; }; +type MaterialTableComponentPropsWithExternalState<TData = {}> = MaterialTableComponentBaseProps<TData> & MaterialTableComponentState & { onToggleFilter: () => void; onFilterChanged: (property: string, filterTerm: string) => void; onHandleChangePage: (page: number) => void; @@ -152,7 +155,7 @@ class MaterialTableComponent<TData extends {} = {}> extends React.Component<Mate orderBy: isMaterialTableComponentPropsWithRowsAndRequestData(this.props) ? this.props.orderBy : null, selected: isMaterialTableComponentPropsWithRowsAndRequestData(this.props) ? this.props.selected : null, rows: isMaterialTableComponentPropsWithRows(this.props) && this.props.rows.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage) || [], - rowCount: isMaterialTableComponentPropsWithRows(this.props) && this.props.rows.length || 0, + total: isMaterialTableComponentPropsWithRows(this.props) && this.props.rows.length || 0, page, rowsPerPage, }; @@ -167,58 +170,58 @@ class MaterialTableComponent<TData extends {} = {}> extends React.Component<Mate } render(): JSX.Element { const { classes, columns } = this.props; - const { rows, rowCount, order, orderBy, selected, rowsPerPage, page, showFilter, filter } = this.state; + const { rows, total: rowCount, order, orderBy, selected, rowsPerPage, page, showFilter, filter } = this.state; const emptyRows = rowsPerPage - Math.min(rowsPerPage, rowCount - page * rowsPerPage); - const getId = typeof this.props.idProperty !== "function" ? (data: TData) => ((data as {[key:string]: any })[this.props.idProperty as any as string] as string | number) : this.props.idProperty; + const getId = typeof this.props.idProperty !== "function" ? (data: TData) => ((data as { [key: string]: any })[this.props.idProperty as any as string] as string | number) : this.props.idProperty; const toggleFilter = isMaterialTableComponentPropsWithRowsAndRequestData(this.props) ? this.props.onToggleFilter : () => { !this.props.disableFilter && this.setState({ showFilter: !showFilter }, this.update) } return ( - <Paper className={ classes.root }> - <TableToolbar numSelected={ selected && selected.length } title={ this.props.title } customActionButtons={ this.props.customActionButtons } onExportToCsv={ this.exportToCsv } - onToggleFilter={ toggleFilter } /> - <div className={ classes.tableWrapper }> - <Table className={ classes.table } aria-labelledby="tableTitle"> + <Paper className={classes.root}> + <TableToolbar tableId={this.props.tableId} numSelected={selected && selected.length} title={this.props.title} customActionButtons={this.props.customActionButtons} onExportToCsv={this.exportToCsv} + onToggleFilter={toggleFilter} /> + <div className={classes.tableWrapper}> + <Table className={classes.table} aria-labelledby="tableTitle"> <EnhancedTableHead - columns={ columns } - numSelected={ selected && selected.length } - order={ order } - orderBy={ orderBy } - onSelectAllClick={ this.handleSelectAllClick } - onRequestSort={ this.onHandleRequestSort } - rowCount={ rows.length } - enableSelection={ this.props.enableSelection } + columns={columns} + numSelected={selected && selected.length} + order={order} + orderBy={orderBy} + onSelectAllClick={this.handleSelectAllClick} + onRequestSort={this.onHandleRequestSort} + rowCount={rows.length} + enableSelection={this.props.enableSelection} /> <TableBody> - { showFilter && <EnhancedTableFilter columns={ columns } filter={ filter } onFilterChanged={ this.onFilterChanged } enableSelection={this.props.enableSelection} /> || null } - { rows // may need ordering here + {showFilter && <EnhancedTableFilter columns={columns} filter={filter} onFilterChanged={this.onFilterChanged} enableSelection={this.props.enableSelection} /> || null} + {rows // may need ordering here .map((entry: TData & { [key: string]: any }) => { const entryId = getId(entry); const isSelected = this.isSelected(entryId); return ( <TableRow hover - onClick={ event => this.handleClick(event, entry, entryId) } + onClick={event => this.handleClick(event, entry, entryId)} role="checkbox" - aria-checked={ isSelected } - tabIndex={ -1 } - key={ entryId } - selected={ isSelected } + aria-checked={isSelected} + tabIndex={-1} + key={entryId} + selected={isSelected} > - { this.props.enableSelection - ? <TableCell padding="checkbox" style={ { width: "50px" } }> - <Checkbox checked={ isSelected } /> + {this.props.enableSelection + ? <TableCell padding="checkbox" style={{ width: "50px" }}> + <Checkbox checked={isSelected} /> </TableCell> - : null + : null } { this.props.columns.map( col => { - const style = col.width ? { width: col.width } : { }; + const style = col.width ? { width: col.width } : {}; return ( - <TableCell key={ col.property } align={ col.type === ColumnType.numeric && !col.align ? "right": col.align } style={ style }> - { col.type === ColumnType.custom && col.customControl - ? <col.customControl className={col.className} style={col.style} rowData={ entry } /> + <TableCell key={col.property} align={col.type === ColumnType.numeric && !col.align ? "right" : col.align} style={style}> + {col.type === ColumnType.custom && col.customControl + ? <col.customControl className={col.className} style={col.style} rowData={entry} /> : col.type === ColumnType.boolean - ? <span className={col.className} style={col.style}>{col.labels ? col.labels[entry[col.property] ? "true": "false"] : String(entry[col.property]) }</span> + ? <span className={col.className} style={col.style}>{col.labels ? col.labels[entry[col.property] ? "true" : "false"] : String(entry[col.property])}</span> : <span className={col.className} style={col.style}>{String(entry[col.property])}</span> } </TableCell> @@ -228,29 +231,29 @@ class MaterialTableComponent<TData extends {} = {}> extends React.Component<Mate } </TableRow> ); - }) } - { emptyRows > 0 && ( - <TableRow style={ { height: 49 * emptyRows } }> - <TableCell colSpan={ this.props.columns.length } /> + })} + {emptyRows > 0 && ( + <TableRow style={{ height: 49 * emptyRows }}> + <TableCell colSpan={this.props.columns.length} /> </TableRow> - ) } + )} </TableBody> </Table> </div> <TablePagination - rowsPerPageOptions={[5, 10, 20, 50] } + rowsPerPageOptions={[5, 10, 20, 50]} component="div" - count={ rowCount } - rowsPerPage={ rowsPerPage } - page={ page } - backIconButtonProps={ { + count={rowCount} + rowsPerPage={rowsPerPage} + page={page} + backIconButtonProps={{ 'aria-label': 'Previous Page', - } } - nextIconButtonProps={ { + }} + nextIconButtonProps={{ 'aria-label': 'Next Page', - } } - onChangePage={ this.onHandleChangePage } - onChangeRowsPerPage={ this.onHandleChangeRowsPerPage } + }} + onChangePage={this.onHandleChangePage} + onChangeRowsPerPage={this.onHandleChangeRowsPerPage} /> </Paper> ); @@ -261,7 +264,7 @@ class MaterialTableComponent<TData extends {} = {}> extends React.Component<Mate return { ...state, rows: props.rows, - rowCount: props.rowCount, + total: props.total, orderBy: props.orderBy, order: props.order, filter: props.filter, @@ -281,9 +284,11 @@ class MaterialTableComponent<TData extends {} = {}> extends React.Component<Mate return state; } - private static updateRows(props: MaterialTableComponentPropsWithRows, state: MaterialTableComponentState): { rows: {}[], rowCount: number } { + private static updateRows(props: MaterialTableComponentPropsWithRows, state: MaterialTableComponentState): { rows: {}[], total: number, page: number } { + + const { page, rowsPerPage, order, orderBy, filter } = state; + try { - const { page, rowsPerPage, order, orderBy, filter } = state; let data: dataType[] = props.rows || []; let filtered = false; if (state.showFilter) { @@ -292,25 +297,84 @@ class MaterialTableComponent<TData extends {} = {}> extends React.Component<Mate filtered = filtered || exp !== undefined; data = exp !== undefined ? data.filter((val) => { const value = val[prop]; - return (value == exp) || (value && value.toString().indexOf(String(exp)) > -1); + + if (value) { + + if (typeof exp === 'boolean') { + return value == exp; + + } else if (typeof exp === 'string') { + + const valueAsString = value.toString(); + if (exp.length === 0) return value; + + const regex = new RegExp("\\*", "g"); + const regex2 = new RegExp("\\?", "g"); + + const countStar = (exp.match(regex) || []).length; + const countQuestionmarks = (exp.match(regex2) || []).length; + + if (countStar > 0 || countQuestionmarks > 0) { + let editableExpression = exp; + + if (!exp.startsWith('*')) { + editableExpression = '^' + exp; + } + + if (!exp.endsWith('*')) { + editableExpression = editableExpression + '$'; + } + + const expressionAsRegex = editableExpression.replace(/\*/g, ".*").replace(/\?/g, "."); + + return valueAsString.match(new RegExp(expressionAsRegex, "g")); + } + else if (exp.includes('>=')) { + return Number(valueAsString) >= Number(exp.replace('>=', '')); + } else if (exp.includes('<=')) { + return Number(valueAsString) <= Number(exp.replace('<=', '')); + } else + if (exp.includes('>')) { + return Number(valueAsString) > Number(exp.replace('>', '')); + } else if (exp.includes('<')) { + return Number(valueAsString) < Number(exp.replace('<', '')); + } + } + } + + return (value == exp) }) : data; }); } const rowCount = data.length; - data = (orderBy && order - ? stableSort(data, getSorting(order, orderBy)) - : data).slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage); + if (page > 0 && rowsPerPage * page > rowCount) { //if result is smaller than the currently shown page, new search and repaginate + let newPage = Math.floor(rowCount / rowsPerPage); + return { + rows: data, + total: rowCount, + page: newPage + }; + } else { + data = (orderBy && order + ? stableSort(data, getSorting(order, orderBy)) + : data).slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage); + + return { + rows: data, + total: rowCount, + page: page + }; + } - return { - rows: data, - rowCount - }; - } catch{ + + } catch (e) { + console.error(e); return { rows: [], - rowCount: 0 + total: 0, + page: page } } } @@ -323,7 +387,8 @@ class MaterialTableComponent<TData extends {} = {}> extends React.Component<Mate ); this.setState(response); } else { - this.setState(MaterialTableComponent.updateRows(this.props, this.state)); + let updateResult = MaterialTableComponent.updateRows(this.props, this.state); + this.setState(updateResult); } } @@ -361,7 +426,7 @@ class MaterialTableComponent<TData extends {} = {}> extends React.Component<Mate handleSelectAllClick: () => {}; - private onHandleChangePage = (event: React.MouseEvent<HTMLButtonElement> | null, page: number) => { + private onHandleChangePage = (event: any | null, page: number) => { if (isMaterialTableComponentPropsWithRowsAndRequestData(this.props)) { this.props.onHandleChangePage(page); return; @@ -390,12 +455,12 @@ class MaterialTableComponent<TData extends {} = {}> extends React.Component<Mate return (selectedIndex > -1); } - private handleClick(event: React.MouseEvent<HTMLTableRowElement>, rowData: TData, id: string | number): void { + private handleClick(event: any, rowData: TData, id: string | number): void { if (this.props.onHandleClick instanceof Function) { this.props.onHandleClick(event, rowData); return; } - if (!this.props.enableSelection){ + if (!this.props.enableSelection) { return; } let selected = this.state.selected || []; @@ -416,20 +481,26 @@ class MaterialTableComponent<TData extends {} = {}> extends React.Component<Mate }); } + private exportToCsv = async () => { let file; let data: dataType[] | null = null; let csv: string[] = []; - if (isMaterialTableComponentPropsWithRequestData(this.props)) { + // table with extra request handler this.setState({ loading: true }); const result = await Promise.resolve( - this.props.onRequestData( 0, 1000, this.state.orderBy, this.state.order, this.state.showFilter && this.state.filter || {}) + this.props.onRequestData(0, 1000, this.state.orderBy, this.state.order, this.state.showFilter && this.state.filter || {}) ); data = result.rows; this.setState({ loading: true }); - } else { + } else if (isMaterialTableComponentPropsWithRowsAndRequestData(this.props)) { + // table with generated handlers note: exports data shown on current page + data = this.props.rows; + } + else { + // table with local data data = MaterialTableComponent.updateRows(this.props, this.state).rows; } @@ -438,7 +509,7 @@ class MaterialTableComponent<TData extends {} = {}> extends React.Component<Mate this.state.rows && this.state.rows.forEach((row: any) => { csv.push(this.props.columns.map(col => row[col.property]).join(',') + "\r\n"); }); - const properties = { type: "text/csv;charset=utf-8" }; // Specify the file's mime-type. + const properties = { type: "text/csv;charset=utf-8" }; // Specify the file's mime-type. try { // Specify the filename using the File constructor, but ... file = new File(csv, "export.csv", properties); diff --git a/sdnr/wt/odlux/framework/src/components/material-table/tableFilter.tsx b/sdnr/wt/odlux/framework/src/components/material-table/tableFilter.tsx index 0356aa225..737ea85f9 100644 --- a/sdnr/wt/odlux/framework/src/components/material-table/tableFilter.tsx +++ b/sdnr/wt/odlux/framework/src/components/material-table/tableFilter.tsx @@ -33,7 +33,7 @@ const styles = (theme: Theme) => createStyles({ flexWrap: 'wrap', }, input: { - margin: theme.spacing.unit, + margin: theme.spacing(1), }, }); @@ -45,7 +45,7 @@ interface IEnhancedTableFilterComponentProps extends WithStyles<typeof styles> { } class EnhancedTableFilterComponent extends React.Component<IEnhancedTableFilterComponentProps> { - createFilterHandler = (property: string) => (event: React.ChangeEvent<HTMLInputElement|HTMLSelectElement|HTMLTextAreaElement>) => { + createFilterHandler = (property: string) => (event: React.ChangeEvent<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement>) => { this.props.onFilterChanged && this.props.onFilterChanged(property, event.target.value); }; @@ -53,33 +53,33 @@ class EnhancedTableFilterComponent extends React.Component<IEnhancedTableFilterC const { columns, filter, classes } = this.props; return ( <TableRow> - { this.props.enableSelection - ? <TableCell padding="checkbox" style={ { width: "50px" } }> - </TableCell> - : null - } - { columns.map(col => { + {this.props.enableSelection + ? <TableCell padding="checkbox" style={{ width: "50px" }}> + </TableCell> + : null + } + {columns.map(col => { const style = col.width ? { width: col.width } : {}; return ( <TableCell - key={ col.property } - padding={ col.disablePadding ? 'none' : 'default' } - style={ style } + key={col.property} + padding={col.disablePadding ? 'none' : 'default'} + style={style} > - { col.disableFilter || (col.type === ColumnType.custom) + {col.disableFilter || (col.type === ColumnType.custom) ? null : (col.type === ColumnType.boolean) ? <Select className={classes.input} value={filter[col.property] !== undefined ? filter[col.property] : ''} onChange={this.createFilterHandler(col.property)} inputProps={{ name: `${col.property}-bool`, id: `${col.property}-bool` }} > <MenuItem value={undefined}> <em>None</em> </MenuItem> - <MenuItem value={true as any as string}>{ col.labels ? col.labels["true"]:"true"}</MenuItem> - <MenuItem value={false as any as string}>{ col.labels ? col.labels["false"] : "false"}</MenuItem> + <MenuItem value={true as any as string}>{col.labels ? col.labels["true"] : "true"}</MenuItem> + <MenuItem value={false as any as string}>{col.labels ? col.labels["false"] : "false"}</MenuItem> </Select> : <Input className={classes.input} inputProps={{ 'aria-label': 'Filter' }} value={filter[col.property] || ''} onChange={this.createFilterHandler(col.property)} />} </TableCell> ); - }, this) } + }, this)} </TableRow> ); } diff --git a/sdnr/wt/odlux/framework/src/components/material-table/tableToolbar.tsx b/sdnr/wt/odlux/framework/src/components/material-table/tableToolbar.tsx index 80d38ab52..a4080b51b 100644 --- a/sdnr/wt/odlux/framework/src/components/material-table/tableToolbar.tsx +++ b/sdnr/wt/odlux/framework/src/components/material-table/tableToolbar.tsx @@ -32,7 +32,7 @@ import { SvgIconProps } from '@material-ui/core/SvgIcon/SvgIcon'; const styles = (theme: Theme) => createStyles({ root: { - paddingRight: theme.spacing.unit, + paddingRight: theme.spacing(1), }, highlight: theme.palette.type === 'light' @@ -65,6 +65,7 @@ const styles = (theme: Theme) => createStyles({ interface ITableToolbarComponentProps extends WithStyles<typeof styles> { numSelected: number | null; title?: string; + tableId?: string; customActionButtons?: { icon: React.ComponentType<SvgIconProps>, tooltip?: string, onClick: () => void }[]; onToggleFilter: () => void; onExportToCsv: () => void; @@ -79,7 +80,7 @@ class TableToolbarComponent extends React.Component<ITableToolbarComponentProps, }; } - private handleMenu = (event: React.MouseEvent<HTMLElement>) => { + private handleMenu = (event: React.MouseEvent<HTMLElement>) => { this.setState({ anchorEl: event.currentTarget }); }; @@ -89,55 +90,55 @@ class TableToolbarComponent extends React.Component<ITableToolbarComponentProps, render() { const { numSelected, classes } = this.props; const open = !!this.state.anchorEl; - + const buttonPrefix = this.props.tableId !== undefined ? this.props.tableId + '-' : ''; return ( - <Toolbar className={ `${ classes.root } ${ numSelected && numSelected > 0 ? classes.highlight : '' } ` } > - <div className={ classes.title }> - { numSelected && numSelected > 0 ? ( - <Typography color="inherit" variant="subheading"> - { numSelected } selected + <Toolbar className={`${classes.root} ${numSelected && numSelected > 0 ? classes.highlight : ''} `} > + <div className={classes.title}> + {numSelected && numSelected > 0 ? ( + <Typography color="inherit" variant="subtitle1"> + {numSelected} selected </Typography> ) : ( - <Typography variant="headline" id="tableTitle"> - { this.props.title || null } + <Typography variant="h5" id="tableTitle"> + {this.props.title || null} </Typography> - ) } + )} </div> - <div className={ classes.spacer } /> - <div className={ classes.actions }> - { this.props.customActionButtons - ? this.props.customActionButtons.map((action, ind) =>( - <Tooltip key={ `custom-action-${ ind }`} title={action.tooltip}> - <IconButton aria-label={ `custom-action-${ind}` } onClick={() => action.onClick() }> - <action.icon /> - </IconButton> - </Tooltip> + <div className={classes.spacer} /> + <div className={classes.actions}> + {this.props.customActionButtons + ? this.props.customActionButtons.map((action, ind) => ( + <Tooltip key={`custom-action-${ind}`} title={action.tooltip}> + <IconButton aria-label={buttonPrefix + `custom-action-${ind}`} onClick={() => action.onClick()}> + <action.icon /> + </IconButton> + </Tooltip> )) - : null } - { numSelected && numSelected > 0 ? ( + : null} + {numSelected && numSelected > 0 ? ( <Tooltip title="Delete"> - <IconButton aria-label="Delete"> + <IconButton aria-label={buttonPrefix + "delete"}> <DeleteIcon /> </IconButton> </Tooltip> ) : ( <Tooltip title="Filter list"> - <IconButton aria-label="Filter list" onClick={ () => { this.props.onToggleFilter && this.props.onToggleFilter() } }> + <IconButton aria-label={buttonPrefix + "filter-list"} onClick={() => { this.props.onToggleFilter && this.props.onToggleFilter() }}> <FilterListIcon /> </IconButton> </Tooltip> - ) } + )} <Tooltip title="Actions"> <IconButton color="inherit" - aria-owns={ open ? 'menu-appbar' : undefined } + aria-owns={open ? 'menu-appbar' : undefined} aria-haspopup="true" - onClick={ this.handleMenu } > + onClick={this.handleMenu} > <MoreIcon /> </IconButton> </Tooltip> - <Menu id="menu-appbar" anchorEl={ this.state.anchorEl } anchorOrigin={ { vertical: 'top', horizontal: 'right' } } - transformOrigin={ { vertical: 'top', horizontal: 'right' } } open={ open } onClose={ this.handleClose } > - <MenuItem onClick={ this.props.onExportToCsv }>Export as CSV</MenuItem> + <Menu id="menu-appbar" anchorEl={this.state.anchorEl} anchorOrigin={{ vertical: 'top', horizontal: 'right' }} + transformOrigin={{ vertical: 'top', horizontal: 'right' }} open={open} onClose={this.handleClose} > + <MenuItem onClick={this.props.onExportToCsv}>Export as CSV</MenuItem> </Menu> </div> </Toolbar> diff --git a/sdnr/wt/odlux/framework/src/components/material-table/utilities.ts b/sdnr/wt/odlux/framework/src/components/material-table/utilities.ts index 43c3b5e35..6e8902c07 100644 --- a/sdnr/wt/odlux/framework/src/components/material-table/utilities.ts +++ b/sdnr/wt/odlux/framework/src/components/material-table/utilities.ts @@ -27,7 +27,7 @@ export interface IExternalTableState<TData> { orderBy: string | null; selected: any[] | null; rows: TData[]; - rowCount: number; + total: number; page: number; rowsPerPage: number; loading: boolean; @@ -68,13 +68,13 @@ export function createExternal<TData>(callback: DataCallback<TData>, selectState } class SetPreFilterChangedAction extends TableAction { - constructor(public preFilter: {[key: string]: string}) { + constructor(public preFilter: { [key: string]: string }) { super(); } } class SetFilterChangedAction extends TableAction { - constructor (public filter: { [key: string]: string }) { + constructor(public filter: { [key: string]: string }) { super(); } } @@ -92,7 +92,7 @@ export function createExternal<TData>(callback: DataCallback<TData>, selectState } class SetResultAction extends TableAction { - constructor(public result: { page: number, rowCount: number, rows: TData[] }) { + constructor(public result: { page: number, total: number, rows: TData[] }) { super(); } } @@ -105,7 +105,7 @@ export function createExternal<TData>(callback: DataCallback<TData>, selectState orderBy: null, selected: null, rows: [], - rowCount: 0, + total: 0, page: 0, rowsPerPage: 10, loading: false, @@ -126,14 +126,14 @@ export function createExternal<TData>(callback: DataCallback<TData>, selectState ...state, loading: false, rows: action.result.rows, - rowCount: action.result.rowCount, + total: action.result.total, page: action.result.page, } } else if (action instanceof RequestSortAction) { state = { ...state, loading: true, - orderBy : state.orderBy === action.orderBy && state.order === 'desc' ? null : action.orderBy , + orderBy: state.orderBy === action.orderBy && state.order === 'desc' ? null : action.orderBy, order: state.orderBy === action.orderBy && state.order === 'asc' ? 'desc' : 'asc', } } else if (action instanceof SetShowFilterAction) { @@ -176,9 +176,23 @@ export function createExternal<TData>(callback: DataCallback<TData>, selectState const reloadAction = (dispatch: Dispatch, getAppState: () => IApplicationStoreState) => { dispatch(new RefreshAction()); const ownState = selectState(getAppState()); - const filter = { ...ownState.preFilter, ...(ownState.showFilter && ownState.filter || {})}; - Promise.resolve(callback(ownState.page, ownState.rowsPerPage, ownState.orderBy, ownState.order, filter )).then(result => { - dispatch(new SetResultAction(result)); + const filter = { ...ownState.preFilter, ...(ownState.showFilter && ownState.filter || {}) }; + Promise.resolve(callback(ownState.page, ownState.rowsPerPage, ownState.orderBy, ownState.order, filter)).then(result => { + + if (ownState.page > 0 && ownState.rowsPerPage * ownState.page > result.total) { //if result is smaller than the currently shown page, new search and repaginate + + let newPage = Math.floor(result.total / ownState.rowsPerPage); + + Promise.resolve(callback(newPage, ownState.rowsPerPage, ownState.orderBy, ownState.order, filter)).then(result1 => { + dispatch(new SetResultAction(result1)); + }); + + + } else { + dispatch(new SetResultAction(result)); + } + + }).catch(error => new AddErrorInfoAction(error)); }; @@ -186,8 +200,8 @@ export function createExternal<TData>(callback: DataCallback<TData>, selectState return { onPreFilterChanged: (preFilter: { [key: string]: string }) => { dispatch(new SetPreFilterChangedAction(preFilter)); - (!skipRefresh) && dispatch(reloadAction); - } + (!skipRefresh) && dispatch(reloadAction); + } }; } @@ -236,7 +250,7 @@ export function createExternal<TData>(callback: DataCallback<TData>, selectState const createProperties = (state: IApplicationStoreState) => { return { ...selectState(state) - } + } } return { |