Merge branch 'develop' into release/0.9.0
This commit is contained in:
@@ -9,6 +9,7 @@ jest.mock("../../services/vehiclesAPI");
|
|||||||
jest.mock("../../services/superset");
|
jest.mock("../../services/superset");
|
||||||
jest.mock("../../services/suppliersAPI");
|
jest.mock("../../services/suppliersAPI");
|
||||||
jest.mock("../../services/issueAPI");
|
jest.mock("../../services/issueAPI");
|
||||||
|
jest.mock("../TransformModal");
|
||||||
|
|
||||||
import {
|
import {
|
||||||
act, cleanup, render,
|
act, cleanup, render,
|
||||||
|
|||||||
@@ -11276,6 +11276,7 @@ exports[`App Route /vehicle-status authenticated 1`] = `
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
aria-labelledby="tab-0"
|
aria-labelledby="tab-0"
|
||||||
|
class="makeStyles-tabContainer-0"
|
||||||
id="tabpanel-0"
|
id="tabpanel-0"
|
||||||
role="tabpanel"
|
role="tabpanel"
|
||||||
>
|
>
|
||||||
@@ -11528,54 +11529,63 @@ exports[`App Route /vehicle-status authenticated 1`] = `
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
aria-labelledby="tab-1"
|
aria-labelledby="tab-1"
|
||||||
|
class="makeStyles-tabContainer-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-tabContainer-0"
|
||||||
hidden=""
|
hidden=""
|
||||||
id="tabpanel-2"
|
id="tabpanel-2"
|
||||||
role="tabpanel"
|
role="tabpanel"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
aria-labelledby="tab-3"
|
aria-labelledby="tab-3"
|
||||||
|
class="makeStyles-tabContainer-0"
|
||||||
hidden=""
|
hidden=""
|
||||||
id="tabpanel-3"
|
id="tabpanel-3"
|
||||||
role="tabpanel"
|
role="tabpanel"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
aria-labelledby="tab-4"
|
aria-labelledby="tab-4"
|
||||||
|
class="makeStyles-tabContainer-0"
|
||||||
hidden=""
|
hidden=""
|
||||||
id="tabpanel-4"
|
id="tabpanel-4"
|
||||||
role="tabpanel"
|
role="tabpanel"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
aria-labelledby="tab-5"
|
aria-labelledby="tab-5"
|
||||||
|
class="makeStyles-tabContainer-0"
|
||||||
hidden=""
|
hidden=""
|
||||||
id="tabpanel-5"
|
id="tabpanel-5"
|
||||||
role="tabpanel"
|
role="tabpanel"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
aria-labelledby="tab-6"
|
aria-labelledby="tab-6"
|
||||||
|
class="makeStyles-tabContainer-0"
|
||||||
hidden=""
|
hidden=""
|
||||||
id="tabpanel-6"
|
id="tabpanel-6"
|
||||||
role="tabpanel"
|
role="tabpanel"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
aria-labelledby="tab-7"
|
aria-labelledby="tab-7"
|
||||||
|
class="makeStyles-tabContainer-0"
|
||||||
hidden=""
|
hidden=""
|
||||||
id="tabpanel-7"
|
id="tabpanel-7"
|
||||||
role="tabpanel"
|
role="tabpanel"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
aria-labelledby="tab-8"
|
aria-labelledby="tab-8"
|
||||||
|
class="makeStyles-tabContainer-0"
|
||||||
hidden=""
|
hidden=""
|
||||||
id="tabpanel-8"
|
id="tabpanel-8"
|
||||||
role="tabpanel"
|
role="tabpanel"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
aria-labelledby="tab-9"
|
aria-labelledby="tab-9"
|
||||||
|
class="makeStyles-tabContainer-0"
|
||||||
hidden=""
|
hidden=""
|
||||||
id="tabpanel-9"
|
id="tabpanel-9"
|
||||||
role="tabpanel"
|
role="tabpanel"
|
||||||
@@ -12652,6 +12662,13 @@ exports[`App Route /vehicles authenticated 1`] = `
|
|||||||
</tfoot>
|
</tfoot>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
data-testid="transform-modal"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
data-testid="transform-modal"
|
||||||
|
/>
|
||||||
|
<div />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
@@ -482,6 +482,7 @@ exports[`VehicleTable Render 1`] = `
|
|||||||
</tfoot>
|
</tfoot>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
<div />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -14,12 +14,14 @@ import { RoleWrap } from "../../Controls/RoleWrap";
|
|||||||
import SearchField from "../../Controls/SearchField";
|
import SearchField from "../../Controls/SearchField";
|
||||||
import DropDownButton from "../../Controls/DropDownButton";
|
import DropDownButton from "../../Controls/DropDownButton";
|
||||||
import TransformModal from "../../TransformModal";
|
import TransformModal from "../../TransformModal";
|
||||||
|
import { useLocalStorage } from "../../useLocalStorage";
|
||||||
import useStyles from "../../useStyles";
|
import useStyles from "../../useStyles";
|
||||||
import TaskRunner from "../../../utils/taskRunner";
|
import TaskRunner from "../../../utils/taskRunner";
|
||||||
|
import GeneralConfirmation from "../../GeneralConfirmation";
|
||||||
|
|
||||||
const MainForm = () => {
|
const MainForm = () => {
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
const [search, setSearch] = useState("");
|
const [search, setSearch] = useLocalStorage("VEHICLE_SEARCH", "");
|
||||||
const [online, setOnline] = useState(false);
|
const [online, setOnline] = useState(false);
|
||||||
const [onlineHMI, setOnlineHMI] = useState(false);
|
const [onlineHMI, setOnlineHMI] = useState(false);
|
||||||
const [selectedVins, setSelectedVins] = useState([]);
|
const [selectedVins, setSelectedVins] = useState([]);
|
||||||
@@ -29,8 +31,15 @@ const MainForm = () => {
|
|||||||
type: "boolean",
|
type: "boolean",
|
||||||
value: false
|
value: false
|
||||||
},
|
},
|
||||||
})
|
});
|
||||||
const [showUpdateConfigModal, setShowUpdateConfigModal] = useState(false);
|
const [tagsToAdd, setTagsToAdd] = useState({
|
||||||
|
tags: {
|
||||||
|
label: "Tags",
|
||||||
|
type: "list.string",
|
||||||
|
value: [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const [activeModal, setActiveModal] = useState(null);
|
||||||
const { setTitle, setSitePath, setMessage } = useStatusContext();
|
const { setTitle, setSitePath, setMessage } = useStatusContext();
|
||||||
const {
|
const {
|
||||||
token: {
|
token: {
|
||||||
@@ -64,7 +73,7 @@ const MainForm = () => {
|
|||||||
const handleUploadConfig = (fn) => {
|
const handleUploadConfig = (fn) => {
|
||||||
const taskRunner = new TaskRunner(5);
|
const taskRunner = new TaskRunner(5);
|
||||||
const request = (vin, i) => {
|
const request = (vin, i) => {
|
||||||
const messagePrefix = `${i+1}/${selectedVins.length} "${vin}":`;
|
const messagePrefix = `${i + 1}/${selectedVins.length} "${vin}":`;
|
||||||
return async () => {
|
return async () => {
|
||||||
const result = await fn(vin, config.force.value, token)
|
const result = await fn(vin, config.force.value, token)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
@@ -79,11 +88,44 @@ const MainForm = () => {
|
|||||||
selectedVins.forEach((vin, i) => taskRunner.push(request(vin, i)))
|
selectedVins.forEach((vin, i) => taskRunner.push(request(vin, i)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleAddTags = async (fn) => {
|
||||||
|
await fn(selectedVins, tagsToAdd.tags.value, token)
|
||||||
|
.then(() => setMessage(`Added ${tagsToAdd.tags.value.length} tags to ${selectedVins.length} vehicles.`))
|
||||||
|
.catch((error) => setMessage(error.message));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDelete = async (fn) => {
|
||||||
|
const taskRunner = new TaskRunner(5);
|
||||||
|
const request = (vin) => {
|
||||||
|
return async () => {
|
||||||
|
return fn(vin, token)
|
||||||
|
.then(() => {
|
||||||
|
setMessage(`Deleted ${selectedVins.length} vehicles`);
|
||||||
|
setSelectedVins([]);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
setMessage(error.message);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
selectedVins.forEach((vin) => taskRunner.push(request(vin)));
|
||||||
|
};
|
||||||
|
|
||||||
const actions = [
|
const actions = [
|
||||||
{
|
{
|
||||||
name: "Update Configs",
|
name: "Update Configs",
|
||||||
disabled: selectedVins.length === 0,
|
disabled: selectedVins.length === 0,
|
||||||
trigger: () => setShowUpdateConfigModal(true),
|
trigger: () => setActiveModal("updateConfig"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Add Tags",
|
||||||
|
disabled: selectedVins.length === 0,
|
||||||
|
trigger: () => setActiveModal("addTags"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Delete",
|
||||||
|
disabled: selectedVins.length === 0,
|
||||||
|
trigger: () => setActiveModal("delete"),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -113,7 +155,7 @@ const MainForm = () => {
|
|||||||
<DropDownButton actions={actions} payload={[selectedVins]} />
|
<DropDownButton actions={actions} payload={[selectedVins]} />
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item md={4} className={classes.textCenterAlign}>
|
<Grid item md={4} className={classes.textCenterAlign}>
|
||||||
<SearchField classes={classes} onSearch={handleSearch} />
|
<SearchField classes={classes} onSearch={handleSearch} savedSearchValue={search} />
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item md={2} className={clsx(classes.textJustifyAlign, classes.actionsBar)}>
|
<Grid item md={2} className={clsx(classes.textJustifyAlign, classes.actionsBar)}>
|
||||||
<OptionsDropdown listId="filter-menu">
|
<OptionsDropdown listId="filter-menu">
|
||||||
@@ -149,17 +191,33 @@ const MainForm = () => {
|
|||||||
onSelectAll={handleSelectAll}
|
onSelectAll={handleSelectAll}
|
||||||
/>
|
/>
|
||||||
<VehicleConsumer>
|
<VehicleConsumer>
|
||||||
{(context) => (
|
{(context) => (<>
|
||||||
<TransformModal
|
<TransformModal
|
||||||
open={showUpdateConfigModal}
|
open={activeModal === "updateConfig"}
|
||||||
close={() => setShowUpdateConfigModal(false)}
|
close={() => setActiveModal(null)}
|
||||||
title="Update Configs"
|
title="Update Configs"
|
||||||
body={`You are updating the config for the following VINs: ${selectedVins.join(", ")}.`}
|
body={`You are updating the config for the following VINs: ${selectedVins.join(", ")}.`}
|
||||||
data={config}
|
data={config}
|
||||||
setData={setConfig}
|
setData={setConfig}
|
||||||
submit={() => handleUploadConfig(context.uploadConfig)}
|
submit={() => handleUploadConfig(context.uploadConfig)}
|
||||||
/>
|
/>
|
||||||
)}
|
<TransformModal
|
||||||
|
open={activeModal === "addTags"}
|
||||||
|
close={() => setActiveModal(null)}
|
||||||
|
title="Add Tags"
|
||||||
|
body={`You are adding tags for the following VINs: ${selectedVins.join(", ")}.`}
|
||||||
|
data={tagsToAdd}
|
||||||
|
setData={setTagsToAdd}
|
||||||
|
submit={() => handleAddTags(context.addTags)}
|
||||||
|
/>
|
||||||
|
<GeneralConfirmation
|
||||||
|
open={activeModal === "delete"}
|
||||||
|
close={() => setActiveModal(null)}
|
||||||
|
title="Delete"
|
||||||
|
message={`You are about to delete the following VINs: ${selectedVins.join(", ")}`}
|
||||||
|
actionFunction={() => handleDelete(context.deleteVehicle)}
|
||||||
|
/>
|
||||||
|
</>)}
|
||||||
</VehicleConsumer>
|
</VehicleConsumer>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ const MainForm = ({ vin }) => {
|
|||||||
} = useUserContext();
|
} = useUserContext();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={clsx(classes.paper, classes.tableSize)}>
|
<div className={clsx(classes.tableSize, classes.textCenterAlign)}>
|
||||||
<Typography variant="h6" className={classes.labelInline}>
|
<Typography variant="h6" className={classes.labelInline}>
|
||||||
Car ECUs
|
Car ECUs
|
||||||
</Typography>
|
</Typography>
|
||||||
@@ -25,9 +25,9 @@ const MainForm = ({ vin }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const CarUpdatesTab = ({vin}) => (
|
const CarUpdatesTab = ({ vin }) => (
|
||||||
<VehicleProvider>
|
<VehicleProvider>
|
||||||
<MainForm vin={vin}/>
|
<MainForm vin={vin} />
|
||||||
</VehicleProvider>
|
</VehicleProvider>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -55,6 +55,13 @@ exports[`DigitalTwinTab Render 1`] = `
|
|||||||
:
|
:
|
||||||
12000 km
|
12000 km
|
||||||
</p>
|
</p>
|
||||||
|
<p>
|
||||||
|
<b>
|
||||||
|
Voltage
|
||||||
|
</b>
|
||||||
|
:
|
||||||
|
12.5 V
|
||||||
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<b>
|
<b>
|
||||||
Max Range
|
Max Range
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ exports[`ECUsTab Render 1`] = `
|
|||||||
data-testid="mocked-userprovider"
|
data-testid="mocked-userprovider"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="makeStyles-paper-0 makeStyles-tableSize-0"
|
class="makeStyles-tableSize-0 makeStyles-textCenterAlign-0"
|
||||||
>
|
>
|
||||||
<h6
|
<h6
|
||||||
class="MuiTypography-root makeStyles-labelInline-0 MuiTypography-h6"
|
class="MuiTypography-root makeStyles-labelInline-0 MuiTypography-h6"
|
||||||
@@ -17,7 +17,7 @@ exports[`ECUsTab Render 1`] = `
|
|||||||
Car ECUs
|
Car ECUs
|
||||||
</h6>
|
</h6>
|
||||||
<div
|
<div
|
||||||
class="makeStyles-paper-0 makeStyles-tableSize-0"
|
class="MuiTableContainer-root"
|
||||||
>
|
>
|
||||||
<table
|
<table
|
||||||
class="MuiTable-root"
|
class="MuiTable-root"
|
||||||
|
|||||||
@@ -223,6 +223,7 @@ exports[`CarStatus Render 1`] = `
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
aria-labelledby="tab-0"
|
aria-labelledby="tab-0"
|
||||||
|
class="makeStyles-tabContainer-0"
|
||||||
id="tabpanel-0"
|
id="tabpanel-0"
|
||||||
role="tabpanel"
|
role="tabpanel"
|
||||||
>
|
>
|
||||||
@@ -418,54 +419,63 @@ exports[`CarStatus Render 1`] = `
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
aria-labelledby="tab-1"
|
aria-labelledby="tab-1"
|
||||||
|
class="makeStyles-tabContainer-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-tabContainer-0"
|
||||||
hidden=""
|
hidden=""
|
||||||
id="tabpanel-2"
|
id="tabpanel-2"
|
||||||
role="tabpanel"
|
role="tabpanel"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
aria-labelledby="tab-3"
|
aria-labelledby="tab-3"
|
||||||
|
class="makeStyles-tabContainer-0"
|
||||||
hidden=""
|
hidden=""
|
||||||
id="tabpanel-3"
|
id="tabpanel-3"
|
||||||
role="tabpanel"
|
role="tabpanel"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
aria-labelledby="tab-4"
|
aria-labelledby="tab-4"
|
||||||
|
class="makeStyles-tabContainer-0"
|
||||||
hidden=""
|
hidden=""
|
||||||
id="tabpanel-4"
|
id="tabpanel-4"
|
||||||
role="tabpanel"
|
role="tabpanel"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
aria-labelledby="tab-5"
|
aria-labelledby="tab-5"
|
||||||
|
class="makeStyles-tabContainer-0"
|
||||||
hidden=""
|
hidden=""
|
||||||
id="tabpanel-5"
|
id="tabpanel-5"
|
||||||
role="tabpanel"
|
role="tabpanel"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
aria-labelledby="tab-6"
|
aria-labelledby="tab-6"
|
||||||
|
class="makeStyles-tabContainer-0"
|
||||||
hidden=""
|
hidden=""
|
||||||
id="tabpanel-6"
|
id="tabpanel-6"
|
||||||
role="tabpanel"
|
role="tabpanel"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
aria-labelledby="tab-7"
|
aria-labelledby="tab-7"
|
||||||
|
class="makeStyles-tabContainer-0"
|
||||||
hidden=""
|
hidden=""
|
||||||
id="tabpanel-7"
|
id="tabpanel-7"
|
||||||
role="tabpanel"
|
role="tabpanel"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
aria-labelledby="tab-8"
|
aria-labelledby="tab-8"
|
||||||
|
class="makeStyles-tabContainer-0"
|
||||||
hidden=""
|
hidden=""
|
||||||
id="tabpanel-8"
|
id="tabpanel-8"
|
||||||
role="tabpanel"
|
role="tabpanel"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
aria-labelledby="tab-9"
|
aria-labelledby="tab-9"
|
||||||
|
class="makeStyles-tabContainer-0"
|
||||||
hidden=""
|
hidden=""
|
||||||
id="tabpanel-9"
|
id="tabpanel-9"
|
||||||
role="tabpanel"
|
role="tabpanel"
|
||||||
|
|||||||
@@ -147,8 +147,13 @@ const CarStatus = () => {
|
|||||||
</Tabs>
|
</Tabs>
|
||||||
</Box>
|
</Box>
|
||||||
{tabs.map((item, index) => (
|
{tabs.map((item, index) => (
|
||||||
<TabPanel key={index} value={tabIndex} index={index}>
|
<TabPanel
|
||||||
<item.component vin={vin}/>
|
key={index}
|
||||||
|
value={tabIndex}
|
||||||
|
index={index}
|
||||||
|
className={classes.tabContainer}
|
||||||
|
>
|
||||||
|
<item.component vin={vin} />
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -61,6 +61,22 @@ export const VehicleProvider = ({ children }) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const addTags = async (vins, tags, token) => {
|
||||||
|
try {
|
||||||
|
setBusy(true);
|
||||||
|
vins.forEach(vin => validateVIN(vin));
|
||||||
|
const validateTags = tags.every(tag => typeof tag === "string");
|
||||||
|
if (!validateTags)
|
||||||
|
throw new Error("Invalid Tag");
|
||||||
|
|
||||||
|
const result = await api.addTags(vins, tags, token)
|
||||||
|
if (result.error)
|
||||||
|
throw new Error(`Add tags error. ${result.message}`);
|
||||||
|
} finally {
|
||||||
|
setBusy(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const getConnections = async (vins, token) => {
|
const getConnections = async (vins, token) => {
|
||||||
try {
|
try {
|
||||||
setBusy(true);
|
setBusy(true);
|
||||||
@@ -301,6 +317,7 @@ export const VehicleProvider = ({ children }) => {
|
|||||||
getFleets,
|
getFleets,
|
||||||
getVersionLog,
|
getVersionLog,
|
||||||
uploadConfig,
|
uploadConfig,
|
||||||
|
addTags,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ let vehicleState = {
|
|||||||
battery: {
|
battery: {
|
||||||
total_mileage_odometer: 12000,
|
total_mileage_odometer: 12000,
|
||||||
percent: 95,
|
percent: 95,
|
||||||
|
battery_voltage: 12.5,
|
||||||
},
|
},
|
||||||
max_range: {
|
max_range: {
|
||||||
max_miles: 577,
|
max_miles: 577,
|
||||||
|
|||||||
@@ -2,12 +2,12 @@ import {
|
|||||||
Table,
|
Table,
|
||||||
TableBody,
|
TableBody,
|
||||||
TableCell,
|
TableCell,
|
||||||
|
TableContainer,
|
||||||
TableFooter,
|
TableFooter,
|
||||||
TablePagination,
|
TablePagination,
|
||||||
TableRow,
|
TableRow,
|
||||||
Tooltip
|
Tooltip
|
||||||
} from "@material-ui/core";
|
} from "@material-ui/core";
|
||||||
import clsx from "clsx";
|
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
|
|
||||||
import { logger } from "../../../services/monitoring";
|
import { logger } from "../../../services/monitoring";
|
||||||
@@ -122,7 +122,7 @@ const CarECUsTable = ({ vin, token, classes }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={clsx(classes.paper, classes.tableSize)}>
|
<TableContainer>
|
||||||
<Table>
|
<Table>
|
||||||
<TableHeaderSortable
|
<TableHeaderSortable
|
||||||
classes={classes}
|
classes={classes}
|
||||||
@@ -166,7 +166,7 @@ const CarECUsTable = ({ vin, token, classes }) => {
|
|||||||
</TableRow>
|
</TableRow>
|
||||||
</TableFooter>
|
</TableFooter>
|
||||||
</Table>
|
</Table>
|
||||||
</div>
|
</TableContainer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import {
|
import {
|
||||||
FormControl,
|
FormControl,
|
||||||
IconButton,
|
IconButton,
|
||||||
@@ -10,7 +10,7 @@ import SearchIcon from "@material-ui/icons/Search";
|
|||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
|
|
||||||
const SearchField = (props) => {
|
const SearchField = (props) => {
|
||||||
const { classes, onSearch } = props;
|
const { classes, onSearch, savedSearchValue } = props;
|
||||||
const [searchTerm, setSearchTerm] = useState("");
|
const [searchTerm, setSearchTerm] = useState("");
|
||||||
const handleChange = (e) => {
|
const handleChange = (e) => {
|
||||||
setSearchTerm(e.target.value);
|
setSearchTerm(e.target.value);
|
||||||
@@ -29,6 +29,12 @@ const SearchField = (props) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (savedSearchValue) {
|
||||||
|
setSearchTerm(savedSearchValue);
|
||||||
|
}
|
||||||
|
}, [savedSearchValue]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FormControl className={clsx(classes.margin, classes.fullWidth)}>
|
<FormControl className={clsx(classes.margin, classes.fullWidth)}>
|
||||||
<InputLabel htmlFor="search">Search</InputLabel>
|
<InputLabel htmlFor="search">Search</InputLabel>
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`DropDownButton Render 1`] = `
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
class="MuiFormControl-root MuiFormControl-marginNormal MuiFormControl-fullWidth MuiTextField-root css-17vbkzs-MuiFormControl-root-MuiTextField-root"
|
||||||
|
data-testid="text-input-list"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-sizeSmall MuiInputLabel-outlined MuiFormLabel-colorPrimary Mui-required MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-sizeSmall MuiInputLabel-outlined css-1pysi21-MuiFormLabel-root-MuiInputLabel-root"
|
||||||
|
data-shrink="false"
|
||||||
|
for="mui-0"
|
||||||
|
id="mui-0-label"
|
||||||
|
>
|
||||||
|
The input label (use commas to add multiple)
|
||||||
|
<span
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiFormLabel-asterisk MuiInputLabel-asterisk css-wgai2y-MuiFormLabel-asterisk"
|
||||||
|
>
|
||||||
|
|
||||||
|
*
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<div
|
||||||
|
class="MuiInputBase-root MuiOutlinedInput-root MuiInputBase-colorPrimary MuiInputBase-fullWidth MuiInputBase-formControl MuiInputBase-sizeSmall css-md26zr-MuiInputBase-root-MuiOutlinedInput-root"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
aria-invalid="false"
|
||||||
|
class="MuiInputBase-input MuiOutlinedInput-input MuiInputBase-inputSizeSmall css-1n4twyu-MuiInputBase-input-MuiOutlinedInput-input"
|
||||||
|
id="mui-0"
|
||||||
|
name="text"
|
||||||
|
required=""
|
||||||
|
type="text"
|
||||||
|
value=""
|
||||||
|
/>
|
||||||
|
<fieldset
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiOutlinedInput-notchedOutline css-1d3z3hw-MuiOutlinedInput-notchedOutline"
|
||||||
|
>
|
||||||
|
<legend
|
||||||
|
class="css-yjsfm1"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
The input label (use commas to add multiple)
|
||||||
|
|
||||||
|
*
|
||||||
|
</span>
|
||||||
|
</legend>
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="MuiBox-root css-6km64s"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
97
src/components/Controls/TextInputList/index.jsx
Normal file
97
src/components/Controls/TextInputList/index.jsx
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
import { useState, useEffect } from "react";
|
||||||
|
import { Cancel } from "@mui/icons-material";
|
||||||
|
import { TextField } from "@mui/material";
|
||||||
|
import { Box } from "@mui/system";
|
||||||
|
import { useStatusContext } from "../../Contexts/StatusContext";
|
||||||
|
|
||||||
|
const TextInput = ({ text, handleDelete }) => {
|
||||||
|
return (
|
||||||
|
<Box sx={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: "2px",
|
||||||
|
p: "2px 4px",
|
||||||
|
backgroundColor: "#ccc",
|
||||||
|
borderRadius: 1,
|
||||||
|
}}>
|
||||||
|
{text}
|
||||||
|
<Cancel
|
||||||
|
sx={{
|
||||||
|
color: "#666",
|
||||||
|
fontSize: "16px",
|
||||||
|
cursor: "pointer",
|
||||||
|
}}
|
||||||
|
onClick={() => handleDelete(text)}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const TextInputList = ({
|
||||||
|
onChange = () => {},
|
||||||
|
validate = () => {},
|
||||||
|
label
|
||||||
|
}) => {
|
||||||
|
const [textList, setTextList] = useState([]);
|
||||||
|
const [input, setInput] = useState("");
|
||||||
|
|
||||||
|
const { setMessage } = useStatusContext();
|
||||||
|
|
||||||
|
const handleDelete = (textToDelete) => {
|
||||||
|
setTextList(textList => textList.filter(text => text !== textToDelete));
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleOnChange = (event) => {
|
||||||
|
const char = event.nativeEvent.data;
|
||||||
|
if (char === ",") {
|
||||||
|
try {
|
||||||
|
if (validate) validate(input);
|
||||||
|
setTextList(textList => [...textList, input]);
|
||||||
|
setInput("");
|
||||||
|
} catch {
|
||||||
|
setMessage(`"${input}" is not valid.`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setInput(event.target.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
onChange(input.length ? [...textList, input] : [...textList]);
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [textList, input]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<TextField
|
||||||
|
data-testid="text-input-list"
|
||||||
|
name="text"
|
||||||
|
label={`${label} (use commas to add multiple)`}
|
||||||
|
value={input}
|
||||||
|
variant="outlined"
|
||||||
|
margin="normal"
|
||||||
|
required
|
||||||
|
size="small"
|
||||||
|
fullWidth
|
||||||
|
onChange={handleOnChange}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Box sx={{
|
||||||
|
display: "flex",
|
||||||
|
flexWrap: "wrap",
|
||||||
|
gap: 1,
|
||||||
|
pr: 1,
|
||||||
|
}}>
|
||||||
|
{textList.map((text) => (
|
||||||
|
<TextInput
|
||||||
|
text={text}
|
||||||
|
handleDelete={handleDelete}
|
||||||
|
key={text}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TextInputList;
|
||||||
57
src/components/Controls/TextInputList/index.test.jsx
Normal file
57
src/components/Controls/TextInputList/index.test.jsx
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
jest.mock("../../Contexts/StatusContext");
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
import { render, waitFor } from "@testing-library/react";
|
||||||
|
import userEvent from '@testing-library/user-event';
|
||||||
|
|
||||||
|
import TextInputList from ".";
|
||||||
|
import addSnapshotSerializer from "../../../utils/snapshot";
|
||||||
|
|
||||||
|
describe("DropDownButton", () => {
|
||||||
|
beforeAll(() => {
|
||||||
|
addSnapshotSerializer(expect);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Render", async () => {
|
||||||
|
const { container } = render(
|
||||||
|
<TextInputList
|
||||||
|
label={"The input label"}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
await waitFor(() => {
|
||||||
|
/* render */
|
||||||
|
});
|
||||||
|
expect(container).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("properly adds tag after comma", async () => {
|
||||||
|
const { getByText, getByTestId } = render(
|
||||||
|
<TextInputList
|
||||||
|
label={"The input label"}
|
||||||
|
payload={[]}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
const [inputEl] = getByTestId("text-input-list").getElementsByTagName("input");
|
||||||
|
userEvent.type(inputEl, "tag1");
|
||||||
|
userEvent.type(inputEl, ",");
|
||||||
|
expect(getByText("tag1").nodeName).toBe("DIV");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("properly passes payload to callback", async () => {
|
||||||
|
const mockCallback = jest.fn();
|
||||||
|
|
||||||
|
const { getByTestId } = render(
|
||||||
|
<TextInputList
|
||||||
|
label={"The input label"}
|
||||||
|
onChange={mockCallback}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
const [inputEl] = getByTestId("text-input-list").getElementsByTagName("input");
|
||||||
|
userEvent.type(inputEl, "tag1");
|
||||||
|
userEvent.type(inputEl, ",");
|
||||||
|
userEvent.type(inputEl, "tag2");
|
||||||
|
expect(mockCallback).toHaveBeenCalledWith(["tag1", "tag2"]);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -8,6 +8,11 @@ const UNKNOWN = "unknown";
|
|||||||
const LOCKED = "Locked";
|
const LOCKED = "Locked";
|
||||||
const UNLOCKED = "Unlocked";
|
const UNLOCKED = "Unlocked";
|
||||||
|
|
||||||
|
const appendUnits = (value, units) => {
|
||||||
|
if (value || value === 0) return `${value}${units}`;
|
||||||
|
return UNKNOWN;
|
||||||
|
}
|
||||||
|
|
||||||
const keyValueTemplate = (key, value) => (
|
const keyValueTemplate = (key, value) => (
|
||||||
<p key={key}>
|
<p key={key}>
|
||||||
<b>{key}</b>: {value}
|
<b>{key}</b>: {value}
|
||||||
@@ -37,16 +42,17 @@ const DigitalTwin = (props) => {
|
|||||||
{(battery || max_range) && (
|
{(battery || max_range) && (
|
||||||
<div className={classes.popupSection}>
|
<div className={classes.popupSection}>
|
||||||
<h3>Battery</h3>
|
<h3>Battery</h3>
|
||||||
{keyValueTemplate("Percentage", `${battery?.percent || 0}%`)}
|
{keyValueTemplate("Percentage", appendUnits(battery?.percent || 0, "%"))}
|
||||||
{keyValueTemplate("Total Mileage", `${battery?.total_mileage_odometer} km` || UNKNOWN)}
|
{keyValueTemplate("Total Mileage", appendUnits(battery?.total_mileage_odometer, " km"))}
|
||||||
{keyValueTemplate("Max Range", `${max_range?.max_miles} km` || UNKNOWN)}
|
{keyValueTemplate("Voltage", appendUnits(battery?.battery_voltage, " V"))}
|
||||||
|
{keyValueTemplate("Max Range", appendUnits(max_range?.max_miles, " km"))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{(vcu0x260 || charging_metrics) && (
|
{(vcu0x260 || charging_metrics) && (
|
||||||
<div className={classes.popupSection}>
|
<div className={classes.popupSection}>
|
||||||
<h3>Charging</h3>
|
<h3>Charging</h3>
|
||||||
{keyValueTemplate("Charge Type", vcu0x260?.charge_type || UNKNOWN)}
|
{keyValueTemplate("Charge Type", vcu0x260?.charge_type || UNKNOWN)}
|
||||||
{keyValueTemplate("Remaining Time", `${charging_metrics?.remaining_charging_time} min` || UNKNOWN)}
|
{keyValueTemplate("Remaining Time", appendUnits(charging_metrics?.remaining_charging_time, " min"))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{doors && (
|
{doors && (
|
||||||
@@ -95,9 +101,9 @@ const DigitalTwin = (props) => {
|
|||||||
return keyValueTemplate(value[0], "Invalid")
|
return keyValueTemplate(value[0], "Invalid")
|
||||||
}
|
}
|
||||||
if (value[0] === "altitude") {
|
if (value[0] === "altitude") {
|
||||||
return keyValueTemplate(value[0], `${value[1]} m`);
|
return keyValueTemplate(value[0], appendUnits(value[1], " m"));
|
||||||
} else {
|
} else {
|
||||||
return keyValueTemplate(value[0], `${value[1]}°`);
|
return keyValueTemplate(value[0], appendUnits(value[1], "°"));
|
||||||
}
|
}
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
@@ -124,7 +130,7 @@ const DigitalTwin = (props) => {
|
|||||||
)}
|
)}
|
||||||
{vehicle_speed && (
|
{vehicle_speed && (
|
||||||
<div className={classes.popupSection}>
|
<div className={classes.popupSection}>
|
||||||
{keyValueTemplate("Vehicle Speed", `${vehicle_speed.speed} km/h`)}
|
{keyValueTemplate("Vehicle Speed", appendUnits(vehicle_speed?.speed, " km/h"))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -86,6 +86,23 @@ exports[`FleetDetailsTab Render 1`] = `
|
|||||||
:
|
:
|
||||||
3
|
3
|
||||||
</p>
|
</p>
|
||||||
|
<p>
|
||||||
|
<b>
|
||||||
|
DTC Enabled
|
||||||
|
</b>
|
||||||
|
:
|
||||||
|
false
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="MuiGrid-root makeStyles-textCenterAlign-0 MuiGrid-item MuiGrid-grid-md-12"
|
||||||
|
>
|
||||||
|
<p>
|
||||||
|
<b>
|
||||||
|
Debug Mask
|
||||||
|
</b>
|
||||||
|
:
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="MuiGrid-root makeStyles-textCenterAlign-0 MuiGrid-item MuiGrid-grid-md-12"
|
class="MuiGrid-root makeStyles-textCenterAlign-0 MuiGrid-item MuiGrid-grid-md-12"
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ const MainForm = ({ name }) => {
|
|||||||
const [showDeleteModal, setShowDeleteModal] = useState(false)
|
const [showDeleteModal, setShowDeleteModal] = useState(false)
|
||||||
const { token: { idToken: { jwtToken: token } } } = useUserContext();
|
const { token: { idToken: { jwtToken: token } } } = useUserContext();
|
||||||
|
|
||||||
|
const showDebugMask = (process.env.REACT_APP_ENABLE_DEBUGMASK === "1");
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
(async () => {
|
(async () => {
|
||||||
try {
|
try {
|
||||||
@@ -70,6 +72,14 @@ const MainForm = ({ name }) => {
|
|||||||
<p><b>Enabled</b>: {fleet.canbus.data_logger_enabled.toString()}</p>
|
<p><b>Enabled</b>: {fleet.canbus.data_logger_enabled.toString()}</p>
|
||||||
<p><b>Max Disk Buffer Size</b>: {fleet.canbus.max_disk_buffer_size ?? "Default"}</p>
|
<p><b>Max Disk Buffer Size</b>: {fleet.canbus.max_disk_buffer_size ?? "Default"}</p>
|
||||||
<p><b>Filters</b>: {fleet.canbus.filters ? fleet.canbus.filters.length : 0}</p>
|
<p><b>Filters</b>: {fleet.canbus.filters ? fleet.canbus.filters.length : 0}</p>
|
||||||
|
<p><b>DTC Enabled</b>: {(fleet.canbus.dtc_enabled || false).toString()}</p>
|
||||||
|
</Grid>
|
||||||
|
)}
|
||||||
|
{showDebugMask && (
|
||||||
|
<Grid item md={12} className={classes.textCenterAlign}>
|
||||||
|
<p>
|
||||||
|
<b>Debug Mask</b>: {fleet.debug_mask}
|
||||||
|
</p>
|
||||||
</Grid>
|
</Grid>
|
||||||
)}
|
)}
|
||||||
<Grid item md={12} className={classes.textCenterAlign}>
|
<Grid item md={12} className={classes.textCenterAlign}>
|
||||||
@@ -85,7 +95,7 @@ const MainForm = ({ name }) => {
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
<DeleteConfirmation message={name} open={showDeleteModal} close={()=> setShowDeleteModal(false)} deleteFunction={onDelete}/>
|
<DeleteConfirmation message={name} open={showDeleteModal} close={() => setShowDeleteModal(false)} deleteFunction={onDelete} />
|
||||||
</div >
|
</div >
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -94,6 +94,23 @@ exports[`DetailsTab Render 1`] = `
|
|||||||
:
|
:
|
||||||
3
|
3
|
||||||
</p>
|
</p>
|
||||||
|
<p>
|
||||||
|
<b>
|
||||||
|
DTC Enabled
|
||||||
|
</b>
|
||||||
|
:
|
||||||
|
false
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="MuiGrid-root makeStyles-textCenterAlign-0 MuiGrid-item MuiGrid-grid-md-12"
|
||||||
|
>
|
||||||
|
<p>
|
||||||
|
<b>
|
||||||
|
Debug Mask
|
||||||
|
</b>
|
||||||
|
:
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="MuiGrid-root makeStyles-textCenterAlign-0 MuiGrid-item MuiGrid-grid-md-12"
|
class="MuiGrid-root makeStyles-textCenterAlign-0 MuiGrid-item MuiGrid-grid-md-12"
|
||||||
|
|||||||
@@ -182,6 +182,23 @@ exports[`FleetStatus Render 1`] = `
|
|||||||
:
|
:
|
||||||
3
|
3
|
||||||
</p>
|
</p>
|
||||||
|
<p>
|
||||||
|
<b>
|
||||||
|
DTC Enabled
|
||||||
|
</b>
|
||||||
|
:
|
||||||
|
false
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="MuiGrid-root makeStyles-textCenterAlign-0 MuiGrid-item MuiGrid-grid-md-12"
|
||||||
|
>
|
||||||
|
<p>
|
||||||
|
<b>
|
||||||
|
Debug Mask
|
||||||
|
</b>
|
||||||
|
:
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="MuiGrid-root makeStyles-textCenterAlign-0 MuiGrid-item MuiGrid-grid-md-12"
|
class="MuiGrid-root makeStyles-textCenterAlign-0 MuiGrid-item MuiGrid-grid-md-12"
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
import React, {useEffect, useState} from "react";
|
import React, { useEffect } from "react";
|
||||||
import {Link} from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import {Grid,} from "@material-ui/core";
|
import { Grid, } from "@material-ui/core";
|
||||||
import AddCircleIcon from "@material-ui/icons/AddCircle";
|
import AddCircleIcon from "@material-ui/icons/AddCircle";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
|
|
||||||
import {useUserContext} from "../../Contexts/UserContext"
|
import { useUserContext } from "../../Contexts/UserContext"
|
||||||
import {useStatusContext} from "../../Contexts/StatusContext";
|
import { useStatusContext } from "../../Contexts/StatusContext";
|
||||||
import {FleetProvider} from "../../Contexts/FleetContext"
|
import { FleetProvider } from "../../Contexts/FleetContext"
|
||||||
|
import { useLocalStorage } from "../../useLocalStorage";
|
||||||
import useStyles from "../../useStyles";
|
import useStyles from "../../useStyles";
|
||||||
import SearchField from "../../Controls/SearchField";
|
import SearchField from "../../Controls/SearchField";
|
||||||
import FleetSelectionTable from "../../Controls/FleetSelectionTable";
|
import FleetSelectionTable from "../../Controls/FleetSelectionTable";
|
||||||
@@ -14,9 +15,9 @@ import FleetSelectionTable from "../../Controls/FleetSelectionTable";
|
|||||||
|
|
||||||
const MainForm = () => {
|
const MainForm = () => {
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
const [search, setSearch] = useState("");
|
const [search, setSearch] = useLocalStorage("FLEET_SEARCH", "");
|
||||||
const {setSitePath, setTitle} = useStatusContext();
|
const { setSitePath, setTitle } = useStatusContext();
|
||||||
const {token: {idToken: {jwtToken: token}}} = useUserContext();
|
const { token: { idToken: { jwtToken: token } } } = useUserContext();
|
||||||
|
|
||||||
const handleSearch = (query) => {
|
const handleSearch = (query) => {
|
||||||
setSearch(query);
|
setSearch(query);
|
||||||
@@ -33,18 +34,18 @@ const MainForm = () => {
|
|||||||
<Grid container className={classes.root} spacing={2}>
|
<Grid container className={classes.root} spacing={2}>
|
||||||
<Grid item md={4} className={classes.textJustifyAlign}>
|
<Grid item md={4} className={classes.textJustifyAlign}>
|
||||||
<Link to={"/fleet-add"}>
|
<Link to={"/fleet-add"}>
|
||||||
<AddCircleIcon fontSize="large"/>
|
<AddCircleIcon fontSize="large" />
|
||||||
</Link>
|
</Link>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item md={4} className={classes.textCenterAlign}>
|
<Grid item md={4} className={classes.textCenterAlign}>
|
||||||
<SearchField classes={classes} onSearch={handleSearch}/>
|
<SearchField classes={classes} onSearch={handleSearch} savedSearchValue={search} />
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item md={4} className={classes.textRightAlign}></Grid>
|
<Grid item md={4} className={classes.textRightAlign}></Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
<FleetSelectionTable
|
<FleetSelectionTable
|
||||||
token={token}
|
token={token}
|
||||||
classes={classes}
|
classes={classes}
|
||||||
search={{search}}
|
search={{ search }}
|
||||||
multiSelect={false}
|
multiSelect={false}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -565,6 +565,81 @@ exports[`FleetUpdate Render 1`] = `
|
|||||||
</fieldset>
|
</fieldset>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<label
|
||||||
|
class="MuiFormControlLabel-root"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
aria-disabled="false"
|
||||||
|
class="MuiButtonBase-root MuiIconButton-root PrivateSwitchBase-root-0 MuiCheckbox-root MuiCheckbox-colorSecondary PrivateSwitchBase-checked-0 Mui-checked MuiIconButton-colorSecondary"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="MuiIconButton-label"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
checked=""
|
||||||
|
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 3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.11 0 2-.9 2-2V5c0-1.1-.89-2-2-2zm-9 14l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="MuiTouchRipple-root"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="MuiTypography-root MuiFormControlLabel-label MuiTypography-body1"
|
||||||
|
>
|
||||||
|
DTC Enabled
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<div
|
||||||
|
class="MuiFormControl-root MuiTextField-root MuiFormControl-marginNormal MuiFormControl-fullWidth"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-shrink MuiInputLabel-outlined"
|
||||||
|
data-shrink="true"
|
||||||
|
for="debug_mask"
|
||||||
|
id="debug_mask-label"
|
||||||
|
>
|
||||||
|
Debug Mask
|
||||||
|
</label>
|
||||||
|
<div
|
||||||
|
class="MuiInputBase-root MuiOutlinedInput-root MuiInputBase-fullWidth MuiInputBase-formControl"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
aria-invalid="false"
|
||||||
|
class="MuiInputBase-input MuiOutlinedInput-input"
|
||||||
|
id="debug_mask"
|
||||||
|
maxlength="255"
|
||||||
|
name="debug_mask"
|
||||||
|
type="text"
|
||||||
|
value=""
|
||||||
|
/>
|
||||||
|
<fieldset
|
||||||
|
aria-hidden="true"
|
||||||
|
class="PrivateNotchedOutline-root-0 MuiOutlinedInput-notchedOutline"
|
||||||
|
>
|
||||||
|
<legend
|
||||||
|
class="PrivateNotchedOutline-legendLabelled-0 PrivateNotchedOutline-legendNotched-0"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
Debug Mask
|
||||||
|
</span>
|
||||||
|
</legend>
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<button
|
<button
|
||||||
class="MuiButtonBase-root MuiButton-root MuiButton-contained makeStyles-submit-0 MuiButton-containedPrimary MuiButton-fullWidth"
|
class="MuiButtonBase-root MuiButton-root MuiButton-contained makeStyles-submit-0 MuiButton-containedPrimary MuiButton-fullWidth"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useRef, useState } from "react";
|
||||||
import { Redirect } from "react-router";
|
import { Redirect } from "react-router";
|
||||||
import { useLocation } from "react-router-dom";
|
import { useLocation } from "react-router-dom";
|
||||||
import {
|
import {
|
||||||
@@ -38,6 +38,10 @@ const MainForm = () => {
|
|||||||
const [dataLoggerEnabled, setDataLoggerEnabled] = useState(false);
|
const [dataLoggerEnabled, setDataLoggerEnabled] = useState(false);
|
||||||
const [maxMemBufferSize, setMaxMemBufferSize] = useState(0);
|
const [maxMemBufferSize, setMaxMemBufferSize] = useState(0);
|
||||||
const [maxDiskBufferSize, setMaxDiskBufferSize] = useState(0);
|
const [maxDiskBufferSize, setMaxDiskBufferSize] = useState(0);
|
||||||
|
const [dtcEnabled, setDTCEnabled] = useState(true);
|
||||||
|
const debugMaskEl = useRef(null);
|
||||||
|
|
||||||
|
const showDebugMask = (process.env.REACT_APP_ENABLE_DEBUGMASK === "1");
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setTitle("Update Fleet");
|
setTitle("Update Fleet");
|
||||||
@@ -78,7 +82,13 @@ const MainForm = () => {
|
|||||||
setMaxDiskBufferSize(
|
setMaxDiskBufferSize(
|
||||||
fleet.canbus.max_disk_buffer_size ?? maxDiskBufferSize
|
fleet.canbus.max_disk_buffer_size ?? maxDiskBufferSize
|
||||||
);
|
);
|
||||||
|
setDTCEnabled(fleet.canbus.dtc_enabled ?? dtcEnabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (showDebugMask) {
|
||||||
|
debugMaskEl.current.value = fleet.debug_mask ?? ""
|
||||||
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [fleet]);
|
}, [fleet]);
|
||||||
|
|
||||||
@@ -98,6 +108,10 @@ const MainForm = () => {
|
|||||||
setDataLoggerEnabled(event.target.checked);
|
setDataLoggerEnabled(event.target.checked);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onDtcEnabledChange = (event) => {
|
||||||
|
setDTCEnabled(event.target.checked);
|
||||||
|
}
|
||||||
|
|
||||||
const onMaxMemBufferSizeChange = (event) => {
|
const onMaxMemBufferSizeChange = (event) => {
|
||||||
setMaxMemBufferSize(event.target.value);
|
setMaxMemBufferSize(event.target.value);
|
||||||
};
|
};
|
||||||
@@ -116,11 +130,10 @@ const MainForm = () => {
|
|||||||
enabled: canbusEnabled,
|
enabled: canbusEnabled,
|
||||||
data_logger_enabled: canbusEnabled ? dataLoggerEnabled : false,
|
data_logger_enabled: canbusEnabled ? dataLoggerEnabled : false,
|
||||||
max_mem_buffer_size: canbusEnabled ? parseInt(maxMemBufferSize) : 0,
|
max_mem_buffer_size: canbusEnabled ? parseInt(maxMemBufferSize) : 0,
|
||||||
max_disk_buffer_size:
|
max_disk_buffer_size: canbusEnabled && dataLoggerEnabled ? parseInt(maxDiskBufferSize) : 0,
|
||||||
canbusEnabled && dataLoggerEnabled
|
dtc_enabled: dtcEnabled
|
||||||
? parseInt(maxDiskBufferSize)
|
|
||||||
: 0,
|
|
||||||
},
|
},
|
||||||
|
debug_mask: debugMaskEl.current?.value
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = await updateFleet(oldName, formData, token);
|
const result = await updateFleet(oldName, formData, token);
|
||||||
@@ -226,6 +239,30 @@ const MainForm = () => {
|
|||||||
required
|
required
|
||||||
fullWidth
|
fullWidth
|
||||||
/>
|
/>
|
||||||
|
<FormControlLabel control={
|
||||||
|
<Checkbox
|
||||||
|
checked={dtcEnabled}
|
||||||
|
onChange={onDtcEnabledChange}
|
||||||
|
/>
|
||||||
|
} label="DTC Enabled" />
|
||||||
|
{showDebugMask && (
|
||||||
|
<TextField
|
||||||
|
id="debug_mask"
|
||||||
|
name="debug_mask"
|
||||||
|
label="Debug Mask"
|
||||||
|
InputLabelProps={{
|
||||||
|
shrink: true
|
||||||
|
}}
|
||||||
|
defaultValue=""
|
||||||
|
variant="outlined"
|
||||||
|
margin="normal"
|
||||||
|
inputProps={{
|
||||||
|
maxLength: "255",
|
||||||
|
}}
|
||||||
|
fullWidth
|
||||||
|
inputRef={debugMaskEl}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={busy}
|
disabled={busy}
|
||||||
|
|||||||
@@ -90,8 +90,8 @@ const MainForm = () => {
|
|||||||
const [pageIndex, setPageIndex] = useState(0);
|
const [pageIndex, setPageIndex] = useState(0);
|
||||||
const [orderBy, setOrderBy] = useState("id");
|
const [orderBy, setOrderBy] = useState("id");
|
||||||
const [order, setOrder] = useState("asc");
|
const [order, setOrder] = useState("asc");
|
||||||
const [search, setSearch] = useState("");
|
const [search, setSearch] = useLocalStorage("DEPLOYMENT_SEARCH", "");
|
||||||
const [active, setActive] = useState(true);
|
const [active, setActive] = useLocalStorage("DEPLOYMENT_ACTIVE", "true");
|
||||||
|
|
||||||
const [showDeleteModal, setShowDeleteModal] = useState(false);
|
const [showDeleteModal, setShowDeleteModal] = useState(false);
|
||||||
const [deleteId, setDeleteId] = useState("");
|
const [deleteId, setDeleteId] = useState("");
|
||||||
@@ -130,6 +130,7 @@ const MainForm = () => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
(async () => {
|
(async () => {
|
||||||
try {
|
try {
|
||||||
|
handleActiveChange(null, active);
|
||||||
await getManifests(
|
await getManifests(
|
||||||
{
|
{
|
||||||
limit: pageSize,
|
limit: pageSize,
|
||||||
@@ -164,7 +165,7 @@ const MainForm = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleActiveChange = (event, newAlignment) => {
|
const handleActiveChange = (event, newAlignment) => {
|
||||||
if (newAlignment !== null){
|
if (newAlignment !== null) {
|
||||||
setActive(newAlignment)
|
setActive(newAlignment)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -246,7 +247,7 @@ const MainForm = () => {
|
|||||||
<Grid container className={classes.root} spacing={2}>
|
<Grid container className={classes.root} spacing={2}>
|
||||||
<Grid item md={4} className={classes.textJustifyAlign}></Grid>
|
<Grid item md={4} className={classes.textJustifyAlign}></Grid>
|
||||||
<Grid item md={4} className={classes.textCenterAlign}>
|
<Grid item md={4} className={classes.textCenterAlign}>
|
||||||
<SearchField classes={classes} onSearch={handleSearch} />
|
<SearchField classes={classes} onSearch={handleSearch} savedSearchValue={search} />
|
||||||
<RoleWrap
|
<RoleWrap
|
||||||
groups={groups}
|
groups={groups}
|
||||||
providers={providers}
|
providers={providers}
|
||||||
@@ -258,8 +259,8 @@ const MainForm = () => {
|
|||||||
aria-label="Active"
|
aria-label="Active"
|
||||||
onChange={handleActiveChange}
|
onChange={handleActiveChange}
|
||||||
>
|
>
|
||||||
<ToggleButton value={true}>Active</ToggleButton>
|
<ToggleButton value={"true"}>Active</ToggleButton>
|
||||||
<ToggleButton value={false}>Archived</ToggleButton>
|
<ToggleButton value={"false"}>Archived</ToggleButton>
|
||||||
</ToggleButtonGroup>
|
</ToggleButtonGroup>
|
||||||
</RoleWrap>
|
</RoleWrap>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|||||||
5
src/components/TransformModal/__mocks__/index.jsx
Normal file
5
src/components/TransformModal/__mocks__/index.jsx
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
const TransformModalMock = jest.fn().mockImplementation(() => {
|
||||||
|
return <div data-testid="transform-modal" />
|
||||||
|
});
|
||||||
|
|
||||||
|
export default TransformModalMock;
|
||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
FormGroup,
|
FormGroup,
|
||||||
FormControlLabel,
|
FormControlLabel,
|
||||||
} from '@material-ui/core';
|
} from '@material-ui/core';
|
||||||
|
import TextInputList from "../Controls/TextInputList";
|
||||||
|
|
||||||
const TransformModal = ({
|
const TransformModal = ({
|
||||||
open,
|
open,
|
||||||
@@ -25,16 +26,24 @@ const TransformModal = ({
|
|||||||
submit();
|
submit();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleChange = (key) => {
|
const handleChange = (key, value) => {
|
||||||
setData((data) => {
|
setData((data) => {
|
||||||
const {[key]: toChange, ...rest} = data;
|
const {[key]: toChange, ...rest} = data;
|
||||||
|
switch (data[key].type) {
|
||||||
|
case "boolean":
|
||||||
toChange.value = !toChange.value;
|
toChange.value = !toChange.value;
|
||||||
|
break;
|
||||||
|
case "list.string":
|
||||||
|
toChange.value = value;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
[key]: toChange,
|
[key]: toChange,
|
||||||
...rest
|
...rest
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog
|
<Dialog
|
||||||
@@ -64,6 +73,14 @@ const TransformModal = ({
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
case "list.string":
|
||||||
|
return (
|
||||||
|
<TextInputList
|
||||||
|
key={key}
|
||||||
|
label={value.label}
|
||||||
|
onChange={(list) => handleChange(key, list)}
|
||||||
|
/>
|
||||||
|
)
|
||||||
default:
|
default:
|
||||||
return <></>;
|
return <></>;
|
||||||
}
|
}
|
||||||
@@ -72,6 +89,7 @@ const TransformModal = ({
|
|||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button
|
<Button
|
||||||
|
label="Test"
|
||||||
onClick={close}
|
onClick={close}
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
|
|||||||
@@ -267,7 +267,12 @@ const useStyles = makeStyles((theme) => ({
|
|||||||
textDecoration: "inherit",
|
textDecoration: "inherit",
|
||||||
color: "inherit",
|
color: "inherit",
|
||||||
},
|
},
|
||||||
tableSize: { height: 700, width: "100%" },
|
tableSize: {
|
||||||
|
width: "100%",
|
||||||
|
},
|
||||||
|
tabContainer: {
|
||||||
|
maxWidth: "100%",
|
||||||
|
},
|
||||||
whiteBackground: { backgroundColor: "White" },
|
whiteBackground: { backgroundColor: "White" },
|
||||||
progressIcon: { width: 40, height: 40 },
|
progressIcon: { width: 40, height: 40 },
|
||||||
progressSuccess: { color: "green" },
|
progressSuccess: { color: "green" },
|
||||||
@@ -288,7 +293,7 @@ const useStyles = makeStyles((theme) => ({
|
|||||||
},
|
},
|
||||||
tableHeader: {
|
tableHeader: {
|
||||||
textDecorationStyle: "solid",
|
textDecorationStyle: "solid",
|
||||||
fontWeight:500,
|
fontWeight: 500,
|
||||||
},
|
},
|
||||||
limitWidthTableCell: {
|
limitWidthTableCell: {
|
||||||
maxWidth: "200px",
|
maxWidth: "200px",
|
||||||
|
|||||||
@@ -17,6 +17,18 @@ const vehiclesAPI = {
|
|||||||
.then(fetchRespHandler)
|
.then(fetchRespHandler)
|
||||||
.catch(errorHandler),
|
.catch(errorHandler),
|
||||||
|
|
||||||
|
addTags: async (vins, tags, token) =>
|
||||||
|
fetch(`${API_ENDPOINT}/tags`, {
|
||||||
|
method: "PUT",
|
||||||
|
headers: Object.assign(
|
||||||
|
{ "Content-Type": "application/json" },
|
||||||
|
getAuthHeaderOptions(token),
|
||||||
|
),
|
||||||
|
body: JSON.stringify({ vins, tags }),
|
||||||
|
})
|
||||||
|
.then(fetchRespHandler)
|
||||||
|
.catch(errorHandler),
|
||||||
|
|
||||||
deleteVehicle: async (vin, token) =>
|
deleteVehicle: async (vin, token) =>
|
||||||
fetch(`${API_ENDPOINT}/vehicle/${vin}`, {
|
fetch(`${API_ENDPOINT}/vehicle/${vin}`, {
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
|
|||||||
Reference in New Issue
Block a user