webui/src/modules/manager/customfields/customFieldDetails.tsx
2026-02-12 18:12:55 +00:00

645 lines
19 KiB
TypeScript

import Joi from "joi";
import React, { useEffect } from "react";
import { Navigate, useParams } from "react-router-dom";
import { toast } from "react-toastify";
import { useTranslation } from "react-i18next";
import { Namespaces } from "../../../i18n/i18n";
import { InputType } from "../../../components/common/Input";
import {
renderInput,
renderButton,
renderError,
renderSelect,
renderInputNumber,
renderInputTextarea,
renderGlossaryPicker,
renderSequencePicker,
} from "../../../components/common/formHelpers";
import { MakeGeneralIdRef } from "../../../utils/GeneralIdRef";
import customFieldsService, {
numberParams,
textParams,
} from "./services/customFieldsService";
import Option from "../../../components/common/option";
import { GeneralIdRef } from "./../../../utils/GeneralIdRef";
import {
CustomFieldValue,
SystemGlossaries,
} from "../glossary/services/glossaryService";
import Loading from "../../../components/common/Loading";
import { useFormWithGuard } from "../../../components/common/useFormRouter";
interface CustomFieldDetailsProps {
editMode?: boolean;
}
const CustomFieldDetails: React.FC<CustomFieldDetailsProps> = ({
editMode = false,
}) => {
const { customFieldId } = useParams<{ customFieldId: string }>();
const { t } = useTranslation(Namespaces.Common);
const labelName = t("Name");
const labelFieldType = t("FieldType");
const labelMultiLine = t("MultiLine");
const labelDefaultValue = t("DefaultValue");
const labelMinValue = t("MinimumValue");
const labelMaxValue = t("MaximumValue");
const labelStep = t("Step");
const labelRequired = t("Required");
const labelMinEntries = t("MinEntries");
const labelMaxEntries = t("MaxEntriesEmptyUnlimited");
let labelRefElementId = t("SequenceFormGlossary");
const labelApply = t("Save");
const labelSave = t("SaveAndClose");
const form = useFormWithGuard({
loaded: false,
data: {
name: "",
fieldType: "Text",
defaultValue: "",
multiLine: false,
minEntries: 0,
maxEntries: 1,
refElementId: undefined,
minValue: undefined,
maxValue: undefined,
step: undefined,
required: false,
},
errors: {},
redirect: "",
});
form.schema = {
name: Joi.string().required().max(450).label(labelName),
fieldType: Joi.string().required().label(labelFieldType),
multiLine: Joi.boolean().label(labelMultiLine),
minEntries: Joi.number().min(0).label(labelMinEntries),
maxEntries: Joi.number().empty("").label(labelMaxEntries),
refElementId: Joi.when("fieldType", {
is: Joi.string().valid("Sequence"),
then: Joi.object({
id: Joi.optional(),
guid: Joi.optional(),
}).required(),
}).when("fieldType", {
is: Joi.string().valid("Glossary"),
then: Joi.array()
.min(1)
.items(
Joi.object({
displayValue: Joi.string().optional(),
value: Joi.object({
id: Joi.optional(),
guid: Joi.optional(),
}).required(),
}),
)
.required(),
}),
minValue: Joi.number().allow("").label(labelMinValue),
maxValue: Joi.number().allow("").label(labelMaxValue),
step: Joi.number().optional().allow("").min(0).label(labelStep),
required: Joi.boolean().label(labelRequired),
//defaultValue: Joi.string().allow("").label(labelDefaultValue)
defaultValue: Joi.when("fieldType", {
is: Joi.string().valid("Number"),
then: Joi.when("minValue", {
is: Joi.any().valid(null, ""),
then: Joi.number(),
otherwise: Joi.number()
.min(Joi.ref("minValue"))
.message(t("DefaultMustBeGreaterThanOrEqualToMinimumValue")),
})
.when("maxValue", {
is: Joi.any().valid(null, ""),
then: Joi.number(),
otherwise: Joi.number()
.max(Joi.ref("maxValue"))
.message(t("DefaultMustBeLessThanOrEqualToMaximumValue")),
})
.allow(""),
otherwise: Joi.string().allow(""),
}).label(labelDefaultValue),
};
const isEditMode = () => {
return editMode;
};
useEffect(() => {
const loadData = async () => {
if (customFieldId !== undefined) {
try {
const loadedData = await customFieldsService.getField(
BigInt(customFieldId),
);
if (loadedData) {
const newData = { ...form.state.data };
newData.name = loadedData.name;
newData.fieldType = loadedData.fieldType;
newData.defaultValue = loadedData.defaultValue;
newData.minEntries = loadedData.minEntries;
newData.maxEntries = loadedData.maxEntries;
switch (newData.fieldType) {
case "Glossary":
let convertedRefElementId: CustomFieldValue = {
value: loadedData.refElementId,
};
newData.refElementId = [convertedRefElementId];
newData.required = loadedData.minEntries > 0;
break;
case "Sequence":
newData.refElementId = loadedData.refElementId;
break;
case "Domain":
newData.required = loadedData.minEntries > 0;
break;
}
if (loadedData.parameters !== undefined) {
switch (newData.fieldType) {
case "Number":
newData.required = loadedData.minEntries > 0;
const parameters: numberParams = JSON.parse(
loadedData.parameters,
);
newData.minValue = parameters.minValue ?? undefined;
newData.maxValue = parameters.maxValue ?? undefined;
newData.step = parameters.step ?? undefined;
break;
case "Text":
const textParameters: textParams = JSON.parse(
loadedData.parameters,
);
newData.multiLine = textParameters.multiLine ?? false;
break;
}
}
form.setState({ loaded: true, data: newData });
} else {
form.setState({ loaded: false });
}
} catch (ex: any) {
form.handleGeneralError(ex);
}
}
if (!editMode) form.setState({ loaded: true });
};
loadData();
}, [customFieldId, editMode]); // eslint-disable-line react-hooks/exhaustive-deps
const doSubmit = async (buttonName: string) => {
try {
const { name, fieldType } = form.state.data;
const nameStr = typeof name === "string" ? name : "";
const fieldTypeStr = typeof fieldType === "string" ? fieldType : "";
let { refElementId, defaultValue, minEntries, maxEntries, required } =
form.state.data;
let numberParams: numberParams | undefined = undefined;
let textParams: textParams | undefined = undefined;
let params;
let refElementIdValue: GeneralIdRef | undefined;
switch (fieldTypeStr) {
case "Sequence":
minEntries = 1;
maxEntries = 1;
defaultValue = "";
refElementIdValue = refElementId as GeneralIdRef;
break;
case "FormTemplate":
minEntries = 1;
maxEntries = 1;
defaultValue = "";
break;
case "Domain":
minEntries = required ? 1 : 0;
maxEntries = maxEntries === 0 ? undefined : maxEntries;
defaultValue = "";
break;
case "Glossary":
minEntries = required ? 1 : 0;
maxEntries = maxEntries === 0 ? undefined : maxEntries;
defaultValue = "";
refElementIdValue = (refElementId as CustomFieldValue[])[0]
.value as GeneralIdRef;
break;
case "Text":
minEntries = 1;
maxEntries = 1;
let { multiLine } = form.state.data;
textParams = { multiLine: Boolean(multiLine) };
params = textParams;
refElementIdValue = undefined;
break;
case "Number":
refElementIdValue = undefined;
let { minValue, maxValue, step } = form.state.data;
const minValueNum =
minValue === null || minValue === undefined || minValue === ""
? undefined
: Number(minValue);
const maxValueNum =
maxValue === null || maxValue === undefined || maxValue === ""
? undefined
: Number(maxValue);
const stepNum =
step === null || step === undefined || step === ""
? undefined
: Number(step);
numberParams = {
minValue: minValueNum,
maxValue: maxValueNum,
step: stepNum,
};
params = numberParams;
minEntries = required ? 1 : 0;
maxEntries = 1;
break;
default:
refElementIdValue = undefined;
}
const minEntriesValue =
typeof minEntries === "number"
? minEntries
: minEntries === undefined || minEntries === null || minEntries === ""
? 0
: Number(minEntries);
const cleanMaxEntries: Number | undefined =
maxEntries === "" ? undefined : Number(maxEntries);
const defaultValueStr =
typeof defaultValue === "string"
? defaultValue
: defaultValue === undefined || defaultValue === null
? ""
: String(defaultValue);
if (isEditMode()) {
var generalIdRef = MakeGeneralIdRef(BigInt(customFieldId!));
const response = await customFieldsService.putField(
generalIdRef,
nameStr,
fieldTypeStr,
defaultValueStr,
minEntriesValue,
cleanMaxEntries,
refElementIdValue,
params,
);
if (response) {
toast.info(t("CustomFieldEdited"));
}
} else {
const response = await customFieldsService.postField(
nameStr,
fieldTypeStr,
defaultValueStr,
minEntriesValue,
cleanMaxEntries,
refElementIdValue,
params,
);
if (response) {
toast.info(t("NewCustomFieldAdded"));
}
}
if (buttonName === "save") form.setState({ redirect: "/customfields" });
form.markAsSaved();
} catch (ex: any) {
form.handleGeneralError(ex);
}
};
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
form.handleSubmit(e, doSubmit);
};
if (form.state.redirect) return <Navigate to={form.state.redirect} />;
const { fieldType } = form.state.data;
const fieldTypeValue = typeof fieldType === "string" ? fieldType : "";
let mode = t("Add");
if (isEditMode()) mode = t("Edit");
const fieldTypeOptions: Option[] = [
{ _id: "Text", name: t("Text") },
{ _id: "Number", name: t("Number") },
{ _id: "Sequence", name: t("Sequence") },
{ _id: "FormTemplate", name: t("FormTemplate") },
{ _id: "Glossary", name: t("Glossary") },
{ _id: "Domain", name: t("Domain") },
];
switch (fieldTypeValue) {
case "Sequence":
labelRefElementId = t("Sequence");
break;
case "FormTemplate":
labelRefElementId = t("Form");
break;
case "Glossary":
labelRefElementId = t("Glossary");
break;
}
return (
<Loading loaded={form.state.loaded}>
<h1>
{mode} {t("CustomField")}
</h1>
<form onSubmit={handleSubmit}>
{renderError("_general", form.state.errors)}
{renderInput(
"name",
labelName,
form.state.data,
form.state.errors,
InputType.text,
false,
"",
"",
0,
true,
undefined,
form.handleChange,
)}
{renderSelect(
"fieldType",
labelFieldType,
form.state.data,
form.state.errors,
fieldTypeOptions,
form.handleSelectChange,
)}
{fieldTypeValue === "Domain" && (
<>
{renderInput(
"required",
labelRequired,
form.state.data,
form.state.errors,
InputType.checkbox,
false,
"",
"",
0,
true,
undefined,
form.handleChange,
)}
{renderInputNumber(
"maxEntries",
labelMaxEntries,
form.state.data,
form.state.errors,
false,
"",
undefined,
undefined,
1,
form.handleChange,
)}
</>
)}
{fieldTypeValue === "Glossary" && (
<>
{renderGlossaryPicker(
true,
"refElementId",
labelRefElementId,
form.state.data,
form.state.errors,
1,
SystemGlossaries,
form.handleGlossaryPickerChange,
)}
{renderInput(
"required",
labelRequired,
form.state.data,
form.state.errors,
InputType.checkbox,
false,
"",
"",
0,
true,
undefined,
form.handleChange,
)}
{renderInputNumber(
"maxEntries",
labelMaxEntries,
form.state.data,
form.state.errors,
false,
"",
undefined,
undefined,
1,
form.handleChange,
)}
</>
)}
{fieldTypeValue === "Sequence" && (
<>
{renderSequencePicker(
true,
"refElementId",
labelRefElementId,
form.state.data,
form.state.errors,
form.handlePickerChange,
)}
</>
)}
{fieldTypeValue === "Text" && (
<>
{renderInput(
"multiLine",
labelMultiLine,
form.state.data,
form.state.errors,
InputType.checkbox,
false,
"",
"",
0,
true,
undefined,
form.handleChange,
)}
{form.state.data.multiLine === true &&
renderInputTextarea(
true,
"defaultValue",
labelDefaultValue,
form.state.data,
form.state.errors,
false,
"",
form.handleTextAreaChange,
)}
{form.state.data.multiLine === false &&
renderInput(
"defaultValue",
labelDefaultValue,
form.state.data,
form.state.errors,
InputType.text,
false,
"",
"",
0,
true,
undefined,
form.handleChange,
)}
</>
)}
{fieldTypeValue === "Number" && (
<>
{renderInput(
"required",
labelRequired,
form.state.data,
form.state.errors,
InputType.checkbox,
false,
"",
"",
0,
true,
undefined,
form.handleChange,
)}
{renderInputNumber(
"minValue",
labelMinValue,
form.state.data,
form.state.errors,
false,
"",
undefined,
undefined,
1,
form.handleChange,
)}
{renderInputNumber(
"maxValue",
labelMaxValue,
form.state.data,
form.state.errors,
false,
"",
undefined,
undefined,
1,
form.handleChange,
)}
{renderInput(
"step",
labelStep,
form.state.data,
form.state.errors,
InputType.number,
false,
"",
"",
0,
true,
undefined,
form.handleChange,
)}
{renderInputNumber(
"defaultValue",
labelDefaultValue,
form.state.data,
form.state.errors,
false,
"",
undefined,
undefined,
1,
form.handleChange,
)}
</>
)}
{![
"Domain",
"Glossary",
"Sequence",
"FormTemplate",
"Text",
"Number",
].includes(fieldTypeValue) && (
<>
{renderInput(
"defaultValue",
labelDefaultValue,
form.state.data,
form.state.errors,
InputType.text,
false,
"",
"",
0,
true,
undefined,
form.handleChange,
)}
{renderInput(
"minEntries",
labelMinEntries,
form.state.data,
form.state.errors,
InputType.number,
false,
"",
"",
0,
true,
undefined,
form.handleChange,
)}
{renderInput(
"maxEntries",
labelMaxEntries,
form.state.data,
form.state.errors,
InputType.number,
false,
"",
"",
0,
true,
undefined,
form.handleChange,
)}
</>
)}
{isEditMode() && renderButton(labelApply, form.state.errors, "apply")}
{renderButton(labelSave, form.state.errors, "save")}
</form>
</Loading>
);
};
export default CustomFieldDetails;