diff --git a/src/components/BulkActions/index.jsx b/src/components/BulkActions/index.jsx new file mode 100644 index 0000000..81f5f3c --- /dev/null +++ b/src/components/BulkActions/index.jsx @@ -0,0 +1,84 @@ +import { useEffect, useState } from "react"; +import TransformModal from "../TransformModal"; +import DropDownButton from "../Controls/DropDownButton"; +import { useUserContext } from "../Contexts/UserContext"; +import { useStatusContext } from "../Contexts/StatusContext"; +import useAddTags from "./useAddTags"; +import useUpdateConfig from "./useUpdateConfig"; + +const transformArrayToCSV = (arr) => arr.join(", "); + +export default function BulkActions({ + vins = [], +}) { + const [vinCSV, setVinCSV] = useState(transformArrayToCSV(vins)); + const [active, setActive] = useState(null); + const actions = [ + { + name: "Update Configs", + disabled: vins.length === 0, + trigger: () => setActive("updateConfig"), + }, + { + name: "Add Tags", + disabled: vins.length === 0, + trigger: () => setActive("addTags"), + }, + ]; + + const updateConfig = useUpdateConfig(); + const addTags = useAddTags(); + + const { setMessage } = useStatusContext(); + const { + token: { + idToken: { jwtToken: token }, + }, + } = useUserContext(); + + const handleUpdateConfig = () => { + updateConfig.submit(vins, token) + .then(() => { + setMessage(`${vins.length} vehicles updated.`); + }) + .catch((error) => { + setMessage(error.message); + }); + } + + const handleAddTags = () => { + addTags.submit(vins, token) + .then(() => setMessage(`Added ${addTags.data.tags.value.length} tags to ${vins.length} vehicles.`)) + .catch((error) => setMessage(error.message)); + } + + const handleClose = () => setActive(null); + + useEffect(() => { + setVinCSV(transformArrayToCSV(vins)); + }, [vins]); + + return ( + <> + + + + + ); +} diff --git a/src/components/BulkActions/useAddTags.js b/src/components/BulkActions/useAddTags.js new file mode 100644 index 0000000..7696595 --- /dev/null +++ b/src/components/BulkActions/useAddTags.js @@ -0,0 +1,22 @@ +import { useState } from "react"; +import vehiclesAPI from "../../services/vehiclesAPI"; + +export default function useAddTags() { + const [tags, setTags] = useState({ + tags: { + label: "Tags", + type: "list.string", + value: [], + }, + }); + + const submit = async (vins, token) => { + return vehiclesAPI.addTags(vins, tags.tags.value, token); + } + + return { + data: tags, + setData: setTags, + submit, + }; +} diff --git a/src/components/BulkActions/useUpdateConfig.js b/src/components/BulkActions/useUpdateConfig.js new file mode 100644 index 0000000..c648bd1 --- /dev/null +++ b/src/components/BulkActions/useUpdateConfig.js @@ -0,0 +1,40 @@ +import { useState } from "react"; +import TaskRunner from "../../utils/taskRunner"; +import vehiclesAPI from "../../services/vehiclesAPI"; + +export default function useUpdateConfig() { + const [config, setConfig] = useState({ + force: { + label: "Force Push", + type: "boolean", + value: false, + }, + }); + + const submit = async (vins, token) => { + return new Promise((resolve, reject) => { + const taskRunner = new TaskRunner(5); + + const task = (vin, isLast) => { + return async () => vehiclesAPI.updateConfig(vin, config.force.value, token) + .then((response) => { + if (isLast) { + if (response.error) { + reject(response); + } + resolve(response) + } + }) + .catch((error) => reject(error)); + } + + vins.forEach((vin, index) => taskRunner.push(task(vin, index === vins.length - 1))); + }); + } + + return { + data: config, + setData: setConfig, + submit, + }; +} \ No newline at end of file diff --git a/src/components/Controls/DropDownButton/index.jsx b/src/components/Controls/DropDownButton/index.jsx index 77a5562..6820877 100644 --- a/src/components/Controls/DropDownButton/index.jsx +++ b/src/components/Controls/DropDownButton/index.jsx @@ -40,6 +40,10 @@ const DropDownButton = ({ actions = [], payload = [] }) => { setOpen(false); }; + if (!actions.length) { + return <>; + } + return ( <> +
+
+ + +
+
diff --git a/src/components/Fleets/Status/Details/index.jsx b/src/components/Fleets/Status/Details/index.jsx index 8b81643..2926b38 100644 --- a/src/components/Fleets/Status/Details/index.jsx +++ b/src/components/Fleets/Status/Details/index.jsx @@ -15,6 +15,7 @@ import { FleetProvider, useFleetContext } from "../../../Contexts/FleetContext" import useStyles from "../../../useStyles"; import { logger } from "../../../../services/monitoring"; import DeleteConfirmation from "../../../DeleteConfirmation"; +import BulkActions from "../../../BulkActions"; const MainForm = ({ name }) => { const classes = useStyles(); @@ -94,6 +95,9 @@ const MainForm = ({ name }) => { + + + setShowDeleteModal(false)} deleteFunction={onDelete} /> diff --git a/src/components/Fleets/Status/__snapshots__/DetailsTab.test.jsx.snap b/src/components/Fleets/Status/__snapshots__/DetailsTab.test.jsx.snap index b62b7fd..b9ee191 100644 --- a/src/components/Fleets/Status/__snapshots__/DetailsTab.test.jsx.snap +++ b/src/components/Fleets/Status/__snapshots__/DetailsTab.test.jsx.snap @@ -151,6 +151,48 @@ exports[`DetailsTab Render 1`] = ` +
+
+ + +
+
diff --git a/src/components/Fleets/Status/__snapshots__/index.test.jsx.snap b/src/components/Fleets/Status/__snapshots__/index.test.jsx.snap index 19171b8..42fdb41 100644 --- a/src/components/Fleets/Status/__snapshots__/index.test.jsx.snap +++ b/src/components/Fleets/Status/__snapshots__/index.test.jsx.snap @@ -239,6 +239,48 @@ exports[`FleetStatus Render 1`] = ` +
+
+ + +
+
diff --git a/src/components/TransformModal/index.jsx b/src/components/TransformModal/index.jsx index 8a58a0c..ee0b13f 100644 --- a/src/components/TransformModal/index.jsx +++ b/src/components/TransformModal/index.jsx @@ -28,7 +28,7 @@ const TransformModal = ({ const handleChange = (key, value) => { setData((data) => { - const {[key]: toChange, ...rest} = data; + const { [key]: toChange, ...rest } = data; switch (data[key].type) { case "boolean": toChange.value = !toChange.value;