Procházet zdrojové kódy

Merge branch 'master' of phyothandar/mokkon-reactjs into master

sainw před 3 roky
rodič
revize
3958386e82

+ 6 - 1
package.json

@@ -49,5 +49,10 @@
   },
   "files": [
     "dist"
-  ]
+  ],
+  "dependencies": {
+    "@material-ui/core": "^4.11.2",
+    "@material-ui/icons": "^4.11.2",
+    "create-react-library": "^3.1.1"
+  }
 }

+ 392 - 0
src/FieldEditor/FieldEditor.js

@@ -0,0 +1,392 @@
+import React, { useEffect, useRef } from 'react';
+import PropTypes from 'prop-types';
+import {
+    TextField,
+    Grid,
+    InputBase,
+    Button,
+    NativeSelect,
+    Box,
+    Typography
+} from '@material-ui/core';
+import { withStyles, makeStyles } from '@material-ui/core/styles';
+import SaveIcon from '@material-ui/icons/Save';
+import CancelIcon from '@material-ui/icons/Cancel';
+
+const BootstrapInput = withStyles((theme) => ({
+    root: {
+        'label + &': {
+            marginTop: theme.spacing(3),
+        },
+    },
+    input: {
+        borderRadius: 4,
+        position: 'relative',
+        backgroundColor: 'transparent',
+        border: '1px solid #ced4da',
+        fontSize: 16,
+        padding: '10px 26px 10px 12px',
+        transition: theme.transitions.create(['border-color', 'box-shadow']),
+        // Use the system font instead of the default Roboto font.
+        fontFamily: [
+            '-apple-system',
+            'BlinkMacSystemFont',
+            '"Segoe UI"',
+            'Roboto',
+            '"Helvetica Neue"',
+            'Arial',
+            'sans-serif',
+            '"Apple Color Emoji"',
+            '"Segoe UI Emoji"',
+            '"Segoe UI Symbol"',
+        ].join(','),
+        '&:focus': {
+            borderRadius: 4,
+            borderColor: '#80bdff',
+            boxShadow: '0 0 0 0.2rem rgba(0,123,255,.25)',
+        },
+    },
+}))(InputBase);
+
+const useStyles = makeStyles((theme) => ({
+    root: {
+        width: '100%',
+    },
+    paper: {
+        width: '100%',
+        marginBottom: theme.spacing(2),
+    },
+    table: {
+        minWidth: 750,
+    },
+    visuallyHidden: {
+        border: 0,
+        clip: 'rect(0 0 0 0)',
+        height: 1,
+        margin: -1,
+        overflow: 'hidden',
+        padding: 0,
+        position: 'absolute',
+        top: 20,
+        width: 1,
+    },
+    underline: {
+        "&&&:before": {
+            borderBottom: "none"
+        },
+        "&&:after": {
+            borderBottom: "none"
+        }
+    }
+}));
+
+function FieldEditor(props) {
+    const classes = useStyles();
+    const { fields = [],
+        currentTabName = "account",
+        isNew = false,
+        buttonColor = 'blue',
+        updateData = {},
+        onCreate,
+        onUpdate,
+        onInvite,
+        onCancel,
+        onApproved } = props;
+    const [data, setDataField] = React.useState(updateData != undefined ? updateData : {});
+    const [imgPreview, setImgPreviewPath] = React.useState(null);
+
+    const handleTextString = (e, fieldName) => {
+        if (isNew) {
+            setDataField({ ...data, [fieldName]: e.target.value });
+        } else {
+            setDataField({ ...data, [fieldName]: e.target.value });
+        }
+    }
+
+    const handleTextNumber = (e, fieldName) => {
+        if (isNew) {
+            setDataField({ ...data, [fieldName]: e.target.value });
+        } else {
+            setDataField({ ...data, [fieldName]: e.target.value });
+        }
+    }
+
+    const handleTextMultiline = (e, fieldName) => {
+        if (isNew) {
+            setDataField({ ...data, [fieldName]: e.target.value });
+        } else {
+            setDataField({ ...data, [fieldName]: e.target.value });
+        }
+    }
+
+    const handleDate = (e, fieldName) => {
+        if (isNew) {
+            setDataField({ ...data, [fieldName]: e.target.value });
+        } else {
+            setDataField({ ...data, [fieldName]: e.target.value });
+        }
+    }
+
+    const handleDropDownChange = (e, fieldName) => {
+        var selectedIndex = e.target.options.selectedIndex;
+        var selectedValue = e.target.options[selectedIndex].getAttribute('name');
+
+        var fn = fieldName.split('_');
+        var fieldId = fn[0] + '_' + 'id';
+
+        if (isNew) {
+            setDataField({ ...data, [fieldName]: selectedValue, [fieldId]: e.target.value });
+
+        } else {
+            setDataField({ ...data, [fieldName]: selectedValue, [fieldId]: e.target.value });
+
+        }
+    }
+
+    const handleSave = () => {
+        if (isNew) {
+            onCreate(data);
+        } else {
+            onUpdate(data);
+        }
+    }
+
+    const handleApproved = () => {
+        onApproved(data);
+    }
+
+    const handleCancel = () => {
+        onCancel();
+    }
+
+    const handleInvite = () => {
+        onInvite(data);
+    }
+
+    const handleImgUpload = (e, fieldName) => {
+        e.preventDefault();
+        let reader = new FileReader();
+        let file = e.target.files[0];
+
+        reader.onloadend = () => {
+            setImgPreviewPath(reader.result);
+        }
+        reader.readAsDataURL(file);
+        // setDataField({ ...data, [fieldName]: e.target.files[0].name });
+    }
+    console.log('file upload :', data);
+
+    return (
+        <div className={classes.root}>
+            <Grid container>
+                <Grid item xs={11}>
+                    {fields.map((f, i) => {
+
+                        if (f.type == 'text_string') {
+                            return <Grid key={f.fieldName} container style={{ display: 'flex', alignItems: 'center', marginBottom: '10px' }}>
+                                <Grid item xs={12} sm={5}>
+                                    <Box style={{ width: '150px' }}>
+                                        <Typography style={{ paddingRight: '30px', color: 'grey' }}>{f.label}</Typography>
+                                    </Box>
+                                </Grid>
+                                <Grid item xs={12} sm={7}>
+                                    <TextField id={f.fieldName}
+                                        variant="outlined"
+                                        autoComplete="off"
+                                        size={"small"}
+                                        style={{ width: '100%' }}
+                                        value={data != undefined ? data[f.fieldName] : null}
+                                        onChange={(e) => handleTextString(e, f.fieldName)}
+                                    />
+                                </Grid>
+                            </Grid>;
+                        }
+                        else if (f.type == 'text_number') {
+                            return <Grid key={f.fieldName} container style={{ display: 'flex', alignItems: 'center', marginBottom: '10px' }}>
+                                <Grid item xs={12} sm={5}>
+                                    <Box style={{ width: '150px' }}>
+                                        <Typography style={{ paddingRight: '30px', color: 'grey' }}>{f.label}</Typography>
+                                    </Box>
+                                </Grid>
+                                <Grid item xs={12} sm={7}>
+                                    <TextField
+                                        id={f.fieldName}
+                                        variant="outlined"
+                                        autoComplete="off"
+                                        size={"small"}
+                                        style={{ width: '100%' }}
+                                        type="number"
+                                        value={data != undefined ? data[f.fieldName] : ''}
+                                        // onChange={handleTextNumber}
+                                        onChange={(e) => handleTextNumber(e, f.fieldName)}
+                                    />
+                                </Grid>
+                            </Grid>;
+                        }
+                        else if (f.type == 'text_multiline') {
+                            return <Grid key={f.fieldName} container style={{ display: 'flex', alignItems: 'center', marginBottom: '10px' }}>
+                                <Grid item xs={12} sm={5}>
+                                    <Box style={{ width: '150px' }}>
+                                        <Typography style={{ paddingRight: '30px', color: 'grey' }}>{f.label}</Typography>
+                                    </Box>
+                                </Grid>
+                                <Grid item xs={12} sm={7}>
+                                    <TextField
+                                        id={f.fieldName}
+                                        multiline
+                                        autoComplete="off"
+                                        rows={3}
+                                        size={"small"}
+                                        style={{ width: '100%' }}
+                                        value={data != undefined ? data[f.fieldName] : ''}
+                                        variant="outlined"
+                                        onChange={(e) => handleTextMultiline(e, f.fieldName)}
+                                    />
+                                </Grid>
+                            </Grid>;
+                        }
+                        else if (f.type == 'date') {
+                            return <Grid key={f.fieldName} container style={{ display: 'flex', alignItems: 'center', marginBottom: '10px' }}>
+                                <Grid item xs={12} sm={5}>
+                                    <Box style={{ width: '150px' }}>
+                                        <Typography style={{ paddingRight: '30px', color: 'grey' }}>{f.label}</Typography>
+                                    </Box>
+                                </Grid>
+                                <Grid item xs={12} sm={7}>
+                                    <TextField
+                                        id={f.fieldName}
+                                        variant="outlined"
+                                        autoComplete="off"
+                                        size={"small"}
+                                        value={data != undefined ? data[f.fieldName] : ''}
+                                        type="date"
+                                        style={{ width: '100%' }}
+                                        // onChange={handleDate}
+                                        onChange={(e) => handleDate(e, f.fieldName)}
+                                    />
+                                </Grid>
+                            </Grid>;
+                        }
+                        else if (f.type == 'photo') {
+                            return <Grid key={f.fieldName} container style={{ display: 'flex', alignItems: 'center', marginBottom: '10px' }}>
+                                <Grid item xs={12} sm={5}>
+                                    <Box style={{ width: '150px' }}>
+                                        <Typography style={{ paddingRight: '30px', color: 'grey' }}>{f.label}</Typography>
+                                    </Box>
+                                </Grid>
+                                <Grid item xs={12} sm={7}>
+                                    <img src={imgPreview == null ? data['photo_url'] : imgPreview} style={{ width: '120px', height: '120px', border: '1px solid grey' }}></img>
+                                    <input type='file' id='img-upload' onChange={(e) => handleImgUpload(e, f.fieldName)}></input>
+                                </Grid>
+                            </Grid>;
+                        }
+                        else if (f.type == 'dropdown') {
+                            if (f.fieldName == 'priority') {
+
+                                return <Grid key={f.fieldName} container style={{ display: 'flex', alignItems: 'center', marginBottom: '10px' }}>
+                                    <Grid item xs={12} sm={5}>
+                                        <Box style={{ width: '150px' }}>
+                                            <Typography style={{ paddingRight: '30px', color: 'grey' }}>{f.label}</Typography>
+                                        </Box>
+                                    </Grid>
+                                    <Grid item xs={12} sm={7}>
+                                        <NativeSelect
+                                            id="demo-customized-select-native"
+                                            value={data != undefined ? data[f.fieldName] : ''}
+                                            onChange={(e) => handleDropDownChange(e, f.fieldName)}
+                                            id={f.fieldName}
+                                            input={<BootstrapInput />}
+                                            style={{ width: '100%' }}
+                                        >
+                                            <option aria-label="None" value="" >Select</option>
+                                            {f.options.map((d, i) => {
+                                                return <option name={d.name} value={d.id}>{d.name}</option>;
+                                            })}
+                                        </NativeSelect>
+                                    </Grid>
+                                </Grid>;
+                            } else {
+                                var fn = f.fieldName.split('_');
+                                var fieldId = fn[0] + '_' + 'id';
+
+                                return <Grid key={f.fieldName} container style={{ display: 'flex', alignItems: 'center', marginBottom: '10px' }}>
+                                    <Grid item xs={12} sm={5}>
+                                        <Box style={{ width: '150px' }}>
+                                            <Typography style={{ paddingRight: '30px', color: 'grey' }}>{f.label}</Typography>
+                                        </Box>
+                                    </Grid>
+                                    <Grid item xs={12} sm={7}>
+                                        <NativeSelect
+                                            id="demo-customized-select-native"
+                                            value={data != undefined ? data[fieldId] : ''}
+                                            onChange={(e) => handleDropDownChange(e, f.fieldName)}
+                                            id={f.fieldName}
+                                            input={<BootstrapInput />}
+                                            style={{ width: '100%' }}
+                                        >
+                                            <option aria-label="None" value="" >Select</option>
+                                            {f.options.map((d, i) => {
+                                                return <option name={d.name} value={d.id}>{d.name}</option>;
+                                            })}
+                                        </NativeSelect>
+                                    </Grid>
+                                </Grid>;
+                            }
+                        }
+                    })}
+
+                </Grid>
+
+                {currentTabName == 'account' ?
+                    <Grid item xs={11}>
+                        <Box>
+                            <Button style={{ color: 'white', backgroundColor: buttonColor != undefined ? buttonColor : 'blue', float: 'right' }}
+                                onClick={handleInvite}
+                            > Invite</Button>
+                        </Box>
+                    </Grid>
+                    :
+                    <Grid item xs={11}>
+                        <Box>
+                            <Button style={{ color: 'white', backgroundColor: 'grey', float: 'right', marginLeft: '10px' }}
+                                onClick={handleCancel}
+                            ><CancelIcon /> Cancel</Button>
+                        </Box>
+                        {data.status == 'requested' ? <Box>
+                            <Button style={{ color: 'white', backgroundColor: buttonColor != undefined ? buttonColor : 'blue', float: 'right', marginLeft: '10px' }}
+                                onClick={() => handleApproved()}
+                            >Approved</Button>
+                        </Box> :
+                            <Box >
+                                <Button style={{ color: 'white', backgroundColor: buttonColor != undefined ? buttonColor : 'blue', float: 'right' }}
+                                    onClick={handleSave}
+                                ><SaveIcon /> Save</Button>
+                            </Box>
+                        }
+
+                    </Grid>
+                }
+
+
+            </Grid>
+        </div>
+    );
+}
+
+FieldEditor.propTypes = {
+    history: PropTypes.object,
+    fields: PropTypes.array.isRequired,
+    optionsData: PropTypes.array,
+    isNew: PropTypes.bool.isRequired,
+    onCreate: PropTypes.func.isRequired,
+    onUpdate: PropTypes.func.isRequired,
+    onCancel: PropTypes.func,
+    onApproved: PropTypes.func,
+    onInvite: PropTypes.func,
+    data: PropTypes.object,
+    currentTabName: PropTypes.string,
+    buttonColor: PropTypes.any
+};
+
+export default (FieldEditor);
+

