Skip to content

Enhancement ItemTable virtualization #207

Merged
merged 12 commits into from
May 5, 2021
Merged
40 changes: 40 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@
"react-relative-time": "0.0.7",
"react-router-dom": "^5.2.0",
"react-scripts": "3.4.1",
"react-table": "^7.5.1"
"react-table": "^7.5.1",
"react-virtualized": "^9.22.3",
"react-window": "^1.8.6"
},
"scripts": {
"start:frontend": "react-scripts start",
Expand Down
178 changes: 97 additions & 81 deletions src/components/ItemTable/ItemTable.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import React, { useState } from "react";
import React, { memo, useState } from "react";
import PropTypes from "prop-types";
import { useTable, useFilters, useFlexLayout, useSortBy } from "react-table";
import { Table, TableBody, TableCell, TableHead, TableRow, TableContainer, Box, Grid, ButtonGroup, IconButton, makeStyles, useTheme } from "@material-ui/core";
import { ArrowDownward, ArrowUpward } from "@material-ui/icons";
import { useHistory } from "react-router-dom";
import RelativeTime from "react-relative-time";
import { FixedSizeList, areEqual } from 'react-window';
import { AutoSizer } from "react-virtualized";
import ItemTableFilter from "../ItemTableFilter/"
import { ArrowDownward, ArrowUpward } from "@material-ui/icons";
import ItemTableCell from "../ItemTableCell";
import LastUpdatedCell from "../LastUpdatedCell/";
import jester from "./loading-annimation.gif";
Expand All @@ -22,7 +24,7 @@ export default function ItemTable({ data, rowCanBeSelected, loading }) {
},
hoverBackgroundColor: {
"&:hover": {
// The !important is placed here to enforce CSS specificity.
// The !important is placed here to enforce CSS specificity.
// See: https://material-ui.com/styles/advanced/#css-injection-order
backgroundColor: `${theme.palette.primary[200]} !important`,
},
Expand All @@ -35,10 +37,12 @@ export default function ItemTable({ data, rowCanBeSelected, loading }) {
backgroundColor: theme.palette.type === 'light' ? theme.palette.grey[50] : theme.palette.grey[700],
},
},
columnBorders: {
borderLeftWidth: "1px",
borderLeftStyle: "solid",
borderColor: theme.palette.type === "light" ? theme.palette.grey[300] : theme.palette.grey[500]
// The AutoSizer and FixedSizeList components create extra div elements within the table body
// that break DOM nesting rules and cause the table body to have a 0px height. These styles
// are a hotfix to force the table body to have height until a better solution is found.
VirtualizedTableStyles: {
height: '82vh !important',
display:"table-row"
},
});
const classes = useStyles();
Expand All @@ -59,7 +63,6 @@ export default function ItemTable({ data, rowCanBeSelected, loading }) {
{ Header: 'Department', accessor: 'department' },
{ Header: 'Building', accessor: 'building' },
{ Header: 'Date Received', accessor: 'dateReceived', sortInverted: true, Cell: ({ value }) => <RelativeTime value={value} /> },

], []);
const tableInstance = useTable(
{
Expand Down Expand Up @@ -90,27 +93,23 @@ export default function ItemTable({ data, rowCanBeSelected, loading }) {
const { getTableProps, getTableBodyProps, headerGroups, rows, prepareRow, } = tableInstance;

return (
loading
? (
<Box className={classes.loadingAnnimation}>
<img src={jester} alt="Items are loading." />
</Box>
)
: (
<TableContainer>
<Table
{...getTableProps}
aria-label="Table of Queue Items"
size="small"
>
<TableHead>
{headerGroups.map(headerGroup => (
<TableRow {...headerGroup.getHeaderGroupProps()}>
{headerGroup.headers.map(column => (
loading
? (
<Box className={classes.loadingAnnimation}>
<img src={jester} alt="Items are loading." />
</Box>
)
: (
<TableContainer>
<Table stickyHeader {...getTableProps()} size="small">
<TableHead>
{headerGroups.map(headerGroup => (
<TableRow {...headerGroup.getHeaderGroupProps()}>
{headerGroup.headers.map(column => (
<TableCell {...column.getHeaderProps()}>
<Grid container spacing={0}>
<Grid item sm={10} >
{column.render("Filter")}
<Grid item sm={10}>
{column.render('Filter')}
</Grid>
<Grid item sm={2} alignItems='center' justify='center'>
<ButtonGroup orientation="vertical" size="small">
Expand Down Expand Up @@ -142,62 +141,79 @@ export default function ItemTable({ data, rowCanBeSelected, loading }) {
</Grid>
</Grid>
</TableCell>
))}
</TableRow>
))}
</TableHead>
<TableBody {...getTableBodyProps()}>
{rows.map((row) => {
prepareRow(row);
let isSelected = selectedRow.queue === row.original.queue && selectedRow.number === row.original.number
return (
<TableRow
hover
onClick={() => {
history.push(`/${row.original.queue}/${row.original.number}`);
setSelectedRow({ queue: row.original.queue, number: row.original.number });
}}
// This functionality should be achieved by using the selected prop and
// overriding the selected class but this applied the secondary color at 0.08% opacity.
// Overridding the root class is a workaround.
classes={{
root: (isSelected && rowCanBeSelected) ? classes.rowSelected : classes.bandedRows,
hover: classes.hoverBackgroundColor
}}
{...row.getRowProps()}
>
{row.cells.map(cell => (
cell.render(_ => {
switch (cell.column.id) {
case "dateReceived":
return (
<ItemTableCell TableCellProps={cell.getCellProps()}>
<RelativeTime value={cell.value} />
</ItemTableCell>
);
case "lastUpdated":
return (
<LastUpdatedCell
time={cell.value}
ItemTableCellProps={cell.getCellProps()}
/>
);
default:
return (
<ItemTableCell TableCellProps={cell.getCellProps()}>
{cell.value}
</ItemTableCell>
);
}
})
))}
</TableRow>
);
})}
</TableBody>
</Table>
</TableContainer>
)
))}
</TableHead>

<TableBody {...getTableBodyProps()} classes={{ root: classes.VirtualizedTableStyles }}>
<AutoSizer disableWidth>
{({ height, width }) => (
<FixedSizeList
className="Table"
height={height}
width="100%"
itemCount={rows.length}
itemSize={100}
>
{memo(({ index, style }) => {
const row = rows[index]
prepareRow(row)
let isSelected = selectedRow.queue === row.original.queue && selectedRow.number === row.original.number
return (
<TableRow
hover
onClick={() => {
history.push(`/${row.original.queue}/${row.original.number}`);
setSelectedRow({ queue: row.original.queue, number: row.original.number });
}}
// This functionality should be achieved by using the selected prop and
// overriding the selected class but this applied the secondary color at 0.08% opacity.
// Overridding the root class is a workaround.
classes={{
root: (isSelected && rowCanBeSelected) ? classes.rowSelected : classes.bandedRows,
hover: classes.hoverBackgroundColor
}}
{...row.getRowProps({
style,
})}
>
{row.cells.map(cell => (
cell.render(_ => {
switch (cell.column.id) {
case "dateReceived":
return (
<ItemTableCell TableCellProps={cell.getCellProps()}>
<RelativeTime value={cell.value} />
</ItemTableCell>
);
case "lastUpdated":
return (
<LastUpdatedCell
time={cell.value}
ItemTableCellProps={cell.getCellProps()}
/>
);
default:
return (
<ItemTableCell TableCellProps={cell.getCellProps()}>
{cell.value}
</ItemTableCell>
);
}
})
))}
</TableRow>
);
}, areEqual, [prepareRow, rows]
)}
</FixedSizeList>
)}
</AutoSizer>
</TableBody>
</Table>
</TableContainer >
)
);
};

Expand Down
Loading