diff --git a/src/App.tsx b/src/App.tsx
index 4510c97..296db35 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -278,6 +278,16 @@ export default function App()
component: ,
});
+ ///////////////////////////////////////////////////////////////////////////////////////////////////////
+ // this is the path to open a modal-form when viewing a record, to create a different (child) record //
+ // it can also be done with a hash like: #/createChild=:childTableName //
+ ///////////////////////////////////////////////////////////////////////////////////////////////////////
+ routeList.push({
+ key: `${app.name}.createChild`,
+ route: `${path}/:id/createChild/:childTableName`,
+ component: ,
+ });
+
routeList.push({
name: `${app.label} View`,
key: `${app.name}.view`,
@@ -302,6 +312,10 @@ export default function App()
const processesForTable = QProcessUtils.getProcessesForTable(metaData, table.name, true);
processesForTable.forEach((process) =>
{
+ ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+ // paths to open modal process under its owning table. //
+ // note, processes can also be launched (at least initially on entityView screen) with a hash like: #/launchProcess=:processName //
+ ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
routeList.push({
name: process.label,
key: process.name,
diff --git a/src/qqq/components/EntityForm/index.tsx b/src/qqq/components/EntityForm/index.tsx
index 49ab1ed..32d4b3f 100644
--- a/src/qqq/components/EntityForm/index.tsx
+++ b/src/qqq/components/EntityForm/index.tsx
@@ -34,7 +34,7 @@ import Grid from "@mui/material/Grid";
import Icon from "@mui/material/Icon";
import {Form, Formik} from "formik";
import React, {useContext, useReducer, useState} from "react";
-import {useLocation, useNavigate, useParams} from "react-router-dom";
+import {useLocation, useNavigate, useParams, useSearchParams} from "react-router-dom";
import * as Yup from "yup";
import QContext from "QContext";
import {QCancelButton, QSaveButton} from "qqq/components/QButtons";
@@ -67,11 +67,11 @@ EntityForm.defaultProps = {
disabledFields: {},
};
-function EntityForm({table, isModal, id, closeModalHandler, defaultValues, disabledFields}: Props): JSX.Element
+function EntityForm(props: Props): JSX.Element
{
const qController = QClient.getInstance();
const tableNameParam = useParams().tableName;
- const tableName = table === null ? tableNameParam : table.name;
+ const tableName = props.table === null ? tableNameParam : props.table.name;
const [formTitle, setFormTitle] = useState("");
const [validations, setValidations] = useState({});
@@ -96,6 +96,34 @@ function EntityForm({table, isModal, id, closeModalHandler, defaultValues, disab
const navigate = useNavigate();
const location = useLocation();
+ ////////////////////////////////////////////////////////////////////
+ // first take defaultValues and disabledFields from props //
+ // but, also allow them to be sent in the hash, in the format of: //
+ // #/defaultValues={jsonName=value}/disabledFields={jsonName=any} //
+ ////////////////////////////////////////////////////////////////////
+ let defaultValues = props.defaultValues;
+ let disabledFields = props.disabledFields;
+
+ const hashParts = location.hash.split("/");
+ for (let i = 0; i < hashParts.length; i++)
+ {
+ try
+ {
+ const parts = hashParts[i].split("=")
+ if (parts.length > 1 && parts[0] == "defaultValues")
+ {
+ defaultValues = JSON.parse(decodeURIComponent(parts[1])) as { [key: string]: any };
+ }
+
+ if (parts.length > 1 && parts[0] == "disabledFields")
+ {
+ disabledFields = JSON.parse(decodeURIComponent(parts[1])) as { [key: string]: any };
+ }
+ }
+ catch (e)
+ {}
+ }
+
function getFormSection(values: any, touched: any, formFields: any, errors: any): JSX.Element
{
const formData: any = {};
@@ -142,13 +170,13 @@ function EntityForm({table, isModal, id, closeModalHandler, defaultValues, disab
/////////////////////////////////////////////////////////////////////////////////
let record: QRecord = null;
let defaultDisplayValues = new Map();
- if (id !== null)
+ if (props.id !== null)
{
- record = await qController.get(tableName, id);
+ record = await qController.get(tableName, props.id);
setRecord(record);
setFormTitle(`Edit ${tableMetaData?.label}: ${record?.recordLabel}`);
- if (!isModal)
+ if (!props.isModal)
{
setPageHeader(`Edit ${tableMetaData?.label}: ${record?.recordLabel}`);
}
@@ -172,7 +200,7 @@ function EntityForm({table, isModal, id, closeModalHandler, defaultValues, disab
///////////////////////////////////////////
setFormTitle(`Creating New ${tableMetaData?.label}`);
- if (!isModal)
+ if (!props.isModal)
{
setPageHeader(`Creating New ${tableMetaData?.label}`);
}
@@ -268,7 +296,7 @@ function EntityForm({table, isModal, id, closeModalHandler, defaultValues, disab
// if id !== null - means we're on the edit screen -- show all fields on the edit screen. //
// || (or) we're on the insert screen in which case, only show editable fields. //
////////////////////////////////////////////////////////////////////////////////////////////
- if (id !== null || field.isEditable)
+ if (props.id !== null || field.isEditable)
{
sectionDynamicFormFields.push(dynamicFormFields[fieldName]);
}
@@ -316,7 +344,7 @@ function EntityForm({table, isModal, id, closeModalHandler, defaultValues, disab
// but if the user used the anchors on the page, this doesn't effectively cancel... //
// what we have here pushed a new history entry (I think?), so could be better //
///////////////////////////////////////////////////////////////////////////////////////
- if (id !== null)
+ if (props.id !== null)
{
const path = `${location.pathname.replace(/\/edit$/, "")}`;
navigate(path, {replace: true});
@@ -333,15 +361,15 @@ function EntityForm({table, isModal, id, closeModalHandler, defaultValues, disab
actions.setSubmitting(true);
await (async () =>
{
- if (id !== null)
+ if (props.id !== null)
{
await qController
- .update(tableName, id, values)
+ .update(tableName, props.id, values)
.then((record) =>
{
- if (isModal)
+ if (props.isModal)
{
- closeModalHandler(null, "recordUpdated");
+ props.closeModalHandler(null, "recordUpdated");
}
else
{
@@ -362,9 +390,9 @@ function EntityForm({table, isModal, id, closeModalHandler, defaultValues, disab
.create(tableName, values)
.then((record) =>
{
- if (isModal)
+ if (props.isModal)
{
- closeModalHandler(null, "recordCreated");
+ props.closeModalHandler(null, "recordCreated");
}
else
{
@@ -380,7 +408,7 @@ function EntityForm({table, isModal, id, closeModalHandler, defaultValues, disab
})();
};
- const formId = id != null ? `edit-${tableMetaData?.name}-form` : `create-${tableMetaData?.name}-form`;
+ const formId = props.id != null ? `edit-${tableMetaData?.name}-form` : `create-${tableMetaData?.name}-form`;
let body;
if (noCapabilityError)
@@ -399,7 +427,7 @@ function EntityForm({table, isModal, id, closeModalHandler, defaultValues, disab
}
else
{
- const cardElevation = isModal ? 3 : 1;
+ const cardElevation = props.isModal ? 3 : 1;
body = (
@@ -413,12 +441,12 @@ function EntityForm({table, isModal, id, closeModalHandler, defaultValues, disab
{
- !isModal &&
+ !props.isModal &&
}
-
+
-
+
@@ -490,7 +518,7 @@ function EntityForm({table, isModal, id, closeModalHandler, defaultValues, disab
);
}
- if (isModal)
+ if (props.isModal)
{
return (
diff --git a/src/qqq/pages/dashboards/Widgets/RecordGridWidget.tsx b/src/qqq/pages/dashboards/Widgets/RecordGridWidget.tsx
index 7ab339a..8f0a826 100644
--- a/src/qqq/pages/dashboards/Widgets/RecordGridWidget.tsx
+++ b/src/qqq/pages/dashboards/Widgets/RecordGridWidget.tsx
@@ -24,7 +24,7 @@ import {QRecord} from "@kingsrook/qqq-frontend-core/lib/model/QRecord";
import {DataGridPro} from "@mui/x-data-grid-pro";
import React, {useEffect, useState} from "react";
import DataGridUtils from "qqq/utils/DataGridUtils";
-import Widget, {AddNewRecordButton, HeaderLink} from "./Widget";
+import Widget, {AddNewRecordButton, HeaderLink, LabelComponent} from "./Widget";
interface Props
{
@@ -65,15 +65,23 @@ function RecordGridWidget({title, data, reloadWidgetCallback}: Props): JSX.Eleme
}
}, [data]);
+ const labelAdditionalComponentsLeft: LabelComponent[] = []
+ if(data && data.viewAllLink)
+ {
+ labelAdditionalComponentsLeft.push(new HeaderLink("View All", data.viewAllLink));
+ }
+
+ const labelAdditionalComponentsRight: LabelComponent[] = []
+ if(data && data.canAddChildRecord)
+ {
+ labelAdditionalComponentsRight.push(new AddNewRecordButton(data.childTableMetaData, data.defaultValuesForNewChildRecords))
+ }
+
return (
void;
}
@@ -46,7 +46,7 @@ Widget.defaultProps = {
-class LabelComponent
+export class LabelComponent
{
}
@@ -89,40 +89,13 @@ export class AddNewRecordButton extends LabelComponent
function Widget(props: React.PropsWithChildren): JSX.Element
{
- const [showEditForm, setShowEditForm] = useState(null as any);
+ const navigate = useNavigate();
function openEditForm(table: QTableMetaData, id: any = null, defaultValues: any, disabledFields: any)
{
- const showEditForm: any = {};
- showEditForm.table = table;
- showEditForm.id = id;
- showEditForm.defaultValues = defaultValues;
- showEditForm.disabledFields = disabledFields;
- setShowEditForm(showEditForm);
+ navigate(`#/createChild=${table.name}/defaultValues=${JSON.stringify(defaultValues)}/disabledFields=${JSON.stringify(disabledFields)}`)
}
- const closeEditForm = (event: object, reason: string) =>
- {
- if (reason === "backdropClick")
- {
- return;
- }
-
- if (reason === "recordUpdated" || reason === "recordCreated")
- {
- if(props.reloadWidgetCallback)
- {
- props.reloadWidgetCallback(0, "ok");
- }
- else
- {
- window.location.reload()
- }
- }
-
- setShowEditForm(null);
- };
-
function renderComponent(component: LabelComponent)
{
if(component instanceof HeaderLink)
@@ -152,7 +125,7 @@ function Widget(props: React.PropsWithChildren): JSX.Element
-
+
{props.label}
{
@@ -173,20 +146,6 @@ function Widget(props: React.PropsWithChildren): JSX.Element
{props.children}
- {
- showEditForm &&
- closeEditForm(event, reason)}>
-
-
-
-
- }
>
);
}
diff --git a/src/qqq/pages/entity-list/EntityList.tsx b/src/qqq/pages/entity-list/EntityList.tsx
index bfcd30b..b5f5e13 100644
--- a/src/qqq/pages/entity-list/EntityList.tsx
+++ b/src/qqq/pages/entity-list/EntityList.tsx
@@ -795,7 +795,7 @@ function EntityList({table, launchProcess}: Props): JSX.Element
const closeModalProcess = (event: object, reason: string) =>
{
- if (reason === "backdropClick")
+ if (reason === "backdropClick" || reason === "escapeKeyDown")
{
return;
}
diff --git a/src/qqq/pages/entity-view/EntityDeveloperView.tsx b/src/qqq/pages/entity-view/EntityDeveloperView.tsx
index b92a446..84b25ec 100644
--- a/src/qqq/pages/entity-view/EntityDeveloperView.tsx
+++ b/src/qqq/pages/entity-view/EntityDeveloperView.tsx
@@ -162,7 +162,7 @@ function EntityDeveloperView({table}: Props): JSX.Element
const closeEditingScript = (event: object, reason: string, alert: string = null) =>
{
- if (reason === "backdropClick")
+ if (reason === "backdropClick" || reason === "escapeKeyDown")
{
return;
}
diff --git a/src/qqq/pages/entity-view/EntityView.tsx b/src/qqq/pages/entity-view/EntityView.tsx
index a7225eb..c95dc6d 100644
--- a/src/qqq/pages/entity-view/EntityView.tsx
+++ b/src/qqq/pages/entity-view/EntityView.tsx
@@ -46,6 +46,7 @@ import {useLocation, useNavigate, useParams, useSearchParams} from "react-router
import QContext from "QContext";
import BaseLayout from "qqq/components/BaseLayout";
import DashboardWidgets from "qqq/components/DashboardWidgets";
+import EntityForm from "qqq/components/EntityForm";
import {QActionsMenuButton, QDeleteButton, QEditButton} from "qqq/components/QButtons";
import QRecordSidebar from "qqq/components/QRecordSidebar";
import colors from "qqq/components/Temporary/colors";
@@ -60,7 +61,6 @@ import QValueUtils from "qqq/utils/QValueUtils";
const qController = QClient.getInstance();
-// Declaring props types for ViewForm
interface Props
{
table?: QTableMetaData;
@@ -70,7 +70,7 @@ interface Props
EntityView.defaultProps =
{
table: null,
- launchProcess: null
+ launchProcess: null,
};
function EntityView({table, launchProcess}: Props): JSX.Element
@@ -102,6 +102,7 @@ function EntityView({table, launchProcess}: Props): JSX.Element
const [, forceUpdate] = useReducer((x) => x + 1, 0);
const [launchingProcess, setLaunchingProcess] = useState(launchProcess);
+ const [showEditChildForm, setShowEditChildForm] = useState(null as any);
const openActionsMenu = (event: any) => setActionsMenu(event.currentTarget);
const closeActionsMenu = () => setActionsMenu(null);
@@ -126,10 +127,16 @@ function EntityView({table, launchProcess}: Props): JSX.Element
{
try
{
- /////////////////////////////////////////////////////////////////
- // the path for a process looks like: .../table/id/process //
- // so if our tableName is in the -3 index, try to open process //
- /////////////////////////////////////////////////////////////////
+ const hashParts = location.hash.split("/");
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // the path for a process looks like: .../table/id/process //
+ // the path for creating a child record looks like: .../table/id/createChild/:childTableName //
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+
+ //////////////////////////////////////////////////////////////
+ // if our tableName is in the -3 index, try to open process //
+ //////////////////////////////////////////////////////////////
if (pathParts[pathParts.length - 3] === tableName)
{
const processName = pathParts[pathParts.length - 1];
@@ -144,19 +151,69 @@ function EntityView({table, launchProcess}: Props): JSX.Element
console.log(`Couldn't find process named ${processName}`);
}
}
+
+ ///////////////////////////////////////////////////////////////////////
+ // alternatively, look for a launchProcess specification in the hash //
+ // e.g., for non-natively rendered links to open the modal. //
+ ///////////////////////////////////////////////////////////////////////
+ for (let i = 0; i < hashParts.length; i++)
+ {
+ const parts = hashParts[i].split("=")
+ if (parts.length > 1 && parts[0] == "launchProcess")
+ {
+ (async () =>
+ {
+ const processMetaData = await qController.loadProcessMetaData(parts[1])
+ setActiveModalProcess(processMetaData);
+ })();
+ return;
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+ // if our table is in the -4 index, and there's `createChild` in the -2 index, try to open a createChild form //
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+ if(pathParts[pathParts.length - 4] === tableName && pathParts[pathParts.length - 2] == "createChild")
+ {
+ (async () =>
+ {
+ const childTable = await qController.loadTableMetaData(pathParts[pathParts.length - 1])
+ const childId: any = null; // todo - for editing a child, not just creating one.
+ openEditChildForm(childTable, childId, null, null); // todo - defaults & disableds
+ })();
+ return;
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ // alternatively, look for a createChild specification in the hash //
+ // e.g., for non-natively rendered links to open the modal. //
+ /////////////////////////////////////////////////////////////////////
+ for (let i = 0; i < hashParts.length; i++)
+ {
+ const parts = hashParts[i].split("=")
+ if (parts.length > 1 && parts[0] == "createChild")
+ {
+ (async () =>
+ {
+ const childTable = await qController.loadTableMetaData(parts[1])
+ const childId: any = null; // todo - for editing a child, not just creating one.
+ openEditChildForm(childTable, childId, null, null); // todo - defaults & disableds
+ })();
+ return;
+ }
+ }
}
catch (e)
{
console.log(e);
}
- /////////////////////////////////////////////////////////////
- // if we didn't open a process, assume we need to (re)load //
- /////////////////////////////////////////////////////////////
- reload();
-
+ ///////////////////////////////////////////////////////////////////
+ // if we didn't open something, then, assume we need to (re)load //
+ ///////////////////////////////////////////////////////////////////
setActiveModalProcess(null);
- }, [location.pathname]);
+ reload();
+ }, [location.pathname, location.hash]);
if (!asyncLoadInited)
{
@@ -275,7 +332,7 @@ function EntityView({table, launchProcess}: Props): JSX.Element
-
+
{section.label}
@@ -395,7 +452,7 @@ function EntityView({table, launchProcess}: Props): JSX.Element
const closeModalProcess = (event: object, reason: string) =>
{
- if (reason === "backdropClick")
+ if (reason === "backdropClick" || reason === "escapeKeyDown")
{
return;
}
@@ -403,9 +460,53 @@ function EntityView({table, launchProcess}: Props): JSX.Element
//////////////////////////////////////////////////////////////////////////
// when closing a modal process, navigate up to the record being viewed //
//////////////////////////////////////////////////////////////////////////
- const newPath = location.pathname.split("/");
- newPath.pop();
- navigate(newPath.join("/"));
+ if(location.hash)
+ {
+ navigate(location.pathname);
+ }
+ else
+ {
+ const newPath = location.pathname.split("/");
+ newPath.pop();
+ navigate(newPath.join("/"));
+ }
+
+ setActiveModalProcess(null);
+ };
+
+ function openEditChildForm(table: QTableMetaData, id: any = null, defaultValues: any, disabledFields: any)
+ {
+ const showEditChildForm: any = {};
+ showEditChildForm.table = table;
+ showEditChildForm.id = id;
+ showEditChildForm.defaultValues = defaultValues;
+ showEditChildForm.disabledFields = disabledFields;
+ setShowEditChildForm(showEditChildForm);
+ }
+
+ const closeEditChildForm = (event: object, reason: string) =>
+ {
+ if (reason === "backdropClick" || reason === "escapeKeyDown")
+ {
+ return;
+ }
+
+ /////////////////////////////////////////////////
+ // navigate back up to the record being viewed //
+ /////////////////////////////////////////////////
+ if(location.hash)
+ {
+ navigate(location.pathname);
+ }
+ else
+ {
+ const newPath = location.pathname.split("/");
+ newPath.pop();
+ newPath.pop();
+ navigate(newPath.join("/"));
+ }
+
+ setShowEditChildForm(null);
};
return (
@@ -515,6 +616,21 @@ function EntityView({table, launchProcess}: Props): JSX.Element
}
+ {
+ showEditChildForm &&
+ closeEditChildForm(event, reason)}>
+
+
+
+
+ }
+
}