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",
name: "Send Diagnostic",
disabled: false, // TODO set role
disabled: !hasRole(groups, Permissions.CarDiagnostic, providers),
trigger: () => setActive("diagnostic"),
}
].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_status: vehicle.carupdate?.status || "",
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(
carUpdateIdsRef.current.join(","),
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;
setFleetVehicles((fleetVehicles) => fleetVehicles.map((vehicle) => {
result.statuses.find((status, i) => {
@@ -184,6 +195,7 @@ export const FleetProvider = ({ children }) => {
break;
default:
vehicle.car_update_progress = -1;
vehicle.car_update_status = status.msg;
break;
}

View File

@@ -12,7 +12,7 @@ const FleetCANFiltersTab = () => {
return (
<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} />
</div >
);

View File

@@ -12,7 +12,7 @@ const FleetDetailsTab = () => {
return (
<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} />
</div >
);

View File

@@ -232,6 +232,75 @@ exports[`FleetVehiclesTable Render 1`] = `
</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"
>
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
class="MuiTableCell-root MuiTableCell-head MuiTableCell-alignCenter"
scope="col"
@@ -251,7 +320,7 @@ exports[`FleetVehiclesTable Render 1`] = `
>
<td
class="MuiTableCell-root MuiTableCell-footer MuiTablePagination-root"
colspan="8"
colspan="9"
>
<div
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 ConnectedIcon from "../../../../Controls/ConnectedIcon";
import BulkActions from "../../../../BulkActions";
import Battery from "../../../../Battery";
import useStyles from "../../../../useStyles";
const tableColumns = [
@@ -49,6 +50,18 @@ const tableColumns = [
id: "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: "",
label: "Actions",
@@ -257,13 +270,22 @@ const MainForm = ({ name }) => {
</Link>
</Tooltip>
</TableCell>
<TableCell key={"cell4" + car.car_update_status}>
<TableCell key={"cell4" + car.vin}>
{car.car_update_status}
{car.car_update_progress > -1 && (
<LinearProgress variant="determinate" value={car.car_update_progress} />
)}
</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>
)
})}
@@ -272,7 +294,7 @@ const MainForm = ({ name }) => {
<TableRow>
<TablePagination
rowsPerPageOptions={[5, 10, 25, 100]}
colSpan={8}
colSpan={9}
count={totalFleetVehicles}
rowsPerPage={pageSize}
page={pageIndex}

View File

@@ -12,7 +12,7 @@ const FleetVehiclesTab = () => {
return (
<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} />
</div >
);

View File

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

View File

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

View File

@@ -6,7 +6,7 @@ exports[`VehiclesTab Render 1`] = `
class="makeStyles-paper-0 makeStyles-tableSize-0"
>
<h6
class="MuiTypography-root MuiTypography-h6"
class="MuiTypography-root makeStyles-textCenterAlign-0 MuiTypography-h6"
>
Vehicles
</h6>
@@ -231,6 +231,75 @@ exports[`VehiclesTab Render 1`] = `
</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"
>
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
class="MuiTableCell-root MuiTableCell-head MuiTableCell-alignCenter"
scope="col"
@@ -250,7 +319,7 @@ exports[`VehiclesTab Render 1`] = `
>
<td
class="MuiTableCell-root MuiTableCell-footer MuiTablePagination-root"
colspan="8"
colspan="9"
>
<div
class="MuiToolbar-root MuiToolbar-regular MuiTablePagination-toolbar MuiToolbar-gutters"

View File

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

View File

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

View File

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