update mission validation and action
This commit is contained in:
@@ -14,7 +14,7 @@ const MissionsPage = () => {
|
|||||||
const [showMissionModal, setShowMissionModal] = useState(false);
|
const [showMissionModal, setShowMissionModal] = useState(false);
|
||||||
const [showNewMissionModal, setShowNewMissionModal] = useState(false);
|
const [showNewMissionModal, setShowNewMissionModal] = useState(false);
|
||||||
|
|
||||||
const [selectedMission, setSelectedMission] = useState<Mission | null>(null);
|
const [selectedMission, setSelectedMission] = useState<Mission>();
|
||||||
|
|
||||||
const { loading, error, data, refetch } = useQuery(GET_MISSIONS);
|
const { loading, error, data, refetch } = useQuery(GET_MISSIONS);
|
||||||
|
|
||||||
@@ -67,9 +67,12 @@ const MissionsPage = () => {
|
|||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
<MissionModal
|
<MissionModal
|
||||||
mission={selectedMission}
|
mission={selectedMission!}
|
||||||
showModal={showMissionModal}
|
showModal={showMissionModal}
|
||||||
setShowModal={setShowMissionModal}
|
setShowModal={setShowMissionModal}
|
||||||
|
onUpdateMission={() => {
|
||||||
|
refetch();
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { agents, tools } from "@/data/data";
|
|
||||||
import { Icon } from "@iconify/react";
|
import { Icon } from "@iconify/react";
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import {
|
import {
|
||||||
@@ -18,23 +17,62 @@ import { Mission } from "@/types/mission";
|
|||||||
import MissionTaskEditor from "../inputs/mission_tasks_editor";
|
import MissionTaskEditor from "../inputs/mission_tasks_editor";
|
||||||
import { TasksAccordion } from "../ui/tasks_accordions";
|
import { TasksAccordion } from "../ui/tasks_accordions";
|
||||||
import { Process, selectTheme } from "@/data/consts";
|
import { Process, selectTheme } from "@/data/consts";
|
||||||
import { Switch } from "@material-tailwind/react";
|
import { Button, Switch } from "@material-tailwind/react";
|
||||||
|
import { useMutation, useQuery } from "@apollo/client";
|
||||||
|
import { GET_AGENTS, UPDATE_MISSION } from "@/utils/graphql_queries";
|
||||||
|
import withReactContent from "sweetalert2-react-content";
|
||||||
|
import Swal from "sweetalert2";
|
||||||
|
import { Agent } from "@/types/agent";
|
||||||
|
|
||||||
export default function MissionModal(props: {
|
export default function MissionModal(props: {
|
||||||
mission: Mission | null;
|
mission: Mission;
|
||||||
showModal: boolean;
|
showModal: boolean;
|
||||||
setShowModal: Function;
|
setShowModal: Function;
|
||||||
|
onUpdateMission: Function;
|
||||||
}): JSX.Element {
|
}): JSX.Element {
|
||||||
const { mission, showModal, setShowModal } = props;
|
const {
|
||||||
|
mission,
|
||||||
|
showModal,
|
||||||
|
setShowModal,
|
||||||
|
onUpdateMission = () => {},
|
||||||
|
} = props;
|
||||||
|
|
||||||
const [isEdit, setEdit] = useState(false);
|
const [isEdit, setEdit] = useState(false);
|
||||||
|
|
||||||
const [tempMission, setTempMission] = useState<Mission | null>(mission);
|
const [tempMission, setTempMission] = useState<Mission>(mission);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setTempMission(mission);
|
setTempMission(mission);
|
||||||
}, [mission]);
|
}, [mission]);
|
||||||
|
|
||||||
|
const { loading, error, data: agentsData } = useQuery(GET_AGENTS);
|
||||||
|
|
||||||
|
const [updateMission] = useMutation(UPDATE_MISSION);
|
||||||
|
const [updateMissionLoading, setUpdateMissionLoading] = useState(false);
|
||||||
|
|
||||||
|
const handleUpdateMission = async (missionData: Mission) => {
|
||||||
|
setUpdateMissionLoading(true);
|
||||||
|
return updateMission({
|
||||||
|
variables: {
|
||||||
|
...missionData,
|
||||||
|
// TODO: Check why Prisma Schema (id Int) return String.
|
||||||
|
id: Number.parseInt(missionData.id as string),
|
||||||
|
crew: missionData.crew
|
||||||
|
.filter((agent) => Number.parseInt(agent?.id as string))
|
||||||
|
.map((agent) => Number.parseInt(agent.id as string)),
|
||||||
|
tasks: missionData.tasks.map((task) => ({
|
||||||
|
name: task.name,
|
||||||
|
description: task.description,
|
||||||
|
agent: Number.parseInt(task.agent?.id as string),
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
}).finally(() => {
|
||||||
|
setUpdateMissionLoading(false);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const ReactSwal = withReactContent(Swal);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<TEModal show={showModal} setShow={setShowModal}>
|
<TEModal show={showModal} setShow={setShowModal}>
|
||||||
@@ -44,14 +82,12 @@ export default function MissionModal(props: {
|
|||||||
<h1 className="text-xl font-medium leading-normal">
|
<h1 className="text-xl font-medium leading-normal">
|
||||||
{mission?.name}
|
{mission?.name}
|
||||||
</h1>
|
</h1>
|
||||||
<button
|
<Button
|
||||||
type="button"
|
|
||||||
className="box-content rounded-none border-none hover:no-underline hover:opacity-75 focus:opacity-100 focus:shadow-none focus:outline-none"
|
|
||||||
onClick={() => setShowModal(false)}
|
onClick={() => setShowModal(false)}
|
||||||
aria-label="Close"
|
placeholder={undefined}
|
||||||
>
|
>
|
||||||
<Icon icon="ep:close-bold" width={20} height={20} />
|
<Icon icon="ep:close-bold" width={20} height={20} />
|
||||||
</button>
|
</Button>
|
||||||
</TEModalHeader>
|
</TEModalHeader>
|
||||||
<TEModalBody>
|
<TEModalBody>
|
||||||
<div>
|
<div>
|
||||||
@@ -75,22 +111,35 @@ export default function MissionModal(props: {
|
|||||||
<span className="font-bold mr-2 text-lg">Crew (Agents):</span>
|
<span className="font-bold mr-2 text-lg">Crew (Agents):</span>
|
||||||
<br />
|
<br />
|
||||||
{isEdit ? (
|
{isEdit ? (
|
||||||
<TESelect
|
loading ? (
|
||||||
data={agents.map((agent) => ({
|
<Button
|
||||||
text: agent.role,
|
variant="text"
|
||||||
value: agent.role,
|
loading={true}
|
||||||
}))}
|
placeholder={"Loading"}
|
||||||
multiple
|
className="text-white"
|
||||||
value={tempMission?.crew.map((agent) => agent.role) ?? []}
|
>
|
||||||
onValueChange={(event: any) => {
|
Loading
|
||||||
const newValue = event.map((item: any) => item.value);
|
</Button>
|
||||||
setTempMission((prevState) => ({
|
) : (
|
||||||
...prevState!,
|
<TESelect
|
||||||
tools: newValue,
|
data={agentsData.agents.map((agent: Agent) => ({
|
||||||
}));
|
text: agent.role,
|
||||||
}}
|
value: agent.id,
|
||||||
theme={selectTheme}
|
}))}
|
||||||
/>
|
multiple
|
||||||
|
onValueChange={(event: any) => {
|
||||||
|
const newValue = event.map((item: any) => item.value);
|
||||||
|
const newCrew = agentsData.agents.filter(
|
||||||
|
(agent: Agent) => newValue.includes(agent.id)
|
||||||
|
);
|
||||||
|
setTempMission((prevState) => ({
|
||||||
|
...prevState!,
|
||||||
|
crew: newCrew,
|
||||||
|
}));
|
||||||
|
}}
|
||||||
|
theme={selectTheme}
|
||||||
|
/>
|
||||||
|
)
|
||||||
) : (
|
) : (
|
||||||
mission?.crew.map((agent, i) => (
|
mission?.crew.map((agent, i) => (
|
||||||
<>
|
<>
|
||||||
@@ -191,45 +240,68 @@ export default function MissionModal(props: {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="my-3">
|
<div className="my-3">
|
||||||
<button className="mx-auto block rounded bg-primary px-6 pb-2 pt-2.5 text-xs font-medium uppercase leading-normal text-white">
|
<Button color="blue" placeholder={undefined}>
|
||||||
{mission?.result ? "Re-Run" : "Run"}
|
{mission?.result ? "Re-Run" : "Run"}
|
||||||
</button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</TEModalBody>
|
</TEModalBody>
|
||||||
|
|
||||||
|
{/* Update Mission */}
|
||||||
<TEModalFooter>
|
<TEModalFooter>
|
||||||
{!isEdit && (
|
{!isEdit && (
|
||||||
<TERipple rippleColor="light">
|
<TERipple rippleColor="light">
|
||||||
<button
|
<Button
|
||||||
type="button"
|
color="green"
|
||||||
onClick={() => setEdit(true)}
|
onClick={() => setEdit(true)}
|
||||||
className="ml-1 inline-block rounded bg-success-600 px-6 pb-2 pt-2.5 text-xs font-medium uppercase leading-normal text-white shadow-[0_4px_9px_-4px_#3b71ca] transition duration-150 ease-in-out hover:bg-primary-600 hover:shadow-[0_8px_9px_-4px_rgba(59,113,202,0.3),0_4px_18px_0_rgba(59,113,202,0.2)] focus:bg-primary-600 focus:shadow-[0_8px_9px_-4px_rgba(59,113,202,0.3),0_4px_18px_0_rgba(59,113,202,0.2)] focus:outline-none focus:ring-0 active:bg-primary-700 active:shadow-[0_8px_9px_-4px_rgba(59,113,202,0.3),0_4px_18px_0_rgba(59,113,202,0.2)] dark:shadow-[0_4px_9px_-4px_rgba(59,113,202,0.5)] dark:hover:shadow-[0_8px_9px_-4px_rgba(59,113,202,0.2),0_4px_18px_0_rgba(59,113,202,0.1)] dark:focus:shadow-[0_8px_9px_-4px_rgba(59,113,202,0.2),0_4px_18px_0_rgba(59,113,202,0.1)] dark:active:shadow-[0_8px_9px_-4px_rgba(59,113,202,0.2),0_4px_18px_0_rgba(59,113,202,0.1)]"
|
placeholder={undefined}
|
||||||
>
|
>
|
||||||
Edit
|
Edit
|
||||||
</button>
|
</Button>
|
||||||
</TERipple>
|
</TERipple>
|
||||||
)}
|
)}
|
||||||
{isEdit && (
|
{isEdit && (
|
||||||
<>
|
<>
|
||||||
<TERipple rippleColor="light">
|
<TERipple rippleColor="light">
|
||||||
<button
|
<Button
|
||||||
type="button"
|
color="white"
|
||||||
className="inline-block rounded bg-primary-100 px-6 pb-2 pt-2.5 text-xs font-medium uppercase leading-normal text-primary-700 transition duration-150 ease-in-out hover:bg-primary-accent-100 focus:bg-primary-accent-100 focus:outline-none focus:ring-0 active:bg-primary-accent-200"
|
onClick={() => setShowModal(false)}
|
||||||
onClick={() => setEdit(false)}
|
placeholder={undefined}
|
||||||
>
|
>
|
||||||
Close
|
Cancel
|
||||||
</button>
|
</Button>
|
||||||
</TERipple>
|
</TERipple>
|
||||||
<TERipple rippleColor="light">
|
<TERipple rippleColor="light">
|
||||||
<button
|
<Button
|
||||||
type="button"
|
color="green"
|
||||||
className="ml-1 inline-block rounded bg-primary px-6 pb-2 pt-2.5 text-xs font-medium uppercase leading-normal text-white shadow-[0_4px_9px_-4px_#3b71ca] transition duration-150 ease-in-out hover:bg-primary-600 hover:shadow-[0_8px_9px_-4px_rgba(59,113,202,0.3),0_4px_18px_0_rgba(59,113,202,0.2)] focus:bg-primary-600 focus:shadow-[0_8px_9px_-4px_rgba(59,113,202,0.3),0_4px_18px_0_rgba(59,113,202,0.2)] focus:outline-none focus:ring-0 active:bg-primary-700 active:shadow-[0_8px_9px_-4px_rgba(59,113,202,0.3),0_4px_18px_0_rgba(59,113,202,0.2)] dark:shadow-[0_4px_9px_-4px_rgba(59,113,202,0.5)] dark:hover:shadow-[0_8px_9px_-4px_rgba(59,113,202,0.2),0_4px_18px_0_rgba(59,113,202,0.1)] dark:focus:shadow-[0_8px_9px_-4px_rgba(59,113,202,0.2),0_4px_18px_0_rgba(59,113,202,0.1)] dark:active:shadow-[0_8px_9px_-4px_rgba(59,113,202,0.2),0_4px_18px_0_rgba(59,113,202,0.1)]"
|
loading={updateMissionLoading}
|
||||||
|
disabled={!tempMission.name || updateMissionLoading}
|
||||||
|
onClick={() => {
|
||||||
|
handleUpdateMission(tempMission)
|
||||||
|
.then(() => {
|
||||||
|
setShowModal(false);
|
||||||
|
ReactSwal.fire({
|
||||||
|
title: "Update Mission",
|
||||||
|
text: "Mission updated successfully",
|
||||||
|
icon: "success",
|
||||||
|
});
|
||||||
|
onUpdateMission();
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
ReactSwal.fire({
|
||||||
|
title: "Error",
|
||||||
|
text: error,
|
||||||
|
icon: "error",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
className="mx-1"
|
||||||
|
placeholder={undefined}
|
||||||
>
|
>
|
||||||
Save changes
|
Save Changes
|
||||||
</button>
|
</Button>
|
||||||
</TERipple>
|
</TERipple>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { Task, TaskInput } from "./task";
|
|||||||
type ProcessType = "SEQUENTIAL" | "HIERARCHICAL";
|
type ProcessType = "SEQUENTIAL" | "HIERARCHICAL";
|
||||||
|
|
||||||
export type Mission = {
|
export type Mission = {
|
||||||
id?: number;
|
id?: number | string;
|
||||||
name: string;
|
name: string;
|
||||||
crew: Array<Agent>;
|
crew: Array<Agent>;
|
||||||
tasks: Array<Task>;
|
tasks: Array<Task>;
|
||||||
|
|||||||
Reference in New Issue
Block a user