Table.tsx 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334
  1. import { FC, useEffect, useRef, useState } from 'react';
  2. import React from 'react';
  3. import { makeStyles } from '@material-ui/core/styles';
  4. import Table from '@material-ui/core/Table';
  5. import TableBody from '@material-ui/core/TableBody';
  6. import TableCell from '@material-ui/core/TableCell';
  7. import TableContainer from '@material-ui/core/TableContainer';
  8. import TableRow from '@material-ui/core/TableRow';
  9. import Checkbox from '@material-ui/core/Checkbox';
  10. import { TableType } from './Types';
  11. import { Box, Button, Typography } from '@material-ui/core';
  12. import EnhancedTableHead from './TableHead';
  13. import { stableSort, getComparator } from './Utils';
  14. import Copy from '../../components/copy/Copy';
  15. import ActionBar from './ActionBar';
  16. import LoadingTable from './LoadingTable';
  17. const useStyles = makeStyles(theme => ({
  18. root: {
  19. width: '100%',
  20. flexGrow: 1,
  21. /* set flex basis to make child item height 100% work on Safari */
  22. flexBasis: 0,
  23. // change scrollbar style
  24. '&::-webkit-scrollbar': {
  25. width: '8px',
  26. },
  27. '&::-webkit-scrollbar-track': {
  28. backgroundColor: '#f9f9f9',
  29. },
  30. '&::-webkit-scrollbar-thumb': {
  31. borderRadius: '8px',
  32. backgroundColor: '#eee',
  33. },
  34. },
  35. box: {
  36. backgroundColor: '#fff',
  37. },
  38. paper: {
  39. width: '100%',
  40. marginBottom: theme.spacing(2),
  41. },
  42. table: {
  43. minWidth: 750,
  44. },
  45. tableCell: {
  46. background: theme.palette.common.white,
  47. paddingLeft: theme.spacing(2),
  48. },
  49. hoverActionCell: {
  50. transition: '0.2s all',
  51. padding: 0,
  52. width: '50px',
  53. backgroundColor: '#fff',
  54. '& span': {
  55. opacity: 0,
  56. },
  57. },
  58. checkbox: {
  59. background: theme.palette.common.white,
  60. },
  61. rowHover: {
  62. '&:hover': {
  63. backgroundColor: '#f3fcfe !important',
  64. '& td': {
  65. background: 'inherit',
  66. },
  67. '& $hoverActionCell': {
  68. backgroundColor: theme.palette.primary.main,
  69. '& span': {
  70. opacity: 1,
  71. },
  72. },
  73. },
  74. },
  75. cell: {
  76. borderBottom: '1px solid #e9e9ed',
  77. '& p': {
  78. display: 'inline-block',
  79. verticalAlign: 'middle',
  80. overflow: 'hidden',
  81. textOverflow: 'ellipsis',
  82. whiteSpace: 'nowrap',
  83. maxWidth: '300px',
  84. fontSize: '14px',
  85. lineHeight: '20px',
  86. },
  87. },
  88. noData: {
  89. paddingTop: theme.spacing(6),
  90. textAlign: 'center',
  91. letterSpacing: '0.5px',
  92. color: 'rgba(0, 0, 0, 0.6)',
  93. },
  94. }));
  95. const EnhancedTable: FC<TableType> = props => {
  96. const {
  97. selected,
  98. onSelected,
  99. isSelected,
  100. onSelectedAll,
  101. rows = [],
  102. colDefinitions,
  103. primaryKey,
  104. openCheckBox = true,
  105. disableSelect,
  106. noData,
  107. showHoverStyle,
  108. isLoading,
  109. setPageSize,
  110. } = props;
  111. const classes = useStyles();
  112. const [order, setOrder] = React.useState('asc');
  113. const [orderBy, setOrderBy] = React.useState<string>('');
  114. const [tableMouseStatus, setTableMouseStatus] = React.useState<boolean[]>([]);
  115. const [loadingRowCount, setLoadingRowCount] = useState<number>(0);
  116. const containerRef = useRef(null);
  117. const handleRequestSort = (event: any, property: string) => {
  118. const isAsc = orderBy === property && order === 'asc';
  119. setOrder(isAsc ? 'desc' : 'asc');
  120. setOrderBy(property);
  121. };
  122. useEffect(() => {
  123. const height: number = (containerRef.current as any)!.offsetHeight;
  124. // table header 57px, loading row 40px
  125. const count = Math.floor((height - 57) / 40);
  126. setLoadingRowCount(count);
  127. }, []);
  128. useEffect(() => {
  129. if (setPageSize) {
  130. const containerHeight: number = (containerRef.current as any)!
  131. .offsetHeight;
  132. // table default row height is 54
  133. // if pass component as row item, its max height should be 54 too
  134. const rowHeight = 54;
  135. // table header default height is 57
  136. const tableHeaderHeight: number = 57;
  137. if (rowHeight > 0) {
  138. const pageSize = Math.floor(
  139. (containerHeight - tableHeaderHeight) / rowHeight
  140. );
  141. setPageSize(pageSize);
  142. }
  143. }
  144. }, [setPageSize]);
  145. return (
  146. <TableContainer ref={containerRef} className={classes.root}>
  147. <Box height="100%" className={classes.box}>
  148. <Table
  149. stickyHeader
  150. className={classes.table}
  151. aria-labelledby="tableTitle"
  152. size="medium"
  153. aria-label="enhanced table"
  154. >
  155. <EnhancedTableHead
  156. colDefinitions={colDefinitions}
  157. numSelected={selected.length}
  158. order={order}
  159. orderBy={orderBy}
  160. onSelectAllClick={onSelectedAll}
  161. onRequestSort={handleRequestSort}
  162. rowCount={rows.length}
  163. openCheckBox={openCheckBox}
  164. />
  165. {!isLoading && (
  166. <TableBody>
  167. {rows && rows.length ? (
  168. stableSort(rows, getComparator(order, orderBy)).map(
  169. (row, index) => {
  170. const isItemSelected = isSelected(row);
  171. const labelId = `enhanced-table-checkbox-${index}`;
  172. const handleMouseEnter = () => {
  173. setTableMouseStatus(v => {
  174. const copy = [...v];
  175. copy[index] = true;
  176. return copy;
  177. });
  178. };
  179. const handleMouseLeave = () =>
  180. setTableMouseStatus(v => {
  181. const copy = [...v];
  182. copy[index] = false;
  183. return copy;
  184. });
  185. return (
  186. <TableRow
  187. hover={showHoverStyle}
  188. key={'row' + row[primaryKey] + index}
  189. onClick={event => onSelected(event, row)}
  190. role="checkbox"
  191. aria-checked={isItemSelected}
  192. tabIndex={-1}
  193. selected={isItemSelected && !disableSelect}
  194. classes={{
  195. hover: classes.rowHover,
  196. }}
  197. onMouseEnter={handleMouseEnter}
  198. onMouseLeave={handleMouseLeave}
  199. >
  200. {openCheckBox && (
  201. <TableCell
  202. padding="checkbox"
  203. className={classes.checkbox}
  204. >
  205. <Checkbox
  206. checked={isItemSelected}
  207. color="primary"
  208. inputProps={{ 'aria-labelledby': labelId }}
  209. />
  210. </TableCell>
  211. )}
  212. {colDefinitions.map((colDef, i) => {
  213. const { actionBarConfigs = [], needCopy = false } =
  214. colDef;
  215. const cellStyle = colDef.getStyle
  216. ? colDef.getStyle(row[colDef.id])
  217. : {};
  218. return colDef.showActionCell ? (
  219. <TableCell
  220. className={`${classes.cell} ${
  221. classes.tableCell
  222. } ${
  223. colDef.isHoverAction
  224. ? classes.hoverActionCell
  225. : ''
  226. }`}
  227. key="manage"
  228. style={cellStyle}
  229. >
  230. <ActionBar
  231. showLabel={tableMouseStatus[index]}
  232. configs={actionBarConfigs}
  233. isHoverType={colDef.isHoverAction}
  234. row={row}
  235. ></ActionBar>
  236. </TableCell>
  237. ) : (
  238. <TableCell
  239. key={'cell' + row[primaryKey] + i}
  240. padding={i === 0 ? 'none' : 'default'}
  241. align={colDef.align || 'left'}
  242. className={`${classes.cell} ${classes.tableCell}`}
  243. style={cellStyle}
  244. >
  245. {row[colDef.id] &&
  246. typeof row[colDef.id] === 'string' ? (
  247. <Typography title={row[colDef.id]}>
  248. {colDef.onClick ? (
  249. <Button
  250. color="primary"
  251. data-data={row[colDef.id]}
  252. data-index={index}
  253. onClick={e => {
  254. colDef.onClick &&
  255. colDef.onClick(e, row);
  256. }}
  257. >
  258. {row[colDef.id]}
  259. </Button>
  260. ) : (
  261. row[colDef.id]
  262. )}
  263. </Typography>
  264. ) : (
  265. <>
  266. {colDef.onClick ? (
  267. <Button
  268. color="primary"
  269. data-data={row[colDef.id]}
  270. data-index={index}
  271. onClick={e => {
  272. colDef.onClick &&
  273. colDef.onClick(e, row);
  274. }}
  275. >
  276. {row[colDef.id]}
  277. </Button>
  278. ) : (
  279. row[colDef.id]
  280. )}
  281. </>
  282. )}
  283. {needCopy && row[colDef.id] && (
  284. <Copy data={row[colDef.id]} />
  285. )}
  286. </TableCell>
  287. );
  288. })}
  289. </TableRow>
  290. );
  291. }
  292. )
  293. ) : (
  294. <tr>
  295. <td
  296. className={classes.noData}
  297. colSpan={
  298. openCheckBox
  299. ? colDefinitions.length + 1
  300. : colDefinitions.length
  301. }
  302. >
  303. {noData}
  304. </td>
  305. </tr>
  306. )}
  307. </TableBody>
  308. )}
  309. </Table>
  310. {isLoading && <LoadingTable count={loadingRowCount} />}
  311. </Box>
  312. </TableContainer>
  313. );
  314. };
  315. export default EnhancedTable;