Merge branch 'release/0.9.0'

This commit is contained in:
jwu-fisker
2023-07-18 14:34:29 -07:00
9 changed files with 686 additions and 216 deletions

View File

@@ -5301,14 +5301,63 @@ exports[`App Route /package-status authenticated 1`] = `
<div <div
data-testid="mocked-carupdatesprovider" data-testid="mocked-carupdatesprovider"
> >
<div
class="makeStyles-link-0"
>
Show Details
</div>
<div <div
class="makeStyles-paper-0 makeStyles-tableSize-0" class="makeStyles-paper-0 makeStyles-tableSize-0"
> >
<div
class="MuiGrid-root makeStyles-root-0 MuiGrid-container MuiGrid-spacing-xs-2"
>
<div
class="MuiGrid-root makeStyles-textJustifyAlign-0 MuiGrid-item MuiGrid-grid-md-8"
>
<div
class="makeStyles-link-0"
>
Show Details
</div>
</div>
<div
class="MuiGrid-root makeStyles-textRightAlign-0 MuiGrid-item MuiGrid-grid-md-4"
>
<div
aria-label="choose action"
class="MuiButtonGroup-root MuiButtonGroup-contained css-zqcytf-MuiButtonGroup-root"
role="group"
>
<button
class="MuiButtonBase-root MuiButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeMedium MuiButton-containedSizeMedium Mui-disabled MuiButtonGroup-grouped MuiButtonGroup-groupedHorizontal MuiButtonGroup-groupedContained MuiButtonGroup-groupedContainedHorizontal MuiButtonGroup-groupedContainedPrimary MuiButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeMedium MuiButton-containedSizeMedium MuiButtonGroup-grouped MuiButtonGroup-groupedHorizontal MuiButtonGroup-groupedContained MuiButtonGroup-groupedContainedHorizontal MuiButtonGroup-groupedContainedPrimary css-sghohy-MuiButtonBase-root-MuiButton-root"
disabled=""
tabindex="-1"
type="button"
>
Cancel Updates
</button>
<button
aria-haspopup="menu"
aria-label="select action"
class="MuiButtonBase-root MuiButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeSmall MuiButton-containedSizeSmall MuiButtonGroup-grouped MuiButtonGroup-groupedHorizontal MuiButtonGroup-groupedContained MuiButtonGroup-groupedContainedHorizontal MuiButtonGroup-groupedContainedPrimary MuiButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeSmall MuiButton-containedSizeSmall MuiButtonGroup-grouped MuiButtonGroup-groupedHorizontal MuiButtonGroup-groupedContained MuiButtonGroup-groupedContainedHorizontal MuiButtonGroup-groupedContainedPrimary css-11qr2p8-MuiButtonBase-root-MuiButton-root"
data-testid="dropdown-button-expand"
tabindex="0"
type="button"
>
<svg
aria-hidden="true"
class="MuiSvgIcon-root MuiSvgIcon-fontSizeMedium css-i4bv87-MuiSvgIcon-root"
data-testid="ArrowDropDownIcon"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="m7 10 5 5 5-5z"
/>
</svg>
<span
class="MuiTouchRipple-root css-8je8zh-MuiTouchRipple-root"
/>
</button>
</div>
</div>
</div>
<table <table
class="MuiTable-root" class="MuiTable-root"
> >
@@ -5319,39 +5368,160 @@ exports[`App Route /package-status authenticated 1`] = `
class="MuiTableRow-root MuiTableRow-head" class="MuiTableRow-root MuiTableRow-head"
> >
<th <th
class="MuiTableCell-root MuiTableCell-head MuiTableCell-paddingCheckbox"
scope="col"
>
<span
aria-disabled="false"
class="MuiButtonBase-root MuiIconButton-root PrivateSwitchBase-root-0 MuiCheckbox-root MuiCheckbox-colorSecondary MuiIconButton-colorSecondary"
>
<span
class="MuiIconButton-label"
>
<input
aria-label="select all desserts"
class="PrivateSwitchBase-input-0"
data-indeterminate="false"
type="checkbox"
value=""
/>
<svg
aria-hidden="true"
class="MuiSvgIcon-root"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M19 5v14H5V5h14m0-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2z"
/>
</svg>
</span>
<span
class="MuiTouchRipple-root"
/>
</span>
</th>
<th
aria-sort="ascending"
class="MuiTableCell-root MuiTableCell-head MuiTableCell-alignCenter" class="MuiTableCell-root MuiTableCell-head MuiTableCell-alignCenter"
scope="col" scope="col"
> >
ID <span
aria-disabled="false"
class="MuiButtonBase-root MuiTableSortLabel-root MuiTableSortLabel-active"
role="button"
tabindex="0"
>
ID
<span
class="makeStyles-hiddenSortSpan-0"
>
sorted ascending
</span>
<svg
aria-hidden="true"
class="MuiSvgIcon-root MuiTableSortLabel-icon MuiTableSortLabel-iconDirectionAsc"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M20 12l-1.41-1.41L13 16.17V4h-2v12.17l-5.58-5.59L4 12l8 8 8-8z"
/>
</svg>
</span>
</th> </th>
<th <th
class="MuiTableCell-root MuiTableCell-head MuiTableCell-alignCenter" class="MuiTableCell-root MuiTableCell-head MuiTableCell-alignCenter"
scope="col" scope="col"
> >
Vehicle <span
aria-disabled="false"
class="MuiButtonBase-root MuiTableSortLabel-root"
role="button"
tabindex="0"
>
Vehicle
<svg
aria-hidden="true"
class="MuiSvgIcon-root MuiTableSortLabel-icon MuiTableSortLabel-iconDirectionAsc"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M20 12l-1.41-1.41L13 16.17V4h-2v12.17l-5.58-5.59L4 12l8 8 8-8z"
/>
</svg>
</span>
</th> </th>
<th <th
class="MuiTableCell-root MuiTableCell-head MuiTableCell-alignCenter" class="MuiTableCell-root MuiTableCell-head MuiTableCell-alignCenter"
scope="col" scope="col"
> >
Status <span
aria-disabled="false"
class="MuiButtonBase-root MuiTableSortLabel-root"
role="button"
tabindex="0"
>
Status
<svg
aria-hidden="true"
class="MuiSvgIcon-root MuiTableSortLabel-icon MuiTableSortLabel-iconDirectionAsc"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M20 12l-1.41-1.41L13 16.17V4h-2v12.17l-5.58-5.59L4 12l8 8 8-8z"
/>
</svg>
</span>
</th> </th>
<th <th
class="MuiTableCell-root MuiTableCell-head MuiTableCell-alignCenter" class="MuiTableCell-root MuiTableCell-head MuiTableCell-alignCenter"
scope="col" scope="col"
> >
Created <span
aria-disabled="false"
class="MuiButtonBase-root MuiTableSortLabel-root"
role="button"
tabindex="0"
>
Created
<svg
aria-hidden="true"
class="MuiSvgIcon-root MuiTableSortLabel-icon MuiTableSortLabel-iconDirectionAsc"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M20 12l-1.41-1.41L13 16.17V4h-2v12.17l-5.58-5.59L4 12l8 8 8-8z"
/>
</svg>
</span>
</th> </th>
<th <th
class="MuiTableCell-root MuiTableCell-head MuiTableCell-alignCenter" class="MuiTableCell-root MuiTableCell-head MuiTableCell-alignCenter"
scope="col" scope="col"
> >
Updated <span
aria-disabled="false"
class="MuiButtonBase-root MuiTableSortLabel-root"
role="button"
tabindex="0"
>
Updated
<svg
aria-hidden="true"
class="MuiSvgIcon-root MuiTableSortLabel-icon MuiTableSortLabel-iconDirectionAsc"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M20 12l-1.41-1.41L13 16.17V4h-2v12.17l-5.58-5.59L4 12l8 8 8-8z"
/>
</svg>
</span>
</th> </th>
<th
class="MuiTableCell-root MuiTableCell-head MuiTableCell-alignCenter"
scope="col"
/>
</tr> </tr>
</thead> </thead>
<tbody <tbody
@@ -5360,6 +5530,38 @@ exports[`App Route /package-status authenticated 1`] = `
<tr <tr
class="MuiTableRow-root" class="MuiTableRow-root"
> >
<td
class="MuiTableCell-root MuiTableCell-body MuiTableCell-paddingCheckbox"
>
<span
aria-disabled="false"
class="MuiButtonBase-root MuiIconButton-root PrivateSwitchBase-root-0 MuiCheckbox-root MuiCheckbox-colorSecondary MuiIconButton-colorSecondary"
>
<span
class="MuiIconButton-label"
>
<input
class="PrivateSwitchBase-input-0"
data-indeterminate="false"
type="checkbox"
value=""
/>
<svg
aria-hidden="true"
class="MuiSvgIcon-root"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M19 5v14H5V5h14m0-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2z"
/>
</svg>
</span>
<span
class="MuiTouchRipple-root"
/>
</span>
</td>
<td <td
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter" class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
> >
@@ -5390,30 +5592,42 @@ exports[`App Route /package-status authenticated 1`] = `
> >
7/12/2021 6:22:13 PM 7/12/2021 6:22:13 PM
</td> </td>
<td
class="MuiTableCell-root MuiTableCell-body"
>
<a
class=""
href="/package-status/1"
title="Send cancel for 1G1FP87S3GN100062"
>
<svg
aria-hidden="true"
class="MuiSvgIcon-root"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M12 2C6.47 2 2 6.47 2 12s4.47 10 10 10 10-4.47 10-10S17.53 2 12 2zm5 13.59L15.59 17 12 13.41 8.41 17 7 15.59 10.59 12 7 8.41 8.41 7 12 10.59 15.59 7 17 8.41 13.41 12 17 15.59z"
/>
</svg>
</a>
</td>
</tr> </tr>
<tr <tr
class="MuiTableRow-root" class="MuiTableRow-root"
> >
<td
class="MuiTableCell-root MuiTableCell-body MuiTableCell-paddingCheckbox"
>
<span
aria-disabled="false"
class="MuiButtonBase-root MuiIconButton-root PrivateSwitchBase-root-0 MuiCheckbox-root MuiCheckbox-colorSecondary MuiIconButton-colorSecondary"
>
<span
class="MuiIconButton-label"
>
<input
class="PrivateSwitchBase-input-0"
data-indeterminate="false"
type="checkbox"
value=""
/>
<svg
aria-hidden="true"
class="MuiSvgIcon-root"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M19 5v14H5V5h14m0-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2z"
/>
</svg>
</span>
<span
class="MuiTouchRipple-root"
/>
</span>
</td>
<td <td
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter" class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
> >
@@ -5444,30 +5658,42 @@ exports[`App Route /package-status authenticated 1`] = `
> >
7/12/2021 6:22:13 PM 7/12/2021 6:22:13 PM
</td> </td>
<td
class="MuiTableCell-root MuiTableCell-body"
>
<a
class=""
href="/package-status/1"
title="Send cancel for 1G1FP87S3GN100062"
>
<svg
aria-hidden="true"
class="MuiSvgIcon-root"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M12 2C6.47 2 2 6.47 2 12s4.47 10 10 10 10-4.47 10-10S17.53 2 12 2zm5 13.59L15.59 17 12 13.41 8.41 17 7 15.59 10.59 12 7 8.41 8.41 7 12 10.59 15.59 7 17 8.41 13.41 12 17 15.59z"
/>
</svg>
</a>
</td>
</tr> </tr>
<tr <tr
class="MuiTableRow-root" class="MuiTableRow-root"
> >
<td
class="MuiTableCell-root MuiTableCell-body MuiTableCell-paddingCheckbox"
>
<span
aria-disabled="false"
class="MuiButtonBase-root MuiIconButton-root PrivateSwitchBase-root-0 MuiCheckbox-root MuiCheckbox-colorSecondary MuiIconButton-colorSecondary"
>
<span
class="MuiIconButton-label"
>
<input
class="PrivateSwitchBase-input-0"
data-indeterminate="false"
type="checkbox"
value=""
/>
<svg
aria-hidden="true"
class="MuiSvgIcon-root"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M19 5v14H5V5h14m0-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2z"
/>
</svg>
</span>
<span
class="MuiTouchRipple-root"
/>
</span>
</td>
<td <td
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter" class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
> >
@@ -5498,26 +5724,6 @@ exports[`App Route /package-status authenticated 1`] = `
> >
7/12/2021 6:22:13 PM 7/12/2021 6:22:13 PM
</td> </td>
<td
class="MuiTableCell-root MuiTableCell-body"
>
<a
class=""
href="/package-status/1"
title="Send cancel for 1G1FP87S3GN100062"
>
<svg
aria-hidden="true"
class="MuiSvgIcon-root"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M12 2C6.47 2 2 6.47 2 12s4.47 10 10 10 10-4.47 10-10S17.53 2 12 2zm5 13.59L15.59 17 12 13.41 8.41 17 7 15.59 10.59 12 7 8.41 8.41 7 12 10.59 15.59 7 17 8.41 13.41 12 17 15.59z"
/>
</svg>
</a>
</td>
</tr> </tr>
</tbody> </tbody>
<tfoot <tfoot
@@ -12295,45 +12501,6 @@ exports[`App Route /vehicles authenticated 1`] = `
/> />
</svg> </svg>
</a> </a>
<div
aria-label="choose action"
class="MuiButtonGroup-root MuiButtonGroup-contained css-zqcytf-MuiButtonGroup-root"
role="group"
>
<button
class="MuiButtonBase-root MuiButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeMedium MuiButton-containedSizeMedium MuiButtonGroup-grouped MuiButtonGroup-groupedHorizontal MuiButtonGroup-groupedContained MuiButtonGroup-groupedContainedHorizontal MuiButtonGroup-groupedContainedPrimary MuiButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeMedium MuiButton-containedSizeMedium MuiButtonGroup-grouped MuiButtonGroup-groupedHorizontal MuiButtonGroup-groupedContained MuiButtonGroup-groupedContainedHorizontal MuiButtonGroup-groupedContainedPrimary css-sghohy-MuiButtonBase-root-MuiButton-root"
tabindex="0"
type="button"
>
Add Tags
<span
class="MuiTouchRipple-root css-8je8zh-MuiTouchRipple-root"
/>
</button>
<button
aria-haspopup="menu"
aria-label="select action"
class="MuiButtonBase-root MuiButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeSmall MuiButton-containedSizeSmall MuiButtonGroup-grouped MuiButtonGroup-groupedHorizontal MuiButtonGroup-groupedContained MuiButtonGroup-groupedContainedHorizontal MuiButtonGroup-groupedContainedPrimary MuiButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeSmall MuiButton-containedSizeSmall MuiButtonGroup-grouped MuiButtonGroup-groupedHorizontal MuiButtonGroup-groupedContained MuiButtonGroup-groupedContainedHorizontal MuiButtonGroup-groupedContainedPrimary css-11qr2p8-MuiButtonBase-root-MuiButton-root"
data-testid="dropdown-button-expand"
tabindex="0"
type="button"
>
<svg
aria-hidden="true"
class="MuiSvgIcon-root MuiSvgIcon-fontSizeMedium css-i4bv87-MuiSvgIcon-root"
data-testid="ArrowDropDownIcon"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="m7 10 5 5 5-5z"
/>
</svg>
<span
class="MuiTouchRipple-root css-8je8zh-MuiTouchRipple-root"
/>
</button>
</div>
</div> </div>
<div <div
class="MuiGrid-root makeStyles-textCenterAlign-0 MuiGrid-item MuiGrid-grid-md-4" class="MuiGrid-root makeStyles-textCenterAlign-0 MuiGrid-item MuiGrid-grid-md-4"

View File

@@ -46,7 +46,7 @@ export default function BulkActions({
const payload = { const payload = {
vins, vins,
vinCSV: vins.join(", "), vinCSV: (vins && vins.length > 0) ? vins.join(", ") : "N/A",
ref: activeRef ref: activeRef
}; };
@@ -63,6 +63,8 @@ export default function BulkActions({
setTitle(filteredActions.find((action) => active === action.id)?.name || "Action"); setTitle(filteredActions.find((action) => active === action.id)?.name || "Action");
}, [active, filteredActions]); }, [active, filteredActions]);
if (!vins || vins.length === 0) return <></>;
return ( return (
<> <>
<DropDownButton actions={filteredActions} /> <DropDownButton actions={filteredActions} />

View File

@@ -37,45 +37,6 @@ exports[`VehicleTable Render 1`] = `
/> />
</svg> </svg>
</a> </a>
<div
aria-label="choose action"
class="MuiButtonGroup-root MuiButtonGroup-contained css-zqcytf-MuiButtonGroup-root"
role="group"
>
<button
class="MuiButtonBase-root MuiButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeMedium MuiButton-containedSizeMedium MuiButtonGroup-grouped MuiButtonGroup-groupedHorizontal MuiButtonGroup-groupedContained MuiButtonGroup-groupedContainedHorizontal MuiButtonGroup-groupedContainedPrimary MuiButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeMedium MuiButton-containedSizeMedium MuiButtonGroup-grouped MuiButtonGroup-groupedHorizontal MuiButtonGroup-groupedContained MuiButtonGroup-groupedContainedHorizontal MuiButtonGroup-groupedContainedPrimary css-sghohy-MuiButtonBase-root-MuiButton-root"
tabindex="0"
type="button"
>
Add Tags
<span
class="MuiTouchRipple-root css-8je8zh-MuiTouchRipple-root"
/>
</button>
<button
aria-haspopup="menu"
aria-label="select action"
class="MuiButtonBase-root MuiButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeSmall MuiButton-containedSizeSmall MuiButtonGroup-grouped MuiButtonGroup-groupedHorizontal MuiButtonGroup-groupedContained MuiButtonGroup-groupedContainedHorizontal MuiButtonGroup-groupedContainedPrimary MuiButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeSmall MuiButton-containedSizeSmall MuiButtonGroup-grouped MuiButtonGroup-groupedHorizontal MuiButtonGroup-groupedContained MuiButtonGroup-groupedContainedHorizontal MuiButtonGroup-groupedContainedPrimary css-11qr2p8-MuiButtonBase-root-MuiButton-root"
data-testid="dropdown-button-expand"
tabindex="0"
type="button"
>
<svg
aria-hidden="true"
class="MuiSvgIcon-root MuiSvgIcon-fontSizeMedium css-i4bv87-MuiSvgIcon-root"
data-testid="ArrowDropDownIcon"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="m7 10 5 5 5-5z"
/>
</svg>
<span
class="MuiTouchRipple-root css-8je8zh-MuiTouchRipple-root"
/>
</button>
</div>
</div> </div>
<div <div
class="MuiGrid-root makeStyles-textCenterAlign-0 MuiGrid-item MuiGrid-grid-md-4" class="MuiGrid-root makeStyles-textCenterAlign-0 MuiGrid-item MuiGrid-grid-md-4"

