QQQ-32 udpating styles on query; record list & view sections; record labels; apps in nav; updated breadcrumb; updated navigation

This commit is contained in:
2022-08-09 15:57:10 -05:00
parent 7cb3c5ee88
commit 74717a092a
38 changed files with 4156 additions and 967 deletions

2175
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -13,7 +13,7 @@
"@fullcalendar/interaction": "5.10.0",
"@fullcalendar/react": "5.10.0",
"@fullcalendar/timegrid": "5.10.0",
"@kingsrook/qqq-frontend-core": "1.0.7",
"@kingsrook/qqq-frontend-core": "file:.yalc/@kingsrook/qqq-frontend-core",
"@mui/icons-material": "5.4.1",
"@mui/material": "5.4.1",
"@mui/styled-engine": "5.4.1",
@ -31,6 +31,7 @@
"@types/react-dom": "17.0.11",
"chart.js": "3.4.1",
"chroma-js": "2.4.2",
"datejs": "1.0.0-rc3",
"dropzone": "5.9.2",
"flatpickr": "4.6.9",
"form-data": "4.0.0",

View File

@ -1,14 +1,10 @@
import React, {
useState,
useEffect,
JSXElementConstructor,
Key,
ReactElement,
JSXElementConstructor, Key, ReactElement, useEffect, useState,
} from "react";
// react-router components
import {
Routes, Route, Navigate, useLocation,
Navigate, Route, Routes, useLocation,
} from "react-router-dom";
import {useAuth0} from "@auth0/auth0-react";
@ -33,23 +29,28 @@ import theme from "assets/theme";
import themeDark from "assets/theme-dark";
// Material Dashboard 2 PRO React TS contexts
import {useMaterialUIController, setMiniSidenav, setOpenConfigurator} from "context";
import {setMiniSidenav, setOpenConfigurator, useMaterialUIController} from "context";
// Images
import nfLogo from "assets/images/nutrifresh_one_icon_white.png";
import {Md5} from "ts-md5/dist/md5";
import {useCookies} from "react-cookie";
import {QAppMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QAppMetaData";
import EntityCreate from "./qqq/pages/entity-create";
import EntityList from "./qqq/pages/entity-list";
import EntityView from "./qqq/pages/entity-view";
import EntityEdit from "./qqq/pages/entity-edit";
import ProcessRun from "./qqq/pages/process-run";
import AppHome from "qqq/pages/app-home";
import MDAvatar from "./components/MDAvatar";
import ProfileOverview from "./layouts/pages/profile/profile-overview";
import Settings from "./layouts/pages/account/settings";
import Analytics from "./layouts/dashboards/analytics";
import Sales from "./layouts/dashboards/sales";
import QClient from "./qqq/utils/QClient";
import {QAppNodeType} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QAppNodeType";
import {QInstance} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QInstance";
import QProcessUtils from "qqq/utils/QProcessUtils";
///////////////////////////////////////////////////////////////////////////////////////////////
// define the parts of the nav that are static - before the qqq tables etc get dynamic added //
@ -79,7 +80,6 @@ function getStaticRoutes()
],
},
{type: "divider", key: "divider-1"},
{type: "title", title: "Tables", key: "title-docs"},
];
}
@ -134,7 +134,10 @@ export default function App()
const {pathname} = useLocation();
const [needToLoadRoutes, setNeedToLoadRoutes] = useState(true);
const [routes, setRoutes] = useState(getStaticRoutes());
const [sideNavRoutes, setSideNavRoutes] = useState(getStaticRoutes());
const [appRoutes, setAppRoutes] = useState(null as any);
const dynamicAppChildElement = <AppHome />;
////////////////////////////////////////////
// load qqq meta data to make more routes //
@ -149,32 +152,149 @@ export default function App()
(async () =>
{
function addAppToSideNavList(app: QAppMetaData, appList: any[], parentPath: string, depth: number)
{
const path = `${parentPath}/${app.name}`;
if (app.type !== QAppNodeType.APP)
{
return;
}
if (app.type === QAppNodeType.APP && depth <= 2)
{
const childList: any[] = [];
app.children.forEach((child: QAppMetaData) =>
{
addAppToSideNavList(child, childList, path, depth + 1);
});
if (childList.length === 0)
{
if (depth === 0)
{
/////////////////////////////////////////////////////
// at level 0, the entry must always be a collapse //
/////////////////////////////////////////////////////
appList.push({
type: "collapse",
name: app.label,
key: app.name,
route: path,
icon: <Icon fontSize="medium">{app.iconName}</Icon>,
noCollapse: true,
component: <AppHome />,
});
}
else
{
appList.push({
name: app.label,
key: app.name,
route: path,
icon: <Icon fontSize="medium">{app.iconName}</Icon>,
component: <AppHome />,
});
}
}
else
{
appList.push({
type: "collapse",
name: app.label,
key: app.name,
dropdown: true,
icon: <Icon fontSize="medium">{app.iconName}</Icon>,
collapse: childList,
});
}
}
}
function addAppToAppRoutesList(metaData: QInstance, app: QAppMetaData, routeList: any[], parentPath: string, depth: number)
{
const path = `${parentPath}/${app.name}`;
if (app.type === QAppNodeType.APP)
{
app.children.forEach((child: QAppMetaData) =>
{
addAppToAppRoutesList(metaData, child, routeList, path, depth + 1);
});
routeList.push({
name: `${app.label}`,
key: app.name,
route: path,
component: <AppHome app={app} />,
});
}
else if (app.type === QAppNodeType.TABLE)
{
const table = metaData.tables.get(app.name);
routeList.push({
name: `${app.label}`,
key: app.name,
route: path,
component: <EntityList table={table} />,
});
routeList.push({
name: `${app.label} Create`,
key: `${app.name}.create`,
route: `${path}/create`,
component: <EntityCreate table={table} />,
});
routeList.push({
name: `${app.label} View`,
key: `${app.name}.view`,
route: `${path}/:id`,
component: <EntityView table={table} />,
});
routeList.push({
name: `${app.label}`,
key: `${app.name}.edit`,
route: `${path}/:id/edit`,
component: <EntityEdit table={table} />,
});
const processesForTable = QProcessUtils.getProcessesForTable(metaData, table.name, true);
processesForTable.forEach((process) =>
{
////////////////////////////////////////////////////////////////////////////////////////////////////
// special provision for the standard bulk processes - strip the table name from the process name //
// todo - this var isn't used - is this needed?
////////////////////////////////////////////////////////////////////////////////////////////////////
let processName = process.name;
if (processName.startsWith(`${process.tableName}.`))
{
processName = processName.replace(`${process.tableName}.`, "");
}
routeList.push({
name: process.label,
key: process.name,
route: `${path}/${process.name}`,
component: <ProcessRun process={process} />,
});
});
}
else if (app.type === QAppNodeType.PROCESS)
{
const process = metaData.processes.get(app.name);
routeList.push({
name: `${app.label}`,
key: app.name,
route: path,
component: <ProcessRun process={process} />,
});
}
}
try
{
const metaData = await QClient.getInstance().loadMetaData();
// get the keys sorted
const keys = [...metaData.tables.keys()].sort((a, b): number =>
{
const labelA = metaData.tables.get(a).label;
const labelB = metaData.tables.get(b).label;
return (labelA.localeCompare(labelB));
});
const tableList = [] as any[];
keys.forEach((key) =>
{
const table = metaData.tables.get(key);
if (!table.isHidden)
{
tableList.push({
name: `${table.label}`,
key: table.name,
route: `/${table.name}`,
component: <EntityList table={table} />,
});
}
});
let profileRoute = {};
const gravatarBase = "http://www.gravatar.com/avatar/";
const hash = Md5.hashStr(user.email);
@ -200,19 +320,25 @@ export default function App()
],
};
const tables = {
type: "collapse",
name: "Tables",
key: "tables",
icon: <Icon fontSize="medium">dashboard</Icon>,
collapse: tableList,
};
const sideNavAppList = [] as any[];
const appRoutesList = [] as any[];
for (let i = 0; i < metaData.appTree.length; i++)
{
const app = metaData.appTree[i];
addAppToSideNavList(app, sideNavAppList, "", 0);
addAppToAppRoutesList(metaData, app, appRoutesList, "", 0);
}
const newDynamicRoutes = getStaticRoutes();
const newSideNavRoutes = getStaticRoutes();
// @ts-ignore
newDynamicRoutes.unshift(profileRoute);
newDynamicRoutes.push(tables);
setRoutes(newDynamicRoutes);
newSideNavRoutes.unshift(profileRoute);
for (let i = 0; i < sideNavAppList.length; i++)
{
newSideNavRoutes.push(sideNavAppList[i]);
}
setSideNavRoutes(newSideNavRoutes);
setAppRoutes(appRoutesList);
}
catch (e)
{
@ -306,13 +432,8 @@ export default function App()
</MDBox>
);
const entityListElement = <EntityList />;
const entityCreateElement = <EntityCreate />;
const entityViewElement = <EntityView />;
const entityEditElement = <EntityEdit />;
const processRunElement = <ProcessRun />;
return (
appRoutes && (
<ThemeProvider theme={darkMode ? themeDark : theme}>
<CssBaseline />
{layout === "dashboard" && (
@ -321,23 +442,18 @@ export default function App()
color={sidenavColor}
brand={nfLogo}
brandName="Nutrifresh One"
routes={routes}
routes={sideNavRoutes}
onMouseEnter={handleOnMouseEnter}
onMouseLeave={handleOnMouseLeave}
/>
<Configurator />
{configsButton}
</>
)}
<Routes>
<Route path="*" element={<Navigate to="/dashboards/analytics" />} />
<Route path="/:tableName" element={entityListElement} key="entity-list" />
<Route path="/:tableName/create" element={entityCreateElement} key="entity-create" />
<Route path="/processes/:processName" element={processRunElement} key="process-run" />
<Route path="/:tableName/:id" element={entityViewElement} key="entity-view" />
<Route path="/:tableName/:id/edit" element={entityEditElement} key="entity-edit" />
{getRoutes(routes)}
{appRoutes && getRoutes(appRoutes)}
{sideNavRoutes && getRoutes(sideNavRoutes)}
</Routes>
</ThemeProvider>
)
);
}

