mirror of
https://github.com/Kingsrook/qqq-frontend-material-dashboard.git
synced 2025-07-17 21:00:45 +00:00
QQQ-21 checkpoint - close-to-working QDynamicForm and process-run
This commit is contained in:
@ -23,6 +23,8 @@
|
||||
"no-console": "off",
|
||||
"react/prop-types": "off",
|
||||
"no-shadow": "off",
|
||||
"no-unused-vars": "off",
|
||||
"spaced-comment": "off",
|
||||
"react/jsx-props-no-spreading": "off",
|
||||
"react/react-in-jsx-scope": "off",
|
||||
"import/extensions": [
|
||||
|
@ -60,17 +60,7 @@ function EntityForm({ id }: Props): JSX.Element {
|
||||
setTableMetaData(tableMetaData);
|
||||
|
||||
if (id !== null) {
|
||||
const records = await qController.query(tableName, 250);
|
||||
let foundRecord: QRecord;
|
||||
records.forEach((innerRecord) => {
|
||||
const fieldKeys = [...innerRecord.values.keys()];
|
||||
fieldKeys.forEach((key) => {
|
||||
const value = innerRecord.values.get(key);
|
||||
if (key === tableMetaData.primaryKeyField && `${value}` === `${id}`) {
|
||||
foundRecord = innerRecord;
|
||||
}
|
||||
});
|
||||
});
|
||||
const foundRecord = await qController.get(tableName, id);
|
||||
|
||||
tableMetaData.fields.forEach((fieldMetaData, key) => {
|
||||
formValues[key] = foundRecord.values.get(key);
|
||||
|
162
src/qqq/components/QDynamicForm/index.tsx
Normal file
162
src/qqq/components/QDynamicForm/index.tsx
Normal file
@ -0,0 +1,162 @@
|
||||
/**
|
||||
=========================================================
|
||||
* 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.
|
||||
*/
|
||||
|
||||
// @mui material components
|
||||
import Grid from "@mui/material/Grid";
|
||||
|
||||
// Material Dashboard 2 PRO React TS components
|
||||
import MDBox from "components/MDBox";
|
||||
import MDTypography from "components/MDTypography";
|
||||
|
||||
// NewUser page components
|
||||
import FormField from "layouts/pages/users/new-user/components/FormField";
|
||||
import { QFrontendStepMetaData } from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFrontendStepMetaData";
|
||||
|
||||
interface Props {
|
||||
formData: any;
|
||||
step?: QFrontendStepMetaData | undefined;
|
||||
}
|
||||
|
||||
function QDynamicForm(props: Props): JSX.Element {
|
||||
const { formData, step } = props;
|
||||
const { formFields, values, errors, touched } = formData;
|
||||
|
||||
/*
|
||||
const {
|
||||
firstName: firstNameV,
|
||||
lastName: lastNameV,
|
||||
company: companyV,
|
||||
email: emailV,
|
||||
password: passwordV,
|
||||
repeatPassword: repeatPasswordV,
|
||||
} = values;
|
||||
*/
|
||||
|
||||
return (
|
||||
<MDBox>
|
||||
<MDBox lineHeight={0}>
|
||||
<MDTypography variant="h5">{step?.label}</MDTypography>
|
||||
{/* TODO - help text
|
||||
<MDTypography variant="button" color="text">
|
||||
Mandatory information
|
||||
</MDTypography>
|
||||
*/}
|
||||
</MDBox>
|
||||
<MDBox mt={1.625}>
|
||||
<Grid container spacing={3}>
|
||||
{formFields &&
|
||||
Object.keys(formFields).length > 0 &&
|
||||
Object.keys(formFields).map((fieldName: any) => {
|
||||
const field = formFields[fieldName];
|
||||
if (values[fieldName] === undefined) {
|
||||
values[fieldName] = "";
|
||||
}
|
||||
return (
|
||||
<Grid item xs={12} sm={6} key={fieldName}>
|
||||
<FormField
|
||||
type={field.type}
|
||||
label={field.label}
|
||||
name={fieldName}
|
||||
value={values[fieldName]}
|
||||
error={errors[fieldName] && touched[fieldName]}
|
||||
/>
|
||||
</Grid>
|
||||
);
|
||||
})}
|
||||
</Grid>
|
||||
{/*
|
||||
<Grid container spacing={3}>
|
||||
<Grid item xs={12} sm={6}>
|
||||
<FormField
|
||||
type={firstName.type}
|
||||
label={firstName.label}
|
||||
name={firstName.name}
|
||||
value={firstNameV}
|
||||
placeholder={firstName.placeholder}
|
||||
error={errors.firstName && touched.firstName}
|
||||
success={firstNameV.length > 0 && !errors.firstName}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={6}>
|
||||
<FormField
|
||||
type={lastName.type}
|
||||
label={lastName.label}
|
||||
name={lastName.name}
|
||||
value={lastNameV}
|
||||
placeholder={lastName.placeholder}
|
||||
error={errors.lastName && touched.lastName}
|
||||
success={lastNameV.length > 0 && !errors.lastName}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid container spacing={3}>
|
||||
<Grid item xs={12} sm={6}>
|
||||
<FormField
|
||||
type={company.type}
|
||||
label={company.label}
|
||||
name={company.name}
|
||||
value={companyV}
|
||||
placeholder={company.placeholder}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={6}>
|
||||
<FormField
|
||||
type={email.type}
|
||||
label={email.label}
|
||||
name={email.name}
|
||||
value={emailV}
|
||||
placeholder={email.placeholder}
|
||||
error={errors.email && touched.email}
|
||||
success={emailV.length > 0 && !errors.email}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid container spacing={3}>
|
||||
<Grid item xs={12} sm={6}>
|
||||
<FormField
|
||||
type={password.type}
|
||||
label={password.label}
|
||||
name={password.name}
|
||||
value={passwordV}
|
||||
placeholder={password.placeholder}
|
||||
error={errors.password && touched.password}
|
||||
success={passwordV.length > 0 && !errors.password}
|
||||
inputProps={{ autoComplete: "" }}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={6}>
|
||||
<FormField
|
||||
type={repeatPassword.type}
|
||||
label={repeatPassword.label}
|
||||
name={repeatPassword.name}
|
||||
value={repeatPasswordV}
|
||||
placeholder={repeatPassword.placeholder}
|
||||
error={errors.repeatPassword && touched.repeatPassword}
|
||||
success={repeatPasswordV.length > 0 && !errors.repeatPassword}
|
||||
inputProps={{ autoComplete: "" }}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
*/}
|
||||
</MDBox>
|
||||
</MDBox>
|
||||
);
|
||||
}
|
||||
|
||||
QDynamicForm.defaultProps = {
|
||||
step: undefined,
|
||||
};
|
||||
|
||||
export default QDynamicForm;
|
@ -71,7 +71,7 @@ function EntityList({ table }: Props): JSX.Element {
|
||||
if (tableState === "") {
|
||||
(async () => {
|
||||
const tableMetaData = await qController.loadTableMetaData(tableName);
|
||||
// const metaData = await qController.loadMetaData();
|
||||
const metaData = await qController.loadMetaData();
|
||||
const results = await qController.query(tableName, 250);
|
||||
dataTableData = {
|
||||
columns: [],
|
||||
@ -170,7 +170,7 @@ function EntityList({ table }: Props): JSX.Element {
|
||||
<Icon>keyboard_arrow_down</Icon>
|
||||
</MDButton>
|
||||
)}
|
||||
{/* renderActionsMenu */}
|
||||
{renderActionsMenu}
|
||||
<MDBox ml={1}>
|
||||
<MDButton
|
||||
variant={filtersMenu ? "contained" : "outlined"}
|
||||
|
@ -62,18 +62,7 @@ function ViewContents({ id }: Props): JSX.Element {
|
||||
const tableMetaData = await qController.loadTableMetaData(tableName);
|
||||
setTableMetaData(tableMetaData);
|
||||
|
||||
// TODO: make a call to query (just get all for now, and iterate and filter like a caveman) - FIX!
|
||||
const records = await qController.query(tableName, 250);
|
||||
let foundRecord: QRecord;
|
||||
records.forEach((innerRecord) => {
|
||||
const fieldKeys = [...innerRecord.values.keys()];
|
||||
fieldKeys.forEach((key) => {
|
||||
const value = innerRecord.values.get(key);
|
||||
if (key === tableMetaData.primaryKeyField && `${value}` === `${id}`) {
|
||||
foundRecord = innerRecord;
|
||||
}
|
||||
});
|
||||
});
|
||||
const foundRecord = await qController.get(tableName, id);
|
||||
|
||||
nameValues.push(
|
||||
<MDBox key={tableMetaData.primaryKeyField} display="flex" py={1} pr={2}>
|
||||
|
239
src/qqq/pages/process-run/index.tsx
Normal file
239
src/qqq/pages/process-run/index.tsx
Normal file
@ -0,0 +1,239 @@
|
||||
/**
|
||||
=========================================================
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import { useReducer, useState } from "react";
|
||||
|
||||
// formik components
|
||||
import { Formik, Form } from "formik";
|
||||
|
||||
// @mui material components
|
||||
import Grid from "@mui/material/Grid";
|
||||
import Card from "@mui/material/Card";
|
||||
import Stepper from "@mui/material/Stepper";
|
||||
import Step from "@mui/material/Step";
|
||||
import StepLabel from "@mui/material/StepLabel";
|
||||
|
||||
// Material Dashboard 2 PRO React TS components
|
||||
import MDBox from "components/MDBox";
|
||||
import MDButton from "components/MDButton";
|
||||
|
||||
// Material Dashboard 2 PRO React TS examples components
|
||||
import DashboardLayout from "examples/LayoutContainers/DashboardLayout";
|
||||
import DashboardNavbar from "examples/Navbars/DashboardNavbar";
|
||||
import Footer from "examples/Footer";
|
||||
|
||||
// ProcessRun page components
|
||||
import UserInfo from "layouts/pages/users/new-user/components/UserInfo";
|
||||
import Address from "layouts/pages/users/new-user/components/Address";
|
||||
import Socials from "layouts/pages/users/new-user/components/Socials";
|
||||
import Profile from "layouts/pages/users/new-user/components/Profile";
|
||||
|
||||
// ProcessRun layout schemas for form and form fields
|
||||
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 QDynamicForm from "../../components/QDynamicForm";
|
||||
|
||||
function getDynamicStepContent(stepIndex: number, stepParam: any, formData: any): JSX.Element {
|
||||
const { formFields, values, errors, touched } = formData;
|
||||
const { step } = stepParam;
|
||||
// console.log(`in getDynamicStepContent: step label ${step?.label}`);
|
||||
|
||||
if (!Object.keys(formFields).length) {
|
||||
console.log("in getDynamicStepContent. No fields yet, so returning 'loading'");
|
||||
return <div>Loading...</div>;
|
||||
}
|
||||
|
||||
return <QDynamicForm formData={formData} step={step} />;
|
||||
}
|
||||
|
||||
const qController = new QController("");
|
||||
console.log(qController);
|
||||
|
||||
function ProcessRun(): JSX.Element {
|
||||
const { processName } = useParams();
|
||||
const [activeStepIndex, setActiveStepIndex] = useState(0);
|
||||
const [activeStep, setActiveStep] = useState(null as QFrontendStepMetaData);
|
||||
const [steps, setSteps] = useState([] as QFrontendStepMetaData[]);
|
||||
const [loadCounter, setLoadCounter] = useState(0);
|
||||
const [processMetaData, setProcessMetaData] = useState(null);
|
||||
const [formId, setFormId] = useState("");
|
||||
const [formFields, setFormFields] = useState({});
|
||||
const [initialValues, setInitialValues] = useState({});
|
||||
const [validations, setValidations] = useState({});
|
||||
// const currentValidation = validations[activeStepIndex];
|
||||
const [_, forceUpdate] = useReducer((x) => x + 1, 0);
|
||||
const isLastStep = activeStepIndex === steps.length - 1;
|
||||
|
||||
console.log("In the function");
|
||||
|
||||
const updateActiveStep = (newIndex: number, steps: QFrontendStepMetaData[]) => {
|
||||
console.log(`Steps are: ${steps}`);
|
||||
console.log(`Setting step to ${newIndex}`);
|
||||
setActiveStepIndex(newIndex);
|
||||
|
||||
if (steps) {
|
||||
const activeStep = steps[newIndex];
|
||||
setActiveStep(activeStep);
|
||||
setFormId(activeStep.name);
|
||||
|
||||
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);
|
||||
setInitialValues(initialValues);
|
||||
setValidations(Yup.object().shape(validations));
|
||||
console.log(`in updateActiveStep: formFields ${JSON.stringify(formFields)}`);
|
||||
console.log(`in updateActiveStep: initialValues ${JSON.stringify(initialValues)}`);
|
||||
}
|
||||
};
|
||||
|
||||
const doInitialLoad = async () => {
|
||||
console.log("Starting doInitialLoad");
|
||||
const processMetaData = await qController.loadProcessMetaData(processName);
|
||||
console.log(processMetaData);
|
||||
setProcessMetaData(processMetaData);
|
||||
setSteps(processMetaData.frontendSteps);
|
||||
updateActiveStep(0, processMetaData.frontendSteps);
|
||||
console.log("Done with doInitialLoad");
|
||||
};
|
||||
|
||||
if (loadCounter === 0) {
|
||||
setLoadCounter(1);
|
||||
doInitialLoad();
|
||||
}
|
||||
|
||||
const sleep = (ms: any) =>
|
||||
new Promise((resolve) => {
|
||||
setTimeout(resolve, ms);
|
||||
});
|
||||
const handleBack = () => updateActiveStep(activeStepIndex - 1, processMetaData.frontendSteps);
|
||||
|
||||
const submitForm = async (values: any, actions: any) => {
|
||||
await sleep(1000);
|
||||
|
||||
// eslint-disable-next-line no-alert
|
||||
alert(JSON.stringify(values, null, 2));
|
||||
|
||||
actions.setSubmitting(false);
|
||||
actions.resetForm();
|
||||
|
||||
updateActiveStep(0, processMetaData.frontendSteps);
|
||||
};
|
||||
|
||||
const handleSubmit = (values: any, actions: any) => {
|
||||
submitForm(values, actions);
|
||||
|
||||
// if (isLastStep) {
|
||||
// submitForm(values, actions);
|
||||
// } else {
|
||||
// updateActiveStep(activeStepIndex + 1, processMetaData.frontendSteps);
|
||||
// actions.setTouched({});
|
||||
// actions.setSubmitting(false);
|
||||
// }
|
||||
};
|
||||
|
||||
return (
|
||||
<DashboardLayout>
|
||||
<DashboardNavbar />
|
||||
<MDBox py={3} mb={20} height="65vh">
|
||||
<Grid container justifyContent="center" alignItems="center" sx={{ height: "100%", mt: 8 }}>
|
||||
<Grid item xs={12} lg={8}>
|
||||
<Formik
|
||||
initialValues={initialValues}
|
||||
validationSchema={validations}
|
||||
onSubmit={handleSubmit}
|
||||
>
|
||||
{({ values, errors, touched, isSubmitting }) => (
|
||||
<Form id={formId} autoComplete="off">
|
||||
<Card sx={{ height: "100%" }}>
|
||||
<MDBox mx={2} mt={-3}>
|
||||
<Stepper activeStep={activeStepIndex} alternativeLabel>
|
||||
{steps.map((step) => (
|
||||
<Step key={step.name}>
|
||||
<StepLabel>{step.label}</StepLabel>
|
||||
</Step>
|
||||
))}
|
||||
</Stepper>
|
||||
</MDBox>
|
||||
<MDBox p={3}>
|
||||
<MDBox>
|
||||
{/***************************************************************************
|
||||
** step content - e.g., the appropriate form or other screen for the step **
|
||||
***************************************************************************/}
|
||||
{getDynamicStepContent(
|
||||
activeStepIndex,
|
||||
{ step: activeStep },
|
||||
{
|
||||
values,
|
||||
touched,
|
||||
formFields,
|
||||
errors,
|
||||
}
|
||||
)}
|
||||
{/********************************
|
||||
** back &| next/submit buttons **
|
||||
********************************/}
|
||||
<MDBox mt={2} width="100%" display="flex" justifyContent="space-between">
|
||||
{activeStepIndex === 0 ? (
|
||||
<MDBox />
|
||||
) : (
|
||||
<MDButton variant="gradient" color="light" onClick={handleBack}>
|
||||
back
|
||||
</MDButton>
|
||||
)}
|
||||
<MDButton
|
||||
disabled={isSubmitting}
|
||||
type="submit"
|
||||
variant="gradient"
|
||||
color="dark"
|
||||
>
|
||||
{isLastStep ? "submit" : "next"}
|
||||
</MDButton>
|
||||
</MDBox>
|
||||
</MDBox>
|
||||
</MDBox>
|
||||
</Card>
|
||||
</Form>
|
||||
)}
|
||||
</Formik>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</MDBox>
|
||||
<Footer />
|
||||
</DashboardLayout>
|
||||
);
|
||||
}
|
||||
|
||||
export default ProcessRun;
|
Reference in New Issue
Block a user