diff --git a/api/requirements.txt b/api/requirements.txt
index d87bdac..59e3e3c 100644
--- a/api/requirements.txt
+++ b/api/requirements.txt
@@ -11,4 +11,9 @@ Flask-RESTful
python-dateutil
Flask-JWT-Extended
# Prevent upgrade to 2.x until Flask-JWT-Extended is updated to support it
-PyJWT == 1.*
\ No newline at end of file
+PyJWT == 1.*
+
+# API Documentation
+mkdocs
+mkdocs-material
+mkautodoc
\ No newline at end of file
diff --git a/src/components/ItemTable/ItemTable.js b/src/components/ItemTable/ItemTable.js
index 6c4df69..6677f28 100644
--- a/src/components/ItemTable/ItemTable.js
+++ b/src/components/ItemTable/ItemTable.js
@@ -1,12 +1,13 @@
import React, { useState } from "react";
import PropTypes from "prop-types";
import { useTable, useFilters, useFlexLayout, useSortBy } from "react-table";
-import { Table, TableBody, TableCell, TableHead, TableRow, TableContainer, Paper, Grid, ButtonGroup, IconButton, makeStyles, useTheme } from "@material-ui/core";
+import { Table, TableBody, TableCell, TableHead, TableRow, TableContainer, Paper, Grid, ButtonGroup, IconButton, makeStyles, useTheme, } from "@material-ui/core";
import { useHistory } from "react-router-dom";
import RelativeTime from "react-relative-time";
import ItemTableFilter from "../ItemTableFilter/"
import { ArrowDownward, ArrowUpward } from "@material-ui/icons";
-
+import ItemTableCell from "../ItemTableCell";
+import LastUpdatedCell from "../LastUpdatedCell/";
export default function ItemTable({ data, rowCanBeSelected }) {
const [selectedRow, setSelectedRow] = useState({ queue: null, number: null});
@@ -38,7 +39,6 @@ export default function ItemTable({ data, rowCanBeSelected }) {
const classes = useStyles();
const history = useHistory();
-
const columns = React.useMemo(
() => [
{ Header: 'Queue', accessor: 'queue', },
@@ -47,10 +47,10 @@ export default function ItemTable({ data, rowCanBeSelected }) {
{ Header: 'Subject', accessor: 'subject' },
{ Header: 'Status', accessor: 'status', },
{ Header: 'Priority', accessor: 'priority' },
- { Header: 'Last Updated', accessor: 'lastUpdated', Cell: ({ value }) => },
+ { Header: 'Last Updated', accessor: 'lastUpdated', },
{ Header: 'Department', accessor: 'department' },
{ Header: 'Building', accessor: 'building' },
- { Header: 'Date Received', accessor: 'dateReceived', Cell: ({ value }) => },
+ { Header: 'Date Received', accessor: 'dateReceived', },
], []);
const tableInstance = useTable(
@@ -67,7 +67,7 @@ export default function ItemTable({ data, rowCanBeSelected }) {
onChange={(event) => setFilter(event.target.value)}
/>
);
- }
+ },
},
},
useFilters, useFlexLayout, useSortBy,
@@ -75,8 +75,12 @@ export default function ItemTable({ data, rowCanBeSelected }) {
const { getTableProps, getTableBodyProps, headerGroups, rows, prepareRow, } = tableInstance;
return (
-
-
+
+
{headerGroups.map(headerGroup => (
@@ -123,7 +127,7 @@ export default function ItemTable({ data, rowCanBeSelected }) {
))}
- {rows.map((row, i) => {
+ {rows.map((row) => {
prepareRow(row);
let isSelected = selectedRow.queue === row.original.queue && selectedRow.number === row.original.number
return (
@@ -136,15 +140,33 @@ export default function ItemTable({ data, rowCanBeSelected }) {
// 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 }}
- {...row.getRowProps()} >
+ {...row.getRowProps()}
+ >
{row.cells.map(cell => (
-
- {cell.render("Cell")}
-
- ))}
+ cell.render(_ => {
+ switch (cell.column.id) {
+ case "dateReceived":
+ return (
+
+
+
+ );
+ case "lastUpdated":
+ return (
+
+ );
+ default:
+ return (
+
+ {cell.value}
+
+ );
+ }
+ })
+ ))};
);
})}
diff --git a/src/components/ItemTableCell/ItemTableCell.js b/src/components/ItemTableCell/ItemTableCell.js
new file mode 100644
index 0000000..cf13a74
--- /dev/null
+++ b/src/components/ItemTableCell/ItemTableCell.js
@@ -0,0 +1,37 @@
+import React from 'react'
+import PropTypes from "prop-types";
+import { makeStyles, TableCell, useTheme } from '@material-ui/core'
+
+export default function ItemTableCell({ children, TableCellProps }) {
+ const theme = useTheme();
+ const useStyles = makeStyles({
+ columnBorders: {
+ // Add column borders
+ borderLeftWidth: "1px",
+ borderLeftStyle: "solid",
+ borderColor: theme.palette.type === "light" ? theme.palette.grey[300] : theme.palette.grey[500]
+ },
+ })
+
+ const classes = useStyles();
+ return (
+
+ {children}
+
+ );
+}
+
+ItemTableCell.propTypes = {
+ /** Child object passed to display cell data. */
+ "children": PropTypes.object,
+ /** Props applied to the TableCell component. */
+ "TableCellProps": PropTypes.object
+};
+
+ItemTableCell.defaultProps = {
+ "children": {},
+ "TableCellProps": {},
+};
\ No newline at end of file
diff --git a/src/components/ItemTableCell/ItemTableCell.md b/src/components/ItemTableCell/ItemTableCell.md
new file mode 100644
index 0000000..5a3bcea
--- /dev/null
+++ b/src/components/ItemTableCell/ItemTableCell.md
@@ -0,0 +1,38 @@
+The ItemTableCell wraps an [MUI TableCell](https://material-ui.com/api/table-cell/) and adds styling.
+
+## Default Usage
+```jsx
+import { Paper } from '@material-ui/core';
+import ItemTableCell from "./ItemTableCell";
+
+
+
+ Hello, moto!
+
+
+```
+
+```jsx static
+
+
+ Hello, moto!
+
+
+```
+
+## Forwarded TableCell Props
+Props can be passed to the TableCell component using the TableCellProps prop.
+```jsx
+import { Paper } from '@material-ui/core';
+import ItemTableCell from "./ItemTableCell";
+
+
+ Hello, moto!
+
+```
+
+```jsx static
+
+ Hello, moto!
+
+```
\ No newline at end of file
diff --git a/src/components/ItemTableCell/index.js b/src/components/ItemTableCell/index.js
new file mode 100644
index 0000000..a358a7b
--- /dev/null
+++ b/src/components/ItemTableCell/index.js
@@ -0,0 +1,3 @@
+import ItemTableCell from "./ItemTableCell";
+
+export default ItemTableCell;
\ No newline at end of file
diff --git a/src/components/LastUpdatedCell/LastUpdatedCell.js b/src/components/LastUpdatedCell/LastUpdatedCell.js
new file mode 100644
index 0000000..6729508
--- /dev/null
+++ b/src/components/LastUpdatedCell/LastUpdatedCell.js
@@ -0,0 +1,69 @@
+import React from 'react'
+import PropTypes from "prop-types";
+import { red } from '@material-ui/core/colors';
+import RelativeTime from 'react-relative-time';
+import ItemTableCell from '../ItemTableCell';
+
+/**
+ * Returns a color showing how old an item is.
+ * @param {string} time ISO 8601 formatted time string.
+ * @example
+ * // Returns "#e57373"
+ * timeToBackgroundColor("2021-01-04T11:47:00-0500")
+ * @returns {string} Hex color code.
+ */
+const timeToBackgroundColor = (time) => {
+ const lastUpdated = new Date(time).getTime();
+ const now = new Date().getTime();
+ const timeDelta = now - lastUpdated;
+
+ const day = 24 * 60 * 60 * 1000;
+ const week = day * 7;
+ const month = week * 4;
+
+ let backgroundColor = "white";
+
+ // 1-6 days old
+ if (timeDelta > day && timeDelta <= week) {
+ backgroundColor = red[100];
+ }
+ // 7-28 days old
+ else if (timeDelta > week && timeDelta <= month) {
+ backgroundColor = red[300];
+ }
+ // 29+ days old
+ else if (timeDelta > month) {
+ backgroundColor = red[500];
+ }
+
+ return backgroundColor;
+}
+
+export default function LastUpdatedCell({ time, ItemTableCellProps }) {
+ // Insert the calculated background color into props so it isn't overriden.
+ // Inspired by: https://github.com/mui-org/material-ui/issues/19479
+ ItemTableCellProps = {
+ ...ItemTableCellProps,
+ style: {
+ ...ItemTableCellProps.style,
+ backgroundColor: timeToBackgroundColor(time)
+ }
+ };
+
+ return (
+
+
+
+ );
+};
+
+LastUpdatedCell.propTypes = {
+ /** ISO 8601 formatted time string, Date object or UNIX time. See: https://www.npmjs.com/package/react-relative-time */
+ "time": PropTypes.string.isRequired,
+ /** Props to be applied to the ItemTableCell. */
+ "ItemTableCellProps": PropTypes.object,
+};
+
+LastUpdatedCell.defaultProps = {
+ "ItemTableCellProps": {},
+};
\ No newline at end of file
diff --git a/src/components/LastUpdatedCell/LastUpdatedCell.md b/src/components/LastUpdatedCell/LastUpdatedCell.md
new file mode 100644
index 0000000..ce823a5
--- /dev/null
+++ b/src/components/LastUpdatedCell/LastUpdatedCell.md
@@ -0,0 +1,38 @@
+A table cell that takes a time value and returns a relative time with a background color to indicate how old an item is.
+
+The LastUpdatedCell wraps an [ItemTableCell](/#/Components/ItemTableCell)
+
+## Default Usage
+```jsx
+import { Paper } from '@material-ui/core';
+import LastUpdatedCell from "./LastUpdatedCell";
+
+let today = new Date();
+let threeDaysAgo = new Date().setDate(today.getDate() - 3);
+let lastWeek = new Date().setDate(today.getDate() - 8);
+let lastMonth = new Date().setDate(today.getDate() - 28);
+
+
+ { /* Today */ }
+
+ { /* Three Days Ago */ }
+
+ { /* Last Week */ }
+
+ { /* Last Month */ }
+
+
+```
+
+```jsx static
+
+ { /* Today */ }
+
+ { /* Three Days Ago */ }
+
+ { /* Last Week */ }
+
+ { /* Last Month */ }
+
+
+```
\ No newline at end of file
diff --git a/src/components/LastUpdatedCell/index.js b/src/components/LastUpdatedCell/index.js
new file mode 100644
index 0000000..35380d3
--- /dev/null
+++ b/src/components/LastUpdatedCell/index.js
@@ -0,0 +1,3 @@
+import LastUpdatedCell from "./LastUpdatedCell";
+
+export default LastUpdatedCell;
\ No newline at end of file
diff --git a/utils/venv-manager.py b/utils/venv-manager.py
index 72ecd3b..3bb15ce 100644
--- a/utils/venv-manager.py
+++ b/utils/venv-manager.py
@@ -79,7 +79,7 @@ def get_args() -> argparse.Namespace:
return parser.parse_args()
-def run_logged_subprocess(command: Union[str, list], timeout: int = 10, shell: bool = True) -> tuple:
+def run_logged_subprocess(command: Union[str, list], timeout: int = 60, shell: bool = True) -> tuple:
"""Executes a shell command using subprocess with logging.
stderr is redirected to stdout and stdout is pipped to logger.
@@ -96,7 +96,7 @@ def run_logged_subprocess(command: Union[str, list], timeout: int = 10, shell: b
Args:
command (Union): The command to run. If shell=False, pass a list with the first item being the command and the subsequent items being arguments. If shell=True, pass a string as you would type it into a shell.
- timeout (int): The number of seconds to wait for a program before killing it
+ timeout (int): The number of seconds to wait for a program before killing it. Defaults to 60.
Returns:
tuple: With the first value being the return code and second being the combined stdout+stderr