Started working on the assignments editor
This commit is contained in:
parent
0130a578ea
commit
3cc7a42f57
@ -11,6 +11,7 @@ interface UserPickerProps {
|
|||||||
value: any;
|
value: any;
|
||||||
domain?: GeneralIdRef;
|
domain?: GeneralIdRef;
|
||||||
onChange?: (name: string, value: GeneralIdRef) => void;
|
onChange?: (name: string, value: GeneralIdRef) => void;
|
||||||
|
includeLabel?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function UserPicker({
|
export default function UserPicker({
|
||||||
@ -20,6 +21,7 @@ export default function UserPicker({
|
|||||||
value,
|
value,
|
||||||
domain,
|
domain,
|
||||||
onChange,
|
onChange,
|
||||||
|
includeLabel = true,
|
||||||
}: UserPickerProps) {
|
}: UserPickerProps) {
|
||||||
const [options, setOptions] = useState<Option[] | undefined>(undefined);
|
const [options, setOptions] = useState<Option[] | undefined>(undefined);
|
||||||
|
|
||||||
@ -59,6 +61,7 @@ export default function UserPicker({
|
|||||||
options={options}
|
options={options}
|
||||||
includeBlankFirstEntry={true}
|
includeBlankFirstEntry={true}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
|
includeLabel={includeLabel}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -104,7 +104,11 @@ const WorkflowTemplateDetails: React.FC<{ editMode: boolean }> = ({
|
|||||||
// -----------------------------
|
// -----------------------------
|
||||||
const doSubmit = async (buttonName: string) => {
|
const doSubmit = async (buttonName: string) => {
|
||||||
try {
|
try {
|
||||||
//const { name } = form.state.data;
|
const { data } = form.state;
|
||||||
|
|
||||||
|
var json = JSON.stringify(data);
|
||||||
|
|
||||||
|
console.log("Submitting workflow template:", json);
|
||||||
|
|
||||||
if (editMode) {
|
if (editMode) {
|
||||||
//await templateVersionsService.putTemplateVersion({ name });
|
//await templateVersionsService.putTemplateVersion({ name });
|
||||||
|
|||||||
@ -0,0 +1,214 @@
|
|||||||
|
import React from "react";
|
||||||
|
import UserPicker from "../../../../../components/pickers/UserPicker";
|
||||||
|
import { GeneralIdRef } from "../../../../../utils/GeneralIdRef";
|
||||||
|
import { TaskDefinition } from "../../services/WorkflowTemplateService";
|
||||||
|
import {
|
||||||
|
CapabilityEditorProps,
|
||||||
|
capabilityEditorRegistryEntry,
|
||||||
|
defaultsContext,
|
||||||
|
} from "../useCapabilityDefaults";
|
||||||
|
import Button, { ButtonType } from "../../../../../components/common/Button";
|
||||||
|
|
||||||
|
// TODO: Replace with your real RolePicker and RaciPicker
|
||||||
|
const RolePicker = (props: any) => (
|
||||||
|
<div style={{ marginBottom: 8 }}>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
name={props.name}
|
||||||
|
value={props.value?.id ?? ""}
|
||||||
|
onChange={(e) =>
|
||||||
|
props.onChange(props.name, { id: BigInt(e.target.value) })
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
const RaciPicker = (props: any) => (
|
||||||
|
<div style={{ marginBottom: 8 }}>
|
||||||
|
<select
|
||||||
|
name={props.name}
|
||||||
|
value={props.value}
|
||||||
|
onChange={(e) => props.onChange(props.name, e.target.value)}
|
||||||
|
>
|
||||||
|
<option value="Responsible">Responsible</option>
|
||||||
|
<option value="Accountable">Accountable</option>
|
||||||
|
<option value="Consulted">Consulted</option>
|
||||||
|
<option value="Informed">Informed</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
// ---------------------------
|
||||||
|
// Domain Interfaces
|
||||||
|
// ---------------------------
|
||||||
|
|
||||||
|
export interface ITaskAssignee {
|
||||||
|
role?: GeneralIdRef | null;
|
||||||
|
contact?: GeneralIdRef | null;
|
||||||
|
raci: string; // Raci enum as string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IAssigneesCapability {
|
||||||
|
assignees: ITaskAssignee[];
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------
|
||||||
|
// Editor Component
|
||||||
|
// ---------------------------
|
||||||
|
|
||||||
|
export const AssigneesOfITaskAssigneeEditor: React.FC<CapabilityEditorProps> = (
|
||||||
|
props,
|
||||||
|
) => {
|
||||||
|
const { task, onChange, fieldErrors } = props;
|
||||||
|
const assignees = (task.config.assignees ?? []) as ITaskAssignee[];
|
||||||
|
|
||||||
|
function updateAssignee(index: number, updated: ITaskAssignee) {
|
||||||
|
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",
|
||||||
|
});
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
Select assigness (you can select either a role or a contact)
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Role</th>
|
||||||
|
<th>Contact</th>
|
||||||
|
<th>RACI</th>
|
||||||
|
<th>
|
||||||
|
<Button onClick={addAssignee} buttonType={ButtonType.secondary}>
|
||||||
|
Add Assignee
|
||||||
|
</Button>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{assignees.map((assignee, index) => (
|
||||||
|
<tr key={index}>
|
||||||
|
<td>
|
||||||
|
<RolePicker
|
||||||
|
name="role"
|
||||||
|
label="Role"
|
||||||
|
value={assignee.role}
|
||||||
|
error={fieldErrors?.[`assignees[${index}].role`]}
|
||||||
|
onChange={(name: string, val: GeneralIdRef | null) =>
|
||||||
|
updateAssignee(index, { ...assignee, role: val })
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<UserPicker
|
||||||
|
includeLabel={false}
|
||||||
|
name="contact"
|
||||||
|
label="Contact"
|
||||||
|
value={assignee.contact}
|
||||||
|
error={fieldErrors?.[`assignees[${index}].contact`]}
|
||||||
|
onChange={(name: string, val: GeneralIdRef | null) =>
|
||||||
|
updateAssignee(index, { ...assignee, contact: val })
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<RaciPicker
|
||||||
|
name="raci"
|
||||||
|
label="RACI"
|
||||||
|
value={assignee.raci}
|
||||||
|
error={fieldErrors?.[`assignees[${index}].raci`]}
|
||||||
|
onChange={(name: string, val: string) =>
|
||||||
|
updateAssignee(index, { ...assignee, raci: val })
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<Button
|
||||||
|
onClick={() => removeAssignee(index)}
|
||||||
|
buttonType={ButtonType.secondary}
|
||||||
|
>
|
||||||
|
Remove
|
||||||
|
</Button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---------------------------
|
||||||
|
// Validation
|
||||||
|
// ---------------------------
|
||||||
|
|
||||||
|
const runValidation = (
|
||||||
|
task: TaskDefinition,
|
||||||
|
tasks: TaskDefinition[],
|
||||||
|
): Record<string, string> => {
|
||||||
|
const errors: Record<string, string> = {};
|
||||||
|
const assignees = task.config.assignees as ITaskAssignee[] | undefined;
|
||||||
|
|
||||||
|
if (!assignees || assignees.length === 0) {
|
||||||
|
errors["assignees"] = "At least one assignee is required.";
|
||||||
|
return errors;
|
||||||
|
}
|
||||||
|
|
||||||
|
assignees.forEach((a, i) => {
|
||||||
|
if (!a.role && !a.contact) {
|
||||||
|
//errors[`assignees[${i}]`] = "Either role or contact must be selected.";
|
||||||
|
errors[`assignees`] = "Either role or contact must be selected.";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!a.raci) {
|
||||||
|
//errors[`assignees[${i}].raci`] = "RACI is required.";
|
||||||
|
errors[`assignees`] = "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 AssigneesOfITaskAssigneeRegistryEntry: capabilityEditorRegistryEntry =
|
||||||
|
{
|
||||||
|
Editor: AssigneesOfITaskAssigneeEditor,
|
||||||
|
DefaultsAssignment: defaultsAssignment,
|
||||||
|
ValidationRunner: runValidation,
|
||||||
|
};
|
||||||
@ -1,4 +1,5 @@
|
|||||||
import { tagsEditorRegistryEntry } from "./CapabilityEditors/TagsEditor";
|
import { tagsEditorRegistryEntry } from "./CapabilityEditors/TagsEditor";
|
||||||
|
import { AssigneesOfITaskAssigneeRegistryEntry } from "./CapabilityEditors/AssigneesOfITaskAssigneeEditor";
|
||||||
import { taskCoreEditorRegistryEntry } from "./CapabilityEditors/TaskCoreEditor";
|
import { taskCoreEditorRegistryEntry } from "./CapabilityEditors/TaskCoreEditor";
|
||||||
import { capabilityEditorRegistryEntry } from "./useCapabilityDefaults";
|
import { capabilityEditorRegistryEntry } from "./useCapabilityDefaults";
|
||||||
|
|
||||||
@ -8,4 +9,5 @@ export const capabilityEditorRegistry: Record<
|
|||||||
> = {
|
> = {
|
||||||
ITask: taskCoreEditorRegistryEntry,
|
ITask: taskCoreEditorRegistryEntry,
|
||||||
ITags: tagsEditorRegistryEntry,
|
ITags: tagsEditorRegistryEntry,
|
||||||
|
"IAssignees<ITaskAssignee>": AssigneesOfITaskAssigneeRegistryEntry,
|
||||||
};
|
};
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user