1053 lines
33 KiB
JavaScript
1053 lines
33 KiB
JavaScript
import {
|
|
Alert,
|
|
Button,
|
|
Col,
|
|
Form,
|
|
Image,
|
|
Input,
|
|
InputNumber,
|
|
Modal,
|
|
Popover,
|
|
Result,
|
|
Row,
|
|
Space,
|
|
Steps,
|
|
Tag,
|
|
notification,
|
|
} from "antd";
|
|
import { useContext, useMemo, useRef, useState } from "react";
|
|
import { useNavigate, useParams } from "react-router-dom";
|
|
import {
|
|
Constants,
|
|
FormatDatetime,
|
|
WebSocketContext,
|
|
SentMessagesCommands,
|
|
GetDuration,
|
|
MyAvatar,
|
|
getUserId,
|
|
GroupTasksStepsLockedAndUserUpdateInputValueRememberId,
|
|
hasXYPermission,
|
|
} from "../../../utils";
|
|
import {
|
|
CheckOutlined,
|
|
FileImageOutlined,
|
|
InfoCircleOutlined,
|
|
LockOutlined,
|
|
RetweetOutlined,
|
|
UndoOutlined,
|
|
} from "@ant-design/icons";
|
|
import { StlViewer } from "react-stl-viewer";
|
|
import TextArea from "antd/es/input/TextArea";
|
|
import { useTranslation } from "react-i18next";
|
|
|
|
export default function GroupTasksViewModal({ isOpen }) {
|
|
const webSocketContext = useContext(WebSocketContext);
|
|
const navigate = useNavigate();
|
|
const [notificationApi, notificationContextHolder] =
|
|
notification.useNotification();
|
|
let { paramGroupTaskId } = useParams();
|
|
const { t } = useTranslation();
|
|
|
|
const handleCancel = () => navigate(Constants.ROUTE_PATHS.GROUP_TASKS);
|
|
let currentGroupTask;
|
|
|
|
webSocketContext.GroupTasks.forEach((groupTask) => {
|
|
if (groupTask.Id === paramGroupTaskId) {
|
|
currentGroupTask = groupTask;
|
|
}
|
|
});
|
|
|
|
// invalid group task id in url specified or no permissions
|
|
if (
|
|
!currentGroupTask ||
|
|
!hasXYPermission(
|
|
webSocketContext.User.Permissions,
|
|
Constants.PERMISSIONS.GROUP_TASKS.OVERVIEW.XYView,
|
|
currentGroupTask.Category
|
|
)
|
|
) {
|
|
return (
|
|
<Modal
|
|
open={isOpen}
|
|
width="70%"
|
|
maskClosable={true}
|
|
onCancel={handleCancel}
|
|
footer={
|
|
<Button onClick={handleCancel}>{t("common.button.close")}</Button>
|
|
}
|
|
>
|
|
<Result
|
|
status="500"
|
|
title={t("groupTasks.groupTasksViewModal.groupTaskNotFound")}
|
|
/>
|
|
</Modal>
|
|
);
|
|
}
|
|
|
|
const getAlertType = (status) => {
|
|
switch (status) {
|
|
case Constants.GROUP_TASKS_STATUS.FINISHED:
|
|
return "success";
|
|
case Constants.GROUP_TASKS_STATUS.FAILED:
|
|
return "error";
|
|
case Constants.GROUP_TASKS_STATUS.CANCELED:
|
|
case Constants.GROUP_TASKS_STATUS.PAUSED:
|
|
case Constants.GROUP_TASKS_STATUS.UNDO_ENDED:
|
|
return "warning";
|
|
case Constants.GROUP_TASKS_STATUS.INPUT_REQUIRED:
|
|
case Constants.GROUP_TASKS_STATUS.RUNNING:
|
|
default:
|
|
return "info";
|
|
}
|
|
};
|
|
|
|
const getAlertMessage = (status) => {
|
|
switch (status) {
|
|
case Constants.GROUP_TASKS_STATUS.FINISHED:
|
|
return t("groupTasks.groupTasksViewModal.alertMessage.successful");
|
|
case Constants.GROUP_TASKS_STATUS.RUNNING:
|
|
return t("groupTasks.groupTasksViewModal.alertMessage.taskIsRunning");
|
|
case Constants.GROUP_TASKS_STATUS.CANCELED:
|
|
return t("groupTasks.groupTasksViewModal.alertMessage.taskCanceled");
|
|
case Constants.GROUP_TASKS_STATUS.FAILED:
|
|
return t("groupTasks.groupTasksViewModal.alertMessage.taskFailed");
|
|
case Constants.GROUP_TASKS_STATUS.INPUT_REQUIRED:
|
|
return t(
|
|
"groupTasks.groupTasksViewModal.alertMessage.taskInputRequired"
|
|
);
|
|
case Constants.GROUP_TASKS_STATUS.PAUSED:
|
|
return t("groupTasks.groupTasksViewModal.alertMessage.paused");
|
|
case Constants.GROUP_TASKS_STATUS.UNDO_ENDED:
|
|
return t("groupTasks.groupTasksViewModal.alertMessage.undoEnded");
|
|
default:
|
|
return "Alert message not found";
|
|
}
|
|
};
|
|
|
|
const getStepItemStatus = (status) => {
|
|
switch (status) {
|
|
case Constants.GROUP_TASKS_STATUS.FINISHED:
|
|
return "finish";
|
|
case Constants.GROUP_TASKS_STATUS.RUNNING:
|
|
return "process";
|
|
case Constants.GROUP_TASKS_STATUS.CANCELED:
|
|
case Constants.GROUP_TASKS_STATUS.FAILED:
|
|
return "error";
|
|
case Constants.GROUP_TASKS_STATUS.INPUT_REQUIRED:
|
|
case Constants.GROUP_TASKS_STATUS.PAUSED:
|
|
case Constants.GROUP_TASKS_STATUS.UNDO_ENDED:
|
|
default:
|
|
return "wait";
|
|
}
|
|
};
|
|
|
|
const handleTaskFailedTryAgainRunTaskStep = (taskStepId, step) => {
|
|
webSocketContext.SendSocketMessage(
|
|
SentMessagesCommands.TaskFailedTryAgainRunTaskStep,
|
|
{
|
|
groupTaskId: currentGroupTask.Id,
|
|
category: currentGroupTask.Category,
|
|
groupId: currentGroupTask.GroupId,
|
|
step: step,
|
|
taskStepId: taskStepId,
|
|
}
|
|
);
|
|
};
|
|
|
|
const handleTaskContinueTaskStep = (taskStepId, step) => {
|
|
const groupTasksViewModalRequiredInputsForm = document.getElementById(
|
|
"groupTasksViewModalRequiredInputsForm"
|
|
);
|
|
|
|
let canTaskContinued = true;
|
|
let taskInputs = [];
|
|
|
|
if (groupTasksViewModalRequiredInputsForm !== null) {
|
|
const specifiedTaskInputs =
|
|
groupTasksViewModalRequiredInputsForm.getElementsByTagName("input");
|
|
|
|
if (specifiedTaskInputs.length > 0) {
|
|
for (let i = 0; i < specifiedTaskInputs.length; i++) {
|
|
if (specifiedTaskInputs[i].value === "") {
|
|
canTaskContinued = false;
|
|
break;
|
|
}
|
|
|
|
taskInputs.push({
|
|
parameterName:
|
|
specifiedTaskInputs[i].id.split(
|
|
"-"
|
|
)[6] /* Format: UUID-STEP-PARAMETER_NAME */,
|
|
value: specifiedTaskInputs[i].value,
|
|
});
|
|
}
|
|
}
|
|
|
|
const specifiedTaskTextareas =
|
|
groupTasksViewModalRequiredInputsForm.getElementsByTagName("textarea");
|
|
|
|
if (specifiedTaskTextareas.length > 0) {
|
|
for (let i = 0; i < specifiedTaskTextareas.length; i++) {
|
|
if (specifiedTaskTextareas[i].value === "") {
|
|
canTaskContinued = false;
|
|
break;
|
|
}
|
|
|
|
taskInputs.push({
|
|
parameterName:
|
|
specifiedTaskTextareas[i].id.split(
|
|
"-"
|
|
)[6] /* Format: UUID-STEP-PARAMETER_NAME */,
|
|
value: specifiedTaskTextareas[i].value,
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!canTaskContinued) {
|
|
notificationApi["error"]({
|
|
message: t(
|
|
"groupTasks.groupTasksViewModal.notification.inputsCannotBeEmpty.message"
|
|
),
|
|
description: t(
|
|
"groupTasks.groupTasksViewModal.notification.inputsCannotBeEmpty.description"
|
|
),
|
|
});
|
|
return;
|
|
}
|
|
|
|
webSocketContext.SendSocketMessage(
|
|
SentMessagesCommands.TaskContinueTaskStep,
|
|
{
|
|
groupTaskId: currentGroupTask.Id,
|
|
category: currentGroupTask.Category,
|
|
groupId: currentGroupTask.GroupId,
|
|
step: step,
|
|
taskStepId: taskStepId,
|
|
taskInputs: taskInputs,
|
|
}
|
|
);
|
|
};
|
|
|
|
const handleUserActionTaskStep = (action, taskStepId, step) => {
|
|
webSocketContext.SendSocketMessage(
|
|
SentMessagesCommands.HandleUserActionTaskStep,
|
|
{
|
|
action: action,
|
|
groupTaskId: currentGroupTask.Id,
|
|
category: currentGroupTask.Category,
|
|
groupId: currentGroupTask.GroupId,
|
|
step: step,
|
|
taskStepId: taskStepId,
|
|
}
|
|
);
|
|
};
|
|
|
|
const ActionHandler = ({ status, taskStepId, index, taskLocked }) => {
|
|
const currentStepTask = webSocketContext.CategoryGroups.find(
|
|
(category) => category.category === currentGroupTask.Category
|
|
).groups.find((group) => group.id === currentGroupTask.GroupId).tasks[
|
|
index
|
|
];
|
|
|
|
switch (status) {
|
|
case Constants.GROUP_TASKS_STATUS.FAILED:
|
|
return (
|
|
<Button
|
|
size="small"
|
|
danger
|
|
disabled={taskLocked}
|
|
onClick={() =>
|
|
handleTaskFailedTryAgainRunTaskStep(taskStepId, index + 1)
|
|
}
|
|
>
|
|
{t("common.button.tryAgain")}
|
|
</Button>
|
|
);
|
|
case Constants.GROUP_TASKS_STATUS.INPUT_REQUIRED:
|
|
return (
|
|
<Button
|
|
size="small"
|
|
disabled={taskLocked}
|
|
onClick={() => handleTaskContinueTaskStep(taskStepId, index + 1)}
|
|
>
|
|
{t("common.button.continue")}
|
|
</Button>
|
|
);
|
|
case Constants.GROUP_TASKS_STATUS.PAUSED:
|
|
return (
|
|
<Space>
|
|
{currentStepTask.repeatPossible && (
|
|
<Button
|
|
icon={<RetweetOutlined />}
|
|
size="small"
|
|
disabled={taskLocked}
|
|
onClick={() =>
|
|
handleUserActionTaskStep(2, taskStepId, index + 1)
|
|
}
|
|
>
|
|
{t("common.button.repeat")}
|
|
</Button>
|
|
)}
|
|
|
|
{currentStepTask.undoPossible && (
|
|
<Button
|
|
icon={<UndoOutlined />}
|
|
size="small"
|
|
disabled={taskLocked}
|
|
onClick={() =>
|
|
handleUserActionTaskStep(1, taskStepId, index + 1)
|
|
}
|
|
>
|
|
{t("common.button.undo")}
|
|
</Button>
|
|
)}
|
|
|
|
<Button
|
|
icon={<CheckOutlined />}
|
|
size="small"
|
|
type={
|
|
currentStepTask.repeatPossible && currentStepTask.undoPossible
|
|
? "primary"
|
|
: "default"
|
|
}
|
|
disabled={taskLocked}
|
|
onClick={() => handleUserActionTaskStep(0, taskStepId, index + 1)}
|
|
>
|
|
{t("common.button.resume")}
|
|
</Button>
|
|
</Space>
|
|
);
|
|
case Constants.GROUP_TASKS_STATUS.UNDO_ENDED:
|
|
return (
|
|
<Button
|
|
size="small"
|
|
disabled={taskLocked}
|
|
onClick={() => handleUserActionTaskStep(2, taskStepId, index + 1)}
|
|
>
|
|
{t("common.button.resume")}
|
|
</Button>
|
|
);
|
|
|
|
default:
|
|
return <></>;
|
|
}
|
|
};
|
|
|
|
const stepsItemHandler = () => {
|
|
let groupTaskSteps = [];
|
|
let groupTasks = [];
|
|
|
|
webSocketContext.GroupTasksSteps.forEach((step) => {
|
|
if (step.GroupTasksId === paramGroupTaskId) {
|
|
groupTaskSteps.push(step);
|
|
}
|
|
});
|
|
|
|
groupTaskSteps.sort((a, b) => a.Step - b.Step);
|
|
|
|
webSocketContext.CategoryGroups.forEach((categoryGroup) => {
|
|
if (categoryGroup.category === currentGroupTask.Category) {
|
|
categoryGroup.groups.forEach((group) => {
|
|
if (currentGroupTask.GroupId === group.id) {
|
|
groupTasks = group.tasks;
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
const getStepItem = (groupTask, index) => {
|
|
return {
|
|
key: index,
|
|
title:
|
|
groupTaskSteps[index] !== undefined &&
|
|
groupTaskSteps[index].Inputs !== "" &&
|
|
groupTaskSteps[index].Status !==
|
|
Constants.GROUP_TASKS_STATUS.INPUT_REQUIRED &&
|
|
groupTaskSteps[index].Status !==
|
|
Constants.GROUP_TASKS_STATUS.UNDO_ENDED ? (
|
|
<>
|
|
{groupTask.name}{" "}
|
|
<Popover
|
|
key={groupTaskSteps[index].Id + index}
|
|
trigger="click"
|
|
placement="bottomLeft"
|
|
title={
|
|
<h2
|
|
style={{
|
|
fontWeight: "bold",
|
|
color: Constants.COLORS.SECONDARY,
|
|
}}
|
|
>
|
|
{t(
|
|
"groupTasks.groupTasksViewModal.popover.specifiedTaskInputs"
|
|
)}
|
|
</h2>
|
|
}
|
|
content={
|
|
<>
|
|
{groupTask.parameters.length > 0 &&
|
|
groupTask.parameters.map((task) => {
|
|
return (
|
|
<span key={task.parameterName}>
|
|
<span style={{ fontWeight: "bold" }}>
|
|
{task.displayName}:{" "}
|
|
</span>
|
|
<span>
|
|
{
|
|
JSON.parse(groupTaskSteps[index].Inputs).find(
|
|
(input) =>
|
|
input.parameterName === task.parameterName
|
|
)?.value
|
|
}
|
|
</span>
|
|
<br />
|
|
</span>
|
|
);
|
|
})}
|
|
</>
|
|
}
|
|
>
|
|
<InfoCircleOutlined
|
|
style={{
|
|
fontSize: "18px",
|
|
color: Constants.COLORS.ICON_INFO,
|
|
marginBottom: "4px",
|
|
verticalAlign: "middle",
|
|
}}
|
|
/>
|
|
</Popover>{" "}
|
|
{groupTaskSteps[index]?.CreatorUserId !== undefined && (
|
|
<MyAvatar
|
|
tooltip
|
|
allUsers={webSocketContext.AllUsers}
|
|
userId={groupTaskSteps[index].CreatorUserId}
|
|
/>
|
|
)}
|
|
</>
|
|
) : (
|
|
<>
|
|
{groupTask.name}{" "}
|
|
{groupTaskSteps[index]?.CreatorUserId !== undefined && (
|
|
<MyAvatar
|
|
tooltip
|
|
allUsers={webSocketContext.AllUsers}
|
|
userId={groupTaskSteps[index].CreatorUserId}
|
|
/>
|
|
)}
|
|
</>
|
|
),
|
|
status:
|
|
groupTaskSteps[index] !== undefined
|
|
? getStepItemStatus(groupTaskSteps[index].Status)
|
|
: "wait",
|
|
description:
|
|
groupTaskSteps[index] !== undefined ? (
|
|
<>
|
|
<p style={{ color: "#000" }}>
|
|
<b>ID:</b> {groupTaskSteps[index].Id}
|
|
<br />
|
|
<b>{t("groupTasks.groupTasksViewModal.startedAt")}:</b>{" "}
|
|
{FormatDatetime(groupTaskSteps[index].StartedAt)}
|
|
<br />
|
|
<b>{t("groupTasks.groupTasksViewModal.endedAt")}:</b>{" "}
|
|
{groupTaskSteps[index].EndedAt !== "0001-01-01T00:00:00Z"
|
|
? FormatDatetime(groupTaskSteps[index].EndedAt)
|
|
: Constants.TEXT_EMPTY_PLACEHOLDER}
|
|
<br />
|
|
<b>{t("groupTasks.groupTasksViewModal.duration")}:</b>{" "}
|
|
{GetDuration(
|
|
groupTaskSteps[index].StartedAt,
|
|
groupTaskSteps[index].EndedAt
|
|
)}
|
|
</p>
|
|
|
|
<Alert
|
|
style={{ width: "100%" }}
|
|
message={
|
|
<div
|
|
style={{
|
|
display: "flex",
|
|
justifyContent: "space-between",
|
|
}}
|
|
>
|
|
<b>
|
|
{getAlertMessage(groupTaskSteps[index].Status)}{" "}
|
|
{groupTaskSteps[index].LockedByUserId !== "" && (
|
|
<>
|
|
<LockOutlined />{" "}
|
|
<MyAvatar
|
|
tooltip
|
|
allUsers={webSocketContext.AllUsers}
|
|
userId={groupTaskSteps[index].LockedByUserId}
|
|
avatarWidth={24}
|
|
/>
|
|
</>
|
|
)}
|
|
</b>
|
|
<ActionHandler
|
|
status={groupTaskSteps[index].Status}
|
|
index={index}
|
|
taskLocked={groupTaskSteps[index].LockedByUserId}
|
|
taskStepId={groupTaskSteps[index].Id}
|
|
/>
|
|
</div>
|
|
}
|
|
description={
|
|
groupTaskSteps[index].Status ===
|
|
Constants.GROUP_TASKS_STATUS.INPUT_REQUIRED ? (
|
|
<div id={`${currentGroupTask.Id}-scroll-${index}`}>
|
|
<InputRequiredHandler
|
|
webSocketContext={webSocketContext}
|
|
currentGroupTask={currentGroupTask}
|
|
groupTaskParameters={groupTask.parameters}
|
|
groupTaskStepInputs={
|
|
groupTaskSteps[index] !== undefined &&
|
|
groupTaskSteps[index].Inputs !== "" &&
|
|
groupTaskSteps[index].Inputs
|
|
}
|
|
notificationApi={notificationApi}
|
|
step={index + 1}
|
|
taskLockedByUserId={
|
|
groupTaskSteps[index].LockedByUserId
|
|
}
|
|
/>
|
|
</div>
|
|
) : (
|
|
groupTaskSteps[index].Log.length > 0 && (
|
|
<div
|
|
id={`${currentGroupTask.Id}-scroll-${index}`}
|
|
style={{ width: "100%" }}
|
|
>
|
|
<GroupTaskStepLogHandler
|
|
currentGroupTaskId={currentGroupTask.Id}
|
|
log={groupTaskSteps[index].Log}
|
|
files={groupTaskSteps[index].Files}
|
|
/>
|
|
</div>
|
|
)
|
|
)
|
|
}
|
|
type={getAlertType(groupTaskSteps[index].Status)}
|
|
showIcon
|
|
/>
|
|
</>
|
|
) : (
|
|
""
|
|
),
|
|
};
|
|
};
|
|
|
|
let stepItems = [];
|
|
|
|
groupTasks.forEach((groupTask, index) =>
|
|
stepItems.push(getStepItem(groupTask, index))
|
|
);
|
|
|
|
// occurs when tasks were taken from the group task config, but at a previous time the tasks existed
|
|
if (currentGroupTask.NumberOfSteps > groupTasks.length) {
|
|
for (let i = groupTasks.length; i < currentGroupTask.NumberOfSteps; i++) {
|
|
let stepParams = [];
|
|
|
|
if (groupTaskSteps[i].Inputs !== "") {
|
|
let params = JSON.parse(groupTaskSteps[i].Inputs);
|
|
|
|
for (let i2 = 0; i2 < params.length; i2++) {
|
|
params[i2].displayName = params[i2].parameterName;
|
|
}
|
|
|
|
stepParams = params;
|
|
}
|
|
|
|
stepItems.push(getStepItem({ name: "???", parameters: stepParams }, i));
|
|
}
|
|
}
|
|
|
|
return stepItems;
|
|
};
|
|
|
|
return (
|
|
<Modal
|
|
open={isOpen}
|
|
width="70%"
|
|
onCancel={handleCancel}
|
|
maskClosable={false}
|
|
footer={
|
|
<Button onClick={handleCancel}>{t("common.button.close")}</Button>
|
|
}
|
|
>
|
|
{notificationContextHolder}
|
|
|
|
{webSocketContext.GroupTasks.map((groupTask) => {
|
|
if (groupTask.Id === paramGroupTaskId) {
|
|
let currentGroupTask = groupTask;
|
|
|
|
return (
|
|
<div key={paramGroupTaskId}>
|
|
<h1
|
|
style={{
|
|
color: Constants.COLORS.PRIMARY,
|
|
fontWeight: "bold",
|
|
marginBottom: 0,
|
|
}}
|
|
>
|
|
{currentGroupTask.GroupName}{" "}
|
|
<Popover
|
|
trigger="click"
|
|
title={
|
|
<h2
|
|
style={{
|
|
fontWeight: "bold",
|
|
color: Constants.COLORS.SECONDARY,
|
|
}}
|
|
>
|
|
{t("groupTasks.groupTasksViewModal.popover.details")}
|
|
</h2>
|
|
}
|
|
content={
|
|
<>
|
|
<p>
|
|
<span style={{ fontWeight: "bold" }}>ID:</span>{" "}
|
|
{paramGroupTaskId} <br />
|
|
<span style={{ fontWeight: "bold" }}>
|
|
{t("groupTasks.groupTasksViewModal.category")}:
|
|
</span>{" "}
|
|
{currentGroupTask.Category}
|
|
<br />
|
|
<span style={{ fontWeight: "bold" }}>
|
|
{t("groupTasks.groupTasksViewModal.startedAt")}:
|
|
</span>{" "}
|
|
{FormatDatetime(currentGroupTask.StartedAt)}
|
|
<br />
|
|
<span style={{ fontWeight: "bold" }}>
|
|
{t("groupTasks.groupTasksViewModal.endedAt")}:
|
|
</span>{" "}
|
|
{FormatDatetime(currentGroupTask.EndedAt)}
|
|
<br />
|
|
<span style={{ fontWeight: "bold" }}>
|
|
{t("groupTasks.groupTasksViewModal.duration")}:
|
|
</span>{" "}
|
|
{GetDuration(
|
|
currentGroupTask.StartedAt,
|
|
currentGroupTask.EndedAt
|
|
)}
|
|
</p>
|
|
|
|
{currentGroupTask.GlobalInputs !== "[]" ? (
|
|
<>
|
|
<h2
|
|
style={{
|
|
fontWeight: "bold",
|
|
color: Constants.COLORS.SECONDARY,
|
|
}}
|
|
>
|
|
{t(
|
|
"groupTasks.groupTasksViewModal.popover.specifiedGlobalInputs"
|
|
)}
|
|
</h2>
|
|
|
|
<span>
|
|
{JSON.parse(currentGroupTask.GlobalInputs).map(
|
|
(globalInput) => {
|
|
return (
|
|
<span
|
|
key={
|
|
globalInput.parameterName +
|
|
globalInput.value
|
|
}
|
|
>
|
|
<span style={{ fontWeight: "bold" }}>
|
|
{
|
|
webSocketContext.CategoryGroups.find(
|
|
(categoryGroup) =>
|
|
categoryGroup.category ===
|
|
currentGroupTask.Category
|
|
)
|
|
.groups.find(
|
|
(group) =>
|
|
group.id ===
|
|
currentGroupTask.GroupId
|
|
)
|
|
.globalInputs.find(
|
|
(gI) =>
|
|
gI.parameterName ===
|
|
globalInput.parameterName
|
|
).displayName
|
|
}
|
|
:{" "}
|
|
</span>
|
|
<span>
|
|
{globalInput.value !== ""
|
|
? globalInput.value
|
|
: Constants.TEXT_EMPTY_PLACEHOLDER}
|
|
</span>
|
|
<br />
|
|
</span>
|
|
);
|
|
}
|
|
)}
|
|
</span>
|
|
</>
|
|
) : null}
|
|
</>
|
|
}
|
|
>
|
|
<InfoCircleOutlined
|
|
style={{
|
|
fontSize: "18px",
|
|
color: Constants.COLORS.ICON_INFO,
|
|
marginBottom: "4px",
|
|
verticalAlign: "middle",
|
|
}}
|
|
/>
|
|
</Popover>{" "}
|
|
<MyAvatar
|
|
tooltip
|
|
allUsers={webSocketContext.AllUsers}
|
|
userId={currentGroupTask.CreatorUserId}
|
|
/>
|
|
<br />
|
|
</h1>
|
|
<div style={{ marginBottom: 16 }}>
|
|
<span>{currentGroupTask.Description}</span>
|
|
</div>
|
|
|
|
<Steps direction="vertical" items={stepsItemHandler()} />
|
|
</div>
|
|
);
|
|
}
|
|
return "";
|
|
})}
|
|
</Modal>
|
|
);
|
|
}
|
|
|
|
function InputRequiredHandler({
|
|
webSocketContext,
|
|
currentGroupTask,
|
|
groupTaskParameters,
|
|
groupTaskStepInputs,
|
|
notificationApi,
|
|
step,
|
|
taskLockedByUserId,
|
|
}) {
|
|
const { t } = useTranslation();
|
|
const [inputFields, setInputFields] = useState({});
|
|
|
|
const globalInputs = JSON.parse(currentGroupTask.GlobalInputs);
|
|
const stepInputs = JSON.parse(groupTaskStepInputs);
|
|
|
|
const getDefaultValue = (groupTaskParameter) => {
|
|
if (stepInputs !== false) {
|
|
const stepInput = stepInputs.find(
|
|
(stepInput) =>
|
|
stepInput.parameterName === groupTaskParameter.parameterName
|
|
)?.value;
|
|
|
|
if (stepInput) {
|
|
return stepInput;
|
|
}
|
|
}
|
|
|
|
if (globalInputs === undefined || !groupTaskParameter.global) return null;
|
|
|
|
const globalInputValue = globalInputs.find(
|
|
(globalInput) =>
|
|
globalInput.parameterName === groupTaskParameter.parameterName
|
|
);
|
|
|
|
return globalInputValue !== undefined ? globalInputValue.value : "";
|
|
};
|
|
|
|
const getLabel = (displayName, groupTaskParameter) => {
|
|
return groupTaskParameter.global ? (
|
|
<>
|
|
{displayName}
|
|
<Tag style={{ marginLeft: 6 }} color="purple">
|
|
{t("groupTasks.tag.global")}
|
|
</Tag>
|
|
</>
|
|
) : (
|
|
displayName
|
|
);
|
|
};
|
|
|
|
let lastChange = useRef();
|
|
let typingTimer = useRef();
|
|
|
|
const sendTypingMessage = (
|
|
currentGroupTaskId,
|
|
groupTaskParameterName,
|
|
inputValue
|
|
) => {
|
|
webSocketContext.SendSocketMessage(SentMessagesCommands.TaskLocking, {
|
|
element: `${currentGroupTaskId}-${step}-${groupTaskParameterName}`,
|
|
lockedByUserId: getUserId(),
|
|
groupTaskId: currentGroupTaskId,
|
|
parameterName: groupTaskParameterName,
|
|
value: inputValue,
|
|
step: step,
|
|
rememberId: GroupTasksStepsLockedAndUserUpdateInputValueRememberId,
|
|
});
|
|
};
|
|
|
|
const onInputChange = (
|
|
inputValue,
|
|
currentGroupTaskId,
|
|
groupTaskParameterName
|
|
) => {
|
|
setInputFields((fields) => {
|
|
return ({ ...fields }[groupTaskParameterName] =
|
|
inputValue === null ? "" : inputValue);
|
|
});
|
|
|
|
if (taskLockedByUserId !== "") return;
|
|
|
|
if (Date.now() >= lastChange.current || lastChange.current === undefined) {
|
|
lastChange.current = Date.now() + 1000;
|
|
|
|
sendTypingMessage(currentGroupTaskId, groupTaskParameterName, inputValue);
|
|
} else {
|
|
clearTimeout(typingTimer.current);
|
|
}
|
|
|
|
typingTimer.current = setTimeout(
|
|
() =>
|
|
sendTypingMessage(
|
|
currentGroupTaskId,
|
|
groupTaskParameterName,
|
|
inputValue
|
|
),
|
|
1000
|
|
);
|
|
};
|
|
|
|
return (
|
|
<Form layout="vertical" id="groupTasksViewModalRequiredInputsForm">
|
|
{groupTaskParameters.map((groupTaskParameter) => {
|
|
switch (groupTaskParameter.type) {
|
|
case "text":
|
|
return (
|
|
<Form.Item
|
|
key={"fitem-" + groupTaskParameter.parameterName}
|
|
label={getLabel(
|
|
groupTaskParameter.displayName,
|
|
groupTaskParameter
|
|
)}
|
|
required
|
|
>
|
|
<Input
|
|
id={`${currentGroupTask.Id}-${step}-${groupTaskParameter.parameterName}`}
|
|
defaultValue={getDefaultValue(groupTaskParameter)}
|
|
disabled={taskLockedByUserId !== ""}
|
|
onChange={(e) =>
|
|
onInputChange(
|
|
e.target.value,
|
|
currentGroupTask.Id,
|
|
groupTaskParameter.parameterName
|
|
)
|
|
}
|
|
value={inputFields[groupTaskParameter.parameterName]}
|
|
/>
|
|
</Form.Item>
|
|
);
|
|
case "number":
|
|
return (
|
|
<Form.Item
|
|
key={"fitem-" + groupTaskParameter.parameterName}
|
|
label={getLabel(
|
|
groupTaskParameter.displayName,
|
|
groupTaskParameter
|
|
)}
|
|
required
|
|
>
|
|
<InputNumber
|
|
id={`${currentGroupTask.Id}-${step}-${groupTaskParameter.parameterName}`}
|
|
style={{ width: "100%" }}
|
|
defaultValue={getDefaultValue(groupTaskParameter)}
|
|
disabled={taskLockedByUserId !== ""}
|
|
max={Number.MAX_SAFE_INTEGER}
|
|
onChange={(e) =>
|
|
onInputChange(
|
|
e,
|
|
currentGroupTask.Id,
|
|
groupTaskParameter.parameterName
|
|
)
|
|
}
|
|
value={inputFields[groupTaskParameter.parameterName]}
|
|
/>
|
|
</Form.Item>
|
|
);
|
|
case "textarea":
|
|
return (
|
|
<Form.Item
|
|
key={"fitem-" + groupTaskParameter.parameterName}
|
|
label={getLabel(
|
|
groupTaskParameter.displayName,
|
|
groupTaskParameter
|
|
)}
|
|
required
|
|
>
|
|
<TextArea
|
|
id={`${currentGroupTask.Id}-${step}-${groupTaskParameter.parameterName}`}
|
|
defaultValue={getDefaultValue(groupTaskParameter)}
|
|
disabled={taskLockedByUserId !== ""}
|
|
onChange={(e) =>
|
|
onInputChange(
|
|
e.target.value,
|
|
currentGroupTask.Id,
|
|
groupTaskParameter.parameterName
|
|
)
|
|
}
|
|
value={inputFields[groupTaskParameter.parameterName]}
|
|
/>
|
|
</Form.Item>
|
|
);
|
|
default:
|
|
notificationApi["error"]({
|
|
message: t(
|
|
"groupTasks.groupTasksViewModal.notification.groupTaskParameterNotImplemented.message",
|
|
{ groupTaskParameterType: groupTaskParameter.type }
|
|
),
|
|
description: t(
|
|
"groupTasks.groupTasksViewModal.notification.groupTaskParameterNotImplemented.description",
|
|
{
|
|
groupTaskParameterDisplayName: groupTaskParameter.displayName,
|
|
}
|
|
),
|
|
});
|
|
return (
|
|
<p>
|
|
Type <b>{groupTaskParameter.type}</b> not implemented. Was
|
|
specified in: <b>{groupTaskParameter.displayName}</b>
|
|
</p>
|
|
);
|
|
}
|
|
})}
|
|
</Form>
|
|
);
|
|
}
|
|
|
|
const MyStlViewer = ({ url }) => {
|
|
const memoizedCode = useMemo(() => {
|
|
return (
|
|
<StlViewer
|
|
style={{ top: 0, left: 0, height: "100%" }}
|
|
orbitControls
|
|
modelProps={{
|
|
color: Constants.COLORS.SECONDARY,
|
|
scale: 1,
|
|
positionX: 200,
|
|
positionY: 200,
|
|
}}
|
|
shadows
|
|
floorProps={{ gridWidth: 400 }}
|
|
url={url}
|
|
onError={(err) => console.error(err)}
|
|
onFinishLoading={(ev) => console.log(ev)}
|
|
/>
|
|
);
|
|
}, [url]);
|
|
|
|
return memoizedCode;
|
|
};
|
|
|
|
function GroupTaskStepLogHandler({ currentGroupTaskId, log, files }) {
|
|
const getDownloadUrl = (file) => {
|
|
return `${Constants.STATIC_CONTENT_ADDRESS}grouptasks/${currentGroupTaskId}/${file.SystemFileName}`;
|
|
};
|
|
|
|
const getTag = (file) => {
|
|
return (
|
|
<a
|
|
key={"a" + file.SystemFileName}
|
|
href={getDownloadUrl(file)}
|
|
download="test"
|
|
target="_blank"
|
|
rel="noreferrer"
|
|
>
|
|
<Tag
|
|
icon={<FileImageOutlined />}
|
|
color="processing"
|
|
style={{ marginTop: 6 }}
|
|
>
|
|
{file.OriginalFileName}
|
|
</Tag>
|
|
</a>
|
|
);
|
|
};
|
|
|
|
const fileContent = (files) => {
|
|
const nonImageFiles = [];
|
|
const imageFiles = [];
|
|
|
|
for (const file of files) {
|
|
const fileExtension = file.SystemFileName.split(".")[1];
|
|
|
|
if (
|
|
fileExtension === "png" ||
|
|
fileExtension === "jpeg" ||
|
|
fileExtension === "jpg" ||
|
|
fileExtension === "webp"
|
|
) {
|
|
imageFiles.push(file);
|
|
} else {
|
|
nonImageFiles.push(file);
|
|
}
|
|
}
|
|
|
|
const elements = [];
|
|
|
|
for (const nonImageFile of nonImageFiles) {
|
|
const fileExtension = nonImageFile.SystemFileName.split(".")[1];
|
|
|
|
if (fileExtension === "stl") {
|
|
elements.push(
|
|
<div key={"tag" + nonImageFile.SystemFileName}>
|
|
<div style={{ height: 300, backgroundColor: "#fff" }}>
|
|
<MyStlViewer url={getDownloadUrl(nonImageFile)} />
|
|
</div>
|
|
{getTag(nonImageFile)}
|
|
</div>
|
|
);
|
|
} else {
|
|
elements.push(getTag(nonImageFile));
|
|
}
|
|
}
|
|
|
|
const imageFileElements = [];
|
|
|
|
for (const imageFile of imageFiles) {
|
|
imageFileElements.push(
|
|
<Col
|
|
key={imageFile.SystemFileName}
|
|
style={{ marginTop: 6 }}
|
|
className="gutter-row"
|
|
span={6}
|
|
>
|
|
<Image width={"100%"} src={getDownloadUrl(imageFile)} />
|
|
{getTag(imageFile)}
|
|
</Col>
|
|
);
|
|
}
|
|
|
|
if (imageFileElements.length > 0) {
|
|
elements.push(
|
|
<Row key={"imgrow"} gutter={16}>
|
|
{imageFileElements}
|
|
</Row>
|
|
);
|
|
}
|
|
|
|
return elements;
|
|
};
|
|
|
|
return (
|
|
<span style={{ whiteSpace: "pre-line" }}>
|
|
{log}
|
|
|
|
{files !== "" && files !== " " && fileContent(JSON.parse(files))}
|
|
</span>
|
|
);
|
|
}
|