Merge branch 'release/0.9.0'
This commit is contained in:
@@ -9,6 +9,7 @@ jest.mock("../../services/vehiclesAPI");
|
||||
jest.mock("../../services/superset");
|
||||
jest.mock("../../services/suppliersAPI");
|
||||
jest.mock("../../services/issueAPI");
|
||||
jest.mock("../TransformModal");
|
||||
|
||||
import {
|
||||
act, cleanup, render,
|
||||
|
||||
@@ -11276,6 +11276,7 @@ exports[`App Route /vehicle-status authenticated 1`] = `
|
||||
</div>
|
||||
<div
|
||||
aria-labelledby="tab-0"
|
||||
class="makeStyles-tabContainer-0"
|
||||
id="tabpanel-0"
|
||||
role="tabpanel"
|
||||
>
|
||||
@@ -11528,54 +11529,63 @@ exports[`App Route /vehicle-status authenticated 1`] = `
|
||||
</div>
|
||||
<div
|
||||
aria-labelledby="tab-1"
|
||||
class="makeStyles-tabContainer-0"
|
||||
hidden=""
|
||||
id="tabpanel-1"
|
||||
role="tabpanel"
|
||||
/>
|
||||
<div
|
||||
aria-labelledby="tab-2"
|
||||
class="makeStyles-tabContainer-0"
|
||||
hidden=""
|
||||
id="tabpanel-2"
|
||||
role="tabpanel"
|
||||
/>
|
||||
<div
|
||||
aria-labelledby="tab-3"
|
||||
class="makeStyles-tabContainer-0"
|
||||
hidden=""
|
||||
id="tabpanel-3"
|
||||
role="tabpanel"
|
||||
/>
|
||||
<div
|
||||
aria-labelledby="tab-4"
|
||||
class="makeStyles-tabContainer-0"
|
||||
hidden=""
|
||||
id="tabpanel-4"
|
||||
role="tabpanel"
|
||||
/>
|
||||
<div
|
||||
aria-labelledby="tab-5"
|
||||
class="makeStyles-tabContainer-0"
|
||||
hidden=""
|
||||
id="tabpanel-5"
|
||||
role="tabpanel"
|
||||
/>
|
||||
<div
|
||||
aria-labelledby="tab-6"
|
||||
class="makeStyles-tabContainer-0"
|
||||
hidden=""
|
||||
id="tabpanel-6"
|
||||
role="tabpanel"
|
||||
/>
|
||||
<div
|
||||
aria-labelledby="tab-7"
|
||||
class="makeStyles-tabContainer-0"
|
||||
hidden=""
|
||||
id="tabpanel-7"
|
||||
role="tabpanel"
|
||||
/>
|
||||
<div
|
||||
aria-labelledby="tab-8"
|
||||
class="makeStyles-tabContainer-0"
|
||||
hidden=""
|
||||
id="tabpanel-8"
|
||||
role="tabpanel"
|
||||
/>
|
||||
<div
|
||||
aria-labelledby="tab-9"
|
||||
class="makeStyles-tabContainer-0"
|
||||
hidden=""
|
||||
id="tabpanel-9"
|
||||
role="tabpanel"
|
||||
@@ -12652,6 +12662,13 @@ exports[`App Route /vehicles authenticated 1`] = `
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
<div
|
||||
data-testid="transform-modal"
|
||||
/>
|
||||
<div
|
||||
data-testid="transform-modal"
|
||||
/>
|
||||
<div />
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
@@ -482,6 +482,7 @@ exports[`VehicleTable Render 1`] = `
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
<div />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -14,12 +14,14 @@ import { RoleWrap } from "../../Controls/RoleWrap";
|
||||
import SearchField from "../../Controls/SearchField";
|
||||
import DropDownButton from "../../Controls/DropDownButton";
|
||||
import TransformModal from "../../TransformModal";
|
||||
import { useLocalStorage } from "../../useLocalStorage";
|
||||
import useStyles from "../../useStyles";
|
||||
import TaskRunner from "../../../utils/taskRunner";
|
||||
import GeneralConfirmation from "../../GeneralConfirmation";
|
||||
|
||||
const MainForm = () => {
|
||||
const classes = useStyles();
|
||||
const [search, setSearch] = useState("");
|
||||
const [search, setSearch] = useLocalStorage("VEHICLE_SEARCH", "");
|
||||
const [online, setOnline] = useState(false);
|
||||
const [onlineHMI, setOnlineHMI] = useState(false);
|
||||
const [selectedVins, setSelectedVins] = useState([]);
|
||||
@@ -29,8 +31,15 @@ const MainForm = () => {
|
||||
type: "boolean",
|
||||
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 {
|
||||
token: {
|
||||
@@ -64,7 +73,7 @@ const MainForm = () => {
|
||||
const handleUploadConfig = (fn) => {
|
||||
const taskRunner = new TaskRunner(5);
|
||||
const request = (vin, i) => {
|
||||
const messagePrefix = `${i+1}/${selectedVins.length} "${vin}":`;
|
||||
const messagePrefix = `${i + 1}/${selectedVins.length} "${vin}":`;
|
||||
return async () => {
|
||||
const result = await fn(vin, config.force.value, token)
|
||||
.then(() => {
|
||||
@@ -79,11 +88,44 @@ const MainForm = () => {
|
||||
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 = [
|
||||
{
|
||||
name: "Update Configs",
|
||||
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]} />
|
||||
</Grid>
|
||||
<Grid item md={4} className={classes.textCenterAlign}>
|
||||
<SearchField classes={classes} onSearch={handleSearch} />
|
||||
<SearchField classes={classes} onSearch={handleSearch} savedSearchValue={search} />
|
||||
</Grid>
|
||||
<Grid item md={2} className={clsx(classes.textJustifyAlign, classes.actionsBar)}>
|
||||
<OptionsDropdown listId="filter-menu">
|
||||
@@ -149,17 +191,33 @@ const MainForm = () => {
|
||||
onSelectAll={handleSelectAll}
|
||||
/>
|
||||
<VehicleConsumer>
|
||||
{(context) => (
|
||||
{(context) => (<>
|
||||
<TransformModal
|
||||
open={showUpdateConfigModal}
|
||||
close={() => setShowUpdateConfigModal(false)}
|
||||
open={activeModal === "updateConfig"}
|
||||
close={() => setActiveModal(null)}
|
||||
title="Update Configs"
|
||||
body={`You are updating the config for the following VINs: ${selectedVins.join(", ")}.`}
|
||||
data={config}
|
||||
setData={setConfig}
|
||||
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>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -16,7 +16,7 @@ const MainForm = ({ vin }) => {
|
||||
} = useUserContext();
|
||||
|
||||
return (
|
||||
<div className={clsx(classes.paper, classes.tableSize)}>
|
||||
<div className={clsx(classes.tableSize, classes.textCenterAlign)}>
|
||||
<Typography variant="h6" className={classes.labelInline}>
|
||||
Car ECUs
|
||||
</Typography>
|
||||
@@ -25,9 +25,9 @@ const MainForm = ({ vin }) => {
|
||||
);
|
||||
};
|
||||
|
||||
const CarUpdatesTab = ({vin}) => (
|
||||
const CarUpdatesTab = ({ vin }) => (
|
||||
<VehicleProvider>
|
||||
<MainForm vin={vin}/>
|
||||
<MainForm vin={vin} />
|
||||
</VehicleProvider>
|
||||
);
|
||||
|
||||
|
||||
@@ -55,6 +55,13 @@ exports[`DigitalTwinTab Render 1`] = `
|
||||
:
|
||||
12000 km
|
||||
</p>
|
||||
<p>
|
||||
<b>
|
||||
Voltage
|
||||
</b>
|
||||
:
|
||||
12.5 V
|
||||
</p>
|
||||
<p>
|
||||
<b>
|
||||
Max Range
|
||||
|
||||
@@ -9,7 +9,7 @@ exports[`ECUsTab Render 1`] = `
|
||||
data-testid="mocked-userprovider"
|
||||
>
|
||||
<div
|
||||
class="makeStyles-paper-0 makeStyles-tableSize-0"
|
||||
class="makeStyles-tableSize-0 makeStyles-textCenterAlign-0"
|
||||
>
|
||||
<h6
|
||||
class="MuiTypography-root makeStyles-labelInline-0 MuiTypography-h6"
|
||||
@@ -17,7 +17,7 @@ exports[`ECUsTab Render 1`] = `
|
||||
Car ECUs
|
||||
</h6>
|
||||
<div
|
||||
class="makeStyles-paper-0 makeStyles-tableSize-0"
|
||||
class="MuiTableContainer-root"
|
||||
>
|
||||
<table
|
||||
class="MuiTable-root"
|
||||
|
||||
@@ -223,6 +223,7 @@ exports[`CarStatus Render 1`] = `
|
||||
</div>
|
||||
<div
|
||||
aria-labelledby="tab-0"
|
||||
class="makeStyles-tabContainer-0"
|
||||
id="tabpanel-0"
|
||||
role="tabpanel"
|
||||
>
|
||||
@@ -418,54 +419,63 @@ exports[`CarStatus Render 1`] = `
|
||||
</div>
|
||||
<div
|
||||
aria-labelledby="tab-1"
|
||||
class="makeStyles-tabContainer-0"
|
||||
hidden=""
|
||||
id="tabpanel-1"
|
||||
role="tabpanel"
|
||||
/>
|
||||
<div
|
||||
aria-labelledby="tab-2"
|
||||
class="makeStyles-tabContainer-0"
|
||||
hidden=""
|
||||
id="tabpanel-2"
|
||||
role="tabpanel"
|
||||
/>
|
||||
<div
|
||||
aria-labelledby="tab-3"
|
||||
class="makeStyles-tabContainer-0"
|
||||
hidden=""
|
||||
id="tabpanel-3"
|
||||
role="tabpanel"
|
||||
/>
|
||||
<div
|
||||
aria-labelledby="tab-4"
|
||||
class="makeStyles-tabContainer-0"
|
||||
hidden=""
|
||||
id="tabpanel-4"
|
||||
role="tabpanel"
|
||||
/>
|
||||
<div
|
||||
aria-labelledby="tab-5"
|
||||
class="makeStyles-tabContainer-0"
|
||||
hidden=""
|
||||
id="tabpanel-5"
|
||||
role="tabpanel"
|
||||
/>
|
||||
<div
|
||||
aria-labelledby="tab-6"
|
||||
class="makeStyles-tabContainer-0"
|
||||
hidden=""
|
||||
id="tabpanel-6"
|
||||
role="tabpanel"
|
||||
/>
|
||||
<div
|
||||
aria-labelledby="tab-7"
|
||||
class="makeStyles-tabContainer-0"
|
||||
hidden=""
|
||||
id="tabpanel-7"
|
||||
role="tabpanel"
|
||||
/>
|
||||
<div
|
||||
aria-labelledby="tab-8"
|
||||
class="makeStyles-tabContainer-0"
|
||||
hidden=""
|
||||
id="tabpanel-8"
|
||||
role="tabpanel"
|
||||
/>
|
||||
<div
|
||||
aria-labelledby="tab-9"
|
||||
class="makeStyles-tabContainer-0"
|
||||
hidden=""
|
||||
id="tabpanel-9"
|
||||
role="tabpanel"
|
||||
|
||||
@@ -147,8 +147,13 @@ const CarStatus = () => {
|
||||
</Tabs>
|
||||
</Box>
|
||||
{tabs.map((item, index) => (
|
||||
<TabPanel key={index} value={tabIndex} index={index}>
|
||||
<item.component vin={vin}/>
|
||||
<TabPanel
|
||||
key={index}
|
||||
value={tabIndex}
|
||||
index={index}
|
||||
className={classes.tabContainer}
|
||||
>
|
||||
<item.component vin={vin} />
|
||||
</TabPanel>
|
||||
))}
|
||||
</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) => {
|
||||
try {
|
||||
setBusy(true);
|
||||
@@ -301,6 +317,7 @@ export const VehicleProvider = ({ children }) => {
|
||||
getFleets,
|
||||
getVersionLog,
|
||||
uploadConfig,
|
||||
addTags,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -39,6 +39,7 @@ let vehicleState = {
|
||||
battery: {
|
||||
total_mileage_odometer: 12000,
|
||||
percent: 95,
|
||||
battery_voltage: 12.5,
|
||||
},
|
||||
max_range: {
|
||||
max_miles: 577,
|
||||
|
||||
@@ -2,12 +2,12 @@ import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableContainer,
|
||||
TableFooter,
|
||||
TablePagination,
|
||||
TableRow,
|
||||
Tooltip
|
||||
} from "@material-ui/core";
|
||||
import clsx from "clsx";
|
||||
import React, { useEffect, useState } from "react";
|
||||
|
||||
import { logger } from "../../../services/monitoring";
|
||||
@@ -122,7 +122,7 @@ const CarECUsTable = ({ vin, token, classes }) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={clsx(classes.paper, classes.tableSize)}>
|
||||
<TableContainer>
|
||||
<Table>
|
||||
<TableHeaderSortable
|
||||
classes={classes}
|
||||
@@ -166,7 +166,7 @@ const CarECUsTable = ({ vin, token, classes }) => {
|
||||
</TableRow>
|
||||
</TableFooter>
|
||||
</Table>
|
||||
</div>
|
||||
</TableContainer>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState } from "react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import {
|
||||
FormControl,
|
||||
IconButton,
|
||||
@@ -10,7 +10,7 @@ import SearchIcon from "@material-ui/icons/Search";
|
||||
import clsx from "clsx";
|
||||
|
||||
const SearchField = (props) => {
|
||||
const { classes, onSearch } = props;
|
||||
const { classes, onSearch, savedSearchValue } = props;
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
const handleChange = (e) => {
|
||||
setSearchTerm(e.target.value);
|
||||
@@ -29,6 +29,12 @@ const SearchField = (props) => {
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (savedSearchValue) {
|
||||
setSearchTerm(savedSearchValue);
|
||||
}
|
||||
}, [savedSearchValue]);
|
||||
|
||||
return (
|
||||
<FormControl className={clsx(classes.margin, classes.fullWidth)}>
|
||||
<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 UNLOCKED = "Unlocked";
|
||||
|
||||
const appendUnits = (value, units) => {
|
||||
if (value || value === 0) return `${value}${units}`;
|
||||
return UNKNOWN;
|
||||
}
|
||||
|
||||
const keyValueTemplate = (key, value) => (
|
||||
<p key={key}>
|
||||
<b>{key}</b>: {value}
|
||||
@@ -37,16 +42,17 @@ const DigitalTwin = (props) => {
|
||||
{(battery || max_range) && (
|
||||
<div className={classes.popupSection}>
|
||||
<h3>Battery</h3>
|
||||
{keyValueTemplate("Percentage", `${battery?.percent || 0}%`)}
|
||||
{keyValueTemplate("Total Mileage", `${battery?.total_mileage_odometer} km` || UNKNOWN)}
|
||||
{keyValueTemplate("Max Range", `${max_range?.max_miles} km` || UNKNOWN)}
|
||||
{keyValueTemplate("Percentage", appendUnits(battery?.percent || 0, "%"))}
|
||||
{keyValueTemplate("Total Mileage", appendUnits(battery?.total_mileage_odometer, " km"))}
|
||||
{keyValueTemplate("Voltage", appendUnits(battery?.battery_voltage, " V"))}
|
||||
{keyValueTemplate("Max Range", appendUnits(max_range?.max_miles, " km"))}
|
||||
</div>
|
||||
)}
|
||||
{(vcu0x260 || charging_metrics) && (
|
||||
<div className={classes.popupSection}>
|
||||
<h3>Charging</h3>
|
||||
{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>
|
||||
)}
|
||||
{doors && (
|
||||
@@ -95,9 +101,9 @@ const DigitalTwin = (props) => {
|
||||
return keyValueTemplate(value[0], "Invalid")
|
||||
}
|
||||
if (value[0] === "altitude") {
|
||||
return keyValueTemplate(value[0], `${value[1]} m`);
|
||||
return keyValueTemplate(value[0], appendUnits(value[1], " m"));
|
||||
} else {
|
||||
return keyValueTemplate(value[0], `${value[1]}°`);
|
||||
return keyValueTemplate(value[0], appendUnits(value[1], "°"));
|
||||
}
|
||||
})}
|
||||
</div>
|
||||
@@ -124,7 +130,7 @@ const DigitalTwin = (props) => {
|
||||
)}
|
||||
{vehicle_speed && (
|
||||
<div className={classes.popupSection}>
|
||||
{keyValueTemplate("Vehicle Speed", `${vehicle_speed.speed} km/h`)}
|
||||
{keyValueTemplate("Vehicle Speed", appendUnits(vehicle_speed?.speed, " km/h"))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -86,6 +86,23 @@ exports[`FleetDetailsTab Render 1`] = `
|
||||
:
|
||||
3
|
||||
</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
|
||||
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 { token: { idToken: { jwtToken: token } } } = useUserContext();
|
||||
|
||||
const showDebugMask = (process.env.REACT_APP_ENABLE_DEBUGMASK === "1");
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
try {
|
||||
@@ -70,6 +72,14 @@ const MainForm = ({ name }) => {
|
||||
<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>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 item md={12} className={classes.textCenterAlign}>
|
||||
@@ -85,7 +95,7 @@ const MainForm = ({ name }) => {
|
||||
</Tooltip>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<DeleteConfirmation message={name} open={showDeleteModal} close={()=> setShowDeleteModal(false)} deleteFunction={onDelete}/>
|
||||
<DeleteConfirmation message={name} open={showDeleteModal} close={() => setShowDeleteModal(false)} deleteFunction={onDelete} />
|
||||
</div >
|
||||
);
|
||||
};
|
||||
|
||||
@@ -94,6 +94,23 @@ exports[`DetailsTab Render 1`] = `
|
||||
:
|
||||
3
|
||||
</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
|
||||
class="MuiGrid-root makeStyles-textCenterAlign-0 MuiGrid-item MuiGrid-grid-md-12"
|
||||
|
||||
@@ -182,6 +182,23 @@ exports[`FleetStatus Render 1`] = `
|
||||
:
|
||||
3
|
||||
</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
|
||||
class="MuiGrid-root makeStyles-textCenterAlign-0 MuiGrid-item MuiGrid-grid-md-12"
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import React, {useEffect, useState} from "react";
|
||||
import {Link} from 'react-router-dom';
|
||||
import {Grid,} from "@material-ui/core";
|
||||
import React, { useEffect } from "react";
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Grid, } from "@material-ui/core";
|
||||
import AddCircleIcon from "@material-ui/icons/AddCircle";
|
||||
import clsx from "clsx";
|
||||
|
||||
import {useUserContext} from "../../Contexts/UserContext"
|
||||
import {useStatusContext} from "../../Contexts/StatusContext";
|
||||
import {FleetProvider} from "../../Contexts/FleetContext"
|
||||
import { useUserContext } from "../../Contexts/UserContext"
|
||||
import { useStatusContext } from "../../Contexts/StatusContext";
|
||||
import { FleetProvider } from "../../Contexts/FleetContext"
|
||||
import { useLocalStorage } from "../../useLocalStorage";
|
||||
import useStyles from "../../useStyles";
|
||||
import SearchField from "../../Controls/SearchField";
|
||||
import FleetSelectionTable from "../../Controls/FleetSelectionTable";
|
||||
@@ -14,9 +15,9 @@ import FleetSelectionTable from "../../Controls/FleetSelectionTable";
|
||||
|
||||
const MainForm = () => {
|
||||
const classes = useStyles();
|
||||
const [search, setSearch] = useState("");
|
||||
const {setSitePath, setTitle} = useStatusContext();
|
||||
const {token: {idToken: {jwtToken: token}}} = useUserContext();
|
||||
const [search, setSearch] = useLocalStorage("FLEET_SEARCH", "");
|
||||
const { setSitePath, setTitle } = useStatusContext();
|
||||
const { token: { idToken: { jwtToken: token } } } = useUserContext();
|
||||
|
||||
const handleSearch = (query) => {
|
||||
setSearch(query);
|
||||
@@ -33,18 +34,18 @@ const MainForm = () => {
|
||||
<Grid container className={classes.root} spacing={2}>
|
||||
<Grid item md={4} className={classes.textJustifyAlign}>
|
||||
<Link to={"/fleet-add"}>
|
||||
<AddCircleIcon fontSize="large"/>
|
||||
<AddCircleIcon fontSize="large" />
|
||||
</Link>
|
||||
</Grid>
|
||||
<Grid item md={4} className={classes.textCenterAlign}>
|
||||
<SearchField classes={classes} onSearch={handleSearch}/>
|
||||
<SearchField classes={classes} onSearch={handleSearch} savedSearchValue={search} />
|
||||
</Grid>
|
||||
<Grid item md={4} className={classes.textRightAlign}></Grid>
|
||||
</Grid>
|
||||
<FleetSelectionTable
|
||||
token={token}
|
||||
classes={classes}
|
||||
search={{search}}
|
||||
search={{ search }}
|
||||
multiSelect={false}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -565,6 +565,81 @@ exports[`FleetUpdate Render 1`] = `
|
||||
</fieldset>
|
||||
</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
|
||||
class="MuiButtonBase-root MuiButton-root MuiButton-contained makeStyles-submit-0 MuiButton-containedPrimary MuiButton-fullWidth"
|
||||
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 { useLocation } from "react-router-dom";
|
||||
import {
|
||||
@@ -38,6 +38,10 @@ const MainForm = () => {
|
||||
const [dataLoggerEnabled, setDataLoggerEnabled] = useState(false);
|
||||
const [maxMemBufferSize, setMaxMemBufferSize] = 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(() => {
|
||||
setTitle("Update Fleet");
|
||||
@@ -78,7 +82,13 @@ const MainForm = () => {
|
||||
setMaxDiskBufferSize(
|
||||
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
|
||||
}, [fleet]);
|
||||
|
||||
@@ -98,6 +108,10 @@ const MainForm = () => {
|
||||
setDataLoggerEnabled(event.target.checked);
|
||||
};
|
||||
|
||||
const onDtcEnabledChange = (event) => {
|
||||
setDTCEnabled(event.target.checked);
|
||||
}
|
||||
|
||||
const onMaxMemBufferSizeChange = (event) => {
|
||||
setMaxMemBufferSize(event.target.value);
|
||||
};
|
||||
@@ -116,11 +130,10 @@ const MainForm = () => {
|
||||
enabled: canbusEnabled,
|
||||
data_logger_enabled: canbusEnabled ? dataLoggerEnabled : false,
|
||||
max_mem_buffer_size: canbusEnabled ? parseInt(maxMemBufferSize) : 0,
|
||||
max_disk_buffer_size:
|
||||
canbusEnabled && dataLoggerEnabled
|
||||
? parseInt(maxDiskBufferSize)
|
||||
: 0,
|
||||
max_disk_buffer_size: canbusEnabled && dataLoggerEnabled ? parseInt(maxDiskBufferSize) : 0,
|
||||
dtc_enabled: dtcEnabled
|
||||
},
|
||||
debug_mask: debugMaskEl.current?.value
|
||||
};
|
||||
|
||||
const result = await updateFleet(oldName, formData, token);
|
||||
@@ -230,6 +243,30 @@ const MainForm = () => {
|
||||
required
|
||||
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
|
||||
type="submit"
|
||||
disabled={busy}
|
||||
|
||||
@@ -90,8 +90,8 @@ const MainForm = () => {
|
||||
const [pageIndex, setPageIndex] = useState(0);
|
||||
const [orderBy, setOrderBy] = useState("id");
|
||||
const [order, setOrder] = useState("asc");
|
||||
const [search, setSearch] = useState("");
|
||||
const [active, setActive] = useState(true);
|
||||
const [search, setSearch] = useLocalStorage("DEPLOYMENT_SEARCH", "");
|
||||
const [active, setActive] = useLocalStorage("DEPLOYMENT_ACTIVE", "true");
|
||||
|
||||
const [showDeleteModal, setShowDeleteModal] = useState(false);
|
||||
const [deleteId, setDeleteId] = useState("");
|
||||
@@ -130,6 +130,7 @@ const MainForm = () => {
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
try {
|
||||
handleActiveChange(null, active);
|
||||
await getManifests(
|
||||
{
|
||||
limit: pageSize,
|
||||
@@ -164,7 +165,7 @@ const MainForm = () => {
|
||||
};
|
||||
|
||||
const handleActiveChange = (event, newAlignment) => {
|
||||
if (newAlignment !== null){
|
||||
if (newAlignment !== null) {
|
||||
setActive(newAlignment)
|
||||
}
|
||||
}
|
||||
@@ -246,7 +247,7 @@ const MainForm = () => {
|
||||
<Grid container className={classes.root} spacing={2}>
|
||||
<Grid item md={4} className={classes.textJustifyAlign}></Grid>
|
||||
<Grid item md={4} className={classes.textCenterAlign}>
|
||||
<SearchField classes={classes} onSearch={handleSearch} />
|
||||
<SearchField classes={classes} onSearch={handleSearch} savedSearchValue={search} />
|
||||
<RoleWrap
|
||||
groups={groups}
|
||||
providers={providers}
|
||||
@@ -258,8 +259,8 @@ const MainForm = () => {
|
||||
aria-label="Active"
|
||||
onChange={handleActiveChange}
|
||||
>
|
||||
<ToggleButton value={true}>Active</ToggleButton>
|
||||
<ToggleButton value={false}>Archived</ToggleButton>
|
||||
<ToggleButton value={"true"}>Active</ToggleButton>
|
||||
<ToggleButton value={"false"}>Archived</ToggleButton>
|
||||
</ToggleButtonGroup>
|
||||
</RoleWrap>
|
||||
</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,
|
||||
FormControlLabel,
|
||||
} from '@material-ui/core';
|
||||
import TextInputList from "../Controls/TextInputList";
|
||||
|
||||
const TransformModal = ({
|
||||
open,
|
||||
@@ -25,16 +26,24 @@ const TransformModal = ({
|
||||
submit();
|
||||
};
|
||||
|
||||
const handleChange = (key) => {
|
||||
const handleChange = (key, value) => {
|
||||
setData((data) => {
|
||||
const {[key]: toChange, ...rest} = data;
|
||||
toChange.value = !toChange.value;
|
||||
switch (data[key].type) {
|
||||
case "boolean":
|
||||
toChange.value = !toChange.value;
|
||||
break;
|
||||
case "list.string":
|
||||
toChange.value = value;
|
||||
break;
|
||||
default:
|
||||
}
|
||||
return {
|
||||
[key]: toChange,
|
||||
...rest
|
||||
};
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
@@ -64,6 +73,14 @@ const TransformModal = ({
|
||||
}
|
||||
/>
|
||||
)
|
||||
case "list.string":
|
||||
return (
|
||||
<TextInputList
|
||||
key={key}
|
||||
label={value.label}
|
||||
onChange={(list) => handleChange(key, list)}
|
||||
/>
|
||||
)
|
||||
default:
|
||||
return <></>;
|
||||
}
|
||||
@@ -72,6 +89,7 @@ const TransformModal = ({
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button
|
||||
label="Test"
|
||||
onClick={close}
|
||||
>
|
||||
Cancel
|
||||
|
||||
@@ -267,7 +267,12 @@ const useStyles = makeStyles((theme) => ({
|
||||
textDecoration: "inherit",
|
||||
color: "inherit",
|
||||
},
|
||||
tableSize: { height: 700, width: "100%" },
|
||||
tableSize: {
|
||||
width: "100%",
|
||||
},
|
||||
tabContainer: {
|
||||
maxWidth: "100%",
|
||||
},
|
||||
whiteBackground: { backgroundColor: "White" },
|
||||
progressIcon: { width: 40, height: 40 },
|
||||
progressSuccess: { color: "green" },
|
||||
@@ -288,7 +293,7 @@ const useStyles = makeStyles((theme) => ({
|
||||
},
|
||||
tableHeader: {
|
||||
textDecorationStyle: "solid",
|
||||
fontWeight:500,
|
||||
fontWeight: 500,
|
||||
},
|
||||
limitWidthTableCell: {
|
||||
maxWidth: "200px",
|
||||
|
||||
@@ -17,6 +17,18 @@ const vehiclesAPI = {
|
||||
.then(fetchRespHandler)
|
||||
.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) =>
|
||||
fetch(`${API_ENDPOINT}/vehicle/${vin}`, {
|
||||
method: "DELETE",
|
||||
|
||||
Reference in New Issue
Block a user