+ );
+}
+
+HandleAuthorizationError.defaultProps = {
+ errorMessage: "User authorization error.",
+};
+
+export default HandleAuthorizationError;
diff --git a/src/examples/Sidenav/index.tsx b/src/examples/Sidenav/index.tsx
index c6efb07..99b9a57 100644
--- a/src/examples/Sidenav/index.tsx
+++ b/src/examples/Sidenav/index.tsx
@@ -44,6 +44,7 @@ import {
setTransparentSidenav,
setWhiteSidenav,
} from "context";
+import AuthenticationButton from "qqq/components/Buttons/AuthenticationButton";
// Declaring props types for Sidenav
interface Props {
@@ -52,16 +53,21 @@ interface Props {
brandName: string;
routes: {
[key: string]:
- | ReactNode
- | string
- | {
- [key: string]:
- | ReactNode
- | string
- | {
- [key: string]: ReactNode | string;
- }[];
+ | ReactNode
+ | string
+ | {
+ [key: string]:
+ | ReactNode
+ | string
+ | {
+ [key: string]:
+ | ReactNode
+ | string
+ | {
+ [key: string]: ReactNode | string;
}[];
+ }[];
+ }[];
}[];
[key: string]: any;
}
@@ -314,6 +320,7 @@ function Sidenav({ color, brand, brandName, routes, ...rest }: Props): JSX.Eleme
}
/>
{renderRoutes}
+
);
}
diff --git a/src/index.tsx b/src/index.tsx
index 69ff9d7..4f978af 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -1,5 +1,5 @@
import ReactDOM from "react-dom";
-import {BrowserRouter, useNavigate} from "react-router-dom";
+import {BrowserRouter, useNavigate, useSearchParams} from "react-router-dom";
import {Auth0Provider} from "@auth0/auth0-react";
import App from "App";
@@ -8,39 +8,39 @@ import {MaterialUIControllerProvider} from "context";
import "./qqq/styles/qqq-override-styles.css";
import ProtectedRoute from "qqq/auth0/protected-route";
import React from "react";
+import HandleAuthorizationError from "HandleAuthorizationError";
// Auth0 params from env
const domain = process.env.REACT_APP_AUTH0_DOMAIN;
const clientId = process.env.REACT_APP_AUTH0_CLIENT_ID;
-/*
-ReactDOM.render(
-
-
-
-
- ,
- document.getElementById("root"),
-);
- */
-
-console.log("what");
-
// @ts-ignore
function Auth0ProviderWithRedirectCallback({children, ...props})
{
const navigate = useNavigate();
+ const [searchParams] = useSearchParams();
+
// @ts-ignore
const onRedirectCallback = (appState) =>
{
navigate((appState && appState.returnTo) || window.location.pathname);
};
- return (
- // @ts-ignore
-
- {children}
-
- );
+ if (searchParams.get("error"))
+ {
+ return (
+ // @ts-ignore
+
+ );
+ }
+ else
+ {
+ return (
+ // @ts-ignore
+
+ {children}
+
+ );
+ }
}
ReactDOM.render(
@@ -57,5 +57,3 @@ ReactDOM.render(
,
document.getElementById("root"),
);
-
-export * from "components/MDButton";
diff --git a/src/qqq/auth0/code-snippet.tsx b/src/qqq/auth0/code-snippet.tsx
index e2d55e1..aaed6c0 100644
--- a/src/qqq/auth0/code-snippet.tsx
+++ b/src/qqq/auth0/code-snippet.tsx
@@ -1,3 +1,24 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2022. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
import React from "react";
interface CodeSnippetProps
diff --git a/src/qqq/auth0/loader.tsx b/src/qqq/auth0/loader.tsx
index b38d26e..909cedd 100644
--- a/src/qqq/auth0/loader.tsx
+++ b/src/qqq/auth0/loader.tsx
@@ -1,3 +1,24 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2022. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
import React from "react";
function Loader() : JSX.Element
diff --git a/src/qqq/auth0/profile.tsx b/src/qqq/auth0/profile.tsx
index 7d51b7a..9de25c0 100644
--- a/src/qqq/auth0/profile.tsx
+++ b/src/qqq/auth0/profile.tsx
@@ -1,3 +1,24 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2022. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
import {useAuth0} from "@auth0/auth0-react";
import React from "react";
import CodeSnippet from "./code-snippet";
diff --git a/src/qqq/auth0/protected-route.tsx b/src/qqq/auth0/protected-route.tsx
index 2e66154..0aa4e7b 100644
--- a/src/qqq/auth0/protected-route.tsx
+++ b/src/qqq/auth0/protected-route.tsx
@@ -1,3 +1,24 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2022. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
import {withAuthenticationRequired} from "@auth0/auth0-react";
import React from "react";
import Loader from "./loader";
diff --git a/src/qqq/components/BaseLayout/index.tsx b/src/qqq/components/BaseLayout/index.tsx
index b0c4572..8b450b3 100644
--- a/src/qqq/components/BaseLayout/index.tsx
+++ b/src/qqq/components/BaseLayout/index.tsx
@@ -1,16 +1,22 @@
-/**
- =========================================================
- * 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.
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2022. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
*/
import {useState, useEffect, ReactNode} from "react";
@@ -22,7 +28,6 @@ import breakpoints from "assets/theme/base/breakpoints";
import DashboardLayout from "examples/LayoutContainers/DashboardLayout";
import Navbar from "qqq/components/Navbar";
import Footer from "qqq/components/Footer";
-import DashboardNavbar from "examples/Navbars/DashboardNavbar";
import MDBox from "../../../components/MDBox";
// Declaring props types for BaseLayout
@@ -60,7 +65,7 @@ function BaseLayout({stickyNavbar, children}: Props): JSX.Element
return (
- {children}
+ {children}
);
diff --git a/src/qqq/components/Buttons/AuthenticationButton/index.tsx b/src/qqq/components/Buttons/AuthenticationButton/index.tsx
new file mode 100644
index 0000000..f8e89f0
--- /dev/null
+++ b/src/qqq/components/Buttons/AuthenticationButton/index.tsx
@@ -0,0 +1,38 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2022. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+import {useAuth0} from "@auth0/auth0-react";
+import React from "react";
+import {Button} from "@mui/material";
+
+function AuthenticationButton()
+{
+ const {loginWithRedirect, logout, isAuthenticated} = useAuth0();
+
+ if (isAuthenticated)
+ {
+ return ;
+ }
+
+ return ;
+}
+
+export default AuthenticationButton;
diff --git a/src/qqq/components/EntityForm/components/CustomerCell/index.tsx b/src/qqq/components/EntityForm/components/CustomerCell/index.tsx
deleted file mode 100644
index 19ee4a1..0000000
--- a/src/qqq/components/EntityForm/components/CustomerCell/index.tsx
+++ /dev/null
@@ -1,57 +0,0 @@
-/**
-=========================================================
-* 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.
-*/
-
-// Material Dashboard 2 PRO React TS components
-import MDBox from "components/MDBox";
-import MDTypography from "components/MDTypography";
-import MDAvatar from "components/MDAvatar";
-
-// Declaring props types for CustomerCell
-interface Props {
- image?: string;
- name: string;
- color?:
- | "transparent"
- | "primary"
- | "secondary"
- | "info"
- | "success"
- | "warning"
- | "error"
- | "light"
- | "dark";
-}
-
-function CustomerCell({image, name, color}: Props): JSX.Element
-{
- return (
-
-
-
-
-
- {name}
-
-
- );
-}
-
-// Declaring default props for CustomerCell
-CustomerCell.defaultProps = {
- image: "",
- color: "dark",
-};
-
-export default CustomerCell;
diff --git a/src/qqq/components/EntityForm/components/DefaultCell/index.tsx b/src/qqq/components/EntityForm/components/DefaultCell/index.tsx
deleted file mode 100644
index 022623e..0000000
--- a/src/qqq/components/EntityForm/components/DefaultCell/index.tsx
+++ /dev/null
@@ -1,45 +0,0 @@
-/**
-=========================================================
-* 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.
-*/
-
-// Material Dashboard 2 PRO React TS components
-import MDTypography from "components/MDTypography";
-
-// Declaring props types for DefaultCell
-interface Props {
- value: string;
- suffix?: string | boolean;
-}
-
-function DefaultCell({value, suffix}: Props): JSX.Element
-{
- return (
-
- {value}
- {suffix && (
-
-
- {suffix}
-
- )}
-
- );
-}
-
-// Declaring default props for DefaultCell
-DefaultCell.defaultProps = {
- suffix: "",
-};
-
-export default DefaultCell;
diff --git a/src/qqq/components/EntityForm/components/IdCell/index.tsx b/src/qqq/components/EntityForm/components/IdCell/index.tsx
deleted file mode 100644
index 0a6f91b..0000000
--- a/src/qqq/components/EntityForm/components/IdCell/index.tsx
+++ /dev/null
@@ -1,54 +0,0 @@
-/**
- =========================================================
- * 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 Checkbox from "@mui/material/Checkbox";
-
-// Material Dashboard 2 PRO React TS components
-import MDBox from "components/MDBox";
-import MDTypography from "components/MDTypography";
-import Link from "@mui/material/Link";
-
-// Declaring props types for IdCell
-interface Props {
- id: string;
- checked?: boolean;
-}
-
-function IdCell({id, checked}: Props): JSX.Element
-{
- const pathParts = window.location.pathname.split("/");
- const tableName = pathParts[1];
- const href = `/${tableName}/${id}`;
- const link = {id};
-
- return (
-
-
-
-
- {link}
-
-
-
- );
-}
-
-// Declaring default props for IdCell
-IdCell.defaultProps = {
- checked: false,
-};
-
-export default IdCell;
diff --git a/src/qqq/components/EntityForm/components/StatusCell/index.tsx b/src/qqq/components/EntityForm/components/StatusCell/index.tsx
deleted file mode 100644
index 1f2508b..0000000
--- a/src/qqq/components/EntityForm/components/StatusCell/index.tsx
+++ /dev/null
@@ -1,57 +0,0 @@
-/**
-=========================================================
-* 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 Icon from "@mui/material/Icon";
-
-// Material Dashboard 2 PRO React TS components
-import MDBox from "components/MDBox";
-import MDTypography from "components/MDTypography";
-import MDButton from "components/MDButton";
-
-// Declaring props types for StatusCell
-interface Props {
- icon: string;
- color:
- | "primary"
- | "secondary"
- | "info"
- | "success"
- | "warning"
- | "error"
- | "dark"
- | "light"
- | "white"
- | "default";
- status: string;
-}
-
-function StatusCell({icon, color, status}: Props): JSX.Element
-{
- return (
-
-
-
- {icon}
-
-
-
- {status}
-
-
- );
-}
-
-export default StatusCell;
diff --git a/src/qqq/components/EntityForm/data/selectData.ts b/src/qqq/components/EntityForm/data/selectData.ts
deleted file mode 100644
index 3bc1d38..0000000
--- a/src/qqq/components/EntityForm/data/selectData.ts
+++ /dev/null
@@ -1,193 +0,0 @@
-/**
-=========================================================
-* 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.
-*/
-
-const selectData = {
- gender: ["Male", "Female"],
- birthDate: [
- "January",
- "February",
- "March",
- "April",
- "May",
- "June",
- "July",
- "August",
- "September",
- "October",
- "November",
- "December",
- ],
- days: [
- "1",
- "2",
- "3",
- "4",
- "5",
- "6",
- "7",
- "8",
- "9",
- "10",
- "11",
- "12",
- "13",
- "14",
- "15",
- "16",
- "17",
- "18",
- "19",
- "20",
- "21",
- "22",
- "23",
- "24",
- "25",
- "26",
- "27",
- "28",
- "29",
- "30",
- ],
- years: [
- "1900",
- "1901",
- "1902",
- "1903",
- "1904",
- "1905",
- "1906",
- "1907",
- "1908",
- "1909",
- "1910",
- "1911",
- "1912",
- "1913",
- "1914",
- "1915",
- "1915",
- "1915",
- "1916",
- "1917",
- "1918",
- "1919",
- "1920",
- "1921",
- "1922",
- "1923",
- "1924",
- "1925",
- "1926",
- "1927",
- "1928",
- "1929",
- "1930",
- "1931",
- "1932",
- "1933",
- "1934",
- "1935",
- "1936",
- "1937",
- "1938",
- "1939",
- "1940",
- "1941",
- "1942",
- "1943",
- "1944",
- "1945",
- "1946",
- "1947",
- "1948",
- "1949",
- "1950",
- "1951",
- "1952",
- "1953",
- "1954",
- "1955",
- "1956",
- "1957",
- "1958",
- "1959",
- "1960",
- "1961",
- "1962",
- "1963",
- "1964",
- "1965",
- "1966",
- "1967",
- "1968",
- "1969",
- "1970",
- "1971",
- "1972",
- "1973",
- "1974",
- "1975",
- "1976",
- "1977",
- "1978",
- "1979",
- "1980",
- "1981",
- "1982",
- "1983",
- "1984",
- "1985",
- "1986",
- "1987",
- "1988",
- "1989",
- "1990",
- "1991",
- "1992",
- "1993",
- "1994",
- "1995",
- "1996",
- "1997",
- "1998",
- "1999",
- "2000",
- "2001",
- "2002",
- "2003",
- "2004",
- "2005",
- "2006",
- "2007",
- "2008",
- "2009",
- "2010",
- "2011",
- "2012",
- "2013",
- "2014",
- "2015",
- "2016",
- "2017",
- "2018",
- "2019",
- "2020",
- "2021",
- ],
- skills: ["react", "vue", "angular", "svelte", "javascript"],
-};
-
-export default selectData;
diff --git a/src/qqq/components/EntityForm/index.tsx b/src/qqq/components/EntityForm/index.tsx
index 41d8a3f..2a2329a 100644
--- a/src/qqq/components/EntityForm/index.tsx
+++ b/src/qqq/components/EntityForm/index.tsx
@@ -1,13 +1,14 @@
// react components
-import {useParams, useSearchParams} from "react-router-dom";
+import {useParams, useNavigate, useLocation} from "react-router-dom";
import React, {useReducer, useState} from "react";
// misc components
import * as Yup from "yup";
-import {Form, Formik} from "formik";
+import {
+ Form, Formik, useFormik, useFormikContext,
+} from "formik";
// qqq components
-import {QController} from "@kingsrook/qqq-frontend-core/lib/controllers/QController";
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";
@@ -20,45 +21,65 @@ import {Alert} from "@mui/material";
// Material Dashboard 2 PRO React TS components
import MDBox from "components/MDBox";
import MDTypography from "components/MDTypography";
-import MDButton from "../../../components/MDButton";
+import QClient from "qqq/utils/QClient";
+import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData";
+import {QCancelButton, QSaveButton} from "qqq/components/QButtons";
+import {QRecord} from "@kingsrook/qqq-frontend-core/lib/model/QRecord";
+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";
-// Declaring props types for EntityForm
interface Props
{
- id?: string;
+ id?: string;
+ table?: QTableMetaData;
}
-function EntityForm({id}: Props): JSX.Element
+function EntityForm({table, id}: Props): JSX.Element
{
- const qController = new QController("");
- const {tableName} = useParams();
+ const qController = QClient.getInstance();
+ const tableNameParam = useParams().tableName;
+ const tableName = table === null ? tableNameParam : table.name;
const [validations, setValidations] = useState({});
const [initialValues, setInitialValues] = useState({} as { [key: string]: string });
- const [formFields, setFormFields] = useState({});
+ const [formFields, setFormFields] = useState(null as Map);
+ const [t1sectionName, setT1SectionName] = useState(null as string);
+ const [nonT1Sections, setNonT1Sections] = useState([] as QSection[]);
const [alertContent, setAlertContent] = useState("");
const [asyncLoadInited, setAsyncLoadInited] = useState(false);
const [formValues, setFormValues] = useState({} as { [key: string]: string });
- const [tableMetaData, setTableMetaData] = useState(null);
+ const [tableMetaData, setTableMetaData] = useState(null as QTableMetaData);
+ const [record, setRecord] = useState(null as QRecord);
+ const [tableSections, setTableSections] = useState(null as QSection[]);
const [, forceUpdate] = useReducer((x) => x + 1, 0);
- function getDynamicStepContent(formData: any): JSX.Element
+ const navigate = useNavigate();
+ const location = useLocation();
+
+ function getFormSection(values: any, touched: any, formFields: any, errors: any): JSX.Element
{
- const {
- formFields,
- values,
- errors,
- touched,
- } = formData;
+ const formData: any = {};
+ formData.values = values;
+ formData.touched = touched;
+ formData.errors = errors;
+ formData.formFields = {};
+ console.log(formFields);
+ for (let i = 0; i < formFields.length; i++)
+ {
+ formData.formFields[formFields[i].name] = formFields[i];
+ }
if (!Object.keys(formFields).length)
{
return
Loading...
;
}
-
- return ;
+ return ;
}
if (!asyncLoadInited)
@@ -69,20 +90,27 @@ function EntityForm({id}: Props): JSX.Element
const tableMetaData = await qController.loadTableMetaData(tableName);
setTableMetaData(tableMetaData);
+ /////////////////////////////////////////////////
+ // define the sections, e.g., for the left-bar //
+ /////////////////////////////////////////////////
+ const tableSections = QTableUtils.getSectionsForRecordSidebar(tableMetaData);
+ setTableSections(tableSections);
+
const fieldArray = [] as QFieldMetaData[];
const sortedKeys = [...tableMetaData.fields.keys()].sort();
sortedKeys.forEach((key) =>
{
const fieldMetaData = tableMetaData.fields.get(key);
- if (fieldMetaData.isEditable)
- {
- fieldArray.push(fieldMetaData);
- }
+ fieldArray.push(fieldMetaData);
});
+ /////////////////////////////////////////////////////////////////////////////////
+ // if doing an edit, fetch the record and pre-populate the form values from it //
+ /////////////////////////////////////////////////////////////////////////////////
if (id !== null)
{
const record = await qController.get(tableName, id);
+ setRecord(record);
tableMetaData.fields.forEach((fieldMetaData, key) =>
{
@@ -91,20 +119,96 @@ function EntityForm({id}: Props): JSX.Element
setFormValues(formValues);
}
+ setInitialValues(initialValues);
+ /////////////////////////////////////////////////////////
+ // get formField and formValidation objects for Formik //
+ /////////////////////////////////////////////////////////
const {
dynamicFormFields,
formValidations,
} = DynamicFormUtils.getFormData(fieldArray);
- setInitialValues(initialValues);
- setFormFields(dynamicFormFields);
- setValidations(Yup.object()
- .shape(formValidations));
+
+ /////////////////////////////////////
+ // group the formFields by section //
+ /////////////////////////////////////
+ const dynamicFormFieldsBySection = new Map();
+ let t1sectionName;
+ const nonT1Sections: QSection[] = [];
+ for (let i = 0; i < tableSections.length; i++)
+ {
+ const section = tableSections[i];
+ const sectionDynamicFormFields: any[] = [];
+
+ for (let j = 0; j < section.fieldNames.length; j++)
+ {
+ 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]);
+ }
+ }
+
+ if (sectionDynamicFormFields.length === 0)
+ {
+ ////////////////////////////////////////////////////////////////////////////////////////////////
+ // in case there are no active fields in this section, remove it from the tableSections array //
+ ////////////////////////////////////////////////////////////////////////////////////////////////
+ tableSections.splice(i, 1);
+ i--;
+ continue;
+ }
+ else
+ {
+ dynamicFormFieldsBySection.set(section.name, sectionDynamicFormFields);
+ }
+
+ //////////////////////////////////////
+ // capture the tier1 section's name //
+ //////////////////////////////////////
+ if (section.tier === "T1")
+ {
+ t1sectionName = section.name;
+ }
+ else
+ {
+ nonT1Sections.push(section);
+ }
+ }
+ setT1SectionName(t1sectionName);
+ setNonT1Sections(nonT1Sections);
+ setFormFields(dynamicFormFieldsBySection);
+ setValidations(Yup.object().shape(formValidations));
forceUpdate();
})();
}
+ 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$/, "")}`;
+ navigate(path, {replace: true});
+ }
+ else
+ {
+ const path = `${location.pathname.replace(/\/create$/, "")}`;
+ navigate(path, {replace: true});
+ }
+ };
+
const handleSubmit = async (values: any, actions: any) =>
{
actions.setSubmitting(true);
@@ -116,9 +220,8 @@ function EntityForm({id}: Props): JSX.Element
.update(tableName, id, values)
.then((record) =>
{
- window.location.href = `/${tableName}/${record.values.get(
- tableMetaData.primaryKeyField,
- )}?updateSuccess=true`;
+ const path = `${location.pathname.replace(/\/edit$/, "")}?updateSuccess=true`;
+ navigate(path);
})
.catch((error) =>
{
@@ -131,9 +234,8 @@ function EntityForm({id}: Props): JSX.Element
.create(tableName, values)
.then((record) =>
{
- window.location.href = `/${tableName}/${record.values.get(
- tableMetaData.primaryKeyField,
- )}?createSuccess=true`;
+ const path = `${location.pathname.replace(/create$/, record.values.get(tableMetaData.primaryKeyField))}?createSuccess=true`;
+ navigate(path);
})
.catch((error) =>
{
@@ -143,8 +245,20 @@ function EntityForm({id}: Props): JSX.Element
})();
};
- const formTitle = id != null ? `Edit ${tableMetaData?.label} (${id})` : `Create New ${tableMetaData?.label}`;
- const formId = id != null ? `edit-${tableMetaData?.label}-form` : `create-${tableMetaData?.label}-form`;
+ let formTitle = "";
+ if (tableMetaData)
+ {
+ if (id == null)
+ {
+ formTitle = `Create new ${tableMetaData?.label}`;
+ }
+ else if (record != null)
+ {
+ formTitle = `Edit ${tableMetaData?.label}: ${record?.recordLabel}`;
+ }
+ }
+
+ const formId = id != null ? `edit-${tableMetaData?.name}-form` : `create-${tableMetaData?.name}-form`;
return (
@@ -154,62 +268,90 @@ function EntityForm({id}: Props): JSX.Element
{alertContent}
- ) : (
- ""
- )}
-
-
- {formTitle}
-
-
-
-
- {({
- values,
- errors,
- touched,
- isSubmitting,
- }) => (
-
+
+
+
+
+
+
+
+
+ {({
+ values,
+ errors,
+ touched,
+ isSubmitting,
+ }) => (
+
- )}
-
-
-
-
+
+ {formTitle}
+
+
+ {
+ t1sectionName && formFields ? (
+
+
+ {getFormSection(values, touched, formFields.get(t1sectionName), errors)}
+
+
+ ) : null
+ }
+
+
+ {formFields && nonT1Sections.length ? nonT1Sections.map((section: QSection) => (
+
+
+
+ {section.label}
+
+
+
+ {
+ getFormSection(values, touched, formFields.get(section.name), errors)
+ }
+
+
+
+
+ )) : null}
+
+
+
+
+
+
+
+
+
+ )}
+
+
);
}
-// Declaring default props for DefaultCell
EntityForm.defaultProps = {
id: null,
+ table: null,
};
export default EntityForm;
diff --git a/src/qqq/components/Footer/index.tsx b/src/qqq/components/Footer/index.tsx
index 2743222..e976520 100644
--- a/src/qqq/components/Footer/index.tsx
+++ b/src/qqq/components/Footer/index.tsx
@@ -1,17 +1,23 @@
-/**
-=========================================================
-* 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.
-*/
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2022. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
// @mui material components
import Link from "@mui/material/Link";
@@ -60,6 +66,9 @@ function Footer({company, links}: Props): JSX.Element
justifyContent="space-between"
alignItems="center"
px={1.5}
+ style={{
+ position: "fixed", bottom: "0px", zIndex: -1, marginBottom: "10px",
+ }}
>
.
+ */
+
import {useState, useEffect} from "react";
// react-router components
-import {useLocation, Link} from "react-router-dom";
+import {useLocation} from "react-router-dom";
// @material-ui core components
import AppBar from "@mui/material/AppBar";
@@ -16,7 +37,6 @@ import MDInput from "components/MDInput";
import MDBadge from "components/MDBadge";
// Material Dashboard 2 PRO React TS examples components
-import Breadcrumbs from "examples/Breadcrumbs";
import NotificationItem from "examples/Items/NotificationItem";
// Custom styles for Navbar
@@ -38,7 +58,7 @@ import {
} from "context";
// qqq
-import AuthenticationButton from "qqq/components/buttons/AuthenticationButton";
+import QBreadcrumbs, {routeToLabel} from "qqq/components/QBreadcrumbs";
// Declaring prop types for Navbar
interface Props
@@ -107,9 +127,11 @@ function Navbar({absolute, light, isMini}: Props): JSX.Element
onClose={handleCloseMenu}
sx={{mt: 2}}
>
- email} title="Check new messages" />
+ email} title="0 messages available" />
+ {/*
podcasts} title="Manage Podcast sessions" />
shopping_cart} title="Payment successfully completed" />
+ */}
);
@@ -134,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 (
navbarRow(theme, {isMini})}>
-
+
{miniSidenav ? "menu_open" : "menu"}
@@ -159,14 +181,6 @@ function Navbar({absolute, light, isMini}: Props): JSX.Element
-
- { /*
-
-
- account_circle
-
-
- */ }
-
+ notifications
diff --git a/src/qqq/components/Navbar/styles.ts b/src/qqq/components/Navbar/styles.ts
index 3c18da8..7fa3afc 100644
--- a/src/qqq/components/Navbar/styles.ts
+++ b/src/qqq/components/Navbar/styles.ts
@@ -1,16 +1,22 @@
-/**
- =========================================================
- * 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.
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2022. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
*/
// @mui material components
diff --git a/src/qqq/components/ProcessLinkCard/index.tsx b/src/qqq/components/ProcessLinkCard/index.tsx
new file mode 100644
index 0000000..0be3bd4
--- /dev/null
+++ b/src/qqq/components/ProcessLinkCard/index.tsx
@@ -0,0 +1,105 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2022. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+import {ReactNode} from "react";
+
+// @mui material components
+import Card from "@mui/material/Card";
+import Divider from "@mui/material/Divider";
+import Icon from "@mui/material/Icon";
+
+// Material Dashboard 2 PRO React TS components
+import MDBox from "components/MDBox";
+import MDTypography from "components/MDTypography";
+
+interface Props {
+ color?: "primary" | "secondary" | "info" | "success" | "warning" | "error" | "light" | "dark";
+ title: string;
+ percentage?: {
+ color: "primary" | "secondary" | "info" | "success" | "warning" | "error" | "dark" | "white";
+ amount: string | number;
+ label: string;
+ };
+ icon: ReactNode;
+ [key: string]: any;
+}
+
+function ProcessLinkCard({
+ color, title, percentage, icon,
+}: Props): JSX.Element
+{
+ return (
+
+
+
+
+ {icon}
+
+
+
+
+ {title}
+
+
+
+
+
+
+
+ {percentage.amount}
+
+ Click here to run the process called
+ {" "}
+ {title}
+ .
+ {percentage.label}
+
+
+
+ );
+}
+
+ProcessLinkCard.defaultProps = {
+ color: "info",
+ percentage: {
+ color: "success",
+ text: "",
+ label: "",
+ },
+};
+
+export default ProcessLinkCard;
diff --git a/src/qqq/components/QBreadcrumbs/index.tsx b/src/qqq/components/QBreadcrumbs/index.tsx
new file mode 100644
index 0000000..248cfa1
--- /dev/null
+++ b/src/qqq/components/QBreadcrumbs/index.tsx
@@ -0,0 +1,138 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2022. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+import {ReactNode} from "react";
+
+import {Link} from "react-router-dom";
+import {Breadcrumbs as MuiBreadcrumbs} from "@mui/material";
+import Icon from "@mui/material/Icon";
+import MDBox from "components/MDBox";
+import MDTypography from "components/MDTypography";
+
+interface Props {
+ icon: ReactNode;
+ title: string;
+ route: string | string[];
+ light?: boolean;
+ [key: string]: any;
+}
+
+const ucFirst = (input: string): string =>
+{
+ if (!input)
+ {
+ return (input);
+ }
+ return (input.substring(0, 1).toUpperCase() + input.substring(1));
+};
+
+export const routeToLabel = (route: string): string =>
+{
+ 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);
+};
+
+function QBreadcrumbs({
+ icon, title, route, light,
+}: Props): JSX.Element
+{
+ const routes: string[] | any = route.slice(0, -1);
+
+ let pageTitle = "Nutrifresh One";
+ const fullRoutes: string[] = [];
+ let accumulatedPath = "";
+ for (let i = 0; i < routes.length; i++)
+ {
+ accumulatedPath = `${accumulatedPath}/${routes[i]}`;
+ fullRoutes.push(accumulatedPath);
+ pageTitle = `${routeToLabel(routes[i])} | ${pageTitle}`;
+ }
+
+ document.title = `${ucFirst(title)} | ${pageTitle}`;
+
+ return (
+
+ (light ? white.main : grey[600]),
+ },
+ }}
+ >
+
+
+ {icon}
+
+
+ {fullRoutes.map((fullRoute: string) => (
+
+
+ {routeToLabel(fullRoute.replace(/.*\//, ""))}
+
+
+ ))}
+
+ {routeToLabel(title)}
+
+
+
+ {routeToLabel(title)}
+
+
+ );
+}
+
+QBreadcrumbs.defaultProps = {
+ light: false,
+};
+
+export default QBreadcrumbs;
diff --git a/src/qqq/components/QButtons/index.tsx b/src/qqq/components/QButtons/index.tsx
new file mode 100644
index 0000000..867ee66
--- /dev/null
+++ b/src/qqq/components/QButtons/index.tsx
@@ -0,0 +1,122 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2022. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+import MDBox from "components/MDBox";
+import {Link} from "react-router-dom";
+import MDButton from "components/MDButton";
+import Icon from "@mui/material/Icon";
+import React from "react";
+
+// eslint-disable import/prefer-default-export
+
+const standardWidth = "150px";
+
+export function QCreateNewButton(): JSX.Element
+{
+ return (
+
+
+ add}>
+ Create New
+
+
+
+ );
+}
+
+export function QSaveButton(): JSX.Element
+{
+ return (
+
+ save}>
+ Save
+
+
+ );
+}
+
+interface QDeleteButtonProps
+{
+ onClickHandler: any
+}
+
+export function QDeleteButton({onClickHandler}: QDeleteButtonProps): JSX.Element
+{
+ return (
+
+ delete}>
+ Delete
+
+
+ );
+}
+
+export function QEditButton(): JSX.Element
+{
+ return (
+
+
+ edit}>
+ Edit
+
+
+
+ );
+}
+
+interface QActionsMenuButtonProps
+{
+ isOpen: boolean;
+ onClickHandler: any;
+}
+
+export function QActionsMenuButton({isOpen, onClickHandler}: QActionsMenuButtonProps): JSX.Element
+{
+ return (
+
+
+ actions
+ keyboard_arrow_down
+
+
+ );
+}
+
+interface QCancelButtonProps
+{
+ onClickHandler: any;
+}
+
+export function QCancelButton({onClickHandler}: QCancelButtonProps): JSX.Element
+{
+ return (
+
+ cancel} onClick={onClickHandler}>
+ Cancel
+
+
+ );
+}
diff --git a/src/qqq/components/QDynamicForm/index.tsx b/src/qqq/components/QDynamicForm/index.tsx
index 25190c2..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,
@@ -77,10 +76,6 @@ function QDynamicForm(props: Props): JSX.Element
&& Object.keys(formFields).map((fieldName: any) =>
{
const field = formFields[fieldName];
- if (primaryKeyId && fieldName === primaryKeyId)
- {
- return null;
- }
if (values[fieldName] === undefined)
{
values[fieldName] = "";
@@ -109,17 +104,18 @@ function QDynamicForm(props: Props): JSX.Element
// todo? inputProps={{ autoComplete: "" }}
// todo? placeholder={password.placeholder}
- // todo? success={!errors[fieldName] && touched[fieldName]}
return (
);
@@ -132,7 +128,6 @@ function QDynamicForm(props: Props): JSX.Element
QDynamicForm.defaultProps = {
formLabel: undefined,
- primaryKeyId: undefined,
bulkEditMode: false,
bulkEditSwitchChangeHandler: () =>
{},
diff --git a/src/qqq/components/QDynamicForm/utils/DynamicFormUtils.ts b/src/qqq/components/QDynamicForm/utils/DynamicFormUtils.ts
index 6f4d4cc..e5dc5d6 100644
--- a/src/qqq/components/QDynamicForm/utils/DynamicFormUtils.ts
+++ b/src/qqq/components/QDynamicForm/utils/DynamicFormUtils.ts
@@ -66,6 +66,9 @@ class DynamicFormUtils
case QFieldType.BLOB:
fieldType = "file";
break;
+ case QFieldType.BOOLEAN:
+ fieldType = "checkbox";
+ break;
case QFieldType.TEXT:
case QFieldType.HTML:
case QFieldType.STRING:
@@ -80,6 +83,7 @@ class DynamicFormUtils
name: field.name,
label: label,
isRequired: field.isRequired,
+ isEditable: field.isEditable,
type: fieldType,
// todo invalidMsg: "Zipcode is not valid (e.g. 70000).",
});
diff --git a/src/qqq/components/QDynamicFormField/index.tsx b/src/qqq/components/QDynamicFormField/index.tsx
index 4ce43e6..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";
@@ -36,24 +35,33 @@ interface Props
{
label: string;
name: string;
+ type: string;
+ isEditable?: boolean;
[key: string]: any;
bulkEditMode?: boolean;
bulkEditSwitchChangeHandler?: any
}
function QDynamicFormField({
- label, name, bulkEditMode, bulkEditSwitchChangeHandler, ...rest
+ label, name, bulkEditMode, bulkEditSwitchChangeHandler, type, isEditable, ...rest
}: Props): JSX.Element
{
const [switchChecked, setSwitchChecked] = useState(false);
- const [isDisabled, setIsDisabled] = useState(bulkEditMode);
+ const [isDisabled, setIsDisabled] = useState(!isEditable || bulkEditMode);
+
+ const inputLabelProps = {};
+ if (type.toLowerCase().match("(date|time)"))
+ {
+ // @ts-ignore
+ inputLabelProps.shrink = true;
+ }
const field = () => (
<>
-
+
- {!isDisabled && }
+ {!isDisabled &&
}
>
@@ -100,6 +108,7 @@ function QDynamicFormField({
QDynamicFormField.defaultProps = {
bulkEditMode: false,
+ isEditable: true,
bulkEditSwitchChangeHandler: () =>
{},
};
diff --git a/src/qqq/components/QRecordSidebar/index.tsx b/src/qqq/components/QRecordSidebar/index.tsx
new file mode 100644
index 0000000..85c5b04
--- /dev/null
+++ b/src/qqq/components/QRecordSidebar/index.tsx
@@ -0,0 +1,85 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2022. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+import React, {ReactNode} from "react";
+
+import {Link} from "react-router-dom";
+import {Breadcrumbs as MuiBreadcrumbs} from "@mui/material";
+import Icon from "@mui/material/Icon";
+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: QSection[];
+ light?: boolean;
+}
+
+function QRecordSidebar({tableSections, light}: Props): JSX.Element
+{
+ return (
+ borderRadius.lg, position: "sticky", top: "1%"}}>
+
+ {
+ tableSections ? tableSections.map((section: QSection, key: number) => (
+
+ ({
+ display: "flex",
+ alignItems: "center",
+ borderRadius: borderRadius.md,
+ padding: `${pxToRem(10)} ${pxToRem(16)}`,
+ transition: transitions.create("background-color", {
+ easing: transitions.easing.easeInOut,
+ duration: transitions.duration.shorter,
+ }),
+
+ "&:hover": {
+ backgroundColor: light.main,
+ },
+ })}
+ >
+
+ {section.iconName}
+
+ {section.label}
+
+
+ )) : null
+ }
+
+
+ );
+}
+
+QRecordSidebar.defaultProps = {
+ light: false,
+};
+
+export default QRecordSidebar;
diff --git a/src/qqq/components/buttons/AuthenticationButton/index.tsx b/src/qqq/components/buttons/AuthenticationButton/index.tsx
deleted file mode 100644
index 6696503..0000000
--- a/src/qqq/components/buttons/AuthenticationButton/index.tsx
+++ /dev/null
@@ -1,17 +0,0 @@
-import {useAuth0} from "@auth0/auth0-react";
-import React from "react";
-import {Button} from "@mui/material";
-
-function AuthenticationButton()
-{
- const {loginWithRedirect, logout, isAuthenticated} = useAuth0();
-
- if (isAuthenticated)
- {
- return ;
- }
-
- return ;
-}
-
-export default AuthenticationButton;
diff --git a/src/qqq/pages/app-home/index.tsx b/src/qqq/pages/app-home/index.tsx
new file mode 100644
index 0000000..7cda0a4
--- /dev/null
+++ b/src/qqq/pages/app-home/index.tsx
@@ -0,0 +1,264 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2022. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+import React, {useEffect, useState} from "react";
+import {Link, useLocation} from "react-router-dom";
+
+// Material Dashboard 2 PRO React TS examples components
+import QClient from "qqq/utils/QClient";
+import {QInstance} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QInstance";
+import {QAppMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QAppMetaData";
+import BaseLayout from "qqq/components/BaseLayout";
+import MDBox from "components/MDBox";
+import Grid from "@mui/material/Grid";
+import MiniStatisticsCard from "examples/Cards/StatisticsCards/MiniStatisticsCard";
+import {Icon} from "@mui/material";
+import MDTypography from "components/MDTypography";
+import Card from "@mui/material/Card";
+import ComplexStatisticsCard from "examples/Cards/StatisticsCards/ComplexStatisticsCard";
+import ReportsLineChart from "examples/Charts/LineCharts/ReportsLineChart";
+import DefaultLineChart from "examples/Charts/LineCharts/DefaultLineChart";
+import MDBadgeDot from "components/MDBadgeDot";
+import ReportsBarChart from "examples/Charts/BarCharts/ReportsBarChart";
+import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData";
+import {QAppNodeType} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QAppNodeType";
+import {QProcessMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QProcessMetaData";
+import DefaultInfoCard from "examples/Cards/InfoCards/DefaultInfoCard";
+import ProcessLinkCard from "qqq/components/ProcessLinkCard";
+
+const qController = QClient.getInstance();
+
+interface Props
+{
+ app?: QAppMetaData;
+}
+
+function AppHome({app}: Props): JSX.Element
+{
+ const [qInstance, setQInstance] = useState(null as QInstance);
+ const [tables, setTables] = useState([] as QTableMetaData[]);
+ const [processes, setProcesses] = useState([] as QProcessMetaData[]);
+ const [childApps, setChildApps] = useState([] as QAppMetaData[]);
+
+ const location = useLocation();
+
+ useEffect(() =>
+ {
+ (async () =>
+ {
+ const newQInstance = await qController.loadMetaData();
+ setQInstance(newQInstance);
+ })();
+ }, []);
+
+ useEffect(() =>
+ {
+ if (!qInstance)
+ {
+ return;
+ }
+
+ const newTables: QTableMetaData[] = [];
+ const newProcesses: QProcessMetaData[] = [];
+ const newChildApps: QAppMetaData[] = [];
+
+ app.children.forEach((child) =>
+ {
+ switch (child.type)
+ {
+ // todo - filter out hidden
+ case QAppNodeType.TABLE:
+ newTables.push(qInstance.tables.get(child.name));
+ break;
+ case QAppNodeType.PROCESS:
+ newProcesses.push(qInstance.processes.get(child.name));
+ break;
+ case QAppNodeType.APP:
+ newChildApps.push(qInstance.apps.get(child.name));
+ break;
+ default:
+ console.log(`Unexpected child type: ${child.type}`);
+ }
+ });
+
+ setTables(newTables);
+ setProcesses(newProcesses);
+ setChildApps(newChildApps);
+ }, [qInstance, location]);
+
+ const reportsBarChartData = {
+ labels: ["M", "T", "W", "T", "F", "S", "S"],
+ datasets: {label: "Sales", data: [50, 20, 10, 22, 50, 10, 40]},
+ };
+
+ interface Types {
+ labels: string[];
+ datasets: {
+ label: string;
+ color: "primary" | "secondary" | "info" | "success" | "warning" | "error" | "light" | "dark";
+ data: number[];
+ }[];
+ }
+
+ const demoLineChartData: Types = {
+ labels: ["Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"],
+ datasets: [
+ {
+ label: "Facebook Ads",
+ color: "info",
+ data: [50, 100, 200, 190, 400, 350, 500, 450, 700],
+ },
+ {
+ label: "Google Ads",
+ color: "dark",
+ data: [10, 30, 40, 120, 150, 220, 280, 250, 280],
+ },
+ ],
+ };
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )}
+ chart={demoLineChartData}
+ />
+
+
+
+
+
+
+ {
+ tables.length ? (
+
+
+
+ Tables
+
+
+ {tables.map((table) => (
+
+
+
+ {table.iconName || app.iconName}}}
+ direction="right"
+ />
+
+
+
+ ))}
+
+
+
+ ) : null
+ }
+
+ {
+ processes.length ? (
+
+
+
+ Processes
+
+
+ {processes.map((process) => (
+
+
+
+
+
+ ))}
+
+
+
+ ) : null
+ }
+
+ {
+ childApps.length ? (
+
+
+
+ {childApps.map((childApp) => (
+
+
+
+
+
+ ))}
+
+
+
+ ) : null
+ }
+
+
+
+
+
+ );
+}
+
+AppHome.defaultProps = {
+ app: null,
+};
+
+export default AppHome;
diff --git a/src/qqq/pages/entity-create/index.tsx b/src/qqq/pages/entity-create/index.tsx
index e9c24df..cef402b 100644
--- a/src/qqq/pages/entity-create/index.tsx
+++ b/src/qqq/pages/entity-create/index.tsx
@@ -1,16 +1,22 @@
-/**
- =========================================================
- * 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.
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2022. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
*/
// @mui material components
@@ -22,15 +28,21 @@ import MDBox from "components/MDBox";
// Settings page components
import EntityForm from "qqq/components/EntityForm";
import BaseLayout from "qqq/components/BaseLayout";
+import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData";
-function EntityCreate(): JSX.Element
+interface Props
+{
+ table?: QTableMetaData;
+}
+
+function EntityCreate({table}: Props): JSX.Element
{
return (
-
+
@@ -38,4 +50,8 @@ function EntityCreate(): JSX.Element
);
}
+EntityCreate.defaultProps = {
+ table: null,
+};
+
export default EntityCreate;
diff --git a/src/qqq/pages/entity-edit/index.tsx b/src/qqq/pages/entity-edit/index.tsx
index ffe4155..e3a672f 100644
--- a/src/qqq/pages/entity-edit/index.tsx
+++ b/src/qqq/pages/entity-edit/index.tsx
@@ -1,16 +1,22 @@
-/**
- =========================================================
- * 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.
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2022. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
*/
// @mui material components
@@ -23,8 +29,15 @@ import MDBox from "components/MDBox";
import BaseLayout from "qqq/components/BaseLayout";
import {useParams} from "react-router-dom";
import EntityForm from "qqq/components/EntityForm";
+import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData";
+import EntityList from "qqq/pages/entity-list";
-function EntityEdit(): JSX.Element
+interface Props
+{
+ table?: QTableMetaData;
+}
+
+function EntityEdit({table}: Props): JSX.Element
{
const {id} = useParams();
@@ -36,7 +49,7 @@ function EntityEdit(): JSX.Element
-
+
@@ -47,4 +60,8 @@ function EntityEdit(): JSX.Element
);
}
+EntityEdit.defaultProps = {
+ table: null,
+};
+
export default EntityEdit;
diff --git a/src/qqq/pages/entity-list/index.tsx b/src/qqq/pages/entity-list/index.tsx
index bae32c6..a6b4cf9 100644
--- a/src/qqq/pages/entity-list/index.tsx
+++ b/src/qqq/pages/entity-list/index.tsx
@@ -1,26 +1,39 @@
-/**
- =========================================================
- * 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.
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2022. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
*/
-/* eslint-disable react/no-unstable-nested-components */
-import React, {useEffect, useReducer, useState} from "react";
-import {useParams, useSearchParams} from "react-router-dom";
+import React, {
+ SyntheticEvent,
+ useCallback,
+ useEffect, useReducer, useRef, useState,
+} from "react";
+import {
+ Link, useNavigate, useParams, useSearchParams,
+} from "react-router-dom";
// @mui material components
import Card from "@mui/material/Card";
import Icon from "@mui/material/Icon";
import Menu from "@mui/material/Menu";
import MenuItem from "@mui/material/MenuItem";
-import Link from "@mui/material/Link";
-import {Alert, TablePagination} from "@mui/material";
+import {Alert, Pagination, TablePagination} from "@mui/material";
import {
DataGridPro,
GridCallbackDetails,
@@ -40,6 +53,7 @@ import {
GridToolbarExportContainer,
GridToolbarFilterButton,
GridExportMenuItemProps,
+ MuiEvent,
} from "@mui/x-data-grid-pro";
// Material Dashboard 2 PRO React TS components
@@ -64,11 +78,13 @@ import Footer from "../../components/Footer";
import QProcessUtils from "../../utils/QProcessUtils";
import "./styles.css";
+import {QActionsMenuButton, QCreateNewButton} from "qqq/components/QButtons";
+import QValueUtils from "qqq/utils/QValueUtils";
+import LinearProgress from "@mui/material/LinearProgress";
const COLUMN_VISIBILITY_LOCAL_STORAGE_KEY_ROOT = "qqq.columnVisibility";
const COLUMN_SORT_LOCAL_STORAGE_KEY_ROOT = "qqq.columnSort";
-// Declaring props types for DefaultCell
interface Props
{
table?: QTableMetaData;
@@ -87,6 +103,7 @@ function EntityList({table}: Props): JSX.Element
const columnVisibilityLocalStorageKey = `${COLUMN_VISIBILITY_LOCAL_STORAGE_KEY_ROOT}.${tableName}`;
let defaultSort = [] as GridSortItem[];
let defaultVisibility = {};
+ const qController = QClient.getInstance();
if (localStorage.getItem(sortLocalStorageKey))
{
@@ -97,7 +114,6 @@ function EntityList({table}: Props): JSX.Element
defaultVisibility = JSON.parse(localStorage.getItem(columnVisibilityLocalStorageKey));
}
- const [buttonText, setButtonText] = useState("");
const [tableState, setTableState] = useState("");
const [tableMetaData, setTableMetaData] = useState(null as QTableMetaData);
const [, setFiltersMenu] = useState(null);
@@ -116,6 +132,10 @@ function EntityList({table}: Props): JSX.Element
const [tableLabel, setTableLabel] = useState("");
const [columnSortModel, setColumnSortModel] = useState(defaultSort);
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);
@@ -191,29 +211,30 @@ function EntityList({table}: Props): JSX.Element
const updateTable = () =>
{
+ setRows([]);
(async () =>
{
- const newTableMetaData = await QClient.loadTableMetaData(tableName);
- setTableMetaData(newTableMetaData);
+ const tableMetaData = await qController.loadTableMetaData(tableName);
+ setTableMetaData(tableMetaData);
if (columnSortModel.length === 0)
{
columnSortModel.push({
- field: newTableMetaData.primaryKeyField,
+ field: tableMetaData.primaryKeyField,
sort: "desc",
});
setColumnSortModel(columnSortModel);
}
+ setPinnedColumns({left: ["__check__", tableMetaData.primaryKeyField]});
const qFilter = buildQFilter();
- const count = await QClient.count(tableName, qFilter);
+ const count = await qController.count(tableName, qFilter);
setTotalRecords(count);
- setButtonText(`new ${newTableMetaData.label}`);
- setTableLabel(newTableMetaData.label);
+ setTableLabel(tableMetaData.label);
const columns = [] as GridColDef[];
- const results = await QClient.query(
+ const results = await qController.query(
tableName,
qFilter,
rowsPerPage,
@@ -232,47 +253,75 @@ function EntityList({table}: Props): JSX.Element
throw error;
});
+ const fields = [...tableMetaData.fields.values()];
+
const rows = [] as any[];
results.forEach((record) =>
{
- rows.push(Object.fromEntries(record.values.entries()));
+ const row: any = {};
+ fields.forEach((field) =>
+ {
+ row[field.name] = QValueUtils.getDisplayValue(field, record);
+ });
+
+ rows.push(row);
});
- const sortedKeys = [...newTableMetaData.fields.keys()].sort();
+ const sortedKeys: string[] = [];
+
+ for (let i = 0; i < tableMetaData.sections.length; i++)
+ {
+ const section = tableMetaData.sections[i];
+ for (let j = 0; j < section.fieldNames.length; j++)
+ {
+ sortedKeys.push(section.fieldNames[j]);
+ }
+ }
+
sortedKeys.forEach((key) =>
{
- const field = newTableMetaData.fields.get(key);
+ const field = tableMetaData.fields.get(key);
let columnType = "string";
+ let columnWidth = 200;
switch (field.type)
{
case QFieldType.DECIMAL:
case QFieldType.INTEGER:
columnType = "number";
+ columnWidth = 100;
+
+ if (key === tableMetaData.primaryKeyField && field.label.length < 3)
+ {
+ columnWidth = 75;
+ }
+
break;
case QFieldType.DATE:
columnType = "date";
+ columnWidth = 100;
break;
case QFieldType.DATE_TIME:
columnType = "dateTime";
+ columnWidth = 200;
break;
case QFieldType.BOOLEAN:
columnType = "boolean";
+ columnWidth = 75;
break;
default:
- // noop
+ // noop - leave as string
}
const column = {
field: field.name,
type: columnType,
headerName: field.label,
- width: 200,
+ width: columnWidth,
};
- if (key === newTableMetaData.primaryKeyField)
+ if (key === tableMetaData.primaryKeyField)
{
- column.width = 75;
columns.splice(0, 0, column);
}
else
@@ -298,11 +347,49 @@ function EntityList({table}: Props): JSX.Element
setRowsPerPage(size);
};
- const handleRowClick = (params: GridRowParams) =>
+ const navigate = useNavigate();
+ const handleRowClick = (params: GridRowParams, event: MuiEvent, details: GridCallbackDetails) =>
{
- document.location.href = `/${tableName}/${params.id}`;
+ ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+ // strategy for when to trigger or not trigger a row click: //
+ // To avoid a drag-event that highlighted text in a cell: //
+ // - we capture the x & y upon mouse-down - then compare them in this method (which fires when the mouse is up) //
+ // if they are more than 5 pixels away from the mouse-down, then assume it's a drag, not a click. //
+ // - avoid clicking the row upon double-click, by setting a 500ms timer here - and in the onDoubleClick handler, //
+ // cancelling the timer. //
+ // - also avoid a click, then click-again-and-start-dragging, by always cancelling the timer in mouse-down. //
+ // All in, these seem to have good results - the only downside being the half-second delay... //
+ ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+ navigate(`${params.id}`);
+ /*
+ const diff = Math.max(Math.abs(event.clientX - gridMouseDownX), Math.abs(event.clientY - gridMouseDownY));
+ if (diff < 5)
+ {
+ clearTimeout(instance.current.timer);
+ instance.current.timer = setTimeout(() =>
+ {
+ navigate(`${params.id}`);
+ }, 500);
+ }
+ else
+ {
+ console.log(`row-click mouse-up happened ${diff} x or y pixels away from the mouse-down - so not considering it a click.`);
+ }
+ */
};
+ const handleGridMouseDown = useCallback((event: any) =>
+ {
+ setGridMouseDownX(event.clientX);
+ setGridMouseDownY(event.clientY);
+ clearTimeout(instance.current.timer);
+ }, []);
+
+ const handleGridDoubleClick = useCallback((event: any) =>
+ {
+ clearTimeout(instance.current.timer);
+ }, []);
+
const handleFilterChange = (filterModel: GridFilterModel) =>
{
setFilterModel(filterModel);
@@ -362,7 +449,7 @@ function EntityList({table}: Props): JSX.Element
setTableState(tableName);
setFilterModel(null);
setFiltersMenu(null);
- const metaData = await QClient.loadMetaData();
+ const metaData = await qController.loadMetaData();
setTableProcesses(QProcessUtils.getProcessesForTable(metaData, tableName));
@@ -376,6 +463,8 @@ function EntityList({table}: Props): JSX.Element
format: string;
}
+ // todo - figure out what's up here...
+ // eslint-disable-next-line react/no-unstable-nested-components
function ExportMenuItem(props: QExportMenuItemProps)
{
const {format, hideMenu} = props;
@@ -475,7 +564,7 @@ function EntityList({table}: Props): JSX.Element
const bulkLoadClicked = () =>
{
- document.location.href = `/processes/${tableName}.bulkInsert`;
+ navigate(`${tableName}.bulkInsert`);
};
const bulkEditClicked = () =>
@@ -485,7 +574,7 @@ function EntityList({table}: Props): JSX.Element
setAlertContent("No records were selected to Bulk Edit.");
return;
}
- document.location.href = `/processes/${tableName}.bulkEdit${getRecordsQueryString()}`;
+ navigate(`${tableName}.bulkEdit${getRecordsQueryString()}`);
};
const bulkDeleteClicked = () =>
@@ -495,12 +584,21 @@ function EntityList({table}: Props): JSX.Element
setAlertContent("No records were selected to Bulk Delete.");
return;
}
- document.location.href = `/processes/${tableName}.bulkDelete${getRecordsQueryString()}`;
+ navigate(`${tableName}.bulkDelete${getRecordsQueryString()}`);
+ };
+
+ const processClicked = (process: QProcessMetaData) =>
+ {
+ // todo - let the process specify that it needs initial rows - err if none selected.
+ // alternatively, let a process itself have an initial screen to select rows...
+ navigate(`${process.name}${getRecordsQueryString()}`);
};
// @ts-ignore
- const defaultLabelDisplayedRows = ({from, to, count}) => `${from.toLocaleString()}–${to.toLocaleString()} of ${count !== -1 ? count.toLocaleString() : `more than ${to.toLocaleString()}`}`;
+ const defaultLabelDisplayedRows = ({from, to, count}) => `Showing ${from.toLocaleString()} to ${to.toLocaleString()} of ${count !== -1 ? `${count.toLocaleString()} records` : `more than ${to.toLocaleString()} records`}`;
+ // todo - figure out what's up here...
+ // eslint-disable-next-line react/no-unstable-nested-components
function CustomPagination()
{
return (
@@ -517,21 +615,19 @@ function EntityList({table}: Props): JSX.Element
);
}
+ // todo - figure out what's up here...
+ // eslint-disable-next-line react/no-unstable-nested-components
+ function Loading()
+ {
+ return (
+
+ );
+ }
+
+ // todo - figure out what's up here...
+ // eslint-disable-next-line react/no-unstable-nested-components
function CustomToolbar()
{
- const [bulkActionsMenuAnchor, setBulkActionsMenuAnchor] = useState(null as HTMLElement);
- const bulkActionsMenuOpen = Boolean(bulkActionsMenuAnchor);
-
- const openBulkActionsMenu = (event: React.MouseEvent) =>
- {
- setBulkActionsMenuAnchor(event.currentTarget);
- };
-
- const closeBulkActionsMenu = () =>
- {
- setBulkActionsMenuAnchor(null);
- };
-
return (
@@ -550,29 +646,6 @@ function EntityList({table}: Props): JSX.Element
-
-
-
-
{
selectFullFilterState === "checked" && (
@@ -620,29 +693,38 @@ function EntityList({table}: Props): JSX.Element
anchorEl={actionsMenu}
anchorOrigin={{
vertical: "bottom",
- horizontal: "left",
+ horizontal: "right",
}}
transformOrigin={{
vertical: "top",
- horizontal: "left",
+ horizontal: "right",
}}
open={Boolean(actionsMenu)}
onClose={closeActionsMenu}
keepMounted
>
+
+
+
+ {tableProcesses.length > 0 && }
{tableProcesses.map((process) => (
-
+
))}
);
useEffect(() =>
{
+ setLoading(true);
updateTable();
}, [pageNumber, rowsPerPage, tableState, columnSortModel, filterModel]);
+ useEffect(() =>
+ {
+ document.documentElement.scrollTop = 0;
+ document.scrollingElement.scrollTop = 0;
+ }, [pageNumber, rowsPerPage]);
+
return (
@@ -667,39 +749,25 @@ function EntityList({table}: Props): JSX.Element
{`${tableLabel} successfully deleted`}
- ) : ("")
+ ) : null
}
-
- {buttonText ? (
-
-
- {
- buttonText
- }
-
-
- ) : (
- ""
- )}
+
-
- {tableProcesses.length > 0 && (
-
- actions
- keyboard_arrow_down
-
- )}
+
+
{renderActionsMenu}
+
+
+
+ {/* with these turned on, the toolbar & pagination controls become very flaky...
+ onMouseDown={(e) => handleGridMouseDown(e)} onDoubleClick={(e) => handleGridDoubleClick(e)} */}
.
*/
// react components
-import {useParams, useSearchParams} from "react-router-dom";
+import {useLocation, useNavigate, useSearchParams} from "react-router-dom";
import React, {useReducer, useState} from "react";
// @material-ui core components
import Card from "@mui/material/Card";
import Grid from "@mui/material/Grid";
-import Link from "@mui/material/Link";
import Dialog from "@mui/material/Dialog";
import DialogTitle from "@mui/material/DialogTitle";
import DialogContent from "@mui/material/DialogContent";
@@ -26,7 +34,6 @@ import DialogActions from "@mui/material/DialogActions";
import Button from "@mui/material/Button";
// qqq imports
-import {QController} from "@kingsrook/qqq-frontend-core/lib/controllers/QController";
import {QProcessMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QProcessMetaData";
// Material Dashboard 2 PRO React TS components
@@ -34,30 +41,50 @@ import MDBox from "components/MDBox";
import MDTypography from "components/MDTypography";
import Menu from "@mui/material/Menu";
import MenuItem from "@mui/material/MenuItem";
-import Icon from "@mui/material/Icon";
import MDAlert from "components/MDAlert";
-import MDButton from "../../../../../components/MDButton";
import QProcessUtils from "../../../../utils/QProcessUtils";
+import QClient from "qqq/utils/QClient";
+import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData";
+import {QActionsMenuButton, QDeleteButton, QEditButton} from "qqq/components/QButtons";
+import QValueUtils from "qqq/utils/QValueUtils";
+import {QRecord} from "@kingsrook/qqq-frontend-core/lib/model/QRecord";
+import Icon from "@mui/material/Icon";
+import Avatar from "@mui/material/Avatar";
+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";
-const qController = new QController("");
+const qController = QClient.getInstance();
// Declaring props types for ViewForm
interface Props
{
- id: string;
+ id: string;
+ table?: QTableMetaData;
}
-function ViewContents({id}: Props): JSX.Element
+function ViewContents({id, table}: Props): JSX.Element
{
- const {tableName} = useParams();
+ const location = useLocation();
+ const navigate = useNavigate();
+
+ const pathParts = location.pathname.split("/");
+ const tableName = table ? table.name : pathParts[pathParts.length - 2];
const [asyncLoadInited, setAsyncLoadInited] = useState(false);
const [nameValues, setNameValues] = useState([] as JSX.Element[]);
- const [open, setOpen] = useState(false);
+ const [sectionFieldElements, setSectionFieldElements] = useState(null as Map);
+ const [deleteConfirmationOpen, setDeleteConfirmationOpen] = useState(false);
const [tableMetaData, setTableMetaData] = useState(null);
+ const [record, setRecord] = useState(null as QRecord);
+ const [tableSections, setTableSections] = useState([] as QSection[]);
+ const [t1SectionName, setT1SectionName] = useState(null as string);
+ const [t1SectionElement, setT1SectionElement] = useState(null as JSX.Element);
+ const [nonT1TableSections, setNonT1TableSections] = useState([] as QSection[]);
const [tableProcesses, setTableProcesses] = useState([] as QProcessMetaData[]);
const [actionsMenu, setActionsMenu] = useState(null);
- const [searchParams, setSearchParams] = useSearchParams();
+ const [searchParams] = useSearchParams();
const [, forceUpdate] = useReducer((x) => x + 1, 0);
const openActionsMenu = (event: any) => setActionsMenu(event.currentTarget);
@@ -69,60 +96,83 @@ function ViewContents({id}: Props): JSX.Element
(async () =>
{
- const tableMetaData = await qController.loadTableMetaData(tableName);
+ //////////////////////////////////////////
+ // load the table meta-data (if needed) //
+ //////////////////////////////////////////
+ const tableMetaData = table || await qController.loadTableMetaData(tableName);
setTableMetaData(tableMetaData);
+ //////////////////////////////////////////////////////////////////
+ // load top-level meta-data (e.g., to find processes for table) //
+ //////////////////////////////////////////////////////////////////
const metaData = await qController.loadMetaData();
setTableProcesses(QProcessUtils.getProcessesForTable(metaData, tableName));
- const foundRecord = await qController.get(tableName, id);
+ /////////////////////
+ // load the record //
+ /////////////////////
+ const record = await qController.get(tableName, id);
+ setRecord(record);
- nameValues.push(
-
-
- {tableMetaData.primaryKeyField}
- :
-
-
-
- {id}
-
- ,
- );
+ /////////////////////////////////////////////////
+ // define the sections, e.g., for the left-bar //
+ /////////////////////////////////////////////////
+ const tableSections = QTableUtils.getSectionsForRecordSidebar(tableMetaData);
+ setTableSections(tableSections);
- const sortedKeys = [...foundRecord.values.keys()].sort();
- sortedKeys.forEach((key) =>
+ ////////////////////////////////////////////////////
+ // make elements with the values for each section //
+ ////////////////////////////////////////////////////
+ const sectionFieldElements = new Map();
+ const nonT1TableSections = [];
+ for (let i = 0; i < tableSections.length; i++)
{
- if (key !== tableMetaData.primaryKeyField)
- {
- nameValues.push(
-
-
- {tableMetaData.fields.get(key).label}
- :
-
-
-
- {foundRecord.values.get(key)}
-
- ,
- );
- }
- });
+ const section = tableSections[i];
+ sectionFieldElements.set(
+ section.name,
+
+ {
+ section.fieldNames.map((fieldName: string) => (
+
+
+ {tableMetaData.fields.get(fieldName).label}
+ :
+
+
+
+ {QValueUtils.getDisplayValue(tableMetaData.fields.get(fieldName), record)}
+
+
+ ))
+ }
+ ,
+ );
+
+ if (section.tier === "T1")
+ {
+ setT1SectionElement(sectionFieldElements.get(section.name));
+ setT1SectionName(section.name);
+ }
+ else
+ {
+ nonT1TableSections.push(tableSections[i]);
+ }
+ }
+ setSectionFieldElements(sectionFieldElements);
+ setNonT1TableSections(nonT1TableSections);
- setNameValues(nameValues);
forceUpdate();
})();
}
- const handleClickConfirmOpen = () =>
+ const handleClickDeleteButton = () =>
{
- setOpen(true);
+ setDeleteConfirmationOpen(true);
};
- const handleClickConfirmClose = () =>
+ const handleDeleteConfirmClose = () =>
{
- setOpen(false);
+ setDeleteConfirmationOpen(false);
};
const handleDelete = (event: { preventDefault: () => void }) =>
@@ -133,32 +183,45 @@ function ViewContents({id}: Props): JSX.Element
await qController.delete(tableName, id)
.then(() =>
{
- window.location.href = `/${tableName}?deleteSuccess=true`;
+ const path = `${pathParts.slice(0, -1).join("/")}?deleteSuccess=true`;
+ navigate(path);
});
})();
};
- const editPath = `/${tableName}/${id}/edit`;
+ function processClicked(process: QProcessMetaData)
+ {
+ const path = `${pathParts.slice(0, -1).join("/")}/${process.name}?recordIds=${id}`;
+ navigate(path);
+ }
const renderActionsMenu = (
);
@@ -178,77 +241,85 @@ function ViewContents({id}: Props): JSX.Element
) : ("")
}
-
-
-
-
- Viewing
- {" "}
- {tableMetaData?.label}
- {" "}
- (
- {id}
- )
-
- {tableProcesses.length > 0 && (
-
- actions
- keyboard_arrow_down
-
- )}
- {renderActionsMenu}
-
-
- {nameValues}
-
-
-
-
- delete
- {" "}
- {tableMetaData?.label}
-
-
-
-
-
-
- {`edit ${tableMetaData?.label}`}
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {tableMetaData?.iconName}
+
+
+
+
+
+ {tableMetaData && record ? `Viewing ${tableMetaData?.label}: ${record?.recordLabel}` : ""}
+
+
+ {renderActionsMenu}
+
+
+ {t1SectionElement ? ({t1SectionElement}) : null}
+
+
-
-
+ {nonT1TableSections.length > 0 ? nonT1TableSections.map(({
+ iconName, label, name, fieldNames, tier,
+ }: any) => (
+
+
+
+ {label}
+
+ {sectionFieldElements.get(name)}
+
+
+ )) : null}
+
+
+
+
+
+
+
+
+
+
+ {/* Delete confirmation Dialog */}
+
+
);
}
+ViewContents.defaultProps = {
+ table: null,
+};
+
export default ViewContents;
diff --git a/src/qqq/pages/entity-view/index.tsx b/src/qqq/pages/entity-view/index.tsx
index 7a85816..b2ff022 100644
--- a/src/qqq/pages/entity-view/index.tsx
+++ b/src/qqq/pages/entity-view/index.tsx
@@ -1,16 +1,22 @@
-/**
- =========================================================
- * 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.
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2022. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
*/
import {useParams} from "react-router-dom";
@@ -24,22 +30,25 @@ import MDBox from "components/MDBox";
// Settings page components
import BaseLayout from "qqq/components/BaseLayout";
import ViewContents from "./components/ViewContents";
+import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData";
+import EntityList from "qqq/pages/entity-list";
-function EntityView(): JSX.Element
+interface Props
+{
+ table?: QTableMetaData;
+}
+
+function EntityView({table}: Props): JSX.Element
{
const {id} = useParams();
return (
-
-
-
+
+
+
-
-
-
-
-
+
@@ -48,4 +57,8 @@ function EntityView(): JSX.Element
);
}
+EntityView.defaultProps = {
+ table: null,
+};
+
export default EntityView;
diff --git a/src/qqq/pages/process-run/index.tsx b/src/qqq/pages/process-run/index.tsx
index fc9e540..cb9ace8 100644
--- a/src/qqq/pages/process-run/index.tsx
+++ b/src/qqq/pages/process-run/index.tsx
@@ -1,16 +1,22 @@
-/**
- =========================================================
- * 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.
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2022. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
*/
import React, {useEffect, useState, Fragment} from "react";
@@ -31,9 +37,7 @@ 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";
-// ProcessRun layout schemas for form and form fields
import * as Yup from "yup";
import {QFieldMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldMetaData";
import {QFrontendStepMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFrontendStepMetaData";
@@ -52,10 +56,18 @@ import {CircularProgress, TablePagination} from "@mui/material";
import QDynamicForm from "../../components/QDynamicForm";
import MDTypography from "../../../components/MDTypography";
import Footer from "examples/Footer";
+import {QProcessMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QProcessMetaData";
+import Navbar from "qqq/components/Navbar";
-function ProcessRun(): JSX.Element
+interface Props
{
- const {processName} = useParams();
+ process?: QProcessMetaData;
+}
+
+function ProcessRun({process}: Props): JSX.Element
+{
+ const processNameParam = useParams().processName;
+ const processName = process === null ? processNameParam : process.name;
///////////////////
// process state //
@@ -686,7 +698,7 @@ function ProcessRun(): JSX.Element
return (
-
+ .
*/
-.MuiDrawer-docked .MuiPaper-elevation {
+.MuiDrawer-docked .MuiPaper-elevation
+{
white-space: normal;
}
+
+/* Disable red outlines on clicked cells */
+.MuiDataGrid-cell:focus,
+.MuiDataGrid-columnHeader:focus,
+.MuiDataGrid-columnHeader:focus-within,
+.MuiDataGrid-cell:focus-within
+{
+ outline: none !important;
+}
+
+/* lighten & shrink cell font */
+.MuiDataGrid-cell
+{
+ color: rgb(45, 45, 45);
+ font-size: 0.85rem;
+}
+
+/* lighten & shrink header font */
+.MuiDataGrid-columnHeaderTitle
+{
+ color: rgb(95, 95, 95);
+ font-size: 0.75rem;
+ font-weight: 500 !important;
+}
+
+/* tighten widths in headers */
+.MuiDataGrid-iconButtonContainer
+{
+ width: 20px;
+}
+
+/* tighten widths in headers */
+.MuiDataGrid-iconButtonContainer .MuiIconButton-root
+{
+ padding: 2px;
+}
+
+/* tighten widths in headers */
+.MuiDataGrid-menuIcon
+{
+ margin-left: -5px !important;
+}
+
+/* tighten widths in headers */
+.MuiDataGrid-menuIcon .MuiDataGrid-menuIconButton
+{
+ padding: 2px;
+}
+
+/* when checked-column is pinned, its checkboxes disappear as gray-on-gray - this helps. */
+.MuiCheckbox-root .MuiSvgIcon-root
+{
+ background-color: white;
+}
+
+/* shrink font on in the pagination control */
+.MuiTablePagination-displayedRows,
+.MuiTablePagination-selectLabel,
+.MuiTablePagination-select.MuiSelect-select.MuiSelect-standard,
+.MuiDataGrid-selectedRowCount
+{
+ font-size: 0.85rem !important;
+}
+
+/* try to make the pagination select box look like one */
+.MuiTablePagination-select
+{
+ border: 1px solid rgb(175 175 175) !important;
+ border-radius: 5px !important;
+}
+
+/* move the green check / red x down to align with the calendar icon */
+.MuiFormControl-root
+{
+ background-position-y: 1.4rem !important;
+}
\ No newline at end of file
diff --git a/src/qqq/utils/QClient.ts b/src/qqq/utils/QClient.ts
index a8153cb..960595d 100644
--- a/src/qqq/utils/QClient.ts
+++ b/src/qqq/utils/QClient.ts
@@ -19,9 +19,9 @@
* along with this program. If not, see .
*/
-import {QFieldMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldMetaData";
import {QController} from "@kingsrook/qqq-frontend-core/lib/controllers/QController";
-import {QQueryFilter} from "@kingsrook/qqq-frontend-core/lib/model/query/QQueryFilter";
+import {QException} from "@kingsrook/qqq-frontend-core/lib/exceptions/QException";
+import {useAuth0} from "@auth0/auth0-react";
/*******************************************************************************
** client wrapper of qqq backend
@@ -31,40 +31,25 @@ class QClient
{
private static qController: QController;
+ private static handleException(exception: QException)
+ {
+ console.log(`Caught Exception: ${JSON.stringify(exception)}`);
+ const {logout} = useAuth0();
+ if (exception.status === "401")
+ {
+ logout();
+ }
+ }
+
public static getInstance()
{
if (this.qController == null)
{
- this.qController = new QController("");
+ this.qController = new QController("", this.handleException);
}
return this.qController;
}
-
- public static loadTableMetaData(tableName: string)
- {
- return this.getInstance().loadTableMetaData(tableName);
- }
-
- public static loadMetaData()
- {
- return this.getInstance().loadMetaData();
- }
-
- public static query(tableName: string, filter: QQueryFilter, limit: number, skip: number)
- {
- return this.getInstance()
- .query(tableName, filter, limit, skip)
- .catch((error) =>
- {
- throw error;
- });
- }
-
- public static count(tableName: string, filter: QQueryFilter)
- {
- return this.getInstance().count(tableName, filter);
- }
}
export default QClient;
diff --git a/src/qqq/utils/QProcessUtils.ts b/src/qqq/utils/QProcessUtils.ts
index 79bce82..d706b65 100644
--- a/src/qqq/utils/QProcessUtils.ts
+++ b/src/qqq/utils/QProcessUtils.ts
@@ -28,14 +28,14 @@ import {QInstance} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QInstan
*******************************************************************************/
class QProcessUtils
{
- public static getProcessesForTable(metaData: QInstance, tableName: string): QProcessMetaData[]
+ public static getProcessesForTable(metaData: QInstance, tableName: string, includeHidden = false): QProcessMetaData[]
{
const matchingProcesses: QProcessMetaData[] = [];
const processKeys = [...metaData.processes.keys()];
processKeys.forEach((key) =>
{
const process = metaData.processes.get(key);
- if (!process.isHidden && process.tableName === tableName)
+ if (process.tableName === tableName && (includeHidden || !process.isHidden))
{
matchingProcesses.push(process);
}
diff --git a/src/qqq/utils/QTableUtils.ts b/src/qqq/utils/QTableUtils.ts
new file mode 100644
index 0000000..d75cc73
--- /dev/null
+++ b/src/qqq/utils/QTableUtils.ts
@@ -0,0 +1,46 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2022. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData";
+import {QSection} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QSection";
+
+/*******************************************************************************
+ ** Utility class for working with QQQ Tables
+ **
+ *******************************************************************************/
+class QTableUtils
+{
+ public static getSectionsForRecordSidebar(tableMetaData: QTableMetaData): QSection[]
+ {
+ if (tableMetaData.sections)
+ {
+ return (tableMetaData.sections);
+ }
+ else
+ {
+ return ([new QSection({
+ iconName: "description", label: "All Fields", name: "allFields", fieldNames: [...tableMetaData.fields.keys()],
+ })]);
+ }
+ }
+}
+
+export default QTableUtils;
diff --git a/src/qqq/utils/QValueUtils.ts b/src/qqq/utils/QValueUtils.ts
new file mode 100644
index 0000000..00bdb87
--- /dev/null
+++ b/src/qqq/utils/QValueUtils.ts
@@ -0,0 +1,63 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2022. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+import {QFieldMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldMetaData";
+import {QRecord} from "@kingsrook/qqq-frontend-core/lib/model/QRecord";
+import {QFieldType} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldType";
+
+import "datejs";
+
+/*******************************************************************************
+ ** Utility class for working with QQQ Values
+ **
+ *******************************************************************************/
+class QValueUtils
+{
+ public static getDisplayValue(field: QFieldMetaData, record: QRecord): string
+ {
+ const displayValue = record.displayValues ? record.displayValues.get(field.name) : undefined;
+ const rawValue = record.values ? record.values.get(field.name) : undefined;
+
+ if (field.type === QFieldType.DATE_TIME)
+ {
+ if (!rawValue)
+ {
+ return ("");
+ }
+ const date = new Date(rawValue);
+ // @ts-ignore
+ return (`${date.toString("yyyy-MM-dd hh:mm:ss")} ${date.getHours() < 12 ? "AM" : "PM"} ${date.getTimezone()}`);
+ }
+ else if (field.type === QFieldType.DATE)
+ {
+ // unclear if we need any customization for DATE or TIME, but leaving blocks for them just in case
+ return (displayValue);
+ }
+ else if (field.type === QFieldType.TIME)
+ {
+ return (displayValue);
+ }
+
+ return (displayValue);
+ }
+}
+
+export default QValueUtils;