+ 1 - 0
src/FieldEditor/index.js

@@ -0,0 +1 @@
+export { default } from '../FieldEditor';

+ 287 - 0
src/TableTemplate/TableTemplate.js

@@ -0,0 +1,287 @@
+import React, { useEffect } from 'react';
+import PropTypes from 'prop-types';
+import { makeStyles } from '@material-ui/core/styles';
+import Table from '@material-ui/core/Table';
+import TableBody from '@material-ui/core/TableBody';
+import TableCell from '@material-ui/core/TableCell';
+import TableContainer from '@material-ui/core/TableContainer';
+import TableHead from '@material-ui/core/TableHead';
+import TablePagination from '@material-ui/core/TablePagination';
+import TableRow from '@material-ui/core/TableRow';
+import TableSortLabel from '@material-ui/core/TableSortLabel';
+import { Grid } from '@material-ui/core';
+
+function descendingComparator(a, b, orderBy) {
+    if (b[orderBy] < a[orderBy]) {
+        return -1;
+    }
+    if (b[orderBy] > a[orderBy]) {
+        return 1;
+    }
+    return 0;
+}
+
+function getComparator(order, orderBy) {
+    return order === 'desc'
+        ? (a, b) => descendingComparator(a, b, orderBy)
+        : (a, b) => -descendingComparator(a, b, orderBy);
+}
+
+function stableSort(array, comparator) {
+    const stabilizedThis = array.map((el, index) => [el, index]);
+    stabilizedThis.sort((a, b) => {
+        const order = comparator(a[0], b[0]);
+        if (order !== 0) return order;
+        return a[1] - b[1];
+    });
+    return stabilizedThis.map((el) => el[0]);
+}
+
+function EnhancedTableHead(props) {
+    const { classes,
+        order,
+        orderBy,
+        onRequestSort,
+        onRefreshData,
+        headCells } = props;
+    const createSortHandler = (property) => (event) => {
+        onRequestSort(event, property);
+        onRefreshData();
+    };
+
+    return (
+        <TableHead>
+            <TableRow>
+                {headCells.map((headCell) => (
+                    <TableCell
+                        key={headCell.id}
+                        align={headCell.numeric ? 'right' : 'left'}
+                        padding={headCell.disablePadding ? 'none' : 'default'}
+                        sortDirection={orderBy === headCell.id ? order : false}
+                    >
+                        {headCell.id != 'id' ?
+                            <TableSortLabel
+                                active={orderBy === headCell.id}
+                                direction={orderBy === headCell.id ? order : 'asc'}
+                                onClick={createSortHandler(headCell.id)}
+                            >
+                                {headCell.label}
+                                {orderBy === headCell.id ? (
+                                    <span className={classes.visuallyHidden}>
+                                        {order === 'desc' ? 'sorted descending' : 'sorted ascending'}
+                                    </span>
+                                ) : null}
+                            </TableSortLabel>
+                            : <TableSortLabel
+                                hideSortIcon={true}
+                            >
+                                {headCell.label}
+
+                            </TableSortLabel>
+                        }
+
+                    </TableCell>
+                ))}
+            </TableRow>
+        </TableHead>
+    );
+}
+
+EnhancedTableHead.propTypes = {
+    classes: PropTypes.object.isRequired,
+    numSelected: PropTypes.number.isRequired,
+    onRequestSort: PropTypes.func.isRequired,
+    onRefreshData: PropTypes.func.isRequired,
+    onSelectAllClick: PropTypes.func.isRequired,
+    order: PropTypes.oneOf(['asc', 'desc']).isRequired,
+    orderBy: PropTypes.string.isRequired,
+    rowCount: PropTypes.number.isRequired,
+};
+
+const useStyles = makeStyles((theme) => ({
+    root: {
+        width: '100%',
+    },
+    paper: {
+        width: '100%',
+        marginBottom: theme.spacing(2),
+    },
+    table: {
+        minWidth: 750,
+    },
+    visuallyHidden: {
+        border: 0,
+        clip: 'rect(0 0 0 0)',
+        height: 1,
+        margin: -1,
+        overflow: 'hidden',
+        padding: 0,
+        position: 'absolute',
+        top: 20,
+        width: 1,
+    },
+    underline: {
+        "&&&:before": {
+            borderBottom: "none"
+        },
+        "&&:after": {
+            borderBottom: "none"
+        }
+    }
+}));
+
+function TableTemplate(props) {
+    const classes = useStyles();
+    const {
+        data = [],
+        headers = [],
+        onUpdateData,
+        onReloadData,
+        onChangePage,
+        onChangeRowsPerPage,
+        rowsPerPage = 10,
+        page = 0,
+        order = 'asc',
+        orderBy
+    } = props;
+
+    const [selected, setSelected] = React.useState([]);
+    const [dense, setDense] = React.useState(false);
+    const [rowDataPerPage, setRowsPerPage] = React.useState(rowsPerPage != undefined ? rowsPerPage : 10);
+    const [paginatePage, setPage] = React.useState(page != undefined ? page : 0);
+    const [paginateOrder, setOrder] = React.useState(order != undefined ? order : 'asc');
+    const [paginateOrderBy, setOrderBy] = React.useState(orderBy != undefined ? orderBy : 'name');
+
+    var offset = paginatePage * rowDataPerPage;
+
+    const handleRequestSort = (event, property) => {
+        const isAsc = orderBy === property && order === 'asc';
+        setOrder(isAsc ? 'desc' : 'asc');
+        setOrderBy(property);
+    };
+
+    const handleSelectAllClick = (event) => {
+        if (event.target.checked) {
+            const newSelecteds = data.map((n) => n.name);
+            setSelected(newSelecteds);
+            return;
+        }
+        setSelected([]);
+    };
+
+    const handleClick = (event, rowdata) => {
+        onUpdateData(true, rowdata);
+    };
+
+    const handleChangePage = (event, newPage) => {
+        onChangePage(newPage);
+
+    };
+
+    const handleReloadData = () => {
+        onReloadData();
+    }
+
+    const handleChangeRowsPerPage = (event) => {
+        setRowsPerPage(parseInt(event.target.value, 10));
+        onChangeRowsPerPage(parseInt(event.target.value));
+        onReloadData();
+    };
+
+    const isSelected = (name) => selected.indexOf(name) !== -1;
+
+    const emptyRows = rowsPerPage - Math.min(rowsPerPage, data.length - page * rowsPerPage);
+
+    return (
+        <div className={classes.root}>
+            <Grid container>
+                <Grid item style={{ marginRight: '10%' }}>
+
+                    <TableContainer>
+                        <Table
+                            className={classes.table}
+                            aria-labelledby="tableTitle"
+                            size={dense ? 'small' : 'medium'}
+                            aria-label="enhanced table"
+                        >
+                            <EnhancedTableHead
+                                classes={classes}
+                                numSelected={selected.length}
+                                headCells={headers}
+                                order={order}
+                                orderBy={orderBy}
+                                onSelectAllClick={handleSelectAllClick}
+                                onRequestSort={handleRequestSort}
+                                onRefreshData={handleReloadData}
+                                rowCount={data.length}
+                            />
+                            <TableBody>
+                                {data.length != 0 ? stableSort(data, getComparator(order, orderBy))
+                                    .slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage)
+                                    .map((row, index) => {
+                                        const isItemSelected = isSelected(row.name);
+                                        const labelId = `enhanced-table-checkbox-${index}`;
+
+                                        return (
+                                            <TableRow
+                                                hover
+                                                onClick={(event) => handleClick(event, row)}
+                                                role="checkbox"
+                                                aria-checked={isItemSelected}
+                                                tabIndex={-1}
+                                                key={index}
+                                                selected={isItemSelected}
+                                            >
+
+                                                {headers.map((h, i) => {
+                                                    if (h.id == 'id') {
+                                                        return (<TableCell key={h.id} align="right">{++offset}</TableCell>);
+                                                    } else {
+                                                        return (<TableCell key={h.id} align="right">{row[h.id]}</TableCell>);
+                                                    }
+                                                })}
+
+                                            </TableRow>
+                                        );
+                                    }) : <div></div>}
+                                {emptyRows > 0 && (
+                                    <TableRow style={{ height: (dense ? 33 : 53) * emptyRows }}>
+                                        <TableCell colSpan={6} />
+                                    </TableRow>
+                                )}
+                            </TableBody>
+                        </Table>
+                    </TableContainer>
+
+                    <TablePagination
+                        rowsPerPageOptions={[5, 10, 20, 30]}
+                        labelDisplayedRows={function ({ from, to, count }) { }}
+                        component="div"
+                        count={data.length}
+                        rowsPerPage={rowsPerPage}
+                        rowsPerPage={rowsPerPage}
+                        page={page}
+                        onChangePage={handleChangePage}
+                        onChangeRowsPerPage={handleChangeRowsPerPage}
+                    />
+                </Grid></Grid>
+        </div>
+    );
+}
+
+TableTemplate.propTypes = {
+    history: PropTypes.object,
+    headers: PropTypes.array.isRequired,
+    data: PropTypes.array.isRequired,
+    onUpdateData: PropTypes.func,
+    onReloadData: PropTypes.func,
+    onChangePage: PropTypes.func,
+    onChangeRowsPerPage: PropTypes.func,
+    query: PropTypes.object,
+    page: PropTypes.any,
+    rowsPerPage: PropTypes.any,
+    order: PropTypes.any,
+    orderBy: PropTypes.any
+};
+
+export default (TableTemplate);

+ 1 - 0
src/TableTemplate/index.js

@@ -0,0 +1 @@
+export { default } from '../TableTemplate';

+ 3 - 1
src/index.js

@@ -1 +1,3 @@
-export { default as List } from './List/List';
+export { default as List } from './List/List';
+export { default as FieldEditor } from './FieldEditor/FieldEditor';
+export { default as TableTemplate } from './TableTemplate/TableTemplate';