From dca30cfca0691eade3542c574cc4614a1539db54 Mon Sep 17 00:00:00 2001 From: Colin Dawson Date: Wed, 25 Feb 2026 20:14:30 +0000 Subject: [PATCH] Approval steps can now be added to approval tasks --- public/locales/en/common.json | 3 +- public/locales/en/taskTypes.json | 3 +- .../components/AddTaskButton.tsx | 21 +- ...AssigneesOfIApprovalTaskAssigneeEditor.tsx | 248 ++++++++++++++++++ .../OutcomeOfApprovalVerdictRegistryEntry.tsx | 2 +- .../CapabilityEditors/StageEditor.tsx | 124 +++++++++ .../StageOfGeneralTaskEditor.tsx | 108 -------- .../components/TasksEditor.tsx | 38 ++- .../workflowTemplates/components/TasksTab.tsx | 50 +++- .../components/capabilityEditorRegistry.ts | 10 +- .../components/useCapabilityDefaults.tsx | 7 +- 11 files changed, 480 insertions(+), 134 deletions(-) create mode 100644 src/modules/manager/workflowTemplates/components/CapabilityEditors/AssigneesOfIApprovalTaskAssigneeEditor.tsx create mode 100644 src/modules/manager/workflowTemplates/components/CapabilityEditors/StageEditor.tsx delete mode 100644 src/modules/manager/workflowTemplates/components/CapabilityEditors/StageOfGeneralTaskEditor.tsx diff --git a/public/locales/en/common.json b/public/locales/en/common.json index c2b246f..c15fe15 100644 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -4,6 +4,7 @@ "Active": "Active", "ActivityNameTemplate": "Activity Name Template", "Add": "Add", + "AddWithValue": "Add {{value}}", "AddDomain": "Add Domain", "AddTask": "Add Task", "AddUser": "Add User", @@ -210,4 +211,4 @@ "e-print": "e-print", "e-suite": "e-suite", "e-suiteLogo": "e-suite logo" -} \ No newline at end of file +} diff --git a/public/locales/en/taskTypes.json b/public/locales/en/taskTypes.json index 067cb79..bf0bb3f 100644 --- a/public/locales/en/taskTypes.json +++ b/public/locales/en/taskTypes.json @@ -1,6 +1,7 @@ { "AdhocApprovalTask": "Adhoc Approval", "ApprovalTask": "Approval", + "ApprovalStep": "Approval Step", "AssetUploadTask": "Asset Upload", "BasicTask": "Basic", "ContentCollationTask": "Content Collation", @@ -12,4 +13,4 @@ "StageTask": "Stage", "VisualBriefReviewTask": "Visual Brief Review", "VisualBriefUploadTask": "Visual Brief Upload" -} \ No newline at end of file +} diff --git a/src/modules/manager/workflowTemplates/components/AddTaskButton.tsx b/src/modules/manager/workflowTemplates/components/AddTaskButton.tsx index 4286ea1..b493f86 100644 --- a/src/modules/manager/workflowTemplates/components/AddTaskButton.tsx +++ b/src/modules/manager/workflowTemplates/components/AddTaskButton.tsx @@ -2,6 +2,7 @@ import React from "react"; import { TaskMetadata } from "../services/WorkflowTemplateService"; import { useTranslation } from "react-i18next"; import { Namespaces } from "../../../../i18n/i18n"; +import Button, { ButtonType } from "../../../../components/common/Button"; interface AddTaskButtonProps { tasksMetadata: TaskMetadata[]; @@ -22,11 +23,27 @@ const AddTaskButton: React.FC = ({ setOpen(next); }; + if (tasksMetadata.length === 1) { + return ( + + ); + } + return (
- + {open && (
diff --git a/src/modules/manager/workflowTemplates/components/CapabilityEditors/AssigneesOfIApprovalTaskAssigneeEditor.tsx b/src/modules/manager/workflowTemplates/components/CapabilityEditors/AssigneesOfIApprovalTaskAssigneeEditor.tsx new file mode 100644 index 0000000..fb9e73d --- /dev/null +++ b/src/modules/manager/workflowTemplates/components/CapabilityEditors/AssigneesOfIApprovalTaskAssigneeEditor.tsx @@ -0,0 +1,248 @@ +import React from "react"; +import UserPicker from "../../../../../components/pickers/UserPicker"; +import { + GeneralIdRef, + MakeGeneralIdRef, +} from "../../../../../utils/GeneralIdRef"; +import { TaskDefinition } from "../../services/WorkflowTemplateService"; +import { + CapabilityEditorProps, + capabilityEditorRegistryEntry, + defaultsContext, +} from "../useCapabilityDefaults"; +import Button, { ButtonType } from "../../../../../components/common/Button"; +import ValidationErrorIcon from "../../../../../components/validationErrorIcon"; +import ErrorBlock from "../../../../../components/common/ErrorBlock"; +import RolePicker from "../../../../../components/pickers/RolePicker"; +import { getCurrentUser } from "../../../../frame/services/authenticationService"; +import RaciPicker from "../../../../../components/pickers/RaciPicker"; +import VerdictPicker from "../../../../../components/pickers/VerdictPicker"; +import { renderInput } from "../../../../../components/common/formHelpers"; +import { InputType } from "../../../../../components/common/Input"; +import { error } from "console"; + +// --------------------------- +// Domain Interfaces +// --------------------------- + +export interface IApprovalTaskAssignee { + role?: GeneralIdRef | null; + contact?: GeneralIdRef | null; + raci: string; // Raci enum as string + allowNoVerdict: boolean; + bypassable: boolean; +} + +export interface IApprovalTaskAssigneesCapability { + assignees: IApprovalTaskAssignee[]; +} + +// --------------------------- +// Editor Component +// --------------------------- + +export const AssigneesOfIApprovalTaskAssigneeEditor: React.FC< + CapabilityEditorProps +> = (props) => { + const { task, onChange, fieldErrors } = props; + const assignees = (task.config.assignees ?? []) as IApprovalTaskAssignee[]; + + function updateAssignee(index: number, updated: IApprovalTaskAssignee) { + const clone = structuredClone(task); + const list = clone.config.assignees ?? []; + list[index] = updated; + clone.config.assignees = list; + onChange(clone); + } + + function addAssignee() { + const clone = structuredClone(task); + const list = clone.config.assignees ?? []; + list.push({ + role: null, + contact: null, + raci: "Responsible", + allowNoVerdict: false, + bypassable: false, + }); + clone.config.assignees = list; + onChange(clone); + } + + function removeAssignee(index: number) { + const clone = structuredClone(task); + const list = clone.config.assignees ?? []; + clone.config.assignees = list.filter((_, i) => i !== index); + onChange(clone); + } + + const guid = task.config.guid as string; + + const domain = MakeGeneralIdRef(getCurrentUser()?.domainid); + + return ( +
+ Select assigness (you can select either a role or a contact) + + + + + + + + + + + + + + {assignees.map((assignee, index) => ( + + + + + + + + + + ))} + +
RoleContactRACIAllow No VerdictBypassable + +
+ + + + updateAssignee(index, { ...assignee, role: val }) + } + /> + + + updateAssignee(index, { ...assignee, contact: val }) + } + /> + + + updateAssignee(index, { ...assignee, raci: val }) + } + /> + allowNoVerdict goes here.Bypass goes here. + +
+ + Object.keys(fieldErrors ?? {}).some( + (key) => key === `${guid}.assignees`, + ), + ) + .join("; ")} + /> +
+ ); +}; + +// --------------------------- +// Validation +// --------------------------- + +const runValidation = ( + task: TaskDefinition, + tasks: TaskDefinition[], +): Promise> => { + const errors: Record = {}; + const guid = task.config.guid as string; + const assignees = task.config.assignees as ITaskAssignee[] | undefined; + + if (!assignees || assignees.length === 0) { + errors[`${guid}.assignees`] = "At least one assignee is required."; + return Promise.resolve(errors); + } + + assignees.forEach((a, i) => { + const noContactSelected = !a.contact || a.contact?.id === BigInt(0); + const noRoleSelected = !a.role || a.role?.id === BigInt(0); + + if (!noContactSelected && !noRoleSelected) { + errors[`${guid}.assignees[${i}].contact`] = + "Cannot select both a contact and a role."; + errors[`${guid}.assignees[${i}].role`] = + "Cannot select both a contact and a role."; + } else { + if (!(!noContactSelected || !noRoleSelected)) { + if (noContactSelected) { + errors[`${guid}.assignees[${i}].contact`] = + "A contact must be selected."; + } + + if (noRoleSelected) { + errors[`${guid}.assignees[${i}].role`] = "A role must be selected."; + } + } + } + + if (!a.raci) { + errors[`${guid}.assignees[${i}].raci`] = "RACI is required."; + } + }); + + return errors; +}; + +// --------------------------- +// Defaults Assignment +// --------------------------- + +export function defaultsAssignment( + task: TaskDefinition, + tasks: TaskDefinition[], + ctx: defaultsContext, +) { + task.config.assignees = [{ raci: "Responsible" } as ITaskAssignee]; + //task.config.assignees = []; +} + +// --------------------------- +// Registry Entry +// --------------------------- + +export const assigneesOfIApprovalTaskAssigneeRegistryEntry: capabilityEditorRegistryEntry = + { + Editor: AssigneesOfIApprovalTaskAssigneeEditor, + DefaultsAssignment: defaultsAssignment, + ValidationRunner: runValidation, + }; diff --git a/src/modules/manager/workflowTemplates/components/CapabilityEditors/OutcomeOfApprovalVerdictRegistryEntry.tsx b/src/modules/manager/workflowTemplates/components/CapabilityEditors/OutcomeOfApprovalVerdictRegistryEntry.tsx index 00afd7b..0b248f7 100644 --- a/src/modules/manager/workflowTemplates/components/CapabilityEditors/OutcomeOfApprovalVerdictRegistryEntry.tsx +++ b/src/modules/manager/workflowTemplates/components/CapabilityEditors/OutcomeOfApprovalVerdictRegistryEntry.tsx @@ -199,7 +199,7 @@ export function defaultsAssignment( ctx: defaultsContext, ) { task.config.outcomeActions = [] as IOutcomeAction[]; - task.config.overrideDefaultTaskProgression = true; + task.config.overrideDefaultTaskProgression = false; } export const outcomeOfApprovalVerdictRegistryEntry: capabilityEditorRegistryEntry = diff --git a/src/modules/manager/workflowTemplates/components/CapabilityEditors/StageEditor.tsx b/src/modules/manager/workflowTemplates/components/CapabilityEditors/StageEditor.tsx new file mode 100644 index 0000000..fb76553 --- /dev/null +++ b/src/modules/manager/workflowTemplates/components/CapabilityEditors/StageEditor.tsx @@ -0,0 +1,124 @@ +import React, { useEffect } from "react"; +import templateVersionsService, { + TaskDefinition, + TaskMetadata, +} from "../../services/WorkflowTemplateService"; +import AddTaskButton from "../AddTaskButton"; +import { + CapabilityEditorProps, + capabilityEditorRegistryEntry, + defaultsContext, + useCapabilityDefaults, + validateTask, +} from "../useCapabilityDefaults"; + +function createStageEditor(taskType: string): React.FC { + return (props) => { + const { + task, + onChange, + onValidate, + onValidateTask, + onTaskAdded, + fieldErrors, + } = props; + + const [tasksMetadata, setTasksMetadata] = React.useState( + [], + ); + + useEffect(() => { + const fetchTaskMetadata = async () => { + const meta = await templateVersionsService.getTaskMetadata(taskType); + setTasksMetadata(meta); + }; + + fetchTaskMetadata(); + }, []); + + const runDefaults = useCapabilityDefaults(tasksMetadata); + + const handleAddTask = async (selectedType: TaskMetadata) => { + const newTask: TaskDefinition = { + type: selectedType.taskType, + + config: { + guid: crypto.randomUUID(), + }, + }; + + var childTasks = task.config.tasks as TaskDefinition[]; //Type assertion to satisfy the compiler, we know this will be the correct type. + + //Assign the default values for the task here. + selectedType.capabilities.forEach((capability) => { + runDefaults(capability, newTask, childTasks); + }); + + const updatedTasks = [...childTasks, newTask]; + const errors = await validateTask(newTask, updatedTasks, tasksMetadata); + + const isValid = Object.keys(errors).length === 0; + + // Mark the child task as valid/invalid + onValidateTask?.(newTask.config.guid as string, isValid); + + task.config.tasks = updatedTasks; + + onChange(task); + onTaskAdded?.(newTask.config.guid as string); + }; + + return ( + <> + + + ); + }; +} + +function createValidationRunner( + taskType: string, +): ( + task: TaskDefinition, + tasks: TaskDefinition[], +) => Promise> { + return async (task: TaskDefinition, tasks: TaskDefinition[]) => { + const errors: Record = {}; + + const meta = await templateVersionsService.getTaskMetadata(taskType); + + if (task.config.tasks) { + for (const childTask of task.config.tasks as TaskDefinition[]) { + const childErrors = await validateTask( + childTask, + task.config.tasks as TaskDefinition[], + meta, + ); + + if (childErrors && Object.keys(childErrors).length > 0) { + errors[`${task.config.guid}.tasks`] = + `One or more child tasks are invalid.`; + } + } + } + return errors; + }; +} + +function defaultsAssignment( + task: TaskDefinition, + tasks: TaskDefinition[], + ctx: defaultsContext, +) { + task.config.tasks = [] as TaskDefinition[]; +} + +export function createStageEditorRegistryEntry( + taskType: string, +): capabilityEditorRegistryEntry { + return { + Editor: createStageEditor(taskType), + DefaultsAssignment: defaultsAssignment, + ValidationRunner: createValidationRunner(taskType), + }; +} diff --git a/src/modules/manager/workflowTemplates/components/CapabilityEditors/StageOfGeneralTaskEditor.tsx b/src/modules/manager/workflowTemplates/components/CapabilityEditors/StageOfGeneralTaskEditor.tsx deleted file mode 100644 index 4ab4605..0000000 --- a/src/modules/manager/workflowTemplates/components/CapabilityEditors/StageOfGeneralTaskEditor.tsx +++ /dev/null @@ -1,108 +0,0 @@ -import React, { useEffect } from "react"; -import templateVersionsService, { - TaskDefinition, - TaskMetadata, -} from "../../services/WorkflowTemplateService"; -import AddTaskButton from "../AddTaskButton"; -import { - CapabilityEditorProps, - capabilityEditorRegistryEntry, - defaultsContext, - TaskValidationResult, - useCapabilityDefaults, - validateTask, -} from "../useCapabilityDefaults"; - -export const StageOfGeneralTaskEditor: React.FC = ( - props, -) => { - const { task, onChange, onValidate, onValidateTask, fieldErrors } = props; - - const [tasksMetadata, setTasksMetadata] = React.useState([]); - - useEffect(() => { - const fetchTaskMetadata = async () => { - const meta = await templateVersionsService.getTaskMetadata("GeneralTask"); - setTasksMetadata(meta); - }; - - fetchTaskMetadata(); - }, []); - - const runDefaults = useCapabilityDefaults(tasksMetadata); - - const handleAddTask = async (selectedType: TaskMetadata) => { - const newTask: TaskDefinition = { - type: selectedType.taskType, - - config: { - guid: crypto.randomUUID(), - }, - }; - - var childTasks = task.config.tasks as TaskDefinition[]; //Type assertion to satisfy the compiler, we know this will be the correct type. - - //Assign the default values for the task here. - selectedType.capabilities.forEach((capability) => { - runDefaults(capability, newTask, childTasks); - }); - - const updatedTasks = [...childTasks, newTask]; - const errors = await validateTask(newTask, updatedTasks, tasksMetadata); - - const isValid = Object.keys(errors).length === 0; - - // Mark the child task as valid/invalid - onValidateTask?.(newTask.config.guid as string, isValid); - - task.config.tasks = updatedTasks; - - onChange(task); - }; - - return ( - <> - - - ); -}; - -const runValidation = async ( - task: TaskDefinition, - tasks: TaskDefinition[], -): Promise> => { - const errors: Record = {}; - - const meta = await templateVersionsService.getTaskMetadata("GeneralTask"); - - if (task.config.tasks) { - for (const childTask of task.config.tasks as TaskDefinition[]) { - const childErrors = await validateTask( - childTask, - task.config.tasks as TaskDefinition[], - meta, - ); - - if (childErrors && Object.keys(childErrors).length > 0) { - errors[`${task.config.guid}.tasks`] = - `One or more child tasks are invalid.`; - } - } - } - return errors; -}; - -export function defaultsAssignment( - task: TaskDefinition, - tasks: TaskDefinition[], - ctx: defaultsContext, -) { - task.config.tasks = [] as TaskDefinition[]; -} - -export const stageOfGeneralTaskEditorRegistryEntry: capabilityEditorRegistryEntry = - { - Editor: StageOfGeneralTaskEditor, - DefaultsAssignment: defaultsAssignment, - ValidationRunner: runValidation, - }; diff --git a/src/modules/manager/workflowTemplates/components/TasksEditor.tsx b/src/modules/manager/workflowTemplates/components/TasksEditor.tsx index 4239485..f451240 100644 --- a/src/modules/manager/workflowTemplates/components/TasksEditor.tsx +++ b/src/modules/manager/workflowTemplates/components/TasksEditor.tsx @@ -12,24 +12,28 @@ interface TaskEditorProps { task: TaskDefinition; tasks: TaskDefinition[]; siblingTasks: TaskDefinition[]; + parentTask: TaskDefinition | null; onChange: (updatedTask: TaskDefinition) => void; onValidate: (taskId: string, isValid: boolean) => void; onDelete: (taskId: string) => void; + onTaskAdded?: (taskGuid: string) => void; } const TaskEditorComponent: React.FC = ({ task, tasks, siblingTasks, + parentTask, onChange, onValidate, onDelete, + onTaskAdded, }) => { const [tasksMetadata, setTasksMetadata] = React.useState([]); useEffect(() => { const fetchTaskMetadata = async () => { - const meta = await templateVersionsService.getTaskMetadata("GeneralTask"); + const meta = await templateVersionsService.getTaskMetadata(""); setTasksMetadata(meta); }; @@ -52,12 +56,23 @@ const TaskEditorComponent: React.FC = ({ tasksMetadataList, ); setFieldErrors(errors); - onValidate( - taskToValidate.config.guid as string, - Object.keys(errors).length === 0, - ); + const isValid = Object.keys(errors).length === 0; + onValidate(taskToValidate.config.guid as string, isValid); + + // If this task has a parent, re-validate the parent + if (parentTask) { + const parentErrors = await validateTask( + parentTask, + tasks, + tasksMetadataList, + ); + onValidate( + parentTask.config.guid as string, + Object.keys(parentErrors).length === 0, + ); + } }, - [onValidate], + [onValidate, parentTask, tasks], ); const tasksRef = React.useRef(tasks); @@ -108,14 +123,16 @@ const TaskEditorComponent: React.FC = ({ onChange={handleTaskChange} fieldErrors={fieldErrors} onValidateTask={onValidate} + onTaskAdded={onTaskAdded} /> ); })} onDelete(task.config.guid as string)} > - Delete {task.config.name || "Task"} + Delete {(task.config.name as string) || "Task"}
); @@ -126,17 +143,18 @@ export const TaskEditor = React.memo( (prevProps, nextProps) => { const taskIdChanged = prevProps.task.config.guid !== nextProps.task.config.guid; - const metaChanged = prevProps.tasksMetadata !== nextProps.tasksMetadata; const onChangeChanged = prevProps.onChange !== nextProps.onChange; const onValidateChanged = prevProps.onValidate !== nextProps.onValidate; + const parentChanged = prevProps.parentTask !== nextProps.parentTask; // Skip tasks comparison - we use a ref to access it return !( taskIdChanged || - metaChanged || onChangeChanged || onValidateChanged || - prevProps.onDelete !== nextProps.onDelete + parentChanged || + prevProps.onDelete !== nextProps.onDelete || + prevProps.onTaskAdded !== nextProps.onTaskAdded ); }, ); diff --git a/src/modules/manager/workflowTemplates/components/TasksTab.tsx b/src/modules/manager/workflowTemplates/components/TasksTab.tsx index 24c0cb3..f151bee 100644 --- a/src/modules/manager/workflowTemplates/components/TasksTab.tsx +++ b/src/modules/manager/workflowTemplates/components/TasksTab.tsx @@ -26,8 +26,25 @@ const TasksTab: React.FC = ({ }) => { const tasks = data.tasks; const [selectedTask, setSelectedTask] = useState(null); + const [pendingSelectGuid, setPendingSelectGuid] = useState( + null, + ); useEffect(() => { + // If we have a pending task GUID to select, find it and select it + if (pendingSelectGuid) { + const { task: foundTask } = findTaskAndSiblings( + pendingSelectGuid, + tasks, + null, + ); + if (foundTask) { + setSelectedTask(foundTask); + setPendingSelectGuid(null); + } + return; + } + // Don't override user selection if (selectedTask) return; @@ -48,7 +65,7 @@ const TasksTab: React.FC = ({ // Otherwise select first task setSelectedTask(tasks[0]); - }, [tasks, taskValidation, selectedTask]); + }, [tasks, taskValidation, selectedTask, pendingSelectGuid]); const handleTasksChange = React.useCallback( (newTasks: TaskDefinition[]) => { @@ -61,22 +78,27 @@ const TasksTab: React.FC = ({ const findTaskAndSiblings = ( targetGuid: string, source: TaskDefinition[], - ): { task: TaskDefinition | null; siblings: TaskDefinition[] } => { + parent: TaskDefinition | null = null, + ): { + task: TaskDefinition | null; + siblings: TaskDefinition[]; + parent: TaskDefinition | null; + } => { for (const task of source) { if (task.config.guid === targetGuid) { - return { task, siblings: source }; + return { task, siblings: source, parent }; } const childTasks = (task.config.tasks as TaskDefinition[]) ?? []; if (childTasks.length === 0) continue; - const result = findTaskAndSiblings(targetGuid, childTasks); + const result = findTaskAndSiblings(targetGuid, childTasks, task); if (result.task) { return result; } } - return { task: null, siblings: [] }; + return { task: null, siblings: [], parent: null }; }; const handleTaskEditorChange = React.useCallback( @@ -84,6 +106,7 @@ const TasksTab: React.FC = ({ const { siblings } = findTaskAndSiblings( updatedTask.config.guid as string, tasks, + null, ); if (siblings.length === 0) { @@ -242,12 +265,25 @@ const TasksTab: React.FC = ({ task={selectedTask} tasks={tasks} siblingTasks={ - findTaskAndSiblings(selectedTask.config.guid as string, tasks) - .siblings + findTaskAndSiblings( + selectedTask.config.guid as string, + tasks, + null, + ).siblings + } + parentTask={ + findTaskAndSiblings( + selectedTask.config.guid as string, + tasks, + null, + ).parent } onChange={handleTaskEditorChange} onValidate={onValidate} onDelete={handleTaskDelete} + onTaskAdded={(taskGuid: string) => { + setPendingSelectGuid(taskGuid); + }} />
)} diff --git a/src/modules/manager/workflowTemplates/components/capabilityEditorRegistry.ts b/src/modules/manager/workflowTemplates/components/capabilityEditorRegistry.ts index 824116a..2d38b0a 100644 --- a/src/modules/manager/workflowTemplates/components/capabilityEditorRegistry.ts +++ b/src/modules/manager/workflowTemplates/components/capabilityEditorRegistry.ts @@ -5,7 +5,8 @@ import { capabilityEditorRegistryEntry } from "./useCapabilityDefaults"; import { outcomeOfApprovalVerdictRegistryEntry } from "./CapabilityEditors/OutcomeOfApprovalVerdictRegistryEntry"; import { budgetEditorRegistryEntry } from "./CapabilityEditors/BudgetEditorRegistryEntry"; import { bypassableEditorRegistryEntry } from "./CapabilityEditors/BypassableEditor"; -import { stageOfGeneralTaskEditorRegistryEntry } from "./CapabilityEditors/StageOfGeneralTaskEditor"; +import { createStageEditorRegistryEntry } from "./CapabilityEditors/StageEditor"; +import { assigneesOfIApprovalTaskAssigneeRegistryEntry } from "./CapabilityEditors/AssigneesOfIApprovalTaskAssigneeEditor"; export const capabilityEditorRegistry: Record< string, @@ -15,9 +16,12 @@ export const capabilityEditorRegistry: Record< ITags: tagsEditorRegistryEntry, IBudget: budgetEditorRegistryEntry, "IAssignees": assigneesOfITaskAssigneeRegistryEntry, + "IAssignees": + assigneesOfIApprovalTaskAssigneeRegistryEntry, "IOutcome": outcomeOfApprovalVerdictRegistryEntry, // IFormTemplate: null, //ToDo implement this IBypassable: bypassableEditorRegistryEntry, - "IStage": stageOfGeneralTaskEditorRegistryEntry, - // "IStage": null, //ToDo implement this + "IStage": createStageEditorRegistryEntry("GeneralTask"), + "IStage": + createStageEditorRegistryEntry("ApprovalTask"), }; diff --git a/src/modules/manager/workflowTemplates/components/useCapabilityDefaults.tsx b/src/modules/manager/workflowTemplates/components/useCapabilityDefaults.tsx index 9cbd0eb..3a7083a 100644 --- a/src/modules/manager/workflowTemplates/components/useCapabilityDefaults.tsx +++ b/src/modules/manager/workflowTemplates/components/useCapabilityDefaults.tsx @@ -18,6 +18,7 @@ export interface CapabilityEditorProps { onChange: (updated: TaskDefinition) => void; onValidate: (result: TaskValidationResult) => void; onValidateTask?: (taskId: string, isValid: boolean) => void; + onTaskAdded?: (taskGuid: string) => void; fieldErrors: Record; } @@ -56,7 +57,11 @@ export async function validateTask( continue; } - const validationErrors = await entry.ValidationRunner(task, tasks); + const validationErrors = await entry.ValidationRunner( + task, + tasks, + tasksMetadata, + ); Object.assign(errors, validationErrors); } return errors;