From 451af347f79dbed209e7112c7b0f92a33c566f64 Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Mon, 25 Nov 2024 10:16:22 -0600 Subject: [PATCH] CE-1955 - Add support for pre-submit callbacks, defined in components - specifically, ones used by bulk-load. Add awareness of the bulkLoad components, specifically with a block for value-mapping form, to integrate with formik --- src/qqq/pages/processes/ProcessRun.tsx | 170 ++++++++++++++++++++++++- 1 file changed, 168 insertions(+), 2 deletions(-) diff --git a/src/qqq/pages/processes/ProcessRun.tsx b/src/qqq/pages/processes/ProcessRun.tsx index ef3b1fa..0cf07f5 100644 --- a/src/qqq/pages/processes/ProcessRun.tsx +++ b/src/qqq/pages/processes/ProcessRun.tsx @@ -60,6 +60,9 @@ import MDProgress from "qqq/components/legacy/MDProgress"; import MDTypography from "qqq/components/legacy/MDTypography"; import HelpContent, {hasHelpContent} from "qqq/components/misc/HelpContent"; import QRecordSidebar from "qqq/components/misc/RecordSidebar"; +import BulkLoadFileMappingForm from "qqq/components/processes/BulkLoadFileMappingForm"; +import BulkLoadProfileForm from "qqq/components/processes/BulkLoadProfileForm"; +import BulkLoadValueMappingForm from "qqq/components/processes/BulkLoadValueMappingForm"; import {GoogleDriveFolderPickerWrapper} from "qqq/components/processes/GoogleDriveFolderPickerWrapper"; import ProcessSummaryResults from "qqq/components/processes/ProcessSummaryResults"; import ValidationReview from "qqq/components/processes/ValidationReview"; @@ -72,7 +75,7 @@ import {TABLE_VARIANT_LOCAL_STORAGE_KEY_ROOT} from "qqq/pages/records/query/Reco import Client from "qqq/utils/qqq/Client"; import TableUtils from "qqq/utils/qqq/TableUtils"; import ValueUtils from "qqq/utils/qqq/ValueUtils"; -import React, {useContext, useEffect, useState} from "react"; +import React, {useContext, useEffect, useRef, useState} from "react"; import {useLocation, useNavigate, useParams} from "react-router-dom"; import * as Yup from "yup"; @@ -109,6 +112,10 @@ let formikSetTouched = ({}: any, touched: boolean): void => const cachedPossibleValueLabels: { [fieldName: string]: { [id: string | number]: string } } = {}; +export interface SubFormPreSubmitCallbackResultType {maySubmit: boolean; values: {[name: string]: any}} +type SubFormPreSubmitCallback = () => SubFormPreSubmitCallbackResultType; +type SubFormPreSubmitCallbackWithName = {name: string, callback: SubFormPreSubmitCallback} + function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, isReport, recordIds, closeModalHandler, forceReInit, overrideLabel}: Props): JSX.Element { const processNameParam = useParams().processName; @@ -129,6 +136,7 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is const [qJobRunningDate, setQJobRunningDate] = useState(null as Date); const [activeStepIndex, setActiveStepIndex] = useState(0); const [activeStep, setActiveStep] = useState(null as QFrontendStepMetaData); + const [activeStepLabel, setActiveStepLabel] = useState(null as string); const [newStep, setNewStep] = useState(null); const [stepInstanceCounter, setStepInstanceCounter] = useState(0); const [steps, setSteps] = useState([] as QFrontendStepMetaData[]); @@ -151,6 +159,7 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is const [renderedWidgets, setRenderedWidgets] = useState({} as { [step: string]: { [widgetName: string]: any } }); const [controlCallbacks, setControlCallbacks] = useState({} as {[name: string]: () => void}) + const [subFormPreSubmitCallbacks, setSubFormPreSubmitCallbacks] = useState([] as SubFormPreSubmitCallbackWithName[]) const {pageHeader, recordAnalytics, setPageHeader, helpHelpActive} = useContext(QContext); @@ -220,6 +229,11 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is const navigate = useNavigate(); const location = useLocation(); + const bulkLoadFileMappingFormRef = useRef(); + const bulkLoadValueMappingFormRef = useRef(); + const bulkLoadProfileFormRef = useRef(); + const [bulkLoadValueMappingFormFields, setBulkLoadValueMappingFormFields] = useState([] as any[]) + const doesStepHaveComponent = (step: QFrontendStepMetaData, type: QComponentType): boolean => { if (step.components) @@ -652,6 +666,42 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is }); } + //////////////////////////////////////////////////////////////////////////////// + // if we have a bulk-load file mapping form, register its pre-submit callback // + //////////////////////////////////////////////////////////////////////////////// + if (doesStepHaveComponent(activeStep, QComponentType.BULK_LOAD_FILE_MAPPING_FORM)) + { + if(bulkLoadFileMappingFormRef?.current) + { + // @ts-ignore ... + addSubFormPreSubmitCallbacks("bulkLoadFileMappingForm", bulkLoadFileMappingFormRef?.current?.preSubmit) + } + } + + ///////////////////////////////////////////////////////////////////////////////// + // if we have a bulk-load value mapping form, register its pre-submit callback // + ///////////////////////////////////////////////////////////////////////////////// + if (doesStepHaveComponent(activeStep, QComponentType.BULK_LOAD_VALUE_MAPPING_FORM)) + { + if(bulkLoadValueMappingFormRef?.current) + { + // @ts-ignore ... + addSubFormPreSubmitCallbacks("bulkLoadValueMappingForm", bulkLoadValueMappingFormRef?.current?.preSubmit) + } + } + + /////////////////////////////////////////////////////////////////////////// + // if we have a bulk-load profile form, register its pre-submit callback // + /////////////////////////////////////////////////////////////////////////// + if (doesStepHaveComponent(activeStep, QComponentType.BULK_LOAD_PROFILE_FORM)) + { + if(bulkLoadProfileFormRef?.current) + { + // @ts-ignore ... + addSubFormPreSubmitCallbacks("bulkLoadProfileFormRef", bulkLoadProfileFormRef?.current?.preSubmit) + } + } + ///////////////////////////////////// // screen(step)-level help content // ///////////////////////////////////// @@ -670,7 +720,7 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is !isWidget && !isFormatScanner && {(isModal) ? `${overrideLabel ?? process.label}: ` : ""} - {step?.label} + {activeStepLabel} } @@ -970,6 +1020,39 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is ) } + { + component.type === QComponentType.BULK_LOAD_FILE_MAPPING_FORM && ( + + ) + } + { + component.type === QComponentType.BULK_LOAD_VALUE_MAPPING_FORM && ( + + ) + } + { + component.type === QComponentType.BULK_LOAD_PROFILE_FORM && ( + + ) + } ); })) @@ -1076,6 +1159,7 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is { const activeStep = steps[newIndex]; setActiveStep(activeStep); + setActiveStepLabel(activeStep.label); setFormId(activeStep.name); let dynamicFormFields: any = {}; @@ -1202,6 +1286,43 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is } } + ///////////////////////////////////////////////////////////////// + // Help make this component's fields work with our formik form // + ///////////////////////////////////////////////////////////////// + if(activeStep && doesStepHaveComponent(activeStep, QComponentType.BULK_LOAD_VALUE_MAPPING_FORM)) + { + const fileValues = processValues.fileValues ?? []; + const valueMapping = processValues.valueMapping ?? {}; + const mappedValueLabels = processValues.mappedValueLabels ?? {}; + + const fieldFullName = processValues.valueMappingFullFieldName; + const fieldTableName = processValues.valueMappingFieldTableName; + + const field = new QFieldMetaData(processValues.valueMappingField); + const qFieldMetaData = new QFieldMetaData(field); + + const fieldsForComponent: any[] = []; + for (let i = 0; i < fileValues.length; i++) + { + const dynamicField = DynamicFormUtils.getDynamicField(qFieldMetaData); + const wrappedField: any = {}; + wrappedField[field.name] = dynamicField; + DynamicFormUtils.addPossibleValueProps(wrappedField, [field], fieldTableName, null, null); + + const initialValue = valueMapping[fileValues[i]]; + + if(dynamicField.possibleValueProps) + { + dynamicField.possibleValueProps.initialDisplayValue = mappedValueLabels[initialValue] + } + + addField(`${fieldFullName}.value.${i}`, dynamicField, initialValue, null) + fieldsForComponent.push(dynamicField); + } + + setBulkLoadValueMappingFormFields(fieldsForComponent) + } + if (Object.keys(dynamicFormFields).length > 0) { /////////////////////////////////////////// @@ -1357,6 +1478,24 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is } + /*************************************************************************** + ** manage adding pre-submit callbacks (so they get added just once) + ***************************************************************************/ + function addSubFormPreSubmitCallbacks(name: string, callback: SubFormPreSubmitCallback) + { + if(subFormPreSubmitCallbacks.findIndex(c => c.name == name) == -1) + { + const newCallbacks: SubFormPreSubmitCallbackWithName[] = [] + for(let i = 0; i < subFormPreSubmitCallbacks.length; i++) + { + newCallbacks[i] = subFormPreSubmitCallbacks[i]; + } + newCallbacks.push({name, callback}) + setSubFormPreSubmitCallbacks(newCallbacks) + } + } + + ////////////////////////////////////////////////////////////////////////////////////////////////////////// // handle a response from the server - e.g., after starting a backend job, or getting its status/result // ////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -1437,6 +1576,7 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is setStepInstanceCounter(1 + stepInstanceCounter); setProcessValues(newValues); setRenderedWidgets({}); + setSubFormPreSubmitCallbacks([]); setQJobRunning(null); if (formikSetFieldValueFunction) @@ -1698,6 +1838,27 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is { setFormError(null); + /////////////////////////////////////////////////////////////// + // run any sub-form pre-submit callbacks that are registered // + /////////////////////////////////////////////////////////////// + for(let i = 0; i < subFormPreSubmitCallbacks.length; i++) + { + const {maySubmit, values: moreValues} = subFormPreSubmitCallbacks[i].callback(); + if(!maySubmit) + { + console.log(`May not submit form, per callback: ${subFormPreSubmitCallbacks[i].name}`); + return; + } + + if(moreValues) + { + for (let key in moreValues) + { + values[key] = moreValues[key] + } + } + } + const formData = new FormData(); Object.keys(values).forEach((key) => { @@ -1742,6 +1903,11 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is setOverrideOnLastStep(null); setLastProcessResponse(new QJobRunning({message: "Working..."})); + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // clear out the active step now, to avoid a flash of the old one after the job completes, but before the new one is all set // + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + setActiveStep(null); + setTimeout(async () => { recordAnalytics({category: "processEvents", action: "processStep", label: activeStep.label});