diff --git a/src/qqq/components/EntityForm/index.tsx b/src/qqq/components/EntityForm/index.tsx index 97a6a2f..77f4d5e 100644 --- a/src/qqq/components/EntityForm/index.tsx +++ b/src/qqq/components/EntityForm/index.tsx @@ -1,26 +1,16 @@ -/** - ========================================================= - * Material Dashboard 2 PRO React TS - v1.0.0 - ========================================================= - - * Product Page: https://www.creative-tim.com/product/material-dashboard-2-pro-react-ts - * Copyright 2022 Creative Tim (https://www.creative-tim.com) - - Coded by www.creative-tim.com - - ========================================================= - - * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - */ - -// react imports +// react components import { useParams } from "react-router-dom"; import React, { useReducer, useState } from "react"; -// qqq imports +// misc components +import * as Yup from "yup"; +import { Form, Formik } from "formik"; + +// qqq components import { QController } from "@kingsrook/qqq-frontend-core/lib/controllers/QController"; -import { QRecord } from "@kingsrook/qqq-frontend-core/lib/model/QRecord"; -import { QFieldType } from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldType"; +import DynamicFormUtils from "qqq/components/QDynamicForm/utils/DynamicFormUtils"; +import QDynamicForm from "qqq/components/QDynamicForm"; +import { QFieldMetaData } from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldMetaData"; // @material-ui core components import Card from "@mui/material/Card"; @@ -29,7 +19,6 @@ import Grid from "@mui/material/Grid"; // Material Dashboard 2 PRO React TS components import MDBox from "components/MDBox"; import MDTypography from "components/MDTypography"; -import FormField from "layouts/pages/account/components/FormField"; import MDButton from "../../../components/MDButton"; // Declaring props types for EntityForm @@ -41,17 +30,24 @@ function EntityForm({ id }: Props): JSX.Element { const qController = new QController(""); const { tableName } = useParams(); + const [validations, setValidations] = useState({}); + const [initialValues, setInitialValues] = useState({} as { [key: string]: string }); + const [formFields, setFormFields] = useState({}); + const [asyncLoadInited, setAsyncLoadInited] = useState(false); const [formValues, setFormValues] = useState({} as { [key: string]: string }); - const [formFields, setFormFields] = useState([] as JSX.Element[]); const [tableMetaData, setTableMetaData] = useState(null); const [, forceUpdate] = useReducer((x) => x + 1, 0); - const handleInputChange = (e: { target: { name: any; value: any } }) => { - const { name, value } = e.target; - formValues[name] = value; - setFormValues(formValues); - }; + function getDynamicStepContent(formData: any): JSX.Element { + const { formFields, values, errors, touched } = formData; + + if (!Object.keys(formFields).length) { + return
Loading...
; + } + + return ; + } if (!asyncLoadInited) { setAsyncLoadInited(true); @@ -59,73 +55,46 @@ function EntityForm({ id }: Props): JSX.Element { const tableMetaData = await qController.loadTableMetaData(tableName); setTableMetaData(tableMetaData); + const fieldArray = [] as QFieldMetaData[]; + const sortedKeys = [...tableMetaData.fields.keys()].sort(); + sortedKeys.forEach((key) => { + const fieldMetaData = tableMetaData.fields.get(key); + fieldArray.push(fieldMetaData); + }); + if (id !== null) { - const foundRecord = await qController.get(tableName, id); + const record = await qController.get(tableName, id); tableMetaData.fields.forEach((fieldMetaData, key) => { - formValues[key] = foundRecord.values.get(key); + initialValues[key] = record.values.get(key); }); setFormValues(formValues); } - const sortedKeys = [...tableMetaData.fields.keys()].sort(); - sortedKeys.forEach((key) => { - const fieldMetaData = tableMetaData.fields.get(key); - if (fieldMetaData.name !== tableMetaData.primaryKeyField) { - let fieldType: string; - switch (fieldMetaData.type.toString()) { - case QFieldType.DECIMAL: - case QFieldType.INTEGER: - fieldType = "number"; - break; - case QFieldType.DATE_TIME: - fieldType = "datetime-local"; - break; - case QFieldType.PASSWORD: - case QFieldType.TIME: - case QFieldType.DATE: - fieldType = fieldMetaData.type.toString(); - break; - case QFieldType.TEXT: - case QFieldType.HTML: - case QFieldType.STRING: - default: - fieldType = "text"; - } + const { dynamicFormFields, formValidations } = DynamicFormUtils.getFormData(fieldArray); + setInitialValues(initialValues); + setFormFields(dynamicFormFields); + setValidations(Yup.object().shape(formValidations)); - formFields.push( - - - - ); - } - }); - - setFormFields(formFields); forceUpdate(); })(); } - const handleSubmit = (event: { preventDefault: () => void }) => { - event.preventDefault(); - - (async () => { + const handleSubmit = async (values: any, actions: any) => { + actions.setSubmitting(true); + await (async () => { if (id !== null) { - await qController.update(tableName, id, formValues).then((record) => { - window.location.href = `/${tableName}/view/${record.values.get("id")}`; // todo - primaryKeyField + await qController.update(tableName, id, values).then((record) => { + window.location.href = `/${tableName}/${record.values.get( + tableMetaData.primaryKeyField + )}`; }); } else { - await qController.create(tableName, formValues).then((record) => { - window.location.href = `/${tableName}/view/${record.values.get("id")}`; // todo - primaryKeyField + await qController.create(tableName, values).then((record) => { + window.location.href = `/${tableName}/${record.values.get( + tableMetaData.primaryKeyField + )}`; }); } })(); @@ -133,24 +102,46 @@ function EntityForm({ id }: Props): JSX.Element { const pageTitle = id != null ? `Edit ${tableMetaData?.label} (${id})` : `Create New ${tableMetaData?.label}`; + const formId = + id != null ? `edit-${tableMetaData?.label}-form` : `create-${tableMetaData?.label}-form`; return ( - + {pageTitle} - - - {formFields} - - - - - - - save {tableMetaData?.label} - - + + + + {({ values, errors, touched, isSubmitting }) => ( +
+ + + {/*************************************************************************** + ** step content - e.g., the appropriate form or other screen for the step ** + ***************************************************************************/} + {getDynamicStepContent({ + values, + touched, + formFields, + errors, + })} + + + + save {tableMetaData?.label} + + + + + +
+ )} +
diff --git a/src/qqq/components/QDynamicForm/index.tsx b/src/qqq/components/QDynamicForm/index.tsx index 0119315..ce5c695 100644 --- a/src/qqq/components/QDynamicForm/index.tsx +++ b/src/qqq/components/QDynamicForm/index.tsx @@ -25,29 +25,30 @@ import FormField from "layouts/pages/users/new-user/components/FormField"; import { QFrontendStepMetaData } from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFrontendStepMetaData"; interface Props { + formLabel?: string; formData: any; - step?: QFrontendStepMetaData | undefined; + primaryKeyId?: string; } function QDynamicForm(props: Props): JSX.Element { - const { formData, step } = props; + const { formData, formLabel, primaryKeyId } = props; const { formFields, values, errors, touched } = formData; /* - const { - firstName: firstNameV, - lastName: lastNameV, - company: companyV, - email: emailV, - password: passwordV, - repeatPassword: repeatPasswordV, - } = values; - */ + const { + firstName: firstNameV, + lastName: lastNameV, + company: companyV, + email: emailV, + password: passwordV, + repeatPassword: repeatPasswordV, + } = values; + */ return ( - {step?.label} + {formLabel} {/* TODO - help text Mandatory information @@ -60,12 +61,16 @@ function QDynamicForm(props: Props): JSX.Element { Object.keys(formFields).length > 0 && Object.keys(formFields).map((fieldName: any) => { const field = formFields[fieldName]; + if (primaryKeyId && fieldName === primaryKeyId) { + return null; + } if (values[fieldName] === undefined) { values[fieldName] = ""; } return ( . + */ + +import * as Yup from "yup"; + +import { QFieldMetaData } from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldMetaData"; +import { QFieldType } from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldType"; + +/******************************************************************************* + ** Meta-data to represent a single field in a table. + ** + *******************************************************************************/ +class DynamicFormUtils { + public static getFormData(qqqFormFields: QFieldMetaData[]) { + const dynamicFormFields: any = {}; + const formValidations: any = {}; + + qqqFormFields.forEach((field) => { + let fieldType: string; + switch (field.type.toString()) { + case QFieldType.DECIMAL: + case QFieldType.INTEGER: + fieldType = "number"; + break; + case QFieldType.DATE_TIME: + fieldType = "datetime-local"; + break; + case QFieldType.PASSWORD: + case QFieldType.TIME: + case QFieldType.DATE: + fieldType = field.type.toString(); + break; + case QFieldType.TEXT: + case QFieldType.HTML: + case QFieldType.STRING: + default: + fieldType = "text"; + } + + dynamicFormFields[field.name] = { + name: field.name, + label: field.label ? field.label : field.name, + isRequired: field.isRequired, + type: fieldType, + // todo invalidMsg: "Zipcode is not valid (e.g. 70000).", + }; + + if (field.isRequired) { + formValidations[field.name] = Yup.string().required(`${field.label} is required.`); + } + }); + + return { dynamicFormFields, formValidations }; + } +} + +export default DynamicFormUtils; diff --git a/src/qqq/pages/entity-list/index.tsx b/src/qqq/pages/entity-list/index.tsx index 5d18b0d..cd53a75 100644 --- a/src/qqq/pages/entity-list/index.tsx +++ b/src/qqq/pages/entity-list/index.tsx @@ -22,23 +22,24 @@ import Icon from "@mui/material/Icon"; import Menu from "@mui/material/Menu"; import MenuItem from "@mui/material/MenuItem"; import Divider from "@mui/material/Divider"; - -// Material Dashboard 2 PRO React TS components -import MDBox from "components/MDBox"; -import MDTypography from "components/MDTypography"; -import MDButton from "components/MDButton"; +import Link from "@mui/material/Link"; // Material Dashboard 2 PRO React TS examples components import DashboardLayout from "examples/LayoutContainers/DashboardLayout"; import DashboardNavbar from "examples/Navbars/DashboardNavbar"; import DataTable from "examples/Tables/DataTable"; -// Data +// QQQ import { QProcessMetaData } from "@kingsrook/qqq-frontend-core/lib/model/metaData/QProcessMetaData"; import { QController } from "@kingsrook/qqq-frontend-core/lib/controllers/QController"; -import Link from "@mui/material/Link"; import { QTableMetaData } from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData"; import { useParams } from "react-router-dom"; +import DynamicFormUtils from "qqq/components/QDynamicForm/utils/DynamicFormUtils"; + +// Material Dashboard 2 PRO React TS components +import MDBox from "components/MDBox"; +import MDTypography from "components/MDTypography"; +import MDButton from "components/MDButton"; import Footer from "../../components/Footer"; import IdCell from "../../components/EntityForm/components/IdCell"; diff --git a/src/qqq/pages/entity-view/components/ViewContents/index.tsx b/src/qqq/pages/entity-view/components/ViewContents/index.tsx index a48880c..97b03f7 100644 --- a/src/qqq/pages/entity-view/components/ViewContents/index.tsx +++ b/src/qqq/pages/entity-view/components/ViewContents/index.tsx @@ -13,7 +13,9 @@ * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. */ +// react components import { useParams } from "react-router-dom"; +import React, { useReducer, useState } from "react"; // @material-ui core components import Card from "@mui/material/Card"; @@ -26,17 +28,12 @@ import DialogContentText from "@mui/material/DialogContentText"; import DialogActions from "@mui/material/DialogActions"; import Button from "@mui/material/Button"; +// qqq imports +import { QController } from "@kingsrook/qqq-frontend-core/lib/controllers/QController"; + // Material Dashboard 2 PRO React TS components import MDBox from "components/MDBox"; import MDTypography from "components/MDTypography"; - -// Settings page components - -// qqq imports -import { QController } from "@kingsrook/qqq-frontend-core/lib/controllers/QController"; -import { QRecord } from "@kingsrook/qqq-frontend-core/lib/model/QRecord"; -import React, { useReducer, useState } from "react"; - import MDButton from "../../../../../components/MDButton"; const qController = new QController(""); @@ -104,20 +101,16 @@ function ViewContents({ id }: Props): JSX.Element { setOpen(false); }; - /* const handleDelete = (event: { preventDefault: () => void }) => { event.preventDefault(); - /* (async () => { await qController.delete(tableName, id).then(() => { - window.location.href = `/${tableName}/list/`; + window.location.href = `/${tableName}`; }); })(); - }; - */ - const editPath = `/${tableName}/edit/${id}`; + const editPath = `/${tableName}/${id}/edit`; return ( @@ -131,7 +124,6 @@ function ViewContents({ id }: Props): JSX.Element { - - + edit {tableMetaData?.label} diff --git a/src/qqq/pages/process-run/index.tsx b/src/qqq/pages/process-run/index.tsx index 1415c9d..a926fd6 100644 --- a/src/qqq/pages/process-run/index.tsx +++ b/src/qqq/pages/process-run/index.tsx @@ -45,6 +45,7 @@ import * as Yup from "yup"; import { QController } from "@kingsrook/qqq-frontend-core/lib/controllers/QController"; import { QFrontendStepMetaData } from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFrontendStepMetaData"; import { useParams } from "react-router-dom"; +import DynamicFormUtils from "qqq/components/QDynamicForm/utils/DynamicFormUtils"; import QDynamicForm from "../../components/QDynamicForm"; function getDynamicStepContent(stepIndex: number, stepParam: any, formData: any): JSX.Element { @@ -57,7 +58,7 @@ function getDynamicStepContent(stepIndex: number, stepParam: any, formData: any) return
Loading...
; } - return ; + return ; } const qController = new QController(""); @@ -90,31 +91,23 @@ function ProcessRun(): JSX.Element { setActiveStep(activeStep); setFormId(activeStep.name); + const { dynamicFormFields, formValidations } = DynamicFormUtils.getFormData( + activeStep.formFields + ); + const formFields: any = {}; const initialValues: any = {}; const validations: any = {}; activeStep.formFields.forEach((field) => { - formFields[field.name] = { - name: field.name, - label: field.label, - type: "text", // todo better - // todo invalidMsg: "Zipcode is not valid (e.g. 70000).", - }; - // todo - not working - also, needs real value. initialValues[field.name] = "Hi"; - - // todo - all this based on type and other metadata. - // see src/layouts/pages/users/new-user/schemas/validations.ts - validations[field.name] = Yup.string().required(`${field.label} is required.`); - // validations[field.name] = Yup.string().optional(); }); - setFormFields(formFields); + setFormFields(dynamicFormFields); setInitialValues(initialValues); - setValidations(Yup.object().shape(validations)); - console.log(`in updateActiveStep: formFields ${JSON.stringify(formFields)}`); + setValidations(Yup.object().shape(formValidations)); + console.log(`in updateActiveStep: formFields ${JSON.stringify(dynamicFormFields)}`); console.log(`in updateActiveStep: initialValues ${JSON.stringify(initialValues)}`); } };