View File

@ -1,13 +1,6 @@
import React, {
useState,
useEffect,
JSXElementConstructor,
Key,
ReactElement,
} from "react";
import React, {useEffect} from "react";
import {SESSION_ID_COOKIE_NAME} from "App";
import {useCookies} from "react-cookie";
import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData";
interface Props
{
@ -27,3 +20,7 @@ export default function HandleAuthorizationError({errorMessage}: Props)
<div>{errorMessage}</div>
);
}
HandleAuthorizationError.defaultProps = {
errorMessage: "User authorization error.",
};

View File

@ -56,6 +56,10 @@ interface Props {
| ReactNode
| string
| {
[key: string]:
| ReactNode
| string
| {
[key: string]:
| ReactNode
| string
@ -64,6 +68,7 @@ interface Props {
}[];
}[];
}[];
}[];
[key: string]: any;
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
import React from "react";
interface CodeSnippetProps

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
import React from "react";
function Loader() : JSX.Element

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
import {useAuth0} from "@auth0/auth0-react";
import React from "react";
import CodeSnippet from "./code-snippet";

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
import {withAuthenticationRequired} from "@auth0/auth0-react";
import React from "react";
import Loader from "./loader";

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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 (
<DashboardLayout>
<Navbar />
<MDBox mt={stickyNavbar ? 3 : 10}>{children}</MDBox>
<MDBox mt={stickyNavbar ? 3 : 6}>{children}</MDBox>
<Footer />
</DashboardLayout>
);

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
import {useAuth0} from "@auth0/auth0-react";
import React from "react";
import {Button} from "@mui/material";

View File

