CEC-3943-display-ecu-vin-timeline (#303)

* first push

* fix test

* Fix test

* resolve comments

* fix undefined

* align items and change search field

* fix dates

* resolve comments

* fix lint

* fix test

---------

Co-authored-by: jwu-fisker <jwu@fiskerinc.com>
This commit is contained in:
das31
2023-03-27 12:05:40 -04:00
committed by GitHub
parent 234252a100
commit 8ece05a4b9
9 changed files with 420 additions and 0 deletions

View File

@@ -11229,6 +11229,24 @@ exports[`App Route /vehicle-status authenticated 1`] = `
class="MuiTouchRipple-root"
/>
</button>
<button
aria-controls="tabpanel-10"
aria-selected="false"
class="MuiButtonBase-root MuiTab-root MuiTab-textColorInherit"
id="tab-10"
role="tab"
tabindex="-1"
type="button"
>
<span
class="MuiTab-wrapper"
>
DTC Timeline
</span>
<span
class="MuiTouchRipple-root"
/>
</button>
</div>
<span
class="PrivateTabIndicator-root-0 PrivateTabIndicator-colorSecondary-0 MuiTabs-indicator"
@@ -11472,6 +11490,12 @@ exports[`App Route /vehicle-status authenticated 1`] = `
id="tabpanel-9"
role="tabpanel"
/>
<div
aria-labelledby="tab-10"
hidden=""
id="tabpanel-10"
role="tabpanel"
/>
</div>
</main>
</main>

View File

@@ -213,6 +213,24 @@ exports[`CarStatus Render 1`] = `
class="MuiTouchRipple-root"
/>
</button>
<button
aria-controls="tabpanel-10"
aria-selected="false"
class="MuiButtonBase-root MuiTab-root MuiTab-textColorInherit"
id="tab-10"
role="tab"
tabindex="-1"
type="button"
>
<span
class="MuiTab-wrapper"
>
DTC Timeline
</span>
<span
class="MuiTouchRipple-root"
/>
</button>
</div>
<span
class="PrivateTabIndicator-root-0 PrivateTabIndicator-colorSecondary-0 MuiTabs-indicator"
@@ -400,6 +418,12 @@ exports[`CarStatus Render 1`] = `
id="tabpanel-9"
role="tabpanel"
/>
<div
aria-labelledby="tab-10"
hidden=""
id="tabpanel-10"
role="tabpanel"
/>
</div>
</div>
</div>

View File

@@ -19,6 +19,7 @@ import ECUsTab from "./ECUsTab";
import FleetsTab from "./FleetsTab";
import RemoteCommandsTab from "./RemoteCommandsTab";
import TRexLogsTab from "./TRexLogsTab";
import DTCTimeline from "../../DTCTimeline/DTCTimeline";
const tabHashes = ["details", "updates", "filters"];
@@ -72,7 +73,13 @@ const TabViews = [
label: "CAN Signal Export",
component: SelfServeTab,
rolesPerProvider: Permissions.FiskerRead,
},
{
label: "DTC Timeline",
component: DTCTimeline,
rolesPerProvider: Permissions.FiskerMagnaRead,
}
];
const filterTabs = (data, groups, providers) => {

View File

@@ -0,0 +1,43 @@
import React, { useContext, useState } from "react";
import api from "../../services/DTCTimelineAPI";
const DTCTimelineContext = React.createContext();
export const DTCTimelineProvider = ({ children }) => {
const [busy, setBusy] = useState(false);
const [dtcData, setDTCData] = useState([]);
const [total, setTotal] = useState(0)
const getDTCData = async (vin, ecu, startDate, endDate, search,token) => {
try {
setBusy(true);
const result = await api.getDTCData(vin, ecu, startDate, endDate, search,token);
if (result.error) {
throw new Error(`Get DTC data error. ${result.message}`);
}
setDTCData(result.data ?? []);
if (result.total){
setTotal(result.total)
}
return result;
} finally {
setBusy(false);
}
};
return (
<DTCTimelineContext.Provider
value={{
busy,
total,
dtcData,
getDTCData
}}
>
{children}
</DTCTimelineContext.Provider>
);
};
export const useDTCTimelineContext = () => useContext(DTCTimelineContext);

View File

@@ -0,0 +1,228 @@
import DateFnsUtils from '@date-io/date-fns';
import { Button, CircularProgress, Grid, TableFooter, TablePagination, TableCell, Table, TableRow, TableBody} from "@material-ui/core";
import { KeyboardDatePicker, MuiPickersUtilsProvider } from '@material-ui/pickers';
import React, { useEffect, useState } from "react";
import { logger } from "../../../services/monitoring";
import { DTCTimelineProvider, useDTCTimelineContext } from '../../Contexts/DTCTimelineContext';
import { useStatusContext } from "../../Contexts/StatusContext";
import { useUserContext } from "../../Contexts/UserContext";
import { useLocalStorage } from "../../useLocalStorage";
import clsx from "clsx";
import TableHeaderSortable from "../../Table/HeaderSortable";
import SearchField from '../../Controls/SearchField';
import useStyles from "../../useStyles";
const MainForm = ({ vin }) => {
const classes = useStyles();
const PAGE_SIZE = "DTC_TABLE_PAGE_SIZE";
const [pageSize, setPageSize] = useLocalStorage(PAGE_SIZE, 10);
const [pageIndex, setPageIndex] = useState(0);
const [orderBy, setOrderBy] = useState("epoch_usec");
const [order, setOrder] = useState("desc");
const { dtcData, getDTCData, total=0 } = useDTCTimelineContext();
const [selectedStartDate, setSelectedStartDate] = useState(new Date(Date.now() - 24 * 60 * 60 * 1000));
const [selectedEndDate, setSelectedEndDate] = useState(new Date());
const [selectedECU, setSelectedECU] = useState("");
const [loading, setLoading] = useState(false);
const { setMessage } = useStatusContext();
const tableColumns = [
{
id: "id",
label: "Id",
},
{
id: "vin",
label: "VIN",
},
{
id: "ecu",
label: "ECU",
},
{
id: "dtc",
label: "DTC",
},
{
id: "epoch_usec",
label: "Date",
},
];
const handleSort = (_event, property) => {
if (property === orderBy) {
if (order === "asc") {
setOrder("desc");
} else {
setOrder("asc");
}
} else {
setOrderBy(property);
setOrder("desc");
}
};
const handleChangePageIndex = (_event, newIndex) => {
setPageIndex(newIndex);
};
const handleChangePageSize = (event) => {
setPageSize(parseInt(event.target.value, 10));
setPageIndex(0);
};
const {
token: {
idToken: { jwtToken: token },
},
} = useUserContext();
const fetchDTCData = async () => {
setLoading(true);
try {
let start_date = new Date(selectedStartDate);
start_date.setHours(0, 0, 0, 0);
start_date = start_date.toISOString();
let end_date = new Date(selectedEndDate);
end_date.setHours(23, 59, 59, 999);
end_date = end_date.toISOString();
const search = {
limit: pageSize,
offset: pageSize * pageIndex,
order: `${orderBy} ${order}`,
}
await getDTCData(vin, selectedECU, start_date, end_date, search, token);
// setDTCData(data);
} catch (e) {
setMessage(e.message);
logger.warn(e.stack);
}
setLoading(false);
};
function formatDate(microseconds) {
const date = new Date(microseconds / 1000);
return date.toLocaleString();
}
useEffect(() => {
fetchDTCData();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [vin, selectedECU, selectedStartDate, selectedEndDate, pageIndex, pageSize, order, orderBy]);
return (
<div className={classes.paper}>
<Grid container spacing={3} alignItems="flex-end">
<Grid item xs={6} md={3}>
<div style={{ marginBottom: '8px' }}>
<SearchField
classes={classes}
onSearch={(searchValue) => {
setSelectedECU(searchValue);
}}
/>
</div>
</Grid>
<Grid item xs={12} sm={6} md={3}>
<MuiPickersUtilsProvider utils={DateFnsUtils}>
<KeyboardDatePicker
disableToolbar
variant="inline"
format="MM/dd/yyyy"
margin="normal"
label="Start Date"
value={selectedStartDate}
onChange={setSelectedStartDate}
KeyboardButtonProps={{
'aria-label': 'change date',
}}
fullWidth
/>
</MuiPickersUtilsProvider>
</Grid>
<Grid item xs={12} sm={6} md={3}>
<MuiPickersUtilsProvider utils={DateFnsUtils}>
<KeyboardDatePicker
disableToolbar
variant="inline"
format="MM/dd/yyyy"
margin="normal"
label="End Date"
value={selectedEndDate}
onChange={setSelectedEndDate}
KeyboardButtonProps={{
'aria-label': 'change date',
}}
fullWidth
/>
</MuiPickersUtilsProvider>
</Grid>
<Grid item xs={12} sm={6} md={3}>
<Button
variant="contained"
color="primary"
onClick={fetchDTCData}
disabled={loading}
fullWidth
style={{ marginTop: '15px' }}
>
{loading ? <CircularProgress size={24} /> : "Filter"}
</Button>
</Grid>
</Grid>
<div className={clsx(classes.paper, classes.tableSize)}>
<Table aria-label="dtc table">
<TableHeaderSortable
classes={classes}
orderBy={orderBy}
order={order}
columnData={tableColumns}
onSortRequest={handleSort}
/>
<TableBody>
{(dtcData || []).map((dtc, index) => (
<TableRow key={index}>
<TableCell component="th" scope="row">
{dtc.id}
</TableCell>
<TableCell>{dtc.vin}</TableCell>
<TableCell>{dtc.ecu_name}</TableCell>
<TableCell>{dtc.dtc}</TableCell>
<TableCell>{formatDate(dtc.epoch_usec)}</TableCell>
</TableRow>
))}
</TableBody>
<TableFooter>
<TableRow>
<TablePagination
rowsPerPageOptions={[5, 10, 25, 100]}
colSpan={6}
count={total}
rowsPerPage={pageSize}
page={pageIndex}
SelectProps={{
inputProps: { "aria-label": "rows per page" },
native: true,
}}
onPageChange={handleChangePageIndex}
onRowsPerPageChange={handleChangePageSize}
/>
</TableRow>
</TableFooter>
</Table>
</div>
</div>
);
};
const DTCTimeline = (props) => (
<DTCTimelineProvider>
<MainForm {...props} />
</DTCTimelineProvider>
);
export default DTCTimeline;

View File

@@ -0,0 +1,21 @@
import { Typography } from "@material-ui/core";
import clsx from "clsx";
import React from "react";
import { useParams } from "react-router";
import useStyles from "../useStyles";
import DTCTimeline from "./DTCTimeline";
const SelfServeTab = () => {
const { vin } = useParams();
const classes = useStyles();
return (
<div className={clsx(classes.paper, classes.tableSize)}>
<Typography variant="h6">DTC Timeline</Typography>
<DTCTimeline id={vin} classes={classes} />
</div >
);
};
export default SelfServeTab;