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