CE-1727 - Add inlinePossibleValueSources option to fields; pass more data into DynamicSelect in a named object, so it's a little less loosely defined.

This commit is contained in:
2024-09-09 16:01:30 -05:00
parent d2705c3aed
commit 3dc92aec88
8 changed files with 142 additions and 61 deletions

View File

@ -172,14 +172,10 @@ function QDynamicForm({formData, formLabel, bulkEditMode, bulkEditSwitchChangeHa
<Grid item xs={12} sm={6} key={fieldName}>
{labelElement}
<DynamicSelect
tableName={field.possibleValueProps.tableName}
processName={field.possibleValueProps.processName}
possibleValueSourceName={field.possibleValueProps.possibleValueSourceName}
fieldName={field.possibleValueProps.fieldName}
fieldPossibleValueProps={field.possibleValueProps}
isEditable={field.isEditable}
fieldLabel=""
initialValue={values[fieldName]}
initialDisplayValue={field.possibleValueProps.initialDisplayValue}
bulkEditMode={bulkEditMode}
bulkEditSwitchChangeHandler={bulkEditSwitchChanged}
otherValues={otherValuesMap}

View File

@ -22,6 +22,7 @@
import {AdornmentType} from "@kingsrook/qqq-frontend-core/lib/model/metaData/AdornmentType";
import {QFieldMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldMetaData";
import {QFieldType} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldType";
import {FieldPossibleValueProps} from "qqq/models/fields/FieldPossibleValueProps";
import * as Yup from "yup";
@ -155,47 +156,49 @@ class DynamicFormUtils
{
const field = qFields[i];
if(!dynamicFormFields[field.name])
{
continue;
}
/////////////////////////////////////////
// add props for possible value fields //
/////////////////////////////////////////
if (field.possibleValueSourceName && dynamicFormFields[field.name])
if (field.possibleValueSourceName || field.inlinePossibleValueSource)
{
let initialDisplayValue = null;
let props: FieldPossibleValueProps =
{
isPossibleValue: true,
fieldName: field.name,
initialDisplayValue: null
}
if (displayValues)
{
initialDisplayValue = displayValues.get(field.name);
props.initialDisplayValue = displayValues.get(field.name);
}
if (tableName)
if(field.inlinePossibleValueSource)
{
dynamicFormFields[field.name].possibleValueProps =
{
isPossibleValue: true,
tableName: tableName,
fieldName: field.name,
initialDisplayValue: initialDisplayValue,
};
//////////////////////////////////////////////////////////////////////
// handle an inline PVS - which is a list of possible value objects //
//////////////////////////////////////////////////////////////////////
props.possibleValues = field.inlinePossibleValueSource;
}
else if (tableName)
{
props.tableName = tableName;
}
else if (processName)
{
dynamicFormFields[field.name].possibleValueProps =
{
isPossibleValue: true,
processName: processName,
fieldName: field.name,
initialDisplayValue: initialDisplayValue,
};
props.processName = processName;
}
else
{
dynamicFormFields[field.name].possibleValueProps =
{
isPossibleValue: true,
initialDisplayValue: initialDisplayValue,
fieldName: field.name,
possibleValueSourceName: field.possibleValueSourceName
};
props.possibleValueSourceName = field.possibleValueSourceName;
}
dynamicFormFields[field.name].possibleValueProps = props;
}
}
}

View File

@ -30,20 +30,17 @@ import TextField from "@mui/material/TextField";
import {ErrorMessage, useFormikContext} from "formik";
import colors from "qqq/assets/theme/base/colors";
import MDTypography from "qqq/components/legacy/MDTypography";
import {FieldPossibleValueProps} from "qqq/models/fields/FieldPossibleValueProps";
import Client from "qqq/utils/qqq/Client";
import React, {useEffect, useState} from "react";
interface Props
{
tableName?: string;
processName?: string;
fieldName?: string;
possibleValueSourceName?: string;
fieldPossibleValueProps: FieldPossibleValueProps;
overrideId?: string;
fieldLabel: string;
inForm: boolean;
initialValue?: any;
initialDisplayValue?: string;
initialValues?: QPossibleValue[];
onChange?: any;
isEditable?: boolean;
@ -57,13 +54,8 @@ interface Props
}
DynamicSelect.defaultProps = {
tableName: null,
processName: null,
fieldName: null,
possibleValueSourceName: null,
inForm: true,
initialValue: null,
initialDisplayValue: null,
initialValues: undefined,
onChange: null,
isEditable: true,
@ -103,8 +95,10 @@ export const getAutocompleteOutlinedStyle = (isDisabled: boolean) =>
const qController = Client.getInstance();
function DynamicSelect({tableName, processName, fieldName, possibleValueSourceName, overrideId, fieldLabel, inForm, initialValue, initialDisplayValue, initialValues, onChange, isEditable, isMultiple, bulkEditMode, bulkEditSwitchChangeHandler, otherValues, variant, initiallyOpen, useCase}: Props)
function DynamicSelect({fieldPossibleValueProps, overrideId, fieldLabel, inForm, initialValue, initialValues, onChange, isEditable, isMultiple, bulkEditMode, bulkEditSwitchChangeHandler, otherValues, variant, initiallyOpen, useCase}: Props)
{
const {fieldName, initialDisplayValue, possibleValueSourceName, possibleValues, processName, tableName} = fieldPossibleValueProps;
const [open, setOpen] = useState(initiallyOpen);
const [options, setOptions] = useState<readonly QPossibleValue[]>([]);
const [searchTerm, setSearchTerm] = useState(null);
@ -172,6 +166,35 @@ function DynamicSelect({tableName, processName, fieldName, possibleValueSourceNa
setFieldValueRef = setFieldValue;
}
/*******************************************************************************
**
*******************************************************************************/
const filterInlinePossibleValues = (searchTerm: string, possibleValues: QPossibleValue[]): QPossibleValue[] =>
{
return possibleValues.filter(pv => pv.label?.toLowerCase().startsWith(searchTerm));
}
/***************************************************************************
**
***************************************************************************/
const loadResults = async (): Promise<QPossibleValue[]> =>
{
if(possibleValues)
{
return filterInlinePossibleValues(searchTerm, possibleValues)
}
else
{
return await qController.possibleValues(tableName, processName, possibleValueSourceName ?? fieldName, searchTerm ?? "", null, otherValues, useCase);
}
}
/***************************************************************************
**
***************************************************************************/
useEffect(() =>
{
if (firstRender)
@ -195,7 +218,7 @@ function DynamicSelect({tableName, processName, fieldName, possibleValueSourceNa
(async () =>
{
// console.log(`doing a search with ${searchTerm}`);
const results: QPossibleValue[] = await qController.possibleValues(tableName, processName, possibleValueSourceName ?? fieldName, searchTerm ?? "", null, otherValues, useCase);
const results: QPossibleValue[] = await loadResults();
if (tableMetaData == null && tableName)
{
@ -218,7 +241,10 @@ function DynamicSelect({tableName, processName, fieldName, possibleValueSourceNa
};
}, [searchTerm]);
// todo - finish... call it in onOpen?
/***************************************************************************
** todo - finish... call it in onOpen?
***************************************************************************/
const reloadIfOtherValuesAreChanged = () =>
{
if (JSON.stringify(Object.fromEntries(otherValues)) != otherValuesWhenResultsWereLoaded)
@ -227,8 +253,10 @@ function DynamicSelect({tableName, processName, fieldName, possibleValueSourceNa
{
setLoading(true);
setOptions([]);
console.log("Refreshing possible values...");
const results: QPossibleValue[] = await qController.possibleValues(tableName, processName, possibleValueSourceName ?? fieldName, searchTerm ?? "", null, otherValues, useCase);
const results: QPossibleValue[] = await loadResults();
setLoading(false);
setOptions([...results]);
setOtherValuesWhenResultsWereLoaded(JSON.stringify(Object.fromEntries(otherValues)));
@ -236,6 +264,10 @@ function DynamicSelect({tableName, processName, fieldName, possibleValueSourceNa
}
};
/***************************************************************************
**
***************************************************************************/
const inputChanged = (event: React.SyntheticEvent, value: string, reason: string) =>
{
// console.log(`input changed. Reason: ${reason}, setting search term to ${value}`);
@ -246,11 +278,19 @@ function DynamicSelect({tableName, processName, fieldName, possibleValueSourceNa
}
};
/***************************************************************************
**
***************************************************************************/
const handleBlur = (x: any) =>
{
setSearchTerm(null);
};
/***************************************************************************
**
***************************************************************************/
const handleChanged = (event: React.SyntheticEvent, value: any | any[], reason: string, details?: string) =>
{
// console.log("handleChanged. value is:");
@ -274,6 +314,10 @@ function DynamicSelect({tableName, processName, fieldName, possibleValueSourceNa
}
};
/***************************************************************************
**
***************************************************************************/
const filterOptions = (options: { id: any; label: string; }[], state: FilterOptionsState<{ id: any; label: string; }>): { id: any; label: string; }[] =>
{
/////////////////////////////////////////////////////////////////////////////////
@ -283,6 +327,10 @@ function DynamicSelect({tableName, processName, fieldName, possibleValueSourceNa
return (options);
};
/***************************************************************************
**
***************************************************************************/
// @ts-ignore
const renderOption = (props: Object, option: any, {selected}) =>
{
@ -331,6 +379,10 @@ function DynamicSelect({tableName, processName, fieldName, possibleValueSourceNa
);
};
/***************************************************************************
**
***************************************************************************/
const bulkEditSwitchChanged = () =>
{
const newSwitchValue = !switchChecked;
@ -351,7 +403,7 @@ function DynamicSelect({tableName, processName, fieldName, possibleValueSourceNa
const autocomplete = (
<Box>
<Autocomplete
id={overrideId ?? fieldName ?? possibleValueSourceName}
id={overrideId ?? fieldName ?? possibleValueSourceName ?? "anonymous"}
sx={autocompleteSX}
open={open}
fullWidth
@ -431,7 +483,7 @@ function DynamicSelect({tableName, processName, fieldName, possibleValueSourceNa
inForm &&
<Box mt={0.75}>
<MDTypography component="div" variant="caption" color="error" fontWeight="regular">
{!isDisabled && <div className="fieldErrorMessage"><ErrorMessage name={fieldName ?? possibleValueSourceName} render={msg => <span data-field-error="true">{msg}</span>} /></div>}
{!isDisabled && <div className="fieldErrorMessage"><ErrorMessage name={overrideId ?? fieldName ?? possibleValueSourceName ?? "anonymous"} render={msg => <span data-field-error="true">{msg}</span>} /></div>}
</MDTypography>
</Box>
}

View File

@ -367,13 +367,11 @@ function FilterCriteriaRowValues({operatorOption, criteria, field, table, valueC
) : (
<Box width={"100%"}>
<DynamicSelect
tableName={table.name}
fieldName={field.name}
fieldPossibleValueProps={{tableName: table.name, fieldName: field.name, initialDisplayValue: selectedPossibleValue?.label}}
overrideId={field.name + "-single-" + criteria.id}
key={field.name + "-single-" + criteria.id}
fieldLabel="Value"
initialValue={selectedPossibleValue?.id}
initialDisplayValue={selectedPossibleValue?.label}
inForm={false}
onChange={(value: any) => valueChangeHandler(null, 0, value)}
variant="standard"
@ -402,8 +400,7 @@ function FilterCriteriaRowValues({operatorOption, criteria, field, table, valueC
}
return <Box>
<DynamicSelect
tableName={table.name}
fieldName={field.name}
fieldPossibleValueProps={{tableName: table.name, fieldName: field.name, initialDisplayValue: null}}
overrideId={field.name + "-multi-" + criteria.id}
key={field.name + "-multi-" + criteria.id}
isMultiple

View File

@ -440,10 +440,10 @@ function ScriptEditor({title, scriptId, scriptRevisionRecord, closeCallback, tab
<Box sx={{height: openTool ? "45%" : "100%"}}>
<Grid container alignItems="flex-end">
<Box maxWidth={"50%"} minWidth={300}>
<DynamicSelect fieldName={"apiName"} initialValue={apiName} initialDisplayValue={apiNameLabel} fieldLabel={"API Name *"} tableName={"scriptRevision"} inForm={false} onChange={changeApiName} useCase="form" />
<DynamicSelect fieldPossibleValueProps={{tableName: "scriptRevision", fieldName: "apiName", initialDisplayValue: apiNameLabel}} initialValue={apiName} fieldLabel={"API Name *"} inForm={false} onChange={changeApiName} useCase="form" />
</Box>
<Box maxWidth={"50%"} minWidth={300} pl={2}>
<DynamicSelect fieldName={"apiVersion"} initialValue={apiVersion} initialDisplayValue={apiVersionLabel} fieldLabel={"API Version *"} tableName={"scriptRevision"} inForm={false} onChange={changeApiVersion} useCase="form" />
<DynamicSelect fieldPossibleValueProps={{tableName: "scriptRevision", fieldName: "apiVersion", initialDisplayValue: apiVersionLabel}} initialValue={apiVersion} fieldLabel={"API Version *"} inForm={false} onChange={changeApiVersion} useCase="form" />
</Box>
</Grid>
<Box display="flex" sx={{height: "100%"}}>

View File

@ -391,10 +391,9 @@ export default function ShareModal({open, onClose, tableMetaData, record}: Share
<Box display="flex" flexDirection="row" alignItems="center">
<Box width="550px" pr={2} mb={-1.5}>
<DynamicSelect
possibleValueSourceName={shareableTableMetaData.audiencePossibleValueSourceName}
fieldPossibleValueProps={{possibleValueSourceName: shareableTableMetaData.audiencePossibleValueSourceName, initialDisplayValue: selectedAudienceOption?.label}}
fieldLabel="User or Group" // todo should come from shareableTableMetaData
initialValue={selectedAudienceOption?.id}
initialDisplayValue={selectedAudienceOption?.label}
inForm={false}
onChange={handleAudienceChange}
useCase="form"

View File

@ -0,0 +1,38 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2024. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {QPossibleValue} from "@kingsrook/qqq-frontend-core/lib/model/QPossibleValue";
/*******************************************************************************
** Properties attached to a (formik?) form field, to denote how it behaves as
** as related to a possible value source.
*******************************************************************************/
export interface FieldPossibleValueProps
{
isPossibleValue?: boolean;
possibleValues?: QPossibleValue[];
initialDisplayValue: string | null;
fieldName?: string;
tableName?: string;
processName?: string;
possibleValueSourceName?: string;
}

View File

@ -779,11 +779,9 @@ function InputPossibleValueSourceSingle(tableName: string, field: QFieldMetaData
}}
>
<DynamicSelect
tableName={tableName}
fieldName={field.name}
fieldPossibleValueProps={{tableName: tableName, fieldName: field.name, initialDisplayValue: selectedPossibleValue?.label}}
fieldLabel="Value"
initialValue={selectedPossibleValue?.id}
initialDisplayValue={selectedPossibleValue?.label}
inForm={false}
onChange={handleChange}
useCase="filter"
@ -848,11 +846,9 @@ function InputPossibleValueSourceMultiple(tableName: string, field: QFieldMetaDa
}}
>
<DynamicSelect
tableName={tableName}
fieldName={field.name}
fieldPossibleValueProps={{tableName: tableName, fieldName: field.name, initialDisplayValue: null}}
isMultiple={true}
fieldLabel="Value"
initialValues={selectedPossibleValues}
inForm={false}
onChange={handleChange}
useCase="filter"