OutcomeOfApprovalVerdict should now be working

This commit is contained in:
Colin Dawson 2026-02-23 22:59:15 +00:00
parent a3430d221d
commit fb09052476
4 changed files with 127 additions and 84 deletions

View File

@ -1,6 +1,7 @@
{
"Approved": "Схвалено",
"ApprovedWithComments": "Схвалено з коментарями",
"None": "Ніхто.",
"Pending": "У підготовці.",
"Rejected": "Відхилено",
"Reviewed": "Оглянуто."

View File

@ -42,7 +42,7 @@ export interface InputProps {
placeHolder?: string;
readOnly?: boolean;
type: InputType;
value?: string | number | readonly string[] | undefined;
value?: string | number | readonly string[] | boolean | undefined;
defaultValue?: string | number | readonly string[] | undefined;
min?: number;
max?: number;
@ -56,6 +56,7 @@ export interface InputProps {
) => void;
maxLength?: number;
options?: { value: string; label: string }[];
title: string;
}
function Input(props: InputProps) {
@ -74,6 +75,7 @@ function Input(props: InputProps) {
autoComplete,
onChange,
options,
title,
...rest
} = props;
@ -88,7 +90,7 @@ function Input(props: InputProps) {
const [showPasswordIcon, setShowPasswordIcon] = useState(faEyeSlash);
if (type === InputType.checkbox) {
checked = value === String(true);
checked = value === true || value === "true";
showValue = undefined;
divClassName = "form-check allignedCheckBox";
className = "form-check-input";
@ -154,7 +156,12 @@ function Input(props: InputProps) {
return (
<div className={divClassName} hidden={hidden}>
{(includeLabel === true || includeLabel === undefined) && (
<label className={labelClassName} htmlFor={name} hidden={hidden}>
<label
className={labelClassName}
htmlFor={name}
hidden={hidden}
title={title}
>
{label}
</label>
)}
@ -178,6 +185,7 @@ function Input(props: InputProps) {
defaultValue={defaultValue}
maxLength={maxLength! > 0 ? maxLength : undefined}
autoComplete={autoComplete}
title={title}
/>
)}

View File

@ -1,8 +1,10 @@
import Button, { ButtonType } from "../../../../../components/common/Button";
import ErrorBlock from "../../../../../components/common/ErrorBlock";
import { InputType } from "../../../../../components/common/Input";
import VerdictPicker from "../../../../../components/pickers/VerdictPicker";
import ValidationErrorIcon from "../../../../../components/validationErrorIcon";
import { TaskDefinition } from "../../services/WorkflowTemplateService";
import { renderTaskField } from "../taskEditorHelpers";
import TaskPicker from "../TaskPicker";
import {
@ -27,6 +29,7 @@ export const outcomeEditor: React.FC<CapabilityEditorProps> = (props) => {
task: null,
});
clone.config.outcomeActions = list;
clone.config.overrideDefaultTaskProgression = false;
onChange(clone);
}
@ -51,82 +54,98 @@ export const outcomeEditor: React.FC<CapabilityEditorProps> = (props) => {
const otherTasks = tasks.filter((t) => (t.config.guid as string) !== guid);
return (
<div>
<table>
<thead>
<tr>
<th></th>
<th>Verdict</th>
<th>Task</th>
<th>
<Button onClick={addOutcome} buttonType={ButtonType.secondary}>
Add Outcome
</Button>
</th>
</tr>
</thead>
<tbody>
{outcomeActions.map((outcomeAction, index) => (
<tr key={index} className="align-top">
<td className="form-group">
<ValidationErrorIcon
visible={
!!fieldErrors?.[
`${guid}.outcomeActions[${index}].verdict`
] ||
!!fieldErrors?.[`${guid}.outcomeActions[${index}].task`]
}
/>
</td>
<td>
<VerdictPicker
includeLabel={false}
name="verdict"
label="Verdict"
value={outcomeAction.verdict}
error={
fieldErrors?.[`${guid}.outcomeActions[${index}].verdict`]
}
onChange={(name: string, val: string) =>
updateOutcome(index, { ...outcomeAction, verdict: val })
}
/>
</td>
<td>
<TaskPicker
includeLabel={false}
name="task"
label="Task"
value={(outcomeAction.task as string) ?? null}
tasks={otherTasks}
error={fieldErrors?.[`${guid}.outcomeActions[${index}].task`]}
onChange={(name, val) =>
updateOutcome(index, { ...outcomeAction, task: val })
}
/>
</td>
<td className="form-group">
<Button
onClick={() => removeOutcome(index)}
buttonType={ButtonType.secondary}
>
Remove
<>
<div>
<table>
<thead>
<tr>
<th></th>
<th>Verdict</th>
<th>Task</th>
<th>
<Button onClick={addOutcome} buttonType={ButtonType.secondary}>
Add Outcome
</Button>
</td>
</th>
</tr>
))}
</tbody>
</table>
<ErrorBlock
error={Object.values(fieldErrors ?? {})
.filter((_, i) =>
Object.keys(fieldErrors ?? {}).some(
(key) => key === `${guid}.outcomeActions[${i}]`,
),
)
.join("; ")}
/>
</div>
</thead>
<tbody>
{outcomeActions.map((outcomeAction, index) => (
<tr key={index} className="align-top">
<td className="form-group">
<ValidationErrorIcon
visible={
!!fieldErrors?.[
`${guid}.outcomeActions[${index}].verdict`
] ||
!!fieldErrors?.[`${guid}.outcomeActions[${index}].task`]
}
/>
</td>
<td>
<VerdictPicker
includeLabel={false}
name="verdict"
label="Verdict"
value={outcomeAction.verdict}
error={
fieldErrors?.[`${guid}.outcomeActions[${index}].verdict`]
}
onChange={(name: string, val: string) =>
updateOutcome(index, { ...outcomeAction, verdict: val })
}
/>
</td>
<td>
<TaskPicker
includeLabel={false}
name="task"
label="Task"
value={(outcomeAction.task as string) ?? null}
tasks={otherTasks}
error={
fieldErrors?.[`${guid}.outcomeActions[${index}].task`]
}
onChange={(name, val) =>
updateOutcome(index, { ...outcomeAction, task: val })
}
/>
</td>
<td className="form-group">
<Button
onClick={() => removeOutcome(index)}
buttonType={ButtonType.secondary}
>
Remove
</Button>
</td>
</tr>
))}
</tbody>
</table>
<ErrorBlock
error={Object.values(fieldErrors ?? {})
.filter((_, i) =>
Object.keys(fieldErrors ?? {}).some(
(key) => key === `${guid}.outcomeActions[${i}]`,
),
)
.join("; ")}
/>
</div>
{renderTaskField(
task,
onChange,
"overrideDefaultTaskProgression",
"Override Default Task Progression",
InputType.checkbox,
fieldErrors,
undefined, //placeholder
undefined, //maxLength
undefined, //extraProps
"Checking this will override the default task progression, allowing manual control over task flow. If unchecked, the default task progression will be followed if no outcome actions are triggered.", //title
)}
</>
);
};
@ -178,7 +197,9 @@ export function defaultsAssignment(
task: TaskDefinition,
tasks: TaskDefinition[],
ctx: defaultsContext,
) {}
) {
task.config.overrideDefaultTaskProgression = true;
}
export const outcomeOfApprovalVerdictRegistryEntry: capabilityEditorRegistryEntry =
{

View File

@ -13,21 +13,31 @@ export const renderTaskField = (
extraProps?: {
options?: { value: string; label: string }[];
},
title?: string,
) => {
const handleChange = (
e: React.ChangeEvent<
HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement
>,
) => {
const newValue = e.target.value;
let newValue: string | boolean;
onChange({
...task,
if (type === InputType.checkbox) {
newValue = (e.target as HTMLInputElement).checked;
} else {
newValue = e.target.value;
}
const clone = structuredClone(task);
const updated = {
...clone,
config: {
...task.config,
...clone.config,
[field]: newValue,
},
});
};
onChange(updated);
};
return renderTaskInput(
@ -41,13 +51,14 @@ export const renderTaskField = (
placeholder ?? "",
maxLength ?? 0,
extraProps,
title,
);
};
export const renderTaskInput = (
name: string,
label: string,
value: string | number | readonly string[] | undefined,
value: string | number | readonly string[] | boolean | undefined,
error: string | undefined,
type: InputType = InputType.text,
onChange: (
@ -59,6 +70,7 @@ export const renderTaskInput = (
extraProps?: {
options?: { value: string; label: string }[];
},
title?: string,
) => {
const normalisedValue =
type === InputType.multiselect
@ -77,6 +89,7 @@ export const renderTaskInput = (
onChange={onChange}
readOnly={readOnly}
placeHolder={placeholder}
title={title}
{...extraProps}
/>
);