@ -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 (
<MDBox display="flex" alignItems="center">
<MDBox mr={1}>
<MDAvatar bgColor={color} src={image} alt={name} size="xs" />
</MDBox>
<MDTypography variant="caption" fontWeight="medium" color="text" sx={{lineHeight: 0}}>
{name}
</MDTypography>
</MDBox>
);
}
// Declaring default props for CustomerCell
CustomerCell.defaultProps = {
image: "",
color: "dark",
};
export default CustomerCell;

View File

@ -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 (
<MDTypography variant="caption" fontWeight="medium" color="text">
{value}
{suffix && (
<MDTypography variant="caption" fontWeight="medium" color="secondary">
&nbsp;&nbsp;
{suffix}
</MDTypography>
)}
</MDTypography>
);
}
// Declaring default props for DefaultCell
DefaultCell.defaultProps = {
suffix: "",
};
export default DefaultCell;

View File

@ -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 = <Link href={href}>{id}</Link>;
return (
<MDBox display="flex" alignItems="center">
<Checkbox defaultChecked={checked} />
<MDBox ml={1}>
<MDTypography variant="caption" fontWeight="medium" color="text">
{link}
</MDTypography>
</MDBox>
</MDBox>
);
}
// Declaring default props for IdCell
IdCell.defaultProps = {
checked: false,
};
export default IdCell;

View File

@ -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 (
<MDBox display="flex" alignItems="center">
<MDBox mr={1}>
<MDButton variant="outlined" color={color} size="small" iconOnly circular>
<Icon sx={{fontWeight: "bold"}}>{icon}</Icon>
</MDButton>
</MDBox>
<MDTypography variant="caption" fontWeight="medium" color="text" sx={{lineHeight: 0}}>
{status}
</MDTypography>
</MDBox>
);
}
export default StatusCell;

View File

@ -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;

View File

@ -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,61 @@ 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";
// Declaring props types for EntityForm
interface Props
{
id?: string;
table?: QTableMetaData;
}
function EntityForm({id}: Props): JSX.Element
function EntityForm({table, id}: Props): JSX.Element
{
const qController = QClient.getInstance();
const {tableName} = useParams();
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<string, any>);
const [t1sectionName, setT1SectionName] = useState(null as string);
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 any);
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 <div>Loading...</div>;
}
return <QDynamicForm formData={formData} primaryKeyId={tableMetaData.primaryKeyField} />;
}
@ -70,20 +87,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);
}
});
/////////////////////////////////////////////////////////////////////////////////
// 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) =>
{
@ -92,20 +116,79 @@ 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<string, any>();
let t1sectionName;
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 || 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--;
}
else
{
dynamicFormFieldsBySection.set(section.name, sectionDynamicFormFields);
}
//////////////////////////////////////
// capture the tier1 section's name //
//////////////////////////////////////
if (section.tier === "T1")
{
t1sectionName = section.name;
}
}
setT1SectionName(t1sectionName);
setFormFields(dynamicFormFieldsBySection);
setValidations(Yup.object().shape(formValidations));
forceUpdate();
})();
}
const handleCancelClicked = () =>
{
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);
@ -117,9 +200,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) =>
{
@ -132,9 +214,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) =>
{
@ -144,8 +225,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 (
<MDBox mb={3}>
@ -155,15 +248,15 @@ function EntityForm({id}: Props): JSX.Element
<MDBox mb={3}>
<Alert severity="error">{alertContent}</Alert>
</MDBox>
) : (
""
)}
<Card id="edit-form-container" sx={{overflow: "visible"}}>
<MDBox p={3}>
<MDTypography variant="h5">{formTitle}</MDTypography>
</MDBox>
<MDBox pb={3} px={3}>
<Grid key="fields-grid" container spacing={3}>
) : ("")}
</Grid>
</Grid>
<Grid container spacing={3}>
<Grid item xs={12} lg={3}>
<QRecordSidebar tableSections={tableSections} />
</Grid>
<Grid item xs={12} lg={9}>
<Formik
initialValues={initialValues}
validationSchema={validations}
@ -176,41 +269,70 @@ function EntityForm({id}: Props): JSX.Element
isSubmitting,
}) => (
<Form id={formId} autoComplete="off">
<MDBox p={3} width="100%">
{/***************************************************************************
** step content - e.g., the appropriate form or other screen for the step **
***************************************************************************/}
{getDynamicStepContent({
values,
touched,
formFields,
errors,
})}
<Grid key="buttonGrid" container spacing={3}>
<MDBox mt={5} ml="auto">
<MDButton type="submit" variant="gradient" color="dark" size="small">
save
{" "}
{tableMetaData?.label}
</MDButton>
<MDBox pb={3} pt={0}>
<Card id={`${t1sectionName}`} sx={{overflow: "visible"}}>
<MDBox display="flex" p={3} pb={1}>
<MDBox mr={1.5}>
<Avatar sx={{bgcolor: "rgb(26, 115, 232)"}}>
<Icon>
{tableMetaData?.iconName}
</Icon>
</Avatar>
</MDBox>
<MDBox display="flex" alignItems="center">
<MDTypography variant="h5">{formTitle}</MDTypography>
</MDBox>
</MDBox>
{
t1sectionName && formFields ? (
<MDBox pb={3} px={3}>
<MDBox p={3} width="100%">
{getFormSection(values, touched, formFields.get(t1sectionName), errors)}
</MDBox>
</MDBox>
) : null
}
</Card>
</MDBox>
{tableSections && formFields ? tableSections.map((section: any) => (section.name !== t1sectionName
? (
<MDBox key={`edit-card-${section.name}`} pb={3}>
<Card id={section.name} sx={{overflow: "visible"}}>
<MDTypography variant="h5" p={3} pb={1}>
{section.label}
</MDTypography>
<MDBox pb={3} px={3}>
<MDBox p={3} width="100%">
{
getFormSection(values, touched, formFields.get(section.name), errors)
}
</MDBox>
</MDBox>
</Card>
</MDBox>
) : null)) : null}
<MDBox component="div" p={3}>
<Grid container justifyContent="flex-end" spacing={3}>
<QCancelButton onClickHandler={handleCancelClicked} />
<QSaveButton />
</Grid>
</MDBox>
</Form>
)}
</Formik>
</Grid>
</MDBox>
</Card>
</Grid>
</Grid>
</MDBox>
);
}
// Declaring default props for DefaultCell
EntityForm.defaultProps = {
id: null,
table: null,
};
export default EntityForm;

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
// @mui material components
@ -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",
}}
>
<MDBox
display="flex"

