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:
Tim Chamberlain
2022-07-12 11:35:24 -05:00
parent cc324fd76d
commit 87d3f070fe
26 changed files with 2099 additions and 1959 deletions

View File

@ -3,7 +3,20 @@
"browser": true, "browser": true,
"es2021": 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", "parser": "@typescript-eslint/parser",
"parserOptions": { "parserOptions": {
"ecmaFeatures": { "ecmaFeatures": {
@ -12,37 +25,68 @@
"ecmaVersion": "latest", "ecmaVersion": "latest",
"sourceType": "module" "sourceType": "module"
}, },
"plugins": ["react", "@typescript-eslint", "prettier"], "plugins": [
"react",
"@typescript-eslint"
],
"rules": { "rules": {
"prettier/prettier": [ "brace-style": [
2,
"allman"
],
"indent": [
"error", "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", "no-console": "off",
"react/prop-types": "off",
"no-shadow": "off", "no-shadow": "off",
"no-unused-vars": "off", "no-unused-vars": "off",
"no-plusplus": "off", "no-plusplus": "off",
"spaced-comment": "off", "spaced-comment": "off",
"object-shorthand": "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/jsx-props-no-spreading": "off",
"react/react-in-jsx-scope": "off", "react/react-in-jsx-scope": "off",
"import/extensions": [ "quotes": [
"error", "error",
"ignorePackages", "double"
{ "ts": "never", "tsx": "never", "js": "never" } ]
],
"react/jsx-filename-extension": ["warn", { "extensions": [".tsx"] }],
"import/no-extraneous-dependencies": ["error", { "devDependencies": true }]
}, },
"settings": { "settings": {
"import/resolver": { "import/resolver": {
"typescript": {} "typescript": {}
} }
},
"globals": {
"JSX": true
} }
} }

View File

@ -1,7 +0,0 @@
{
"printWidth": 100,
"trailingComma": "es5",
"semi": true,
"singleQuote": false,
"endOfLine": "auto"
}

102
package-lock.json generated
View File

@ -63,14 +63,11 @@
"@typescript-eslint/parser": "5.10.2", "@typescript-eslint/parser": "5.10.2",
"eslint": "8.8.0", "eslint": "8.8.0",
"eslint-config-airbnb": "19.0.4", "eslint-config-airbnb": "19.0.4",
"eslint-config-prettier": "8.3.0",
"eslint-import-resolver-typescript": "2.5.0", "eslint-import-resolver-typescript": "2.5.0",
"eslint-plugin-import": "2.25.4", "eslint-plugin-import": "2.25.4",
"eslint-plugin-jsx-a11y": "6.5.1", "eslint-plugin-jsx-a11y": "6.5.1",
"eslint-plugin-prettier": "4.0.0",
"eslint-plugin-react": "7.28.0", "eslint-plugin-react": "7.28.0",
"eslint-plugin-react-hooks": "4.3.0", "eslint-plugin-react-hooks": "4.3.0",
"prettier": "2.5.1",
"typescript": "^4.7.3" "typescript": "^4.7.3"
} }
}, },
@ -7533,18 +7530,6 @@
"semver": "bin/semver.js" "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": { "node_modules/eslint-config-react-app": {
"version": "7.0.1", "version": "7.0.1",
"resolved": "https://registry.npmjs.org/eslint-config-react-app/-/eslint-config-react-app-7.0.1.tgz", "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": ">=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": { "node_modules/eslint-plugin-react": {
"version": "7.28.0", "version": "7.28.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.28.0.tgz", "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", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" "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": { "node_modules/fast-glob": {
"version": "3.2.11", "version": "3.2.11",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz",
@ -13484,30 +13442,6 @@
"node": ">= 0.8.0" "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": { "node_modules/pretty-bytes": {
"version": "5.6.0", "version": "5.6.0",
"resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", "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": { "eslint-config-react-app": {
"version": "7.0.1", "version": "7.0.1",
"resolved": "https://registry.npmjs.org/eslint-config-react-app/-/eslint-config-react-app-7.0.1.tgz", "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": { "eslint-plugin-react": {
"version": "7.28.0", "version": "7.28.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.28.0.tgz", "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", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" "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": { "fast-glob": {
"version": "3.2.11", "version": "3.2.11",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", "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", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
"integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==" "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": { "pretty-bytes": {
"version": "5.6.0", "version": "5.6.0",
"resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz",

View File

@ -87,14 +87,11 @@
"@typescript-eslint/parser": "5.10.2", "@typescript-eslint/parser": "5.10.2",
"eslint": "8.8.0", "eslint": "8.8.0",
"eslint-config-airbnb": "19.0.4", "eslint-config-airbnb": "19.0.4",
"eslint-config-prettier": "8.3.0",
"eslint-import-resolver-typescript": "2.5.0", "eslint-import-resolver-typescript": "2.5.0",
"eslint-plugin-import": "2.25.4", "eslint-plugin-import": "2.25.4",
"eslint-plugin-jsx-a11y": "6.5.1", "eslint-plugin-jsx-a11y": "6.5.1",
"eslint-plugin-prettier": "4.0.0",
"eslint-plugin-react": "7.28.0", "eslint-plugin-react": "7.28.0",
"eslint-plugin-react-hooks": "4.3.0", "eslint-plugin-react-hooks": "4.3.0",
"prettier": "2.5.1",
"typescript": "^4.7.3" "typescript": "^4.7.3"
}, },
"main": "qqq-frontend-material-dashboard.js", "main": "qqq-frontend-material-dashboard.js",

View File

@ -23,7 +23,9 @@ import React, {
} from "react"; } from "react";
// react-router components // react-router components
import { Routes, Route, Navigate, useLocation } from "react-router-dom"; import {
Routes, Route, Navigate, useLocation,
} from "react-router-dom";
// @mui material components // @mui material components
import { ThemeProvider } from "@mui/material/styles"; import { ThemeProvider } from "@mui/material/styles";
@ -58,7 +60,8 @@ import EntityView from "./qqq/pages/entity-view";
import EntityEdit from "./qqq/pages/entity-edit"; import EntityEdit from "./qqq/pages/entity-edit";
import ProcessRun from "./qqq/pages/process-run"; import ProcessRun from "./qqq/pages/process-run";
export default function App() { export default function App()
{
const [controller, dispatch] = useMaterialUIController(); const [controller, dispatch] = useMaterialUIController();
const { const {
miniSidenav, miniSidenav,
@ -74,16 +77,20 @@ export default function App() {
const { pathname } = useLocation(); const { pathname } = useLocation();
// Open sidenav when mouse enter on mini sidenav // Open sidenav when mouse enter on mini sidenav
const handleOnMouseEnter = () => { const handleOnMouseEnter = () =>
if (miniSidenav && !onMouseEnter) { {
if (miniSidenav && !onMouseEnter)
{
setMiniSidenav(dispatch, false); setMiniSidenav(dispatch, false);
setOnMouseEnter(true); setOnMouseEnter(true);
} }
}; };
// Close sidenav when mouse leave mini sidenav // Close sidenav when mouse leave mini sidenav
const handleOnMouseLeave = () => { const handleOnMouseLeave = () =>
if (onMouseEnter) { {
if (onMouseEnter)
{
setMiniSidenav(dispatch, true); setMiniSidenav(dispatch, true);
setOnMouseEnter(false); setOnMouseEnter(false);
} }
@ -93,34 +100,38 @@ export default function App() {
const handleConfiguratorOpen = () => setOpenConfigurator(dispatch, !openConfigurator); const handleConfiguratorOpen = () => setOpenConfigurator(dispatch, !openConfigurator);
// Setting the dir attribute for the body element // Setting the dir attribute for the body element
useEffect(() => { useEffect(() =>
{
document.body.setAttribute("dir", direction); document.body.setAttribute("dir", direction);
}, [direction]); }, [direction]);
// Setting page scroll to 0 when changing the route // Setting page scroll to 0 when changing the route
useEffect(() => { useEffect(() =>
{
document.documentElement.scrollTop = 0; document.documentElement.scrollTop = 0;
document.scrollingElement.scrollTop = 0; document.scrollingElement.scrollTop = 0;
}, [pathname]); }, [pathname]);
const getRoutes = (allRoutes: any[]): any => const getRoutes = (allRoutes: any[]): any => allRoutes.map(
allRoutes.map(
(route: { (route: {
collapse: any; collapse: any;
route: string; route: string;
component: ReactElement<any, string | JSXElementConstructor<any>>; component: ReactElement<any, string | JSXElementConstructor<any>>;
key: Key; key: Key;
}) => { }) =>
if (route.collapse) { {
if (route.collapse)
{
return getRoutes(route.collapse); return getRoutes(route.collapse);
} }
if (route.route) { if (route.route)
{
return <Route path={route.route} element={route.component} key={route.key} />; return <Route path={route.route} element={route.component} key={route.key} />;
} }
return null; return null;
} },
); );
const configsButton = ( const configsButton = (
@ -173,11 +184,11 @@ export default function App() {
{layout === "vr" && <Configurator />} {layout === "vr" && <Configurator />}
<Routes> <Routes>
<Route path="*" element={<Navigate to="/dashboards/analytics" />} /> <Route path="*" element={<Navigate to="/dashboards/analytics" />} />
<Route path="/:tableName" element={entityListElement} key="entity-list" />; <Route path="/:tableName" element={entityListElement} key="entity-list" />
<Route path="/:tableName/create" element={entityCreateElement} key="entity-create" />; <Route path="/:tableName/create" element={entityCreateElement} key="entity-create" />
<Route path="/processes/:processName" element={processRunElement} key="process-run" />; <Route path="/processes/:processName" element={processRunElement} key="process-run" />
<Route path="/:tableName/:id" element={entityViewElement} key="entity-view" />; <Route path="/:tableName/:id" element={entityViewElement} key="entity-view" />
<Route path="/:tableName/:id/edit" element={entityEditElement} key="entity-edit" />; <Route path="/:tableName/:id/edit" element={entityEditElement} key="entity-edit" />
{getRoutes(routes)} {getRoutes(routes)}
</Routes> </Routes>
</ThemeProvider> </ThemeProvider>

View File

@ -26,7 +26,7 @@ ReactDOM.render(
<App /> <App />
</MaterialUIControllerProvider> </MaterialUIControllerProvider>
</BrowserRouter>, </BrowserRouter>,
document.getElementById("root") document.getElementById("root"),
); );
export * from "components/MDButton"; export * from "components/MDButton";

View File

@ -30,12 +30,15 @@ interface Props {
children: ReactNode; children: ReactNode;
} }
function BaseLayout({ stickyNavbar, children }: Props): JSX.Element { function BaseLayout({ stickyNavbar, children }: Props): JSX.Element
{
const [tabsOrientation, setTabsOrientation] = useState<"horizontal" | "vertical">("horizontal"); const [tabsOrientation, setTabsOrientation] = useState<"horizontal" | "vertical">("horizontal");
useEffect(() => { useEffect(() =>
{
// A function that sets the orientation state of the tabs. // A function that sets the orientation state of the tabs.
function handleTabsOrientation() { function handleTabsOrientation()
{
return window.innerWidth < breakpoints.values.sm return window.innerWidth < breakpoints.values.sm
? setTabsOrientation("vertical") ? setTabsOrientation("vertical")
: setTabsOrientation("horizontal"); : setTabsOrientation("horizontal");

View File

@ -34,7 +34,8 @@ interface Props {
| "dark"; | "dark";
} }
function CustomerCell({ image, name, color }: Props): JSX.Element { function CustomerCell({ image, name, color }: Props): JSX.Element
{
return ( return (
<MDBox display="flex" alignItems="center"> <MDBox display="flex" alignItems="center">
<MDBox mr={1}> <MDBox mr={1}>

View File

@ -22,13 +22,15 @@ interface Props {
suffix?: string | boolean; suffix?: string | boolean;
} }
function DefaultCell({ value, suffix }: Props): JSX.Element { function DefaultCell({ value, suffix }: Props): JSX.Element
{
return ( return (
<MDTypography variant="caption" fontWeight="medium" color="text"> <MDTypography variant="caption" fontWeight="medium" color="text">
{value} {value}
{suffix && ( {suffix && (
<MDTypography variant="caption" fontWeight="medium" color="secondary"> <MDTypography variant="caption" fontWeight="medium" color="secondary">
&nbsp;&nbsp;{suffix} &nbsp;&nbsp;
{suffix}
</MDTypography> </MDTypography>
)} )}
</MDTypography> </MDTypography>

View File

@ -27,7 +27,8 @@ interface Props {
checked?: boolean; checked?: boolean;
} }
function IdCell({ id, checked }: Props): JSX.Element { function IdCell({ id, checked }: Props): JSX.Element
{
const pathParts = window.location.pathname.split("/"); const pathParts = window.location.pathname.split("/");
const tableName = pathParts[1]; const tableName = pathParts[1];
const href = `/${tableName}/${id}`; const href = `/${tableName}/${id}`;

View File

@ -38,7 +38,8 @@ interface Props {
status: string; status: string;
} }
function StatusCell({ icon, color, status }: Props): JSX.Element { function StatusCell({ icon, color, status }: Props): JSX.Element
{
return ( return (
<MDBox display="flex" alignItems="center"> <MDBox display="flex" alignItems="center">
<MDBox mr={1}> <MDBox mr={1}>

View File

@ -28,7 +28,8 @@ interface Props {
id?: string; id?: string;
} }
function EntityForm({ id }: Props): JSX.Element { function EntityForm({ id }: Props): JSX.Element
{
const qController = new QController(""); const qController = new QController("");
const { tableName } = useParams(); const { tableName } = useParams();
@ -42,33 +43,42 @@ function EntityForm({ id }: Props): JSX.Element {
const [tableMetaData, setTableMetaData] = useState(null); const [tableMetaData, setTableMetaData] = useState(null);
const [, forceUpdate] = useReducer((x) => x + 1, 0); const [, forceUpdate] = useReducer((x) => x + 1, 0);
function getDynamicStepContent(formData: any): JSX.Element { function getDynamicStepContent(formData: any): JSX.Element
const { formFields, values, errors, touched } = formData; {
const {
formFields, values, errors, touched,
} = formData;
if (!Object.keys(formFields).length) { if (!Object.keys(formFields).length)
{
return <div>Loading...</div>; return <div>Loading...</div>;
} }
return <QDynamicForm formData={formData} primaryKeyId={tableMetaData.primaryKeyField} />; return <QDynamicForm formData={formData} primaryKeyId={tableMetaData.primaryKeyField} />;
} }
if (!asyncLoadInited) { if (!asyncLoadInited)
{
setAsyncLoadInited(true); setAsyncLoadInited(true);
(async () => { (async () =>
{
const tableMetaData = await qController.loadTableMetaData(tableName); const tableMetaData = await qController.loadTableMetaData(tableName);
setTableMetaData(tableMetaData); setTableMetaData(tableMetaData);
const fieldArray = [] as QFieldMetaData[]; const fieldArray = [] as QFieldMetaData[];
const sortedKeys = [...tableMetaData.fields.keys()].sort(); const sortedKeys = [...tableMetaData.fields.keys()].sort();
sortedKeys.forEach((key) => { sortedKeys.forEach((key) =>
{
const fieldMetaData = tableMetaData.fields.get(key); const fieldMetaData = tableMetaData.fields.get(key);
fieldArray.push(fieldMetaData); fieldArray.push(fieldMetaData);
}); });
if (id !== null) { if (id !== null)
{
const record = await qController.get(tableName, id); const record = await qController.get(tableName, id);
tableMetaData.fields.forEach((fieldMetaData, key) => { tableMetaData.fields.forEach((fieldMetaData, key) =>
{
initialValues[key] = record.values.get(key); initialValues[key] = record.values.get(key);
}); });
@ -84,39 +94,46 @@ function EntityForm({ id }: Props): JSX.Element {
})(); })();
} }
const handleSubmit = async (values: any, actions: any) => { const handleSubmit = async (values: any, actions: any) =>
{
actions.setSubmitting(true); actions.setSubmitting(true);
await (async () => { await (async () =>
if (id !== null) { {
if (id !== null)
{
await qController await qController
.update(tableName, id, values) .update(tableName, id, values)
.then((record) => { .then((record) =>
{
window.location.href = `/${tableName}/${record.values.get( window.location.href = `/${tableName}/${record.values.get(
tableMetaData.primaryKeyField tableMetaData.primaryKeyField,
)}`; )}`;
}) })
.catch((error) => { .catch((error) =>
{
setAlertContent(error.response.data.error); setAlertContent(error.response.data.error);
}); });
} else { }
else
{
await qController await qController
.create(tableName, values) .create(tableName, values)
.then((record) => { .then((record) =>
{
window.location.href = `/${tableName}/${record.values.get( window.location.href = `/${tableName}/${record.values.get(
tableMetaData.primaryKeyField tableMetaData.primaryKeyField,
)}`; )}`;
}) })
.catch((error) => { .catch((error) =>
{
setAlertContent(error.response.data.error); setAlertContent(error.response.data.error);
}); });
} }
})(); })();
}; };
const formTitle = const formTitle = id != null ? `Edit ${tableMetaData?.label} (${id})` : `Create New ${tableMetaData?.label}`;
id != null ? `Edit ${tableMetaData?.label} (${id})` : `Create New ${tableMetaData?.label}`; const formId = id != null ? `edit-${tableMetaData?.label}-form` : `create-${tableMetaData?.label}-form`;
const formId =
id != null ? `edit-${tableMetaData?.label}-form` : `create-${tableMetaData?.label}-form`;
return ( return (
<MDBox mb={3}> <MDBox mb={3}>
@ -140,7 +157,9 @@ function EntityForm({ id }: Props): JSX.Element {
validationSchema={validations} validationSchema={validations}
onSubmit={handleSubmit} onSubmit={handleSubmit}
> >
{({ values, errors, touched, isSubmitting }) => ( {({
values, errors, touched, isSubmitting,
}) => (
<Form id={formId} autoComplete="off"> <Form id={formId} autoComplete="off">
<MDBox p={3} width="100%"> <MDBox p={3} width="100%">
{/*************************************************************************** {/***************************************************************************
@ -155,7 +174,9 @@ function EntityForm({ id }: Props): JSX.Element {
<Grid key="buttonGrid" container spacing={3}> <Grid key="buttonGrid" container spacing={3}>
<MDBox mt={5} ml="auto"> <MDBox mt={5} ml="auto">
<MDButton type="submit" variant="gradient" color="dark" size="small"> <MDButton type="submit" variant="gradient" color="dark" size="small">
save {tableMetaData?.label} save
{" "}
{tableMetaData?.label}
</MDButton> </MDButton>
</MDBox> </MDBox>
</Grid> </Grid>

View File

@ -37,12 +37,12 @@ interface Props {
[key: string]: any; [key: string]: any;
} }
function Footer({ company, links }: Props): JSX.Element { function Footer({ company, links }: Props): JSX.Element
{
const { href, name } = company; const { href, name } = company;
const { size } = typography; const { size } = typography;
const renderLinks = () => const renderLinks = () => links.map((link) => (
links.map((link) => (
<MDBox key={link.name} component="li" px={2} lineHeight={1}> <MDBox key={link.name} component="li" px={2} lineHeight={1}>
<Link href={link.href} target="_blank"> <Link href={link.href} target="_blank">
<MDTypography variant="button" fontWeight="regular" color="text"> <MDTypography variant="button" fontWeight="regular" color="text">
@ -70,7 +70,10 @@ function Footer({ company, links }: Props): JSX.Element {
fontSize={size.sm} fontSize={size.sm}
px={1.5} px={1.5}
> >
&copy; {new Date().getFullYear()}, made with &copy;
{" "}
{new Date().getFullYear()}
, made with
<MDBox fontSize={size.md} color="text" mb={-0.5} mx={0.25}> <MDBox fontSize={size.md} color="text" mb={-0.5} mx={0.25}>
<Icon color="inherit" fontSize="inherit"> <Icon color="inherit" fontSize="inherit">
favorite favorite
@ -79,7 +82,9 @@ function Footer({ company, links }: Props): JSX.Element {
by by
<Link href={href} target="_blank"> <Link href={href} target="_blank">
<MDTypography variant="button" fontWeight="medium"> <MDTypography variant="button" fontWeight="medium">
&nbsp;{name}&nbsp; &nbsp;
{name}
&nbsp;
</MDTypography> </MDTypography>
</Link> </Link>
for a better web. for a better web.

View File

@ -30,9 +30,12 @@ interface Props {
primaryKeyId?: string; primaryKeyId?: string;
} }
function QDynamicForm(props: Props): JSX.Element { function QDynamicForm(props: Props): JSX.Element
{
const { formData, formLabel, primaryKeyId } = props; const { formData, formLabel, primaryKeyId } = props;
const { formFields, values, errors, touched } = formData; const {
formFields, values, errors, touched,
} = formData;
/* /*
const { const {
@ -57,14 +60,17 @@ function QDynamicForm(props: Props): JSX.Element {
</MDBox> </MDBox>
<MDBox mt={1.625}> <MDBox mt={1.625}>
<Grid container spacing={3}> <Grid container spacing={3}>
{formFields && {formFields
Object.keys(formFields).length > 0 && && Object.keys(formFields).length > 0
Object.keys(formFields).map((fieldName: any) => { && Object.keys(formFields).map((fieldName: any) =>
{
const field = formFields[fieldName]; const field = formFields[fieldName];
if (primaryKeyId && fieldName === primaryKeyId) { if (primaryKeyId && fieldName === primaryKeyId)
{
return null; return null;
} }
if (values[fieldName] === undefined) { if (values[fieldName] === undefined)
{
values[fieldName] = ""; values[fieldName] = "";
} }
return ( return (

View File

@ -30,14 +30,18 @@ import { QFieldType } from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFie
** Meta-data to represent a single field in a table. ** Meta-data to represent a single field in a table.
** **
*******************************************************************************/ *******************************************************************************/
class DynamicFormUtils { class DynamicFormUtils
public static getFormData(qqqFormFields: QFieldMetaData[]) { {
public static getFormData(qqqFormFields: QFieldMetaData[])
{
const dynamicFormFields: any = {}; const dynamicFormFields: any = {};
const formValidations: any = {}; const formValidations: any = {};
qqqFormFields.forEach((field) => { qqqFormFields.forEach((field) =>
{
let fieldType: string; let fieldType: string;
switch (field.type.toString()) { switch (field.type.toString())
{
case QFieldType.DECIMAL: case QFieldType.DECIMAL:
case QFieldType.INTEGER: case QFieldType.INTEGER:
fieldType = "number"; fieldType = "number";
@ -68,7 +72,8 @@ class DynamicFormUtils {
// todo invalidMsg: "Zipcode is not valid (e.g. 70000).", // todo invalidMsg: "Zipcode is not valid (e.g. 70000).",
}; };
if (field.isRequired) { if (field.isRequired)
{
formValidations[field.name] = Yup.string().required(`${field.label} is required.`); formValidations[field.name] = Yup.string().required(`${field.label} is required.`);
} }
}); });

View File

@ -23,7 +23,8 @@ import MDBox from "components/MDBox";
import EntityForm from "qqq/components/EntityForm"; import EntityForm from "qqq/components/EntityForm";
import BaseLayout from "qqq/components/BaseLayout"; import BaseLayout from "qqq/components/BaseLayout";
function EntityCreate(): JSX.Element { function EntityCreate(): JSX.Element
{
return ( return (
<BaseLayout> <BaseLayout>
<MDBox mt={4}> <MDBox mt={4}>

View File

@ -24,7 +24,8 @@ import BaseLayout from "qqq/components/BaseLayout";
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
import EntityForm from "qqq/components/EntityForm"; import EntityForm from "qqq/components/EntityForm";
function EntityEdit(): JSX.Element { function EntityEdit(): JSX.Element
{
const { id } = useParams(); const { id } = useParams();
return ( return (

View File

@ -66,7 +66,8 @@ interface Props {
table?: QTableMetaData; table?: QTableMetaData;
} }
function EntityList({ table }: Props): JSX.Element { function EntityList({ table }: Props): JSX.Element
{
const tableNameParam = useParams().tableName; const tableNameParam = useParams().tableName;
const tableName = table === null ? tableNameParam : table.name; const tableName = table === null ? tableNameParam : table.name;
@ -93,8 +94,10 @@ function EntityList({ table }: Props): JSX.Element {
const openFiltersMenu = (event: any) => setFiltersMenu(event.currentTarget); const openFiltersMenu = (event: any) => setFiltersMenu(event.currentTarget);
const closeFiltersMenu = () => setFiltersMenu(null); const closeFiltersMenu = () => setFiltersMenu(null);
const translateCriteriaOperator = (operator: string) => { const translateCriteriaOperator = (operator: string) =>
switch (operator) { {
switch (operator)
{
case "contains": case "contains":
return QCriteriaOperator.CONTAINS; return QCriteriaOperator.CONTAINS;
case "starts with": case "starts with":
@ -131,17 +134,21 @@ function EntityList({ table }: Props): JSX.Element {
} }
}; };
const buildQFilter = () => { const buildQFilter = () =>
{
const qFilter = new QQueryFilter(); const qFilter = new QQueryFilter();
sortModel.forEach((gridSortItem) => { sortModel.forEach((gridSortItem) =>
{
qFilter.addOrderBy(new QFilterOrderBy(gridSortItem.field, gridSortItem.sort === "asc")); qFilter.addOrderBy(new QFilterOrderBy(gridSortItem.field, gridSortItem.sort === "asc"));
}); });
if (filterModel) { if (filterModel)
filterModel.items.forEach((item) => { {
filterModel.items.forEach((item) =>
{
qFilter.addCriteria( qFilter.addCriteria(
new QFilterCriteria(item.columnField, translateCriteriaOperator(item.operatorValue), [ new QFilterCriteria(item.columnField, translateCriteriaOperator(item.operatorValue), [
item.value, item.value,
]) ]),
); );
}); });
} }
@ -149,13 +156,16 @@ function EntityList({ table }: Props): JSX.Element {
return qFilter; return qFilter;
}; };
const updateTable = () => { const updateTable = () =>
(async () => { {
(async () =>
{
const tableMetaData = await QClient.loadTableMetaData(tableName); const tableMetaData = await QClient.loadTableMetaData(tableName);
const count = await QClient.count(tableName); const count = await QClient.count(tableName);
setTotalRecords(count); setTotalRecords(count);
if (sortModel.length === 0) { if (sortModel.length === 0)
{
sortModel.push({ field: tableMetaData.primaryKeyField, sort: "desc" }); sortModel.push({ field: tableMetaData.primaryKeyField, sort: "desc" });
setSortModel(sortModel); setSortModel(sortModel);
} }
@ -167,27 +177,34 @@ function EntityList({ table }: Props): JSX.Element {
tableName, tableName,
qFilter, qFilter,
rowsPerPage, rowsPerPage,
pageNumber * rowsPerPage pageNumber * rowsPerPage,
).catch((error) => { ).catch((error) =>
if (error.message) { {
if (error.message)
{
setAlertContent(error.message); setAlertContent(error.message);
} else { }
else
{
setAlertContent(error.response.data.error); setAlertContent(error.response.data.error);
} }
throw error; throw error;
}); });
const rows = [] as any[]; const rows = [] as any[];
results.forEach((record) => { results.forEach((record) =>
{
rows.push(Object.fromEntries(record.values.entries())); rows.push(Object.fromEntries(record.values.entries()));
}); });
const sortedKeys = [...tableMetaData.fields.keys()].sort(); const sortedKeys = [...tableMetaData.fields.keys()].sort();
sortedKeys.forEach((key) => { sortedKeys.forEach((key) =>
{
const field = tableMetaData.fields.get(key); const field = tableMetaData.fields.get(key);
let columnType = "string"; let columnType = "string";
switch (field.type) { switch (field.type)
{
case QFieldType.DECIMAL: case QFieldType.DECIMAL:
case QFieldType.INTEGER: case QFieldType.INTEGER:
columnType = "number"; columnType = "number";
@ -212,18 +229,22 @@ function EntityList({ table }: Props): JSX.Element {
width: 200, width: 200,
}; };
if (key === tableMetaData.primaryKeyField) { if (key === tableMetaData.primaryKeyField)
{
column.width = 75; column.width = 75;
columns.splice(0, 0, column); columns.splice(0, 0, column);
} else { }
else
{
columns.push(column); columns.push(column);
} }
}); });
const columnVisibilityModel = localStorage.getItem(COLUMN_VISIBILITY_LOCAL_STORAGE_KEY); const columnVisibilityModel = localStorage.getItem(COLUMN_VISIBILITY_LOCAL_STORAGE_KEY);
if (columnVisibilityModel) { if (columnVisibilityModel)
{
setColumnVisibilityModel( setColumnVisibilityModel(
JSON.parse(localStorage.getItem(COLUMN_VISIBILITY_LOCAL_STORAGE_KEY)) JSON.parse(localStorage.getItem(COLUMN_VISIBILITY_LOCAL_STORAGE_KEY)),
); );
} }
setColumns(columns); setColumns(columns);
@ -233,45 +254,55 @@ function EntityList({ table }: Props): JSX.Element {
})(); })();
}; };
const handlePageChange = (page: number) => { const handlePageChange = (page: number) =>
{
setPageNumber(page); setPageNumber(page);
}; };
const handleRowsPerPageChange = (size: number) => { const handleRowsPerPageChange = (size: number) =>
{
setRowsPerPage(size); setRowsPerPage(size);
}; };
const handleRowClick = (params: GridRowParams) => { const handleRowClick = (params: GridRowParams) =>
{
document.location.href = `/${tableName}/${params.id}`; document.location.href = `/${tableName}/${params.id}`;
}; };
const handleFilterChange = (filterModel: GridFilterModel) => { const handleFilterChange = (filterModel: GridFilterModel) =>
{
setFilterModel(filterModel); setFilterModel(filterModel);
}; };
const selectionChanged = (selectionModel: GridSelectionModel, details: GridCallbackDetails) => { const selectionChanged = (selectionModel: GridSelectionModel, details: GridCallbackDetails) =>
{
const newSelectedIds: string[] = []; const newSelectedIds: string[] = [];
selectionModel.forEach((value: GridRowId) => { selectionModel.forEach((value: GridRowId) =>
{
newSelectedIds.push(value as string); newSelectedIds.push(value as string);
}); });
setSelectedIds(newSelectedIds); setSelectedIds(newSelectedIds);
}; };
const handleColumnVisibilityChange = (columnVisibilityModel: GridColumnVisibilityModel) => { const handleColumnVisibilityChange = (columnVisibilityModel: GridColumnVisibilityModel) =>
{
setColumnVisibilityModel(columnVisibilityModel); setColumnVisibilityModel(columnVisibilityModel);
localStorage.setItem( localStorage.setItem(
COLUMN_VISIBILITY_LOCAL_STORAGE_KEY, COLUMN_VISIBILITY_LOCAL_STORAGE_KEY,
JSON.stringify(columnVisibilityModel) JSON.stringify(columnVisibilityModel),
); );
}; };
const handleSortChange = (gridSort: GridSortModel) => { const handleSortChange = (gridSort: GridSortModel) =>
{
setSortModel(gridSort); setSortModel(gridSort);
localStorage.setItem(COLUMN_SORT_LOCAL_STORAGE_KEY, JSON.stringify(gridSort)); localStorage.setItem(COLUMN_SORT_LOCAL_STORAGE_KEY, JSON.stringify(gridSort));
}; };
if (tableName !== tableState) { if (tableName !== tableState)
(async () => { {
(async () =>
{
setTableState(tableName); setTableState(tableName);
const metaData = await QClient.loadMetaData(); const metaData = await QClient.loadMetaData();
@ -282,8 +313,10 @@ function EntityList({ table }: Props): JSX.Element {
})(); })();
} }
function getRecordsQueryString() { function getRecordsQueryString()
if (selectedIds.length > 0) { {
if (selectedIds.length > 0)
{
return `?recordsParam=recordIds&recordIds=${selectedIds.join(",")}`; return `?recordsParam=recordIds&recordIds=${selectedIds.join(",")}`;
} }
return ""; return "";
@ -306,7 +339,8 @@ function EntityList({ table }: Props): JSX.Element {
</Menu> </Menu>
); );
useEffect(() => { useEffect(() =>
{
updateTable(); updateTable();
}, [pageNumber, rowsPerPage, tableState, sortModel, filterModel]); }, [pageNumber, rowsPerPage, tableState, sortModel, filterModel]);
@ -324,7 +358,9 @@ function EntityList({ table }: Props): JSX.Element {
<MDBox display="flex" justifyContent="space-between" alignItems="flex-start" mb={2}> <MDBox display="flex" justifyContent="space-between" alignItems="flex-start" mb={2}>
<Link href={`/${tableName}/create`}> <Link href={`/${tableName}/create`}>
<MDButton variant="gradient" color="info"> <MDButton variant="gradient" color="info">
new {tableName} new
{" "}
{tableName}
</MDButton> </MDButton>
</Link> </Link>
@ -371,9 +407,7 @@ function EntityList({ table }: Props): JSX.Element {
onSortModelChange={handleSortChange} onSortModelChange={handleSortChange}
sortingOrder={["asc", "desc"]} sortingOrder={["asc", "desc"]}
sortModel={sortModel} sortModel={sortModel}
getRowClassName={(params) => getRowClassName={(params) => (params.indexRelativeToCurrentPage % 2 === 0 ? "even" : "odd")}
params.indexRelativeToCurrentPage % 2 === 0 ? "even" : "odd"
}
/> />
</MDBox> </MDBox>
</Card> </Card>

View File

@ -48,7 +48,8 @@ interface Props {
id: string; id: string;
} }
function ViewContents({ id }: Props): JSX.Element { function ViewContents({ id }: Props): JSX.Element
{
const { tableName } = useParams(); const { tableName } = useParams();
const [asyncLoadInited, setAsyncLoadInited] = useState(false); const [asyncLoadInited, setAsyncLoadInited] = useState(false);
@ -62,10 +63,12 @@ function ViewContents({ id }: Props): JSX.Element {
const openActionsMenu = (event: any) => setActionsMenu(event.currentTarget); const openActionsMenu = (event: any) => setActionsMenu(event.currentTarget);
const closeActionsMenu = () => setActionsMenu(null); const closeActionsMenu = () => setActionsMenu(null);
if (!asyncLoadInited) { if (!asyncLoadInited)
{
setAsyncLoadInited(true); setAsyncLoadInited(true);
(async () => { (async () =>
{
const tableMetaData = await qController.loadTableMetaData(tableName); const tableMetaData = await qController.loadTableMetaData(tableName);
setTableMetaData(tableMetaData); setTableMetaData(tableMetaData);
@ -77,26 +80,32 @@ function ViewContents({ id }: Props): JSX.Element {
nameValues.push( nameValues.push(
<MDBox key={tableMetaData.primaryKeyField} display="flex" py={1} pr={2}> <MDBox key={tableMetaData.primaryKeyField} display="flex" py={1} pr={2}>
<MDTypography variant="button" fontWeight="bold" textTransform="capitalize"> <MDTypography variant="button" fontWeight="bold" textTransform="capitalize">
{tableMetaData.primaryKeyField}: &nbsp; {tableMetaData.primaryKeyField}
: &nbsp;
</MDTypography> </MDTypography>
<MDTypography variant="button" fontWeight="regular" color="text"> <MDTypography variant="button" fontWeight="regular" color="text">
&nbsp;{id} &nbsp;
{id}
</MDTypography> </MDTypography>
</MDBox> </MDBox>,
); );
const sortedKeys = [...foundRecord.values.keys()].sort(); const sortedKeys = [...foundRecord.values.keys()].sort();
sortedKeys.forEach((key) => { sortedKeys.forEach((key) =>
if (key !== tableMetaData.primaryKeyField) { {
if (key !== tableMetaData.primaryKeyField)
{
nameValues.push( nameValues.push(
<MDBox key={key} display="flex" py={1} pr={2}> <MDBox key={key} display="flex" py={1} pr={2}>
<MDTypography variant="button" fontWeight="bold" textTransform="capitalize"> <MDTypography variant="button" fontWeight="bold" textTransform="capitalize">
{tableMetaData.fields.get(key).label}: &nbsp; {tableMetaData.fields.get(key).label}
: &nbsp;
</MDTypography> </MDTypography>
<MDTypography variant="button" fontWeight="regular" color="text"> <MDTypography variant="button" fontWeight="regular" color="text">
&nbsp;{foundRecord.values.get(key)} &nbsp;
{foundRecord.values.get(key)}
</MDTypography> </MDTypography>
</MDBox> </MDBox>,
); );
} }
}); });
@ -106,18 +115,23 @@ function ViewContents({ id }: Props): JSX.Element {
})(); })();
} }
const handleClickConfirmOpen = () => { const handleClickConfirmOpen = () =>
{
setOpen(true); setOpen(true);
}; };
const handleClickConfirmClose = () => { const handleClickConfirmClose = () =>
{
setOpen(false); setOpen(false);
}; };
const handleDelete = (event: { preventDefault: () => void }) => { const handleDelete = (event: { preventDefault: () => void }) =>
{
event.preventDefault(); event.preventDefault();
(async () => { (async () =>
await qController.delete(tableName, id).then(() => { {
await qController.delete(tableName, id).then(() =>
{
window.location.href = `/${tableName}`; window.location.href = `/${tableName}`;
}); });
})(); })();
@ -147,7 +161,13 @@ function ViewContents({ id }: Props): JSX.Element {
<MDBox p={3}> <MDBox p={3}>
<MDBox display="flex" justifyContent="space-between"> <MDBox display="flex" justifyContent="space-between">
<MDTypography variant="h5"> <MDTypography variant="h5">
Viewing {tableMetaData?.label} ({id}) Viewing
{" "}
{tableMetaData?.label}
{" "}
(
{id}
)
</MDTypography> </MDTypography>
{tableProcesses.length > 0 && ( {tableProcesses.length > 0 && (
<MDButton <MDButton
@ -172,7 +192,9 @@ function ViewContents({ id }: Props): JSX.Element {
size="small" size="small"
onClick={handleClickConfirmOpen} onClick={handleClickConfirmOpen}
> >
delete {tableMetaData?.label} delete
{" "}
{tableMetaData?.label}
</MDButton> </MDButton>
<Dialog <Dialog
open={open} open={open}
@ -196,7 +218,10 @@ function ViewContents({ id }: Props): JSX.Element {
</MDBox> </MDBox>
<MDBox> <MDBox>
<MDButton variant="gradient" color="dark" size="small"> <MDButton variant="gradient" color="dark" size="small">
<Link href={editPath}>edit {tableMetaData?.label}</Link> <Link href={editPath}>
edit
{tableMetaData?.label}
</Link>
</MDButton> </MDButton>
</MDBox> </MDBox>
</Grid> </Grid>

View File

@ -26,7 +26,8 @@ import MDBox from "components/MDBox";
import BaseLayout from "qqq/components/BaseLayout"; import BaseLayout from "qqq/components/BaseLayout";
import ViewContents from "./components/ViewContents"; import ViewContents from "./components/ViewContents";
function EntityView(): JSX.Element { function EntityView(): JSX.Element
{
const { id } = useParams(); const { id } = useParams();
return ( return (

View File

@ -45,7 +45,9 @@ import { QJobStarted } from "@kingsrook/qqq-frontend-core/lib/model/processes/QJ
import { QJobComplete } from "@kingsrook/qqq-frontend-core/lib/model/processes/QJobComplete"; import { QJobComplete } from "@kingsrook/qqq-frontend-core/lib/model/processes/QJobComplete";
import { QJobError } from "@kingsrook/qqq-frontend-core/lib/model/processes/QJobError"; import { QJobError } from "@kingsrook/qqq-frontend-core/lib/model/processes/QJobError";
import { QJobRunning } from "@kingsrook/qqq-frontend-core/lib/model/processes/QJobRunning"; 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 QDynamicForm from "../../components/QDynamicForm";
import MDTypography from "../../../components/MDTypography"; import MDTypography from "../../../components/MDTypography";
@ -55,12 +57,16 @@ function getDynamicStepContent(
formData: any, formData: any,
processError: string, processError: string,
processValues: any, processValues: any,
recordConfig: any recordConfig: any,
): JSX.Element { ): JSX.Element
const { formFields, values, errors, touched } = formData; {
const {
formFields, values, errors, touched,
} = formData;
// console.log(`in getDynamicStepContent: step label ${step?.label}`); // console.log(`in getDynamicStepContent: step label ${step?.label}`);
if (processError) { if (processError)
{
return ( return (
<> <>
<MDTypography color="error" variant="h3"> <MDTypography color="error" variant="h3">
@ -71,7 +77,8 @@ function getDynamicStepContent(
); );
} }
if (!Object.keys(formFields).length) { if (!Object.keys(formFields).length)
{
// console.log("in getDynamicStepContent. No fields yet, so returning 'loading'"); // console.log("in getDynamicStepContent. No fields yet, so returning 'loading'");
return <div>Loading...</div>; return <div>Loading...</div>;
} }
@ -85,14 +92,21 @@ function getDynamicStepContent(
<div> <div>
{step.viewFields.map((field: QFieldMetaData) => ( {step.viewFields.map((field: QFieldMetaData) => (
<div key={field.name}> <div key={field.name}>
<b>{field.label}:</b> {processValues[field.name]} <b>
{field.label}
:
</b>
{" "}
{processValues[field.name]}
</div> </div>
))} ))}
</div> </div>
)} )}
{step.recordListFields && ( {step.recordListFields && (
<div> <div>
<b>Records:</b> <br /> <b>Records:</b>
{" "}
<br />
<MDBox height="100%"> <MDBox height="100%">
<DataGrid <DataGrid
page={recordConfig.pageNo} page={recordConfig.pageNo}
@ -118,17 +132,22 @@ function getDynamicStepContent(
); );
} }
function trace(name: string, isComponent: boolean = false) { function trace(name: string, isComponent: boolean = false)
if (isComponent) { {
if (isComponent)
{
console.log(`COMPONENT: ${name}`); console.log(`COMPONENT: ${name}`);
} else { }
else
{
console.log(` function: ${name}`); console.log(` function: ${name}`);
} }
} }
const qController = new QController(""); const qController = new QController("");
function ProcessRun(): JSX.Element { function ProcessRun(): JSX.Element
{
const { processName } = useParams(); const { processName } = useParams();
const [processUUID, setProcessUUID] = useState(null as string); const [processUUID, setProcessUUID] = useState(null as string);
const [jobUUID, setJobUUID] = useState(null as string); const [jobUUID, setJobUUID] = useState(null as string);
@ -140,7 +159,7 @@ function ProcessRun(): JSX.Element {
const [processMetaData, setProcessMetaData] = useState(null); const [processMetaData, setProcessMetaData] = useState(null);
const [processValues, setProcessValues] = useState({} as any); const [processValues, setProcessValues] = useState({} as any);
const [lastProcessResponse, setLastProcessResponse] = useState( const [lastProcessResponse, setLastProcessResponse] = useState(
null as QJobStarted | QJobComplete | QJobError | QJobRunning null as QJobStarted | QJobComplete | QJobError | QJobRunning,
); );
const [formId, setFormId] = useState(""); const [formId, setFormId] = useState("");
const [formFields, setFormFields] = useState({}); const [formFields, setFormFields] = useState({});
@ -158,10 +177,12 @@ function ProcessRun(): JSX.Element {
////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////
// handle moving to another step in the process - e.g., after the backend told us what screen to show next. // // handle moving to another step in the process - e.g., after the backend told us what screen to show next. //
////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////
useEffect(() => { useEffect(() =>
{
trace("updateActiveStep"); trace("updateActiveStep");
if (!processMetaData) { if (!processMetaData)
{
console.log("No process meta data yet, so returning early"); console.log("No process meta data yet, so returning early");
return; return;
} }
@ -169,22 +190,29 @@ function ProcessRun(): JSX.Element {
// console.log(`Steps are: ${steps}`); // console.log(`Steps are: ${steps}`);
// console.log(`Setting step to ${newStep}`); // console.log(`Setting step to ${newStep}`);
let newIndex = null; let newIndex = null;
if (typeof newStep === "number") { if (typeof newStep === "number")
{
newIndex = newStep as number; newIndex = newStep as number;
} else if (typeof newStep === "string") { }
for (let i = 0; i < steps.length; i++) { else if (typeof newStep === "string")
if (steps[i].name === newStep) { {
for (let i = 0; i < steps.length; i++)
{
if (steps[i].name === newStep)
{
newIndex = i; newIndex = i;
break; break;
} }
} }
} }
if (newIndex === null) { if (newIndex === null)
{
setProcessError(`Unknown process step ${newStep}.`); setProcessError(`Unknown process step ${newStep}.`);
} }
setActiveStepIndex(newIndex); setActiveStepIndex(newIndex);
if (steps) { if (steps)
{
const activeStep = steps[newIndex]; const activeStep = steps[newIndex];
setActiveStep(activeStep); setActiveStep(activeStep);
setFormId(activeStep.name); setFormId(activeStep.name);
@ -192,13 +220,15 @@ function ProcessRun(): JSX.Element {
/////////////////////////////////////////////////// ///////////////////////////////////////////////////
// if this step has form fields, set up the form // // if this step has form fields, set up the form //
/////////////////////////////////////////////////// ///////////////////////////////////////////////////
if (activeStep.formFields) { if (activeStep.formFields)
{
const { dynamicFormFields, formValidations } = DynamicFormUtils.getFormData( const { dynamicFormFields, formValidations } = DynamicFormUtils.getFormData(
activeStep.formFields activeStep.formFields,
); );
const initialValues: any = {}; const initialValues: any = {};
activeStep.formFields.forEach((field) => { activeStep.formFields.forEach((field) =>
{
initialValues[field.name] = processValues[field.name]; initialValues[field.name] = processValues[field.name];
}); });
@ -207,7 +237,8 @@ function ProcessRun(): JSX.Element {
setValidations(Yup.object().shape(formValidations)); setValidations(Yup.object().shape(formValidations));
} }
if (activeStep.recordListFields) { if (activeStep.recordListFields)
{
const newRecordConfig = {} as any; const newRecordConfig = {} as any;
newRecordConfig.pageNo = 1; newRecordConfig.pageNo = 1;
newRecordConfig.rowsPerPage = 20; newRecordConfig.rowsPerPage = 20;
@ -219,7 +250,8 @@ function ProcessRun(): JSX.Element {
newRecordConfig.handleRowClick = null; newRecordConfig.handleRowClick = null;
newRecordConfig.loading = true; newRecordConfig.loading = true;
activeStep.recordListFields.forEach((field) => { activeStep.recordListFields.forEach((field) =>
{
newRecordConfig.columns.push({ field: field.name, headerName: field.label }); newRecordConfig.columns.push({ field: field.name, headerName: field.label });
}); });
@ -232,22 +264,27 @@ function ProcessRun(): JSX.Element {
} }
}, [newStep]); }, [newStep]);
useEffect(() => { useEffect(() =>
if (needRecords) { {
if (needRecords)
{
setNeedRecords(false); setNeedRecords(false);
(async () => { (async () =>
{
const records = await qController.processRecords( const records = await qController.processRecords(
processName, processName,
processUUID, processUUID,
recordConfig.rowsPerPage * (recordConfig.pageNo - 1), recordConfig.rowsPerPage * (recordConfig.pageNo - 1),
recordConfig.rowsPerPage recordConfig.rowsPerPage,
); );
recordConfig.loading = false; recordConfig.loading = false;
recordConfig.rows = []; recordConfig.rows = [];
let rowId = 0; let rowId = 0;
records.forEach((record) => { records.forEach((record) =>
{
const row = Object.fromEntries(record.values.entries()); const row = Object.fromEntries(record.values.entries());
if (!row.id) { if (!row.id)
{
row.id = ++rowId; row.id = ++rowId;
} }
recordConfig.rows.push(row); recordConfig.rows.push(row);
@ -262,24 +299,33 @@ function ProcessRun(): JSX.Element {
////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////
// handle a response from the server - e.g., after starting a backend job, or getting its status/result // // handle a response from the server - e.g., after starting a backend job, or getting its status/result //
////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////
useEffect(() => { useEffect(() =>
if (lastProcessResponse) { {
if (lastProcessResponse)
{
trace("handleProcessResponse"); trace("handleProcessResponse");
setLastProcessResponse(null); setLastProcessResponse(null);
if (lastProcessResponse instanceof QJobComplete) { if (lastProcessResponse instanceof QJobComplete)
{
const qJobComplete = lastProcessResponse as QJobComplete; const qJobComplete = lastProcessResponse as QJobComplete;
console.log("Setting new step."); console.log("Setting new step.");
setNewStep(qJobComplete.nextStep); setNewStep(qJobComplete.nextStep);
setProcessValues(qJobComplete.values); setProcessValues(qJobComplete.values);
// console.log(`Updated process values: ${JSON.stringify(qJobComplete.values)}`); // console.log(`Updated process values: ${JSON.stringify(qJobComplete.values)}`);
} else if (lastProcessResponse instanceof QJobStarted) { }
else if (lastProcessResponse instanceof QJobStarted)
{
const qJobStarted = lastProcessResponse as QJobStarted; const qJobStarted = lastProcessResponse as QJobStarted;
setJobUUID(qJobStarted.jobUUID); setJobUUID(qJobStarted.jobUUID);
setNeedToCheckJobStatus(true); setNeedToCheckJobStatus(true);
} else if (lastProcessResponse instanceof QJobRunning) { }
else if (lastProcessResponse instanceof QJobRunning)
{
const qJobRunning = lastProcessResponse as QJobRunning; const qJobRunning = lastProcessResponse as QJobRunning;
setNeedToCheckJobStatus(true); setNeedToCheckJobStatus(true);
} else if (lastProcessResponse instanceof QJobError) { }
else if (lastProcessResponse instanceof QJobError)
{
const qJobError = lastProcessResponse as QJobError; const qJobError = lastProcessResponse as QJobError;
console.log(`Got an error from the backend... ${qJobError.error}`); console.log(`Got an error from the backend... ${qJobError.error}`);
setProcessError(qJobError.error); setProcessError(qJobError.error);
@ -290,16 +336,20 @@ function ProcessRun(): JSX.Element {
///////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////
// while a backend async job is running, periodically check its status // // while a backend async job is running, periodically check its status //
///////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////
useEffect(() => { useEffect(() =>
if (needToCheckJobStatus) { {
if (needToCheckJobStatus)
{
trace("checkJobStatus"); trace("checkJobStatus");
setNeedToCheckJobStatus(false); setNeedToCheckJobStatus(false);
(async () => { (async () =>
setTimeout(async () => { {
setTimeout(async () =>
{
const processResponse = await qController.processJobStatus( const processResponse = await qController.processJobStatus(
processName, processName,
processUUID, processUUID,
jobUUID jobUUID,
); );
setLastProcessResponse(processResponse); setLastProcessResponse(processResponse);
}, 1500); }, 1500);
@ -310,18 +360,25 @@ function ProcessRun(): JSX.Element {
////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////
// do the initial load of data for the process - that is, meta data, plus the init step // // do the initial load of data for the process - that is, meta data, plus the init step //
////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////
if (needInitialLoad) { if (needInitialLoad)
{
trace("initialLoad"); trace("initialLoad");
setNeedInitialLoad(false); setNeedInitialLoad(false);
(async () => { (async () =>
{
const { search } = useLocation(); const { search } = useLocation();
const urlSearchParams = new URLSearchParams(search); const urlSearchParams = new URLSearchParams(search);
let queryStringForInit = null; let queryStringForInit = null;
if (urlSearchParams.get("recordIds")) { if (urlSearchParams.get("recordIds"))
queryStringForInit = `recordsParam=recordIds&recordIds=${urlSearchParams.get("recordIds")}`; {
} else if (urlSearchParams.get("filterJSON")) { queryStringForInit = `recordsParam=recordIds&recordIds=${urlSearchParams.get(
"recordIds",
)}`;
}
else if (urlSearchParams.get("filterJSON"))
{
queryStringForInit = `recordsParam=filterJSON&filterJSON=${urlSearchParams.get( queryStringForInit = `recordsParam=filterJSON&filterJSON=${urlSearchParams.get(
"filterJSON" "filterJSON",
)}`; )}`;
} }
// todo once saved filters exist // todo once saved filters exist
@ -347,7 +404,8 @@ function ProcessRun(): JSX.Element {
// handle the back button - todo - not really done at all // // 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. // // e.g., qqq needs to say when back is or isn't allowed, and we need to hit the backend upon backs. //
////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////
const handleBack = () => { const handleBack = () =>
{
trace("handleBack"); trace("handleBack");
setNewStep(activeStepIndex - 1); setNewStep(activeStepIndex - 1);
}; };
@ -355,12 +413,14 @@ function ProcessRun(): JSX.Element {
////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////
// handle user submitting the form - which in qqq means moving forward from any screen. // // handle user submitting the form - which in qqq means moving forward from any screen. //
////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////
const handleSubmit = async (values: any, actions: any) => { const handleSubmit = async (values: any, actions: any) =>
{
trace("handleSubmit"); trace("handleSubmit");
// todo - post? // todo - post?
let queryString = ""; let queryString = "";
Object.keys(values).forEach((key) => { Object.keys(values).forEach((key) =>
{
queryString += `${key}=${encodeURIComponent(values[key])}&`; queryString += `${key}=${encodeURIComponent(values[key])}&`;
}); });
@ -371,7 +431,7 @@ function ProcessRun(): JSX.Element {
processName, processName,
processUUID, processUUID,
activeStep.name, activeStep.name,
queryString queryString,
); );
setLastProcessResponse(processResponse); setLastProcessResponse(processResponse);
}; };
@ -380,7 +440,12 @@ function ProcessRun(): JSX.Element {
<DashboardLayout> <DashboardLayout>
<DashboardNavbar /> <DashboardNavbar />
<MDBox py={3} mb={20} height="65vh"> <MDBox py={3} mb={20} height="65vh">
<Grid container justifyContent="center" alignItems="center" sx={{ height: "100%", mt: 8 }}> <Grid
container
justifyContent="center"
alignItems="center"
sx={{ height: "100%", mt: 8 }}
>
<Grid item xs={12} lg={8}> <Grid item xs={12} lg={8}>
<Formik <Formik
enableReinitialize enableReinitialize
@ -388,7 +453,9 @@ function ProcessRun(): JSX.Element {
validationSchema={validations} validationSchema={validations}
onSubmit={handleSubmit} onSubmit={handleSubmit}
> >
{({ values, errors, touched, isSubmitting }) => ( {({
values, errors, touched, isSubmitting,
}) => (
<Form id={formId} autoComplete="off"> <Form id={formId} autoComplete="off">
<Card sx={{ height: "100%" }}> <Card sx={{ height: "100%" }}>
<MDBox mx={2} mt={-3}> <MDBox mx={2} mt={-3}>
@ -416,16 +483,25 @@ function ProcessRun(): JSX.Element {
}, },
processError, processError,
processValues, processValues,
recordConfig recordConfig,
)} )}
{/******************************** {/********************************
** back &| next/submit buttons ** ** back &| next/submit buttons **
********************************/} ********************************/}
<MDBox mt={2} width="100%" display="flex" justifyContent="space-between"> <MDBox
mt={2}
width="100%"
display="flex"
justifyContent="space-between"
>
{true || activeStepIndex === 0 ? ( {true || activeStepIndex === 0 ? (
<MDBox /> <MDBox />
) : ( ) : (
<MDButton variant="gradient" color="light" onClick={handleBack}> <MDButton
variant="gradient"
color="light"
onClick={handleBack}
>
back back
</MDButton> </MDButton>
)} )}

View File

@ -109,13 +109,15 @@ const qqqRoutes = [
const qController = new QController(""); const qController = new QController("");
(async () => { (async () =>
{
const metaData = await qController.loadMetaData(); const metaData = await qController.loadMetaData();
// get the keys sorted // get the keys sorted
const keys = [...metaData.tables.keys()].sort(); const keys = [...metaData.tables.keys()].sort();
const tableList = [] as any[]; const tableList = [] as any[];
keys.forEach((key) => { keys.forEach((key) =>
{
const table = metaData.tables.get(key); const table = metaData.tables.get(key);
tableList.push({ tableList.push({
name: `${table.label}`, name: `${table.label}`,

View File

@ -27,34 +27,42 @@ import { QQueryFilter } from "@kingsrook/qqq-frontend-core/lib/model/query/QQuer
** client wrapper of qqq backend ** client wrapper of qqq backend
** **
*******************************************************************************/ *******************************************************************************/
class QClient { class QClient
{
private static qController: QController; private static qController: QController;
private static getInstance() { private static getInstance()
if (this.qController == null) { {
if (this.qController == null)
{
this.qController = new QController(""); this.qController = new QController("");
} }
return this.qController; return this.qController;
} }
public static loadTableMetaData(tableName: string) { public static loadTableMetaData(tableName: string)
{
return this.getInstance().loadTableMetaData(tableName); return this.getInstance().loadTableMetaData(tableName);
} }
public static loadMetaData() { public static loadMetaData()
{
return this.getInstance().loadMetaData(); return this.getInstance().loadMetaData();
} }
public static query(tableName: string, filter: QQueryFilter, limit: number, skip: number) { public static query(tableName: string, filter: QQueryFilter, limit: number, skip: number)
{
return this.getInstance() return this.getInstance()
.query(tableName, filter, limit, skip) .query(tableName, filter, limit, skip)
.catch((error) => { .catch((error) =>
{
throw error; throw error;
}); });
} }
public static count(tableName: string) { public static count(tableName: string)
{
return this.getInstance().count(tableName); return this.getInstance().count(tableName);
} }
} }

View File

@ -26,13 +26,17 @@ import { QInstance } from "@kingsrook/qqq-frontend-core/lib/model/metaData/QInst
** Utility class for working with QQQ Processes ** Utility class for working with QQQ Processes
** **
*******************************************************************************/ *******************************************************************************/
class QProcessUtils { class QProcessUtils
public static getProcessesForTable(metaData: QInstance, tableName: string): QProcessMetaData[] { {
public static getProcessesForTable(metaData: QInstance, tableName: string): QProcessMetaData[]
{
const matchingProcesses: QProcessMetaData[] = []; const matchingProcesses: QProcessMetaData[] = [];
const processKeys = [...metaData.processes.keys()]; const processKeys = [...metaData.processes.keys()];
processKeys.forEach((key) => { processKeys.forEach((key) =>
{
const process = metaData.processes.get(key); const process = metaData.processes.get(key);
if (process.tableName === tableName) { if (process.tableName === tableName)
{
matchingProcesses.push(process); matchingProcesses.push(process);
} }
}); });