mirror of
https://github.com/Kingsrook/qqq-frontend-material-dashboard.git
synced 2025-07-17 12:50:43 +00:00
QQQ-30: got rid of 'prettier', first pass at eslint configuration pointing only to qqq specific files, reformated all qqq files
This commit is contained in:
@ -3,7 +3,20 @@
|
||||
"browser": true,
|
||||
"es2021": true
|
||||
},
|
||||
"extends": ["plugin:react/recommended", "airbnb", "prettier"],
|
||||
"extends": [
|
||||
"plugin:react/recommended",
|
||||
"airbnb"
|
||||
],
|
||||
"globals": {
|
||||
"JSX": true
|
||||
},
|
||||
"ignorePatterns": [
|
||||
"src/assets",
|
||||
"src/components",
|
||||
"src/context",
|
||||
"src/examples",
|
||||
"src/layouts"
|
||||
],
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"ecmaFeatures": {
|
||||
@ -12,37 +25,68 @@
|
||||
"ecmaVersion": "latest",
|
||||
"sourceType": "module"
|
||||
},
|
||||
"plugins": ["react", "@typescript-eslint", "prettier"],
|
||||
"plugins": [
|
||||
"react",
|
||||
"@typescript-eslint"
|
||||
],
|
||||
"rules": {
|
||||
"prettier/prettier": [
|
||||
"brace-style": [
|
||||
2,
|
||||
"allman"
|
||||
],
|
||||
"indent": [
|
||||
"error",
|
||||
3
|
||||
],
|
||||
"import/extensions": [
|
||||
"error",
|
||||
"ignorePackages",
|
||||
{
|
||||
"endOfLine": "auto"
|
||||
"ts": "never",
|
||||
"tsx": "never",
|
||||
"js": "never"
|
||||
}
|
||||
],
|
||||
"import/no-extraneous-dependencies": [
|
||||
"error",
|
||||
{
|
||||
"devDependencies": true
|
||||
}
|
||||
],
|
||||
"max-len": "off",
|
||||
"no-console": "off",
|
||||
"react/prop-types": "off",
|
||||
"no-shadow": "off",
|
||||
"no-unused-vars": "off",
|
||||
"no-plusplus": "off",
|
||||
"spaced-comment": "off",
|
||||
"object-shorthand": "off",
|
||||
"react/prop-types": "off",
|
||||
"react/jsx-filename-extension": [
|
||||
"warn",
|
||||
{
|
||||
"extensions": [
|
||||
".tsx"
|
||||
]
|
||||
}
|
||||
],
|
||||
"react/jsx-indent": [
|
||||
"error",
|
||||
3
|
||||
],
|
||||
"react/jsx-indent-props": [
|
||||
"error",
|
||||
3
|
||||
],
|
||||
"react/jsx-props-no-spreading": "off",
|
||||
"react/react-in-jsx-scope": "off",
|
||||
"import/extensions": [
|
||||
"quotes": [
|
||||
"error",
|
||||
"ignorePackages",
|
||||
{ "ts": "never", "tsx": "never", "js": "never" }
|
||||
],
|
||||
"react/jsx-filename-extension": ["warn", { "extensions": [".tsx"] }],
|
||||
"import/no-extraneous-dependencies": ["error", { "devDependencies": true }]
|
||||
"double"
|
||||
]
|
||||
},
|
||||
"settings": {
|
||||
"import/resolver": {
|
||||
"typescript": {}
|
||||
}
|
||||
},
|
||||
"globals": {
|
||||
"JSX": true
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +0,0 @@
|
||||
{
|
||||
"printWidth": 100,
|
||||
"trailingComma": "es5",
|
||||
"semi": true,
|
||||
"singleQuote": false,
|
||||
"endOfLine": "auto"
|
||||
}
|
102
package-lock.json
generated
102
package-lock.json
generated
@ -63,14 +63,11 @@
|
||||
"@typescript-eslint/parser": "5.10.2",
|
||||
"eslint": "8.8.0",
|
||||
"eslint-config-airbnb": "19.0.4",
|
||||
"eslint-config-prettier": "8.3.0",
|
||||
"eslint-import-resolver-typescript": "2.5.0",
|
||||
"eslint-plugin-import": "2.25.4",
|
||||
"eslint-plugin-jsx-a11y": "6.5.1",
|
||||
"eslint-plugin-prettier": "4.0.0",
|
||||
"eslint-plugin-react": "7.28.0",
|
||||
"eslint-plugin-react-hooks": "4.3.0",
|
||||
"prettier": "2.5.1",
|
||||
"typescript": "^4.7.3"
|
||||
}
|
||||
},
|
||||
@ -7533,18 +7530,6 @@
|
||||
"semver": "bin/semver.js"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-config-prettier": {
|
||||
"version": "8.3.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.3.0.tgz",
|
||||
"integrity": "sha512-BgZuLUSeKzvlL/VUjx/Yb787VQ26RU3gGjA3iiFvdsp/2bMfVIWUVP7tjxtjS0e+HP409cPlPvNkQloz8C91ew==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"eslint-config-prettier": "bin/cli.js"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"eslint": ">=7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-config-react-app": {
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/eslint-config-react-app/-/eslint-config-react-app-7.0.1.tgz",
|
||||
@ -7756,27 +7741,6 @@
|
||||
"node": ">=6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-prettier": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-4.0.0.tgz",
|
||||
"integrity": "sha512-98MqmCJ7vJodoQK359bqQWaxOE0CS8paAz/GgjaZLyex4TTk3g9HugoO89EqWCrFiOqn9EVvcoo7gZzONCWVwQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"prettier-linter-helpers": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"eslint": ">=7.28.0",
|
||||
"prettier": ">=2.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"eslint-config-prettier": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-react": {
|
||||
"version": "7.28.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.28.0.tgz",
|
||||
@ -8363,12 +8327,6 @@
|
||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
|
||||
},
|
||||
"node_modules/fast-diff": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz",
|
||||
"integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/fast-glob": {
|
||||
"version": "3.2.11",
|
||||
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz",
|
||||
@ -13484,30 +13442,6 @@
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/prettier": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.5.1.tgz",
|
||||
"integrity": "sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"prettier": "bin-prettier.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/prettier-linter-helpers": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz",
|
||||
"integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"fast-diff": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/pretty-bytes": {
|
||||
"version": "5.6.0",
|
||||
"resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz",
|
||||
@ -22393,12 +22327,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"eslint-config-prettier": {
|
||||
"version": "8.3.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.3.0.tgz",
|
||||
"integrity": "sha512-BgZuLUSeKzvlL/VUjx/Yb787VQ26RU3gGjA3iiFvdsp/2bMfVIWUVP7tjxtjS0e+HP409cPlPvNkQloz8C91ew==",
|
||||
"dev": true
|
||||
},
|
||||
"eslint-config-react-app": {
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/eslint-config-react-app/-/eslint-config-react-app-7.0.1.tgz",
|
||||
@ -22561,15 +22489,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"eslint-plugin-prettier": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-4.0.0.tgz",
|
||||
"integrity": "sha512-98MqmCJ7vJodoQK359bqQWaxOE0CS8paAz/GgjaZLyex4TTk3g9HugoO89EqWCrFiOqn9EVvcoo7gZzONCWVwQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"prettier-linter-helpers": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"eslint-plugin-react": {
|
||||
"version": "7.28.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.28.0.tgz",
|
||||
@ -22955,12 +22874,6 @@
|
||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
|
||||
},
|
||||
"fast-diff": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz",
|
||||
"integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==",
|
||||
"dev": true
|
||||
},
|
||||
"fast-glob": {
|
||||
"version": "3.2.11",
|
||||
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz",
|
||||
@ -26510,21 +26423,6 @@
|
||||
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
|
||||
"integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="
|
||||
},
|
||||
"prettier": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.5.1.tgz",
|
||||
"integrity": "sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg==",
|
||||
"dev": true
|
||||
},
|
||||
"prettier-linter-helpers": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz",
|
||||
"integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"fast-diff": "^1.1.2"
|
||||
}
|
||||
},
|
||||
"pretty-bytes": {
|
||||
"version": "5.6.0",
|
||||
"resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz",
|
||||
|
@ -87,14 +87,11 @@
|
||||
"@typescript-eslint/parser": "5.10.2",
|
||||
"eslint": "8.8.0",
|
||||
"eslint-config-airbnb": "19.0.4",
|
||||
"eslint-config-prettier": "8.3.0",
|
||||
"eslint-import-resolver-typescript": "2.5.0",
|
||||
"eslint-plugin-import": "2.25.4",
|
||||
"eslint-plugin-jsx-a11y": "6.5.1",
|
||||
"eslint-plugin-prettier": "4.0.0",
|
||||
"eslint-plugin-react": "7.28.0",
|
||||
"eslint-plugin-react-hooks": "4.3.0",
|
||||
"prettier": "2.5.1",
|
||||
"typescript": "^4.7.3"
|
||||
},
|
||||
"main": "qqq-frontend-material-dashboard.js",
|
||||
|
241
src/App.tsx
241
src/App.tsx
@ -14,16 +14,18 @@ Coded by www.creative-tim.com
|
||||
*/
|
||||
|
||||
import React, {
|
||||
useState,
|
||||
useEffect,
|
||||
JSXElementConstructor,
|
||||
Key,
|
||||
ReactElement,
|
||||
useReducer,
|
||||
useState,
|
||||
useEffect,
|
||||
JSXElementConstructor,
|
||||
Key,
|
||||
ReactElement,
|
||||
useReducer,
|
||||
} from "react";
|
||||
|
||||
// react-router components
|
||||
import { Routes, Route, Navigate, useLocation } from "react-router-dom";
|
||||
import {
|
||||
Routes, Route, Navigate, useLocation,
|
||||
} from "react-router-dom";
|
||||
|
||||
// @mui material components
|
||||
import { ThemeProvider } from "@mui/material/styles";
|
||||
@ -58,128 +60,137 @@ import EntityView from "./qqq/pages/entity-view";
|
||||
import EntityEdit from "./qqq/pages/entity-edit";
|
||||
import ProcessRun from "./qqq/pages/process-run";
|
||||
|
||||
export default function App() {
|
||||
const [controller, dispatch] = useMaterialUIController();
|
||||
const {
|
||||
miniSidenav,
|
||||
direction,
|
||||
layout,
|
||||
openConfigurator,
|
||||
sidenavColor,
|
||||
transparentSidenav,
|
||||
whiteSidenav,
|
||||
darkMode,
|
||||
} = controller;
|
||||
const [onMouseEnter, setOnMouseEnter] = useState(false);
|
||||
const { pathname } = useLocation();
|
||||
export default function App()
|
||||
{
|
||||
const [controller, dispatch] = useMaterialUIController();
|
||||
const {
|
||||
miniSidenav,
|
||||
direction,
|
||||
layout,
|
||||
openConfigurator,
|
||||
sidenavColor,
|
||||
transparentSidenav,
|
||||
whiteSidenav,
|
||||
darkMode,
|
||||
} = controller;
|
||||
const [onMouseEnter, setOnMouseEnter] = useState(false);
|
||||
const { pathname } = useLocation();
|
||||
|
||||
// Open sidenav when mouse enter on mini sidenav
|
||||
const handleOnMouseEnter = () => {
|
||||
if (miniSidenav && !onMouseEnter) {
|
||||
setMiniSidenav(dispatch, false);
|
||||
setOnMouseEnter(true);
|
||||
}
|
||||
};
|
||||
// Open sidenav when mouse enter on mini sidenav
|
||||
const handleOnMouseEnter = () =>
|
||||
{
|
||||
if (miniSidenav && !onMouseEnter)
|
||||
{
|
||||
setMiniSidenav(dispatch, false);
|
||||
setOnMouseEnter(true);
|
||||
}
|
||||
};
|
||||
|
||||
// Close sidenav when mouse leave mini sidenav
|
||||
const handleOnMouseLeave = () => {
|
||||
if (onMouseEnter) {
|
||||
setMiniSidenav(dispatch, true);
|
||||
setOnMouseEnter(false);
|
||||
}
|
||||
};
|
||||
// Close sidenav when mouse leave mini sidenav
|
||||
const handleOnMouseLeave = () =>
|
||||
{
|
||||
if (onMouseEnter)
|
||||
{
|
||||
setMiniSidenav(dispatch, true);
|
||||
setOnMouseEnter(false);
|
||||
}
|
||||
};
|
||||
|
||||
// Change the openConfigurator state
|
||||
const handleConfiguratorOpen = () => setOpenConfigurator(dispatch, !openConfigurator);
|
||||
// Change the openConfigurator state
|
||||
const handleConfiguratorOpen = () => setOpenConfigurator(dispatch, !openConfigurator);
|
||||
|
||||
// Setting the dir attribute for the body element
|
||||
useEffect(() => {
|
||||
document.body.setAttribute("dir", direction);
|
||||
}, [direction]);
|
||||
// Setting the dir attribute for the body element
|
||||
useEffect(() =>
|
||||
{
|
||||
document.body.setAttribute("dir", direction);
|
||||
}, [direction]);
|
||||
|
||||
// Setting page scroll to 0 when changing the route
|
||||
useEffect(() => {
|
||||
document.documentElement.scrollTop = 0;
|
||||
document.scrollingElement.scrollTop = 0;
|
||||
}, [pathname]);
|
||||
// Setting page scroll to 0 when changing the route
|
||||
useEffect(() =>
|
||||
{
|
||||
document.documentElement.scrollTop = 0;
|
||||
document.scrollingElement.scrollTop = 0;
|
||||
}, [pathname]);
|
||||
|
||||
const getRoutes = (allRoutes: any[]): any =>
|
||||
allRoutes.map(
|
||||
const getRoutes = (allRoutes: any[]): any => allRoutes.map(
|
||||
(route: {
|
||||
collapse: any;
|
||||
route: string;
|
||||
component: ReactElement<any, string | JSXElementConstructor<any>>;
|
||||
key: Key;
|
||||
}) => {
|
||||
if (route.collapse) {
|
||||
return getRoutes(route.collapse);
|
||||
}
|
||||
}) =>
|
||||
{
|
||||
if (route.collapse)
|
||||
{
|
||||
return getRoutes(route.collapse);
|
||||
}
|
||||
|
||||
if (route.route) {
|
||||
return <Route path={route.route} element={route.component} key={route.key} />;
|
||||
}
|
||||
if (route.route)
|
||||
{
|
||||
return <Route path={route.route} element={route.component} key={route.key} />;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
);
|
||||
return null;
|
||||
},
|
||||
);
|
||||
|
||||
const configsButton = (
|
||||
<MDBox
|
||||
display="flex"
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
width="3.25rem"
|
||||
height="3.25rem"
|
||||
bgColor="white"
|
||||
shadow="sm"
|
||||
borderRadius="50%"
|
||||
position="fixed"
|
||||
right="2rem"
|
||||
bottom="2rem"
|
||||
zIndex={99}
|
||||
color="dark"
|
||||
sx={{ cursor: "pointer" }}
|
||||
onClick={handleConfiguratorOpen}
|
||||
>
|
||||
<Icon fontSize="small" color="inherit">
|
||||
settings
|
||||
</Icon>
|
||||
</MDBox>
|
||||
);
|
||||
const configsButton = (
|
||||
<MDBox
|
||||
display="flex"
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
width="3.25rem"
|
||||
height="3.25rem"
|
||||
bgColor="white"
|
||||
shadow="sm"
|
||||
borderRadius="50%"
|
||||
position="fixed"
|
||||
right="2rem"
|
||||
bottom="2rem"
|
||||
zIndex={99}
|
||||
color="dark"
|
||||
sx={{ cursor: "pointer" }}
|
||||
onClick={handleConfiguratorOpen}
|
||||
>
|
||||
<Icon fontSize="small" color="inherit">
|
||||
settings
|
||||
</Icon>
|
||||
</MDBox>
|
||||
);
|
||||
|
||||
const entityListElement = <EntityList />;
|
||||
const entityCreateElement = <EntityCreate />;
|
||||
const entityViewElement = <EntityView />;
|
||||
const entityEditElement = <EntityEdit />;
|
||||
const processRunElement = <ProcessRun />;
|
||||
const entityListElement = <EntityList />;
|
||||
const entityCreateElement = <EntityCreate />;
|
||||
const entityViewElement = <EntityView />;
|
||||
const entityEditElement = <EntityEdit />;
|
||||
const processRunElement = <ProcessRun />;
|
||||
|
||||
return (
|
||||
<ThemeProvider theme={darkMode ? themeDark : theme}>
|
||||
<CssBaseline />
|
||||
{layout === "dashboard" && (
|
||||
<>
|
||||
<Sidenav
|
||||
color={sidenavColor}
|
||||
brand={(transparentSidenav && !darkMode) || whiteSidenav ? brandDark : brandWhite}
|
||||
brandName="Material Dashboard PRO"
|
||||
routes={routes}
|
||||
onMouseEnter={handleOnMouseEnter}
|
||||
onMouseLeave={handleOnMouseLeave}
|
||||
/>
|
||||
<Configurator />
|
||||
{configsButton}
|
||||
</>
|
||||
)}
|
||||
{layout === "vr" && <Configurator />}
|
||||
<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)}
|
||||
</Routes>
|
||||
</ThemeProvider>
|
||||
);
|
||||
return (
|
||||
<ThemeProvider theme={darkMode ? themeDark : theme}>
|
||||
<CssBaseline />
|
||||
{layout === "dashboard" && (
|
||||
<>
|
||||
<Sidenav
|
||||
color={sidenavColor}
|
||||
brand={(transparentSidenav && !darkMode) || whiteSidenav ? brandDark : brandWhite}
|
||||
brandName="Material Dashboard PRO"
|
||||
routes={routes}
|
||||
onMouseEnter={handleOnMouseEnter}
|
||||
onMouseLeave={handleOnMouseLeave}
|
||||
/>
|
||||
<Configurator />
|
||||
{configsButton}
|
||||
</>
|
||||
)}
|
||||
{layout === "vr" && <Configurator />}
|
||||
<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)}
|
||||
</Routes>
|
||||
</ThemeProvider>
|
||||
);
|
||||
}
|
||||
|
@ -21,12 +21,12 @@ import App from "App";
|
||||
import { MaterialUIControllerProvider } from "context";
|
||||
|
||||
ReactDOM.render(
|
||||
<BrowserRouter>
|
||||
<MaterialUIControllerProvider>
|
||||
<App />
|
||||
</MaterialUIControllerProvider>
|
||||
</BrowserRouter>,
|
||||
document.getElementById("root")
|
||||
<BrowserRouter>
|
||||
<MaterialUIControllerProvider>
|
||||
<App />
|
||||
</MaterialUIControllerProvider>
|
||||
</BrowserRouter>,
|
||||
document.getElementById("root"),
|
||||
);
|
||||
|
||||
export * from "components/MDButton";
|
||||
|
@ -13,7 +13,7 @@ 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
/**
|
||||
All of the routes for the Material Kit 2 PRO React React are added here,
|
||||
You can add a new route, customize the routes and delete the routes here.
|
||||
|
||||
@ -39,235 +39,235 @@ Coded by www.creative-tim.com
|
||||
import Icon from "@mui/material/Icon";
|
||||
|
||||
const pageRoutes = [
|
||||
{
|
||||
name: "pages",
|
||||
columns: 3,
|
||||
rowsPerColumn: 2,
|
||||
collapse: [
|
||||
{
|
||||
name: "dashboards",
|
||||
icon: <Icon>dashboard</Icon>,
|
||||
collapse: [
|
||||
{
|
||||
name: "analytics",
|
||||
route: "/dashboards/analytics",
|
||||
},
|
||||
{
|
||||
name: "sales",
|
||||
route: "/dashboards/sales",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "users",
|
||||
icon: <Icon>people</Icon>,
|
||||
collapse: [
|
||||
{
|
||||
name: "reports",
|
||||
route: "/pages/users/reports",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "extra",
|
||||
icon: <Icon>queue_play_next</Icon>,
|
||||
collapse: [
|
||||
{
|
||||
name: "pricing page",
|
||||
route: "/pages/pricing-page",
|
||||
},
|
||||
{ name: "RTL", route: "/pages/rtl" },
|
||||
{ name: "widgets", route: "/pages/widgets" },
|
||||
{ name: "charts", route: "/pages/charts" },
|
||||
{
|
||||
name: "notfications",
|
||||
route: "/pages/notifications",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "projects",
|
||||
icon: <Icon>precision_manufacturing</Icon>,
|
||||
collapse: [
|
||||
{
|
||||
name: "timeline",
|
||||
route: "/pages/projects/timeline",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "account",
|
||||
icon: <Icon>account_balance</Icon>,
|
||||
collapse: [
|
||||
{
|
||||
name: "settings",
|
||||
route: "/pages/account/setting",
|
||||
},
|
||||
{
|
||||
name: "billing",
|
||||
route: "/pages/account/billing",
|
||||
},
|
||||
{
|
||||
name: "invoice",
|
||||
route: "/pages/account/invoice",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "profile",
|
||||
icon: <Icon>badge</Icon>,
|
||||
collapse: [
|
||||
{
|
||||
name: "profile overview",
|
||||
route: "/pages/profile/profile-overview",
|
||||
},
|
||||
{
|
||||
name: "all projects",
|
||||
route: "/pages/profile/all-projects",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "authenticaton",
|
||||
collapse: [
|
||||
{
|
||||
name: "sign in",
|
||||
dropdown: true,
|
||||
icon: <Icon>login</Icon>,
|
||||
collapse: [
|
||||
{
|
||||
name: "basic",
|
||||
route: "/authentication/sign-in/basic",
|
||||
},
|
||||
{
|
||||
name: "cover",
|
||||
route: "/authentication/sign-in/cover",
|
||||
},
|
||||
{
|
||||
name: "illustration",
|
||||
route: "/authentication/sign-in/illustration",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "sign up",
|
||||
dropdown: true,
|
||||
icon: <Icon>assignment</Icon>,
|
||||
collapse: [
|
||||
{
|
||||
name: "cover",
|
||||
route: "/authentication/sign-up/cover",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "reset password",
|
||||
dropdown: true,
|
||||
icon: <Icon>restart_alt</Icon>,
|
||||
collapse: [
|
||||
{
|
||||
name: "cover",
|
||||
route: "/authentication/reset-password/cover",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "application",
|
||||
collapse: [
|
||||
{
|
||||
name: "kanban",
|
||||
route: "/applications/kanban",
|
||||
icon: "widgets",
|
||||
},
|
||||
{
|
||||
name: "wizard",
|
||||
route: "/applications/wizard",
|
||||
icon: "import_contacts",
|
||||
},
|
||||
{
|
||||
name: "data tables",
|
||||
route: "/applications/data-tables",
|
||||
icon: "backup_table",
|
||||
},
|
||||
{
|
||||
name: "calendar",
|
||||
route: "/applications/calendar",
|
||||
icon: "event",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "ecommerce",
|
||||
columns: 2,
|
||||
rowsPerColumn: 1,
|
||||
collapse: [
|
||||
{
|
||||
name: "orders",
|
||||
icon: <Icon>shopping_cart</Icon>,
|
||||
collapse: [
|
||||
{
|
||||
name: "order list",
|
||||
route: "/ecommerce/orders/order-list",
|
||||
},
|
||||
{
|
||||
name: "order details",
|
||||
route: "/ecommerce/orders/order-details",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "products",
|
||||
icon: <Icon>memory</Icon>,
|
||||
collapse: [
|
||||
{
|
||||
name: "new product",
|
||||
route: "/ecommerce/products/new-product",
|
||||
},
|
||||
{
|
||||
name: "edit product",
|
||||
route: "/ecommerce/products/edit-product",
|
||||
},
|
||||
{
|
||||
name: "product page",
|
||||
route: "/ecommerce/products/product-page",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "docs",
|
||||
collapse: [
|
||||
{
|
||||
name: "getting started",
|
||||
href: "https://www.creative-tim.com/learning-lab/react/quick-start/material-dashboard/",
|
||||
description: "All about overview, quick start, license and contents",
|
||||
icon: <Icon>article</Icon>,
|
||||
},
|
||||
{
|
||||
name: "foundation",
|
||||
href: "https://www.creative-tim.com/learning-lab/react/colors/material-dashboard/",
|
||||
description: "See our colors, icons and typography",
|
||||
icon: <Icon>grading</Icon>,
|
||||
},
|
||||
{
|
||||
name: "components",
|
||||
href: "https://www.creative-tim.com/learning-lab/react/alerts/material-dashboard/",
|
||||
description: "Explore our collection of fully designed components",
|
||||
icon: <Icon>apps</Icon>,
|
||||
},
|
||||
{
|
||||
name: "plugins",
|
||||
href: "https://www.creative-tim.com/learning-lab/react/datepicker/material-dashboard/",
|
||||
description: "Check how you can integrate our plugins",
|
||||
icon: <Icon>extension</Icon>,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "pages",
|
||||
columns: 3,
|
||||
rowsPerColumn: 2,
|
||||
collapse: [
|
||||
{
|
||||
name: "dashboards",
|
||||
icon: <Icon>dashboard</Icon>,
|
||||
collapse: [
|
||||
{
|
||||
name: "analytics",
|
||||
route: "/dashboards/analytics",
|
||||
},
|
||||
{
|
||||
name: "sales",
|
||||
route: "/dashboards/sales",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "users",
|
||||
icon: <Icon>people</Icon>,
|
||||
collapse: [
|
||||
{
|
||||
name: "reports",
|
||||
route: "/pages/users/reports",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "extra",
|
||||
icon: <Icon>queue_play_next</Icon>,
|
||||
collapse: [
|
||||
{
|
||||
name: "pricing page",
|
||||
route: "/pages/pricing-page",
|
||||
},
|
||||
{ name: "RTL", route: "/pages/rtl" },
|
||||
{ name: "widgets", route: "/pages/widgets" },
|
||||
{ name: "charts", route: "/pages/charts" },
|
||||
{
|
||||
name: "notfications",
|
||||
route: "/pages/notifications",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "projects",
|
||||
icon: <Icon>precision_manufacturing</Icon>,
|
||||
collapse: [
|
||||
{
|
||||
name: "timeline",
|
||||
route: "/pages/projects/timeline",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "account",
|
||||
icon: <Icon>account_balance</Icon>,
|
||||
collapse: [
|
||||
{
|
||||
name: "settings",
|
||||
route: "/pages/account/setting",
|
||||
},
|
||||
{
|
||||
name: "billing",
|
||||
route: "/pages/account/billing",
|
||||
},
|
||||
{
|
||||
name: "invoice",
|
||||
route: "/pages/account/invoice",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "profile",
|
||||
icon: <Icon>badge</Icon>,
|
||||
collapse: [
|
||||
{
|
||||
name: "profile overview",
|
||||
route: "/pages/profile/profile-overview",
|
||||
},
|
||||
{
|
||||
name: "all projects",
|
||||
route: "/pages/profile/all-projects",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "authenticaton",
|
||||
collapse: [
|
||||
{
|
||||
name: "sign in",
|
||||
dropdown: true,
|
||||
icon: <Icon>login</Icon>,
|
||||
collapse: [
|
||||
{
|
||||
name: "basic",
|
||||
route: "/authentication/sign-in/basic",
|
||||
},
|
||||
{
|
||||
name: "cover",
|
||||
route: "/authentication/sign-in/cover",
|
||||
},
|
||||
{
|
||||
name: "illustration",
|
||||
route: "/authentication/sign-in/illustration",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "sign up",
|
||||
dropdown: true,
|
||||
icon: <Icon>assignment</Icon>,
|
||||
collapse: [
|
||||
{
|
||||
name: "cover",
|
||||
route: "/authentication/sign-up/cover",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "reset password",
|
||||
dropdown: true,
|
||||
icon: <Icon>restart_alt</Icon>,
|
||||
collapse: [
|
||||
{
|
||||
name: "cover",
|
||||
route: "/authentication/reset-password/cover",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "application",
|
||||
collapse: [
|
||||
{
|
||||
name: "kanban",
|
||||
route: "/applications/kanban",
|
||||
icon: "widgets",
|
||||
},
|
||||
{
|
||||
name: "wizard",
|
||||
route: "/applications/wizard",
|
||||
icon: "import_contacts",
|
||||
},
|
||||
{
|
||||
name: "data tables",
|
||||
route: "/applications/data-tables",
|
||||
icon: "backup_table",
|
||||
},
|
||||
{
|
||||
name: "calendar",
|
||||
route: "/applications/calendar",
|
||||
icon: "event",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "ecommerce",
|
||||
columns: 2,
|
||||
rowsPerColumn: 1,
|
||||
collapse: [
|
||||
{
|
||||
name: "orders",
|
||||
icon: <Icon>shopping_cart</Icon>,
|
||||
collapse: [
|
||||
{
|
||||
name: "order list",
|
||||
route: "/ecommerce/orders/order-list",
|
||||
},
|
||||
{
|
||||
name: "order details",
|
||||
route: "/ecommerce/orders/order-details",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "products",
|
||||
icon: <Icon>memory</Icon>,
|
||||
collapse: [
|
||||
{
|
||||
name: "new product",
|
||||
route: "/ecommerce/products/new-product",
|
||||
},
|
||||
{
|
||||
name: "edit product",
|
||||
route: "/ecommerce/products/edit-product",
|
||||
},
|
||||
{
|
||||
name: "product page",
|
||||
route: "/ecommerce/products/product-page",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "docs",
|
||||
collapse: [
|
||||
{
|
||||
name: "getting started",
|
||||
href: "https://www.creative-tim.com/learning-lab/react/quick-start/material-dashboard/",
|
||||
description: "All about overview, quick start, license and contents",
|
||||
icon: <Icon>article</Icon>,
|
||||
},
|
||||
{
|
||||
name: "foundation",
|
||||
href: "https://www.creative-tim.com/learning-lab/react/colors/material-dashboard/",
|
||||
description: "See our colors, icons and typography",
|
||||
icon: <Icon>grading</Icon>,
|
||||
},
|
||||
{
|
||||
name: "components",
|
||||
href: "https://www.creative-tim.com/learning-lab/react/alerts/material-dashboard/",
|
||||
description: "Explore our collection of fully designed components",
|
||||
icon: <Icon>apps</Icon>,
|
||||
},
|
||||
{
|
||||
name: "plugins",
|
||||
href: "https://www.creative-tim.com/learning-lab/react/datepicker/material-dashboard/",
|
||||
description: "Check how you can integrate our plugins",
|
||||
icon: <Icon>extension</Icon>,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export default pageRoutes;
|
||||
|
@ -30,41 +30,44 @@ interface Props {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
function BaseLayout({ stickyNavbar, children }: Props): JSX.Element {
|
||||
const [tabsOrientation, setTabsOrientation] = useState<"horizontal" | "vertical">("horizontal");
|
||||
function BaseLayout({ stickyNavbar, children }: Props): JSX.Element
|
||||
{
|
||||
const [tabsOrientation, setTabsOrientation] = useState<"horizontal" | "vertical">("horizontal");
|
||||
|
||||
useEffect(() => {
|
||||
// A function that sets the orientation state of the tabs.
|
||||
function handleTabsOrientation() {
|
||||
return window.innerWidth < breakpoints.values.sm
|
||||
? setTabsOrientation("vertical")
|
||||
: setTabsOrientation("horizontal");
|
||||
}
|
||||
useEffect(() =>
|
||||
{
|
||||
// A function that sets the orientation state of the tabs.
|
||||
function handleTabsOrientation()
|
||||
{
|
||||
return window.innerWidth < breakpoints.values.sm
|
||||
? setTabsOrientation("vertical")
|
||||
: setTabsOrientation("horizontal");
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
The event listener that's calling the handleTabsOrientation function when resizing the window.
|
||||
*/
|
||||
window.addEventListener("resize", handleTabsOrientation);
|
||||
window.addEventListener("resize", handleTabsOrientation);
|
||||
|
||||
// Call the handleTabsOrientation function to set the state with the initial value.
|
||||
handleTabsOrientation();
|
||||
// Call the handleTabsOrientation function to set the state with the initial value.
|
||||
handleTabsOrientation();
|
||||
|
||||
// Remove event listener on cleanup
|
||||
return () => window.removeEventListener("resize", handleTabsOrientation);
|
||||
}, [tabsOrientation]);
|
||||
// Remove event listener on cleanup
|
||||
return () => window.removeEventListener("resize", handleTabsOrientation);
|
||||
}, [tabsOrientation]);
|
||||
|
||||
return (
|
||||
<DashboardLayout>
|
||||
<DashboardNavbar absolute={!stickyNavbar} isMini />
|
||||
<MDBox mt={stickyNavbar ? 3 : 10}>{children}</MDBox>
|
||||
<Footer />
|
||||
</DashboardLayout>
|
||||
);
|
||||
return (
|
||||
<DashboardLayout>
|
||||
<DashboardNavbar absolute={!stickyNavbar} isMini />
|
||||
<MDBox mt={stickyNavbar ? 3 : 10}>{children}</MDBox>
|
||||
<Footer />
|
||||
</DashboardLayout>
|
||||
);
|
||||
}
|
||||
|
||||
// Declaring default props for BaseLayout
|
||||
BaseLayout.defaultProps = {
|
||||
stickyNavbar: false,
|
||||
stickyNavbar: false,
|
||||
};
|
||||
|
||||
export default BaseLayout;
|
||||
|
@ -34,23 +34,24 @@ interface Props {
|
||||
| "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" />
|
||||
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>
|
||||
<MDTypography variant="caption" fontWeight="medium" color="text" sx={{ lineHeight: 0 }}>
|
||||
{name}
|
||||
</MDTypography>
|
||||
</MDBox>
|
||||
);
|
||||
);
|
||||
}
|
||||
|
||||
// Declaring default props for CustomerCell
|
||||
CustomerCell.defaultProps = {
|
||||
image: "",
|
||||
color: "dark",
|
||||
image: "",
|
||||
color: "dark",
|
||||
};
|
||||
|
||||
export default CustomerCell;
|
||||
|
@ -22,22 +22,24 @@ interface Props {
|
||||
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">
|
||||
{suffix}
|
||||
</MDTypography>
|
||||
)}
|
||||
</MDTypography>
|
||||
);
|
||||
function DefaultCell({ value, suffix }: Props): JSX.Element
|
||||
{
|
||||
return (
|
||||
<MDTypography variant="caption" fontWeight="medium" color="text">
|
||||
{value}
|
||||
{suffix && (
|
||||
<MDTypography variant="caption" fontWeight="medium" color="secondary">
|
||||
|
||||
{suffix}
|
||||
</MDTypography>
|
||||
)}
|
||||
</MDTypography>
|
||||
);
|
||||
}
|
||||
|
||||
// Declaring default props for DefaultCell
|
||||
DefaultCell.defaultProps = {
|
||||
suffix: "",
|
||||
suffix: "",
|
||||
};
|
||||
|
||||
export default DefaultCell;
|
||||
|
@ -27,27 +27,28 @@ interface Props {
|
||||
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>;
|
||||
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>
|
||||
return (
|
||||
<MDBox display="flex" alignItems="center">
|
||||
<Checkbox defaultChecked={checked} />
|
||||
<MDBox ml={1}>
|
||||
<MDTypography variant="caption" fontWeight="medium" color="text">
|
||||
{link}
|
||||
</MDTypography>
|
||||
</MDBox>
|
||||
</MDBox>
|
||||
</MDBox>
|
||||
);
|
||||
);
|
||||
}
|
||||
|
||||
// Declaring default props for IdCell
|
||||
IdCell.defaultProps = {
|
||||
checked: false,
|
||||
checked: false,
|
||||
};
|
||||
|
||||
export default IdCell;
|
||||
|
@ -38,19 +38,20 @@ interface Props {
|
||||
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>
|
||||
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>
|
||||
<MDTypography variant="caption" fontWeight="medium" color="text" sx={{ lineHeight: 0 }}>
|
||||
{status}
|
||||
</MDTypography>
|
||||
</MDBox>
|
||||
);
|
||||
);
|
||||
}
|
||||
|
||||
export default StatusCell;
|
||||
|
@ -14,180 +14,180 @@ Coded by www.creative-tim.com
|
||||
*/
|
||||
|
||||
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"],
|
||||
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;
|
||||
|
@ -28,153 +28,174 @@ interface Props {
|
||||
id?: string;
|
||||
}
|
||||
|
||||
function EntityForm({ id }: Props): JSX.Element {
|
||||
const qController = new QController("");
|
||||
const { tableName } = useParams();
|
||||
function EntityForm({ id }: Props): JSX.Element
|
||||
{
|
||||
const qController = new QController("");
|
||||
const { tableName } = useParams();
|
||||
|
||||
const [validations, setValidations] = useState({});
|
||||
const [initialValues, setInitialValues] = useState({} as { [key: string]: string });
|
||||
const [formFields, setFormFields] = useState({});
|
||||
const [alertContent, setAlertContent] = useState("");
|
||||
const [validations, setValidations] = useState({});
|
||||
const [initialValues, setInitialValues] = useState({} as { [key: string]: string });
|
||||
const [formFields, setFormFields] = useState({});
|
||||
const [alertContent, setAlertContent] = useState("");
|
||||
|
||||
const [asyncLoadInited, setAsyncLoadInited] = useState(false);
|
||||
const [formValues, setFormValues] = useState({} as { [key: string]: string });
|
||||
const [tableMetaData, setTableMetaData] = useState(null);
|
||||
const [, forceUpdate] = useReducer((x) => x + 1, 0);
|
||||
const [asyncLoadInited, setAsyncLoadInited] = useState(false);
|
||||
const [formValues, setFormValues] = useState({} as { [key: string]: string });
|
||||
const [tableMetaData, setTableMetaData] = useState(null);
|
||||
const [, forceUpdate] = useReducer((x) => x + 1, 0);
|
||||
|
||||
function getDynamicStepContent(formData: any): JSX.Element {
|
||||
const { formFields, values, errors, touched } = formData;
|
||||
function getDynamicStepContent(formData: any): JSX.Element
|
||||
{
|
||||
const {
|
||||
formFields, values, errors, touched,
|
||||
} = formData;
|
||||
|
||||
if (!Object.keys(formFields).length) {
|
||||
return <div>Loading...</div>;
|
||||
}
|
||||
|
||||
return <QDynamicForm formData={formData} primaryKeyId={tableMetaData.primaryKeyField} />;
|
||||
}
|
||||
|
||||
if (!asyncLoadInited) {
|
||||
setAsyncLoadInited(true);
|
||||
(async () => {
|
||||
const tableMetaData = await qController.loadTableMetaData(tableName);
|
||||
setTableMetaData(tableMetaData);
|
||||
|
||||
const fieldArray = [] as QFieldMetaData[];
|
||||
const sortedKeys = [...tableMetaData.fields.keys()].sort();
|
||||
sortedKeys.forEach((key) => {
|
||||
const fieldMetaData = tableMetaData.fields.get(key);
|
||||
fieldArray.push(fieldMetaData);
|
||||
});
|
||||
|
||||
if (id !== null) {
|
||||
const record = await qController.get(tableName, id);
|
||||
|
||||
tableMetaData.fields.forEach((fieldMetaData, key) => {
|
||||
initialValues[key] = record.values.get(key);
|
||||
});
|
||||
|
||||
setFormValues(formValues);
|
||||
if (!Object.keys(formFields).length)
|
||||
{
|
||||
return <div>Loading...</div>;
|
||||
}
|
||||
|
||||
const { dynamicFormFields, formValidations } = DynamicFormUtils.getFormData(fieldArray);
|
||||
setInitialValues(initialValues);
|
||||
setFormFields(dynamicFormFields);
|
||||
setValidations(Yup.object().shape(formValidations));
|
||||
return <QDynamicForm formData={formData} primaryKeyId={tableMetaData.primaryKeyField} />;
|
||||
}
|
||||
|
||||
forceUpdate();
|
||||
})();
|
||||
}
|
||||
if (!asyncLoadInited)
|
||||
{
|
||||
setAsyncLoadInited(true);
|
||||
(async () =>
|
||||
{
|
||||
const tableMetaData = await qController.loadTableMetaData(tableName);
|
||||
setTableMetaData(tableMetaData);
|
||||
|
||||
const handleSubmit = async (values: any, actions: any) => {
|
||||
actions.setSubmitting(true);
|
||||
await (async () => {
|
||||
if (id !== null) {
|
||||
await qController
|
||||
.update(tableName, id, values)
|
||||
.then((record) => {
|
||||
window.location.href = `/${tableName}/${record.values.get(
|
||||
tableMetaData.primaryKeyField
|
||||
)}`;
|
||||
})
|
||||
.catch((error) => {
|
||||
setAlertContent(error.response.data.error);
|
||||
});
|
||||
} else {
|
||||
await qController
|
||||
.create(tableName, values)
|
||||
.then((record) => {
|
||||
window.location.href = `/${tableName}/${record.values.get(
|
||||
tableMetaData.primaryKeyField
|
||||
)}`;
|
||||
})
|
||||
.catch((error) => {
|
||||
setAlertContent(error.response.data.error);
|
||||
});
|
||||
}
|
||||
})();
|
||||
};
|
||||
const fieldArray = [] as QFieldMetaData[];
|
||||
const sortedKeys = [...tableMetaData.fields.keys()].sort();
|
||||
sortedKeys.forEach((key) =>
|
||||
{
|
||||
const fieldMetaData = tableMetaData.fields.get(key);
|
||||
fieldArray.push(fieldMetaData);
|
||||
});
|
||||
|
||||
const formTitle =
|
||||
id != null ? `Edit ${tableMetaData?.label} (${id})` : `Create New ${tableMetaData?.label}`;
|
||||
const formId =
|
||||
id != null ? `edit-${tableMetaData?.label}-form` : `create-${tableMetaData?.label}-form`;
|
||||
if (id !== null)
|
||||
{
|
||||
const record = await qController.get(tableName, id);
|
||||
|
||||
return (
|
||||
<MDBox mb={3}>
|
||||
<Grid container spacing={3}>
|
||||
<Grid item xs={12}>
|
||||
{alertContent ? (
|
||||
<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}>
|
||||
<Formik
|
||||
initialValues={initialValues}
|
||||
validationSchema={validations}
|
||||
onSubmit={handleSubmit}
|
||||
>
|
||||
{({ values, errors, touched, isSubmitting }) => (
|
||||
<Form id={formId} autoComplete="off">
|
||||
<MDBox p={3} width="100%">
|
||||
{/***************************************************************************
|
||||
tableMetaData.fields.forEach((fieldMetaData, key) =>
|
||||
{
|
||||
initialValues[key] = record.values.get(key);
|
||||
});
|
||||
|
||||
setFormValues(formValues);
|
||||
}
|
||||
|
||||
const { dynamicFormFields, formValidations } = DynamicFormUtils.getFormData(fieldArray);
|
||||
setInitialValues(initialValues);
|
||||
setFormFields(dynamicFormFields);
|
||||
setValidations(Yup.object().shape(formValidations));
|
||||
|
||||
forceUpdate();
|
||||
})();
|
||||
}
|
||||
|
||||
const handleSubmit = async (values: any, actions: any) =>
|
||||
{
|
||||
actions.setSubmitting(true);
|
||||
await (async () =>
|
||||
{
|
||||
if (id !== null)
|
||||
{
|
||||
await qController
|
||||
.update(tableName, id, values)
|
||||
.then((record) =>
|
||||
{
|
||||
window.location.href = `/${tableName}/${record.values.get(
|
||||
tableMetaData.primaryKeyField,
|
||||
)}`;
|
||||
})
|
||||
.catch((error) =>
|
||||
{
|
||||
setAlertContent(error.response.data.error);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
await qController
|
||||
.create(tableName, values)
|
||||
.then((record) =>
|
||||
{
|
||||
window.location.href = `/${tableName}/${record.values.get(
|
||||
tableMetaData.primaryKeyField,
|
||||
)}`;
|
||||
})
|
||||
.catch((error) =>
|
||||
{
|
||||
setAlertContent(error.response.data.error);
|
||||
});
|
||||
}
|
||||
})();
|
||||
};
|
||||
|
||||
const formTitle = id != null ? `Edit ${tableMetaData?.label} (${id})` : `Create New ${tableMetaData?.label}`;
|
||||
const formId = id != null ? `edit-${tableMetaData?.label}-form` : `create-${tableMetaData?.label}-form`;
|
||||
|
||||
return (
|
||||
<MDBox mb={3}>
|
||||
<Grid container spacing={3}>
|
||||
<Grid item xs={12}>
|
||||
{alertContent ? (
|
||||
<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}>
|
||||
<Formik
|
||||
initialValues={initialValues}
|
||||
validationSchema={validations}
|
||||
onSubmit={handleSubmit}
|
||||
>
|
||||
{({
|
||||
values, errors, touched, 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>
|
||||
</Grid>
|
||||
</MDBox>
|
||||
</Form>
|
||||
)}
|
||||
</Formik>
|
||||
</Grid>
|
||||
</MDBox>
|
||||
</Card>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</MDBox>
|
||||
);
|
||||
{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>
|
||||
</Grid>
|
||||
</MDBox>
|
||||
</Form>
|
||||
)}
|
||||
</Formik>
|
||||
</Grid>
|
||||
</MDBox>
|
||||
</Card>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</MDBox>
|
||||
);
|
||||
}
|
||||
|
||||
// Declaring default props for DefaultCell
|
||||
EntityForm.defaultProps = {
|
||||
id: null,
|
||||
id: null,
|
||||
};
|
||||
|
||||
export default EntityForm;
|
||||
|
@ -37,80 +37,85 @@ interface Props {
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
function Footer({ company, links }: Props): JSX.Element {
|
||||
const { href, name } = company;
|
||||
const { size } = typography;
|
||||
function Footer({ company, links }: Props): JSX.Element
|
||||
{
|
||||
const { href, name } = company;
|
||||
const { size } = typography;
|
||||
|
||||
const renderLinks = () =>
|
||||
links.map((link) => (
|
||||
const renderLinks = () => links.map((link) => (
|
||||
<MDBox key={link.name} component="li" px={2} lineHeight={1}>
|
||||
<Link href={link.href} target="_blank">
|
||||
<MDTypography variant="button" fontWeight="regular" color="text">
|
||||
{link.name}
|
||||
</MDTypography>
|
||||
</Link>
|
||||
<Link href={link.href} target="_blank">
|
||||
<MDTypography variant="button" fontWeight="regular" color="text">
|
||||
{link.name}
|
||||
</MDTypography>
|
||||
</Link>
|
||||
</MDBox>
|
||||
));
|
||||
));
|
||||
|
||||
return (
|
||||
<MDBox
|
||||
width="100%"
|
||||
display="flex"
|
||||
flexDirection={{ xs: "column", lg: "row" }}
|
||||
justifyContent="space-between"
|
||||
alignItems="center"
|
||||
px={1.5}
|
||||
>
|
||||
return (
|
||||
<MDBox
|
||||
display="flex"
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
flexWrap="wrap"
|
||||
color="text"
|
||||
fontSize={size.sm}
|
||||
px={1.5}
|
||||
width="100%"
|
||||
display="flex"
|
||||
flexDirection={{ xs: "column", lg: "row" }}
|
||||
justifyContent="space-between"
|
||||
alignItems="center"
|
||||
px={1.5}
|
||||
>
|
||||
© {new Date().getFullYear()}, made with
|
||||
<MDBox fontSize={size.md} color="text" mb={-0.5} mx={0.25}>
|
||||
<Icon color="inherit" fontSize="inherit">
|
||||
favorite
|
||||
</Icon>
|
||||
</MDBox>
|
||||
by
|
||||
<Link href={href} target="_blank">
|
||||
<MDTypography variant="button" fontWeight="medium">
|
||||
{name}
|
||||
</MDTypography>
|
||||
</Link>
|
||||
for a better web.
|
||||
</MDBox>
|
||||
<MDBox
|
||||
component="ul"
|
||||
sx={({ breakpoints }) => ({
|
||||
display: "flex",
|
||||
flexWrap: "wrap",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
listStyle: "none",
|
||||
mt: 3,
|
||||
mb: 0,
|
||||
p: 0,
|
||||
<MDBox
|
||||
display="flex"
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
flexWrap="wrap"
|
||||
color="text"
|
||||
fontSize={size.sm}
|
||||
px={1.5}
|
||||
>
|
||||
©
|
||||
{" "}
|
||||
{new Date().getFullYear()}
|
||||
, made with
|
||||
<MDBox fontSize={size.md} color="text" mb={-0.5} mx={0.25}>
|
||||
<Icon color="inherit" fontSize="inherit">
|
||||
favorite
|
||||
</Icon>
|
||||
</MDBox>
|
||||
by
|
||||
<Link href={href} target="_blank">
|
||||
<MDTypography variant="button" fontWeight="medium">
|
||||
|
||||
{name}
|
||||
|
||||
</MDTypography>
|
||||
</Link>
|
||||
for a better web.
|
||||
</MDBox>
|
||||
<MDBox
|
||||
component="ul"
|
||||
sx={({ breakpoints }) => ({
|
||||
display: "flex",
|
||||
flexWrap: "wrap",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
listStyle: "none",
|
||||
mt: 3,
|
||||
mb: 0,
|
||||
p: 0,
|
||||
|
||||
[breakpoints.up("lg")]: {
|
||||
mt: 0,
|
||||
},
|
||||
})}
|
||||
>
|
||||
{renderLinks()}
|
||||
[breakpoints.up("lg")]: {
|
||||
mt: 0,
|
||||
},
|
||||
})}
|
||||
>
|
||||
{renderLinks()}
|
||||
</MDBox>
|
||||
</MDBox>
|
||||
</MDBox>
|
||||
);
|
||||
);
|
||||
}
|
||||
|
||||
// Declaring default props for Footer
|
||||
Footer.defaultProps = {
|
||||
company: { href: "https://www.kingsrook.com/", name: "Kingsrook" },
|
||||
links: [{ href: "https://www.kingsrook.com/", name: "Kingsrook" }],
|
||||
company: { href: "https://www.kingsrook.com/", name: "Kingsrook" },
|
||||
links: [{ href: "https://www.kingsrook.com/", name: "Kingsrook" }],
|
||||
};
|
||||
|
||||
export default Footer;
|
||||
|
@ -30,11 +30,14 @@ interface Props {
|
||||
primaryKeyId?: string;
|
||||
}
|
||||
|
||||
function QDynamicForm(props: Props): JSX.Element {
|
||||
const { formData, formLabel, primaryKeyId } = props;
|
||||
const { formFields, values, errors, touched } = formData;
|
||||
function QDynamicForm(props: Props): JSX.Element
|
||||
{
|
||||
const { formData, formLabel, primaryKeyId } = props;
|
||||
const {
|
||||
formFields, values, errors, touched,
|
||||
} = formData;
|
||||
|
||||
/*
|
||||
/*
|
||||
const {
|
||||
firstName: firstNameV,
|
||||
lastName: lastNameV,
|
||||
@ -45,42 +48,45 @@ function QDynamicForm(props: Props): JSX.Element {
|
||||
} = values;
|
||||
*/
|
||||
|
||||
return (
|
||||
<MDBox>
|
||||
<MDBox lineHeight={0}>
|
||||
<MDTypography variant="h5">{formLabel}</MDTypography>
|
||||
{/* TODO - help text
|
||||
return (
|
||||
<MDBox>
|
||||
<MDBox lineHeight={0}>
|
||||
<MDTypography variant="h5">{formLabel}</MDTypography>
|
||||
{/* TODO - help text
|
||||
<MDTypography variant="button" color="text">
|
||||
Mandatory information
|
||||
</MDTypography>
|
||||
*/}
|
||||
</MDBox>
|
||||
<MDBox mt={1.625}>
|
||||
<Grid container spacing={3}>
|
||||
{formFields &&
|
||||
Object.keys(formFields).length > 0 &&
|
||||
Object.keys(formFields).map((fieldName: any) => {
|
||||
const field = formFields[fieldName];
|
||||
if (primaryKeyId && fieldName === primaryKeyId) {
|
||||
return null;
|
||||
}
|
||||
if (values[fieldName] === undefined) {
|
||||
values[fieldName] = "";
|
||||
}
|
||||
return (
|
||||
<Grid item xs={12} sm={6} key={fieldName}>
|
||||
<FormField
|
||||
type={field.type}
|
||||
label={field.label}
|
||||
name={fieldName}
|
||||
value={values[fieldName]}
|
||||
error={errors[fieldName] && touched[fieldName]}
|
||||
/>
|
||||
</Grid>
|
||||
);
|
||||
</MDBox>
|
||||
<MDBox mt={1.625}>
|
||||
<Grid container spacing={3}>
|
||||
{formFields
|
||||
&& Object.keys(formFields).length > 0
|
||||
&& Object.keys(formFields).map((fieldName: any) =>
|
||||
{
|
||||
const field = formFields[fieldName];
|
||||
if (primaryKeyId && fieldName === primaryKeyId)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
if (values[fieldName] === undefined)
|
||||
{
|
||||
values[fieldName] = "";
|
||||
}
|
||||
return (
|
||||
<Grid item xs={12} sm={6} key={fieldName}>
|
||||
<FormField
|
||||
type={field.type}
|
||||
label={field.label}
|
||||
name={fieldName}
|
||||
value={values[fieldName]}
|
||||
error={errors[fieldName] && touched[fieldName]}
|
||||
/>
|
||||
</Grid>
|
||||
);
|
||||
})}
|
||||
</Grid>
|
||||
{/*
|
||||
</Grid>
|
||||
{/*
|
||||
<Grid container spacing={3}>
|
||||
<Grid item xs={12} sm={6}>
|
||||
<FormField
|
||||
@ -154,14 +160,14 @@ function QDynamicForm(props: Props): JSX.Element {
|
||||
</Grid>
|
||||
</Grid>
|
||||
*/}
|
||||
</MDBox>
|
||||
</MDBox>
|
||||
</MDBox>
|
||||
);
|
||||
);
|
||||
}
|
||||
|
||||
QDynamicForm.defaultProps = {
|
||||
formLabel: undefined,
|
||||
primaryKeyId: undefined,
|
||||
formLabel: undefined,
|
||||
primaryKeyId: undefined,
|
||||
};
|
||||
|
||||
export default QDynamicForm;
|
||||
|
@ -30,51 +30,56 @@ import { QFieldType } from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFie
|
||||
** Meta-data to represent a single field in a table.
|
||||
**
|
||||
*******************************************************************************/
|
||||
class DynamicFormUtils {
|
||||
public static getFormData(qqqFormFields: QFieldMetaData[]) {
|
||||
const dynamicFormFields: any = {};
|
||||
const formValidations: any = {};
|
||||
class DynamicFormUtils
|
||||
{
|
||||
public static getFormData(qqqFormFields: QFieldMetaData[])
|
||||
{
|
||||
const dynamicFormFields: any = {};
|
||||
const formValidations: any = {};
|
||||
|
||||
qqqFormFields.forEach((field) => {
|
||||
let fieldType: string;
|
||||
switch (field.type.toString()) {
|
||||
case QFieldType.DECIMAL:
|
||||
case QFieldType.INTEGER:
|
||||
fieldType = "number";
|
||||
break;
|
||||
case QFieldType.DATE_TIME:
|
||||
fieldType = "datetime-local";
|
||||
break;
|
||||
case QFieldType.PASSWORD:
|
||||
case QFieldType.TIME:
|
||||
case QFieldType.DATE:
|
||||
fieldType = field.type.toString();
|
||||
break;
|
||||
case QFieldType.TEXT:
|
||||
case QFieldType.HTML:
|
||||
case QFieldType.STRING:
|
||||
default:
|
||||
fieldType = "text";
|
||||
}
|
||||
qqqFormFields.forEach((field) =>
|
||||
{
|
||||
let fieldType: string;
|
||||
switch (field.type.toString())
|
||||
{
|
||||
case QFieldType.DECIMAL:
|
||||
case QFieldType.INTEGER:
|
||||
fieldType = "number";
|
||||
break;
|
||||
case QFieldType.DATE_TIME:
|
||||
fieldType = "datetime-local";
|
||||
break;
|
||||
case QFieldType.PASSWORD:
|
||||
case QFieldType.TIME:
|
||||
case QFieldType.DATE:
|
||||
fieldType = field.type.toString();
|
||||
break;
|
||||
case QFieldType.TEXT:
|
||||
case QFieldType.HTML:
|
||||
case QFieldType.STRING:
|
||||
default:
|
||||
fieldType = "text";
|
||||
}
|
||||
|
||||
let label = field.label ? field.label : field.name;
|
||||
label += field.isRequired ? " *" : "";
|
||||
let label = field.label ? field.label : field.name;
|
||||
label += field.isRequired ? " *" : "";
|
||||
|
||||
dynamicFormFields[field.name] = {
|
||||
name: field.name,
|
||||
label: label,
|
||||
isRequired: field.isRequired,
|
||||
type: fieldType,
|
||||
// todo invalidMsg: "Zipcode is not valid (e.g. 70000).",
|
||||
};
|
||||
dynamicFormFields[field.name] = {
|
||||
name: field.name,
|
||||
label: label,
|
||||
isRequired: field.isRequired,
|
||||
type: fieldType,
|
||||
// todo invalidMsg: "Zipcode is not valid (e.g. 70000).",
|
||||
};
|
||||
|
||||
if (field.isRequired) {
|
||||
formValidations[field.name] = Yup.string().required(`${field.label} is required.`);
|
||||
}
|
||||
});
|
||||
if (field.isRequired)
|
||||
{
|
||||
formValidations[field.name] = Yup.string().required(`${field.label} is required.`);
|
||||
}
|
||||
});
|
||||
|
||||
return { dynamicFormFields, formValidations };
|
||||
}
|
||||
return { dynamicFormFields, formValidations };
|
||||
}
|
||||
}
|
||||
|
||||
export default DynamicFormUtils;
|
||||
|
@ -23,18 +23,19 @@ import MDBox from "components/MDBox";
|
||||
import EntityForm from "qqq/components/EntityForm";
|
||||
import BaseLayout from "qqq/components/BaseLayout";
|
||||
|
||||
function EntityCreate(): JSX.Element {
|
||||
return (
|
||||
<BaseLayout>
|
||||
<MDBox mt={4}>
|
||||
<Grid container spacing={3}>
|
||||
<Grid item xs={12} lg={12}>
|
||||
<EntityForm />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</MDBox>
|
||||
</BaseLayout>
|
||||
);
|
||||
function EntityCreate(): JSX.Element
|
||||
{
|
||||
return (
|
||||
<BaseLayout>
|
||||
<MDBox mt={4}>
|
||||
<Grid container spacing={3}>
|
||||
<Grid item xs={12} lg={12}>
|
||||
<EntityForm />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</MDBox>
|
||||
</BaseLayout>
|
||||
);
|
||||
}
|
||||
|
||||
export default EntityCreate;
|
||||
|
@ -24,26 +24,27 @@ import BaseLayout from "qqq/components/BaseLayout";
|
||||
import { useParams } from "react-router-dom";
|
||||
import EntityForm from "qqq/components/EntityForm";
|
||||
|
||||
function EntityEdit(): JSX.Element {
|
||||
const { id } = useParams();
|
||||
function EntityEdit(): 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}>
|
||||
<Grid item xs={12}>
|
||||
<EntityForm id={id} />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</MDBox>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</MDBox>
|
||||
</BaseLayout>
|
||||
);
|
||||
return (
|
||||
<BaseLayout>
|
||||
<MDBox mt={4}>
|
||||
<Grid container spacing={3}>
|
||||
<Grid item xs={12} lg={12}>
|
||||
<MDBox mb={3}>
|
||||
<Grid container spacing={3}>
|
||||
<Grid item xs={12}>
|
||||
<EntityForm id={id} />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</MDBox>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</MDBox>
|
||||
</BaseLayout>
|
||||
);
|
||||
}
|
||||
|
||||
export default EntityEdit;
|
||||
|
@ -26,18 +26,18 @@ import Divider from "@mui/material/Divider";
|
||||
import Link from "@mui/material/Link";
|
||||
import { makeStyles, Alert } from "@mui/material";
|
||||
import {
|
||||
DataGrid,
|
||||
GridCallbackDetails,
|
||||
GridColDef,
|
||||
GridColumnVisibilityModel,
|
||||
GridFilterModel,
|
||||
GridRowId,
|
||||
GridRowParams,
|
||||
GridRowsProp,
|
||||
GridSelectionModel,
|
||||
GridSortItem,
|
||||
GridSortModel,
|
||||
GridToolbar,
|
||||
DataGrid,
|
||||
GridCallbackDetails,
|
||||
GridColDef,
|
||||
GridColumnVisibilityModel,
|
||||
GridFilterModel,
|
||||
GridRowId,
|
||||
GridRowParams,
|
||||
GridRowsProp,
|
||||
GridSelectionModel,
|
||||
GridSortItem,
|
||||
GridSortModel,
|
||||
GridToolbar,
|
||||
} from "@mui/x-data-grid";
|
||||
|
||||
// Material Dashboard 2 PRO React TS components
|
||||
@ -66,325 +66,359 @@ interface Props {
|
||||
table?: QTableMetaData;
|
||||
}
|
||||
|
||||
function EntityList({ table }: Props): JSX.Element {
|
||||
const tableNameParam = useParams().tableName;
|
||||
const tableName = table === null ? tableNameParam : table.name;
|
||||
function EntityList({ table }: Props): JSX.Element
|
||||
{
|
||||
const tableNameParam = useParams().tableName;
|
||||
const tableName = table === null ? tableNameParam : table.name;
|
||||
|
||||
const [tableState, setTableState] = useState("");
|
||||
const [filtersMenu, setFiltersMenu] = useState(null);
|
||||
const [actionsMenu, setActionsMenu] = useState(null);
|
||||
const [tableProcesses, setTableProcesses] = useState([] as QProcessMetaData[]);
|
||||
const [pageNumber, setPageNumber] = useState(0);
|
||||
const [totalRecords, setTotalRecords] = useState(0);
|
||||
const [rowsPerPage, setRowsPerPage] = useState(10);
|
||||
const [selectedIds, setSelectedIds] = useState([] as string[]);
|
||||
const [columns, setColumns] = useState([] as GridColDef[]);
|
||||
const [rows, setRows] = useState([] as GridRowsProp[]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [columnVisibilityModel, setColumnVisibilityModel] = useState({});
|
||||
const [sortModel, setSortModel] = useState([] as GridSortItem[]);
|
||||
const [filterModel, setFilterModel] = useState(null as GridFilterModel);
|
||||
const [alertContent, setAlertContent] = useState("");
|
||||
const [tableState, setTableState] = useState("");
|
||||
const [filtersMenu, setFiltersMenu] = useState(null);
|
||||
const [actionsMenu, setActionsMenu] = useState(null);
|
||||
const [tableProcesses, setTableProcesses] = useState([] as QProcessMetaData[]);
|
||||
const [pageNumber, setPageNumber] = useState(0);
|
||||
const [totalRecords, setTotalRecords] = useState(0);
|
||||
const [rowsPerPage, setRowsPerPage] = useState(10);
|
||||
const [selectedIds, setSelectedIds] = useState([] as string[]);
|
||||
const [columns, setColumns] = useState([] as GridColDef[]);
|
||||
const [rows, setRows] = useState([] as GridRowsProp[]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [columnVisibilityModel, setColumnVisibilityModel] = useState({});
|
||||
const [sortModel, setSortModel] = useState([] as GridSortItem[]);
|
||||
const [filterModel, setFilterModel] = useState(null as GridFilterModel);
|
||||
const [alertContent, setAlertContent] = useState("");
|
||||
|
||||
const [, forceUpdate] = useReducer((x) => x + 1, 0);
|
||||
const [, forceUpdate] = useReducer((x) => x + 1, 0);
|
||||
|
||||
const openActionsMenu = (event: any) => setActionsMenu(event.currentTarget);
|
||||
const closeActionsMenu = () => setActionsMenu(null);
|
||||
const openFiltersMenu = (event: any) => setFiltersMenu(event.currentTarget);
|
||||
const closeFiltersMenu = () => setFiltersMenu(null);
|
||||
const openActionsMenu = (event: any) => setActionsMenu(event.currentTarget);
|
||||
const closeActionsMenu = () => setActionsMenu(null);
|
||||
const openFiltersMenu = (event: any) => setFiltersMenu(event.currentTarget);
|
||||
const closeFiltersMenu = () => setFiltersMenu(null);
|
||||
|
||||
const translateCriteriaOperator = (operator: string) => {
|
||||
switch (operator) {
|
||||
const translateCriteriaOperator = (operator: string) =>
|
||||
{
|
||||
switch (operator)
|
||||
{
|
||||
case "contains":
|
||||
return QCriteriaOperator.CONTAINS;
|
||||
return QCriteriaOperator.CONTAINS;
|
||||
case "starts with":
|
||||
return QCriteriaOperator.STARTS_WITH;
|
||||
return QCriteriaOperator.STARTS_WITH;
|
||||
case "ends with":
|
||||
return QCriteriaOperator.STARTS_WITH;
|
||||
return QCriteriaOperator.STARTS_WITH;
|
||||
case "is":
|
||||
case "equals":
|
||||
case "=":
|
||||
return QCriteriaOperator.EQUALS;
|
||||
return QCriteriaOperator.EQUALS;
|
||||
case "is not":
|
||||
case "!=":
|
||||
return QCriteriaOperator.NOT_EQUALS;
|
||||
return QCriteriaOperator.NOT_EQUALS;
|
||||
case "is after":
|
||||
case ">":
|
||||
return QCriteriaOperator.GREATER_THAN;
|
||||
return QCriteriaOperator.GREATER_THAN;
|
||||
case "is on or after":
|
||||
case ">=":
|
||||
return QCriteriaOperator.GREATER_THAN_OR_EQUALS;
|
||||
return QCriteriaOperator.GREATER_THAN_OR_EQUALS;
|
||||
case "is before":
|
||||
case "<":
|
||||
return QCriteriaOperator.LESS_THAN;
|
||||
return QCriteriaOperator.LESS_THAN;
|
||||
case "is on or before":
|
||||
case "<=":
|
||||
return QCriteriaOperator.LESS_THAN_OR_EQUALS;
|
||||
return QCriteriaOperator.LESS_THAN_OR_EQUALS;
|
||||
case "is empty":
|
||||
return QCriteriaOperator.IS_BLANK;
|
||||
return QCriteriaOperator.IS_BLANK;
|
||||
case "is not empty":
|
||||
return QCriteriaOperator.IS_NOT_BLANK;
|
||||
return QCriteriaOperator.IS_NOT_BLANK;
|
||||
// case "is any of":
|
||||
// TODO: handle this case
|
||||
default:
|
||||
return QCriteriaOperator.EQUALS;
|
||||
}
|
||||
};
|
||||
return QCriteriaOperator.EQUALS;
|
||||
}
|
||||
};
|
||||
|
||||
const buildQFilter = () => {
|
||||
const qFilter = new QQueryFilter();
|
||||
sortModel.forEach((gridSortItem) => {
|
||||
qFilter.addOrderBy(new QFilterOrderBy(gridSortItem.field, gridSortItem.sort === "asc"));
|
||||
});
|
||||
if (filterModel) {
|
||||
filterModel.items.forEach((item) => {
|
||||
qFilter.addCriteria(
|
||||
new QFilterCriteria(item.columnField, translateCriteriaOperator(item.operatorValue), [
|
||||
item.value,
|
||||
])
|
||||
);
|
||||
const buildQFilter = () =>
|
||||
{
|
||||
const qFilter = new QQueryFilter();
|
||||
sortModel.forEach((gridSortItem) =>
|
||||
{
|
||||
qFilter.addOrderBy(new QFilterOrderBy(gridSortItem.field, gridSortItem.sort === "asc"));
|
||||
});
|
||||
}
|
||||
|
||||
return qFilter;
|
||||
};
|
||||
|
||||
const updateTable = () => {
|
||||
(async () => {
|
||||
const tableMetaData = await QClient.loadTableMetaData(tableName);
|
||||
const count = await QClient.count(tableName);
|
||||
setTotalRecords(count);
|
||||
|
||||
if (sortModel.length === 0) {
|
||||
sortModel.push({ field: tableMetaData.primaryKeyField, sort: "desc" });
|
||||
setSortModel(sortModel);
|
||||
if (filterModel)
|
||||
{
|
||||
filterModel.items.forEach((item) =>
|
||||
{
|
||||
qFilter.addCriteria(
|
||||
new QFilterCriteria(item.columnField, translateCriteriaOperator(item.operatorValue), [
|
||||
item.value,
|
||||
]),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
const qFilter = buildQFilter();
|
||||
const columns = [] as GridColDef[];
|
||||
return qFilter;
|
||||
};
|
||||
|
||||
const results = await QClient.query(
|
||||
tableName,
|
||||
qFilter,
|
||||
rowsPerPage,
|
||||
pageNumber * rowsPerPage
|
||||
).catch((error) => {
|
||||
if (error.message) {
|
||||
setAlertContent(error.message);
|
||||
} else {
|
||||
setAlertContent(error.response.data.error);
|
||||
}
|
||||
throw error;
|
||||
});
|
||||
const updateTable = () =>
|
||||
{
|
||||
(async () =>
|
||||
{
|
||||
const tableMetaData = await QClient.loadTableMetaData(tableName);
|
||||
const count = await QClient.count(tableName);
|
||||
setTotalRecords(count);
|
||||
|
||||
const rows = [] as any[];
|
||||
results.forEach((record) => {
|
||||
rows.push(Object.fromEntries(record.values.entries()));
|
||||
});
|
||||
if (sortModel.length === 0)
|
||||
{
|
||||
sortModel.push({ field: tableMetaData.primaryKeyField, sort: "desc" });
|
||||
setSortModel(sortModel);
|
||||
}
|
||||
|
||||
const sortedKeys = [...tableMetaData.fields.keys()].sort();
|
||||
sortedKeys.forEach((key) => {
|
||||
const field = tableMetaData.fields.get(key);
|
||||
const qFilter = buildQFilter();
|
||||
const columns = [] as GridColDef[];
|
||||
|
||||
let columnType = "string";
|
||||
switch (field.type) {
|
||||
case QFieldType.DECIMAL:
|
||||
case QFieldType.INTEGER:
|
||||
columnType = "number";
|
||||
break;
|
||||
case QFieldType.DATE:
|
||||
columnType = "date";
|
||||
break;
|
||||
case QFieldType.DATE_TIME:
|
||||
columnType = "dateTime";
|
||||
break;
|
||||
case QFieldType.BOOLEAN:
|
||||
columnType = "boolean";
|
||||
break;
|
||||
default:
|
||||
const results = await QClient.query(
|
||||
tableName,
|
||||
qFilter,
|
||||
rowsPerPage,
|
||||
pageNumber * rowsPerPage,
|
||||
).catch((error) =>
|
||||
{
|
||||
if (error.message)
|
||||
{
|
||||
setAlertContent(error.message);
|
||||
}
|
||||
else
|
||||
{
|
||||
setAlertContent(error.response.data.error);
|
||||
}
|
||||
throw error;
|
||||
});
|
||||
|
||||
const rows = [] as any[];
|
||||
results.forEach((record) =>
|
||||
{
|
||||
rows.push(Object.fromEntries(record.values.entries()));
|
||||
});
|
||||
|
||||
const sortedKeys = [...tableMetaData.fields.keys()].sort();
|
||||
sortedKeys.forEach((key) =>
|
||||
{
|
||||
const field = tableMetaData.fields.get(key);
|
||||
|
||||
let columnType = "string";
|
||||
switch (field.type)
|
||||
{
|
||||
case QFieldType.DECIMAL:
|
||||
case QFieldType.INTEGER:
|
||||
columnType = "number";
|
||||
break;
|
||||
case QFieldType.DATE:
|
||||
columnType = "date";
|
||||
break;
|
||||
case QFieldType.DATE_TIME:
|
||||
columnType = "dateTime";
|
||||
break;
|
||||
case QFieldType.BOOLEAN:
|
||||
columnType = "boolean";
|
||||
break;
|
||||
default:
|
||||
// noop
|
||||
}
|
||||
}
|
||||
|
||||
const column = {
|
||||
field: field.name,
|
||||
type: columnType,
|
||||
headerName: field.label,
|
||||
width: 200,
|
||||
};
|
||||
const column = {
|
||||
field: field.name,
|
||||
type: columnType,
|
||||
headerName: field.label,
|
||||
width: 200,
|
||||
};
|
||||
|
||||
if (key === tableMetaData.primaryKeyField) {
|
||||
column.width = 75;
|
||||
columns.splice(0, 0, column);
|
||||
} else {
|
||||
columns.push(column);
|
||||
}
|
||||
if (key === tableMetaData.primaryKeyField)
|
||||
{
|
||||
column.width = 75;
|
||||
columns.splice(0, 0, column);
|
||||
}
|
||||
else
|
||||
{
|
||||
columns.push(column);
|
||||
}
|
||||
});
|
||||
|
||||
const columnVisibilityModel = localStorage.getItem(COLUMN_VISIBILITY_LOCAL_STORAGE_KEY);
|
||||
if (columnVisibilityModel)
|
||||
{
|
||||
setColumnVisibilityModel(
|
||||
JSON.parse(localStorage.getItem(COLUMN_VISIBILITY_LOCAL_STORAGE_KEY)),
|
||||
);
|
||||
}
|
||||
setColumns(columns);
|
||||
setRows(rows);
|
||||
setLoading(false);
|
||||
forceUpdate();
|
||||
})();
|
||||
};
|
||||
|
||||
const handlePageChange = (page: number) =>
|
||||
{
|
||||
setPageNumber(page);
|
||||
};
|
||||
|
||||
const handleRowsPerPageChange = (size: number) =>
|
||||
{
|
||||
setRowsPerPage(size);
|
||||
};
|
||||
|
||||
const handleRowClick = (params: GridRowParams) =>
|
||||
{
|
||||
document.location.href = `/${tableName}/${params.id}`;
|
||||
};
|
||||
|
||||
const handleFilterChange = (filterModel: GridFilterModel) =>
|
||||
{
|
||||
setFilterModel(filterModel);
|
||||
};
|
||||
|
||||
const selectionChanged = (selectionModel: GridSelectionModel, details: GridCallbackDetails) =>
|
||||
{
|
||||
const newSelectedIds: string[] = [];
|
||||
selectionModel.forEach((value: GridRowId) =>
|
||||
{
|
||||
newSelectedIds.push(value as string);
|
||||
});
|
||||
setSelectedIds(newSelectedIds);
|
||||
};
|
||||
|
||||
const columnVisibilityModel = localStorage.getItem(COLUMN_VISIBILITY_LOCAL_STORAGE_KEY);
|
||||
if (columnVisibilityModel) {
|
||||
setColumnVisibilityModel(
|
||||
JSON.parse(localStorage.getItem(COLUMN_VISIBILITY_LOCAL_STORAGE_KEY))
|
||||
);
|
||||
const handleColumnVisibilityChange = (columnVisibilityModel: GridColumnVisibilityModel) =>
|
||||
{
|
||||
setColumnVisibilityModel(columnVisibilityModel);
|
||||
localStorage.setItem(
|
||||
COLUMN_VISIBILITY_LOCAL_STORAGE_KEY,
|
||||
JSON.stringify(columnVisibilityModel),
|
||||
);
|
||||
};
|
||||
|
||||
const handleSortChange = (gridSort: GridSortModel) =>
|
||||
{
|
||||
setSortModel(gridSort);
|
||||
localStorage.setItem(COLUMN_SORT_LOCAL_STORAGE_KEY, JSON.stringify(gridSort));
|
||||
};
|
||||
|
||||
if (tableName !== tableState)
|
||||
{
|
||||
(async () =>
|
||||
{
|
||||
setTableState(tableName);
|
||||
const metaData = await QClient.loadMetaData();
|
||||
|
||||
setTableProcesses(QProcessUtils.getProcessesForTable(metaData, tableName));
|
||||
|
||||
// reset rows to trigger rerender
|
||||
setRows([]);
|
||||
})();
|
||||
}
|
||||
|
||||
function getRecordsQueryString()
|
||||
{
|
||||
if (selectedIds.length > 0)
|
||||
{
|
||||
return `?recordsParam=recordIds&recordIds=${selectedIds.join(",")}`;
|
||||
}
|
||||
setColumns(columns);
|
||||
setRows(rows);
|
||||
setLoading(false);
|
||||
forceUpdate();
|
||||
})();
|
||||
};
|
||||
return "";
|
||||
}
|
||||
|
||||
const handlePageChange = (page: number) => {
|
||||
setPageNumber(page);
|
||||
};
|
||||
const renderActionsMenu = (
|
||||
<Menu
|
||||
anchorEl={actionsMenu}
|
||||
anchorOrigin={{ vertical: "bottom", horizontal: "left" }}
|
||||
transformOrigin={{ vertical: "top", horizontal: "left" }}
|
||||
open={Boolean(actionsMenu)}
|
||||
onClose={closeActionsMenu}
|
||||
keepMounted
|
||||
>
|
||||
{tableProcesses.map((process) => (
|
||||
<MenuItem key={process.name}>
|
||||
<Link href={`/processes/${process.name}${getRecordsQueryString()}`}>{process.label}</Link>
|
||||
</MenuItem>
|
||||
))}
|
||||
</Menu>
|
||||
);
|
||||
|
||||
const handleRowsPerPageChange = (size: number) => {
|
||||
setRowsPerPage(size);
|
||||
};
|
||||
useEffect(() =>
|
||||
{
|
||||
updateTable();
|
||||
}, [pageNumber, rowsPerPage, tableState, sortModel, filterModel]);
|
||||
|
||||
const handleRowClick = (params: GridRowParams) => {
|
||||
document.location.href = `/${tableName}/${params.id}`;
|
||||
};
|
||||
|
||||
const handleFilterChange = (filterModel: GridFilterModel) => {
|
||||
setFilterModel(filterModel);
|
||||
};
|
||||
|
||||
const selectionChanged = (selectionModel: GridSelectionModel, details: GridCallbackDetails) => {
|
||||
const newSelectedIds: string[] = [];
|
||||
selectionModel.forEach((value: GridRowId) => {
|
||||
newSelectedIds.push(value as string);
|
||||
});
|
||||
setSelectedIds(newSelectedIds);
|
||||
};
|
||||
|
||||
const handleColumnVisibilityChange = (columnVisibilityModel: GridColumnVisibilityModel) => {
|
||||
setColumnVisibilityModel(columnVisibilityModel);
|
||||
localStorage.setItem(
|
||||
COLUMN_VISIBILITY_LOCAL_STORAGE_KEY,
|
||||
JSON.stringify(columnVisibilityModel)
|
||||
);
|
||||
};
|
||||
|
||||
const handleSortChange = (gridSort: GridSortModel) => {
|
||||
setSortModel(gridSort);
|
||||
localStorage.setItem(COLUMN_SORT_LOCAL_STORAGE_KEY, JSON.stringify(gridSort));
|
||||
};
|
||||
|
||||
if (tableName !== tableState) {
|
||||
(async () => {
|
||||
setTableState(tableName);
|
||||
const metaData = await QClient.loadMetaData();
|
||||
|
||||
setTableProcesses(QProcessUtils.getProcessesForTable(metaData, tableName));
|
||||
|
||||
// reset rows to trigger rerender
|
||||
setRows([]);
|
||||
})();
|
||||
}
|
||||
|
||||
function getRecordsQueryString() {
|
||||
if (selectedIds.length > 0) {
|
||||
return `?recordsParam=recordIds&recordIds=${selectedIds.join(",")}`;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
const renderActionsMenu = (
|
||||
<Menu
|
||||
anchorEl={actionsMenu}
|
||||
anchorOrigin={{ vertical: "bottom", horizontal: "left" }}
|
||||
transformOrigin={{ vertical: "top", horizontal: "left" }}
|
||||
open={Boolean(actionsMenu)}
|
||||
onClose={closeActionsMenu}
|
||||
keepMounted
|
||||
>
|
||||
{tableProcesses.map((process) => (
|
||||
<MenuItem key={process.name}>
|
||||
<Link href={`/processes/${process.name}${getRecordsQueryString()}`}>{process.label}</Link>
|
||||
</MenuItem>
|
||||
))}
|
||||
</Menu>
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
updateTable();
|
||||
}, [pageNumber, rowsPerPage, tableState, sortModel, filterModel]);
|
||||
|
||||
return (
|
||||
<DashboardLayout>
|
||||
<DashboardNavbar />
|
||||
<MDBox my={3}>
|
||||
{alertContent ? (
|
||||
<MDBox mb={3}>
|
||||
<Alert severity="error">{alertContent}</Alert>
|
||||
</MDBox>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
<MDBox display="flex" justifyContent="space-between" alignItems="flex-start" mb={2}>
|
||||
<Link href={`/${tableName}/create`}>
|
||||
<MDButton variant="gradient" color="info">
|
||||
new {tableName}
|
||||
</MDButton>
|
||||
</Link>
|
||||
|
||||
<MDBox display="flex">
|
||||
{tableProcesses.length > 0 && (
|
||||
<MDButton
|
||||
variant={actionsMenu ? "contained" : "outlined"}
|
||||
color="dark"
|
||||
onClick={openActionsMenu}
|
||||
>
|
||||
actions
|
||||
<Icon>keyboard_arrow_down</Icon>
|
||||
</MDButton>
|
||||
return (
|
||||
<DashboardLayout>
|
||||
<DashboardNavbar />
|
||||
<MDBox my={3}>
|
||||
{alertContent ? (
|
||||
<MDBox mb={3}>
|
||||
<Alert severity="error">{alertContent}</Alert>
|
||||
</MDBox>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
{renderActionsMenu}
|
||||
</MDBox>
|
||||
</MDBox>
|
||||
<Card>
|
||||
<MDBox height="100%">
|
||||
<DataGrid
|
||||
components={{ Toolbar: GridToolbar }}
|
||||
paginationMode="server"
|
||||
sortingMode="server"
|
||||
filterMode="server"
|
||||
page={pageNumber}
|
||||
checkboxSelection
|
||||
disableSelectionOnClick
|
||||
autoHeight
|
||||
rows={rows}
|
||||
columns={columns}
|
||||
rowBuffer={10}
|
||||
rowCount={totalRecords}
|
||||
pageSize={rowsPerPage}
|
||||
rowsPerPageOptions={[10, 25, 50]}
|
||||
onPageSizeChange={handleRowsPerPageChange}
|
||||
onPageChange={handlePageChange}
|
||||
onRowClick={handleRowClick}
|
||||
density="compact"
|
||||
loading={loading}
|
||||
onFilterModelChange={handleFilterChange}
|
||||
columnVisibilityModel={columnVisibilityModel}
|
||||
onColumnVisibilityModelChange={handleColumnVisibilityChange}
|
||||
onSelectionModelChange={selectionChanged}
|
||||
onSortModelChange={handleSortChange}
|
||||
sortingOrder={["asc", "desc"]}
|
||||
sortModel={sortModel}
|
||||
getRowClassName={(params) =>
|
||||
params.indexRelativeToCurrentPage % 2 === 0 ? "even" : "odd"
|
||||
}
|
||||
/>
|
||||
</MDBox>
|
||||
</Card>
|
||||
</MDBox>
|
||||
<Footer />
|
||||
</DashboardLayout>
|
||||
);
|
||||
<MDBox display="flex" justifyContent="space-between" alignItems="flex-start" mb={2}>
|
||||
<Link href={`/${tableName}/create`}>
|
||||
<MDButton variant="gradient" color="info">
|
||||
new
|
||||
{" "}
|
||||
{tableName}
|
||||
</MDButton>
|
||||
</Link>
|
||||
|
||||
<MDBox display="flex">
|
||||
{tableProcesses.length > 0 && (
|
||||
<MDButton
|
||||
variant={actionsMenu ? "contained" : "outlined"}
|
||||
color="dark"
|
||||
onClick={openActionsMenu}
|
||||
>
|
||||
actions
|
||||
<Icon>keyboard_arrow_down</Icon>
|
||||
</MDButton>
|
||||
)}
|
||||
{renderActionsMenu}
|
||||
</MDBox>
|
||||
</MDBox>
|
||||
<Card>
|
||||
<MDBox height="100%">
|
||||
<DataGrid
|
||||
components={{ Toolbar: GridToolbar }}
|
||||
paginationMode="server"
|
||||
sortingMode="server"
|
||||
filterMode="server"
|
||||
page={pageNumber}
|
||||
checkboxSelection
|
||||
disableSelectionOnClick
|
||||
autoHeight
|
||||
rows={rows}
|
||||
columns={columns}
|
||||
rowBuffer={10}
|
||||
rowCount={totalRecords}
|
||||
pageSize={rowsPerPage}
|
||||
rowsPerPageOptions={[10, 25, 50]}
|
||||
onPageSizeChange={handleRowsPerPageChange}
|
||||
onPageChange={handlePageChange}
|
||||
onRowClick={handleRowClick}
|
||||
density="compact"
|
||||
loading={loading}
|
||||
onFilterModelChange={handleFilterChange}
|
||||
columnVisibilityModel={columnVisibilityModel}
|
||||
onColumnVisibilityModelChange={handleColumnVisibilityChange}
|
||||
onSelectionModelChange={selectionChanged}
|
||||
onSortModelChange={handleSortChange}
|
||||
sortingOrder={["asc", "desc"]}
|
||||
sortModel={sortModel}
|
||||
getRowClassName={(params) => (params.indexRelativeToCurrentPage % 2 === 0 ? "even" : "odd")}
|
||||
/>
|
||||
</MDBox>
|
||||
</Card>
|
||||
</MDBox>
|
||||
<Footer />
|
||||
</DashboardLayout>
|
||||
);
|
||||
}
|
||||
|
||||
EntityList.defaultProps = {
|
||||
table: null,
|
||||
table: null,
|
||||
};
|
||||
|
||||
export default EntityList;
|
||||
|
@ -48,161 +48,186 @@ interface Props {
|
||||
id: string;
|
||||
}
|
||||
|
||||
function ViewContents({ id }: Props): JSX.Element {
|
||||
const { tableName } = useParams();
|
||||
function ViewContents({ id }: Props): JSX.Element
|
||||
{
|
||||
const { tableName } = useParams();
|
||||
|
||||
const [asyncLoadInited, setAsyncLoadInited] = useState(false);
|
||||
const [nameValues, setNameValues] = useState([] as JSX.Element[]);
|
||||
const [open, setOpen] = useState(false);
|
||||
const [tableMetaData, setTableMetaData] = useState(null);
|
||||
const [tableProcesses, setTableProcesses] = useState([] as QProcessMetaData[]);
|
||||
const [actionsMenu, setActionsMenu] = useState(null);
|
||||
const [, forceUpdate] = useReducer((x) => x + 1, 0);
|
||||
const [asyncLoadInited, setAsyncLoadInited] = useState(false);
|
||||
const [nameValues, setNameValues] = useState([] as JSX.Element[]);
|
||||
const [open, setOpen] = useState(false);
|
||||
const [tableMetaData, setTableMetaData] = useState(null);
|
||||
const [tableProcesses, setTableProcesses] = useState([] as QProcessMetaData[]);
|
||||
const [actionsMenu, setActionsMenu] = useState(null);
|
||||
const [, forceUpdate] = useReducer((x) => x + 1, 0);
|
||||
|
||||
const openActionsMenu = (event: any) => setActionsMenu(event.currentTarget);
|
||||
const closeActionsMenu = () => setActionsMenu(null);
|
||||
const openActionsMenu = (event: any) => setActionsMenu(event.currentTarget);
|
||||
const closeActionsMenu = () => setActionsMenu(null);
|
||||
|
||||
if (!asyncLoadInited) {
|
||||
setAsyncLoadInited(true);
|
||||
if (!asyncLoadInited)
|
||||
{
|
||||
setAsyncLoadInited(true);
|
||||
|
||||
(async () => {
|
||||
const tableMetaData = await qController.loadTableMetaData(tableName);
|
||||
setTableMetaData(tableMetaData);
|
||||
(async () =>
|
||||
{
|
||||
const tableMetaData = await qController.loadTableMetaData(tableName);
|
||||
setTableMetaData(tableMetaData);
|
||||
|
||||
const metaData = await qController.loadMetaData();
|
||||
setTableProcesses(QProcessUtils.getProcessesForTable(metaData, tableName));
|
||||
const metaData = await qController.loadMetaData();
|
||||
setTableProcesses(QProcessUtils.getProcessesForTable(metaData, tableName));
|
||||
|
||||
const foundRecord = await qController.get(tableName, id);
|
||||
const foundRecord = await qController.get(tableName, id);
|
||||
|
||||
nameValues.push(
|
||||
<MDBox key={tableMetaData.primaryKeyField} display="flex" py={1} pr={2}>
|
||||
<MDTypography variant="button" fontWeight="bold" textTransform="capitalize">
|
||||
{tableMetaData.primaryKeyField}:
|
||||
</MDTypography>
|
||||
<MDTypography variant="button" fontWeight="regular" color="text">
|
||||
{id}
|
||||
</MDTypography>
|
||||
</MDBox>
|
||||
);
|
||||
nameValues.push(
|
||||
<MDBox key={tableMetaData.primaryKeyField} display="flex" py={1} pr={2}>
|
||||
<MDTypography variant="button" fontWeight="bold" textTransform="capitalize">
|
||||
{tableMetaData.primaryKeyField}
|
||||
:
|
||||
</MDTypography>
|
||||
<MDTypography variant="button" fontWeight="regular" color="text">
|
||||
|
||||
{id}
|
||||
</MDTypography>
|
||||
</MDBox>,
|
||||
);
|
||||
|
||||
const sortedKeys = [...foundRecord.values.keys()].sort();
|
||||
sortedKeys.forEach((key) => {
|
||||
if (key !== tableMetaData.primaryKeyField) {
|
||||
nameValues.push(
|
||||
<MDBox key={key} display="flex" py={1} pr={2}>
|
||||
<MDTypography variant="button" fontWeight="bold" textTransform="capitalize">
|
||||
{tableMetaData.fields.get(key).label}:
|
||||
</MDTypography>
|
||||
<MDTypography variant="button" fontWeight="regular" color="text">
|
||||
{foundRecord.values.get(key)}
|
||||
</MDTypography>
|
||||
const sortedKeys = [...foundRecord.values.keys()].sort();
|
||||
sortedKeys.forEach((key) =>
|
||||
{
|
||||
if (key !== tableMetaData.primaryKeyField)
|
||||
{
|
||||
nameValues.push(
|
||||
<MDBox key={key} display="flex" py={1} pr={2}>
|
||||
<MDTypography variant="button" fontWeight="bold" textTransform="capitalize">
|
||||
{tableMetaData.fields.get(key).label}
|
||||
:
|
||||
</MDTypography>
|
||||
<MDTypography variant="button" fontWeight="regular" color="text">
|
||||
|
||||
{foundRecord.values.get(key)}
|
||||
</MDTypography>
|
||||
</MDBox>,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
setNameValues(nameValues);
|
||||
forceUpdate();
|
||||
})();
|
||||
}
|
||||
|
||||
const handleClickConfirmOpen = () =>
|
||||
{
|
||||
setOpen(true);
|
||||
};
|
||||
|
||||
const handleClickConfirmClose = () =>
|
||||
{
|
||||
setOpen(false);
|
||||
};
|
||||
|
||||
const handleDelete = (event: { preventDefault: () => void }) =>
|
||||
{
|
||||
event.preventDefault();
|
||||
(async () =>
|
||||
{
|
||||
await qController.delete(tableName, id).then(() =>
|
||||
{
|
||||
window.location.href = `/${tableName}`;
|
||||
});
|
||||
})();
|
||||
};
|
||||
|
||||
const editPath = `/${tableName}/${id}/edit`;
|
||||
|
||||
const renderActionsMenu = (
|
||||
<Menu
|
||||
anchorEl={actionsMenu}
|
||||
anchorOrigin={{ vertical: "bottom", horizontal: "left" }}
|
||||
transformOrigin={{ vertical: "top", horizontal: "left" }}
|
||||
open={Boolean(actionsMenu)}
|
||||
onClose={closeActionsMenu}
|
||||
keepMounted
|
||||
>
|
||||
{tableProcesses.map((process) => (
|
||||
<MenuItem key={process.name}>
|
||||
<Link href={`/processes/${process.name}?recordIds=${id}`}>{process.label}</Link>
|
||||
</MenuItem>
|
||||
))}
|
||||
</Menu>
|
||||
);
|
||||
|
||||
return (
|
||||
<Card id="basic-info" sx={{ overflow: "visible" }}>
|
||||
<MDBox p={3}>
|
||||
<MDBox display="flex" justifyContent="space-between">
|
||||
<MDTypography variant="h5">
|
||||
Viewing
|
||||
{" "}
|
||||
{tableMetaData?.label}
|
||||
{" "}
|
||||
(
|
||||
{id}
|
||||
)
|
||||
</MDTypography>
|
||||
{tableProcesses.length > 0 && (
|
||||
<MDButton
|
||||
variant={actionsMenu ? "contained" : "outlined"}
|
||||
color="dark"
|
||||
onClick={openActionsMenu}
|
||||
>
|
||||
actions
|
||||
<Icon>keyboard_arrow_down</Icon>
|
||||
</MDButton>
|
||||
)}
|
||||
{renderActionsMenu}
|
||||
</MDBox>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
setNameValues(nameValues);
|
||||
forceUpdate();
|
||||
})();
|
||||
}
|
||||
|
||||
const handleClickConfirmOpen = () => {
|
||||
setOpen(true);
|
||||
};
|
||||
|
||||
const handleClickConfirmClose = () => {
|
||||
setOpen(false);
|
||||
};
|
||||
|
||||
const handleDelete = (event: { preventDefault: () => void }) => {
|
||||
event.preventDefault();
|
||||
(async () => {
|
||||
await qController.delete(tableName, id).then(() => {
|
||||
window.location.href = `/${tableName}`;
|
||||
});
|
||||
})();
|
||||
};
|
||||
|
||||
const editPath = `/${tableName}/${id}/edit`;
|
||||
|
||||
const renderActionsMenu = (
|
||||
<Menu
|
||||
anchorEl={actionsMenu}
|
||||
anchorOrigin={{ vertical: "bottom", horizontal: "left" }}
|
||||
transformOrigin={{ vertical: "top", horizontal: "left" }}
|
||||
open={Boolean(actionsMenu)}
|
||||
onClose={closeActionsMenu}
|
||||
keepMounted
|
||||
>
|
||||
{tableProcesses.map((process) => (
|
||||
<MenuItem key={process.name}>
|
||||
<Link href={`/processes/${process.name}?recordIds=${id}`}>{process.label}</Link>
|
||||
</MenuItem>
|
||||
))}
|
||||
</Menu>
|
||||
);
|
||||
|
||||
return (
|
||||
<Card id="basic-info" sx={{ overflow: "visible" }}>
|
||||
<MDBox p={3}>
|
||||
<MDBox display="flex" justifyContent="space-between">
|
||||
<MDTypography variant="h5">
|
||||
Viewing {tableMetaData?.label} ({id})
|
||||
</MDTypography>
|
||||
{tableProcesses.length > 0 && (
|
||||
<MDButton
|
||||
variant={actionsMenu ? "contained" : "outlined"}
|
||||
color="dark"
|
||||
onClick={openActionsMenu}
|
||||
>
|
||||
actions
|
||||
<Icon>keyboard_arrow_down</Icon>
|
||||
</MDButton>
|
||||
)}
|
||||
{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>
|
||||
<Dialog
|
||||
open={open}
|
||||
onClose={handleClickConfirmClose}
|
||||
aria-labelledby="alert-dialog-title"
|
||||
aria-describedby="alert-dialog-description"
|
||||
>
|
||||
<DialogTitle id="alert-dialog-title">Confirm Deletion</DialogTitle>
|
||||
<DialogContent>
|
||||
<DialogContentText id="alert-dialog-description">
|
||||
Are you sure you want to delete this record?
|
||||
</DialogContentText>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={handleClickConfirmClose}>No</Button>
|
||||
<Button onClick={handleDelete} autoFocus>
|
||||
Yes
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</MDBox>
|
||||
<MDBox>
|
||||
<MDButton variant="gradient" color="dark" size="small">
|
||||
<Link href={editPath}>edit {tableMetaData?.label}</Link>
|
||||
</MDButton>
|
||||
</MDBox>
|
||||
</Grid>
|
||||
</MDBox>
|
||||
</Card>
|
||||
);
|
||||
</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>
|
||||
<Dialog
|
||||
open={open}
|
||||
onClose={handleClickConfirmClose}
|
||||
aria-labelledby="alert-dialog-title"
|
||||
aria-describedby="alert-dialog-description"
|
||||
>
|
||||
<DialogTitle id="alert-dialog-title">Confirm Deletion</DialogTitle>
|
||||
<DialogContent>
|
||||
<DialogContentText id="alert-dialog-description">
|
||||
Are you sure you want to delete this record?
|
||||
</DialogContentText>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={handleClickConfirmClose}>No</Button>
|
||||
<Button onClick={handleDelete} autoFocus>
|
||||
Yes
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</MDBox>
|
||||
<MDBox>
|
||||
<MDButton variant="gradient" color="dark" size="small">
|
||||
<Link href={editPath}>
|
||||
edit
|
||||
{tableMetaData?.label}
|
||||
</Link>
|
||||
</MDButton>
|
||||
</MDBox>
|
||||
</Grid>
|
||||
</MDBox>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
export default ViewContents;
|
||||
|
@ -26,26 +26,27 @@ import MDBox from "components/MDBox";
|
||||
import BaseLayout from "qqq/components/BaseLayout";
|
||||
import ViewContents from "./components/ViewContents";
|
||||
|
||||
function EntityView(): JSX.Element {
|
||||
const { id } = useParams();
|
||||
function EntityView(): 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}>
|
||||
<Grid item xs={12}>
|
||||
<ViewContents id={id} />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</MDBox>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</MDBox>
|
||||
</BaseLayout>
|
||||
);
|
||||
return (
|
||||
<BaseLayout>
|
||||
<MDBox mt={4}>
|
||||
<Grid container spacing={3}>
|
||||
<Grid item xs={12} lg={12}>
|
||||
<MDBox mb={3}>
|
||||
<Grid container spacing={3}>
|
||||
<Grid item xs={12}>
|
||||
<ViewContents id={id} />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</MDBox>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</MDBox>
|
||||
</BaseLayout>
|
||||
);
|
||||
}
|
||||
|
||||
export default EntityView;
|
||||
|
@ -45,415 +45,491 @@ import { QJobStarted } from "@kingsrook/qqq-frontend-core/lib/model/processes/QJ
|
||||
import { QJobComplete } from "@kingsrook/qqq-frontend-core/lib/model/processes/QJobComplete";
|
||||
import { QJobError } from "@kingsrook/qqq-frontend-core/lib/model/processes/QJobError";
|
||||
import { QJobRunning } from "@kingsrook/qqq-frontend-core/lib/model/processes/QJobRunning";
|
||||
import { DataGrid, GridColDef, GridRowParams, GridRowsProp } from "@mui/x-data-grid";
|
||||
import {
|
||||
DataGrid, GridColDef, GridRowParams, GridRowsProp,
|
||||
} from "@mui/x-data-grid";
|
||||
import QDynamicForm from "../../components/QDynamicForm";
|
||||
import MDTypography from "../../../components/MDTypography";
|
||||
|
||||
function getDynamicStepContent(
|
||||
stepIndex: number,
|
||||
step: any,
|
||||
formData: any,
|
||||
processError: string,
|
||||
processValues: any,
|
||||
recordConfig: any
|
||||
): JSX.Element {
|
||||
const { formFields, values, errors, touched } = formData;
|
||||
// console.log(`in getDynamicStepContent: step label ${step?.label}`);
|
||||
stepIndex: number,
|
||||
step: any,
|
||||
formData: any,
|
||||
processError: string,
|
||||
processValues: any,
|
||||
recordConfig: any,
|
||||
): JSX.Element
|
||||
{
|
||||
const {
|
||||
formFields, values, errors, touched,
|
||||
} = formData;
|
||||
// console.log(`in getDynamicStepContent: step label ${step?.label}`);
|
||||
|
||||
if (processError) {
|
||||
return (
|
||||
if (processError)
|
||||
{
|
||||
return (
|
||||
<>
|
||||
<MDTypography color="error" variant="h3">
|
||||
Error
|
||||
</MDTypography>
|
||||
<div>{processError}</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
if (!Object.keys(formFields).length)
|
||||
{
|
||||
// console.log("in getDynamicStepContent. No fields yet, so returning 'loading'");
|
||||
return <div>Loading...</div>;
|
||||
}
|
||||
|
||||
console.log(`in getDynamicStepContent. the step looks like: ${JSON.stringify(step)}`);
|
||||
|
||||
return (
|
||||
<>
|
||||
<MDTypography color="error" variant="h3">
|
||||
Error
|
||||
</MDTypography>
|
||||
<div>{processError}</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
if (!Object.keys(formFields).length) {
|
||||
// console.log("in getDynamicStepContent. No fields yet, so returning 'loading'");
|
||||
return <div>Loading...</div>;
|
||||
}
|
||||
|
||||
console.log(`in getDynamicStepContent. the step looks like: ${JSON.stringify(step)}`);
|
||||
|
||||
return (
|
||||
<>
|
||||
{step.formFields && <QDynamicForm formData={formData} formLabel={step.label} />}
|
||||
{step.viewFields && (
|
||||
<div>
|
||||
{step.viewFields.map((field: QFieldMetaData) => (
|
||||
<div key={field.name}>
|
||||
<b>{field.label}:</b> {processValues[field.name]}
|
||||
{step.formFields && <QDynamicForm formData={formData} formLabel={step.label} />}
|
||||
{step.viewFields && (
|
||||
<div>
|
||||
{step.viewFields.map((field: QFieldMetaData) => (
|
||||
<div key={field.name}>
|
||||
<b>
|
||||
{field.label}
|
||||
:
|
||||
</b>
|
||||
{" "}
|
||||
{processValues[field.name]}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{step.recordListFields && (
|
||||
<div>
|
||||
<b>Records:</b> <br />
|
||||
<MDBox height="100%">
|
||||
<DataGrid
|
||||
page={recordConfig.pageNo}
|
||||
disableSelectionOnClick
|
||||
autoHeight
|
||||
rows={recordConfig.rows}
|
||||
columns={recordConfig.columns}
|
||||
rowBuffer={10}
|
||||
rowCount={recordConfig.totalRecords}
|
||||
pageSize={recordConfig.rowsPerPage}
|
||||
rowsPerPageOptions={[10, 25, 50]}
|
||||
onPageSizeChange={recordConfig.handleRowsPerPageChange}
|
||||
onPageChange={recordConfig.handlePageChange}
|
||||
onRowClick={recordConfig.handleRowClick}
|
||||
paginationMode="server"
|
||||
density="compact"
|
||||
loading={recordConfig.loading}
|
||||
/>
|
||||
</MDBox>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
)}
|
||||
{step.recordListFields && (
|
||||
<div>
|
||||
<b>Records:</b>
|
||||
{" "}
|
||||
<br />
|
||||
<MDBox height="100%">
|
||||
<DataGrid
|
||||
page={recordConfig.pageNo}
|
||||
disableSelectionOnClick
|
||||
autoHeight
|
||||
rows={recordConfig.rows}
|
||||
columns={recordConfig.columns}
|
||||
rowBuffer={10}
|
||||
rowCount={recordConfig.totalRecords}
|
||||
pageSize={recordConfig.rowsPerPage}
|
||||
rowsPerPageOptions={[10, 25, 50]}
|
||||
onPageSizeChange={recordConfig.handleRowsPerPageChange}
|
||||
onPageChange={recordConfig.handlePageChange}
|
||||
onRowClick={recordConfig.handleRowClick}
|
||||
paginationMode="server"
|
||||
density="compact"
|
||||
loading={recordConfig.loading}
|
||||
/>
|
||||
</MDBox>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function trace(name: string, isComponent: boolean = false) {
|
||||
if (isComponent) {
|
||||
console.log(`COMPONENT: ${name}`);
|
||||
} else {
|
||||
console.log(` function: ${name}`);
|
||||
}
|
||||
function trace(name: string, isComponent: boolean = false)
|
||||
{
|
||||
if (isComponent)
|
||||
{
|
||||
console.log(`COMPONENT: ${name}`);
|
||||
}
|
||||
else
|
||||
{
|
||||
console.log(` function: ${name}`);
|
||||
}
|
||||
}
|
||||
|
||||
const qController = new QController("");
|
||||
|
||||
function ProcessRun(): JSX.Element {
|
||||
const { processName } = useParams();
|
||||
const [processUUID, setProcessUUID] = useState(null as string);
|
||||
const [jobUUID, setJobUUID] = useState(null as string);
|
||||
const [activeStepIndex, setActiveStepIndex] = useState(0);
|
||||
const [activeStep, setActiveStep] = useState(null as QFrontendStepMetaData);
|
||||
const [newStep, setNewStep] = useState(null);
|
||||
const [steps, setSteps] = useState([] as QFrontendStepMetaData[]);
|
||||
const [needInitialLoad, setNeedInitialLoad] = useState(true);
|
||||
const [processMetaData, setProcessMetaData] = useState(null);
|
||||
const [processValues, setProcessValues] = useState({} as any);
|
||||
const [lastProcessResponse, setLastProcessResponse] = useState(
|
||||
null as QJobStarted | QJobComplete | QJobError | QJobRunning
|
||||
);
|
||||
const [formId, setFormId] = useState("");
|
||||
const [formFields, setFormFields] = useState({});
|
||||
const [initialValues, setInitialValues] = useState({});
|
||||
const [validations, setValidations] = useState({});
|
||||
const [needToCheckJobStatus, setNeedToCheckJobStatus] = useState(false);
|
||||
const [needRecords, setNeedRecords] = useState(false);
|
||||
const [processError, setProcessError] = useState(null as string);
|
||||
const [recordConfig, setRecordConfig] = useState({} as any);
|
||||
const onLastStep = activeStepIndex === steps.length - 2;
|
||||
const noMoreSteps = activeStepIndex === steps.length - 1;
|
||||
function ProcessRun(): JSX.Element
|
||||
{
|
||||
const { processName } = useParams();
|
||||
const [processUUID, setProcessUUID] = useState(null as string);
|
||||
const [jobUUID, setJobUUID] = useState(null as string);
|
||||
const [activeStepIndex, setActiveStepIndex] = useState(0);
|
||||
const [activeStep, setActiveStep] = useState(null as QFrontendStepMetaData);
|
||||
const [newStep, setNewStep] = useState(null);
|
||||
const [steps, setSteps] = useState([] as QFrontendStepMetaData[]);
|
||||
const [needInitialLoad, setNeedInitialLoad] = useState(true);
|
||||
const [processMetaData, setProcessMetaData] = useState(null);
|
||||
const [processValues, setProcessValues] = useState({} as any);
|
||||
const [lastProcessResponse, setLastProcessResponse] = useState(
|
||||
null as QJobStarted | QJobComplete | QJobError | QJobRunning,
|
||||
);
|
||||
const [formId, setFormId] = useState("");
|
||||
const [formFields, setFormFields] = useState({});
|
||||
const [initialValues, setInitialValues] = useState({});
|
||||
const [validations, setValidations] = useState({});
|
||||
const [needToCheckJobStatus, setNeedToCheckJobStatus] = useState(false);
|
||||
const [needRecords, setNeedRecords] = useState(false);
|
||||
const [processError, setProcessError] = useState(null as string);
|
||||
const [recordConfig, setRecordConfig] = useState({} as any);
|
||||
const onLastStep = activeStepIndex === steps.length - 2;
|
||||
const noMoreSteps = activeStepIndex === steps.length - 1;
|
||||
|
||||
trace("ProcessRun", true);
|
||||
trace("ProcessRun", true);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// handle moving to another step in the process - e.g., after the backend told us what screen to show next. //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
useEffect(() => {
|
||||
trace("updateActiveStep");
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// handle moving to another step in the process - e.g., after the backend told us what screen to show next. //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
useEffect(() =>
|
||||
{
|
||||
trace("updateActiveStep");
|
||||
|
||||
if (!processMetaData) {
|
||||
console.log("No process meta data yet, so returning early");
|
||||
return;
|
||||
}
|
||||
|
||||
// console.log(`Steps are: ${steps}`);
|
||||
// console.log(`Setting step to ${newStep}`);
|
||||
let newIndex = null;
|
||||
if (typeof newStep === "number") {
|
||||
newIndex = newStep as number;
|
||||
} else if (typeof newStep === "string") {
|
||||
for (let i = 0; i < steps.length; i++) {
|
||||
if (steps[i].name === newStep) {
|
||||
newIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (newIndex === null) {
|
||||
setProcessError(`Unknown process step ${newStep}.`);
|
||||
}
|
||||
setActiveStepIndex(newIndex);
|
||||
|
||||
if (steps) {
|
||||
const activeStep = steps[newIndex];
|
||||
setActiveStep(activeStep);
|
||||
setFormId(activeStep.name);
|
||||
|
||||
///////////////////////////////////////////////////
|
||||
// if this step has form fields, set up the form //
|
||||
///////////////////////////////////////////////////
|
||||
if (activeStep.formFields) {
|
||||
const { dynamicFormFields, formValidations } = DynamicFormUtils.getFormData(
|
||||
activeStep.formFields
|
||||
);
|
||||
|
||||
const initialValues: any = {};
|
||||
activeStep.formFields.forEach((field) => {
|
||||
initialValues[field.name] = processValues[field.name];
|
||||
});
|
||||
|
||||
setFormFields(dynamicFormFields);
|
||||
setInitialValues(initialValues);
|
||||
setValidations(Yup.object().shape(formValidations));
|
||||
if (!processMetaData)
|
||||
{
|
||||
console.log("No process meta data yet, so returning early");
|
||||
return;
|
||||
}
|
||||
|
||||
if (activeStep.recordListFields) {
|
||||
const newRecordConfig = {} as any;
|
||||
newRecordConfig.pageNo = 1;
|
||||
newRecordConfig.rowsPerPage = 20;
|
||||
newRecordConfig.columns = [] as GridColDef[];
|
||||
newRecordConfig.rows = [];
|
||||
newRecordConfig.totalRecords = 0;
|
||||
newRecordConfig.handleRowsPerPageChange = null;
|
||||
newRecordConfig.handlePageChange = null;
|
||||
newRecordConfig.handleRowClick = null;
|
||||
newRecordConfig.loading = true;
|
||||
|
||||
activeStep.recordListFields.forEach((field) => {
|
||||
newRecordConfig.columns.push({ field: field.name, headerName: field.label });
|
||||
});
|
||||
|
||||
setRecordConfig(newRecordConfig);
|
||||
setNeedRecords(true);
|
||||
// console.log(`Steps are: ${steps}`);
|
||||
// console.log(`Setting step to ${newStep}`);
|
||||
let newIndex = null;
|
||||
if (typeof newStep === "number")
|
||||
{
|
||||
newIndex = newStep as number;
|
||||
}
|
||||
else if (typeof newStep === "string")
|
||||
{
|
||||
for (let i = 0; i < steps.length; i++)
|
||||
{
|
||||
if (steps[i].name === newStep)
|
||||
{
|
||||
newIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (newIndex === null)
|
||||
{
|
||||
setProcessError(`Unknown process step ${newStep}.`);
|
||||
}
|
||||
setActiveStepIndex(newIndex);
|
||||
|
||||
if (steps)
|
||||
{
|
||||
const activeStep = steps[newIndex];
|
||||
setActiveStep(activeStep);
|
||||
setFormId(activeStep.name);
|
||||
|
||||
///////////////////////////////////////////////////
|
||||
// if this step has form fields, set up the form //
|
||||
///////////////////////////////////////////////////
|
||||
if (activeStep.formFields)
|
||||
{
|
||||
const { dynamicFormFields, formValidations } = DynamicFormUtils.getFormData(
|
||||
activeStep.formFields,
|
||||
);
|
||||
|
||||
const initialValues: any = {};
|
||||
activeStep.formFields.forEach((field) =>
|
||||
{
|
||||
initialValues[field.name] = processValues[field.name];
|
||||
});
|
||||
|
||||
setFormFields(dynamicFormFields);
|
||||
setInitialValues(initialValues);
|
||||
setValidations(Yup.object().shape(formValidations));
|
||||
}
|
||||
|
||||
if (activeStep.recordListFields)
|
||||
{
|
||||
const newRecordConfig = {} as any;
|
||||
newRecordConfig.pageNo = 1;
|
||||
newRecordConfig.rowsPerPage = 20;
|
||||
newRecordConfig.columns = [] as GridColDef[];
|
||||
newRecordConfig.rows = [];
|
||||
newRecordConfig.totalRecords = 0;
|
||||
newRecordConfig.handleRowsPerPageChange = null;
|
||||
newRecordConfig.handlePageChange = null;
|
||||
newRecordConfig.handleRowClick = null;
|
||||
newRecordConfig.loading = true;
|
||||
|
||||
activeStep.recordListFields.forEach((field) =>
|
||||
{
|
||||
newRecordConfig.columns.push({ field: field.name, headerName: field.label });
|
||||
});
|
||||
|
||||
setRecordConfig(newRecordConfig);
|
||||
setNeedRecords(true);
|
||||
}
|
||||
|
||||
// console.log(`in updateActiveStep: formFields ${JSON.stringify(dynamicFormFields)}`);
|
||||
// console.log(`in updateActiveStep: initialValues ${JSON.stringify(initialValues)}`);
|
||||
}
|
||||
}, [newStep]);
|
||||
|
||||
useEffect(() => {
|
||||
if (needRecords) {
|
||||
setNeedRecords(false);
|
||||
(async () => {
|
||||
const records = await qController.processRecords(
|
||||
processName,
|
||||
processUUID,
|
||||
recordConfig.rowsPerPage * (recordConfig.pageNo - 1),
|
||||
recordConfig.rowsPerPage
|
||||
);
|
||||
recordConfig.loading = false;
|
||||
recordConfig.rows = [];
|
||||
let rowId = 0;
|
||||
records.forEach((record) => {
|
||||
const row = Object.fromEntries(record.values.entries());
|
||||
if (!row.id) {
|
||||
row.id = ++rowId;
|
||||
}
|
||||
recordConfig.rows.push(row);
|
||||
});
|
||||
// todo count?
|
||||
recordConfig.totalRecords = records.length;
|
||||
setRecordConfig(recordConfig);
|
||||
})();
|
||||
}
|
||||
}, [needRecords]);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// handle a response from the server - e.g., after starting a backend job, or getting its status/result //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
useEffect(() => {
|
||||
if (lastProcessResponse) {
|
||||
trace("handleProcessResponse");
|
||||
setLastProcessResponse(null);
|
||||
if (lastProcessResponse instanceof QJobComplete) {
|
||||
const qJobComplete = lastProcessResponse as QJobComplete;
|
||||
console.log("Setting new step.");
|
||||
setNewStep(qJobComplete.nextStep);
|
||||
setProcessValues(qJobComplete.values);
|
||||
// console.log(`Updated process values: ${JSON.stringify(qJobComplete.values)}`);
|
||||
} else if (lastProcessResponse instanceof QJobStarted) {
|
||||
const qJobStarted = lastProcessResponse as QJobStarted;
|
||||
setJobUUID(qJobStarted.jobUUID);
|
||||
setNeedToCheckJobStatus(true);
|
||||
} else if (lastProcessResponse instanceof QJobRunning) {
|
||||
const qJobRunning = lastProcessResponse as QJobRunning;
|
||||
setNeedToCheckJobStatus(true);
|
||||
} else if (lastProcessResponse instanceof QJobError) {
|
||||
const qJobError = lastProcessResponse as QJobError;
|
||||
console.log(`Got an error from the backend... ${qJobError.error}`);
|
||||
setProcessError(qJobError.error);
|
||||
}
|
||||
}
|
||||
}, [lastProcessResponse]);
|
||||
}, [newStep]);
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
// while a backend async job is running, periodically check its status //
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
useEffect(() => {
|
||||
if (needToCheckJobStatus) {
|
||||
trace("checkJobStatus");
|
||||
setNeedToCheckJobStatus(false);
|
||||
(async () => {
|
||||
setTimeout(async () => {
|
||||
const processResponse = await qController.processJobStatus(
|
||||
processName,
|
||||
processUUID,
|
||||
jobUUID
|
||||
);
|
||||
setLastProcessResponse(processResponse);
|
||||
}, 1500);
|
||||
})();
|
||||
}
|
||||
}, [needToCheckJobStatus]);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////
|
||||
// do the initial load of data for the process - that is, meta data, plus the init step //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////
|
||||
if (needInitialLoad) {
|
||||
trace("initialLoad");
|
||||
setNeedInitialLoad(false);
|
||||
(async () => {
|
||||
const { search } = useLocation();
|
||||
const urlSearchParams = new URLSearchParams(search);
|
||||
let queryStringForInit = null;
|
||||
if (urlSearchParams.get("recordIds")) {
|
||||
queryStringForInit = `recordsParam=recordIds&recordIds=${urlSearchParams.get("recordIds")}`;
|
||||
} else if (urlSearchParams.get("filterJSON")) {
|
||||
queryStringForInit = `recordsParam=filterJSON&filterJSON=${urlSearchParams.get(
|
||||
"filterJSON"
|
||||
)}`;
|
||||
useEffect(() =>
|
||||
{
|
||||
if (needRecords)
|
||||
{
|
||||
setNeedRecords(false);
|
||||
(async () =>
|
||||
{
|
||||
const records = await qController.processRecords(
|
||||
processName,
|
||||
processUUID,
|
||||
recordConfig.rowsPerPage * (recordConfig.pageNo - 1),
|
||||
recordConfig.rowsPerPage,
|
||||
);
|
||||
recordConfig.loading = false;
|
||||
recordConfig.rows = [];
|
||||
let rowId = 0;
|
||||
records.forEach((record) =>
|
||||
{
|
||||
const row = Object.fromEntries(record.values.entries());
|
||||
if (!row.id)
|
||||
{
|
||||
row.id = ++rowId;
|
||||
}
|
||||
recordConfig.rows.push(row);
|
||||
});
|
||||
// todo count?
|
||||
recordConfig.totalRecords = records.length;
|
||||
setRecordConfig(recordConfig);
|
||||
})();
|
||||
}
|
||||
// todo once saved filters exist
|
||||
//else if(urlSearchParams.get("filterId")) {
|
||||
// queryStringForInit = `recordsParam=filterId&filterId=${urlSearchParams.get("filterId")}`
|
||||
// }
|
||||
}, [needRecords]);
|
||||
|
||||
console.log(`@dk: Query String for init: ${queryStringForInit}`);
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// handle a response from the server - e.g., after starting a backend job, or getting its status/result //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
useEffect(() =>
|
||||
{
|
||||
if (lastProcessResponse)
|
||||
{
|
||||
trace("handleProcessResponse");
|
||||
setLastProcessResponse(null);
|
||||
if (lastProcessResponse instanceof QJobComplete)
|
||||
{
|
||||
const qJobComplete = lastProcessResponse as QJobComplete;
|
||||
console.log("Setting new step.");
|
||||
setNewStep(qJobComplete.nextStep);
|
||||
setProcessValues(qJobComplete.values);
|
||||
// console.log(`Updated process values: ${JSON.stringify(qJobComplete.values)}`);
|
||||
}
|
||||
else if (lastProcessResponse instanceof QJobStarted)
|
||||
{
|
||||
const qJobStarted = lastProcessResponse as QJobStarted;
|
||||
setJobUUID(qJobStarted.jobUUID);
|
||||
setNeedToCheckJobStatus(true);
|
||||
}
|
||||
else if (lastProcessResponse instanceof QJobRunning)
|
||||
{
|
||||
const qJobRunning = lastProcessResponse as QJobRunning;
|
||||
setNeedToCheckJobStatus(true);
|
||||
}
|
||||
else if (lastProcessResponse instanceof QJobError)
|
||||
{
|
||||
const qJobError = lastProcessResponse as QJobError;
|
||||
console.log(`Got an error from the backend... ${qJobError.error}`);
|
||||
setProcessError(qJobError.error);
|
||||
}
|
||||
}
|
||||
}, [lastProcessResponse]);
|
||||
|
||||
const processMetaData = await qController.loadProcessMetaData(processName);
|
||||
// console.log(processMetaData);
|
||||
setProcessMetaData(processMetaData);
|
||||
setSteps(processMetaData.frontendSteps);
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
// while a backend async job is running, periodically check its status //
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
useEffect(() =>
|
||||
{
|
||||
if (needToCheckJobStatus)
|
||||
{
|
||||
trace("checkJobStatus");
|
||||
setNeedToCheckJobStatus(false);
|
||||
(async () =>
|
||||
{
|
||||
setTimeout(async () =>
|
||||
{
|
||||
const processResponse = await qController.processJobStatus(
|
||||
processName,
|
||||
processUUID,
|
||||
jobUUID,
|
||||
);
|
||||
setLastProcessResponse(processResponse);
|
||||
}, 1500);
|
||||
})();
|
||||
}
|
||||
}, [needToCheckJobStatus]);
|
||||
|
||||
const processResponse = await qController.processInit(processName, queryStringForInit);
|
||||
setProcessUUID(processResponse.processUUID);
|
||||
setLastProcessResponse(processResponse);
|
||||
//////////////////////////////////////////////////////////////////////////////////////////
|
||||
// do the initial load of data for the process - that is, meta data, plus the init step //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////
|
||||
if (needInitialLoad)
|
||||
{
|
||||
trace("initialLoad");
|
||||
setNeedInitialLoad(false);
|
||||
(async () =>
|
||||
{
|
||||
const { search } = useLocation();
|
||||
const urlSearchParams = new URLSearchParams(search);
|
||||
let queryStringForInit = null;
|
||||
if (urlSearchParams.get("recordIds"))
|
||||
{
|
||||
queryStringForInit = `recordsParam=recordIds&recordIds=${urlSearchParams.get(
|
||||
"recordIds",
|
||||
)}`;
|
||||
}
|
||||
else if (urlSearchParams.get("filterJSON"))
|
||||
{
|
||||
queryStringForInit = `recordsParam=filterJSON&filterJSON=${urlSearchParams.get(
|
||||
"filterJSON",
|
||||
)}`;
|
||||
}
|
||||
// todo once saved filters exist
|
||||
//else if(urlSearchParams.get("filterId")) {
|
||||
// queryStringForInit = `recordsParam=filterId&filterId=${urlSearchParams.get("filterId")}`
|
||||
// }
|
||||
|
||||
console.log(`@dk: Query String for init: ${queryStringForInit}`);
|
||||
|
||||
const processMetaData = await qController.loadProcessMetaData(processName);
|
||||
// console.log(processMetaData);
|
||||
setProcessMetaData(processMetaData);
|
||||
setSteps(processMetaData.frontendSteps);
|
||||
|
||||
const processResponse = await qController.processInit(processName, queryStringForInit);
|
||||
setProcessUUID(processResponse.processUUID);
|
||||
setLastProcessResponse(processResponse);
|
||||
// console.log(processResponse);
|
||||
})();
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// handle the back button - todo - not really done at all //
|
||||
// e.g., qqq needs to say when back is or isn't allowed, and we need to hit the backend upon backs. //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
const handleBack = () => {
|
||||
trace("handleBack");
|
||||
setNewStep(activeStepIndex - 1);
|
||||
};
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// handle the back button - todo - not really done at all //
|
||||
// e.g., qqq needs to say when back is or isn't allowed, and we need to hit the backend upon backs. //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
const handleBack = () =>
|
||||
{
|
||||
trace("handleBack");
|
||||
setNewStep(activeStepIndex - 1);
|
||||
};
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////
|
||||
// handle user submitting the form - which in qqq means moving forward from any screen. //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////
|
||||
const handleSubmit = async (values: any, actions: any) => {
|
||||
trace("handleSubmit");
|
||||
//////////////////////////////////////////////////////////////////////////////////////////
|
||||
// handle user submitting the form - which in qqq means moving forward from any screen. //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////
|
||||
const handleSubmit = async (values: any, actions: any) =>
|
||||
{
|
||||
trace("handleSubmit");
|
||||
|
||||
// todo - post?
|
||||
let queryString = "";
|
||||
Object.keys(values).forEach((key) => {
|
||||
queryString += `${key}=${encodeURIComponent(values[key])}&`;
|
||||
});
|
||||
// todo - post?
|
||||
let queryString = "";
|
||||
Object.keys(values).forEach((key) =>
|
||||
{
|
||||
queryString += `${key}=${encodeURIComponent(values[key])}&`;
|
||||
});
|
||||
|
||||
actions.setSubmitting(false);
|
||||
actions.resetForm();
|
||||
actions.setSubmitting(false);
|
||||
actions.resetForm();
|
||||
|
||||
const processResponse = await qController.processStep(
|
||||
processName,
|
||||
processUUID,
|
||||
activeStep.name,
|
||||
queryString
|
||||
);
|
||||
setLastProcessResponse(processResponse);
|
||||
};
|
||||
const processResponse = await qController.processStep(
|
||||
processName,
|
||||
processUUID,
|
||||
activeStep.name,
|
||||
queryString,
|
||||
);
|
||||
setLastProcessResponse(processResponse);
|
||||
};
|
||||
|
||||
return (
|
||||
<DashboardLayout>
|
||||
<DashboardNavbar />
|
||||
<MDBox py={3} mb={20} height="65vh">
|
||||
<Grid container justifyContent="center" alignItems="center" sx={{ height: "100%", mt: 8 }}>
|
||||
<Grid item xs={12} lg={8}>
|
||||
<Formik
|
||||
enableReinitialize
|
||||
initialValues={initialValues}
|
||||
validationSchema={validations}
|
||||
onSubmit={handleSubmit}
|
||||
return (
|
||||
<DashboardLayout>
|
||||
<DashboardNavbar />
|
||||
<MDBox py={3} mb={20} height="65vh">
|
||||
<Grid
|
||||
container
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
sx={{ height: "100%", mt: 8 }}
|
||||
>
|
||||
{({ values, errors, touched, isSubmitting }) => (
|
||||
<Form id={formId} autoComplete="off">
|
||||
<Card sx={{ height: "100%" }}>
|
||||
<MDBox mx={2} mt={-3}>
|
||||
<Stepper activeStep={activeStepIndex} alternativeLabel>
|
||||
{steps.map((step) => (
|
||||
<Step key={step.name}>
|
||||
<StepLabel>{step.label}</StepLabel>
|
||||
</Step>
|
||||
))}
|
||||
</Stepper>
|
||||
</MDBox>
|
||||
<MDBox p={3}>
|
||||
<MDBox>
|
||||
{/***************************************************************************
|
||||
** step content - e.g., the appropriate form or other screen for the step **
|
||||
***************************************************************************/}
|
||||
{getDynamicStepContent(
|
||||
activeStepIndex,
|
||||
activeStep,
|
||||
{
|
||||
values,
|
||||
touched,
|
||||
formFields,
|
||||
errors,
|
||||
},
|
||||
processError,
|
||||
processValues,
|
||||
recordConfig
|
||||
)}
|
||||
{/********************************
|
||||
** back &| next/submit buttons **
|
||||
********************************/}
|
||||
<MDBox mt={2} width="100%" display="flex" justifyContent="space-between">
|
||||
{true || activeStepIndex === 0 ? (
|
||||
<MDBox />
|
||||
) : (
|
||||
<MDButton variant="gradient" color="light" onClick={handleBack}>
|
||||
back
|
||||
</MDButton>
|
||||
)}
|
||||
{noMoreSteps || processError ? (
|
||||
<MDBox />
|
||||
) : (
|
||||
<MDButton
|
||||
disabled={isSubmitting}
|
||||
type="submit"
|
||||
variant="gradient"
|
||||
color="dark"
|
||||
>
|
||||
{onLastStep ? "submit" : "next"}
|
||||
</MDButton>
|
||||
)}
|
||||
</MDBox>
|
||||
</MDBox>
|
||||
</MDBox>
|
||||
</Card>
|
||||
</Form>
|
||||
)}
|
||||
</Formik>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</MDBox>
|
||||
<Footer />
|
||||
</DashboardLayout>
|
||||
);
|
||||
<Grid item xs={12} lg={8}>
|
||||
<Formik
|
||||
enableReinitialize
|
||||
initialValues={initialValues}
|
||||
validationSchema={validations}
|
||||
onSubmit={handleSubmit}
|
||||
>
|
||||
{({
|
||||
values, errors, touched, isSubmitting,
|
||||
}) => (
|
||||
<Form id={formId} autoComplete="off">
|
||||
<Card sx={{ height: "100%" }}>
|
||||
<MDBox mx={2} mt={-3}>
|
||||
<Stepper activeStep={activeStepIndex} alternativeLabel>
|
||||
{steps.map((step) => (
|
||||
<Step key={step.name}>
|
||||
<StepLabel>{step.label}</StepLabel>
|
||||
</Step>
|
||||
))}
|
||||
</Stepper>
|
||||
</MDBox>
|
||||
<MDBox p={3}>
|
||||
<MDBox>
|
||||
{/***************************************************************************
|
||||
** step content - e.g., the appropriate form or other screen for the step **
|
||||
***************************************************************************/}
|
||||
{getDynamicStepContent(
|
||||
activeStepIndex,
|
||||
activeStep,
|
||||
{
|
||||
values,
|
||||
touched,
|
||||
formFields,
|
||||
errors,
|
||||
},
|
||||
processError,
|
||||
processValues,
|
||||
recordConfig,
|
||||
)}
|
||||
{/********************************
|
||||
** back &| next/submit buttons **
|
||||
********************************/}
|
||||
<MDBox
|
||||
mt={2}
|
||||
width="100%"
|
||||
display="flex"
|
||||
justifyContent="space-between"
|
||||
>
|
||||
{true || activeStepIndex === 0 ? (
|
||||
<MDBox />
|
||||
) : (
|
||||
<MDButton
|
||||
variant="gradient"
|
||||
color="light"
|
||||
onClick={handleBack}
|
||||
>
|
||||
back
|
||||
</MDButton>
|
||||
)}
|
||||
{noMoreSteps || processError ? (
|
||||
<MDBox />
|
||||
) : (
|
||||
<MDButton
|
||||
disabled={isSubmitting}
|
||||
type="submit"
|
||||
variant="gradient"
|
||||
color="dark"
|
||||
>
|
||||
{onLastStep ? "submit" : "next"}
|
||||
</MDButton>
|
||||
)}
|
||||
</MDBox>
|
||||
</MDBox>
|
||||
</MDBox>
|
||||
</Card>
|
||||
</Form>
|
||||
)}
|
||||
</Formik>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</MDBox>
|
||||
<Footer />
|
||||
</DashboardLayout>
|
||||
);
|
||||
}
|
||||
|
||||
export default ProcessRun;
|
||||
|
@ -56,83 +56,85 @@ import { QController } from "@kingsrook/qqq-frontend-core/lib/controllers/QContr
|
||||
import EntityList from "./pages/entity-list";
|
||||
|
||||
const qqqRoutes = [
|
||||
{
|
||||
type: "collapse",
|
||||
name: "Brooklyn Alice",
|
||||
key: "brooklyn-alice",
|
||||
icon: <MDAvatar src={profilePicture} alt="Brooklyn Alice" size="sm" />,
|
||||
collapse: [
|
||||
{
|
||||
name: "My Profile",
|
||||
key: "my-profile",
|
||||
route: "/pages/profile/profile-overview",
|
||||
component: <ProfileOverview />,
|
||||
},
|
||||
{
|
||||
name: "Settings",
|
||||
key: "profile-settings",
|
||||
route: "/pages/account/settings",
|
||||
component: <Settings />,
|
||||
},
|
||||
{
|
||||
name: "Logout",
|
||||
key: "logout",
|
||||
route: "/authentication/sign-in/basic",
|
||||
component: <SignInBasic />,
|
||||
},
|
||||
],
|
||||
},
|
||||
{ type: "divider", key: "divider-0" },
|
||||
{
|
||||
type: "collapse",
|
||||
name: "Dashboards",
|
||||
key: "dashboards",
|
||||
icon: <Icon fontSize="medium">dashboard</Icon>,
|
||||
collapse: [
|
||||
{
|
||||
name: "Analytics",
|
||||
key: "analytics",
|
||||
route: "/dashboards/analytics",
|
||||
component: <Analytics />,
|
||||
},
|
||||
{
|
||||
name: "Sales",
|
||||
key: "sales",
|
||||
route: "/dashboards/sales",
|
||||
component: <Sales />,
|
||||
},
|
||||
],
|
||||
},
|
||||
{ type: "divider", key: "divider-1" },
|
||||
{ type: "title", title: "Tables", key: "title-docs" },
|
||||
{
|
||||
type: "collapse",
|
||||
name: "Brooklyn Alice",
|
||||
key: "brooklyn-alice",
|
||||
icon: <MDAvatar src={profilePicture} alt="Brooklyn Alice" size="sm" />,
|
||||
collapse: [
|
||||
{
|
||||
name: "My Profile",
|
||||
key: "my-profile",
|
||||
route: "/pages/profile/profile-overview",
|
||||
component: <ProfileOverview />,
|
||||
},
|
||||
{
|
||||
name: "Settings",
|
||||
key: "profile-settings",
|
||||
route: "/pages/account/settings",
|
||||
component: <Settings />,
|
||||
},
|
||||
{
|
||||
name: "Logout",
|
||||
key: "logout",
|
||||
route: "/authentication/sign-in/basic",
|
||||
component: <SignInBasic />,
|
||||
},
|
||||
],
|
||||
},
|
||||
{ type: "divider", key: "divider-0" },
|
||||
{
|
||||
type: "collapse",
|
||||
name: "Dashboards",
|
||||
key: "dashboards",
|
||||
icon: <Icon fontSize="medium">dashboard</Icon>,
|
||||
collapse: [
|
||||
{
|
||||
name: "Analytics",
|
||||
key: "analytics",
|
||||
route: "/dashboards/analytics",
|
||||
component: <Analytics />,
|
||||
},
|
||||
{
|
||||
name: "Sales",
|
||||
key: "sales",
|
||||
route: "/dashboards/sales",
|
||||
component: <Sales />,
|
||||
},
|
||||
],
|
||||
},
|
||||
{ type: "divider", key: "divider-1" },
|
||||
{ type: "title", title: "Tables", key: "title-docs" },
|
||||
];
|
||||
|
||||
const qController = new QController("");
|
||||
|
||||
(async () => {
|
||||
const metaData = await qController.loadMetaData();
|
||||
(async () =>
|
||||
{
|
||||
const metaData = await qController.loadMetaData();
|
||||
|
||||
// get the keys sorted
|
||||
const keys = [...metaData.tables.keys()].sort();
|
||||
const tableList = [] as any[];
|
||||
keys.forEach((key) => {
|
||||
const table = metaData.tables.get(key);
|
||||
tableList.push({
|
||||
name: `${table.label}`,
|
||||
key: table.name,
|
||||
route: `/${table.name}`,
|
||||
component: <EntityList table={table} />,
|
||||
});
|
||||
});
|
||||
// get the keys sorted
|
||||
const keys = [...metaData.tables.keys()].sort();
|
||||
const tableList = [] as any[];
|
||||
keys.forEach((key) =>
|
||||
{
|
||||
const table = metaData.tables.get(key);
|
||||
tableList.push({
|
||||
name: `${table.label}`,
|
||||
key: table.name,
|
||||
route: `/${table.name}`,
|
||||
component: <EntityList table={table} />,
|
||||
});
|
||||
});
|
||||
|
||||
const tables = {
|
||||
type: "collapse",
|
||||
name: "Tables",
|
||||
key: "tables",
|
||||
icon: <Icon fontSize="medium">dashboard</Icon>,
|
||||
collapse: tableList,
|
||||
};
|
||||
qqqRoutes.push(tables);
|
||||
const tables = {
|
||||
type: "collapse",
|
||||
name: "Tables",
|
||||
key: "tables",
|
||||
icon: <Icon fontSize="medium">dashboard</Icon>,
|
||||
collapse: tableList,
|
||||
};
|
||||
qqqRoutes.push(tables);
|
||||
})();
|
||||
|
||||
export default qqqRoutes;
|
||||
|
@ -27,36 +27,44 @@ import { QQueryFilter } from "@kingsrook/qqq-frontend-core/lib/model/query/QQuer
|
||||
** client wrapper of qqq backend
|
||||
**
|
||||
*******************************************************************************/
|
||||
class QClient {
|
||||
private static qController: QController;
|
||||
class QClient
|
||||
{
|
||||
private static qController: QController;
|
||||
|
||||
private static getInstance() {
|
||||
if (this.qController == null) {
|
||||
this.qController = new QController("");
|
||||
}
|
||||
private static getInstance()
|
||||
{
|
||||
if (this.qController == null)
|
||||
{
|
||||
this.qController = new QController("");
|
||||
}
|
||||
|
||||
return this.qController;
|
||||
}
|
||||
return this.qController;
|
||||
}
|
||||
|
||||
public static loadTableMetaData(tableName: string) {
|
||||
return this.getInstance().loadTableMetaData(tableName);
|
||||
}
|
||||
public static loadTableMetaData(tableName: string)
|
||||
{
|
||||
return this.getInstance().loadTableMetaData(tableName);
|
||||
}
|
||||
|
||||
public static loadMetaData() {
|
||||
return this.getInstance().loadMetaData();
|
||||
}
|
||||
public static loadMetaData()
|
||||
{
|
||||
return this.getInstance().loadMetaData();
|
||||
}
|
||||
|
||||
public static query(tableName: string, filter: QQueryFilter, limit: number, skip: number) {
|
||||
return this.getInstance()
|
||||
.query(tableName, filter, limit, skip)
|
||||
.catch((error) => {
|
||||
throw error;
|
||||
});
|
||||
}
|
||||
public static query(tableName: string, filter: QQueryFilter, limit: number, skip: number)
|
||||
{
|
||||
return this.getInstance()
|
||||
.query(tableName, filter, limit, skip)
|
||||
.catch((error) =>
|
||||
{
|
||||
throw error;
|
||||
});
|
||||
}
|
||||
|
||||
public static count(tableName: string) {
|
||||
return this.getInstance().count(tableName);
|
||||
}
|
||||
public static count(tableName: string)
|
||||
{
|
||||
return this.getInstance().count(tableName);
|
||||
}
|
||||
}
|
||||
|
||||
export default QClient;
|
||||
|
@ -26,18 +26,22 @@ import { QInstance } from "@kingsrook/qqq-frontend-core/lib/model/metaData/QInst
|
||||
** Utility class for working with QQQ Processes
|
||||
**
|
||||
*******************************************************************************/
|
||||
class QProcessUtils {
|
||||
public static getProcessesForTable(metaData: QInstance, tableName: string): QProcessMetaData[] {
|
||||
const matchingProcesses: QProcessMetaData[] = [];
|
||||
const processKeys = [...metaData.processes.keys()];
|
||||
processKeys.forEach((key) => {
|
||||
const process = metaData.processes.get(key);
|
||||
if (process.tableName === tableName) {
|
||||
matchingProcesses.push(process);
|
||||
}
|
||||
});
|
||||
return matchingProcesses;
|
||||
}
|
||||
class QProcessUtils
|
||||
{
|
||||
public static getProcessesForTable(metaData: QInstance, tableName: string): QProcessMetaData[]
|
||||
{
|
||||
const matchingProcesses: QProcessMetaData[] = [];
|
||||
const processKeys = [...metaData.processes.keys()];
|
||||
processKeys.forEach((key) =>
|
||||
{
|
||||
const process = metaData.processes.get(key);
|
||||
if (process.tableName === tableName)
|
||||
{
|
||||
matchingProcesses.push(process);
|
||||
}
|
||||
});
|
||||
return matchingProcesses;
|
||||
}
|
||||
}
|
||||
|
||||
export default QProcessUtils;
|
||||
|
Reference in New Issue
Block a user