View File

@ -1,7 +1,28 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
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,6 +58,7 @@ import {
} from "context";
// qqq
import QBreadcrumbs from "qqq/components/QBreadcrumbs";
// Declaring prop types for Navbar
interface Props
@ -106,9 +127,11 @@ function Navbar({absolute, light, isMini}: Props): JSX.Element
onClose={handleCloseMenu}
sx={{mt: 2}}
>
<NotificationItem icon={<Icon>email</Icon>} title="Check new messages" />
<NotificationItem icon={<Icon>email</Icon>} title="0 messages available" />
{/*
<NotificationItem icon={<Icon>podcasts</Icon>} title="Manage Podcast sessions" />
<NotificationItem icon={<Icon>shopping_cart</Icon>} title="Payment successfully completed" />
*/}
</Menu>
);
@ -145,7 +168,7 @@ function Navbar({absolute, light, isMini}: Props): JSX.Element
>
<Toolbar sx={navbarContainer}>
<MDBox color="inherit" mb={{xs: 1, md: 0}} sx={(theme) => navbarRow(theme, {isMini})}>
<Breadcrumbs icon="home" title={breadcrumbTitle} route={route} light={light} />
<QBreadcrumbs icon="home" title={breadcrumbTitle} route={route} light={light} />
<IconButton sx={navbarDesktopMenu} onClick={handleMiniSidenav} size="small" disableRipple>
<Icon fontSize="medium" sx={iconsStyle}>
{miniSidenav ? "menu_open" : "menu"}
@ -184,7 +207,7 @@ function Navbar({absolute, light, isMini}: Props): JSX.Element
sx={navbarIconButton}
onClick={handleOpenMenu}
>
<MDBadge badgeContent={9} color="error" size="xs" circular>
<MDBadge badgeContent={0} color="error" size="xs" circular>
<Icon sx={iconsStyle}>notifications</Icon>
</MDBadge>
</IconButton>

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
// @mui material components

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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 (
<Card>
<MDBox display="flex" justifyContent="space-between" pt={1} px={2}>
<MDBox
variant="gradient"
bgColor={color}
color={color === "light" ? "dark" : "white"}
coloredShadow={color}
borderRadius="xl"
display="flex"
justifyContent="center"
alignItems="center"
width="4rem"
height="4rem"
mt={-3}
>
<Icon fontSize="medium" color="inherit">
{icon}
</Icon>
</MDBox>
<MDBox textAlign="right" lineHeight={1.25}>
<MDTypography variant="button" fontWeight="bold" color="text">
{title}
</MDTypography>
</MDBox>
</MDBox>
<Divider />
<MDBox pb={2} px={2}>
<MDTypography component="p" variant="button" color="text" display="flex">
<MDTypography
component="span"
variant="button"
fontWeight="bold"
color={percentage.color}
>
{percentage.amount}
</MDTypography>
Click here to run the process called
{" "}
{title}
.
{percentage.label}
</MDTypography>
</MDBox>
</Card>
);
}
ProcessLinkCard.defaultProps = {
color: "info",
percentage: {
color: "success",
text: "",
label: "",
},
};
export default ProcessLinkCard;

View File

@ -0,0 +1,133 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
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));
};
const routeToLabel = (route: string): string =>
{
const label = ucFirst(route.replace(".", " ").replace("-", " ").replace("_", " ").replace(/([A-Z])/g, " $1"));
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 (
<MDBox mr={{xs: 0, xl: 8}}>
<MuiBreadcrumbs
sx={{
"& .MuiBreadcrumbs-separator": {
color: ({palette: {white, grey}}) => (light ? white.main : grey[600]),
},
}}
>
<Link to="/">
<MDTypography
component="span"
variant="body2"
color={light ? "white" : "dark"}
opacity={light ? 0.8 : 0.5}
sx={{lineHeight: 0}}
>
<Icon>{icon}</Icon>
</MDTypography>
</Link>
{fullRoutes.map((fullRoute: string) => (
<Link to={fullRoute} key={fullRoute}>
<MDTypography
component="span"
variant="button"
fontWeight="regular"
textTransform="capitalize"
color={light ? "white" : "dark"}
opacity={light ? 0.8 : 0.5}
sx={{lineHeight: 0}}
>
{routeToLabel(fullRoute.replace(/.*\//, ""))}
</MDTypography>
</Link>
))}
<MDTypography
variant="button"
fontWeight="regular"
textTransform="capitalize"
color={light ? "white" : "dark"}
sx={{lineHeight: 0}}
>
{routeToLabel(title)}
</MDTypography>
</MuiBreadcrumbs>
<MDTypography
fontWeight="bold"
textTransform="capitalize"
variant="h6"
color={light ? "white" : "dark"}
noWrap
>
{routeToLabel(title)}
</MDTypography>
</MDBox>
);
}
QBreadcrumbs.defaultProps = {
light: false,
};
export default QBreadcrumbs;

View File

@ -0,0 +1,126 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
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 (
<MDBox ml={3} mr={2} width={standardWidth}>
<Link to="create">
<MDButton variant="gradient" color="info" fullWidth startIcon={<Icon>add</Icon>}>
Create New
</MDButton>
</Link>
</MDBox>
);
}
export function QSaveButton(): JSX.Element
{
return (
<MDBox ml={3} width={standardWidth}>
<MDButton type="submit" variant="gradient" color="info" size="small" fullWidth startIcon={<Icon>save</Icon>}>
Save
</MDButton>
</MDBox>
);
}
interface QDeleteButtonProps
{
onClickHandler: any
}
export function QDeleteButton({onClickHandler}: QDeleteButtonProps): JSX.Element
{
return (
<MDBox ml={3} mr={3}>
<MDBox width={standardWidth}>
<MDButton variant="gradient" color="primary" size="small" onClick={onClickHandler} fullWidth startIcon={<Icon>delete</Icon>}>
Delete
</MDButton>
</MDBox>
</MDBox>
);
}
export function QEditButton(): JSX.Element
{
return (
<MDBox>
<Link to="edit">
<MDBox width={standardWidth}>
<MDButton variant="gradient" color="dark" size="small" fullWidth startIcon={<Icon>edit</Icon>}>
Edit
</MDButton>
</MDBox>
</Link>
</MDBox>
);
}
interface QActionsMenuButtonProps
{
isOpen: boolean;
onClickHandler: any;
}
export function QActionsMenuButton({isOpen, onClickHandler}: QActionsMenuButtonProps): JSX.Element
{
return (
<MDBox width={standardWidth}>
<MDButton
variant={isOpen ? "contained" : "outlined"}
color="dark"
onClick={onClickHandler}
fullWidth
>
actions&nbsp;
<Icon>keyboard_arrow_down</Icon>
</MDButton>
</MDBox>
);
}
interface QCancelButtonProps
{
onClickHandler: any;
}
export function QCancelButton({onClickHandler}: QCancelButtonProps): JSX.Element
{
return (
<MDBox ml="auto" width={standardWidth}>
<MDButton type="button" variant="outlined" color="dark" size="small" fullWidth startIcon={<Icon>cancel</Icon>} onClick={onClickHandler}>
Cancel
</MDButton>
</MDBox>
);
}

View File

@ -77,10 +77,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 +105,18 @@ function QDynamicForm(props: Props): JSX.Element
// todo? inputProps={{ autoComplete: "" }}
// todo? placeholder={password.placeholder}
// todo? success={!errors[fieldName] && touched[fieldName]}
return (
<Grid item xs={12} sm={6} key={fieldName}>
<QDynamicFormField
type={field.type}
label={field.label}
isEditable={field.isEditable}
name={fieldName}
value={values[fieldName]}
error={errors[fieldName] && touched[fieldName]}
bulkEditMode={bulkEditMode}
bulkEditSwitchChangeHandler={bulkEditSwitchChanged}
success={!errors[fieldName] && touched[fieldName]}
/>
</Grid>
);

View File

@ -83,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).",
});

