webui/src/modules/manager/workflowTemplates/WorkflowTemplateDetails.tsx

244 lines
7.1 KiB
TypeScript

import React, { useEffect, useState, useCallback } from "react";
import { useTranslation } from "react-i18next";
import { Namespaces } from "../../../i18n/i18n";
import HorizontalTabs from "../../../components/common/HorizionalTabs";
import Tab from "../../../components/common/Tab";
import GeneralTab from "./components/GeneralTab";
import { Navigate, useParams } from "react-router-dom";
import templateVersionsService, {
CreateWorkflowTemplateVersion,
ReadWorkflowTemplateVersion,
TaskDefinition,
} from "./services/WorkflowTemplateService";
import { toast } from "react-toastify";
import Joi from "joi";
import {
renderButton,
renderError,
} from "../../../components/common/formHelpers";
import ErrorBlock from "../../../components/common/ErrorBlock";
import authentication from "../../frame/services/authenticationService";
import { GeneralIdRef, MakeGeneralIdRef } from "../../../utils/GeneralIdRef";
import TasksTab from "./components/TasksTab";
import { useFormWithGuard } from "../../../components/common/useFormRouter";
import VisualiserTab from "./components/VisualisetTab";
const WorkflowTemplateDetails: React.FC<{ editMode: boolean }> = ({
editMode,
}) => {
const { t } = useTranslation(Namespaces.Common);
const { workflowTemplateId } = useParams<{ workflowTemplateId: string }>();
const [activeTab, setActiveTab] = React.useState("general");
const [taskValidation, setTaskValidation] = useState<Record<string, boolean>>(
{},
);
// useForm promoted to the parent
const form = useFormWithGuard({
loaded: false,
data: {
workflowName: "",
domainId: {} as GeneralIdRef,
activityNameTemplate: "",
description: "",
tasks: [] as TaskDefinition[],
} as CreateWorkflowTemplateVersion | ReadWorkflowTemplateVersion,
errors: {},
redirect: "",
});
const heading = editMode
? t("EditWorkflowTemplate")
: t("AddWorkflowTemplate");
// -----------------------------
// Joi schema (same pattern as original GeneralTab)
// -----------------------------
form.schema = {
workflowName: Joi.string()
.required()
.max(450)
.label(t("WorkflowTemplateName")),
activityNameTemplate: Joi.string()
.required()
.max(450)
.label(t("ActivityNameTemplate")),
description: Joi.string().required().allow("").label(t("Description")),
domainId: Joi.required(),
tasks: Joi.array().required(),
deleted: Joi.boolean().optional(),
guid: Joi.string().optional(),
id: Joi.number().optional(),
lastUpdated: Joi.date().optional(),
version: Joi.number().optional(),
workflowId: Joi.optional(),
};
const load = async () => {
let newData: CreateWorkflowTemplateVersion | ReadWorkflowTemplateVersion = {
...form.state.data,
};
if (editMode && workflowTemplateId) {
try {
const loadedData = await templateVersionsService.getTemplateVersion(
BigInt(workflowTemplateId),
);
if (loadedData) {
newData = loadedData;
}
} catch (ex: any) {
form.handleGeneralError(ex);
}
} else {
const user = authentication.getCurrentUser();
newData.domainId = MakeGeneralIdRef(user?.domainid);
}
form.setState({ ...form.state, loaded: true, data: newData });
};
// -----------------------------
// Load existing template (edit mode)
// -----------------------------
useEffect(() => {
load();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [editMode, workflowTemplateId]);
// -----------------------------
// Save handler (called by useForm.handleSubmit)
// -----------------------------
const doSubmit = async (buttonName: string) => {
try {
const { data } = form.state;
var json = JSON.stringify(data);
if (editMode) {
await templateVersionsService.putTemplateVersion(
data as ReadWorkflowTemplateVersion,
);
toast.info(t("WorkflowTemplateEdited"));
} else {
await templateVersionsService.postTemplateVersion(
data as CreateWorkflowTemplateVersion,
);
toast.info(t("WorkflowTemplateAdded"));
}
if (buttonName === "save") {
form.setState({ ...form.state, redirect: "/workflowTemplates" });
}
form.markAsSaved();
load();
} catch (ex: any) {
form.handleGeneralError(ex);
}
};
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
form.handleSubmit(e, doSubmit);
};
//const [tasksValid, setTasksValid] = useState(true);
const handleTaskValidate = useCallback((taskId: string, isValid: boolean) => {
setTaskValidation((prev) => {
const updated = { ...prev, [taskId]: isValid };
return updated;
});
}, []);
const { redirect, errors: formErrors, data } = form.state;
if (redirect) return <Navigate to={redirect} />;
let errors = { ...formErrors };
const generalTabValid = !(
errors["domainId"] ||
errors["workflowName"] ||
errors["activityNameTemplate"] ||
errors["description"]
);
const tasksValid = Object.values(taskValidation).every((v) => v === true);
if (!tasksValid) {
errors["tasks"] = t("TasksValidationError");
}
// -----------------------------
// Tabs
// -----------------------------
const tabs = [
<Tab
key="general"
id="general"
label={t("General")}
hasError={!generalTabValid}
>
<GeneralTab
data={data}
errors={errors}
isEditMode={editMode}
onFieldChange={form.handleChange}
handleDomainPickerChange={(name, values) =>
form.handlePickerChange(name, values[0].value)
}
/>
</Tab>,
<Tab key="tasks" id="tasks" label={t("Tasks")} hasError={!tasksValid}>
<TasksTab
data={data}
errors={errors}
isEditMode={editMode}
onTasksChange={form.handleTasksChange}
onValidate={handleTaskValidate}
taskValidation={taskValidation}
/>
</Tab>,
<Tab key="fields" id="fields" label={t("Fields")}>
<div>Fields editor coming soon</div>
</Tab>,
<Tab key="visualFlow" id="visualFlow" label={t("Visual Flow")}>
<VisualiserTab data={data} taskValidation={taskValidation} />
</Tab>,
];
return (
<div className="full-height-container">
<form onSubmit={handleSubmit}>
<h1>{heading}</h1>
<div style={{ marginBottom: "1rem" }}>
{editMode && renderButton(t("Save"), errors, "apply")}
{renderButton(t("SaveAndClose"), errors, "save")}
</div>
{Object.keys(errors).length > 0 && (
<ErrorBlock error={t("ResolveAllValidation")} />
)}
{renderError("_general", errors)}
<HorizontalTabs
hashSegment={0}
activeTab={activeTab}
onTabChange={setActiveTab}
>
{tabs}
</HorizontalTabs>
</form>
</div>
);
};
export default WorkflowTemplateDetails;