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