import React, { useContext, useState, useEffect, useRef } from "react"; import api from "../../services/fleetsAPI"; import updatesApi from "../../services/updatesAPI"; import vehiclesAPI from "../../services/vehiclesAPI"; import { downloadPercent, installPercent } from "./CarUpdatesContext"; import { validateCANID, validateFilter, validateVIN } from "../../utils/validationSupplier"; import Polling from "../../utils/polling"; const FleetContext = React.createContext(); export const FleetProvider = ({ children }) => { const [busy, setBusy] = useState(false); const [fleet, setFleet] = useState({}); const [fleets, setFleets] = useState([]); const [totalFleets, setTotalFleets] = useState(0); const [fleetVehicles, setFleetVehicles] = useState([]); const [totalFleetVehicles, setTotalFleetVehicles] = useState(0); const [carUpdateIds, setCarUpdateIds] = useState([]); const carUpdateIdsRef = useRef(); carUpdateIdsRef.current = carUpdateIds; const [fleetCANFilters, setFleetCANFilters] = useState([]); const [totalFleetCANFilters, setTotalFleetCANFilters] = useState(0); const addFleet = async (f, token) => { try { setBusy(true); validateFleet(f); const result = await api.addFleet(f, token); if (result.error) throw new Error(`Add fleet error. ${result.message}`); return result; } finally { setBusy(false); } }; const getFleet = async (name, token) => { try { setBusy(true); validateFleetName(name); const result = await api.getFleet(name, token); if (result.error) { setFleet({}); throw new Error(`Get fleet error. ${result.message}`); } setFleet(result); return result; } finally { setBusy(false); } }; const getFleets = async (search, token) => { try { setBusy(true); const result = await api.getFleets(search, token); if (result.error) { setFleets([]) throw new Error(`Get fleets error. ${result.message}`); } setFleets(result.data) if (result.total) { setTotalFleets(result.total); } return result; } finally { setBusy(false); } }; const updateFleet = async (name, f, token) => { try { setBusy(true); validateFleetName(name); validateFleet(f); const result = await api.updateFleet(name, f, token); if (result.error) { throw new Error(`Update fleet error. ${result.message}`); } return result; } finally { setBusy(false); } }; const deleteFleet = async (name, token) => { try { setBusy(true); validateFleetName(name); const result = await api.deleteFleet(name, token); if (result.error) { throw new Error(`Delete filter error. ${result.message}`); } return result; } finally { setBusy(false); } }; const getFleetVehicles = async (name, search, token) => { try { setBusy(true); const result = await api.getFleetVehicles(name, search, token); if (result.error) { setFleetVehicles([]) throw new Error(`Get fleet vehicles error. ${result.message}`); } const vins = result.data.map(vehicle => vehicle.vin); const connectionsResult = await vehiclesAPI.getConnections({ "VINs": vins }, token) if (connectionsResult.error) { setFleetVehicles([]) throw new Error(`Get vehicles connections error. ${result.message}`); } var cars = [] result.data.forEach((vehicle) => { cars.push({ vin: vehicle.vin, connected: connectionsResult[vehicle.vin] || false, connectedHMI: connectionsResult[`2:${vehicle.vin}`] || false, trex_version: vehicle.carstate?.trex_version || "", car_update_id: vehicle.carupdate?.id || "", car_update_name: vehicle.carupdate?.updatemanifest?.name || "", car_update_status: vehicle.carupdate?.status || "", car_update_type: vehicle.carupdate?.updatemanifest?.type || "", voltage: vehicle.carstate?.battery?.battery_voltage, charge: vehicle.carstate?.battery?.percent, charge_type: vehicle.carstate?.vcu0x260?.charge_type, park: vehicle.carstate?.gear?.in_park, }); }); setFleetVehicles(cars) if (result.total) { setTotalFleetVehicles(result.total); } return result; } finally { setBusy(false); } }; const watchFleetVehicles = new Polling(async ({ token }) => { const result = await updatesApi.getCarUpdateProgress( carUpdateIdsRef.current.join(","), token ) .then((result) => { if (!Array.isArray(result?.statuses)) { return Promise.reject(); } return result; }) .catch(() => { return Promise.reject(); }); let pivot = result.statuses?.length ? result.statuses.length - 1 : 0; setFleetVehicles((fleetVehicles) => fleetVehicles.map((vehicle) => { result.statuses.find((status, i) => { if (vehicle.car_update_id !== status.car_update_id) { return false; } switch (status.msg) { case "downloading": vehicle.car_update_progress = downloadPercent(status); vehicle.car_update_status = `${status.ecu} downloading ${vehicle.car_update_progress}%`.trim(); break; case "package_download_complete": vehicle.car_update_progress = 100; vehicle.car_update_status = `download complete`; break; case "installing": vehicle.car_update_progress = installPercent(status); vehicle.car_update_status = `${status.ecu} installing ${vehicle.car_update_progress}%`.trim(); break; case "package_install_complete": vehicle.car_update_progress = 100; vehicle.car_update_status = `install complete`; break; default: vehicle.car_update_progress = -1; vehicle.car_update_status = status.msg; break; } // Move found status' to end to reduce time complexity to Ologn result.statuses[i] = result.statuses[pivot]; result.statuses[pivot] = status; pivot -= 1; return true; }); return vehicle; })); return Promise.resolve(); }, 2000); const addFleetVehicles = async (name, vehicles, token) => { try { setBusy(true); validateFleetName(name); for (const vin of vehicles.vins) { validateVIN(vin); } const result = await api.addFleetVehicles(name, vehicles, token); if (result.error) { throw new Error(`Add fleet vehicle error. ${result.message}`); } return result; } finally { setBusy(false); } }; const deleteFleetVehicle = async (name, vehicle, token) => { try { setBusy(true); validateFleetName(name); validateVIN(vehicle.vin); const result = await api.deleteFleetVehicle(name, vehicle, token); if (result.error) { throw new Error(`Delete fleet vehicle error. ${result.message}`); } const index = fleetVehicles.findIndex(element => element === vehicle.vin); if (index >= 0) fleetVehicles.splice(index, 1); return result; } finally { setBusy(false); } }; const getFleetCANFilters = async (name, search, token) => { try { setBusy(true); const result = await api.getFleetCANFilters(name, search, token); if (result.error) { setFleetCANFilters([]) throw new Error(`Get fleet filters error. ${result.message}`); } setFleetCANFilters(result.data) if (result.total) { setTotalFleetCANFilters(result.total); } return result; } finally { setBusy(false); } }; const addFleetCANFilter = async (name, filter, token) => { try { setBusy(true); validateFleetName(name); validateFilter(filter); const result = await api.addFleetCANFilter(name, filter, token); if (result.error) { throw new Error(`Add fleet CAN filter error. ${result.message}`); } return result; } finally { setBusy(false); } } const updateFleetCANFilter = async (name, can_id, filter, token) => { try { setBusy(true); validateFleetName(name); validateFilter(filter); const result = await api.updateFleetCANFilter(name, can_id, filter, token); if (result.error) { throw new Error(`Update fleet CAN filter error. ${result.message}`); } return result; } finally { setBusy(false); } } const deleteFleetCANFilter = async (name, can_id, token) => { try { setBusy(true); validateFleetName(name); validateCANID(can_id); const result = await api.deleteFleetCANFilter(name, can_id, token); if (result.error) { throw new Error(`Delete fleet vehicle error. ${result.message}`); } const index = fleetCANFilters.findIndex(element => element.can_id === can_id); if (index >= 0) fleetCANFilters.splice(index, 1); return result; } finally { setBusy(false); } }; useEffect(() => { setCarUpdateIds(() => fleetVehicles .filter((vehicle) => vehicle.car_update_status && vehicle.car_update_status !== "installed") .map((vehicle) => vehicle.car_update_id)); }, [fleetVehicles, setCarUpdateIds]); return ( {children} ); }; const validateFleet = (f) => { if (f == null) { throw new Error("No fleet data"); } validateFleetName(f.name); } const validateFleetName = (name) => { if (name == null || !/^[\w-]+$/.test(name)) { throw new Error("Invalid name"); } }; export const useFleetContext = () => useContext(FleetContext);