From 78c788812af2339afd15f5374bbaf4e4cf000bed Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Thu, 29 May 2025 11:28:50 -0500 Subject: [PATCH] Add support for onLoad and onChange form adjusters, plus isHidden attribute on fields --- src/qqq/components/forms/DynamicForm.tsx | 223 ++++++++++++++++++++++- 1 file changed, 218 insertions(+), 5 deletions(-) diff --git a/src/qqq/components/forms/DynamicForm.tsx b/src/qqq/components/forms/DynamicForm.tsx index 7657abf..33a2724 100644 --- a/src/qqq/components/forms/DynamicForm.tsx +++ b/src/qqq/components/forms/DynamicForm.tsx @@ -20,15 +20,21 @@ */ import {AdornmentType} from "@kingsrook/qqq-frontend-core/lib/model/metaData/AdornmentType"; +import {QFieldMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldMetaData"; import {QRecord} from "@kingsrook/qqq-frontend-core/lib/model/QRecord"; import Box from "@mui/material/Box"; import Grid from "@mui/material/Grid"; +import {useFormikContext} from "formik"; import QDynamicFormField from "qqq/components/forms/DynamicFormField"; +import DynamicFormUtils from "qqq/components/forms/DynamicFormUtils"; import DynamicSelect from "qqq/components/forms/DynamicSelect"; import FileInputField from "qqq/components/forms/FileInputField"; import MDTypography from "qqq/components/legacy/MDTypography"; import HelpContent from "qqq/components/misc/HelpContent"; -import React from "react"; +import Client from "qqq/utils/qqq/Client"; +import React, {useEffect, useState} from "react"; + +const qController = Client.getInstance(); interface Props { @@ -43,7 +49,12 @@ interface Props function QDynamicForm({formData, formLabel, bulkEditMode, bulkEditSwitchChangeHandler, record, helpRoles, helpContentKeyPrefix}: Props): JSX.Element { - const {formFields, values, errors, touched} = formData; + const {formFields: origFormFields, errors, touched} = formData; + const {setFieldValue, values} = useFormikContext>(); + + const [formAdjustmentCounter, setFormAdjustmentCounter] = useState(0) + + const [formFields, setFormFields] = useState(origFormFields as {[key: string]: any}); const bulkEditSwitchChanged = (name: string, value: boolean) => { @@ -51,6 +62,204 @@ function QDynamicForm({formData, formLabel, bulkEditMode, bulkEditSwitchChangeHa }; + ///////////////////////////////////////// + // run on-load handlers if we have any // + ///////////////////////////////////////// + useEffect(() => + { + for (let fieldName in formFields) + { + const field = formFields[fieldName]; + + const materialDashboardFieldMetaData = field.fieldMetaData?.supplementalFieldMetaData?.get("materialDashboard"); + if(materialDashboardFieldMetaData?.onLoadFormAdjuster) + { + ////////////////////////////////////////////////////////////////////////////////////////////////////////// + // todo consider cases with multiple - do they need to list a sequenceNo? do they need to run serially? // + ////////////////////////////////////////////////////////////////////////////////////////////////////////// + considerRunningFormAdjuster("onLoad", fieldName, values[fieldName]); + } + } + }, []); + + + /*************************************************************************** + ** + ***************************************************************************/ + const handleFieldChange = async (fieldName: string, newValue: any) => + { + const field = formFields[fieldName]; + if (!field) + { + return; + } + + ////////////////////////////////////////////////////////////////////// + // map possible-value objects to ids - also capture their labels... // + ////////////////////////////////////////////////////////////////////// + let actualNewValue = newValue; + let possibleValueLabel: string = null; + if (field.possibleValueProps) + { + actualNewValue = newValue ? newValue.id : null; + possibleValueLabel = newValue ? newValue.label : null; + } + + ///////////////////////////////////////////////////////////////////////////////////////////// + // make sure formik has the value - and that we capture the possible-value label if needed // + ///////////////////////////////////////////////////////////////////////////////////////////// + setFieldValue(fieldName, actualNewValue); + if (field.possibleValueProps) + { + field.possibleValueProps.initialDisplayValue = possibleValueLabel; + } + + /////////////////////////////////////////// + // run onChange adjuster if there is one // + /////////////////////////////////////////// + considerRunningFormAdjuster("onChange", fieldName, actualNewValue); + } + + + /*************************************************************************** + ** + ***************************************************************************/ + const considerRunningFormAdjuster = async (event: "onChange" | "onLoad", fieldName: string, newValue: any) => + { + const field = formFields[fieldName]; + if (!field) + { + return; + } + + const materialDashboardFieldMetaData = field.fieldMetaData?.supplementalFieldMetaData?.get("materialDashboard"); + const adjuster = event == "onChange" ? materialDashboardFieldMetaData?.onChangeFormAdjuster : materialDashboardFieldMetaData?.onLoadFormAdjuster; + if (!adjuster) + { + return; + } + + console.log(`Running form adjuster for field ${fieldName} ${event} (value is: ${newValue})`); + + ////////////////////////////////////////////////////////////////// + // disable fields temporarily while waiting on backend response // + ////////////////////////////////////////////////////////////////// + const fieldNamesToTempDisable: string[] = materialDashboardFieldMetaData?.fieldsToDisableWhileRunningAdjusters ?? [] + const previousIsEditableValues: {[key: string]: boolean} = {}; + if(fieldNamesToTempDisable.length > 0) + { + for (let oldFieldName in formFields) + { + if (fieldNamesToTempDisable.indexOf(oldFieldName) > -1) + { + previousIsEditableValues[oldFieldName] = formFields[oldFieldName].isEditable; + formFields[oldFieldName].isEditable = false; + } + } + + setFormAdjustmentCounter(formAdjustmentCounter + 1); + setFormFields({...formFields}); + } + + //////////////////////////////////////////////////// + // build request to backend for field adjustments // + //////////////////////////////////////////////////// + const postBody = new FormData(); + postBody.append("event", event); + postBody.append("fieldName", fieldName); + postBody.append("newValue", newValue); + postBody.append("allValues", JSON.stringify(values)); + const response = await qController.axiosRequest( + { + method: "post", + url: `/material-dashboard-backend/form-adjuster/${encodeURIComponent(materialDashboardFieldMetaData.formAdjusterIdentifier)}/${event}`, + data: postBody, + headers: qController.defaultMultipartFormDataHeaders() + }); + console.log("Form adjuster response: " + JSON.stringify(response)); + + //////////////////////////////////////////////////// + // un-disable any temp disabled fields from above // + //////////////////////////////////////////////////// + if(fieldNamesToTempDisable.length > 0) + { + for (let oldFieldName in formFields) + { + if (fieldNamesToTempDisable.indexOf(oldFieldName) > -1) + { + formFields[oldFieldName].isEditable = previousIsEditableValues[oldFieldName]; + } + } + setFormFields({...formFields}); + } + + /////////////////////////////////////////////////// + // replace field definitions, if we have updates // + /////////////////////////////////////////////////// + const updatedFields: { [fieldName: string]: QFieldMetaData } = response.updatedFieldMetaData; + if(updatedFields) + { + for (let updatedFieldName in updatedFields) + { + const updatedField = new QFieldMetaData(updatedFields[updatedFieldName]); + const dynamicField = DynamicFormUtils.getDynamicField(updatedField); // todo dynamicallyDisabledFields? second param... + + const dynamicFieldInObject: any = {}; + dynamicFieldInObject[updatedFieldName] = dynamicField; + let tableName = null; + let processName = null; + let displayValues = new Map(); + + DynamicFormUtils.addPossibleValueProps(dynamicFieldInObject, [updatedFields[updatedFieldName]], tableName, processName, displayValues); + for (let oldFieldName in formFields) + { + if (oldFieldName == updatedFieldName) + { + formFields[updatedFieldName] = dynamicField; + } + } + } + + setFormAdjustmentCounter(formAdjustmentCounter + 2); + setFormFields({...formFields}); + } + + ///////////////////////// + // update field values // + ///////////////////////// + const updatedFieldValues: {[fieldName: string]: any} = response?.updatedFieldValues ?? {}; + for (let fieldNameToUpdate in updatedFieldValues) + { + setFieldValue(fieldNameToUpdate, updatedFieldValues[fieldNameToUpdate]); + /////////////////////////////////////////////////////////////////////////////////////// + // todo - track if a pvs field gets a value, but not a display value, and fetch it?? // + /////////////////////////////////////////////////////////////////////////////////////// + } + + ///////////////////////////////////////////////// + // set display values in PVS's if we have them // + ///////////////////////////////////////////////// + const updatedFieldDisplayValues: {[fieldName: string]: any} = response?.updatedFieldDisplayValues ?? {}; + for (let fieldNameToUpdate in updatedFieldDisplayValues) + { + const fieldToUpdate = formFields[fieldNameToUpdate]; + if(fieldToUpdate?.possibleValueProps) + { + fieldToUpdate.possibleValueProps.initialDisplayValue = updatedFieldDisplayValues[fieldNameToUpdate]; + } + } + + //////////////////////////////////////// + // clear field values if we have them // + //////////////////////////////////////// + const fieldsToClear: string[] = response?.fieldsToClear ?? []; + for (let fieldToClear of fieldsToClear) + { + setFieldValue(fieldToClear, ""); + } + }; + + return ( @@ -68,6 +277,8 @@ function QDynamicForm({formData, formLabel, bulkEditMode, bulkEditSwitchChangeHa return null; } + const display = field.fieldMetaData?.isHidden ? "none" : "initial"; + if (values[fieldName] === undefined) { values[fieldName] = ""; @@ -100,7 +311,7 @@ function QDynamicForm({formData, formLabel, bulkEditMode, bulkEditSwitchChangeHa } return ( - + {labelElement} @@ -119,7 +330,7 @@ function QDynamicForm({formData, formLabel, bulkEditMode, bulkEditSwitchChangeHa }); return ( - + {labelElement} handleFieldChange(fieldName, newValue)} /> {formattedHelpContent} @@ -140,7 +352,7 @@ function QDynamicForm({formData, formLabel, bulkEditMode, bulkEditSwitchChangeHa // everything else!! // /////////////////////// return ( - + {labelElement} handleFieldChange(fieldName, newValue)} /> {formattedHelpContent}