webui/src/components/common/TemplateFiller.tsx

268 lines
8.2 KiB
TypeScript

import React, {
useCallback,
useEffect,
useImperativeHandle,
useRef,
forwardRef,
} from "react";
import { GeneralIdRef, MakeGeneralIdRef } from "../../utils/GeneralIdRef";
import formsService, {
CreateFormInstance,
EditFormInstance,
} from "../../modules/manager/forms/services/formsService";
import parse, { HTMLReactParserOptions, domToReact } from "html-react-parser";
import { CustomField } from "../../modules/manager/customfields/services/customFieldsService";
import { toast } from "react-toastify";
import { renderCustomField } from "./formHelpers";
import { CustomFieldValue } from "../../modules/manager/glossary/services/glossaryService";
import { useTranslation } from "react-i18next";
import { Namespaces } from "../../i18n/i18n";
import { useFormWithGuard } from "./useFormRouter";
interface TemplateFillerProps {
templateId?: GeneralIdRef;
formInstanceId?: GeneralIdRef;
onValidationChanged?: () => void;
}
export interface TemplateFillerHandle {
hasValidationErrors: () => boolean;
Save: () => Promise<any>;
}
interface TemplateState {
name?: string;
templateId?: GeneralIdRef;
version?: bigint;
definition?: string;
}
const TemplateFiller = forwardRef<TemplateFillerHandle, TemplateFillerProps>(
({ templateId, formInstanceId, onValidationChanged }, ref) => {
const form = useFormWithGuard({
loaded: false,
data: {},
errors: {},
redirect: "",
});
const [template, setTemplate] = React.useState<TemplateState>({
name: undefined,
templateId: undefined,
version: undefined,
definition: undefined,
});
const prevHasErrorsRef = useRef<boolean>(false);
form.schema = {};
const loadTemplate = useCallback(async () => {
let loadedData: any;
if (templateId !== undefined) {
loadedData = await formsService.getForm(
templateId?.id,
templateId?.guid,
);
loadedData.templateId = undefined;
loadedData.customFieldValues = undefined;
loadedData.updatedVersion = undefined;
} else if (formInstanceId !== undefined) {
loadedData = await formsService.getFormInstance(
formInstanceId?.id,
formInstanceId?.guid,
);
} else {
loadedData = {
name: undefined,
id: undefined,
guid: undefined,
version: undefined,
definition: undefined,
customFieldDefinitions: undefined,
templateId: undefined,
customFieldValues: undefined,
updatedVersion: undefined,
};
}
const newTemplate: TemplateState = {
name: loadedData.name,
templateId: MakeGeneralIdRef(loadedData.id, loadedData.guid),
version: loadedData.version,
definition: loadedData.definition,
};
const newCustomFields = loadedData.customFieldDefinitions as
| CustomField[]
| undefined;
const newData = { ...form.state.data } as Record<string, any>;
form.setCustomFieldValues(
newData,
loadedData.customFieldValues,
newCustomFields ?? [],
);
form.setState({
loaded: true,
data: newData,
customFields: newCustomFields,
});
setTemplate(newTemplate);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [templateId, formInstanceId]);
useEffect(() => {
void loadTemplate();
}, [loadTemplate]);
useEffect(() => {
if (!onValidationChanged) return;
const hasErrors = Object.keys(form.state.errors).length > 0;
if (prevHasErrorsRef.current !== hasErrors) {
prevHasErrorsRef.current = hasErrors;
onValidationChanged();
}
}, [form.state.errors, onValidationChanged]);
const handleCustomFieldChange = useCallback(
(e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
if (e.target instanceof HTMLTextAreaElement) {
form.handleTextAreaChange(
e as React.ChangeEvent<HTMLTextAreaElement>,
);
} else {
form.handleCustomFieldChange(
e as React.ChangeEvent<HTMLInputElement>,
);
}
},
[form],
);
const handleCustomFieldPickerChange = useCallback(
(name: string, value: GeneralIdRef | CustomFieldValue[]) => {
if (Array.isArray(value)) {
form.handleGlossaryPickerChange(name, value);
} else {
form.handlePickerChange(name, value as GeneralIdRef);
}
},
[form],
);
const getCustomFieldType = useCallback(
(field: CustomFieldValue, customFields: CustomField[]) => {
return form.getCustomFieldType(field as any, customFields);
},
[form],
);
const parseDefinition = useCallback(
(definition: string, customFieldDefinitions: CustomField[]) => {
const options: HTMLReactParserOptions = {
replace: (domNode) => {
const domNodeAsAny: any = domNode;
if (domNodeAsAny.name === "span") {
if (domNodeAsAny.attribs.fieldtype === "CustomField") {
const customField = customFieldDefinitions.filter(
(x) => x.guid === domNodeAsAny.attribs.guid,
)[0];
return renderCustomField(
customField,
false,
form.state.data,
form.state.errors,
handleCustomFieldChange,
handleCustomFieldPickerChange,
getCustomFieldType,
);
}
} else if (domNodeAsAny.name === "p") {
return (
<div className="p">
{domToReact(domNodeAsAny.children, options)}
</div>
);
}
},
};
return parse(definition, options);
},
[
form.state.data,
form.state.errors,
handleCustomFieldChange,
handleCustomFieldPickerChange,
getCustomFieldType,
],
);
const hasValidationErrors = useCallback((): boolean => {
return Object.keys(form.state.errors).length > 0;
}, [form.state.errors]);
const { t } = useTranslation(Namespaces.Common);
const Save = useCallback(async () => {
const { errors } = form.state;
if (Object.keys(errors).length > 0) {
toast.error(t("ThereAreErrorsOnTheForm"));
throw new Error(t("ThereAreErrorsOnTheForm") as string);
}
const customFieldValues = form.CustomFieldValues();
if (formInstanceId !== undefined) {
if (template.templateId === undefined)
throw Error(t("TemplateIdCannotBeNull") as string);
if (template.version === undefined)
throw Error(t("VersionCannotBeNull") as string);
const editFormInstance: EditFormInstance = {
formInstanceId,
templateId: template.templateId,
version: template.version,
customFieldValues,
};
await formsService.putFormInstance(editFormInstance);
} else {
if (
template.templateId !== undefined &&
template.version !== undefined
) {
const formInstance: CreateFormInstance = {
templateId: template.templateId,
version: template.version,
customFieldValues,
};
return await formsService.postFormInstance(formInstance);
}
throw new Error(t("TemplateUnknown") as string);
}
}, [form, template, formInstanceId, t]);
useImperativeHandle(ref, () => ({
hasValidationErrors,
Save,
}));
const { loaded, customFields } = form.state;
let parsedDefinition: any;
if (template.definition && customFields)
parsedDefinition = parseDefinition(template.definition, customFields);
else parsedDefinition = <></>;
return <div className="ck-content form-editor">{parsedDefinition}</div>;
},
);
TemplateFiller.displayName = "TemplateFiller";
export default TemplateFiller;