mirror of
https://github.com/Kingsrook/qqq-frontend-material-dashboard.git
synced 2025-07-18 05:10:45 +00:00
CE-551 Implement default values from fieldMetaData for record create; scroll to error; add errors to possible-value (DynamicSelect);
This commit is contained in:
@ -135,7 +135,7 @@ function QDynamicFormField({
|
|||||||
/>
|
/>
|
||||||
<Box mt={0.75}>
|
<Box mt={0.75}>
|
||||||
<MDTypography component="div" variant="caption" color="error" fontWeight="regular">
|
<MDTypography component="div" variant="caption" color="error" fontWeight="regular">
|
||||||
{!isDisabled && <div className="fieldErrorMessage"><ErrorMessage name={name} /></div>}
|
{!isDisabled && <div className="fieldErrorMessage"><ErrorMessage name={name} render={msg => <span data-field-error="true">{msg}</span>} /></div>}
|
||||||
</MDTypography>
|
</MDTypography>
|
||||||
</Box>
|
</Box>
|
||||||
</>
|
</>
|
||||||
|
@ -103,9 +103,20 @@ class DynamicFormUtils
|
|||||||
public static getValidationForField(field: QFieldMetaData)
|
public static getValidationForField(field: QFieldMetaData)
|
||||||
{
|
{
|
||||||
if (field.isRequired)
|
if (field.isRequired)
|
||||||
|
{
|
||||||
|
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} is required.`).nullable(true));
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
return (Yup.string().required(`${field.label} is required.`));
|
return (Yup.string().required(`${field.label} is required.`));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return (null);
|
return (null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,8 +27,9 @@ import Autocomplete from "@mui/material/Autocomplete";
|
|||||||
import Box from "@mui/material/Box";
|
import Box from "@mui/material/Box";
|
||||||
import Switch from "@mui/material/Switch";
|
import Switch from "@mui/material/Switch";
|
||||||
import TextField from "@mui/material/TextField";
|
import TextField from "@mui/material/TextField";
|
||||||
import {useFormikContext} from "formik";
|
import {ErrorMessage, useFormikContext} from "formik";
|
||||||
import React, {useEffect, useState} from "react";
|
import React, {useEffect, useState} from "react";
|
||||||
|
import MDTypography from "qqq/components/legacy/MDTypography";
|
||||||
import Client from "qqq/utils/qqq/Client";
|
import Client from "qqq/utils/qqq/Client";
|
||||||
|
|
||||||
interface Props
|
interface Props
|
||||||
@ -246,6 +247,7 @@ function DynamicSelect({tableName, processName, fieldName, overrideId, fieldLabe
|
|||||||
// console.log(`default value: ${JSON.stringify(defaultValue)}`);
|
// console.log(`default value: ${JSON.stringify(defaultValue)}`);
|
||||||
|
|
||||||
const autocomplete = (
|
const autocomplete = (
|
||||||
|
<Box>
|
||||||
<Autocomplete
|
<Autocomplete
|
||||||
id={overrideId ?? fieldName}
|
id={overrideId ?? fieldName}
|
||||||
sx={{background: isDisabled ? "#f0f2f5!important" : "initial"}}
|
sx={{background: isDisabled ? "#f0f2f5!important" : "initial"}}
|
||||||
@ -318,6 +320,12 @@ function DynamicSelect({tableName, processName, fieldName, overrideId, fieldLabe
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
<Box mt={0.75}>
|
||||||
|
<MDTypography component="div" variant="caption" color="error" fontWeight="regular">
|
||||||
|
{!isDisabled && <div className="fieldErrorMessage"><ErrorMessage name={fieldName} render={msg => <span data-field-error="true">{msg}</span>} /></div>}
|
||||||
|
</MDTypography>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
|
@ -32,8 +32,8 @@ import Box from "@mui/material/Box";
|
|||||||
import Card from "@mui/material/Card";
|
import Card from "@mui/material/Card";
|
||||||
import Grid from "@mui/material/Grid";
|
import Grid from "@mui/material/Grid";
|
||||||
import Icon from "@mui/material/Icon";
|
import Icon from "@mui/material/Icon";
|
||||||
import {Form, Formik} from "formik";
|
import {Form, Formik, useFormikContext} from "formik";
|
||||||
import React, {useContext, useReducer, useState} from "react";
|
import React, {useContext, useEffect, useReducer, useState} from "react";
|
||||||
import {useLocation, useNavigate, useParams} from "react-router-dom";
|
import {useLocation, useNavigate, useParams} from "react-router-dom";
|
||||||
import * as Yup from "yup";
|
import * as Yup from "yup";
|
||||||
import QContext from "QContext";
|
import QContext from "QContext";
|
||||||
@ -77,7 +77,7 @@ function EntityForm(props: Props): JSX.Element
|
|||||||
|
|
||||||
const [formTitle, setFormTitle] = useState("");
|
const [formTitle, setFormTitle] = useState("");
|
||||||
const [validations, setValidations] = useState({});
|
const [validations, setValidations] = useState({});
|
||||||
const [initialValues, setInitialValues] = useState({} as { [key: string]: string });
|
const [initialValues, setInitialValues] = useState({} as { [key: string]: any });
|
||||||
const [formFields, setFormFields] = useState(null as Map<string, any>);
|
const [formFields, setFormFields] = useState(null as Map<string, any>);
|
||||||
const [t1sectionName, setT1SectionName] = useState(null as string);
|
const [t1sectionName, setT1SectionName] = useState(null as string);
|
||||||
const [nonT1Sections, setNonT1Sections] = useState([] as QTableSection[]);
|
const [nonT1Sections, setNonT1Sections] = useState([] as QTableSection[]);
|
||||||
@ -233,15 +233,14 @@ function EntityForm(props: Props): JSX.Element
|
|||||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// if default values were supplied for a new record, then populate initialValues, for formik. //
|
// if default values were supplied for a new record, then populate initialValues, for formik. //
|
||||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
if(defaultValues)
|
|
||||||
{
|
|
||||||
for (let i = 0; i < fieldArray.length; i++)
|
for (let i = 0; i < fieldArray.length; i++)
|
||||||
{
|
{
|
||||||
const fieldMetaData = fieldArray[i];
|
const fieldMetaData = fieldArray[i];
|
||||||
const fieldName = fieldMetaData.name;
|
const fieldName = fieldMetaData.name;
|
||||||
if (defaultValues[fieldName])
|
const defaultValue = (defaultValues && defaultValues[fieldName]) ? defaultValues[fieldName] : fieldMetaData.defaultValue;
|
||||||
|
if (defaultValue)
|
||||||
{
|
{
|
||||||
initialValues[fieldName] = defaultValues[fieldName];
|
initialValues[fieldName] = defaultValue;
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// we need to set the initialDisplayValue for possible value fields with a default value //
|
// we need to set the initialDisplayValue for possible value fields with a default value //
|
||||||
@ -253,7 +252,7 @@ function EntityForm(props: Props): JSX.Element
|
|||||||
if (results && results.length > 0)
|
if (results && results.length > 0)
|
||||||
{
|
{
|
||||||
defaultDisplayValues.set(fieldName, results[0].label);
|
defaultDisplayValues.set(fieldName, results[0].label);
|
||||||
}
|
initialValues[fieldName] = {id: defaultValue, value: results[0].label}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -598,6 +597,7 @@ function EntityForm(props: Props): JSX.Element
|
|||||||
isSubmitting,
|
isSubmitting,
|
||||||
}) => (
|
}) => (
|
||||||
<Form id={formId} autoComplete="off">
|
<Form id={formId} autoComplete="off">
|
||||||
|
<ScrollToFirstError />
|
||||||
|
|
||||||
<Box pb={3} pt={0}>
|
<Box pb={3} pt={0}>
|
||||||
<Card id={`${t1sectionName}`} sx={{overflow: "visible", pb: 2, scrollMarginTop: "100px"}} elevation={cardElevation}>
|
<Card id={`${t1sectionName}`} sx={{overflow: "visible", pb: 2, scrollMarginTop: "100px"}} elevation={cardElevation}>
|
||||||
@ -672,4 +672,43 @@ function EntityForm(props: Props): JSX.Element
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function ScrollToFirstError(): JSX.Element
|
||||||
|
{
|
||||||
|
const {submitCount, isValid} = useFormikContext()
|
||||||
|
|
||||||
|
useEffect(() =>
|
||||||
|
{
|
||||||
|
/////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Wrap the code in setTimeout to make sure it runs after the DOM has been //
|
||||||
|
// updated and has the error message elements. //
|
||||||
|
/////////////////////////////////////////////////////////////////////////////
|
||||||
|
setTimeout(() =>
|
||||||
|
{
|
||||||
|
////////////////////////////////////////
|
||||||
|
// Only run on submit or if not valid //
|
||||||
|
////////////////////////////////////////
|
||||||
|
if (submitCount === 0 || isValid)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////////////
|
||||||
|
// Find the first error message //
|
||||||
|
//////////////////////////////////
|
||||||
|
const errorMessageSelector = "[data-field-error]";
|
||||||
|
const firstErrorMessage = document.querySelector(errorMessageSelector);
|
||||||
|
if (!firstErrorMessage)
|
||||||
|
{
|
||||||
|
console.warn(`Form failed validation but no error field was found with selector: ${errorMessageSelector}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
firstErrorMessage.scrollIntoView({block: "center"});
|
||||||
|
|
||||||
|
}, 100)
|
||||||
|
}, [submitCount, isValid])
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
export default EntityForm;
|
export default EntityForm;
|
||||||
|
Reference in New Issue
Block a user