import React from "react";
import Joi from "joi";
import Input, { InputType } from "./Input";
import ToggleSlider from "./ToggleSlider";
import Select from "./Select";
import Option from "./option";
import { GeneralIdRef } from "../../utils/GeneralIdRef";
import SequencePicker from "../pickers/SequencePicker";
import GlossaryPicker from "../pickers/GlossaryPicker";
import CustomFieldsEditor, { CustomFieldEditorAdd, CustomFieldEditorDelete } from "./CustomFieldsEditor";
import { CustomField, numberParams, textParams } from "../../modules/manager/customfields/services/customFieldsService";
import FormTemplatePicker from "../pickers/FormTemplatePicker";
import { CustomFieldValue, CustomFieldValues, Glossary } from "../../modules/manager/glossary/services/glossaryService";
import TemplateEditor from "./TemplateEditor";
import DomainPicker from "../pickers/DomainPicker";
import UserPicker from "../pickers/UserPicker";
import Button, { ButtonType } from "./Button";
import Expando from "./expando";
import ErrorBlock from "./ErrorBlock";
import SsoProviderPicker from "../pickers/SsoProviderPicker";
export interface FormError {
[key: string]: string;
}
export interface FormData {
[key: string]: string | number | boolean | CustomFieldValue[] | GeneralIdRef | CustomField[] | bigint | Glossary | null | undefined;
}
export interface businessValidationError {
path: string;
message: string;
}
export interface businessValidationResult {
details: businessValidationError[];
}
export interface propertyValue {
name: string;
value: string | boolean | number;
}
export interface joiSchema {
[key: string]: object;
}
export interface FormState {
loaded: boolean;
data: FormData;
customFields?: CustomField[];
errors: FormError;
redirect?: string;
}
export interface Match
{
params: P;
isExact: boolean;
path: string;
url: string;
}
export interface State {
from: Location;
}
export interface LocationProps {
hash: string;
pathname: string;
search: string;
state: State;
}
export interface FormProps
{
location: LocationProps;
match: Match
;
staticContext?: any;
}
class Form
, FS extends FormState> extends React.Component {
schema: joiSchema = {};
validate = (data: FormData) => {
let options: Joi.ValidationOptions = {
context: {},
abortEarly: false,
};
const { customFields } = this.state;
let schema = this.schema;
if (customFields !== undefined) {
for (const customfield of customFields) {
const name = "customfield_" + customfield.id;
switch (customfield.fieldType) {
case "Number":
if (customfield.parameters !== undefined) {
const parameters: numberParams = JSON.parse(customfield.parameters!);
options.context![name + "_minEntries"] = customfield.minEntries;
if (parameters.minValue) options.context![name + "_minValue"] = Number(parameters.minValue);
if (parameters.maxValue) options.context![name + "_maxValue"] = Number(parameters.maxValue);
let minCheck = options.context![name + "_minValue"]
? Joi.number()
.empty("")
.min(options.context![name + "_minValue"])
: Joi.number().empty("");
let maxCheck = options.context![name + "_maxValue"]
? Joi.number()
.empty("")
.max(options.context![name + "_maxValue"])
: Joi.number().empty("");
schema[name] = Joi.array()
.min(1)
.items(
Joi.object({
displayValue: Joi.string().allow(""),
value: Joi.when("$" + name + "_minEntries", {
is: 0,
then: Joi.number().empty(""),
otherwise: Joi.number().required(),
})
.when("$" + name + "_minValue", {
is: Joi.number(),
then: minCheck,
})
.when("$" + name + "_maxValue", {
is: Joi.number(),
then: maxCheck,
})
.label(customfield.name),
})
);
} else {
schema[name] = Joi.optional().label(customfield.name);
}
break;
default:
schema[name] = Joi.optional().label(customfield.name);
}
}
}
const joiSchema = Joi.object(schema);
const { error } = joiSchema.validate(data, options);
let errors: FormError = {};
if (error) {
if (error.details === undefined) {
errors[error.name] = error.message;
} else {
for (let item of error.details) {
errors[item.path[0]] = item.message;
}
}
}
return errors;
};
GetCustomFieldValues = (customField: CustomField) => {
const name = "customfield_" + customField.id;
const { data } = this.state;
const codedValue = data[name];
let values: CustomFieldValue[] = [];
switch (customField.fieldType) {
case "FormTemplate":
if (codedValue !== undefined) {
const formTemplateValue = { value: JSON.stringify(codedValue as GeneralIdRef) };
values.push(formTemplateValue);
}
break;
case "Sequence":
if (codedValue !== undefined) {
values = codedValue as CustomFieldValue[];
}
break;
case "Glossary":
if (codedValue !== undefined) {
values = codedValue as CustomFieldValue[];
}
break;
case "Domain":
if (codedValue !== undefined) {
values = codedValue as CustomFieldValue[];
}
break;
case "Text":
const textParameters: textParams = JSON.parse(customField.parameters!);
if (textParameters.multiLine) {
const textValue = {
value: codedValue === undefined ? customField.defaultValue : codedValue,
displayValue: codedValue === undefined ? customField.defaultValue : codedValue,
} as CustomFieldValue;
values.push(textValue);
} else {
if (codedValue === undefined) {
const numberValue = {
value: customField.defaultValue,
displayValue: customField.defaultValue,
} as CustomFieldValue;
values.push(numberValue);
} else {
values = codedValue as CustomFieldValue[];
}
}
break;
case "Number":
if (codedValue === undefined) {
const numberValue = {
value: customField.defaultValue,
displayValue: customField.defaultValue,
} as CustomFieldValue;
values.push(numberValue);
} else {
values = codedValue as CustomFieldValue[];
}
break;
default:
const textValue = { value: codedValue === undefined ? customField.defaultValue : String((codedValue as CustomFieldValue[])[0].displayValue) };
values.push(textValue);
break;
}
return values;
};
CustomFieldValues = () => {
const { customFields } = this.state;
let result: CustomFieldValues[] = [];
if (customFields === undefined) {
return result;
}
for (const customfield of customFields) {
const values = this.GetCustomFieldValues(customfield);
const id: GeneralIdRef = {
id: customfield.id,
guid: customfield.guid,
};
const newItem: CustomFieldValues = {
id,
values,
};
result.push(newItem);
}
return result;
};
setCustomFieldValues(data: object, customFieldValues: CustomFieldValues[], customFields: CustomField[]) {
if (customFieldValues !== undefined) {
for (const x of customFieldValues) {
const customfieldName = "customfield_" + x.id.id;
switch (this.getCustomFieldType(x, customFields).toLowerCase()) {
case "glossary":
case "domain":
case "number":
case "text":
(data as any)[customfieldName] = x.values.map((x) => {
return {
displayValue: x.displayValue,
value: x.value,
};
});
break;
case "formtemplate":
case "multilinetext":
(data as any)[customfieldName] = x.values[0].value;
break;
default:
(data as any)[customfieldName] = x.values;
break;
}
}
}
}
getCustomFieldType = (field: CustomFieldValues, childCustomFieldDefinition: CustomField[]): string => {
const fieldDefinition = childCustomFieldDefinition.filter((x) => x.id === field.id.id)[0];
if (fieldDefinition.parameters) {
const textParameters: textParams = JSON.parse(fieldDefinition.parameters!);
if (textParameters.multiLine) return "multilinetext";
}
return fieldDefinition.fieldType;
};
handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
const submitEvent = e.nativeEvent as SubmitEvent;
const submitter = submitEvent.submitter as any;
const errors = this.validate(this.state.data);
this.setState({ errors: errors });
const disabled = Object.keys(errors).length > 0;
if (disabled) return;
this.doSubmit(submitter.name);
};
doSubmit = async (buttonName: string) => {};
handleGeneralError = (ex: any) => {
const errors: FormError = { ...this.state.errors };
if (ex.response) {
errors._general = ex.response.data.detail;
} else {
errors._general = ex.message;
}
this.setState({ errors });
};
handleChange = (e: React.ChangeEvent) => {
const input = e.currentTarget;
const data: FormData = { ...this.state.data };
if ((input as any).type === InputType.checkbox) {
data[input.name] = !data[input.name];
} else data[input.name] = input.value;
const errors = this.validate(data);
this.setState({ data, errors });
};
handleTextAreaChange = (e: React.ChangeEvent) => {
const input = e.currentTarget;
const data: FormData = { ...this.state.data };
data[input.name] = input.value;
const errors = this.validate(data);
this.setState({ data, errors });
};
handleCustomFieldChange = (e: React.ChangeEvent) => {
const input = e.currentTarget;
const data: FormData = { ...this.state.data };
switch ((input as any).type) {
case InputType.checkbox:
data[input.name] = !data[input.name];
break;
default:
const customFieldValue: CustomFieldValue = {
displayValue: input.value,
value: input.value,
};
data[input.name] = [customFieldValue];
break;
}
const errors = this.validate(data);
this.setState({ data, errors });
};
handleTemplateEditorChange = (name: string, value: string) => {
const data: FormData = { ...this.state.data };
data[name] = value;
const errors = this.validate(data);
this.setState({ data, errors });
};
handleSelectChange = (e: React.ChangeEvent) => {
const input = e.currentTarget;
const data: FormData = { ...this.state.data };
data[input.name] = input.value;
const errors = this.validate(data);
this.setState({ data, errors });
};
handlePickerChange = (name: string, value: GeneralIdRef) => {
const data: FormData = { ...this.state.data };
data[name] = value;
const errors = this.validate(data);
this.setState({ data, errors });
};
handleDomainPickerChange = (name: string, values: CustomFieldValue[]) => {
const data: FormData = { ...this.state.data };
data[name] = values;
const errors = this.validate(data);
this.setState({ data, errors });
};
handleGlossaryPickerChange = (name: string, values: CustomFieldValue[]) => {
const data: FormData = { ...this.state.data };
data[name] = values;
const errors = this.validate(data);
this.setState({ data, errors });
};
handleTemplateFormPickerChange = (name: string, value: GeneralIdRef) => {
const data: FormData = { ...this.state.data };
data[name] = value;
const errors = this.validate(data);
this.setState({ data, errors });
};
handleUserPickerChange = (name: string, value: GeneralIdRef) => {
const data: FormData = { ...this.state.data };
data[name] = value;
const errors = this.validate(data);
this.setState({ data, errors });
};
handleSsoProviderPickerChange = (name: string, value: GeneralIdRef) => {
const data: FormData = { ...this.state.data };
data[name] = value;
const errors = this.validate(data);
this.setState({ data, errors });
};
handleToggleChange = (e: React.ChangeEvent) => {
const input = e.currentTarget;
const { name, checked } = input;
const data: FormData = { ...this.state.data };
data[name] = checked;
const errors = this.validate(data);
this.setState({ data, errors });
};
renderButton(
label: string,
name?: string,
onClick?: (keyValue: any) => {},
testid?: string,
enabled: boolean = true,
buttonType: ButtonType = ButtonType.primary,
overrideErrorChecking: boolean = false
) {
const { errors } = this.state;
let disabled = !enabled || Object.keys(errors).filter((x) => !x.startsWith("_")).length > 0;
if (overrideErrorChecking) disabled = !enabled;
return (
);
}
renderError(name: string) {
const { errors } = this.state;
return ;
}
renderInput(
name: string,
label: string,
type: InputType = InputType.text,
readOnly = false,
defaultValue: string = "",
placeHolder: string = "",
maxLength: number = 0,
visible: boolean = true,
autoComplete: string | undefined = undefined
) {
const { data, errors } = this.state;
let value = data[name];
let cleanValue: string | undefined;
if (value === undefined) {
cleanValue = defaultValue;
} else if (typeof value === "string") {
cleanValue = value as string;
} else if (typeof value === "number") {
cleanValue = String(value);
} else if (typeof value === "boolean") {
cleanValue = String(value);
} else if (value as CustomFieldValue) {
cleanValue = (value as CustomFieldValue).displayValue ?? (value as CustomFieldValue).value.toString();
}
if (readOnly) {
return (
);
} else {
return (
);
}
}
renderInputWithChangeEvent(
name: string,
label: string,
type: InputType = InputType.text,
readOnly = false,
handleChangeEvent: any,
defaultValue: string = "",
placeHolder: string = "",
maxLength: number = 0
) {
const { data, errors } = this.state;
let value = data[name];
let cleanValue: string | undefined;
if (value === undefined) {
cleanValue = defaultValue;
} else if (typeof value === "string") {
cleanValue = value as string;
} else if (typeof value === "number") {
cleanValue = String(value);
} else if (typeof value === "boolean") {
cleanValue = String(value);
} else if (value as CustomFieldValue) {
cleanValue = (value as CustomFieldValue).displayValue ?? (value as CustomFieldValue).value.toString();
}
if (readOnly) {
return (
);
} else {
return (
);
}
}
renderInputNumber(name: string, label: string, readOnly = false, defaultValue: string = "", min?: number, max?: number, step: number = 1) {
const { data, errors } = this.state;
let value = data[name];
let cleanValue: string | undefined;
if (value === undefined) {
cleanValue = defaultValue;
} else if (typeof value === "string") {
cleanValue = value as string;
} else if (typeof value === "number") {
cleanValue = String(value);
} else if (typeof value === "boolean") {
cleanValue = String(value);
} else if (value as CustomFieldValue) {
cleanValue = (value as CustomFieldValue).displayValue ?? (value as CustomFieldValue).value.toString();
}
if (readOnly) {
return ;
} else {
return (
);
}
}
renderInputTextarea(includeLabel: boolean, name: string, label: string, readOnly = false, defaultValue: string = "") {
const { data, errors } = this.state;
let value = data[name];
let cleanValue: string | undefined;
if (value === undefined) {
cleanValue = defaultValue;
} else if (typeof value === "string") {
cleanValue = value as string;
}
if (readOnly) {
return (
);
} else {
return (
);
}
}
renderCustomFieldInput(includeLabel: boolean, name: string, label: string, type: InputType = InputType.text, readOnly = false, defaultValue: string = "") {
const { data, errors } = this.state;
let value = data[name];
let cleanValue: string | undefined;
if (value === undefined) {
cleanValue = defaultValue;
} else if (typeof value === "string") {
cleanValue = value as string;
} else if (typeof value === "number") {
cleanValue = String(value);
} else if (typeof value === "boolean") {
cleanValue = String(value);
} else if (value as CustomFieldValue) {
const customFieldValue = value as CustomFieldValue[];
cleanValue = customFieldValue[0].displayValue ?? customFieldValue[0].value?.toString();
}
if (readOnly) {
return ;
} else {
return (
);
}
}
renderCustomFieldNumber(
includeLabel: boolean,
name: string,
label: string,
readOnly = false,
defaultValue: string = "",
min?: number,
max?: number,
step: number = 1
) {
const { data, errors } = this.state;
let values: CustomFieldValue[] = data[name] as CustomFieldValue[];
let value: CustomFieldValue | undefined = undefined;
if (values) {
if (values.length > 0) {
value = values[0];
}
}
let cleanValue: string | undefined;
if (value === undefined) {
cleanValue = defaultValue;
} else if (typeof value === "string") {
cleanValue = value as string;
} else if (typeof value === "number") {
cleanValue = String(value);
} else if (value as CustomFieldValue) {
cleanValue = (value as CustomFieldValue).displayValue ?? (value as CustomFieldValue).value?.toString();
}
if (readOnly) {
return ;
} else {
return (
);
}
}
renderTemplateEditor(className: string, name: string, label: string, allowCustomFields: boolean) {
const { data } = this.state;
let value = data[name] as string;
return (
);
}
renderSelect(name: string, label: string, options: Option[]) {
const { data, errors } = this.state;
return ;
}
renderToggle(name: string, label: string) {
const { data, errors } = this.state;
return ;
}
renderSequencePicker(includeLabel: boolean, name: string, label: string) {
const { data, errors } = this.state;
return (
);
}
renderGlossaryPicker(includeLabel: boolean, name: string, label: string, maxEntries?: number, refElementId?: GeneralIdRef) {
const { data, errors } = this.state;
const glossaryValues: CustomFieldValue[] | undefined = data[name] as any as CustomFieldValue[];
return (
);
}
renderDomainPicker(includeLabel: boolean, name: string, label: string, minEntries: number, maxEntries?: number) {
const { data, errors } = this.state;
const domainValues: CustomFieldValue[] | undefined = data[name] as any as CustomFieldValue[];
return (
);
}
renderTemplatePicker(includeLabel: boolean, name: string, label: string) {
const { data, errors } = this.state;
const templateValue: GeneralIdRef = data[name] as any as GeneralIdRef;
return (
);
}
renderUserPicker(name: string, label: string) {
const { data, errors } = this.state;
const glossaryValue: GeneralIdRef | undefined = data[name] as any as GeneralIdRef;
return ;
}
renderSsoProviderPicker(name: string, label: string) {
const { data, errors } = this.state;
const glossaryValue: GeneralIdRef | undefined = data[name] as any as GeneralIdRef;
return ;
}
renderCustomFieldsEditor(name: string, label: string, selected: CustomField[], onAdd: CustomFieldEditorAdd, onDelete: CustomFieldEditorDelete) {
const { data, errors } = this.state;
return (
);
}
renderCustomFields(customFields: CustomField[] | undefined) {
if (customFields === undefined) return <>>;
let customFieldsBlock: JSX.Element[] = [];
for (const customField of customFields) customFieldsBlock.push(this.renderCustomField(customField, true));
return <>{customFieldsBlock.map((x) => x)}>;
}
renderCustomField(customField: CustomField, includeLabel: boolean) {
switch (customField.fieldType.toLowerCase()) {
case "text":
const textParameters: textParams = JSON.parse(customField.parameters!);
if (textParameters.multiLine) {
return this.renderInputTextarea(includeLabel, "customfield_" + customField.id, customField.name, false, customField.defaultValue);
} else {
return this.renderCustomFieldInput(
includeLabel,
"customfield_" + customField.id,
customField.name,
InputType.text,
false,
customField.defaultValue
);
}
case "sequence":
return this.renderCustomFieldInput(includeLabel, "customfield_" + customField.id, customField.name, InputType.text, true);
case "formtemplate":
return this.renderTemplatePicker(includeLabel, "customfield_" + customField.id, customField.name);
case "glossary":
return this.renderGlossaryPicker(
includeLabel,
"customfield_" + customField.id,
customField.name,
customField.maxEntries,
customField.refElementId
);
case "number":
const numberParameters: numberParams = JSON.parse(customField.parameters!);
return this.renderCustomFieldNumber(
includeLabel,
"customfield_" + customField.id,
customField.name,
false,
customField.defaultValue,
numberParameters.minValue ? Number(numberParameters.minValue) : undefined,
numberParameters.maxValue ? Number(numberParameters.maxValue) : undefined,
numberParameters.step ? Number(numberParameters.step) : undefined
);
case "domain":
return this.renderDomainPicker(includeLabel, "customfield_" + customField.id, customField.name, customField.minEntries, customField.maxEntries);
default:
return <>{customField.name + " " + customField.fieldType}>;
}
}
renderDropSection(name: string, title: JSX.Element, content: JSX.Element) {
const { errors } = this.state;
return (
{content}
);
}
}
export default Form;