diff --git a/.eslintrc.json b/.eslintrc.json
index b9242a5..d9b248b 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -60,6 +60,7 @@
"max-len": "off",
"no-console": "off",
"no-constant-condition": "off",
+ "no-continue": "off",
"no-shadow": "off",
"no-unused-vars": "off",
"no-plusplus": "off",
diff --git a/package.json b/package.json
index 71758d9..b4b223a 100644
--- a/package.json
+++ b/package.json
@@ -13,7 +13,7 @@
"@fullcalendar/interaction": "5.10.0",
"@fullcalendar/react": "5.10.0",
"@fullcalendar/timegrid": "5.10.0",
- "@kingsrook/qqq-frontend-core": "1.0.8",
+ "@kingsrook/qqq-frontend-core": "1.0.9",
"@mui/icons-material": "5.4.1",
"@mui/material": "5.4.1",
"@mui/styled-engine": "5.4.1",
diff --git a/src/App.tsx b/src/App.tsx
index e16f4c0..0540bec 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -35,7 +35,6 @@ import {setMiniSidenav, setOpenConfigurator, useMaterialUIController} from "cont
import nfLogo from "assets/images/nutrifresh_one_icon_white.png";
import {Md5} from "ts-md5/dist/md5";
import {useCookies} from "react-cookie";
-import {QAppMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QAppMetaData";
import EntityCreate from "./qqq/pages/entity-create";
import EntityList from "./qqq/pages/entity-list";
import EntityView from "./qqq/pages/entity-view";
@@ -51,6 +50,7 @@ import QClient from "./qqq/utils/QClient";
import {QAppNodeType} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QAppNodeType";
import {QInstance} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QInstance";
import QProcessUtils from "qqq/utils/QProcessUtils";
+import {QAppTreeNode} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QAppTreeNode";
///////////////////////////////////////////////////////////////////////////////////////////////
// define the parts of the nav that are static - before the qqq tables etc get dynamic added //
@@ -94,6 +94,7 @@ export default function App()
} = useAuth0();
const [loadingToken, setLoadingToken] = useState(false);
const [isFullyAuthenticated, setIsFullyAuthenticated] = useState(false);
+ const [profileRoutes, setProfileRoutes] = useState({});
useEffect(() =>
{
@@ -137,8 +138,6 @@ export default function App()
const [sideNavRoutes, setSideNavRoutes] = useState(getStaticRoutes());
const [appRoutes, setAppRoutes] = useState(null as any);
- const dynamicAppChildElement = ;
-
////////////////////////////////////////////
// load qqq meta data to make more routes //
////////////////////////////////////////////
@@ -152,7 +151,7 @@ export default function App()
(async () =>
{
- function addAppToSideNavList(app: QAppMetaData, appList: any[], parentPath: string, depth: number)
+ function addAppToSideNavList(app: QAppTreeNode, appList: any[], parentPath: string, depth: number)
{
const path = `${parentPath}/${app.name}`;
if (app.type !== QAppNodeType.APP)
@@ -160,62 +159,65 @@ export default function App()
return;
}
- if (app.type === QAppNodeType.APP && depth <= 2)
+ if (depth > 2)
{
- const childList: any[] = [];
- app.children.forEach((child: QAppMetaData) =>
- {
- addAppToSideNavList(child, childList, path, depth + 1);
- });
+ console.warn("App depth is greater than 2 - not including app in side nav...");
+ return;
+ }
- if (childList.length === 0)
- {
- if (depth === 0)
- {
- /////////////////////////////////////////////////////
- // at level 0, the entry must always be a collapse //
- /////////////////////////////////////////////////////
- appList.push({
- type: "collapse",
- name: app.label,
- key: app.name,
- route: path,
- icon: {app.iconName},
- noCollapse: true,
- component: ,
- });
- }
- else
- {
- appList.push({
- name: app.label,
- key: app.name,
- route: path,
- icon: {app.iconName},
- component: ,
- });
- }
- }
- else
+ const childList: any[] = [];
+ app.children.forEach((child: QAppTreeNode) =>
+ {
+ addAppToSideNavList(child, childList, path, depth + 1);
+ });
+
+ if (childList.length === 0)
+ {
+ if (depth === 0)
{
+ /////////////////////////////////////////////////////
+ // at level 0, the entry must always be a collapse //
+ /////////////////////////////////////////////////////
appList.push({
type: "collapse",
name: app.label,
key: app.name,
- dropdown: true,
+ route: path,
icon: {app.iconName},
- collapse: childList,
+ noCollapse: true,
+ component: ,
+ });
+ }
+ else
+ {
+ appList.push({
+ name: app.label,
+ key: app.name,
+ route: path,
+ icon: {app.iconName},
+ component: ,
});
}
}
+ else
+ {
+ appList.push({
+ type: "collapse",
+ name: app.label,
+ key: app.name,
+ dropdown: true,
+ icon: {app.iconName},
+ collapse: childList,
+ });
+ }
}
- function addAppToAppRoutesList(metaData: QInstance, app: QAppMetaData, routeList: any[], parentPath: string, depth: number)
+ function addAppToAppRoutesList(metaData: QInstance, app: QAppTreeNode, routeList: any[], parentPath: string, depth: number)
{
const path = `${parentPath}/${app.name}`;
if (app.type === QAppNodeType.APP)
{
- app.children.forEach((child: QAppMetaData) =>
+ app.children.forEach((child: QAppTreeNode) =>
{
addAppToAppRoutesList(metaData, child, routeList, path, depth + 1);
});
@@ -261,16 +263,6 @@ export default function App()
const processesForTable = QProcessUtils.getProcessesForTable(metaData, table.name, true);
processesForTable.forEach((process) =>
{
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- // special provision for the standard bulk processes - strip the table name from the process name //
- // todo - this var isn't used - is this needed?
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- let processName = process.name;
- if (processName.startsWith(`${process.tableName}.`))
- {
- processName = processName.replace(`${process.tableName}.`, "");
- }
-
routeList.push({
name: process.label,
key: process.name,
@@ -295,11 +287,11 @@ export default function App()
{
const metaData = await QClient.getInstance().loadMetaData();
- let profileRoute = {};
+ let profileRoutes = {};
const gravatarBase = "http://www.gravatar.com/avatar/";
const hash = Md5.hashStr(user.email);
const profilePicture = `${gravatarBase}${hash}`;
- profileRoute = {
+ profileRoutes = {
type: "collapse",
name: user.name,
key: user.name,
@@ -319,6 +311,7 @@ export default function App()
},
],
};
+ setProfileRoutes(profileRoutes);
const sideNavAppList = [] as any[];
const appRoutesList = [] as any[];
@@ -331,7 +324,7 @@ export default function App()
const newSideNavRoutes = getStaticRoutes();
// @ts-ignore
- newSideNavRoutes.unshift(profileRoute);
+ newSideNavRoutes.unshift(profileRoutes);
for (let i = 0; i < sideNavAppList.length; i++)
{
newSideNavRoutes.push(sideNavAppList[i]);
@@ -386,6 +379,9 @@ export default function App()
document.scrollingElement.scrollTop = 0;
}, [pathname]);
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // convert an object that works for the Sidenav into one that works for the react-router //
+ ///////////////////////////////////////////////////////////////////////////////////////////
const getRoutes = (allRoutes: any[]): any => allRoutes.map(
(route: {
collapse: any;
@@ -452,7 +448,8 @@ export default function App()
} />
{appRoutes && getRoutes(appRoutes)}
- {sideNavRoutes && getRoutes(sideNavRoutes)}
+ {getRoutes(getStaticRoutes())}
+ {profileRoutes && getRoutes([profileRoutes])}
)
diff --git a/src/qqq/components/EntityForm/index.tsx b/src/qqq/components/EntityForm/index.tsx
index 02c737c..2a2329a 100644
--- a/src/qqq/components/EntityForm/index.tsx
+++ b/src/qqq/components/EntityForm/index.tsx
@@ -29,6 +29,8 @@ import Avatar from "@mui/material/Avatar";
import Icon from "@mui/material/Icon";
import QRecordSidebar from "qqq/components/QRecordSidebar";
import QTableUtils from "qqq/utils/QTableUtils";
+import colors from "assets/theme/base/colors";
+import {QSection} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QSection";
interface Props
{
@@ -46,6 +48,7 @@ function EntityForm({table, id}: Props): JSX.Element
const [initialValues, setInitialValues] = useState({} as { [key: string]: string });
const [formFields, setFormFields] = useState(null as Map);
const [t1sectionName, setT1SectionName] = useState(null as string);
+ const [nonT1Sections, setNonT1Sections] = useState([] as QSection[]);
const [alertContent, setAlertContent] = useState("");
@@ -53,7 +56,7 @@ function EntityForm({table, id}: Props): JSX.Element
const [formValues, setFormValues] = useState({} as { [key: string]: string });
const [tableMetaData, setTableMetaData] = useState(null as QTableMetaData);
const [record, setRecord] = useState(null as QRecord);
- const [tableSections, setTableSections] = useState(null as any);
+ const [tableSections, setTableSections] = useState(null as QSection[]);
const [, forceUpdate] = useReducer((x) => x + 1, 0);
const navigate = useNavigate();
@@ -76,7 +79,7 @@ function EntityForm({table, id}: Props): JSX.Element
{
return Loading...
;
}
- return ;
+ return ;
}
if (!asyncLoadInited)
@@ -131,6 +134,7 @@ function EntityForm({table, id}: Props): JSX.Element
/////////////////////////////////////
const dynamicFormFieldsBySection = new Map();
let t1sectionName;
+ const nonT1Sections: QSection[] = [];
for (let i = 0; i < tableSections.length; i++)
{
const section = tableSections[i];
@@ -140,6 +144,11 @@ function EntityForm({table, id}: Props): JSX.Element
{
const fieldName = section.fieldNames[j];
const field = tableMetaData.fields.get(fieldName);
+
+ ////////////////////////////////////////////////////////////////////////////////////////////
+ // 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)
{
sectionDynamicFormFields.push(dynamicFormFields[fieldName]);
@@ -153,6 +162,7 @@ function EntityForm({table, id}: Props): JSX.Element
////////////////////////////////////////////////////////////////////////////////////////////////
tableSections.splice(i, 1);
i--;
+ continue;
}
else
{
@@ -166,8 +176,13 @@ function EntityForm({table, id}: Props): JSX.Element
{
t1sectionName = section.name;
}
+ else
+ {
+ nonT1Sections.push(section);
+ }
}
setT1SectionName(t1sectionName);
+ setNonT1Sections(nonT1Sections);
setFormFields(dynamicFormFieldsBySection);
setValidations(Yup.object().shape(formValidations));
@@ -177,6 +192,11 @@ function EntityForm({table, id}: Props): JSX.Element
const handleCancelClicked = () =>
{
+ ///////////////////////////////////////////////////////////////////////////////////////
+ // todo - we might have rather just done a navigate(-1) (to keep history clean) //
+ // 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)
{
const path = `${location.pathname.replace(/\/edit$/, "")}`;
@@ -274,7 +294,7 @@ function EntityForm({table, id}: Props): JSX.Element
-
+
{tableMetaData?.iconName}
@@ -295,23 +315,22 @@ function EntityForm({table, id}: Props): JSX.Element
}
- {tableSections && formFields ? tableSections.map((section: any) => (section.name !== t1sectionName
- ? (
-
-
-
- {section.label}
-
-
-
- {
- getFormSection(values, touched, formFields.get(section.name), errors)
- }
-
+ {formFields && nonT1Sections.length ? nonT1Sections.map((section: QSection) => (
+
+
+
+ {section.label}
+
+
+
+ {
+ getFormSection(values, touched, formFields.get(section.name), errors)
+ }
-
-
- ) : null)) : null}
+
+
+
+ )) : null}
diff --git a/src/qqq/components/Navbar/index.tsx b/src/qqq/components/Navbar/index.tsx
index 3639a3c..3b4ac40 100644
--- a/src/qqq/components/Navbar/index.tsx
+++ b/src/qqq/components/Navbar/index.tsx
@@ -58,7 +58,7 @@ import {
} from "context";
// qqq
-import QBreadcrumbs from "qqq/components/QBreadcrumbs";
+import QBreadcrumbs, {routeToLabel} from "qqq/components/QBreadcrumbs";
// Declaring prop types for Navbar
interface Props
@@ -156,7 +156,7 @@ function Navbar({absolute, light, isMini}: Props): JSX.Element
},
});
- const breadcrumbTitle = route[route.length - 1].replace(/([A-Z])/g, " $1").trim();
+ const breadcrumbTitle = routeToLabel(route[route.length - 1]);
return (
return (input.substring(0, 1).toUpperCase() + input.substring(1));
};
-const routeToLabel = (route: string): string =>
+export const routeToLabel = (route: string): string =>
{
- const label = ucFirst(route.replace(".", " ").replace("-", " ").replace("_", " ").replace(/([A-Z])/g, " $1"));
+ const label = ucFirst(route
+ .replace(".", " ")
+ .replace("-", " ")
+ .replace("_", " ")
+ .replace(/([a-z])([A-Z]+)/g, "$1 $2") // transform personUSA => person USA
+ .replace(/^([A-Z]+)([A-Z])([a-z])/, "$1 $2$3")); // transform USAPerson => USA Person
return (label);
};
diff --git a/src/qqq/components/QButtons/index.tsx b/src/qqq/components/QButtons/index.tsx
index dbe9b1e..867ee66 100644
--- a/src/qqq/components/QButtons/index.tsx
+++ b/src/qqq/components/QButtons/index.tsx
@@ -61,12 +61,10 @@ interface QDeleteButtonProps
export function QDeleteButton({onClickHandler}: QDeleteButtonProps): JSX.Element
{
return (
-
-
- delete}>
- Delete
-
-
+
+ delete}>
+ Delete
+
);
}
@@ -74,13 +72,11 @@ export function QDeleteButton({onClickHandler}: QDeleteButtonProps): JSX.Element
export function QEditButton(): JSX.Element
{
return (
-
+
-
- edit}>
- Edit
-
-
+ edit}>
+ Edit
+
);
diff --git a/src/qqq/components/QDynamicForm/index.tsx b/src/qqq/components/QDynamicForm/index.tsx
index fb02131..23c6fc5 100644
--- a/src/qqq/components/QDynamicForm/index.tsx
+++ b/src/qqq/components/QDynamicForm/index.tsx
@@ -34,7 +34,6 @@ import QDynamicFormField from "qqq/components/QDynamicFormField";
interface Props {
formLabel?: string;
formData: any;
- primaryKeyId?: string;
bulkEditMode?: boolean;
bulkEditSwitchChangeHandler?: any
}
@@ -42,7 +41,7 @@ interface Props {
function QDynamicForm(props: Props): JSX.Element
{
const {
- formData, formLabel, primaryKeyId, bulkEditMode, bulkEditSwitchChangeHandler,
+ formData, formLabel, bulkEditMode, bulkEditSwitchChangeHandler,
} = props;
const {
formFields, values, errors, touched,
@@ -116,7 +115,7 @@ function QDynamicForm(props: Props): JSX.Element
error={errors[fieldName] && touched[fieldName]}
bulkEditMode={bulkEditMode}
bulkEditSwitchChangeHandler={bulkEditSwitchChanged}
- success={!errors[fieldName] && touched[fieldName]}
+ success={`${values[fieldName]}` !== "" && !errors[fieldName] && touched[fieldName]}
/>
);
@@ -129,7 +128,6 @@ function QDynamicForm(props: Props): JSX.Element
QDynamicForm.defaultProps = {
formLabel: undefined,
- primaryKeyId: undefined,
bulkEditMode: false,
bulkEditSwitchChangeHandler: () =>
{},
diff --git a/src/qqq/components/QDynamicFormField/index.tsx b/src/qqq/components/QDynamicFormField/index.tsx
index b1705e2..dc5a9ff 100644
--- a/src/qqq/components/QDynamicFormField/index.tsx
+++ b/src/qqq/components/QDynamicFormField/index.tsx
@@ -26,7 +26,6 @@ import {ErrorMessage, Field} from "formik";
import MDBox from "components/MDBox";
import MDTypography from "components/MDTypography";
import MDInput from "components/MDInput";
-import QDynamicForm from "qqq/components/QDynamicForm";
import React, {useState} from "react";
import Grid from "@mui/material/Grid";
import Switch from "@mui/material/Switch";
diff --git a/src/qqq/components/QRecordSidebar/index.tsx b/src/qqq/components/QRecordSidebar/index.tsx
index 1419652..85c5b04 100644
--- a/src/qqq/components/QRecordSidebar/index.tsx
+++ b/src/qqq/components/QRecordSidebar/index.tsx
@@ -28,9 +28,10 @@ import MDBox from "components/MDBox";
import MDTypography from "components/MDTypography";
import Card from "@mui/material/Card";
import {Theme} from "@mui/material/styles";
+import {QSection} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QSection";
interface Props {
- tableSections: any;
+ tableSections: QSection[];
light?: boolean;
}
@@ -40,11 +41,11 @@ function QRecordSidebar({tableSections, light}: Props): JSX.Element
borderRadius.lg, position: "sticky", top: "1%"}}>
{
- tableSections ? tableSections.map(({icon, label, name}: any, key: number) => (
-
+ tableSections ? tableSections.map((section: QSection, key: number) => (
+
- {icon}
+ {section.iconName}
- {label}
+ {section.label}
)) : null
diff --git a/src/qqq/pages/entity-create/index.tsx b/src/qqq/pages/entity-create/index.tsx
index 91d1d80..cef402b 100644
--- a/src/qqq/pages/entity-create/index.tsx
+++ b/src/qqq/pages/entity-create/index.tsx
@@ -29,7 +29,6 @@ import MDBox from "components/MDBox";
import EntityForm from "qqq/components/EntityForm";
import BaseLayout from "qqq/components/BaseLayout";
import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData";
-import {useParams} from "react-router-dom";
interface Props
{
diff --git a/src/qqq/pages/entity-list/index.tsx b/src/qqq/pages/entity-list/index.tsx
index 879faae..a6b4cf9 100644
--- a/src/qqq/pages/entity-list/index.tsx
+++ b/src/qqq/pages/entity-list/index.tsx
@@ -134,6 +134,7 @@ function EntityList({table}: Props): JSX.Element
const [columnVisibilityModel, setColumnVisibilityModel] = useState(defaultVisibility);
const [gridMouseDownX, setGridMouseDownX] = useState(0);
const [gridMouseDownY, setGridMouseDownY] = useState(0);
+ const [pinnedColumns, setPinnedColumns] = useState({left: ["__check__", "id"]});
const instance = useRef({timer: null});
const [, forceUpdate] = useReducer((x) => x + 1, 0);
@@ -223,6 +224,7 @@ function EntityList({table}: Props): JSX.Element
});
setColumnSortModel(columnSortModel);
}
+ setPinnedColumns({left: ["__check__", tableMetaData.primaryKeyField]});
const qFilter = buildQFilter();
@@ -626,15 +628,8 @@ function EntityList({table}: Props): JSX.Element
// eslint-disable-next-line react/no-unstable-nested-components
function CustomToolbar()
{
- function gtcMouseDown(e: React.MouseEvent)
- {
- console.log(e.target);
- }
-
return (
- gtcMouseDown(e)}
- >
+