diff --git a/src/modules/manager/task/assignments/AssignmentComplete.tsx b/src/modules/manager/task/assignments/AssignmentComplete.tsx index 9c53d08..9ab8d0f 100644 --- a/src/modules/manager/task/assignments/AssignmentComplete.tsx +++ b/src/modules/manager/task/assignments/AssignmentComplete.tsx @@ -11,11 +11,14 @@ import LoadingPanel from "../../../../components/common/LoadingPanel"; import AssigneePanel from "../../tasks/components/AssigneePanel"; import TaskTypeAndNameDisplayPanel from "../../workflowTemplates/components/TaskTypeAndNameDisplayPanel"; import Button, { ButtonType } from "../../../../components/common/Button"; +import TaskProcessor from "./components/TaskProcessor"; import "../../../../Sass/_assignmentComplete.scss"; const AssignmentComplete: React.FC = () => { const { assignmentId } = useParams<{ assignmentId: string }>(); const { t } = useTranslation(Namespaces.Common); + const [isTaskValid, setIsTaskValid] = useState(false); + const [taskData, setTaskData] = useState>({}); const [assignmentDetails, setAssignmentDetails] = useState(); @@ -31,6 +34,9 @@ const AssignmentComplete: React.FC = () => { ); if (assignmentDetails) { setAssignmentDetails(assignmentDetails); + setTaskData({ + comments: assignmentDetails.assignment.comments ?? "", + }); } } catch (ex: any) { toast.error(ex.message); @@ -63,16 +69,28 @@ const AssignmentComplete: React.FC = () => {

{assignmentDetails.activity.name}

- +
-
{taskDefinition.config.description}
- -
Comments
-
{assignmentDetails.assignment.comments}
+
diff --git a/src/modules/manager/task/assignments/components/TaskProcessor.tsx b/src/modules/manager/task/assignments/components/TaskProcessor.tsx new file mode 100644 index 0000000..ca563b1 --- /dev/null +++ b/src/modules/manager/task/assignments/components/TaskProcessor.tsx @@ -0,0 +1,51 @@ +import React, { useEffect } from "react"; +import { TaskDefinition } from "../../../workflowTemplates/services/WorkflowTemplateService"; +import { GetAssignmentForCompletion } from "../services/assignmentCompleteService"; +import BasicTask from "./tasksProcessor/BasicTask"; + +type TaskProcessorProps = { + assignmentDetails: GetAssignmentForCompletion; + taskDefinition: TaskDefinition; + taskData: Record; + onTaskDataChange?: (data: Record) => void; + onValidationChange?: (isValid: boolean) => void; +}; + +type TaskProcessorComponent = React.ComponentType; + +const taskProcessorRegistry: Record = { + "e_suite.Workflow.Core.Tasks.BasicTask": BasicTask, +}; + +const TaskProcessor: React.FC = ({ + assignmentDetails, + taskDefinition, + taskData, + onTaskDataChange, + onValidationChange, +}) => { + const processorType = assignmentDetails.task.taskType; + const Processor = taskProcessorRegistry[processorType]; + + useEffect(() => { + if (!Processor) { + onValidationChange?.(false); + } + }, [Processor, onValidationChange]); + + if (!Processor) { + return
Unsupported task type: {processorType}
; + } + + return ( + + ); +}; + +export default TaskProcessor; diff --git a/src/modules/manager/task/assignments/components/tasksProcessor/BasicTask.tsx b/src/modules/manager/task/assignments/components/tasksProcessor/BasicTask.tsx new file mode 100644 index 0000000..2fbe5fa --- /dev/null +++ b/src/modules/manager/task/assignments/components/tasksProcessor/BasicTask.tsx @@ -0,0 +1,114 @@ +import React, { useCallback, useEffect, useState } from "react"; +import Joi from "joi"; +import { InputType } from "../../../../../../components/common/Input"; +import { renderInput } from "../../../../../../components/common/formHelpers"; +import { + FormData, + FormError, +} from "../../../../../../components/common/useForm"; +import { TaskDefinition } from "../../../../workflowTemplates/services/WorkflowTemplateService"; +import { GetAssignmentForCompletion } from "../../services/assignmentCompleteService"; + +type BasicTaskProps = { + assignmentDetails: GetAssignmentForCompletion; + taskDefinition: TaskDefinition; + taskData: Record; + onTaskDataChange?: (data: Record) => void; + onValidationChange?: (isValid: boolean) => void; +}; + +const BasicTask: React.FC = ({ + assignmentDetails, + taskDefinition, + taskData, + onTaskDataChange, + onValidationChange, +}) => { + const [errors, setErrors] = useState({}); + + const validateData = useCallback( + (data: Record) => { + const result = Joi.object({ + comments: Joi.string().required().allow("").label("Comments"), + }).validate(data, { abortEarly: false }); + + const nextErrors: FormError = {}; + + if (result.error) { + for (const detail of result.error.details) { + const key = String(detail.path[0] ?? "_general"); + nextErrors[key] = detail.message; + } + } + + setErrors(nextErrors); + + const hasValidationErrors = Object.keys(nextErrors).some( + (key) => !key.startsWith("_"), + ); + + onValidationChange?.(!hasValidationErrors); + }, + [onValidationChange], + ); + + useEffect(() => { + validateData(taskData); + }, [taskData, validateData]); + + useEffect(() => { + if (taskData.comments === undefined) { + onTaskDataChange?.({ + ...taskData, + comments: assignmentDetails.assignment.comments ?? "", + }); + } + }, [assignmentDetails.assignment.comments, onTaskDataChange, taskData]); + + const commentsValue = String(taskData.comments ?? ""); + + const description = + typeof taskDefinition.config === "object" && + taskDefinition.config !== null && + "description" in taskDefinition.config + ? String( + (taskDefinition.config as Record).description ?? "", + ) + : ""; + + const handleCommentsChange = ( + e: React.ChangeEvent, + ) => { + if (e.target instanceof HTMLTextAreaElement) { + onTaskDataChange?.({ + ...taskData, + comments: e.target.value, + }); + } + }; + + return ( +
+
{description}
+ + {renderInput( + "comments", + "Comments", + { comments: commentsValue } as FormData, + errors, + InputType.textarea, + false, + "", + "", + 0, + true, + undefined, + handleCommentsChange as ( + e: React.ChangeEvent, + ) => void, + )} +
+ ); +}; + +export default BasicTask;