View File

@ -36,24 +36,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 = () => (
<>
<Field {...rest} name={name} as={MDInput} variant="standard" label={label} fullWidth disabled={isDisabled} />
<Field {...rest} name={name} type={type} as={MDInput} variant="standard" label={label} InputLabelProps={inputLabelProps} fullWidth disabled={isDisabled} />
<MDBox mt={0.75}>
<MDTypography component="div" variant="caption" color="error" fontWeight="regular">
{!isDisabled && <ErrorMessage name={name} />}
{!isDisabled && <div className="fieldErrorMessage"><ErrorMessage name={name} /></div>}
</MDTypography>
</MDBox>
</>
@ -100,6 +109,7 @@ function QDynamicFormField({
QDynamicFormField.defaultProps = {
bulkEditMode: false,
isEditable: true,
bulkEditSwitchChangeHandler: () =>
{},
};

View File

@ -0,0 +1,84 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
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";
interface Props {
tableSections: any;
light?: boolean;
}
function QRecordSidebar({tableSections, light}: Props): JSX.Element
{
return (
<Card sx={{borderRadius: ({borders: {borderRadius}}) => borderRadius.lg, position: "sticky", top: "1%"}}>
<MDBox component="ul" display="flex" flexDirection="column" p={2} m={0} sx={{listStyle: "none"}}>
{
tableSections ? tableSections.map(({icon, label, name}: any, key: number) => (
<MDBox key={`section-${name}`} component="li" pt={key === 0 ? 0 : 1}>
<MDTypography
component="a"
href={`#${name}`}
variant="button"
fontWeight="regular"
sx={({
borders: {borderRadius}, functions: {pxToRem}, palette: {light}, transitions,
}: Theme) => ({
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,
},
})}
>
<MDBox mr={1.5} lineHeight={1} color="black">
<Icon fontSize="small">{icon}</Icon>
</MDBox>
{label}
</MDTypography>
</MDBox>
)) : null
}
</MDBox>
</Card>
);
}
QRecordSidebar.defaultProps = {
light: false,
};
export default QRecordSidebar;

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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 (
<BaseLayout>
<MDBox mt={4}>
<Grid container spacing={3}>
<Grid item xs={6} lg={6}>
<Grid container spacing={3}>
<Grid item xs={6} lg={6}>
<MDBox mb={3}>
<MDBox mb={3}>
<ReportsBarChart
color="info"
title="Packages Shipped"
description="Total outbound shipments"
date="Updated at 7:04 a.m. ET"
chart={reportsBarChartData}
/>
</MDBox>
</MDBox>
</Grid>
<Grid item xs={6} lg={6}>
<DefaultLineChart
title="Revenue"
description={(
<MDBox display="flex" justifyContent="space-between">
<MDBox display="flex" ml={-1}>
<MDBadgeDot color="info" size="sm" badgeContent="UPS" />
<MDBadgeDot color="dark" size="sm" badgeContent="FedEx" />
</MDBox>
<MDBox mt={-4} mr={-1} position="absolute" right="1.5rem" />
</MDBox>
)}
chart={demoLineChartData}
/>
</Grid>
</Grid>
</Grid>
<Grid item xs={6} lg={6}>
{
tables.length ? (
<MDBox mb={3}>
<Card id="basic-info" sx={{overflow: "visible"}}>
<MDBox p={3}>
<MDTypography variant="h5">Tables</MDTypography>
</MDBox>
<Grid container spacing={3} padding={3} paddingTop={0}>
{tables.map((table) => (
<Grid key={table.name} item xs={12} md={12} lg={6}>
<Link to={table.name}>
<MDBox mb={3}>
<MiniStatisticsCard
title={{fontWeight: "bold", text: table.label}}
count="17,013"
percentage={{color: "info", text: "total records"}}
icon={{color: "info", component: <Icon>{table.iconName || app.iconName}</Icon>}}
direction="right"
/>
</MDBox>
</Link>
</Grid>
))}
</Grid>
</Card>
</MDBox>
) : null
}
{
processes.length ? (
<MDBox mb={3}>
<Card id="basic-info" sx={{overflow: "visible"}}>
<MDBox p={3}>
<MDTypography variant="h5">Processes</MDTypography>
</MDBox>
<Grid container spacing={3} padding={3} paddingTop={3}>
{processes.map((process) => (
<Grid key={process.name} item xs={12} md={12} lg={6}>
<Link to={process.name}>
<ProcessLinkCard
icon={process.iconName || app.iconName}
title={process.label}
/>
</Link>
</Grid>
))}
</Grid>
</Card>
</MDBox>
) : null
}
{
childApps.length ? (
<MDBox mb={3}>
<Card id="basic-info" sx={{overflow: "visible"}}>
<Grid container spacing={3} padding={3} paddingTop={3}>
{childApps.map((childApp) => (
<Grid key={childApp.name} item xs={12} md={12} lg={6}>
<Link to={childApp.name}>
<DefaultInfoCard
icon={childApp.iconName || app.iconName}
title={childApp.label}
/>
</Link>
</Grid>
))}
</Grid>
</Card>
</MDBox>
) : null
}
</Grid>
</Grid>
</MDBox>
</BaseLayout>
);
}
AppHome.defaultProps = {
app: null,
};
export default AppHome;

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
// @mui material components
@ -22,15 +28,22 @@ 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";
import {useParams} from "react-router-dom";
function EntityCreate(): JSX.Element
interface Props
{
table?: QTableMetaData;
}
function EntityCreate({table}: Props): JSX.Element
{
return (
<BaseLayout>
<MDBox mt={4}>
<Grid container spacing={3}>
<Grid item xs={12} lg={12}>
<EntityForm />
<EntityForm table={table} />
</Grid>
</Grid>
</MDBox>
@ -38,4 +51,8 @@ function EntityCreate(): JSX.Element
);
}
EntityCreate.defaultProps = {
table: null,
};
export default EntityCreate;

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
// @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
<MDBox mb={3}>
<Grid container spacing={3}>
<Grid item xs={12}>
<EntityForm id={id} />
<EntityForm table={table} id={id} />
</Grid>
</Grid>
</MDBox>
@ -47,4 +60,8 @@ function EntityEdit(): JSX.Element
);
}
EntityEdit.defaultProps = {
table: null,
};
export default EntityEdit;

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
/* 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;
@ -98,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);
@ -117,6 +132,9 @@ 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 instance = useRef({timer: null});
const [, forceUpdate] = useReducer((x) => x + 1, 0);
@ -192,14 +210,15 @@ function EntityList({table}: Props): JSX.Element
const updateTable = () =>
{
setRows([]);
(async () =>
{
const newTableMetaData = await qController.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);
@ -209,8 +228,7 @@ function EntityList({table}: Props): JSX.Element
const count = await qController.count(tableName, qFilter);
setTotalRecords(count);
setButtonText(`new ${newTableMetaData.label}`);
setTableLabel(newTableMetaData.label);
setTableLabel(tableMetaData.label);
const columns = [] as GridColDef[];
@ -233,47 +251,65 @@ 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);
});
const sortedKeys = [...newTableMetaData.fields.keys()].sort();
rows.push(row);
});
const sortedKeys = [...tableMetaData.fields.keys()].sort();
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
@ -299,11 +335,49 @@ function EntityList({table}: Props): JSX.Element
setRowsPerPage(size);
};
const handleRowClick = (params: GridRowParams) =>
const navigate = useNavigate();
const handleRowClick = (params: GridRowParams, event: MuiEvent<React.MouseEvent>, 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);
@ -377,6 +451,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;
@ -476,7 +552,7 @@ function EntityList({table}: Props): JSX.Element
const bulkLoadClicked = () =>
{
document.location.href = `/processes/${tableName}.bulkInsert`;
navigate(`${tableName}.bulkInsert`);
};
const bulkEditClicked = () =>
@ -486,7 +562,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 = () =>
@ -496,12 +572,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 (
@ -518,23 +603,28 @@ 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 (
<LinearProgress color="info" />
);
}
// 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<HTMLElement>) =>
function gtcMouseDown(e: React.MouseEvent<HTMLDivElement>)
{
setBulkActionsMenuAnchor(event.currentTarget);
};
const closeBulkActionsMenu = () =>
{
setBulkActionsMenuAnchor(null);
};
console.log(e.target);
}
return (
<GridToolbarContainer>
<GridToolbarContainer
onMouseDown={(e) => gtcMouseDown(e)}
>
<div>
<Button
id="refresh-button"
@ -551,29 +641,6 @@ function EntityList({table}: Props): JSX.Element
<ExportMenuItem format="csv" />
<ExportMenuItem format="xlsx" />
</GridToolbarExportContainer>
<div>
<Button
id="bulk-actions-button"
onClick={openBulkActionsMenu}
aria-controls={bulkActionsMenuOpen ? "basic-menu" : undefined}
aria-haspopup="true"
aria-expanded={bulkActionsMenuOpen ? "true" : undefined}
startIcon={<Icon>table_rows</Icon>}
>
Bulk Actions
</Button>
<Menu
id="bulk-actions-menu"
open={bulkActionsMenuOpen}
anchorEl={bulkActionsMenuAnchor}
onClose={closeBulkActionsMenu}
MenuListProps={{"aria-labelledby": "bulk-actions-button"}}
>
<MenuItem onClick={bulkLoadClicked}>Bulk Load</MenuItem>
<MenuItem onClick={bulkEditClicked}>Bulk Edit</MenuItem>
<MenuItem onClick={bulkDeleteClicked}>Bulk Delete</MenuItem>
</Menu>
</div>
<div>
{
selectFullFilterState === "checked" && (
@ -621,29 +688,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
>
<MenuItem onClick={bulkLoadClicked}>Bulk Load</MenuItem>
<MenuItem onClick={bulkEditClicked}>Bulk Edit</MenuItem>
<MenuItem onClick={bulkDeleteClicked}>Bulk Delete</MenuItem>
<MenuItem divider />
{tableProcesses.map((process) => (
<MenuItem key={process.name}>
<Link href={`/processes/${process.name}${getRecordsQueryString()}`}>{process.label}</Link>
</MenuItem>
<MenuItem key={process.name} onClick={() => processClicked(process)}>{process.label}</MenuItem>
))}
</Menu>
);
useEffect(() =>
{
setLoading(true);
updateTable();
}, [pageNumber, rowsPerPage, tableState, columnSortModel, filterModel]);
useEffect(() =>
{
document.documentElement.scrollTop = 0;
document.scrollingElement.scrollTop = 0;
}, [pageNumber, rowsPerPage]);
return (
<DashboardLayout>
<Navbar />
@ -668,39 +744,27 @@ function EntityList({table}: Props): JSX.Element
<MDAlert color="success" dismissible>
{`${tableLabel} successfully deleted`}
</MDAlert>
) : ("")
) : null
}
<MDBox display="flex" justifyContent="space-between" alignItems="flex-start" mb={2}>
{buttonText ? (
<Link href={`/${tableName}/create`}>
<MDButton variant="gradient" color="info">
{
buttonText
}
</MDButton>
</Link>
) : (
""
)}
<MDBox display="flex" justifyContent="flex-end" alignItems="flex-start" mb={2}>
<MDBox display="flex">
<MDBox display="flex" width="150px">
{tableProcesses.length > 0 && (
<MDButton
variant={actionsMenu ? "contained" : "outlined"}
color="dark"
onClick={openActionsMenu}
>
actions&nbsp;
<Icon>keyboard_arrow_down</Icon>
</MDButton>
<QActionsMenuButton isOpen={actionsMenu} onClickHandler={openActionsMenu} />
)}
{renderActionsMenu}
</MDBox>
<QCreateNewButton />
</MDBox>
<Card>
{/* with these turned on, the toolbar & pagination controls become very flaky...
onMouseDown={(e) => handleGridMouseDown(e)} onDoubleClick={(e) => handleGridDoubleClick(e)} */}
<MDBox height="100%">
<DataGridPro
components={{Toolbar: CustomToolbar, Pagination: CustomPagination}}
components={{Toolbar: CustomToolbar, Pagination: CustomPagination, LoadingOverlay: Loading}}
pinnedColumns={{left: ["__check__", "id"]}}
pagination
paginationMode="server"
sortingMode="server"
@ -715,7 +779,7 @@ function EntityList({table}: Props): JSX.Element
rowCount={totalRecords}
onPageSizeChange={handleRowsPerPageChange}
onRowClick={handleRowClick}
density="compact"
density="standard"
loading={loading}
onFilterModelChange={handleFilterChange}
columnVisibilityModel={columnVisibilityModel}

