CEC-5292: add battery info to fleet vehicles (#474)

* CEC-5292: add battery info to fleet vehicles

* add permission requirement

* set new message

* responsive

* fix missing status

* update snapshots

* update snapshots
This commit is contained in:
Tristan Timblin
2023-10-26 13:42:57 -07:00
committed by GitHub
parent 1059389d30
commit 342aa3e270
14 changed files with 269 additions and 24 deletions

View File

@@ -0,0 +1,62 @@
import { lazy } from "react";
import {
Tooltip
} from "@material-ui/core";
import useStyles from "../useStyles";
const Battery0 = lazy(() => import("@mui/icons-material/Battery0Bar"));
const BatteryFull = lazy(() => import("@mui/icons-material/BatteryFull"));
const BatteryUnknown = lazy(() => import("@mui/icons-material/BatteryUnknown"));
const BatteryCharging = lazy(() => import("@mui/icons-material/BatteryChargingFull"));
const Batteries = [
lazy(() => import("@mui/icons-material/Battery0Bar")),
lazy(() => import("@mui/icons-material/Battery1Bar")),
lazy(() => import("@mui/icons-material/Battery2Bar")),
lazy(() => import("@mui/icons-material/Battery3Bar")),
lazy(() => import("@mui/icons-material/Battery4Bar")),
lazy(() => import("@mui/icons-material/Battery5Bar")),
lazy(() => import("@mui/icons-material/Battery6Bar")),
];
const chargingStates = [
"V2L_trunk_active",
];
function getBatteryByPercent(percent) {
if (isNaN(percent) || percent > 100 || percent < 0) {
return BatteryUnknown;
}
if (percent === 0) {
return Battery0;
}
if (percent === 100) {
return BatteryFull;
}
const unit = 14.2857142857;
const range = Math.floor(percent / unit);
return Batteries[range];
}
export default function Battery({
percent,
charge = "unknown"
}) {
const classes = useStyles();
let Battery = getBatteryByPercent(percent);
if (chargingStates.includes(charge)) {
Battery = BatteryCharging;
}
return (
<Tooltip title={`State: ${charge}`}>
<div className={classes.alignCenter}>
<Battery color="disabled" /> {percent && `${percent}%`}
</div>
</Tooltip>
);
}

View File

@@ -81,7 +81,7 @@ export default function BulkActions({
{ {
id: "diagnostic", id: "diagnostic",
name: "Send Diagnostic", name: "Send Diagnostic",
disabled: false, // TODO set role disabled: !hasRole(groups, Permissions.CarDiagnostic, providers),
trigger: () => setActive("diagnostic"), trigger: () => setActive("diagnostic"),
} }
].filter((action) => actions.includes(action.id)); ].filter((action) => actions.includes(action.id));

View File

@@ -138,6 +138,10 @@ export const FleetProvider = ({ children }) => {
car_update_name: vehicle.carupdate?.updatemanifest?.name || "", car_update_name: vehicle.carupdate?.updatemanifest?.name || "",
car_update_status: vehicle.carupdate?.status || "", car_update_status: vehicle.carupdate?.status || "",
car_update_type: vehicle.carupdate?.updatemanifest?.type || "", car_update_type: vehicle.carupdate?.updatemanifest?.type || "",
voltage: vehicle.carstate?.battery?.battery_voltage,
charge: vehicle.carstate?.battery?.percent,
charge_type: vehicle.carstate?.vcu0x260?.charge_type,
park: vehicle.carstate?.gear?.in_park,
}); });
}); });
@@ -155,9 +159,16 @@ export const FleetProvider = ({ children }) => {
const result = await updatesApi.getCarUpdateProgress( const result = await updatesApi.getCarUpdateProgress(
carUpdateIdsRef.current.join(","), carUpdateIdsRef.current.join(","),
token token
).catch(() => { )
return Promise.reject(); .then((result) => {
}); if (!Array.isArray(result.statuses)) {
return Promise.reject();
}
return result;
})
.catch(() => {
return Promise.reject();
});
let pivot = result.statuses?.length ? result.statuses.length - 1 : 0; let pivot = result.statuses?.length ? result.statuses.length - 1 : 0;
setFleetVehicles((fleetVehicles) => fleetVehicles.map((vehicle) => { setFleetVehicles((fleetVehicles) => fleetVehicles.map((vehicle) => {
result.statuses.find((status, i) => { result.statuses.find((status, i) => {
@@ -184,6 +195,7 @@ export const FleetProvider = ({ children }) => {
break; break;
default: default:
vehicle.car_update_progress = -1; vehicle.car_update_progress = -1;
vehicle.car_update_status = status.msg;
break; break;
} }

View File

@@ -12,7 +12,7 @@ const FleetCANFiltersTab = () => {
return ( return (
<div className={clsx(classes.paper, classes.tableSize)}> <div className={clsx(classes.paper, classes.tableSize)}>
<Typography variant="h6">CAN Filters</Typography> <Typography variant="h6" className={classes.textCenterAlign}>CAN Filters</Typography>
<FleetCANFiltersTable name={name} classes={classes} /> <FleetCANFiltersTable name={name} classes={classes} />
</div > </div >
); );

View File

@@ -12,7 +12,7 @@ const FleetDetailsTab = () => {
return ( return (
<div className={clsx(classes.paper, classes.tableSize)}> <div className={clsx(classes.paper, classes.tableSize)}>
<Typography variant="h6">Fleet Details</Typography> <Typography variant="h6" className={classes.textCenterAlign}>Fleet Details</Typography>
<FleetDetails name={name} classes={classes} /> <FleetDetails name={name} classes={classes} />
</div > </div >
); );

View File

@@ -232,6 +232,75 @@ exports[`FleetVehiclesTable Render 1`] = `
</svg> </svg>
</span> </span>
</th> </th>
<th
class="MuiTableCell-root MuiTableCell-head MuiTableCell-alignCenter"
scope="col"
>
<span
aria-disabled="false"
class="MuiButtonBase-root MuiTableSortLabel-root"
role="button"
tabindex="0"
>
Battery
<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
class="MuiTableCell-root MuiTableCell-head MuiTableCell-alignCenter"
scope="col"
>
<span
aria-disabled="false"
class="MuiButtonBase-root MuiTableSortLabel-root"
role="button"
tabindex="0"
>
Voltage
<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
class="MuiTableCell-root MuiTableCell-head MuiTableCell-alignCenter"
scope="col"
>
<span
aria-disabled="false"
class="MuiButtonBase-root MuiTableSortLabel-root"
role="button"
tabindex="0"
>
Park
<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" class="MuiTableCell-root MuiTableCell-head MuiTableCell-alignCenter"
scope="col" scope="col"
@@ -251,7 +320,7 @@ exports[`FleetVehiclesTable Render 1`] = `
> >
<td <td
class="MuiTableCell-root MuiTableCell-footer MuiTablePagination-root" class="MuiTableCell-root MuiTableCell-footer MuiTablePagination-root"
colspan="8" colspan="9"
> >
<div <div
class="MuiToolbar-root MuiToolbar-regular MuiTablePagination-toolbar MuiToolbar-gutters" class="MuiToolbar-root MuiToolbar-regular MuiTablePagination-toolbar MuiToolbar-gutters"

View File

@@ -30,6 +30,7 @@ import TableHeaderSortable from "../../../../Table/HeaderSortable";
import { useLocalStorage } from "../../../../useLocalStorage"; import { useLocalStorage } from "../../../../useLocalStorage";
import ConnectedIcon from "../../../../Controls/ConnectedIcon"; import ConnectedIcon from "../../../../Controls/ConnectedIcon";
import BulkActions from "../../../../BulkActions"; import BulkActions from "../../../../BulkActions";
import Battery from "../../../../Battery";
import useStyles from "../../../../useStyles"; import useStyles from "../../../../useStyles";
const tableColumns = [ const tableColumns = [
@@ -49,6 +50,18 @@ const tableColumns = [
id: "car_update_status", id: "car_update_status",
label: "Car Update Status", label: "Car Update Status",
}, },
{
id: "battery",
label: "Battery",
},
{
id: "voltage",
label: "Voltage",
},
{
id: "park", // TODO: Update to 'gear' when we confirm each possible state
label: "Park",
},
{ {
id: "", id: "",
label: "Actions", label: "Actions",
@@ -257,13 +270,22 @@ const MainForm = ({ name }) => {
</Link> </Link>
</Tooltip> </Tooltip>
</TableCell> </TableCell>
<TableCell key={"cell4" + car.car_update_status}> <TableCell key={"cell4" + car.vin}>
{car.car_update_status} {car.car_update_status}
{car.car_update_progress > -1 && ( {car.car_update_progress > -1 && (
<LinearProgress variant="determinate" value={car.car_update_progress} /> <LinearProgress variant="determinate" value={car.car_update_progress} />
)} )}
</TableCell> </TableCell>
<TableCell key={"cell5" + car.vin} align="center">{Actions(car.vin)}</TableCell> <TableCell key={"cell5" + car.vin}>
<Battery percent={car.charge} charge={car.charge_type} />
</TableCell>
<TableCell key={"cell6" + car.vin}>
{car.voltage > 0 && `${car.voltage}V`}
</TableCell>
<TableCell key={"cell7" + car.vin}>
{car.park ? "Yes" : "No"}
</TableCell>
<TableCell key={"cell8" + car.vin} align="center">{Actions(car.vin)}</TableCell>
</TableRow> </TableRow>
) )
})} })}
@@ -272,7 +294,7 @@ const MainForm = ({ name }) => {
<TableRow> <TableRow>
<TablePagination <TablePagination
rowsPerPageOptions={[5, 10, 25, 100]} rowsPerPageOptions={[5, 10, 25, 100]}
colSpan={8} colSpan={9}
count={totalFleetVehicles} count={totalFleetVehicles}
rowsPerPage={pageSize} rowsPerPage={pageSize}
page={pageIndex} page={pageIndex}

View File

@@ -12,7 +12,7 @@ const FleetVehiclesTab = () => {
return ( return (
<div className={clsx(classes.paper, classes.tableSize)}> <div className={clsx(classes.paper, classes.tableSize)}>
<Typography variant="h6">Vehicles</Typography> <Typography variant="h6" className={classes.textCenterAlign}>Vehicles</Typography>
<FleetVehiclesTable name={name} classes={classes} /> <FleetVehiclesTable name={name} classes={classes} />
</div > </div >
); );

View File

@@ -6,7 +6,7 @@ exports[`CANFiltersTab Render 1`] = `
class="makeStyles-paper-0 makeStyles-tableSize-0" class="makeStyles-paper-0 makeStyles-tableSize-0"
> >
<h6 <h6
class="MuiTypography-root MuiTypography-h6" class="MuiTypography-root makeStyles-textCenterAlign-0 MuiTypography-h6"
> >
CAN Filters CAN Filters
</h6> </h6>

View File

@@ -15,7 +15,7 @@ exports[`DetailsTab Render 1`] = `
class="makeStyles-paper-0 makeStyles-tableSize-0" class="makeStyles-paper-0 makeStyles-tableSize-0"
> >
<h6 <h6
class="MuiTypography-root MuiTypography-h6" class="MuiTypography-root makeStyles-textCenterAlign-0 MuiTypography-h6"
> >
Fleet Details Fleet Details
</h6> </h6>

View File

@@ -6,7 +6,7 @@ exports[`VehiclesTab Render 1`] = `
class="makeStyles-paper-0 makeStyles-tableSize-0" class="makeStyles-paper-0 makeStyles-tableSize-0"
> >
<h6 <h6
class="MuiTypography-root MuiTypography-h6" class="MuiTypography-root makeStyles-textCenterAlign-0 MuiTypography-h6"
> >
Vehicles Vehicles
</h6> </h6>
@@ -231,6 +231,75 @@ exports[`VehiclesTab Render 1`] = `
</svg> </svg>
</span> </span>
</th> </th>
<th
class="MuiTableCell-root MuiTableCell-head MuiTableCell-alignCenter"
scope="col"
>
<span
aria-disabled="false"
class="MuiButtonBase-root MuiTableSortLabel-root"
role="button"
tabindex="0"
>
Battery
<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
class="MuiTableCell-root MuiTableCell-head MuiTableCell-alignCenter"
scope="col"
>
<span
aria-disabled="false"
class="MuiButtonBase-root MuiTableSortLabel-root"
role="button"
tabindex="0"
>
Voltage
<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
class="MuiTableCell-root MuiTableCell-head MuiTableCell-alignCenter"
scope="col"
>
<span
aria-disabled="false"
class="MuiButtonBase-root MuiTableSortLabel-root"
role="button"
tabindex="0"
>
Park
<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" class="MuiTableCell-root MuiTableCell-head MuiTableCell-alignCenter"
scope="col" scope="col"
@@ -250,7 +319,7 @@ exports[`VehiclesTab Render 1`] = `
> >
<td <td
class="MuiTableCell-root MuiTableCell-footer MuiTablePagination-root" class="MuiTableCell-root MuiTableCell-footer MuiTablePagination-root"
colspan="8" colspan="9"
> >
<div <div
class="MuiToolbar-root MuiToolbar-regular MuiTablePagination-toolbar MuiToolbar-gutters" class="MuiToolbar-root MuiToolbar-regular MuiTablePagination-toolbar MuiToolbar-gutters"

View File

@@ -12,10 +12,10 @@ exports[`FleetStatus Render 1`] = `
data-testid="mocked-userprovider" data-testid="mocked-userprovider"
> >
<div <div
class="makeStyles-paper-0 makeStyles-tableSize-0" class="makeStyles-paper-0 makeStyles-paperLeft-0 makeStyles-tableSize-0"
> >
<div <div
class="MuiBox-root MuiBox-root-0 makeStyles-tableToolbar-0" class="MuiBox-root MuiBox-root-0 makeStyles-tableToolbar-0 makeStyles-alignCenter-0"
> >
<div <div
class="MuiTabs-root" class="MuiTabs-root"
@@ -93,6 +93,7 @@ exports[`FleetStatus Render 1`] = `
</div> </div>
<div <div
aria-labelledby="tab-0" aria-labelledby="tab-0"
class="makeStyles-fullWidth-0"
id="tabpanel-0" id="tabpanel-0"
role="tabpanel" role="tabpanel"
> >
@@ -103,7 +104,7 @@ exports[`FleetStatus Render 1`] = `
class="makeStyles-paper-0 makeStyles-tableSize-0" class="makeStyles-paper-0 makeStyles-tableSize-0"
> >
<h6 <h6
class="MuiTypography-root MuiTypography-h6" class="MuiTypography-root makeStyles-textCenterAlign-0 MuiTypography-h6"
> >
Fleet Details Fleet Details
</h6> </h6>
@@ -291,12 +292,14 @@ exports[`FleetStatus Render 1`] = `
</div> </div>
<div <div
aria-labelledby="tab-1" aria-labelledby="tab-1"
class="makeStyles-fullWidth-0"
hidden="" hidden=""
id="tabpanel-1" id="tabpanel-1"
role="tabpanel" role="tabpanel"
/> />
<div <div
aria-labelledby="tab-2" aria-labelledby="tab-2"
class="makeStyles-fullWidth-0"
hidden="" hidden=""
id="tabpanel-2" id="tabpanel-2"
role="tabpanel" role="tabpanel"

View File

@@ -50,8 +50,8 @@ const FleetStatus = () => {
}; };
return ( return (
<div className={clsx(classes.paper, classes.tableSize)}> <div className={clsx(classes.paper, classes.paperLeft, classes.tableSize)}>
<Box className={classes.tableToolbar} sx={{ borderBottom: 1, borderColor: 'divider' }}> <Box className={clsx(classes.tableToolbar, classes.alignCenter)} sx={{ borderBottom: 1, borderColor: 'divider' }}>
<Tabs value={tabIndex} onChange={handleTabChange} aria-label="car tabs" indicatorColor="secondary"> <Tabs value={tabIndex} onChange={handleTabChange} aria-label="car tabs" indicatorColor="secondary">
<Tab label="Details" {...tabProps(0)} /> <Tab label="Details" {...tabProps(0)} />
<Tab label="Vehicles" {...tabProps(1)} /> <Tab label="Vehicles" {...tabProps(1)} />
@@ -59,15 +59,15 @@ const FleetStatus = () => {
</Tabs> </Tabs>
</Box> </Box>
<TabPanel value={tabIndex} index={0}> <TabPanel value={tabIndex} index={0} className={classes.fullWidth}>
<FleetDetailsTab /> <FleetDetailsTab />
</TabPanel> </TabPanel>
<TabPanel value={tabIndex} index={1}> <TabPanel value={tabIndex} index={1} className={classes.fullWidth}>
<FleetVehiclesTab /> <FleetVehiclesTab />
</TabPanel > </TabPanel >
<TabPanel value={tabIndex} index={2}> <TabPanel value={tabIndex} index={2} className={classes.fullWidth}>
<FleetCANFiltersTab /> <FleetCANFiltersTab />
</TabPanel> </TabPanel>
</div > </div >

View File

@@ -10,6 +10,11 @@ const useStyles = makeStyles((theme) => ({
alignItems: "center", alignItems: "center",
justifyContent: "center", justifyContent: "center",
}, },
alignCenter: {
display: "flex",
justifyContent: "center",
width: "100%",
},
modaldialog: { modaldialog: {
backgroundColor: theme.palette.background.paper, backgroundColor: theme.palette.background.paper,
border: "2px solid #000", border: "2px solid #000",
@@ -22,6 +27,9 @@ const useStyles = makeStyles((theme) => ({
flexDirection: "column", flexDirection: "column",
alignItems: "center", alignItems: "center",
}, },
paperLeft: {
alignItems: "flex-start",
},
avatar: { avatar: {
margin: theme.spacing(1), margin: theme.spacing(1),
backgroundColor: theme.palette.primary.main, backgroundColor: theme.palette.primary.main,
@@ -253,7 +261,7 @@ const useStyles = makeStyles((theme) => ({
padding: 15, padding: 15,
paddingBottom: 20, paddingBottom: 20,
}, },
textJustifyAlign: { textAlign: "justifyContent" }, textJustifyAlign: { textAlign: "justify-content" },
textCenterAlign: { textAlign: "center" }, textCenterAlign: { textAlign: "center" },
textRightAlign: { textAlign: "right" }, textRightAlign: { textAlign: "right" },
fullWidth: { width: "100%" }, fullWidth: { width: "100%" },