View File

@@ -1,15 +1,14 @@
import { import {
Checkbox,
Grid,
LinearProgress, LinearProgress,
Table, Table,
TableBody, TableBody,
TableCell, TableCell,
TableFooter, TableFooter,
TableHead,
TablePagination, TablePagination,
TableRow, TableRow,
Tooltip,
} from "@material-ui/core"; } from "@material-ui/core";
import CancelIcon from "@material-ui/icons/Cancel";
import clsx from "clsx"; import clsx from "clsx";
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { useParams } from "react-router"; import { useParams } from "react-router";
@@ -17,7 +16,6 @@ import { Link } from "react-router-dom";
import { logger } from "../../../services/monitoring"; import { logger } from "../../../services/monitoring";
import { LocalDateTimeString } from "../../../utils/dates"; import { LocalDateTimeString } from "../../../utils/dates";
import { Permissions } from "../../../utils/roles";
import { import {
CarUpdatesProvider, CarUpdatesProvider,
useCarUpdatesContext useCarUpdatesContext
@@ -28,21 +26,47 @@ import {
} from "../../Contexts/ManifestsContext"; } from "../../Contexts/ManifestsContext";
import { useStatusContext } from "../../Contexts/StatusContext"; import { useStatusContext } from "../../Contexts/StatusContext";
import { useUserContext } from "../../Contexts/UserContext"; import { useUserContext } from "../../Contexts/UserContext";
import { RoleWrap } from "../../Controls/RoleWrap";
import { useLocalStorage } from "../../useLocalStorage"; import { useLocalStorage } from "../../useLocalStorage";
import useStyles from "../../useStyles"; import useStyles from "../../useStyles";
import ManifestDetails from "../Details"; import ManifestDetails from "../Details";
import TableHeaderSortable from "../../Table/HeaderSortable";
import Toolbar from "../Toolbar";
const PAGE_SIZE = "MANIFEST_STATUS_PAGE_SIZE"; const PAGE_SIZE = "MANIFEST_STATUS_PAGE_SIZE";
const tableColumns = [
{
id: "id",
label: "ID",
},
{
id: "vin",
label: "Vehicle",
},
{
id: "status",
label: "Status",
},
{
id: "created_at",
label: "Created",
},
{
id: "updated_at",
label: "Updated",
},
];
const MainForm = () => { const MainForm = () => {
const { manifest_id } = useParams(); const { manifest_id } = useParams();
const classes = useStyles(); const classes = useStyles();
const [pageSize, setPageSize] = useLocalStorage(PAGE_SIZE, 10); const [pageSize, setPageSize] = useLocalStorage(PAGE_SIZE, 10);
const [pageIndex, setPageIndex] = useState(0); const [pageIndex, setPageIndex] = useState(0);
const [orderBy, setOrderBy] = useState("id");
const [order, setOrder] = useState("asc");
const [ids, setIds] = useState([]);
const { getManifests, manifests } = useManifestsContext(); const { getManifests, manifests } = useManifestsContext();
const { const {
cancelUpdate,
getCarUpdates, getCarUpdates,
carUpdates, carUpdates,
totalCarUpdates, totalCarUpdates,
@@ -54,10 +78,35 @@ const MainForm = () => {
token: { token: {
idToken: { jwtToken: token }, idToken: { jwtToken: token },
}, },
groups,
providers,
} = useUserContext(); } = useUserContext();
const handleSelectAll = () => {
setIds((ids) => ids.length === 0
? carUpdates.map((carUpdate) => carUpdate.id)
: []);
}
const handleSelect = (newId, selected) => {
if (selected) {
setIds((ids) => ids.filter((id) => id !== newId));
} else {
setIds((ids) => [...ids, newId]);
}
}
const handleSort = (_event, property) => {
if (property === orderBy) {
if (order === "asc") {
setOrder("desc");
} else {
setOrder("asc");
}
} else {
setOrderBy(property);
setOrder("asc");
}
};
useEffect(() => { useEffect(() => {
(async () => { (async () => {
try { try {
@@ -100,6 +149,7 @@ const MainForm = () => {
manifest_id, manifest_id,
limit: pageSize, limit: pageSize,
offset: pageSize * pageIndex, offset: pageSize * pageIndex,
order: `${orderBy} ${order}`,
}, },
token token
); );
@@ -109,7 +159,7 @@ const MainForm = () => {
} }
})(); })();
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [pageIndex, pageSize, token]); }, [pageIndex, pageSize, token, order, orderBy]);
useEffect(() => { useEffect(() => {
try { try {
@@ -134,63 +184,58 @@ const MainForm = () => {
setPageIndex(0); setPageIndex(0);
}; };
const sendCancel = async ({ id, vin }) => {
try {
await cancelUpdate(id, token);
setMessage(`Sent cancel for ${vin}`);
} catch (e) {
setMessage(e.message);
}
};
return ( return (
<div className={clsx(classes.paper, classes.tableSize)}> <div className={clsx(classes.paper, classes.tableSize)}>
<Grid container className={classes.root} spacing={2}>
<Grid item md={8} className={classes.textJustifyAlign}>
<ManifestDetails />
</Grid>
<Grid item md={4} className={classes.textRightAlign}>
<Toolbar ids={ids} actions={["cancel"]} />
</Grid>
</Grid>
<Table> <Table>
<TableHead> <TableHeaderSortable
<TableRow> classes={classes}
<TableCell align="center">ID</TableCell> orderBy={orderBy}
<TableCell align="center">Vehicle</TableCell> order={order}
<TableCell align="center">Status</TableCell> columnData={tableColumns}
<TableCell align="center">Created</TableCell> onSortRequest={handleSort}
<TableCell align="center">Updated</TableCell> multiSelect={true}
<TableCell align="center"></TableCell> onSelectAll={handleSelectAll}
</TableRow> selectCount={ids ? ids.length : 0}
</TableHead> rowCount={carUpdates ? carUpdates.length : 0}
/>
<TableBody> <TableBody>
{carUpdates.map((row) => ( {carUpdates.map((row) => {
<TableRow key={row.id}> const isSelected = ids.indexOf(row.id) !== -1;
<TableCell align="center">{row.id}</TableCell> return (
<TableCell align="center" style={{ verticalAlign: "top" }}> <TableRow key={row.id}>
<Link to={`/vehicle-status/${row.vin}`}>{row.vin}</Link> <TableCell padding="checkbox">
</TableCell> <Checkbox
<TableCell align="center"> checked={isSelected}
{row.status} onChange={() => handleSelect(row.id, isSelected)}
{row.progress > -1 && ( />
<LinearProgress variant="determinate" value={row.progress} /> </TableCell>
)} <TableCell align="center">{row.id}</TableCell>
</TableCell> <TableCell align="center" style={{ verticalAlign: "top" }}>
<TableCell align="center"> <Link to={`/vehicle-status/${row.vin}`}>{row.vin}</Link>
{LocalDateTimeString(row.created)} </TableCell>
</TableCell> <TableCell align="center">
<TableCell align="center"> {row.status}
{LocalDateTimeString(row.updated)} {row.progress > -1 && (
</TableCell> <LinearProgress variant="determinate" value={row.progress} />
<TableCell> )}
<RoleWrap </TableCell>
groups={groups} <TableCell align="center">
providers={providers} {LocalDateTimeString(row.created)}
rolesPerProvider={Permissions.FiskerMagnaCreate} </TableCell>
eitherComponent={<>No action</>} <TableCell align="center">
> {LocalDateTimeString(row.updated)}
<Tooltip key={row.vin} title={`Send cancel for ${row.vin}`}> </TableCell>
<Link to="#" onClick={() => sendCancel(row)}> </TableRow>
<CancelIcon /> )
</Link> })}
</Tooltip>
</RoleWrap>
</TableCell>
</TableRow>
))}
</TableBody> </TableBody>
<TableFooter> <TableFooter>
<TableRow> <TableRow>
@@ -217,7 +262,6 @@ const MainForm = () => {
const ManifestStatus = () => ( const ManifestStatus = () => (
<ManifestsProvider> <ManifestsProvider>
<CarUpdatesProvider> <CarUpdatesProvider>
<ManifestDetails />
<MainForm /> <MainForm />
</CarUpdatesProvider> </CarUpdatesProvider>
</ManifestsProvider> </ManifestsProvider>

View File

@@ -0,0 +1,53 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Toolbar Render 1`] = `
<div>
<div
data-testid="mocked-statusprovider"
>
<div
data-testid="mocked-userprovider"
>
<div
aria-label="choose action"
class="MuiButtonGroup-root MuiButtonGroup-contained css-zqcytf-MuiButtonGroup-root"
role="group"
>
<button
class="MuiButtonBase-root MuiButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeMedium MuiButton-containedSizeMedium MuiButtonGroup-grouped MuiButtonGroup-groupedHorizontal MuiButtonGroup-groupedContained MuiButtonGroup-groupedContainedHorizontal MuiButtonGroup-groupedContainedPrimary MuiButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeMedium MuiButton-containedSizeMedium MuiButtonGroup-grouped MuiButtonGroup-groupedHorizontal MuiButtonGroup-groupedContained MuiButtonGroup-groupedContainedHorizontal MuiButtonGroup-groupedContainedPrimary css-sghohy-MuiButtonBase-root-MuiButton-root"
tabindex="0"
type="button"
>
Cancel Updates
<span
class="MuiTouchRipple-root css-8je8zh-MuiTouchRipple-root"
/>
</button>
<button
aria-haspopup="menu"
aria-label="select action"
class="MuiButtonBase-root MuiButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeSmall MuiButton-containedSizeSmall MuiButtonGroup-grouped MuiButtonGroup-groupedHorizontal MuiButtonGroup-groupedContained MuiButtonGroup-groupedContainedHorizontal MuiButtonGroup-groupedContainedPrimary MuiButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeSmall MuiButton-containedSizeSmall MuiButtonGroup-grouped MuiButtonGroup-groupedHorizontal MuiButtonGroup-groupedContained MuiButtonGroup-groupedContainedHorizontal MuiButtonGroup-groupedContainedPrimary css-11qr2p8-MuiButtonBase-root-MuiButton-root"
data-testid="dropdown-button-expand"
tabindex="0"
type="button"
>
<svg
aria-hidden="true"
class="MuiSvgIcon-root MuiSvgIcon-fontSizeMedium css-i4bv87-MuiSvgIcon-root"
data-testid="ArrowDropDownIcon"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="m7 10 5 5 5-5z"
/>
</svg>
<span
class="MuiTouchRipple-root css-8je8zh-MuiTouchRipple-root"
/>
</button>
</div>
</div>
</div>
</div>
`;

View File

@@ -0,0 +1,55 @@
import { forwardRef, useImperativeHandle } from "react";
import { useStatusContext } from "../../../Contexts/StatusContext";
import { useUserContext } from "../../../Contexts/UserContext";
import TaskRunner from "../../../../utils/taskRunner";
import updatesAPI from "../../../../services/updatesAPI";
export default forwardRef(({
ids,
idCSV,
}, ref) => {
const { setMessage } = useStatusContext();
const { token: { idToken: { jwtToken: token } } } = useUserContext();
useImperativeHandle(ref, () => ({
async submit() {
return new Promise((resolve, reject) => {
const taskRunner = new TaskRunner(5, ids.length);
let errorCount = 0;
const task = (id, index) => {
const progressMessage = `${index + 1}/${ids.length}`;
return async () => updatesAPI.cancelCarUpdate(id, token)
.then((response) => {
if (response.error) {
errorCount += 1;
setMessage(`${progressMessage} ${response.error}: ${response.message}`);
} else {
setMessage(`${progressMessage} Canceled update ${id}`);
}
return response;
})
.catch((error) => reject(error));
}
ids.forEach((id, i) => {
taskRunner.push(task(id, i));
});
taskRunner.onComplete().then((responses) => {
const completeMessage = `${ids.length - errorCount}/${ids.length}`;
setMessage(`Successfully canceled ${completeMessage} updates.`);
resolve(responses);
});
});
},
}));
return (
<div>
<p>
You are canceling the following updates: {idCSV}.
</p>
</div>
);
});

View File

@@ -0,0 +1,40 @@
jest.mock("../../../Contexts/UserContext");
jest.mock("../../../Contexts/StatusContext");
jest.mock("../../../../services/updatesAPI");
import React from "react";
import {
render,
act,
} from "@testing-library/react";
import { UserProvider, setToken } from "../../../Contexts/UserContext";
import { StatusProvider } from "../../../Contexts/StatusContext";
import { TEST_AUTH_OBJECT_FISKER } from "../../../../utils/testing";
import Cancel from "./Cancel";
import updatesAPI from "../../../../services/updatesAPI";
describe("Manifest/Cancel", () => {
beforeAll(() => {
setToken(TEST_AUTH_OBJECT_FISKER);
});
it("makes request to cancel an update", async () => {
const api = jest.spyOn(updatesAPI, "cancelCarUpdate");
const ref = React.createRef();
render(
<StatusProvider>
<UserProvider>
<Cancel
ref={ref}
ids={["1", "2", "3"]}
idCSV=""
/>
</UserProvider>
</StatusProvider>
);
await act(async () => ref.current.submit());
expect(api).toHaveBeenCalledTimes(3);
});
});

View File

@@ -0,0 +1,71 @@
import { useEffect, useState, useRef, Suspense, lazy } from "react";
import DropDownButton from "../../Controls/DropDownButton";
import { Modal } from "../../BulkActions/Modal";
import { useUserContext } from "../../Contexts/UserContext";
import { Permissions, hasRole } from "../../../utils/roles";
// Code-splitting individual actions
// https://react.dev/reference/react/lazy
const Cancel = lazy(() => import("./actions/Cancel"));
export default function Toolbar({
ids = [],
actions = [],
}) {
const [title, setTitle] = useState("Action");
const [open, setOpen] = useState(false);
const [active, setActive] = useState(null);
const activeRef = useRef();
const { groups, providers } = useUserContext();
const hasAccess = hasRole(groups, Permissions.FiskerMagnaCreate, providers);
const filteredActions = [
{
id: "cancel",
name: "Cancel Updates",
disabled: !hasAccess || ids.length <= 0,
trigger: () => {
setOpen(true);
setActive("cancel");
},
},
].filter((action) => actions.includes(action.id));
const payload = {
ids,
idCSV: ids.join(", "),
ref: activeRef
};
const handleClose = () => {
setOpen(false).then(() => setActive(null));
}
const handleSubmit = () => {
activeRef.current.submit();
handleClose();
}
useEffect(() => {
setTitle(filteredActions.find((action) => active === action.id)?.name || "Action");
}, [active, filteredActions]);
return (
<>
<DropDownButton actions={filteredActions} />
<Modal
title={title}
open={open}
close={handleClose}
submit={handleSubmit}
>
<Suspense fallback={<div>Loading...</div>}>
<section>
{active === "cancel" && <Cancel {...payload} />}
</section>
</Suspense>
</Modal>
</>
)
}

View File

@@ -0,0 +1,77 @@
jest.mock("../../Contexts/UserContext");
jest.mock("../../Contexts/StatusContext");
import React from "react";
import {
fireEvent,
render,
screen,
waitFor,
} from "@testing-library/react";
import { UserProvider, setToken } from "../../Contexts/UserContext";
import { StatusProvider } from "../../Contexts/StatusContext";
import { TEST_AUTH_OBJECT_FISKER } from "../../../utils/testing";
import Toolbar from ".";
import addSnapshotSerializer from "../../../utils/snapshot";
describe("Toolbar", () => {
beforeAll(() => {
setToken(TEST_AUTH_OBJECT_FISKER);
global.URL.createObjectURL = jest.fn();
global.URL.revokeObjectURL = jest.fn();
addSnapshotSerializer(expect);
});
it("Render", async () => {
const { container } = render(
<StatusProvider>
<UserProvider>
<Toolbar
actions={["cancel"]}
ids={[1]}
/>
</UserProvider>
</StatusProvider>
);
await waitFor(() => {
/* render */
});
expect(container).toMatchSnapshot();
});
it("opens a modal", async () => {
render(
<StatusProvider>
<UserProvider>
<Toolbar
actions={["cancel"]}
ids={[1]}
/>
</UserProvider>
</StatusProvider>
);
const buttonEl = screen.getByText("Cancel Updates");
fireEvent.click(buttonEl);
const submitEl = screen.getByText("Submit");
expect(submitEl).toBeTruthy();
});
it("filters valid actions", async () => {
render(
<StatusProvider>
<UserProvider>
<Toolbar
actions={["cancel", "someInvalidAction"]}
ids={[1]}
/>
</UserProvider>
</StatusProvider>
);
const dropdownBtn = screen.getByTestId("dropdown-button-expand");
fireEvent.click(dropdownBtn);
const dropdownOptions = screen.getAllByRole("menuitem");
expect(dropdownOptions.length).toBe(1);
});
});