Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- import * as React from 'react';
- import clsx from 'clsx';
- import scrollbarSize from 'dom-helpers/scrollbarSize';
- import ScrollSync from "react-virtualized/dist/commonjs/ScrollSync";
- import AutoSizer from "react-virtualized/dist/commonjs/AutoSizer";
- import {Grid as RVGrid} from "react-virtualized/dist/commonjs/Grid";
- import {ContentBox} from "./ContentBox";
- import ArrowDropDownIcon from '@material-ui/icons/ArrowDropDown';
- import FirstPageIcon from '@material-ui/icons/FirstPage';
- import KeyboardArrowLeft from '@material-ui/icons/KeyboardArrowLeft';
- import KeyboardArrowRight from '@material-ui/icons/KeyboardArrowRight';
- import LastPageIcon from '@material-ui/icons/LastPage';
- import produce from "immer";
- import {
- Button,
- Grid,
- IconButton,
- makeStyles,
- Paper, Table as FTable,
- TableCell, TableFooter, TablePagination, TableRow,
- TextField,
- useTheme,
- withStyles,
- } from "@material-ui/core";
- import PropTypes from "prop-types";
- // import LastPageIcon from "@material-ui/core/SvgIcon/SvgIcon";
- import {Component} from "react";
- import Popper from "@material-ui/core/Popper";
- import Grow from "@material-ui/core/Grow";
- import ClickAwayListener from "@material-ui/core/ClickAwayListener";
- import MenuList from "@material-ui/core/MenuList";
- import MenuItem from "@material-ui/core/MenuItem";
- import Checkbox from "@material-ui/core/Checkbox";
- import InputLabel from "@material-ui/core/InputLabel";
- import Select from "@material-ui/core/Select";
- const operators = ["=", "like", "is", ">", ">=", "<", "<=",];
- const useStyles1 = theme => ({
- GridRow: {
- position: 'relative',
- // display: 'flex !important',
- flexDirection: 'row !important',
- },
- GridColumn: {
- display: 'flex',
- flexDirection: 'column',
- flex: '1 1 auto',
- },
- LeftSideGridContainer: {
- flex: '0 0 75px',
- zIndex: 999,
- display: 'flex',
- backgroundColor: '#fff',
- overflow: 'hidden',
- boxShadow: '1px 0px 2px rgba(0, 0, 0, .2)'
- },
- LeftSideGrid: {
- overflow: 'hidden !important',
- display: 'flex',
- zIndex: 999,
- boxShadow: '1px -1px 2px rgba(0, 0, 0, .2)'
- },
- HeaderGrid: {
- width: '100%',
- overflow: 'hidden !important',
- boxShadow: '1px 1px 2px rgba(0, 0, 0, .2)'
- },
- BodyGrid: {
- // width: '100%',
- },
- evenRow: {
- },
- oddRow: {
- backgroundColor: 'rgba(0, 0, 0, .1)',
- },
- selectedRow: {
- backgroundColor: 'rgba(0,185,255,0.5)'
- },
- cell: {
- width: '100%',
- height: '100%',
- display: 'flex',
- flexDirection: 'column',
- justifyContent: 'center',
- alignItems: 'center',
- textAlign: 'center',
- padding: '0 .5em',
- textOverflow: 'ellipsis',
- overflow: 'hidden',
- boxShadow: '1px 0px 2px rgba(0, 0, 0, .2)'
- // whiteSpace: 'nowrap',
- },
- navigator:{
- width: '400px',
- },
- headerCell: {
- height: '100%',
- display: 'flex',
- flexDirection: 'column',
- justifyContent: 'center',
- alignItems: 'center',
- textAlign: 'center',
- padding: '0 .5em',
- fontWeight: 'bold',
- boxShadow: '1px 1px 2px rgba(0, 0, 0, .2)'
- },
- leftCell: {
- width: '100%',
- height: '100%',
- display: 'flex',
- flexDirection: 'column',
- justifyContent: 'center',
- alignItems: 'center',
- textAlign: 'center',
- padding: '0 .5em',
- fontWeight: 'bold',
- },
- });
- function cellDefaultRenderer(rowData, col){
- const type = col.type || 'text';
- const value = rowData[col.field];
- switch (type){
- case "int":
- return value;
- case "float":
- return value;
- case "date":
- if (value===null){
- return ;
- }
- const d = new Date(value);
- return d.getFullYear()+'-'+(d.getMonth()+1)+'-'+d.getDate();
- case "img":
- return (
- <div style={{textAlign: 'center', height:"100%", width:"100%"}}>
- <img src={value}
- style={{height:"auto", width:"auto", maxHeight:"100%", maxWidth:"100%"}}
- alt="N/A"
- />
- </div>
- );
- case "bool":
- if (value===null){
- return (<div>N/A</div>)
- }
- return (
- <Grid container justify={"center"} alignItems={"center"}>
- <input type='checkbox' defaultChecked={value} disabled={true} />
- </Grid>
- );
- default:
- return (
- <Grid container justify={"center"} alignItems={"center"} title={value} >
- {value}
- </Grid>
- );
- }
- }
- class HeaderSelectCell extends React.Component {
- render() {
- const { checkboxOnClick } = this.props;
- return (
- <Grid container justify={"center"} alignItems={"center"}>
- <input type={"checkbox"} onClick={checkboxOnClick}/>
- </Grid>
- )
- }
- }
- export class DT_MuiGrid extends Component {
- constructor(props, context) {
- super(props, context);
- const {columns, records, rowHeight, selectable} = this.props;
- if (selectable&&columns[0].field!=='selected'){
- columns.unshift({component:HeaderSelectCell, field:'selected', width:50, type:'bool', fixed: true})
- }
- this.columns = columns;
- this.fixed_cols = this.columns.filter(obj=>obj.fixed===true);
- this.n_fixed_cols = this.fixed_cols.length;
- this.state = {
- columnWidth: 75,
- // height: 400,
- overscanColumnCount: 0,
- overscanRowCount: 5,
- rowHeight: rowHeight,
- rowCount: 100,
- records: records,
- page:0,
- };
- }
- render() {
- const {
- // height,
- overscanColumnCount,
- overscanRowCount,
- rowHeight,
- } = this.state;
- const {classes, records}=this.props;
- const rowCount = records.data&&records.data.length||0;
- return (
- <div style={{height: "70vh"}}>
- <AutoSizer disableWidth>
- {({height})=>(
- <ContentBox>
- <ScrollSync>
- {({
- clientHeight,
- clientWidth,
- onScroll,
- scrollHeight,
- scrollLeft,
- scrollTop,
- scrollWidth,
- }) => {
- const x = scrollLeft / (scrollWidth - clientWidth);
- const y = scrollTop / (scrollHeight - clientHeight);
- return (
- <div className={classes.GridRow}>
- <div style={{
- position: 'absolute',
- zIndex: 90,
- background: '#fff',
- }}
- >
- <div className={classes.LeftSideGridContainer}>
- <RVGrid
- cellRenderer={this._renderLeftHeaderCell}
- className={classes.HeaderGrid}
- width={this._getFixedColumnsWidth()}
- height={rowHeight}
- rowHeight={rowHeight}
- columnWidth={this._getColumnWidth}
- rowCount={1}
- columnCount={this.n_fixed_cols}
- />
- </div>
- <div
- className={classes.LeftSideGrid}
- style={{
- position: 'relate',
- left: 0,
- top: rowHeight,
- }}>
- <RVGrid
- overscanColumnCount={overscanColumnCount}
- overscanRowCount={overscanRowCount}
- cellRenderer={this._renderLeftSideCell}
- columnWidth={this._getColumnWidth}
- columnCount={this.n_fixed_cols}
- className={classes.LeftSideGrid}
- height={height + 1 - rowHeight - scrollbarSize()}
- rowHeight={rowHeight}
- rowCount={rowCount}
- scrollTop={scrollTop}
- width={this._getFixedColumnsWidth()}
- />
- </div>
- </div>
- <div >
- <div className={classes.GridColumn}>
- <AutoSizer disableHeight>
- {({width}) => (
- <div>
- <div
- style={{
- height: rowHeight,
- width: width - scrollbarSize(),
- }}>
- <RVGrid
- className={classes.HeaderGrid}
- columnWidth={this._getColumnWidth}
- columnCount={this.columns.length}
- height={rowHeight}
- overscanColumnCount={overscanColumnCount}
- cellRenderer={this._renderHeaderCell}
- rowHeight={rowHeight}
- rowCount={1}
- scrollLeft={scrollLeft}
- width={width - scrollbarSize()}
- />
- </div>
- <div
- style={{
- height,
- width,
- }}>
- <RVGrid
- className={classes.BodyGrid}
- columnWidth={this._getColumnWidth}
- columnCount={this.columns.length}
- height={height-rowHeight}
- onScroll={onScroll}
- overscanColumnCount={overscanColumnCount}
- overscanRowCount={overscanRowCount}
- cellRenderer={this._renderBodyCell}
- rowHeight={rowHeight}
- rowCount={rowCount}
- width={width}
- />
- </div>
- </div>
- )}
- </AutoSizer>
- </div>
- </div>
- </div>
- );
- }}
- </ScrollSync>
- </ContentBox>
- )}
- </AutoSizer>
- </div>
- );
- }
- _getFixedColumnsWidth = () => {
- const ret = this.fixed_cols.reduce( function(a, b){
- return a + b['width'];
- }, 0);
- return ret
- };
- _getColumnWidth = ({index, }) =>{
- const {columns, } = this;
- return columns[index]&&columns[index].width || 120
- };
- _renderBodyCell = ({columnIndex, key, rowIndex, style}) => {
- if (columnIndex < this.n_fixed_cols) {
- return;
- }
- return this._renderLeftSideCell({columnIndex, key, rowIndex, style});
- };
- _renderHeaderCell = ({columnIndex, key, rowIndex, style}) => {
- if (columnIndex < this.n_fixed_cols) {
- return;
- }
- return this._renderLeftHeaderCell({columnIndex, key, rowIndex, style});
- };
- _renderLeftHeaderCell = ({columnIndex, key, style}) => {
- const {classes, clearSelectedRow, nRowSelected} = this.props;
- const col = this.columns[columnIndex];
- let Wrapper = null;
- if (typeof col["component"] === "undefined"){
- Wrapper = ({children})=>(col.label)
- }else{
- const inner = <col.component checkboxOnClick={clearSelectedRow} defaultChecked={nRowSelected>0}/>;
- Wrapper = ({children})=>(inner)
- }
- return (
- <div className={classes.headerCell} key={key} style={style}>
- <Wrapper/>
- </div>
- );
- };
- _onCellClick = (rowIndex) => (event) => {
- const {onRowClick} = this.props;
- onRowClick(rowIndex);
- };
- _renderLeftSideCell = ({columnIndex, key, rowIndex, style}) => {
- const {classes, records , onRowClick} = this.props;
- let rowData = records.data&&records.data[rowIndex];
- const rowClass = rowData.selected ? classes.selectedRow :(
- rowIndex % 2 === 0
- ? classes.oddRow
- : classes.evenRow);
- const classNames = clsx(rowClass, classes.cell);
- const col = this.columns[columnIndex];
- const renderer = col.renderer&&typeof col.renderer==='function'?col.renderer:cellDefaultRenderer;
- return (
- <div className={classNames} key={key} style={style} onClick={this._onCellClick(rowIndex)}>
- {renderer(rowData, col)}
- </div>
- );
- };
- }
- DT_MuiGrid.propTypes = {
- classes: PropTypes.object.isRequired,
- searchForm:PropTypes.object,
- columns: PropTypes.arrayOf(
- PropTypes.shape({
- field: PropTypes.string.isRequired,
- label: PropTypes.string,
- numeric: PropTypes.bool,
- width: PropTypes.number.isRequired,
- }),
- ).isRequired,
- headerHeight: PropTypes.number,
- onRowClick: PropTypes.func,
- rowHeight: PropTypes.number,
- match: PropTypes.object.isRequired,
- selectable: PropTypes.bool,
- };
- const GridExample = withStyles(useStyles1)(DT_MuiGrid);
- // export default GridExample;
- function TablePaginationActions(props) {
- const classes = makeStyles(useStyles1)();
- const theme = useTheme();
- const { count, page, rowsPerPage, onChangePage } = props;
- function handleFirstPageButtonClick(event) {
- onChangePage(event, 0);
- }
- function handleBackButtonClick(event) {
- onChangePage(event, page - 1);
- }
- function handleNextButtonClick(event) {
- onChangePage(event, page + 1);
- }
- function handleLastPageButtonClick(event) {
- onChangePage(event, Math.max(0, Math.ceil(count / rowsPerPage) - 1));
- }
- return (
- <div className={classes.navigator}>
- <IconButton
- onClick={handleFirstPageButtonClick}
- disabled={page === 0}
- aria-label="first page"
- >
- {theme.direction === 'rtl' ? <LastPageIcon /> : <FirstPageIcon />}
- </IconButton>
- <IconButton onClick={handleBackButtonClick} disabled={page === 0} aria-label="previous page">
- {theme.direction === 'rtl' ? <KeyboardArrowRight /> : <KeyboardArrowLeft />}
- </IconButton>
- <IconButton
- onClick={handleNextButtonClick}
- disabled={page >= Math.ceil(count / rowsPerPage) - 1}
- aria-label="next page"
- >
- {theme.direction === 'rtl' ? <KeyboardArrowLeft /> : <KeyboardArrowRight />}
- </IconButton>
- <IconButton
- onClick={handleLastPageButtonClick}
- disabled={page >= Math.ceil(count / rowsPerPage) - 1}
- aria-label="last page"
- >
- {theme.direction === 'rtl' ? <FirstPageIcon /> : <LastPageIcon />}
- </IconButton>
- </div>
- );
- }
- TablePaginationActions.propTypes = {
- count: PropTypes.number.isRequired,
- onChangePage: PropTypes.func.isRequired,
- page: PropTypes.number.isRequired,
- rowsPerPage: PropTypes.number.isRequired,
- };
- class DT_MuiToolbar extends Component {
- constructor(props){
- super(props);
- const {searches} = this.props;
- this.anchorRef = null;
- this.state = {
- open:false,
- searches_form:searches.map((col, index)=>(
- {enabled:false, field:col.field, operator: col.operator, value: ''}
- )),
- };
- this.global_searchValue = "";
- }
- form = () => {
- const {searches, } = this.props;
- if (this.global_searchValue){
- return {
- //TODO set operator according to column type
- searches:searches
- .filter((item, index, array)=>(
- (this.global_searchValue.match(/^[+-]?\d+(\.\d+)?$/) &&
- (item.type==="int" ||item.type==="float")) || (item.type==="text")
- ))
- .map((item, index)=>(
- {field:item.field, operator: item.operator, value: this.global_searchValue}
- )),
- searchLogic:"OR",
- }
- }
- return {
- searches: this.state.searches_form
- .filter((item, index, array)=>{
- return item.enabled
- })
- .map((item, index)=> {
- if (item.enabled){
- return {field:item.field, operator: item.operator, value: item.value}
- }
- }),
- searchLogic:"AND",
- }
- };
- getsearchForm = () => {
- const {page, rowsPerPage} = this.props;
- return {
- page:page,
- rowsPerPage:rowsPerPage,
- ...this.form(),
- }
- };
- refreshData = () => {
- const { recordsSetter, api_url} = this.props;
- const form = this.getsearchForm();
- fetch(api_url, {
- method:"post",
- headers: {'Content-Type':'application/json'},
- body:JSON.stringify(form)
- })
- .then(res => res.json())
- .then(data=>{recordsSetter(data);})
- .catch(err=>(console.log(err)));
- };
- handleSearchBtnClick = (event) => {
- event.preventDefault();
- this.refreshData();
- };
- componentDidMount(){
- this.refreshData();
- }
- componentDidUpdate(prevProps, prevState, snapshot){
- const {page, rowsPerPage} = this.props;
- if (page!==prevProps.page || rowsPerPage!==prevProps.rowsPerPage){
- this.refreshData();
- }
- }
- handleInputChange = (event)=>{
- this.global_searchValue = event.target.value;
- };
- handleToggle = () => {
- this.setState({open:!this.state.open})
- };
- handleClose = (event) => {
- if (this.anchorRef && this.anchorRef.contains(event.target)) {
- return;
- }
- this.setState({open:!this.state.open})
- };
- handleCheckBox = index => event => {
- //this.state.searches[index].enabled = event.target.checked;
- const value = event.target.checked;
- this.setState(produce(draft=>{
- draft.searches_form[index].enabled = value;
- }));
- };
- handleOperatorSelect = index => event => {
- const value = event.target.value;
- this.setState(produce(draft=>{
- draft.searches_form[index].operator = value;
- }));
- };
- handleSearchValue = index => event => {
- const value = event.target.value;
- this.setState(produce(draft=>{
- draft.searches_form[index].value = value;
- }));
- };
- render(){
- const { classes, searches, buttons, toolbar} = this.props;
- return (
- <Grid container className={classes.toolbar_form} spacing={1} alignItems="center" style={{padding: "0 10px"}}>
- <Grid item>
- <form className={classes.container} noValidate
- autoComplete="off"
- onSubmit={this.handleSearchBtnClick}
- >
- <TextField
- id="searchValue"
- label="Search"
- className={classes.textField}
- margin="dense"
- variant="outlined"
- placeholder="All Fields"
- size="small"
- onChange={this.handleInputChange}
- />
- </form>
- </Grid>
- <Grid item >
- <Button
- className={classes.fab}
- color="primary"
- // size="small"
- aria-owns={this.state.open ? 'menu-list-grow' : undefined}
- aria-haspopup="true"
- onClick={this.handleToggle}
- ref={anchorRef=>{this.anchorRef=anchorRef}}
- >
- <ArrowDropDownIcon />
- </Button>
- <Popper open={this.state.open}
- anchorEl={this.anchorRef}
- transition disablePortal
- style={{zIndex: 999}}
- >
- {({ TransitionProps, placement }) => (
- <Grow
- {...TransitionProps}
- style={{transformOrigin: placement === 'bottom' ? 'center top' : 'center bottom' }}
- >
- <Paper style={{overflowY:"scroll", height:'70vh'}}>
- <ClickAwayListener onClickAway={this.handleClose}>
- <MenuList disableListWrap>
- {searches.map((col, index)=>(
- <MenuItem dense disableGutters>
- <Checkbox
- checked={this.state.searches_form[index].enabled}
- onChange={this.handleCheckBox(index)}
- />
- <InputLabel>
- {col.label}
- </InputLabel>
- <Select
- value={this.state.searches_form[index].operator}
- onChange={this.handleOperatorSelect(index)}
- >
- {operators.map((item, index)=>(
- <MenuItem value={item} >{item}</MenuItem>
- ))}
- </Select>
- <TextField
- value={this.state.searches_form[index].value}
- onChange={this.handleSearchValue(index)}
- />
- </MenuItem>
- ))}
- </MenuList>
- </ClickAwayListener>
- </Paper>
- </Grow>
- )}
- </Popper>
- </Grid>
- <Grid item >
- <Button variant="contained" className={classes.button} color="primary" onClick={this.handleSearchBtnClick}>
- Search
- </Button>
- </Grid>
- {buttons.map((item, index)=>(
- <Grid item>
- <Button variant="contained" className={classes.button} color="primary" onClick={item.handler}>
- {item.caption}
- </Button>
- </Grid>
- ))}
- <Grid item>
- {React.createElement(toolbar)}
- </Grid>
- </Grid>
- )
- }
- }
- DT_MuiToolbar.propTypes = {
- classes: PropTypes.object.isRequired,
- api_url: PropTypes.string.isRequired,
- searchForm:PropTypes.object,
- columns: PropTypes.arrayOf(
- PropTypes.shape({
- field: PropTypes.string.isRequired,
- label: PropTypes.string,
- numeric: PropTypes.bool,
- width: PropTypes.number.isRequired,
- }),
- ).isRequired,
- searches:PropTypes.arrayOf(
- PropTypes.shape({
- field: PropTypes.string.isRequired,
- label: PropTypes.string.isRequired,
- type: PropTypes.string,
- width: PropTypes.number.isRequired,
- }),
- ),
- fixed_searches: PropTypes.arrayOf(
- PropTypes.shape(PropTypes.object),
- ),
- recordsSetter: PropTypes.func.isRequired,
- headerHeight: PropTypes.number,
- page: PropTypes.number.isRequired,
- rowsPerPage: PropTypes.number.isRequired,
- rowHeight: PropTypes.number,
- };
- const DT_Toolbar = withStyles(useStyles1)(DT_MuiToolbar);
- export class MuiDynamicTable extends Component{
- constructor(props){
- super(props);
- const {settings, } = this.props;
- //TODO FetchSetting
- // setting start
- this.api_url = settings.api_url;
- this.columns = settings.columns;
- this.searches = settings.searches;
- this.rowsPerPageOptions = settings.rowsPerPageOptions;
- this.rowsPerPage = settings.rowsPerPage;
- this.buttons = settings.buttons;
- this.toolbar = settings.toolbar;
- this.rowHeight = settings.rowHeight||120;
- this.selectable = settings.selectable;
- this.state = {
- records:{},
- page:0,
- nRowSelected:0,
- };
- }
- handleChangePage = (event, newPage) => {
- this.setState({page: newPage});
- };
- setrowsPerPage = (value) => {
- this.rowsPerPage = value;
- };
- handleChangeRowsPerPage = (event) => {
- const value = event.target.value;
- this.setrowsPerPage(parseInt(value, 0));
- this.setState({page: 0});
- };
- // TODO
- onRowClick = (rowIndex) => {
- this.setState(produce(draftState=>{
- const rowData = draftState.records.data[rowIndex];
- if (typeof rowData["selected"] === "undefined"){
- rowData.selected = false
- }
- if (rowData.selected){
- draftState.nRowSelected--
- }else{
- draftState.nRowSelected++
- }
- rowData.selected = !rowData.selected;
- }));
- // this.setState({nRowSelected: this.state.nRowSelected+1});
- this.forceUpdate();
- };
- // TODO
- clearSelectedRow = (event) => {
- this.setState(produce ((draft)=>{
- for (let i=0; i<draft.records.data.length; i++){
- draft.records.data[i].selected = false
- }
- }));
- };
- render(){
- const {classes, match} = this.props;
- return(
- <Paper style={{ height: '100%', width: '100%' }}>
- <DT_Toolbar
- recordsSetter={(newRecords)=>(this.setState({records:newRecords}))}
- page={this.state.page}
- rowsPerPage={this.rowsPerPage}
- api_url = {this.api_url}
- columns = {this.columns}
- searches = {this.searches}
- buttons = {this.buttons||[]}
- toolbar = {this.toolbar}
- />
- <GridExample
- columns={this.columns}
- records={this.state.records}
- onRowClick={this.onRowClick}
- match={match}
- rowHeight={this.rowHeight}
- selectable={this.selectable}
- clearSelectedRow={this.clearSelectedRow}
- nRowSelected={this.state.nRowSelected}
- />
- <div style={{display:"block"}}>
- <FTable className={classes.table}>
- <TableFooter>
- <TableRow>
- <TablePagination
- rowsPerPageOptions={this.rowsPerPageOptions}
- colSpan={3}
- count={this.state.records.total||0}
- rowsPerPage={this.rowsPerPage}
- page={this.state.records.page||0}
- SelectProps={{
- inputProps: { 'aria-label': 'rows per page' },
- native: true,
- }}
- onChangePage={this.handleChangePage}
- onChangeRowsPerPage={this.handleChangeRowsPerPage}
- ActionsComponent={TablePaginationActions}
- />
- </TableRow>
- </TableFooter>
- </FTable>
- </div>
- </Paper>
- )}
- }
- MuiDynamicTable.propTypes = {
- classes: PropTypes.object.isRequired,
- setting_url: PropTypes.string,
- match: PropTypes.object.isRequired,
- settings: PropTypes.object.isRequired,
- };
- const DynamicTable = withStyles(useStyles1)(MuiDynamicTable);
- export default DynamicTable;
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement