Started adding support for stages
This commit is contained in:
parent
70e4258071
commit
4706b78d88
@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
"High": "Élevé",
|
"High": "Élevé",
|
||||||
"Low": "Faible"
|
"Low": "Faible",
|
||||||
|
"Normal": "Normalement"
|
||||||
}
|
}
|
||||||
@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
|
"High": "높은",
|
||||||
"Low": "로우",
|
"Low": "로우",
|
||||||
"Normal": "정상"
|
"Normal": "정상"
|
||||||
}
|
}
|
||||||
@ -5,12 +5,34 @@ export interface SelectableListProps<T> {
|
|||||||
selectedValue?: T | null;
|
selectedValue?: T | null;
|
||||||
renderLabel: (item: T) => React.ReactNode;
|
renderLabel: (item: T) => React.ReactNode;
|
||||||
onSelect: (item: T) => void;
|
onSelect: (item: T) => void;
|
||||||
|
getChildren?: (item: T) => T[] | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SelectableList = <T,>(
|
export const SelectableList = <T,>(
|
||||||
props: SelectableListProps<T>,
|
props: SelectableListProps<T>,
|
||||||
): JSX.Element => {
|
): JSX.Element => {
|
||||||
const { items, selectedValue, renderLabel, onSelect } = props;
|
const { items, selectedValue, renderLabel, onSelect, getChildren } = props;
|
||||||
|
|
||||||
|
const flattenedItems = React.useMemo(() => {
|
||||||
|
const flattened: { item: T; depth: number }[] = [];
|
||||||
|
|
||||||
|
const walk = (source: T[], depth: number) => {
|
||||||
|
source.forEach((item) => {
|
||||||
|
flattened.push({ item, depth });
|
||||||
|
|
||||||
|
const children = getChildren?.(item) ?? [];
|
||||||
|
if (children.length > 0) {
|
||||||
|
walk(children, depth + 1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
walk(items, 0);
|
||||||
|
|
||||||
|
return flattened;
|
||||||
|
}, [items, getChildren]);
|
||||||
|
|
||||||
|
const flatItems = flattenedItems.map((entry) => entry.item);
|
||||||
|
|
||||||
const listRef = useRef<HTMLUListElement | null>(null);
|
const listRef = useRef<HTMLUListElement | null>(null);
|
||||||
const isFocusedRef = useRef(false);
|
const isFocusedRef = useRef(false);
|
||||||
@ -21,39 +43,41 @@ export const SelectableList = <T,>(
|
|||||||
const handleKeyDown = useCallback(
|
const handleKeyDown = useCallback(
|
||||||
(e: React.KeyboardEvent<HTMLUListElement>) => {
|
(e: React.KeyboardEvent<HTMLUListElement>) => {
|
||||||
if (!isFocusedRef.current) return;
|
if (!isFocusedRef.current) return;
|
||||||
if (!items.length) return;
|
if (!flatItems.length) return;
|
||||||
|
|
||||||
const currentIndex = selectedValue ? items.indexOf(selectedValue) : -1;
|
const currentIndex = selectedValue
|
||||||
|
? flatItems.indexOf(selectedValue)
|
||||||
|
: -1;
|
||||||
|
|
||||||
if (e.key === "ArrowDown") {
|
if (e.key === "ArrowDown") {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const nextIndex =
|
const nextIndex =
|
||||||
currentIndex < items.length - 1 ? currentIndex + 1 : 0;
|
currentIndex < flatItems.length - 1 ? currentIndex + 1 : 0;
|
||||||
onSelect(items[nextIndex]);
|
onSelect(flatItems[nextIndex]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (e.key === "ArrowUp") {
|
if (e.key === "ArrowUp") {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const prevIndex =
|
const prevIndex =
|
||||||
currentIndex > 0 ? currentIndex - 1 : items.length - 1;
|
currentIndex > 0 ? currentIndex - 1 : flatItems.length - 1;
|
||||||
onSelect(items[prevIndex]);
|
onSelect(flatItems[prevIndex]);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[items, selectedValue, onSelect],
|
[flatItems, selectedValue, onSelect],
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isFocusedRef.current) return;
|
if (!isFocusedRef.current) return;
|
||||||
if (!selectedValue) return;
|
if (!selectedValue) return;
|
||||||
|
|
||||||
const index = items.indexOf(selectedValue);
|
const index = flatItems.indexOf(selectedValue);
|
||||||
if (index < 0) return;
|
if (index < 0) return;
|
||||||
|
|
||||||
const el = itemRefs.current[index];
|
const el = itemRefs.current[index];
|
||||||
if (el) {
|
if (el) {
|
||||||
el.focus({ preventScroll: false });
|
el.focus({ preventScroll: false });
|
||||||
}
|
}
|
||||||
}, [items, selectedValue]);
|
}, [flatItems, selectedValue]);
|
||||||
|
|
||||||
// Separate effect for scrolling - only when selection changes
|
// Separate effect for scrolling - only when selection changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -64,14 +88,14 @@ export const SelectableList = <T,>(
|
|||||||
|
|
||||||
if (!selectedValue) return;
|
if (!selectedValue) return;
|
||||||
|
|
||||||
const index = items.indexOf(selectedValue);
|
const index = flatItems.indexOf(selectedValue);
|
||||||
if (index < 0) return;
|
if (index < 0) return;
|
||||||
|
|
||||||
const el = itemRefs.current[index];
|
const el = itemRefs.current[index];
|
||||||
if (el) {
|
if (el) {
|
||||||
el.scrollIntoView({ block: "nearest", behavior: "smooth" });
|
el.scrollIntoView({ block: "nearest", behavior: "smooth" });
|
||||||
}
|
}
|
||||||
}, [selectedValue, items]);
|
}, [selectedValue, flatItems]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ul
|
<ul
|
||||||
@ -80,13 +104,13 @@ export const SelectableList = <T,>(
|
|||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
role="listbox"
|
role="listbox"
|
||||||
aria-activedescendant={
|
aria-activedescendant={
|
||||||
selectedValue ? `option-${items.indexOf(selectedValue)}` : undefined
|
selectedValue ? `option-${flatItems.indexOf(selectedValue)}` : undefined
|
||||||
}
|
}
|
||||||
onFocus={() => (isFocusedRef.current = true)}
|
onFocus={() => (isFocusedRef.current = true)}
|
||||||
onBlur={() => (isFocusedRef.current = false)}
|
onBlur={() => (isFocusedRef.current = false)}
|
||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
>
|
>
|
||||||
{items.map((item, index) => {
|
{flattenedItems.map(({ item, depth }, index) => {
|
||||||
const isSelected = selectedValue === item;
|
const isSelected = selectedValue === item;
|
||||||
const className = isSelected ? "selected" : "";
|
const className = isSelected ? "selected" : "";
|
||||||
|
|
||||||
@ -100,6 +124,7 @@ export const SelectableList = <T,>(
|
|||||||
aria-selected={isSelected}
|
aria-selected={isSelected}
|
||||||
onClick={() => onSelect(item)}
|
onClick={() => onSelect(item)}
|
||||||
className={className}
|
className={className}
|
||||||
|
style={{ paddingLeft: `${depth * 16}px` }}
|
||||||
>
|
>
|
||||||
{renderLabel(item)}
|
{renderLabel(item)}
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@ -0,0 +1,91 @@
|
|||||||
|
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<CapabilityEditorProps> = (
|
||||||
|
props,
|
||||||
|
) => {
|
||||||
|
const { task, onChange, onValidate, fieldErrors } = props;
|
||||||
|
|
||||||
|
const [tasksMetadata, setTasksMetadata] = React.useState<TaskMetadata[]>([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchTaskMetadata = async () => {
|
||||||
|
const meta = await templateVersionsService.getTaskMetadata("GeneralTask");
|
||||||
|
setTasksMetadata(meta);
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchTaskMetadata();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const runDefaults = useCapabilityDefaults(tasksMetadata);
|
||||||
|
|
||||||
|
const handleAddTask = (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 = validateTask(newTask, updatedTasks, tasksMetadata);
|
||||||
|
|
||||||
|
const isValid = Object.keys(errors).length === 0;
|
||||||
|
|
||||||
|
task.config.tasks = updatedTasks;
|
||||||
|
|
||||||
|
onValidate({ errors: errors, isValid: isValid } as TaskValidationResult);
|
||||||
|
|
||||||
|
onChange(task);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<AddTaskButton tasksMetadata={tasksMetadata} onAdd={handleAddTask} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const runValidation = (
|
||||||
|
task: TaskDefinition,
|
||||||
|
tasks: TaskDefinition[],
|
||||||
|
): Record<string, string> => {
|
||||||
|
const errors: Record<string, string> = {};
|
||||||
|
|
||||||
|
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,
|
||||||
|
};
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import React from "react";
|
import React, { useEffect } from "react";
|
||||||
import {
|
import templateVersionsService, {
|
||||||
TaskDefinition,
|
TaskDefinition,
|
||||||
TaskMetadata,
|
TaskMetadata,
|
||||||
} from "../services/WorkflowTemplateService";
|
} from "../services/WorkflowTemplateService";
|
||||||
@ -12,7 +12,6 @@ import ValidationErrorIcon from "../../../../components/validationErrorIcon";
|
|||||||
interface TaskListProps {
|
interface TaskListProps {
|
||||||
tasks: TaskDefinition[];
|
tasks: TaskDefinition[];
|
||||||
validTasksList: Record<string, boolean>;
|
validTasksList: Record<string, boolean>;
|
||||||
tasksMetadata: TaskMetadata[];
|
|
||||||
onChange: (tasks: TaskDefinition[]) => void;
|
onChange: (tasks: TaskDefinition[]) => void;
|
||||||
selectedTask: TaskDefinition | null;
|
selectedTask: TaskDefinition | null;
|
||||||
onSelectTask: (task: TaskDefinition | null) => void;
|
onSelectTask: (task: TaskDefinition | null) => void;
|
||||||
@ -22,14 +21,50 @@ interface TaskListProps {
|
|||||||
const TaskList: React.FC<TaskListProps> = ({
|
const TaskList: React.FC<TaskListProps> = ({
|
||||||
tasks,
|
tasks,
|
||||||
validTasksList,
|
validTasksList,
|
||||||
tasksMetadata,
|
|
||||||
onChange,
|
onChange,
|
||||||
selectedTask,
|
selectedTask,
|
||||||
onSelectTask,
|
onSelectTask,
|
||||||
onValidate,
|
onValidate,
|
||||||
}) => {
|
}) => {
|
||||||
|
const [tasksMetadata, setTasksMetadata] = React.useState<TaskMetadata[]>([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchTaskMetadata = async () => {
|
||||||
|
const meta = await templateVersionsService.getTaskMetadata("GeneralTask");
|
||||||
|
setTasksMetadata(meta);
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchTaskMetadata();
|
||||||
|
}, []);
|
||||||
|
|
||||||
const runDefaults = useCapabilityDefaults(tasksMetadata);
|
const runDefaults = useCapabilityDefaults(tasksMetadata);
|
||||||
|
|
||||||
|
const taskMetadataByType = React.useMemo(() => {
|
||||||
|
const map = new Map<string, TaskMetadata>();
|
||||||
|
tasksMetadata.forEach((meta) => {
|
||||||
|
map.set(meta.taskType, meta);
|
||||||
|
});
|
||||||
|
return map;
|
||||||
|
}, [tasksMetadata]);
|
||||||
|
|
||||||
|
const getChildTasks = React.useCallback(
|
||||||
|
(task: TaskDefinition) => {
|
||||||
|
const meta = taskMetadataByType.get(task.type);
|
||||||
|
const isStageTask =
|
||||||
|
meta?.capabilities?.some((capability) =>
|
||||||
|
capability.startsWith("IStage<"),
|
||||||
|
) ?? false;
|
||||||
|
|
||||||
|
if (!isStageTask) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const childTasks = (task.config.tasks as TaskDefinition[]) ?? [];
|
||||||
|
return sortTasksTopologically(childTasks);
|
||||||
|
},
|
||||||
|
[taskMetadataByType],
|
||||||
|
);
|
||||||
|
|
||||||
const handleAddTask = (selectedType: TaskMetadata) => {
|
const handleAddTask = (selectedType: TaskMetadata) => {
|
||||||
const newTask: TaskDefinition = {
|
const newTask: TaskDefinition = {
|
||||||
type: selectedType.taskType,
|
type: selectedType.taskType,
|
||||||
@ -64,6 +99,7 @@ const TaskList: React.FC<TaskListProps> = ({
|
|||||||
<div className="task-list-content">
|
<div className="task-list-content">
|
||||||
<SelectableList
|
<SelectableList
|
||||||
items={sortedTasks}
|
items={sortedTasks}
|
||||||
|
getChildren={getChildTasks}
|
||||||
selectedValue={selectedTask}
|
selectedValue={selectedTask}
|
||||||
renderLabel={(x) => {
|
renderLabel={(x) => {
|
||||||
if (x) {
|
if (x) {
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import React, { useCallback, useMemo } from "react";
|
import React, { useCallback, useEffect, useMemo } from "react";
|
||||||
import {
|
import templateVersionsService, {
|
||||||
TaskDefinition,
|
TaskDefinition,
|
||||||
TaskMetadata,
|
TaskMetadata,
|
||||||
} from "../services/WorkflowTemplateService";
|
} from "../services/WorkflowTemplateService";
|
||||||
@ -11,7 +11,7 @@ import ConfirmButton from "../../../../components/common/ConfirmButton";
|
|||||||
interface TaskEditorProps {
|
interface TaskEditorProps {
|
||||||
task: TaskDefinition;
|
task: TaskDefinition;
|
||||||
tasks: TaskDefinition[];
|
tasks: TaskDefinition[];
|
||||||
tasksMetadata: TaskMetadata[];
|
siblingTasks: TaskDefinition[];
|
||||||
onChange: (updatedTask: TaskDefinition) => void;
|
onChange: (updatedTask: TaskDefinition) => void;
|
||||||
onValidate: (taskId: string, isValid: boolean) => void;
|
onValidate: (taskId: string, isValid: boolean) => void;
|
||||||
onDelete: (taskId: string) => void;
|
onDelete: (taskId: string) => void;
|
||||||
@ -20,11 +20,22 @@ interface TaskEditorProps {
|
|||||||
const TaskEditorComponent: React.FC<TaskEditorProps> = ({
|
const TaskEditorComponent: React.FC<TaskEditorProps> = ({
|
||||||
task,
|
task,
|
||||||
tasks,
|
tasks,
|
||||||
tasksMetadata,
|
siblingTasks,
|
||||||
onChange,
|
onChange,
|
||||||
onValidate,
|
onValidate,
|
||||||
onDelete,
|
onDelete,
|
||||||
}) => {
|
}) => {
|
||||||
|
const [tasksMetadata, setTasksMetadata] = React.useState<TaskMetadata[]>([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchTaskMetadata = async () => {
|
||||||
|
const meta = await templateVersionsService.getTaskMetadata("GeneralTask");
|
||||||
|
setTasksMetadata(meta);
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchTaskMetadata();
|
||||||
|
}, []);
|
||||||
|
|
||||||
const [fieldErrors, setFieldErrors] = React.useState<Record<string, string>>(
|
const [fieldErrors, setFieldErrors] = React.useState<Record<string, string>>(
|
||||||
{},
|
{},
|
||||||
);
|
);
|
||||||
@ -56,17 +67,17 @@ const TaskEditorComponent: React.FC<TaskEditorProps> = ({
|
|||||||
|
|
||||||
const handleTaskChange = useCallback(
|
const handleTaskChange = useCallback(
|
||||||
(updatedTask: TaskDefinition) => {
|
(updatedTask: TaskDefinition) => {
|
||||||
// Update the task list
|
// Update the sibling task list (validation runs on siblings only)
|
||||||
const updatedTasks = tasks.map((t) =>
|
const updatedSiblings = siblingTasks.map((t) =>
|
||||||
t.config.guid === updatedTask.config.guid ? updatedTask : t,
|
t.config.guid === updatedTask.config.guid ? updatedTask : t,
|
||||||
);
|
);
|
||||||
|
|
||||||
runValidation(updatedTask, updatedTasks, tasksMetadata);
|
runValidation(updatedTask, updatedSiblings, tasksMetadata);
|
||||||
|
|
||||||
// Bubble updated task up
|
// Bubble updated task up
|
||||||
onChange(updatedTask);
|
onChange(updatedTask);
|
||||||
},
|
},
|
||||||
[tasks, tasksMetadata, runValidation, onChange],
|
[siblingTasks, tasksMetadata, runValidation, onChange],
|
||||||
);
|
);
|
||||||
|
|
||||||
const taskMeta = useMemo(
|
const taskMeta = useMemo(
|
||||||
|
|||||||
@ -26,16 +26,6 @@ const TasksTab: React.FC<TasksTabProps> = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const tasks = data.tasks;
|
const tasks = data.tasks;
|
||||||
const [selectedTask, setSelectedTask] = useState<TaskDefinition | null>(null);
|
const [selectedTask, setSelectedTask] = useState<TaskDefinition | null>(null);
|
||||||
const [tasksMetadata, setTasksMetadata] = React.useState<TaskMetadata[]>([]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const fetchTaskMetadata = async () => {
|
|
||||||
const meta = await templateVersionsService.getTaskMetadata("GeneralTask");
|
|
||||||
setTasksMetadata(meta);
|
|
||||||
};
|
|
||||||
|
|
||||||
fetchTaskMetadata();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Don't override user selection
|
// Don't override user selection
|
||||||
@ -68,37 +58,165 @@ const TasksTab: React.FC<TasksTabProps> = ({
|
|||||||
[onTasksChange],
|
[onTasksChange],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const findTaskAndSiblings = (
|
||||||
|
targetGuid: string,
|
||||||
|
source: TaskDefinition[],
|
||||||
|
): { task: TaskDefinition | null; siblings: TaskDefinition[] } => {
|
||||||
|
for (const task of source) {
|
||||||
|
if (task.config.guid === targetGuid) {
|
||||||
|
return { task, siblings: source };
|
||||||
|
}
|
||||||
|
|
||||||
|
const childTasks = (task.config.tasks as TaskDefinition[]) ?? [];
|
||||||
|
if (childTasks.length === 0) continue;
|
||||||
|
|
||||||
|
const result = findTaskAndSiblings(targetGuid, childTasks);
|
||||||
|
if (result.task) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { task: null, siblings: [] };
|
||||||
|
};
|
||||||
|
|
||||||
const handleTaskEditorChange = React.useCallback(
|
const handleTaskEditorChange = React.useCallback(
|
||||||
(updatedTask: TaskDefinition) => {
|
(updatedTask: TaskDefinition) => {
|
||||||
const newTasks = tasks.map((t) =>
|
const { siblings } = findTaskAndSiblings(
|
||||||
t.config.guid === updatedTask.config.guid ? updatedTask : t,
|
updatedTask.config.guid as string,
|
||||||
|
tasks,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (siblings.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateTaskRecursive = (
|
||||||
|
source: TaskDefinition[],
|
||||||
|
): TaskDefinition[] => {
|
||||||
|
return source.map((t) => {
|
||||||
|
if (t.config.guid === updatedTask.config.guid) {
|
||||||
|
return updatedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
const childTasks = (t.config.tasks as TaskDefinition[]) ?? [];
|
||||||
|
if (childTasks.length === 0) {
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatedChildren = updateTaskRecursive(childTasks);
|
||||||
|
if (updatedChildren === childTasks) {
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...t,
|
||||||
|
config: {
|
||||||
|
...t.config,
|
||||||
|
tasks: updatedChildren,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const newTasks = updateTaskRecursive(tasks);
|
||||||
handleTasksChange(newTasks);
|
handleTasksChange(newTasks);
|
||||||
|
|
||||||
// Use the updated object from the array, not the raw updatedTask
|
// Use the updated object from the array, not the raw updatedTask
|
||||||
const updatedFromArray = newTasks.find(
|
const { task: updatedFromArray } = findTaskAndSiblings(
|
||||||
(t) => t.config.guid === updatedTask.config.guid,
|
updatedTask.config.guid as string,
|
||||||
|
newTasks,
|
||||||
);
|
);
|
||||||
|
|
||||||
setSelectedTask(updatedFromArray!);
|
if (updatedFromArray) {
|
||||||
|
setSelectedTask(updatedFromArray);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
[tasks, handleTasksChange],
|
[tasks, handleTasksChange],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleTaskDelete = React.useCallback(
|
const handleTaskDelete = React.useCallback(
|
||||||
(taskId: string) => {
|
(taskId: string) => {
|
||||||
const newTasks = tasks.filter((t) => t.config.guid !== taskId);
|
const removeTaskRecursive = (
|
||||||
|
source: TaskDefinition[],
|
||||||
|
): { tasks: TaskDefinition[]; removed: boolean } => {
|
||||||
|
let removed = false;
|
||||||
|
|
||||||
for (const t of newTasks) {
|
const updated = source.flatMap((task) => {
|
||||||
// If any task has a dependency on the deleted task, remove that dependency
|
if (task.config.guid === taskId) {
|
||||||
if (t.config.predecessors) {
|
removed = true;
|
||||||
t.config.predecessors = (t.config.predecessors as string[]).filter(
|
return [];
|
||||||
(d) => d !== taskId,
|
}
|
||||||
);
|
|
||||||
}
|
const childTasks = (task.config.tasks as TaskDefinition[]) ?? [];
|
||||||
|
if (childTasks.length === 0) {
|
||||||
|
return [task];
|
||||||
|
}
|
||||||
|
|
||||||
|
const childResult = removeTaskRecursive(childTasks);
|
||||||
|
if (!childResult.removed) {
|
||||||
|
return [task];
|
||||||
|
}
|
||||||
|
|
||||||
|
removed = true;
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
...task,
|
||||||
|
config: {
|
||||||
|
...task.config,
|
||||||
|
tasks: childResult.tasks,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
|
return { tasks: updated, removed };
|
||||||
|
};
|
||||||
|
|
||||||
|
const removePredecessorRecursive = (
|
||||||
|
source: TaskDefinition[],
|
||||||
|
): TaskDefinition[] => {
|
||||||
|
return source.map((task) => {
|
||||||
|
const childTasks = (task.config.tasks as TaskDefinition[]) ?? [];
|
||||||
|
const updatedChildren =
|
||||||
|
childTasks.length > 0
|
||||||
|
? removePredecessorRecursive(childTasks)
|
||||||
|
: childTasks;
|
||||||
|
|
||||||
|
const predecessors = task.config.predecessors as string[] | undefined;
|
||||||
|
const updatedPredecessors = predecessors
|
||||||
|
? predecessors.filter((d) => d !== taskId)
|
||||||
|
: predecessors;
|
||||||
|
|
||||||
|
const childrenChanged = updatedChildren !== childTasks;
|
||||||
|
const predecessorsChanged = updatedPredecessors !== predecessors;
|
||||||
|
|
||||||
|
if (!childrenChanged && !predecessorsChanged) {
|
||||||
|
return task;
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatedConfig = { ...task.config } as Record<string, unknown>;
|
||||||
|
if (childrenChanged) {
|
||||||
|
updatedConfig.tasks = updatedChildren;
|
||||||
|
}
|
||||||
|
if (predecessorsChanged) {
|
||||||
|
updatedConfig.predecessors = updatedPredecessors;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...task,
|
||||||
|
config: updatedConfig,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteResult = removeTaskRecursive(tasks);
|
||||||
|
if (!deleteResult.removed) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const newTasks = removePredecessorRecursive(deleteResult.tasks);
|
||||||
|
|
||||||
onValidate(taskId, true); // Clear validation state for deleted task
|
onValidate(taskId, true); // Clear validation state for deleted task
|
||||||
setSelectedTask(null);
|
setSelectedTask(null);
|
||||||
handleTasksChange(newTasks);
|
handleTasksChange(newTasks);
|
||||||
@ -112,7 +230,6 @@ const TasksTab: React.FC<TasksTabProps> = ({
|
|||||||
<TaskList
|
<TaskList
|
||||||
tasks={tasks}
|
tasks={tasks}
|
||||||
validTasksList={taskValidation}
|
validTasksList={taskValidation}
|
||||||
tasksMetadata={tasksMetadata}
|
|
||||||
onChange={handleTasksChange}
|
onChange={handleTasksChange}
|
||||||
selectedTask={selectedTask}
|
selectedTask={selectedTask}
|
||||||
onSelectTask={setSelectedTask}
|
onSelectTask={setSelectedTask}
|
||||||
@ -122,9 +239,12 @@ const TasksTab: React.FC<TasksTabProps> = ({
|
|||||||
{selectedTask && (
|
{selectedTask && (
|
||||||
<div>
|
<div>
|
||||||
<TaskEditor
|
<TaskEditor
|
||||||
tasksMetadata={tasksMetadata}
|
|
||||||
task={selectedTask}
|
task={selectedTask}
|
||||||
tasks={tasks}
|
tasks={tasks}
|
||||||
|
siblingTasks={
|
||||||
|
findTaskAndSiblings(selectedTask.config.guid as string, tasks)
|
||||||
|
.siblings
|
||||||
|
}
|
||||||
onChange={handleTaskEditorChange}
|
onChange={handleTaskEditorChange}
|
||||||
onValidate={onValidate}
|
onValidate={onValidate}
|
||||||
onDelete={handleTaskDelete}
|
onDelete={handleTaskDelete}
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import { capabilityEditorRegistryEntry } from "./useCapabilityDefaults";
|
|||||||
import { outcomeOfApprovalVerdictRegistryEntry } from "./CapabilityEditors/OutcomeOfApprovalVerdictRegistryEntry";
|
import { outcomeOfApprovalVerdictRegistryEntry } from "./CapabilityEditors/OutcomeOfApprovalVerdictRegistryEntry";
|
||||||
import { budgetEditorRegistryEntry } from "./CapabilityEditors/BudgetEditorRegistryEntry";
|
import { budgetEditorRegistryEntry } from "./CapabilityEditors/BudgetEditorRegistryEntry";
|
||||||
import { bypassableEditorRegistryEntry } from "./CapabilityEditors/BypassableEditor";
|
import { bypassableEditorRegistryEntry } from "./CapabilityEditors/BypassableEditor";
|
||||||
|
import { stageOfGeneralTaskEditorRegistryEntry } from "./CapabilityEditors/StageOfGeneralTaskEditor";
|
||||||
|
|
||||||
export const capabilityEditorRegistry: Record<
|
export const capabilityEditorRegistry: Record<
|
||||||
string,
|
string,
|
||||||
@ -17,6 +18,6 @@ export const capabilityEditorRegistry: Record<
|
|||||||
"IOutcome<ApprovalVerdict>": outcomeOfApprovalVerdictRegistryEntry,
|
"IOutcome<ApprovalVerdict>": outcomeOfApprovalVerdictRegistryEntry,
|
||||||
// IFormTemplate: null, //ToDo implement this
|
// IFormTemplate: null, //ToDo implement this
|
||||||
IBypassable: bypassableEditorRegistryEntry,
|
IBypassable: bypassableEditorRegistryEntry,
|
||||||
// "IStage<GeneralTaskAttribute>": null, //ToDo implement this
|
"IStage<GeneralTaskAttribute>": stageOfGeneralTaskEditorRegistryEntry,
|
||||||
// "IStage<ApprovalTaskAttribute>": null, //ToDo implement this
|
// "IStage<ApprovalTaskAttribute>": null, //ToDo implement this
|
||||||
};
|
};
|
||||||
|
|||||||
@ -6,7 +6,6 @@ import {
|
|||||||
MakeGeneralIdRefParams,
|
MakeGeneralIdRefParams,
|
||||||
} from "../../../../utils/GeneralIdRef";
|
} from "../../../../utils/GeneralIdRef";
|
||||||
import MapToJson from "../../../../utils/MapToJson";
|
import MapToJson from "../../../../utils/MapToJson";
|
||||||
import { CustomFieldValue } from "../../glossary/services/glossaryService";
|
|
||||||
|
|
||||||
const apiEndpoint = "/WorkflowTemplate";
|
const apiEndpoint = "/WorkflowTemplate";
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user