Table component implementation (#71)
Implementation of a new table wrapper component that will be used across the application. Co-authored-by: Benjamin Perez <benjamin@bexsoft.net>
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2020 MinIO, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
import React from "react";
|
||||
import { IconButton } from "@material-ui/core";
|
||||
import ViewIcon from "./TableActionIcons/ViewIcon";
|
||||
import PencilIcon from "./TableActionIcons/PencilIcon";
|
||||
import DeleteIcon from "./TableActionIcons/DeleteIcon";
|
||||
|
||||
interface IActionButton {
|
||||
type: string;
|
||||
onClick: (id: string) => any;
|
||||
valueToSend: any;
|
||||
selected: boolean;
|
||||
}
|
||||
|
||||
const defineIcon = (type: string, selected: boolean) => {
|
||||
switch (type) {
|
||||
case "view":
|
||||
return <ViewIcon active={selected} />;
|
||||
case "edit":
|
||||
return <PencilIcon active={selected} />;
|
||||
case "delete":
|
||||
return <DeleteIcon active={selected} />;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const TableActionButton = ({
|
||||
type,
|
||||
onClick,
|
||||
valueToSend,
|
||||
selected
|
||||
}: IActionButton) => {
|
||||
return (
|
||||
<IconButton
|
||||
aria-label={type}
|
||||
onClick={() => {
|
||||
onClick(valueToSend);
|
||||
}}
|
||||
>
|
||||
{defineIcon(type, selected)}
|
||||
</IconButton>
|
||||
);
|
||||
};
|
||||
|
||||
export default TableActionButton;
|
||||
@@ -0,0 +1,23 @@
|
||||
import React from "react";
|
||||
import { IIcon, selected, unSelected } from "./common";
|
||||
|
||||
const DeleteIcon = ({ active = false }: IIcon) => {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
>
|
||||
<g transform="translate(-1225 -657)">
|
||||
<path
|
||||
fill={active ? selected : unSelected}
|
||||
d="M0,8H0a8,8,0,0,0,8,8H8a8,8,0,0,0,8-8h0A8,8,0,0,0,8,0H8A8,8,0,0,0,0,8Zm10.007,3.489L8,9.482,5.993,11.489,4.511,10.007,6.518,8,4.511,5.993,5.993,4.511,8,6.518l2.007-2.007,1.482,1.482L9.482,8l2.007,2.007Z"
|
||||
transform="translate(1225 657)"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export default DeleteIcon;
|
||||
@@ -0,0 +1,21 @@
|
||||
import React from "react";
|
||||
import { IIcon, selected, unSelected } from "./common";
|
||||
|
||||
const PencilIcon = ({ active = false }: IIcon) => {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 13.833 13.833"
|
||||
>
|
||||
<path
|
||||
fill={active ? selected : unSelected}
|
||||
d="M2.934,16H0V13.066L10.607,2.459a1,1,0,0,1,1.414,0l1.52,1.52a1,1,0,0,1,0,1.414Z"
|
||||
transform="translate(0 -2.167)"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export default PencilIcon;
|
||||
@@ -0,0 +1,21 @@
|
||||
import React from "react";
|
||||
import { IIcon, selected, unSelected } from "./common";
|
||||
|
||||
const ViewIcon = ({ active = false }: IIcon) => {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 11.856"
|
||||
>
|
||||
<path
|
||||
fill={active ? selected : unSelected}
|
||||
d="M-54,8l1.764,2.614A7.52,7.52,0,0,0-46,13.928h0a7.52,7.52,0,0,0,6.234-3.314L-38,8l-1.764-2.614A7.52,7.52,0,0,0-46,2.072h0a7.52,7.52,0,0,0-6.234,3.314Zm10.286,0A2.285,2.285,0,0,1-46,10.286,2.285,2.285,0,0,1-48.286,8,2.285,2.285,0,0,1-46,5.714,2.285,2.285,0,0,1-43.714,8Zm1.3,0A3.59,3.59,0,0,1-46,11.59,3.59,3.59,0,0,1-49.59,8,3.59,3.59,0,0,1-46,4.41,3.59,3.59,0,0,1-42.41,8Z"
|
||||
transform="translate(54 -2.072)"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export default ViewIcon;
|
||||
@@ -0,0 +1,6 @@
|
||||
export interface IIcon {
|
||||
active: boolean;
|
||||
}
|
||||
|
||||
export const unSelected = "#adadad";
|
||||
export const selected = "#201763";
|
||||
@@ -0,0 +1,301 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2020 MinIO, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
import React from "react";
|
||||
import get from "lodash/get";
|
||||
import {
|
||||
LinearProgress,
|
||||
TablePagination,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableRow,
|
||||
Paper,
|
||||
Grid,
|
||||
Checkbox
|
||||
} from "@material-ui/core";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import { TablePaginationActionsProps } from "@material-ui/core/TablePagination/TablePaginationActions";
|
||||
import TableActionButton from "./TableActionButton";
|
||||
|
||||
interface ItemActions {
|
||||
type: string;
|
||||
onClick(valueToSend: any): any;
|
||||
}
|
||||
|
||||
interface IColumns {
|
||||
label: string;
|
||||
elementKey: string;
|
||||
sortable?: boolean;
|
||||
}
|
||||
|
||||
interface IPaginatorConfig {
|
||||
rowsPerPageOptions: number[];
|
||||
colSpan: number;
|
||||
count: number;
|
||||
rowsPerPage: number;
|
||||
page: number;
|
||||
SelectProps: any;
|
||||
onChangePage: (
|
||||
event: React.MouseEvent<HTMLButtonElement> | null,
|
||||
page: number
|
||||
) => void;
|
||||
onChangeRowsPerPage?: React.ChangeEventHandler<
|
||||
HTMLTextAreaElement | HTMLInputElement
|
||||
>;
|
||||
ActionsComponent?: React.ElementType<TablePaginationActionsProps>;
|
||||
}
|
||||
|
||||
interface TableWrapperProps {
|
||||
itemActions?: ItemActions[] | null;
|
||||
columns: IColumns[];
|
||||
onSelect?: (e: React.ChangeEvent<HTMLInputElement>) => any;
|
||||
idField: string;
|
||||
isLoading: boolean;
|
||||
records: any[];
|
||||
classes: any;
|
||||
entityName: string;
|
||||
selectedItems?: string[];
|
||||
stickyHeader?: boolean;
|
||||
paginatorConfig?: IPaginatorConfig;
|
||||
}
|
||||
|
||||
const borderColor = "#eaeaea";
|
||||
|
||||
const rowText = {
|
||||
fontWeight: 400,
|
||||
fontSize: 14,
|
||||
borderColor: borderColor
|
||||
};
|
||||
|
||||
const checkBoxBasic = {
|
||||
width: 16,
|
||||
height: 16,
|
||||
borderRadius: 3
|
||||
};
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
dialogContainer: {
|
||||
padding: "12px 26px 22px"
|
||||
},
|
||||
paper: {
|
||||
display: "flex",
|
||||
overflow: "auto",
|
||||
flexDirection: "column",
|
||||
padding: "19px 38px"
|
||||
},
|
||||
minTableHeader: {
|
||||
color: "#393939",
|
||||
"& tr": {
|
||||
"& th": {
|
||||
fontWeight: 700,
|
||||
fontSize: 14,
|
||||
paddingBottom: 15,
|
||||
borderColor: borderColor
|
||||
}
|
||||
}
|
||||
},
|
||||
rowUnselected: {
|
||||
...rowText
|
||||
},
|
||||
rowSelected: {
|
||||
...rowText,
|
||||
color: "#201763"
|
||||
},
|
||||
paginatorContainer: {
|
||||
display: "flex",
|
||||
justifyContent: "flex-end",
|
||||
padding: "5px 38px"
|
||||
},
|
||||
checkBoxHeader: {
|
||||
"&.MuiTableCell-paddingCheckbox": {
|
||||
paddingBottom: 9
|
||||
}
|
||||
},
|
||||
actionsContainer: {
|
||||
width: 120,
|
||||
borderColor: borderColor
|
||||
},
|
||||
paginatorComponent: {
|
||||
borderBottom: 0
|
||||
},
|
||||
unCheckedIcon: { ...checkBoxBasic, border: "1px solid #d0d0d0" },
|
||||
checkedIcon: {
|
||||
...checkBoxBasic,
|
||||
border: "1px solid #201763",
|
||||
backgroundColor: "#201763"
|
||||
},
|
||||
checkBoxRow: {
|
||||
borderColor: borderColor
|
||||
}
|
||||
});
|
||||
|
||||
const titleColumnsMap = (columns: IColumns[]) => {
|
||||
const columnsList = columns.map((column: IColumns, index: number) => {
|
||||
return (
|
||||
<TableCell key={`tbCT-${column.elementKey}-${index}`}>
|
||||
{column.label}
|
||||
</TableCell>
|
||||
);
|
||||
});
|
||||
|
||||
return columnsList;
|
||||
};
|
||||
|
||||
const rowColumnsMap = (
|
||||
columns: IColumns[],
|
||||
itemData: any,
|
||||
classes: any,
|
||||
isSelected: boolean
|
||||
) => {
|
||||
const rowElements = columns.map((column: IColumns, index: number) => {
|
||||
const itemElement = get(itemData, column.elementKey, null);
|
||||
return (
|
||||
<TableCell
|
||||
key={`tbRE-${column.elementKey}-${index}`}
|
||||
className={isSelected ? classes.rowSelected : classes.rowUnselected}
|
||||
>
|
||||
{itemElement}
|
||||
</TableCell>
|
||||
);
|
||||
});
|
||||
|
||||
return rowElements;
|
||||
};
|
||||
|
||||
const elementActions = (
|
||||
actions: ItemActions[],
|
||||
valueToSend: any,
|
||||
selected: boolean
|
||||
) => {
|
||||
const actionsElements = actions.map((action: ItemActions, index: number) => {
|
||||
return (
|
||||
<TableActionButton
|
||||
type={action.type}
|
||||
onClick={action.onClick}
|
||||
valueToSend={valueToSend}
|
||||
selected={selected}
|
||||
key={`actions-${action.type}-${index.toString()}`}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
return actionsElements;
|
||||
};
|
||||
|
||||
const TableWrapper = ({
|
||||
itemActions,
|
||||
columns,
|
||||
onSelect,
|
||||
records,
|
||||
isLoading,
|
||||
entityName,
|
||||
selectedItems,
|
||||
idField,
|
||||
classes,
|
||||
stickyHeader = false,
|
||||
paginatorConfig
|
||||
}: TableWrapperProps) => {
|
||||
return (
|
||||
<Grid item xs={12}>
|
||||
<Paper className={classes.paper}>
|
||||
{isLoading && <LinearProgress />}
|
||||
{records && records.length > 0 ? (
|
||||
<Table size="small" stickyHeader={stickyHeader}>
|
||||
<TableHead className={classes.minTableHeader}>
|
||||
<TableRow>
|
||||
{onSelect && selectedItems && (
|
||||
<TableCell
|
||||
padding="checkbox"
|
||||
align="center"
|
||||
className={classes.checkBoxHeader}
|
||||
>
|
||||
Select
|
||||
</TableCell>
|
||||
)}
|
||||
{titleColumnsMap(columns)}
|
||||
{itemActions && itemActions.length > 0 && (
|
||||
<TableCell
|
||||
align="center"
|
||||
className={classes.actionsContainer}
|
||||
>
|
||||
Actions
|
||||
</TableCell>
|
||||
)}
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{records.map((record: any, index: number) => {
|
||||
const isSelected = selectedItems
|
||||
? selectedItems.includes(record[idField])
|
||||
: false;
|
||||
return (
|
||||
<TableRow key={`tb-${entityName}-${index.toString()}`}>
|
||||
{onSelect && selectedItems && (
|
||||
<TableCell
|
||||
padding="checkbox"
|
||||
align="center"
|
||||
className={classes.checkBoxRow}
|
||||
>
|
||||
<Checkbox
|
||||
value={record[idField]}
|
||||
color="primary"
|
||||
inputProps={{ "aria-label": "secondary checkbox" }}
|
||||
checked={isSelected}
|
||||
onChange={onSelect}
|
||||
checkedIcon={<span className={classes.checkedIcon} />}
|
||||
icon={<span className={classes.unCheckedIcon} />}
|
||||
/>
|
||||
</TableCell>
|
||||
)}
|
||||
{rowColumnsMap(columns, record, classes, isSelected)}
|
||||
{itemActions && itemActions.length > 0 && (
|
||||
<TableCell
|
||||
align="right"
|
||||
className={classes.actionsContainer}
|
||||
>
|
||||
{elementActions(itemActions, record, isSelected)}
|
||||
</TableCell>
|
||||
)}
|
||||
</TableRow>
|
||||
);
|
||||
})}
|
||||
</TableBody>
|
||||
</Table>
|
||||
) : (
|
||||
<div>{`There are no ${entityName} yet.`}</div>
|
||||
)}
|
||||
</Paper>
|
||||
{paginatorConfig && (
|
||||
<Grid item xs={12} className={classes.paginatorContainer}>
|
||||
<Table>
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TablePagination
|
||||
{...paginatorConfig}
|
||||
className={classes.paginatorComponent}
|
||||
/>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</Grid>
|
||||
)}
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
||||
export default withStyles(styles)(TableWrapper);
|
||||
@@ -19,25 +19,12 @@ import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import api from "../../../common/api";
|
||||
import {
|
||||
Button,
|
||||
IconButton,
|
||||
LinearProgress,
|
||||
TableFooter,
|
||||
TablePagination,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableRow,
|
||||
Paper,
|
||||
Grid,
|
||||
Typography,
|
||||
TextField,
|
||||
InputAdornment
|
||||
} from "@material-ui/core";
|
||||
import SearchIcon from "@material-ui/icons/Search";
|
||||
import Checkbox from "@material-ui/core/Checkbox";
|
||||
import DeleteIcon from "@material-ui/icons/Delete";
|
||||
import ViewIcon from "@material-ui/icons/Visibility";
|
||||
import GroupIcon from "@material-ui/icons/Group";
|
||||
import { User, UsersList } from "./types";
|
||||
import { usersSort } from "../../../utils/sortFunctions";
|
||||
@@ -46,6 +33,7 @@ import { CreateIcon } from "../../../icons";
|
||||
import AddUser from "./AddUser";
|
||||
import DeleteUser from "./DeleteUser";
|
||||
import AddToGroup from "./AddToGroup";
|
||||
import TableWrapper from "../Common/TableWrapper/TableWrapper";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
@@ -251,6 +239,25 @@ class Users extends React.Component<IUsersProps, IUsersState> {
|
||||
return elements;
|
||||
};
|
||||
|
||||
const viewAction = (selectionElement: any): void => {
|
||||
this.setState({
|
||||
addScreenOpen: true,
|
||||
selectedUser: selectionElement
|
||||
});
|
||||
};
|
||||
|
||||
const deleteAction = (selectionElement: any): void => {
|
||||
this.setState({
|
||||
deleteOpen: true,
|
||||
selectedUser: selectionElement
|
||||
});
|
||||
};
|
||||
|
||||
const tableActions = [
|
||||
{ type: "view", onClick: viewAction },
|
||||
{ type: "delete", onClick: deleteAction }
|
||||
];
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
{addScreenOpen && (
|
||||
@@ -340,82 +347,30 @@ class Users extends React.Component<IUsersProps, IUsersState> {
|
||||
<br />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Paper className={classes.paper}>
|
||||
{loading && <LinearProgress />}
|
||||
{records != null && records.length > 0 ? (
|
||||
<Table size="medium">
|
||||
<TableHead className={classes.minTableHeader}>
|
||||
<TableRow>
|
||||
<TableCell>Select</TableCell>
|
||||
<TableCell>Access Key</TableCell>
|
||||
<TableCell align="right">Actions</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{filteredRecords.map(row => (
|
||||
<TableRow key={`user-${row.accessKey}`}>
|
||||
<TableCell padding="checkbox">
|
||||
<Checkbox
|
||||
value={row.accessKey}
|
||||
color="primary"
|
||||
inputProps={{ "aria-label": "secondary checkbox" }}
|
||||
checked={checkedUsers.includes(row.accessKey)}
|
||||
onChange={selectionChanged}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className={classes.wrapCell}>
|
||||
{row.accessKey}
|
||||
</TableCell>
|
||||
<TableCell align="right">
|
||||
<IconButton
|
||||
aria-label="view"
|
||||
onClick={() => {
|
||||
this.setState({
|
||||
addScreenOpen: true,
|
||||
selectedUser: row
|
||||
});
|
||||
}}
|
||||
>
|
||||
<ViewIcon />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
aria-label="delete"
|
||||
onClick={() => {
|
||||
this.setState({
|
||||
deleteOpen: true,
|
||||
selectedUser: row
|
||||
});
|
||||
}}
|
||||
>
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
<TableFooter>
|
||||
<TableRow>
|
||||
<TablePagination
|
||||
rowsPerPageOptions={[5, 10, 25]}
|
||||
colSpan={3}
|
||||
count={totalRecords}
|
||||
rowsPerPage={rowsPerPage}
|
||||
page={page}
|
||||
SelectProps={{
|
||||
inputProps: { "aria-label": "rows per page" },
|
||||
native: true
|
||||
}}
|
||||
onChangePage={handleChangePage}
|
||||
onChangeRowsPerPage={handleChangeRowsPerPage}
|
||||
ActionsComponent={MinTablePaginationActions}
|
||||
/>
|
||||
</TableRow>
|
||||
</TableFooter>
|
||||
</Table>
|
||||
) : (
|
||||
<div>No Users</div>
|
||||
)}
|
||||
</Paper>
|
||||
<TableWrapper
|
||||
itemActions={tableActions}
|
||||
columns={[{ label: "Access Key", elementKey: "accessKey" }]}
|
||||
onSelect={selectionChanged}
|
||||
selectedItems={checkedUsers}
|
||||
isLoading={loading}
|
||||
records={filteredRecords}
|
||||
entityName="Users"
|
||||
idField="accessKey"
|
||||
paginatorConfig={{
|
||||
rowsPerPageOptions: [5, 10, 25],
|
||||
colSpan: 3,
|
||||
count: totalRecords,
|
||||
rowsPerPage: rowsPerPage,
|
||||
page: page,
|
||||
SelectProps: {
|
||||
inputProps: { "aria-label": "rows per page" },
|
||||
native: true
|
||||
},
|
||||
onChangePage: handleChangePage,
|
||||
onChangeRowsPerPage: handleChangeRowsPerPage,
|
||||
ActionsComponent: MinTablePaginationActions
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
|
||||
Reference in New Issue
Block a user