The task processor is now wired up and ready to complete an assignment.

This commit is contained in:
Colin Dawson 2026-03-18 21:14:18 +00:00
parent ea95b0747d
commit 61465130a6
3 changed files with 188 additions and 5 deletions

View File

@ -11,11 +11,14 @@ import LoadingPanel from "../../../../components/common/LoadingPanel";
import AssigneePanel from "../../tasks/components/AssigneePanel"; import AssigneePanel from "../../tasks/components/AssigneePanel";
import TaskTypeAndNameDisplayPanel from "../../workflowTemplates/components/TaskTypeAndNameDisplayPanel"; import TaskTypeAndNameDisplayPanel from "../../workflowTemplates/components/TaskTypeAndNameDisplayPanel";
import Button, { ButtonType } from "../../../../components/common/Button"; import Button, { ButtonType } from "../../../../components/common/Button";
import TaskProcessor from "./components/TaskProcessor";
import "../../../../Sass/_assignmentComplete.scss"; import "../../../../Sass/_assignmentComplete.scss";
const AssignmentComplete: React.FC = () => { const AssignmentComplete: React.FC = () => {
const { assignmentId } = useParams<{ assignmentId: string }>(); const { assignmentId } = useParams<{ assignmentId: string }>();
const { t } = useTranslation(Namespaces.Common); const { t } = useTranslation(Namespaces.Common);
const [isTaskValid, setIsTaskValid] = useState<boolean>(false);
const [taskData, setTaskData] = useState<Record<string, unknown>>({});
const [assignmentDetails, setAssignmentDetails] = const [assignmentDetails, setAssignmentDetails] =
useState<GetAssignmentForCompletion>(); useState<GetAssignmentForCompletion>();
@ -31,6 +34,9 @@ const AssignmentComplete: React.FC = () => {
); );
if (assignmentDetails) { if (assignmentDetails) {
setAssignmentDetails(assignmentDetails); setAssignmentDetails(assignmentDetails);
setTaskData({
comments: assignmentDetails.assignment.comments ?? "",
});
} }
} catch (ex: any) { } catch (ex: any) {
toast.error(ex.message); toast.error(ex.message);
@ -63,16 +69,28 @@ const AssignmentComplete: React.FC = () => {
<h2>{assignmentDetails.activity.name}</h2> <h2>{assignmentDetails.activity.name}</h2>
</div> </div>
<div className="actions"> <div className="actions">
<Button buttonType={ButtonType.primary}>Complete</Button> <Button
buttonType={ButtonType.primary}
disabled={!isTaskValid}
onClick={() => {
console.log("Task data to submit:", taskData);
toast.info("Complete action not implemented yet");
}}
>
Complete
</Button>
</div> </div>
</div> </div>
<div className="mainContent"> <div className="mainContent">
<div className="workarea"> <div className="workarea">
<div className="workareaInner"> <div className="workareaInner">
<div>{taskDefinition.config.description}</div> <TaskProcessor
assignmentDetails={assignmentDetails}
<div>Comments</div> taskDefinition={taskDefinition}
<div>{assignmentDetails.assignment.comments}</div> taskData={taskData}
onTaskDataChange={setTaskData}
onValidationChange={setIsTaskValid}
/>
</div> </div>
</div> </div>

View File

@ -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<string, unknown>;
onTaskDataChange?: (data: Record<string, unknown>) => void;
onValidationChange?: (isValid: boolean) => void;
};
type TaskProcessorComponent = React.ComponentType<TaskProcessorProps>;
const taskProcessorRegistry: Record<string, TaskProcessorComponent> = {
"e_suite.Workflow.Core.Tasks.BasicTask": BasicTask,
};
const TaskProcessor: React.FC<TaskProcessorProps> = ({
assignmentDetails,
taskDefinition,
taskData,
onTaskDataChange,
onValidationChange,
}) => {
const processorType = assignmentDetails.task.taskType;
const Processor = taskProcessorRegistry[processorType];
useEffect(() => {
if (!Processor) {
onValidationChange?.(false);
}
}, [Processor, onValidationChange]);
if (!Processor) {
return <div>Unsupported task type: {processorType}</div>;
}
return (
<Processor
assignmentDetails={assignmentDetails}
taskDefinition={taskDefinition}
taskData={taskData}
onTaskDataChange={onTaskDataChange}
onValidationChange={onValidationChange}
/>
);
};
export default TaskProcessor;

View File

@ -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<string, unknown>;
onTaskDataChange?: (data: Record<string, unknown>) => void;
onValidationChange?: (isValid: boolean) => void;
};
const BasicTask: React.FC<BasicTaskProps> = ({
assignmentDetails,
taskDefinition,
taskData,
onTaskDataChange,
onValidationChange,
}) => {
const [errors, setErrors] = useState<FormError>({});
const validateData = useCallback(
(data: Record<string, unknown>) => {
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<string, unknown>).description ?? "",
)
: "";
const handleCommentsChange = (
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>,
) => {
if (e.target instanceof HTMLTextAreaElement) {
onTaskDataChange?.({
...taskData,
comments: e.target.value,
});
}
};
return (
<div className="workareaInner">
<div>{description}</div>
{renderInput(
"comments",
"Comments",
{ comments: commentsValue } as FormData,
errors,
InputType.textarea,
false,
"",
"",
0,
true,
undefined,
handleCommentsChange as (
e: React.ChangeEvent<HTMLInputElement>,
) => void,
)}
</div>
);
};
export default BasicTask;