View File

@ -1,23 +1,31 @@
/**
=========================================================
* 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 <https://www.gnu.org/licenses/>.
*/
// 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,11 +41,17 @@ 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";
const qController = QClient.getInstance();
@ -46,19 +59,28 @@ const qController = QClient.getInstance();
interface Props
{
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 [sectionFieldElements, setSectionFieldElements] = useState(null as Map<string, JSX.Element[]>);
const [t1Section, setT1Section] = useState(null as JSX.Element);
const [open, setOpen] = useState(false);
const [tableMetaData, setTableMetaData] = useState(null);
const [record, setRecord] = useState(null as QRecord);
const [tableSections, setTableSections] = useState(null as any);
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);
@ -70,28 +92,66 @@ 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(
<MDBox key={tableMetaData.primaryKeyField} display="flex" py={1} pr={2}>
<MDTypography variant="button" fontWeight="bold" textTransform="capitalize">
{tableMetaData.primaryKeyField}
/////////////////////////////////////////////////
// define the sections, e.g., for the left-bar //
/////////////////////////////////////////////////
const tableSections = QTableUtils.getSectionsForRecordSidebar(tableMetaData);
setTableSections(tableSections);
////////////////////////////////////////////////////
// make elements with the values for each section //
////////////////////////////////////////////////////
const sectionFieldElements = new Map();
for (let i = 0; i < tableSections.length; i++)
{
const section = tableSections[i];
sectionFieldElements.set(
section.name,
<MDBox key={section.name} display="flex" flexDirection="column" py={1} pr={2}>
{
section.fieldNames.map((fieldName: string) => (
<MDBox key={fieldName} flexDirection="row" pr={2}>
<MDTypography variant="button" fontWeight="bold">
{tableMetaData.fields.get(fieldName).label}
: &nbsp;
</MDTypography>
<MDTypography variant="button" fontWeight="regular" color="text">
&nbsp;
{id}
{QValueUtils.getDisplayValue(tableMetaData.fields.get(fieldName), record)}
</MDTypography>
</MDBox>
))
}
</MDBox>,
);
const sortedKeys = [...foundRecord.values.keys()].sort();
if (section.tier === "T1")
{
setT1Section(sectionFieldElements.get(section.name));
}
}
setSectionFieldElements(sectionFieldElements);
// todo - delete this
const sortedKeys = [...record.values.keys()].sort();
sortedKeys.forEach((key) =>
{
if (key !== tableMetaData.primaryKeyField)
@ -104,14 +164,14 @@ function ViewContents({id}: Props): JSX.Element
</MDTypography>
<MDTypography variant="button" fontWeight="regular" color="text">
&nbsp;
{foundRecord.values.get(key)}
{QValueUtils.getDisplayValue(tableMetaData.fields.get(key), record)}
</MDTypography>
</MDBox>,
);
}
});
setNameValues(nameValues);
forceUpdate();
})();
}
@ -134,32 +194,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 = (
<Menu
anchorEl={actionsMenu}
anchorOrigin={{
vertical: "bottom",
horizontal: "left",
horizontal: "right",
}}
transformOrigin={{
vertical: "top",
horizontal: "left",
horizontal: "right",
}}
open={Boolean(actionsMenu)}
onClose={closeActionsMenu}
keepMounted
>
{tableProcesses.map((process) => (
<MenuItem key={process.name}>
<Link href={`/processes/${process.name}?recordIds=${id}`}>{process.label}</Link>
<MenuItem onClick={() => navigate("edit")}>Edit</MenuItem>
<MenuItem onClick={() =>
{
setActionsMenu(null);
handleClickConfirmOpen();
}}
>
Delete
</MenuItem>
<MenuItem divider />
{tableProcesses.map((process) => (
<MenuItem key={process.name} onClick={() => processClicked(process)}>{process.label}</MenuItem>
))}
</Menu>
);
@ -179,45 +252,63 @@ function ViewContents({id}: Props): JSX.Element
</MDAlert>
) : ("")
}
<Card id="basic-info" sx={{overflow: "visible"}}>
<MDBox p={3}>
<MDBox display="flex" justifyContent="space-between">
<Grid container spacing={3}>
<Grid item xs={12} lg={3}>
<QRecordSidebar tableSections={tableSections} />
</Grid>
<Grid item xs={12} lg={9}>
<Grid container spacing={3}>
<Grid item xs={12} mb={3}>
<Card>
<MDBox display="flex" p={3} pb={1}>
<MDBox mr={1.5}>
<Avatar sx={{bgcolor: "rgb(26, 115, 232)"}}>
<Icon>
{tableMetaData?.iconName}
</Icon>
</Avatar>
</MDBox>
<MDBox display="flex" justifyContent="space-between" width="100%" alignItems="center">
<MDTypography variant="h5">
Viewing
{" "}
{tableMetaData?.label}
{" "}
(
{id}
)
{tableMetaData && record ? `Viewing ${tableMetaData?.label}: ${record?.recordLabel}` : ""}
</MDTypography>
{tableProcesses.length > 0 && (
<MDButton
variant={actionsMenu ? "contained" : "outlined"}
color="dark"
onClick={openActionsMenu}
>
actions&nbsp;
<Icon>keyboard_arrow_down</Icon>
</MDButton>
<QActionsMenuButton isOpen={actionsMenu} onClickHandler={openActionsMenu} />
)}
{renderActionsMenu}
</MDBox>
</MDBox>
<MDBox p={3}>{nameValues}</MDBox>
<MDBox component="form" pb={3} px={3}>
<Grid key="tres" container spacing={3}>
<MDBox ml="auto" mr={3}>
<MDButton
variant="gradient"
color="primary"
size="small"
onClick={handleClickConfirmOpen}
>
delete
{" "}
{tableMetaData?.label}
</MDButton>
{t1Section ? (<MDBox p={3} pt={0}>{t1Section}</MDBox>) : null}
</Card>
</Grid>
</Grid>
{tableSections && sectionFieldElements ? tableSections.map(({
icon, label, name, fieldNames, tier,
}: any) => (tier !== "T1"
? (
<MDBox mb={3} key={name}>
<Card key={name} id={name} sx={{overflow: "visible"}}>
<MDTypography variant="h5" p={3} pb={1}>
{label}
</MDTypography>
<MDBox p={3} pt={0} flexDirection="column">{sectionFieldElements.get(name)}</MDBox>
</Card>
</MDBox>
) : null)) : null}
<MDBox component="form" p={3}>
<Grid container justifyContent="flex-end" spacing={3}>
<QDeleteButton onClickHandler={handleClickConfirmOpen} />
<QEditButton />
</Grid>
</MDBox>
</Grid>
</Grid>
{/* Delete confirmation Dialog */}
<Dialog
open={open}
onClose={handleClickConfirmClose}
@ -238,18 +329,12 @@ function ViewContents({id}: Props): JSX.Element
</DialogActions>
</Dialog>
</MDBox>
<MDBox>
<MDButton variant="gradient" color="dark" size="small">
<Link href={editPath}>
{`edit ${tableMetaData?.label}`}
</Link>
</MDButton>
</MDBox>
</Grid>
</MDBox>
</Card>
</MDBox>
);
}
ViewContents.defaultProps = {
table: null,
};
export default ViewContents;

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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 (
<BaseLayout>
<MDBox mt={4}>
<Grid container spacing={3}>
<Grid item xs={12} lg={12}>
<MDBox mb={3}>
<Grid container spacing={3}>
<MDBox>
<Grid container>
<Grid item xs={12}>
<MDBox mb={3}>
<ViewContents id={id} />
</Grid>
</Grid>
</MDBox>
</Grid>
</Grid>
@ -48,4 +57,8 @@ function EntityView(): JSX.Element
);
}
EntityView.defaultProps = {
table: null,
};
export default EntityView;

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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 (
<DashboardLayout>
<DashboardNavbar />
<Navbar />
<MDBox py={3} mb={20}>
<Grid
container
@ -788,4 +800,8 @@ function ProcessRun(): JSX.Element
);
}
ProcessRun.defaultProps = {
process: null,
};
export default ProcessRun;

View File

@ -19,6 +19,88 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
.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
{
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;
}
/* hide the selected row count (we show our own) */
.MuiDataGrid-selectedRowCount
{
visibility: hidden !important;
}
/* move the green check / red x down to align with the calendar icon */
.MuiFormControl-root
{
background-position-y: 1.4rem;
}

View File

@ -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);
}

View File

@ -0,0 +1,54 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData";
/*******************************************************************************
** Utility class for working with QQQ Tables
**
*******************************************************************************/
class QTableUtils
{
public static getSectionsForRecordSidebar(tableMetaData: QTableMetaData): any
{
const tableSections = [];
if (tableMetaData.sections)
{
for (let i = 0; i < tableMetaData.sections.length; i++)
{
const section = tableMetaData.sections[i];
tableSections.push({
icon: section.iconName, label: section.label, name: section.name, fieldNames: section.fieldNames, tier: section.tier,
});
}
}
else
{
tableSections.push({
icon: "description", label: "All Fields", name: "allFields", fieldNames: [...tableMetaData.fields.keys()],
});
}
return (tableSections);
}
}
export default QTableUtils;

View File

@ -0,0 +1,59 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
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)
{
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;