diff --git a/src/components/VehiclePathsMap/index.jsx b/src/components/VehiclePathsMap/index.jsx
new file mode 100644
index 0000000..97f7961
--- /dev/null
+++ b/src/components/VehiclePathsMap/index.jsx
@@ -0,0 +1,253 @@
+import { Button } from "@material-ui/core";
+import L from "leaflet";
+import React, { useEffect, useState } from "react";
+import { MapContainer, Marker, Polyline, Popup, TileLayer, useMap } from "react-leaflet";
+import useStyles from "../useStyles";
+
+import GrayMarkerIcon from "../../assets/gray-marker.png";
+import GreenMarkerIcon from "../../assets/green-marker.png";
+import { logger } from "../../services/monitoring";
+import { ValidateLocationVehiclePathsData } from "../../utils/locations";
+import { useUserContext } from "../Contexts/UserContext";
+import { useVehicleContext, VehicleProvider } from "../Contexts/VehicleContext";
+import { VehiclePopUp } from "../VehicleMap/popup";
+
+const ComponentVehiclePathsMap = (props) => {
+ const classes = useStyles();
+ const {
+ token: {
+ idToken: { jwtToken: token },
+ },
+ } = useUserContext();
+ const { getConnections, getLocationsVehiclePaths, getState } = useVehicleContext();
+
+ const REQUEST_INTERVAL = 10000;
+
+ const [center, setCenter] = useState([0, 0]);
+ const [zoom, setZoom] = useState(2);
+ const [markers, setMarkers] = useState([]);
+ const [connections, setConnections] = useState({});
+
+ useEffect(() => {
+ if (!token) return;
+ retrieveAndStoreLocations(token).then((points) => {
+ centerAroundMarkers(points);
+ });
+ const id = setInterval(function () {
+ retrieveAndStoreLocations(token);
+ }, REQUEST_INTERVAL);
+ return () => {
+ clearInterval(id);
+ };
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [token]);
+
+ const retrieveAndStoreLocations = (accessToken) => {
+ let vinsToShowOnMap = [...props.vinsToShowOnMapColors.keys()];
+ let vinsParam = ""
+ for (let vinToShowOnMap of vinsToShowOnMap) {
+ vinsParam += "vins="
+ vinsParam += vinToShowOnMap
+ vinsParam += "&"
+ }
+ vinsParam += "lookback_hours="
+ vinsParam += props.lookbackHours
+
+ return getLocationsVehiclePaths(accessToken, vinsParam)
+ .then((result) => {
+ let resultArray = Object.entries(result)
+ const points = []
+
+ // validate each location
+ for (let vinLocations of resultArray) {
+ let path = []
+ path[0] = vinLocations[0]
+ path[1] = []
+ for (let location of vinLocations[1]) {
+ if (ValidateLocationVehiclePathsData(location) !== false) {
+ path[1].push(location);
+ }
+ }
+ points.push(path)
+ }
+
+ setMarkers(points);
+ return points;
+ })
+ .catch((error) => logger.warn(error.stack));
+ };
+
+ const centerAroundMarkers = (points) => {
+ // default center
+ let center = [37.0902, -95.7129]
+
+ // center is the very first geographical point
+ if (points && points[0] && points[0][1] && points[0][1][0]) {
+ center = points[0][1][0]
+ }
+
+ setCenter(center);
+ setZoom(4.5);
+ };
+
+ useEffect(() => {
+ if (!token) return;
+
+ const vins = []
+
+ for (let vinLocations of markers) {
+ vins.push(vinLocations[0])
+ }
+
+ if (vins.length === 0) return;
+
+ getConnections(vins, token).then((conns) => {
+ setConnections(conns);
+ });
+ // eslint-disable-next-line
+ }, [markers, token]);
+
+ const [selectedVIN, setSelectedVIN] = useState(null);
+ const [carState, setCarState] = useState(null);
+
+ useEffect(() => {
+ if (selectedVIN != null) {
+ retrieveAndStoreCarState(selectedVIN);
+ const id = setInterval(function () {
+ retrieveAndStoreCarState(selectedVIN);
+ }, REQUEST_INTERVAL);
+ return () => {
+ clearInterval(id);
+ };
+ }
+ // eslint-disable-next-line
+ }, [selectedVIN]);
+
+ const selectCar = (e, vin) => {
+ e.preventDefault();
+ setSelectedVIN(vin);
+ };
+
+ const retrieveAndStoreCarState = (vin) => {
+ getState(token, vin).then((results) => {
+ setCarState({ ...results.data, vin: vin });
+ });
+ };
+
+ const handleClose = () => {
+ setSelectedVIN(null);
+ setCarState(null);
+ };
+
+ const isOnline = (vin) => {
+ return connections[vin];
+ };
+
+ const getZIndex = (vin) => {
+ if (isOnline(vin)) return 1000;
+ return 0;
+ };
+
+ function getCarIcon(vin) {
+ let icon = GrayMarkerIcon;
+
+ if (isOnline(vin)) {
+ icon = GreenMarkerIcon;
+ }
+
+ return new L.Icon({
+ iconUrl: icon,
+ iconAnchor: [24, 42],
+ });
+ }
+
+ return (
+
+
+
+ {markers && markers.map((vinLocations) => (
+
+
+ {vinLocations[1][0] &&
+
{
+ setCenter(vinLocations[1][0]);
+ setZoom(16);
+ },
+ }}
+ >
+
+
+
+ {vinLocations[0]}
+
+
+
+
+
+ }
+
+ ))}
+
+ {carState ? (
+
+ ) : null}
+
+ );
+};
+
+const CenterFocus = ({ center, zoom }) => {
+ const map = useMap();
+
+ useEffect(() => {
+ if (center[0] === 0 && center[1] === 0) {
+ map.flyTo([0, 0], 2, { duration: 1.5 });
+ } else {
+ map.flyTo(center, zoom, { duration: 1.5 });
+ }
+ }, [center, zoom, map]);
+
+ return null;
+};
+
+const VehiclePathsMap = (props) => (
+
+
+
+);
+
+export default VehiclePathsMap;
diff --git a/src/components/useStyles.jsx b/src/components/useStyles.jsx
index 1931093..edb09bc 100644
--- a/src/components/useStyles.jsx
+++ b/src/components/useStyles.jsx
@@ -288,7 +288,12 @@ const useStyles = makeStyles((theme) => ({
tableHeader: {
textDecorationStyle: "solid",
fontWeight:500,
- }
+ },
+ limitWidthTableCell: {
+ maxWidth: "200px",
+ whiteSpace: "normal",
+ wordWrap: "break-word",
+ },
}));
export default useStyles;
diff --git a/src/services/DTCTimelineAPI.js b/src/services/DTCTimelineAPI.js
index c7c2a7e..3fce6c6 100644
--- a/src/services/DTCTimelineAPI.js
+++ b/src/services/DTCTimelineAPI.js
@@ -13,6 +13,7 @@ const DTCTimelineAPI = {
ecu,
start_time: startDate,
end_time: endDate,
+ decode:true,
...search,
};
const url = addQueryParams(`${API_ENDPOINT}/dtcs/${vin}`, queryParams);
diff --git a/src/services/__mocks__/vehiclesAPI.js b/src/services/__mocks__/vehiclesAPI.js
index 4b2844e..369bda6 100644
--- a/src/services/__mocks__/vehiclesAPI.js
+++ b/src/services/__mocks__/vehiclesAPI.js
@@ -55,13 +55,15 @@ const ecusData = [
},
];
-const signals = {data:[
- {
- timestamp: "2021-07-14T20:09:40.98187Z",
- name: "signal",
- value: 123
- },
-]};
+const signals = {
+ data: [
+ {
+ timestamp: "2021-07-14T20:09:40.98187Z",
+ name: "signal",
+ value: 123
+ },
+ ],
+};
const trexLogs = {
RealOffset: 0,
@@ -127,6 +129,12 @@ const vehiclesAPI = {
.mockResolvedValue([
{ altitude: 5, longitude: 10, latitude: 15, vin: "TESTVIN123" },
]),
+ getLocationsVehiclePaths: async () => {
+ return {
+ '3FAFP13P31R199430': [[16.891136999999986, 26.832352999999955], [56.891136999999986, 66.832352999999955], [26.891136999999986, 36.832352999999955]],
+ '3FAFP13P71R199060': [[36.891136999999986, 46.832352999999955], [76.891136999999986, 16.832352999999955]],
+ };
+ },
getVehicle: async (vin) => {
const index = data.findIndex(element => element.vin === vin);
return data[index];
@@ -134,7 +142,7 @@ const vehiclesAPI = {
getVehicles: async () => {
return { data };
},
- getFleets: async (vin) => {return { data: ["fleet1", "fleet2"]}},
+ getFleets: async (vin) => { return { data: ["fleet1", "fleet2"] } },
getYears: async () => {
return {
data: [2021, 2022],
@@ -152,29 +160,29 @@ const vehiclesAPI = {
return vehicle;
},
getCANSignals: async (vin, vehicle) => {
- return signals;
+ return signals;
},
getTRexLogs: async (vin, date, offset, count, direction, token) => {
return trexLogs;
},
getVersionLog: async (vin) => ({
- "data": [
- {
- "id": 1,
- "vin": "${vin}",
- "version_source": "TREX",
- "version": "0.9.56",
- "created_at": "2023-01-13T02:11:33.327214Z"
- },
- {
- "id": 2,
- "vin": "${vin}",
- "version_source": "DBC",
- "version": "386c18977a1be3cda60c953e5902c680dbe82b89523f2527e80cd9db863db991",
- "created_at": "2023-01-13T02:11:33.330932Z"
- }
- ],
- "total": 2
+ "data": [
+ {
+ "id": 1,
+ "vin": "${vin}",
+ "version_source": "TREX",
+ "version": "0.9.56",
+ "created_at": "2023-01-13T02:11:33.327214Z"
+ },
+ {
+ "id": 2,
+ "vin": "${vin}",
+ "version_source": "DBC",
+ "version": "386c18977a1be3cda60c953e5902c680dbe82b89523f2527e80cd9db863db991",
+ "created_at": "2023-01-13T02:11:33.330932Z"
+ }
+ ],
+ "total": 2
})
};
diff --git a/src/services/customDashboards.js b/src/services/customDashboards.js
index 8f9fe65..80dae84 100644
--- a/src/services/customDashboards.js
+++ b/src/services/customDashboards.js
@@ -1,8 +1,30 @@
+import VehiclePathsMap from "../components/VehiclePathsMap";
+
const INVALID_DASHBOARD = {
label: "Invalid Dashboard",
error: "Invalid Dashboard"
}
+const vinsToShowOnMapColors = new Map([
+ ['3FAFP13P71R199267', 'red'],
+ ['3FAFP13P71R199270', 'orange'],
+ ['3FAFP13P71R199222', 'blue'],
+ ['3FAFP13P61R199339', 'yellow'],
+ ['3FAFP13P71R199057', 'turquoise'],
+ ['3FAFP13P61R199387', 'lime'],
+ ['3FAFP13P71R199334', 'purple'],
+ ['3FAFP13P71R199284', 'green'],
+ ['3FAFP13P71R199303', 'sienna'],
+ ['3FAFP13P31R199430', 'navy'],
+ ['3FAFP13P81R199083', 'cadetblue'],
+ ['3FAFP13P71R199060', 'coral'],
+ ['3FAFP13P71R199317', 'darkkhaki'],
+ ['3FAFP13P71R199320', 'fuchsia'],
+ ['3FAFP13P61R199390', 'indigo'],
+ ['3FAFP13P61R199373', 'cyan'],
+])
+const lookbackHours = 24
+
export const CustomDashboardList = [
/*
{
@@ -14,6 +36,11 @@ export const CustomDashboardList = [
component:
}
*/
+
+ {
+ label: "Vehicle Map",
+ component:
+ }
];
export const getCustomDashboard = (index) => {
diff --git a/src/services/manifestsAPI.js b/src/services/manifestsAPI.js
index 9091d97..8fbe7a8 100644
--- a/src/services/manifestsAPI.js
+++ b/src/services/manifestsAPI.js
@@ -78,6 +78,17 @@ const manifestsAPI = {
})
.then(fetchRespHandler)
.catch(errorHandler),
+
+ migrateManifest: async (manifest_id, token) =>
+ fetch(`${API_ENDPOINT}/manifestmigrate/${manifest_id}`,{
+ method: "POST",
+ headers: Object.assign(
+ { "Content-Type": "application/json" },
+ getAuthHeaderOptions(token)
+ ),
+ })
+ .then(fetchRespHandler)
+ .catch(errorHandler),
};
export default manifestsAPI;
diff --git a/src/services/vehiclesAPI.js b/src/services/vehiclesAPI.js
index 80345e4..8ca9e1f 100644
--- a/src/services/vehiclesAPI.js
+++ b/src/services/vehiclesAPI.js
@@ -76,6 +76,17 @@ const vehiclesAPI = {
.then(fetchRespHandler)
.catch(errorHandler),
+ getLocationsVehiclePaths: async (token, vinsParam) =>
+ fetch(`${API_ENDPOINT}/vehicle_paths?${vinsParam}`, {
+ method: "GET",
+ headers: Object.assign(
+ { "Content-Type": "application/json" },
+ getAuthHeaderOptions(token)
+ ),
+ })
+ .then(fetchRespHandler)
+ .catch(errorHandler),
+
getState: async (token, vin) =>
fetch(`${API_ENDPOINT}/carstate?vin=${vin}`, {
method: "GET",
@@ -185,7 +196,7 @@ const vehiclesAPI = {
.then(fetchRespHandler)
.catch(errorHandler),
- getVersionLog: async ({vin, ...search}, token) => {
+ getVersionLog: async ({ vin, ...search }, token) => {
const u = addQueryParams(`${API_ENDPOINT}/vehicle/${vin}/version/logs`, search);
return fetch(u, {
method: "GET",
diff --git a/src/utils/locations.js b/src/utils/locations.js
index f4fdaba..542418c 100644
--- a/src/utils/locations.js
+++ b/src/utils/locations.js
@@ -10,6 +10,15 @@ export const ValidateLocationData = (location) => {
return true;
}
+export const ValidateLocationVehiclePathsData = (location) => {
+ if (location.length < 2) {
+ return false;
+ }
+
+ // location is an array of length 2 as {
float64, float64}
+ return !(Math.abs(location[0]) > 90 || Math.abs(location[1]) > 180);
+}
+
export const ValidateLocationByParam = (parameter, value) => {
if (invalidLocation === value) return false;
switch (parameter) {
diff --git a/src/utils/roles.js b/src/utils/roles.js
index 68a4783..4875da4 100644
--- a/src/utils/roles.js
+++ b/src/utils/roles.js
@@ -8,6 +8,7 @@ export const Roles = {
APPROVESUPPLIERS: process.env.REACT_APP_ROLE_SUPPLIER_APPROVER,
MANUFACTURE: process.env.REACT_APP_ROLE_MANUFACTURE,
MAGNAGROUP: process.env.REACT_APP_MAGNA_GROUP_ID,
+ MANIFEST_MIGRATION: process.env.REACT_APP_ROLE_MANIFEST_MIGRATION
};
export const Providers = {
@@ -94,4 +95,7 @@ export const Permissions = {
[Providers.FISKER_QA]: [Roles.MANUFACTURE],
[Providers.MAGNA]: [Roles.MAGNAGROUP],
},
+ ManifestMigration: {
+ [Providers.FISKER]: [Roles.MANIFEST_MIGRATION]
+ }
};