diff --git a/src/qqq/components/forms/DynamicFormField.tsx b/src/qqq/components/forms/DynamicFormField.tsx index 6930b66..d9991d6 100644 --- a/src/qqq/components/forms/DynamicFormField.tsx +++ b/src/qqq/components/forms/DynamicFormField.tsx @@ -40,6 +40,8 @@ interface Props value: any; type: string; isEditable?: boolean; + placeholder?: string; + backgroundColor?: string; [key: string]: any; @@ -49,7 +51,7 @@ interface Props } function QDynamicFormField({ - label, name, displayFormat, value, bulkEditMode, bulkEditSwitchChangeHandler, type, isEditable, formFieldObject, ...rest + label, name, displayFormat, value, bulkEditMode, bulkEditSwitchChangeHandler, type, isEditable, placeholder, backgroundColor, formFieldObject, ...rest }: Props): JSX.Element { const [switchChecked, setSwitchChecked] = useState(false); @@ -65,18 +67,30 @@ function QDynamicFormField({ inputLabelProps.shrink = true; } - const inputProps = {}; + const inputProps: any = {}; if (displayFormat && displayFormat.startsWith("$")) { - // @ts-ignore inputProps.startAdornment = $; } if (displayFormat && displayFormat.endsWith("%%")) { - // @ts-ignore inputProps.endAdornment = %; } + if (placeholder) + { + inputProps.placeholder = placeholder + } + + if(backgroundColor) + { + inputProps.sx = { + "&.MuiInputBase-root": { + backgroundColor: backgroundColor + } + }; + } + // @ts-ignore const handleOnWheel = (e) => { diff --git a/src/qqq/components/forms/DynamicFormUtils.ts b/src/qqq/components/forms/DynamicFormUtils.ts index 89331a9..e94e4b6 100644 --- a/src/qqq/components/forms/DynamicFormUtils.ts +++ b/src/qqq/components/forms/DynamicFormUtils.ts @@ -130,18 +130,11 @@ class DynamicFormUtils if (effectivelyIsRequired) { - if (field.possibleValueSourceName) - { - //////////////////////////////////////////////////////////////////////////////////////////// - // the "nullable(true)" here doesn't mean that you're allowed to set the field to null... // - // rather, it's more like "null is how empty will be treated" or some-such... // - //////////////////////////////////////////////////////////////////////////////////////////// - return (Yup.string().required(`${field.label ?? "This field"} is required.`).nullable(true)); - } - else - { - return (Yup.string().required(`${field.label ?? "This field"} is required.`)); - } + //////////////////////////////////////////////////////////////////////////////////////////// + // the "nullable(true)" here doesn't mean that you're allowed to set the field to null... // + // rather, it's more like "null is how empty will be treated" or some-such... // + //////////////////////////////////////////////////////////////////////////////////////////// + return (Yup.string().required(`${field.label ?? "This field"} is required.`).nullable(true)); } return (null); } diff --git a/src/qqq/components/widgets/CompositeWidget.tsx b/src/qqq/components/widgets/CompositeWidget.tsx index 98570ad..da756f8 100644 --- a/src/qqq/components/widgets/CompositeWidget.tsx +++ b/src/qqq/components/widgets/CompositeWidget.tsx @@ -22,19 +22,25 @@ import {QWidgetMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QWidgetMetaData"; import {Box, Skeleton} from "@mui/material"; +import Card from "@mui/material/Card"; +import Modal from "@mui/material/Modal"; import parse from "html-react-parser"; import {BlockData} from "qqq/components/widgets/blocks/BlockModels"; import WidgetBlock from "qqq/components/widgets/WidgetBlock"; -import React from "react"; +import ProcessWidgetBlockUtils from "qqq/pages/processes/ProcessWidgetBlockUtils"; +import React, {useEffect, useState} from "react"; export interface CompositeData { + blockId: string; blocks: BlockData[]; styleOverrides?: any; layout?: string; overlayHtml?: string; overlayStyleOverrides?: any; + modalMode: string; + styles?: any; } @@ -42,14 +48,15 @@ interface CompositeWidgetProps { widgetMetaData: QWidgetMetaData; data: CompositeData; - actionCallback?: (blockData: BlockData) => boolean; + actionCallback?: (blockData: BlockData, eventValues?: { [name: string]: any }) => boolean; + values?: { [key: string]: any }; } /******************************************************************************* ** Widget which is a list of Blocks. *******************************************************************************/ -export default function CompositeWidget({widgetMetaData, data, actionCallback}: CompositeWidgetProps): JSX.Element +export default function CompositeWidget({widgetMetaData, data, actionCallback, values}: CompositeWidgetProps): JSX.Element { if (!data || !data.blocks) { @@ -75,6 +82,12 @@ export default function CompositeWidget({widgetMetaData, data, actionCallback}: boxStyle.flexWrap = "wrap"; boxStyle.gap = "0.5rem"; } + else if (layout == "FLEX_ROW") + { + boxStyle.display = "flex"; + boxStyle.flexDirection = "row"; + boxStyle.gap = "0.5rem"; + } else if (layout == "FLEX_ROW_SPACE_BETWEEN") { boxStyle.display = "flex"; @@ -114,6 +127,19 @@ export default function CompositeWidget({widgetMetaData, data, actionCallback}: boxStyle = {...boxStyle, ...data.styleOverrides}; } + if (data.styles?.backgroundColor) + { + boxStyle.backgroundColor = ProcessWidgetBlockUtils.processColorFromStyleMap(data.styles.backgroundColor); + } + + if (data.styles?.padding) + { + boxStyle.paddingTop = data.styles?.padding.top + "px" + boxStyle.paddingBottom = data.styles?.padding.bottom + "px" + boxStyle.paddingLeft = data.styles?.padding.left + "px" + boxStyle.paddingRight = data.styles?.padding.right + "px" + } + let overlayStyle: any = {}; if (data?.overlayStyleOverrides) @@ -121,7 +147,7 @@ export default function CompositeWidget({widgetMetaData, data, actionCallback}: overlayStyle = {...overlayStyle, ...data.overlayStyleOverrides}; } - return ( + const content = ( <> { data?.overlayHtml && @@ -131,7 +157,7 @@ export default function CompositeWidget({widgetMetaData, data, actionCallback}: { data.blocks.map((block: BlockData, index) => ( - + )) } @@ -139,4 +165,53 @@ export default function CompositeWidget({widgetMetaData, data, actionCallback}: ); + if (data.modalMode) + { + const [isModalOpen, setIsModalOpen] = useState(values && (values[data.blockId] == true)); + + /*************************************************************************** + ** + ***************************************************************************/ + const controlCallback = (newValue: boolean) => + { + setIsModalOpen(newValue); + }; + + /*************************************************************************** + ** + ***************************************************************************/ + const modalOnClose = (event: object, reason: string) => + { + values[data.blockId] = false; + setIsModalOpen(false); + actionCallback({blockTypeName: "BUTTON", values: {}}, {controlCode: `hideModal:${data.blockId}`}); + }; + + ////////////////////////////////////////////////////////////////////////////////////////// + // register the control-callback function - so when buttons are clicked, we can be told // + ////////////////////////////////////////////////////////////////////////////////////////// + useEffect(() => + { + if (actionCallback) + { + actionCallback(null, { + registerControlCallbackName: data.blockId, + registerControlCallbackFunction: controlCallback + }); + } + }, []); + + return ( + + + {content} + + + ); + } + else + { + return content; + } + } diff --git a/src/qqq/components/widgets/DashboardWidgets.tsx b/src/qqq/components/widgets/DashboardWidgets.tsx index 38fe772..a77e21e 100644 --- a/src/qqq/components/widgets/DashboardWidgets.tsx +++ b/src/qqq/components/widgets/DashboardWidgets.tsx @@ -29,6 +29,7 @@ import parse from "html-react-parser"; import QContext from "QContext"; import MDTypography from "qqq/components/legacy/MDTypography"; import TabPanel from "qqq/components/misc/TabPanel"; +import {BlockData} from "qqq/components/widgets/blocks/BlockModels"; import BarChart from "qqq/components/widgets/charts/barchart/BarChart"; import HorizontalBarChart from "qqq/components/widgets/charts/barchart/HorizontalBarChart"; import DefaultLineChart from "qqq/components/widgets/charts/linechart/DefaultLineChart"; @@ -71,6 +72,9 @@ interface Props childUrlParams?: string; parentWidgetMetaData?: QWidgetMetaData; wrapWidgetsInTabPanels: boolean; + actionCallback?: (blockData: BlockData) => boolean; + initialWidgetDataList: any[]; + values?: {[key: string]: any}; } DashboardWidgets.defaultProps = { @@ -82,11 +86,14 @@ DashboardWidgets.defaultProps = { childUrlParams: "", parentWidgetMetaData: null, wrapWidgetsInTabPanels: false, + actionCallback: null, + initialWidgetDataList: null, + values: {} }; -function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, record, omitWrappingGridContainer, areChildren, childUrlParams, parentWidgetMetaData, wrapWidgetsInTabPanels}: Props): JSX.Element +function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, record, omitWrappingGridContainer, areChildren, childUrlParams, parentWidgetMetaData, wrapWidgetsInTabPanels, actionCallback, initialWidgetDataList, values}: Props): JSX.Element { - const [widgetData, setWidgetData] = useState([] as any[]); + const [widgetData, setWidgetData] = useState(initialWidgetDataList == null ? [] as any[] : initialWidgetDataList); const [widgetCounter, setWidgetCounter] = useState(0); const [, forceUpdate] = useReducer((x) => x + 1, 0); @@ -114,7 +121,15 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, reco useEffect(() => { + if(initialWidgetDataList && initialWidgetDataList.length > 0) + { + // todo actually, should this check each element of the array, down in the loop? yeah, when we need to, do it that way. + console.log("We already have initial widget data, so not fetching from backend."); + return + } + setWidgetData([]); + for (let i = 0; i < widgetMetaDataList.length; i++) { const widgetMetaData = widgetMetaDataList[i]; @@ -563,7 +578,7 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, reco labelAdditionalComponentsRight={labelAdditionalComponentsRight} labelAdditionalComponentsLeft={labelAdditionalComponentsLeft} > - + ) } diff --git a/src/qqq/components/widgets/WidgetBlock.tsx b/src/qqq/components/widgets/WidgetBlock.tsx index 1115741..12e3569 100644 --- a/src/qqq/components/widgets/WidgetBlock.tsx +++ b/src/qqq/components/widgets/WidgetBlock.tsx @@ -22,9 +22,10 @@ import {QWidgetMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QWidgetMetaData"; import {Alert, Skeleton} from "@mui/material"; -import ActionButtonBlock from "qqq/components/widgets/blocks/ActionButtonBlock"; +import ButtonBlock from "qqq/components/widgets/blocks/ButtonBlock"; import AudioBlock from "qqq/components/widgets/blocks/AudioBlock"; import InputFieldBlock from "qqq/components/widgets/blocks/InputFieldBlock"; +import RevealBlock from "qqq/components/widgets/blocks/RevealBlock"; import React from "react"; import BigNumberBlock from "qqq/components/widgets/blocks/BigNumberBlock"; import {BlockData} from "qqq/components/widgets/blocks/BlockModels"; @@ -42,14 +43,15 @@ interface WidgetBlockProps { widgetMetaData: QWidgetMetaData; block: BlockData; - actionCallback?: (blockData: BlockData) => boolean; + actionCallback?: (blockData: BlockData, eventValues?: {[name: string]: any}) => boolean; + values?: { [key: string]: any }; } /******************************************************************************* ** Component to render a single Block in the widget framework! *******************************************************************************/ -export default function WidgetBlock({widgetMetaData, block, actionCallback}: WidgetBlockProps): JSX.Element +export default function WidgetBlock({widgetMetaData, block, actionCallback, values}: WidgetBlockProps): JSX.Element { if(!block) { @@ -69,7 +71,7 @@ export default function WidgetBlock({widgetMetaData, block, actionCallback}: Wid if(block.blockTypeName == "COMPOSITE") { // @ts-ignore - special case for composite type block... - return (); + return (); } switch(block.blockTypeName) @@ -90,12 +92,14 @@ export default function WidgetBlock({widgetMetaData, block, actionCallback}: Wid return (); case "INPUT_FIELD": return (); - case "ACTION_BUTTON": - return (); + case "BUTTON": + return (); case "AUDIO": return (); case "IMAGE": return (); + case "REVEAL": + return (); default: return (Unsupported block type: {block.blockTypeName}) } diff --git a/src/qqq/components/widgets/blocks/ActionButtonBlock.tsx b/src/qqq/components/widgets/blocks/ButtonBlock.tsx similarity index 58% rename from src/qqq/components/widgets/blocks/ActionButtonBlock.tsx rename to src/qqq/components/widgets/blocks/ButtonBlock.tsx index 8b0e2ec..6efb8ae 100644 --- a/src/qqq/components/widgets/blocks/ActionButtonBlock.tsx +++ b/src/qqq/components/widgets/blocks/ButtonBlock.tsx @@ -29,30 +29,56 @@ import React from "react"; /******************************************************************************* - ** Block that renders ... an action button... + ** Block that renders ... a button... ** *******************************************************************************/ -export default function ActionButtonBlock({widgetMetaData, data, actionCallback}: StandardBlockComponentProps): JSX.Element +export default function ButtonBlock({widgetMetaData, data, actionCallback}: StandardBlockComponentProps): JSX.Element { - const icon = data.values.iconName ? {data.values.iconName} : null; + const startIcon = data.values.startIcon?.name ? {data.values.startIcon.name} : null; + const endIcon = data.values.endIcon?.name ? {data.values.endIcon.name} : null; function onClick() { - if(actionCallback) + if (actionCallback) { - actionCallback(data, {actionCode: data.values?.actionCode}) + actionCallback(data, data.values); } else { - console.log("ActionButtonBlock onClick with no actionCallback present, so, noop"); + console.log("ButtonBlock onClick with no actionCallback present, so, noop"); } } + let buttonVariant: "gradient" | "outlined" | "text" = "gradient"; + if (data.styles?.format == "outlined") + { + buttonVariant = "outlined"; + } + else if (data.styles?.format == "text") + { + buttonVariant = "text"; + } + else if (data.styles?.format == "filled") + { + buttonVariant = "gradient"; + } + + // todo - button colors... but to do RGB's, might need to move away from MDButton? + return ( - - {data.values.label ?? "Action"} + + {data.values.label ?? "Button"} diff --git a/src/qqq/components/widgets/blocks/InputFieldBlock.tsx b/src/qqq/components/widgets/blocks/InputFieldBlock.tsx index 8c16166..0e412a6 100644 --- a/src/qqq/components/widgets/blocks/InputFieldBlock.tsx +++ b/src/qqq/components/widgets/blocks/InputFieldBlock.tsx @@ -52,10 +52,10 @@ export default function InputFieldBlock({widgetMetaData, data, actionCallback}: // so let us remove the default blur handler, for the first (auto) focus/blur // // cycle, and we seem to have a better time. // //////////////////////////////////////////////////////////////////////////////// - let onBlurRest: {onBlur?: any} = {} + let dynamicFormFieldRest: {onBlur?: any, sx?: any} = {} if(autoFocus && blurCount == 0) { - onBlurRest.onBlur = (event: React.SyntheticEvent) => + dynamicFormFieldRest.onBlur = (event: React.SyntheticEvent) => { event.stopPropagation(); event.preventDefault(); @@ -120,7 +120,18 @@ export default function InputFieldBlock({widgetMetaData, data, actionCallback}: <> {labelElement} - + diff --git a/src/qqq/components/widgets/blocks/TextBlock.tsx b/src/qqq/components/widgets/blocks/TextBlock.tsx index 2790918..4be96fc 100644 --- a/src/qqq/components/widgets/blocks/TextBlock.tsx +++ b/src/qqq/components/widgets/blocks/TextBlock.tsx @@ -20,9 +20,11 @@ */ import Box from "@mui/material/Box"; +import Icon from "@mui/material/Icon"; import BlockElementWrapper from "qqq/components/widgets/blocks/BlockElementWrapper"; import {StandardBlockComponentProps} from "qqq/components/widgets/blocks/BlockModels"; -import DumpJsonBox from "qqq/utils/DumpJsonBox"; +import ProcessWidgetBlockUtils from "qqq/pages/processes/ProcessWidgetBlockUtils"; +import React from "react"; /******************************************************************************* ** Block that renders ... just some text. @@ -32,30 +34,13 @@ import DumpJsonBox from "qqq/utils/DumpJsonBox"; export default function TextBlock({widgetMetaData, data}: StandardBlockComponentProps): JSX.Element { let color = "rgba(0, 0, 0, 0.87)"; - if (data.styles?.standardColor) + if (data.styles?.color) { - switch (data.styles?.standardColor) - { - case "SUCCESS": - color = "#2BA83F"; - break; - case "WARNING": - color = "#FBA132"; - break; - case "ERROR": - color = "#FB4141"; - break; - case "INFO": - color = "#458CFF"; - break; - case "MUTED": - color = "#7b809a"; - break; - } + color = ProcessWidgetBlockUtils.processColorFromStyleMap(data.styles.color); } let boxStyle = {}; - if (data.styles?.isAlert) + if (data.styles?.format == "alert") { boxStyle = { @@ -65,17 +50,112 @@ export default function TextBlock({widgetMetaData, data}: StandardBlockComponent borderRadius: "0.5rem", }; } + else if (data.styles?.format == "banner") + { + boxStyle = + { + background: `${color}40`, + padding: "0.5rem", + }; + } + + let fontSize = "1rem"; + if (data.styles?.size) + { + switch (data.styles.size.toLowerCase()) + { + case "largest": + fontSize = "3rem"; + break; + case "headline": + fontSize = "2rem"; + break; + case "title": + fontSize = "1.5rem"; + break; + case "body": + fontSize = "1rem"; + break; + case "smallest": + fontSize = "0.75rem"; + break; + default: + { + if (data.styles.size.match(/^\d+$/)) + { + fontSize = `${data.styles.size}px`; + } + else + { + fontSize = "1rem"; + } + } + } + } + + let fontWeight = "400"; + if (data.styles?.weight) + { + switch (data.styles.weight.toLowerCase()) + { + case "thin": + case "100": + fontWeight = "100"; + break; + case "extralight": + case "200": + fontWeight = "200"; + break; + case "light": + case "300": + fontWeight = "300"; + break; + case "normal": + case "400": + fontWeight = "400"; + break; + case "medium": + case "500": + fontWeight = "500"; + break; + case "semibold": + case "600": + fontWeight = "600"; + break; + case "bold": + case "700": + fontWeight = "700"; + break; + case "extrabold": + case "800": + fontWeight = "800"; + break; + case "black": + case "900": + fontWeight = "900"; + break; + } + } const text = data.values.interpolatedText ?? data.values.text; const lines = text.split("\n"); + const startIcon = data.values.startIcon?.name ? {data.values.startIcon.name} : null; + const endIcon = data.values.endIcon?.name ? {data.values.endIcon.name} : null; + return ( - + {lines.map((line: string, index: number) => ( -
{line}
+
+ <> + {index == 0 && startIcon ? {startIcon} : null} + {line} + {index == lines.length - 1 && endIcon ? {endIcon} : null} + +
)) }
diff --git a/src/qqq/pages/processes/ProcessRun.tsx b/src/qqq/pages/processes/ProcessRun.tsx index 8a00f65..ef3b1fa 100644 --- a/src/qqq/pages/processes/ProcessRun.tsx +++ b/src/qqq/pages/processes/ProcessRun.tsx @@ -95,10 +95,10 @@ const INITIAL_RETRY_MILLIS = 1_500; const RETRY_MAX_MILLIS = 12_000; const BACKOFF_AMOUNT = 1.5; -/////////////////////////////////////////////////////////////////////////////// -// define some functions that we can make referene to, which we'll overwrite // -// with functions from formik, once we're inside formik. // -/////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// define some functions that we can make reference to, which we'll overwrite // +// with functions from formik, once we're inside formik. // +//////////////////////////////////////////////////////////////////////////////// let formikSetFieldValueFunction = (field: string, value: any, shouldValidate?: boolean): void => { }; @@ -150,6 +150,7 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is const [previouslySeenUpdatedFieldMetaDataMap, setPreviouslySeenUpdatedFieldMetaDataMap] = useState(new Map); const [renderedWidgets, setRenderedWidgets] = useState({} as { [step: string]: { [widgetName: string]: any } }); + const [controlCallbacks, setControlCallbacks] = useState({} as {[name: string]: () => void}) const {pageHeader, recordAnalytics, setPageHeader, helpHelpActive} = useContext(QContext); @@ -351,14 +352,68 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is queryStringParts.push(`${name}=${encodeURIComponent(processValues[name])}`); } + let initialWidgetDataList = null; + if(processValues[widgetName]) + { + processValues[widgetName].hasPermission = true + initialWidgetDataList = [processValues[widgetName]] + } + const renderedWidget = ( - + ); renderedWidgets[activeStep.name][widgetName] = renderedWidget; return renderedWidget; } + /*************************************************************************** + ** + ***************************************************************************/ + function handleControlCode(controlCode: string) + { + const split = controlCode.split(":", 2); + let controlCallbackName: string; + let controlCallbackValue: any + if(split.length == 2) + { + if(split[0] == "showModal") + { + processValues[split[1]] = true + controlCallbackName = split[1] + controlCallbackValue = true + } + else if(split[0] == "hideModal") + { + processValues[split[1]] = false + controlCallbackName = split[1] + controlCallbackValue = false + } + else if(split[0] == "toggleModal") + { + const currentValue = processValues[split[1]] + processValues[split[1]] = !!!currentValue; + controlCallbackName = split[1] + controlCallbackValue = processValues[split[1]] + } + else + { + console.log(`Unexpected part[0] (before colon) in controlCode: [${controlCode}]`) + } + } + else + { + console.log(`Expected controlCode to have 2 colon-delimited parts, but was: [${controlCode}]`) + } + + if(controlCallbackName && controlCallbacks[controlCallbackName]) + { + // @ts-ignore ... args are hard + controlCallbacks[controlCallbackName](controlCallbackValue) + } + } + + /*************************************************************************** ** callback used by widget blocks, e.g., for input-text-enter-on-submit, ** and action buttons. @@ -367,29 +422,58 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is { console.log(`in blockWidgetActionCallback, called by block: ${JSON.stringify(blockData)}`); - /////////////////////////////////////////////////////////////////////////////// - // if the eventValues included an actionCode - validate it before proceeding // - /////////////////////////////////////////////////////////////////////////////// - if (eventValues && eventValues.actionCode && !ProcessWidgetBlockUtils.isActionCodeValid(eventValues.actionCode, activeStep, processValues)) + if(eventValues?.registerControlCallbackName && eventValues?.registerControlCallbackFunction) { - setFormError("Unrecognized action code: " + eventValues.actionCode); + controlCallbacks[eventValues.registerControlCallbackName] = eventValues.registerControlCallbackFunction; + setControlCallbacks(controlCallbacks) + return (true) + } - if (eventValues["_fieldToClearIfError"]) - { - ///////////////////////////////////////////////////////////////////////////// - // if the eventValues included a _fieldToClearIfError, well, then do that. // - ///////////////////////////////////////////////////////////////////////////// - formikSetFieldValueFunction(eventValues["_fieldToClearIfError"], "", false); - } + //////////////////////////////////////////////////////////////////////////////////////////////////////// + // we don't validate these on the android frontend, and it seems fine - just let the app validate it? // + //////////////////////////////////////////////////////////////////////////////////////////////////////// + // /////////////////////////////////////////////////////////////////////////////// + // // if the eventValues included an actionCode - validate it before proceeding // + // /////////////////////////////////////////////////////////////////////////////// + // if (eventValues && eventValues.actionCode && !ProcessWidgetBlockUtils.isActionCodeValid(eventValues.actionCode, activeStep, processValues)) + // { + // setFormError("Unrecognized action code: " + eventValues.actionCode); + // if (eventValues["_fieldToClearIfError"]) + // { + // ///////////////////////////////////////////////////////////////////////////// + // // if the eventValues included a _fieldToClearIfError, well, then do that. // + // ///////////////////////////////////////////////////////////////////////////// + // formikSetFieldValueFunction(eventValues["_fieldToClearIfError"], "", false); + // } + // return (false); + // } - return (false); + let doSubmit = false; + if(blockData?.blockTypeName == "BUTTON" && eventValues?.actionCode) + { + doSubmit = true + } + else if(blockData?.blockTypeName == "BUTTON" && eventValues?.controlCode) + { + handleControlCode(eventValues.controlCode); + doSubmit = false + } + else if(blockData?.blockTypeName == "INPUT_FIELD") + { + /////////////////////////////////////////////////////////////////////////////////////////////// + // if action callback was fired from an input field, assume that means we're good to submit. // + /////////////////////////////////////////////////////////////////////////////////////////////// + doSubmit = true } ////////////////// // ok - submit! // ////////////////// - handleSubmit(eventValues); - return (true); + if(doSubmit) + { + handleSubmit(eventValues); + return (true); + } } @@ -412,7 +496,7 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is ProcessWidgetBlockUtils.dynamicEvaluationOfCompositeWidgetData(compositeWidgetData, processValues); renderedWidgets[key] = - + ; setRenderedWidgets(renderedWidgets); @@ -574,6 +658,7 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is let helpRoles = ["PROCESS_SCREEN", "ALL_SCREENS"]; const showHelp = helpHelpActive || hasHelpContent(step.helpContents, helpRoles); const formattedHelpContent = ; + const isFormatScanner = step?.format?.toLowerCase() == "scanner" return ( <> @@ -582,7 +667,7 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is // hide label on widgets - the Widget component itself provides the label // // for modals, show the process label, but not for full-screen processes (for them, it is in the breadcrumb) // /////////////////////////////////////////////////////////////////////////////////////////////////////////////// - !isWidget && + !isWidget && !isFormatScanner && {(isModal) ? `${overrideLabel ?? process.label}: ` : ""} {step?.label} @@ -1044,7 +1129,7 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is if (doesStepHaveComponent(activeStep, QComponentType.WIDGET)) { - ProcessWidgetBlockUtils.addFieldsForCompositeWidget(activeStep, (fieldMetaData) => + ProcessWidgetBlockUtils.addFieldsForCompositeWidget(activeStep, processValues, (fieldMetaData) => { const dynamicField = DynamicFormUtils.getDynamicField(fieldMetaData); const validation = DynamicFormUtils.getValidationForField(fieldMetaData); @@ -1827,7 +1912,7 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is ) : ( back )} - {processError || qJobRunning || !activeStep ? ( + {processError || qJobRunning || !activeStep || activeStep?.format?.toLowerCase() == "scanner" ? ( ) : ( <> diff --git a/src/qqq/pages/processes/ProcessWidgetBlockUtils.tsx b/src/qqq/pages/processes/ProcessWidgetBlockUtils.tsx index 95a9025..fc67d8f 100644 --- a/src/qqq/pages/processes/ProcessWidgetBlockUtils.tsx +++ b/src/qqq/pages/processes/ProcessWidgetBlockUtils.tsx @@ -71,11 +71,11 @@ export default class ProcessWidgetBlockUtils } // else, continue... } - else if (block.blockTypeName == "ACTION_BUTTON") + else if (block.blockTypeName == "BUTTON") { - ////////////////////////////////////////////////////////// - // actually look at actionCodes on action button blocks // - ////////////////////////////////////////////////////////// + ////////////////////////////////////////// + // look at actionCodes on button blocks // + ////////////////////////////////////////// if (block.values?.actionCode == actionCode) { return (true); @@ -182,7 +182,7 @@ export default class ProcessWidgetBlockUtils /*************************************************************************** ** ***************************************************************************/ - public static addFieldsForCompositeWidget(step: QFrontendStepMetaData, addFieldCallback: (fieldMetaData: QFieldMetaData) => void) + public static addFieldsForCompositeWidget(step: QFrontendStepMetaData, processValues: any, addFieldCallback: (fieldMetaData: QFieldMetaData) => void) { /////////////////////////////////////////////////////////// // private recursive function to walk the composite tree // @@ -200,7 +200,7 @@ export default class ProcessWidgetBlockUtils else if (block.blockTypeName == "INPUT_FIELD") { const fieldMetaData = new QFieldMetaData(block.values?.fieldMetaData); - addFieldCallback(fieldMetaData) + addFieldCallback(fieldMetaData); } } } @@ -210,14 +210,57 @@ export default class ProcessWidgetBlockUtils } } - ///////////////////////////////////////////////////////////////////////////// - // foreach component, if it's an adhoc widget, call recursive helper on it // - ///////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // foreach component, if it's an adhoc widget or a widget w/ its data in the processValues, then, call recursive helper on it // + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// for (let component of step.components) { if (component.type == QComponentType.WIDGET && component.values?.isAdHocWidget) { - recursiveHelper(component.values as unknown as CompositeData) + recursiveHelper(component.values as unknown as CompositeData); + } + else if (component.type == QComponentType.WIDGET && processValues[component.values?.widgetName]) + { + recursiveHelper(processValues[component.values?.widgetName] as unknown as CompositeData); + } + } + } + + + /*************************************************************************** + ** + ***************************************************************************/ + public static processColorFromStyleMap(colorFromStyleMap?: string): string + { + if (colorFromStyleMap) + { + switch (colorFromStyleMap.toUpperCase()) + { + case "SUCCESS": + return("#2BA83F"); + case "WARNING": + return("#FBA132"); + case "ERROR": + return("#FB4141"); + case "INFO": + return("#458CFF"); + case "MUTED": + return("#7b809a"); + default: + { + if (colorFromStyleMap.match(/^[0-9A-F]{6}$/)) + { + return(`#${colorFromStyleMap}`); + } + else if (colorFromStyleMap.match(/^[0-9A-F]{8}$/)) + { + return(`#${colorFromStyleMap}`); + } + else + { + return(colorFromStyleMap); + } + } } } }