From 9a00d297db87a168a739214c3f6d584915bfd901 Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Mon, 22 Aug 2022 11:13:32 -0500 Subject: [PATCH 01/11] QQQ-38 Update to load widgets & table counts from backend --- src/App.tsx | 2 +- src/qqq/pages/app-home/index.tsx | 326 ++++++++++++++++++------------- 2 files changed, 191 insertions(+), 137 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 0540bec..9508ad8 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -226,7 +226,7 @@ export default function App() name: `${app.label}`, key: app.name, route: path, - component: , + component: , }); } else if (app.type === QAppNodeType.TABLE) diff --git a/src/qqq/pages/app-home/index.tsx b/src/qqq/pages/app-home/index.tsx index 7cda0a4..cdb68b0 100644 --- a/src/qqq/pages/app-home/index.tsx +++ b/src/qqq/pages/app-home/index.tsx @@ -33,8 +33,6 @@ import MiniStatisticsCard from "examples/Cards/StatisticsCards/MiniStatisticsCar import {Icon} from "@mui/material"; import MDTypography from "components/MDTypography"; import Card from "@mui/material/Card"; -import ComplexStatisticsCard from "examples/Cards/StatisticsCards/ComplexStatisticsCard"; -import ReportsLineChart from "examples/Charts/LineCharts/ReportsLineChart"; import DefaultLineChart from "examples/Charts/LineCharts/DefaultLineChart"; import MDBadgeDot from "components/MDBadgeDot"; import ReportsBarChart from "examples/Charts/BarCharts/ReportsBarChart"; @@ -57,6 +55,9 @@ function AppHome({app}: Props): JSX.Element const [tables, setTables] = useState([] as QTableMetaData[]); const [processes, setProcesses] = useState([] as QProcessMetaData[]); const [childApps, setChildApps] = useState([] as QAppMetaData[]); + const [tableCounts, setTableCounts] = useState(new Map()); + const [updatedTableCounts, setUpdatedTableCounts] = useState(new Date()); + const [widgets, setWidgets] = useState([] as any[]); const location = useLocation(); @@ -102,154 +103,207 @@ function AppHome({app}: Props): JSX.Element setTables(newTables); setProcesses(newProcesses); setChildApps(newChildApps); + + const tableCounts = new Map(); + newTables.forEach((table) => + { + tableCounts.set(table.name, {isLoading: true, value: null}); + + setTimeout(async () => + { + const count = await qController.count(table.name); + tableCounts.set(table.name, {isLoading: false, value: count}); + setTableCounts(tableCounts); + setUpdatedTableCounts(new Date()); + }, 1); + }); + setTableCounts(tableCounts); + + console.log(app.widgets); + if (app.widgets) + { + const widgets: any[] = []; + for (let i = 0; i < app.widgets.length; i++) + { + widgets[i] = {}; + setTimeout(async () => + { + const widget = await qController.widget(app.widgets[i]); + widgets[i] = widget; + setUpdatedTableCounts(new Date()); + }, 1); + } + setWidgets(widgets); + } }, [qInstance, location]); - const reportsBarChartData = { - labels: ["M", "T", "W", "T", "F", "S", "S"], - datasets: {label: "Sales", data: [50, 20, 10, 22, 50, 10, 40]}, - }; - - interface Types { - labels: string[]; - datasets: { - label: string; - color: "primary" | "secondary" | "info" | "success" | "warning" | "error" | "light" | "dark"; - data: number[]; - }[]; - } - - const demoLineChartData: Types = { - labels: ["Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"], - datasets: [ - { - label: "Facebook Ads", - color: "info", - data: [50, 100, 200, 190, 400, 350, 500, 450, 700], + /* + const charts = [ + { + type: "barChart", + title: "Parcel Invoice Lines per Month", + barChartData: { + labels: ["Feb 22", "Mar 22", "Apr 22", "May 22", "Jun 22", "Jul 22", "Aug 22"], + datasets: {label: "Parcel Invoice Lines", data: [50000, 22000, 11111, 22333, 40404, 9876, 2355]}, }, - { - label: "Google Ads", - color: "dark", - data: [10, 30, 40, 120, 150, 220, 280, 250, 280], + }, + { + type: "lineChart", + title: "Total Charges by Carrier per Month", + lineChartData: { + labels: ["Feb 22", "Mar 22", "Apr 22", "May 22", "Jun 22", "Jul 22", "Aug 22"], + datasets: [ + {label: "UPS", color: "info", data: [50000, 22000, 11111, 22333, 40404, 9876, 2355]}, + {label: "FedEx", color: "dark", data: [5000, 22000, 31111, 32333, 20404, 19876, 24355]}, + {label: "LSO", color: "error", data: [500, 2200, 1111, 2333, 404, 17876, 2355]}, + ], }, - ], - }; + }, + ]; + */ + + console.log(`Widgets: ${widgets} and tables: ${tables}`); + + const haveWidgets = widgets && widgets.length; + const widgetCount = widgets ? widgets.length : 0; + + // eslint-disable-next-line no-nested-ternary + const tileSizeLg = (widgetCount === 0 ? 3 : widgetCount === 1 ? 4 : 6); return ( - - - - - - - - - - - - - - - - - - - - - )} - chart={demoLineChartData} - /> - - - - - - - { - tables.length ? ( - - - - Tables - - - {tables.map((table) => ( - - - - {table.iconName || app.iconName}}} - direction="right" + { + widgetCount > 0 ? ( + + + { + widgets.map((chart) => ( + + + { + chart.type === "barChart" && ( + - - - - ))} - - - - ) : null - } + ) + } + { + chart.type === "lineChart" && ( + + + { + chart.lineChartData.datasets.map((dataSet: any) => ( + + )) + } + + + + )} + chart={chart.lineChartData as { labels: string[]; datasets: { label: string; color: "primary" | "secondary" | "info" | "success" | "warning" | "error" | "light" | "dark"; data: number[]; }[]; }} + /> + ) + } + + + )) + } + + + ) : null + } - { - processes.length ? ( - - - - Processes + { + tables.length > 0 || processes.length > 0 || childApps.length > 0 ? ( + // eslint-disable-next-line no-nested-ternary + + { + tables.length ? ( + + + + Tables + + + {tables.map((table) => ( + + + + {table.iconName || app.iconName}}} + direction="right" + /> + + + + ))} + + - - {processes.map((process) => ( - - - - - - ))} - - - - ) : null - } + ) : null + } - { - childApps.length ? ( - - - - {childApps.map((childApp) => ( - - - - + { + processes.length ? ( + + + + Processes + + + {processes.map((process) => ( + + + + + + ))} - ))} - - - - ) : null - } - + + + ) : null + } + + { + childApps.length ? ( + + + + Apps + + + {childApps.map((childApp) => ( + + + + + + ))} + + + + ) : null + } + + ) : null + } From 54c656da9d6ee81f18c5308d275af8f3ce82e9d4 Mon Sep 17 00:00:00 2001 From: Tim Chamberlain Date: Thu, 25 Aug 2022 11:42:01 -0500 Subject: [PATCH 02/11] QQQ-38: updated copyright on all files, standardize import organisation on all files --- .eslintrc.json | 41 +++++-- src/App.tsx | 86 ++++++------- src/HandleAuthorizationError.tsx | 23 +++- src/index.tsx | 31 ++++- src/qqq/components/BaseLayout/index.tsx | 6 +- .../Buttons/AuthenticationButton/index.tsx | 2 +- src/qqq/components/EntityForm/index.tsx | 58 +++++---- src/qqq/components/Footer/index.tsx | 33 +++-- src/qqq/components/Navbar/index.tsx | 40 ++---- src/qqq/components/Navbar/styles.ts | 1 - src/qqq/components/ProcessLinkCard/index.tsx | 5 +- src/qqq/components/QBreadcrumbs/index.tsx | 5 +- src/qqq/components/QButtons/index.tsx | 6 +- src/qqq/components/QDynamicForm/index.tsx | 115 +++++++++--------- .../QDynamicForm/utils/DynamicFormUtils.ts | 5 +- .../components/QDynamicFormField/index.tsx | 15 +-- src/qqq/components/QRecordSidebar/index.tsx | 11 +- src/qqq/pages/app-home/index.tsx | 36 +++--- src/qqq/pages/entity-create/index.tsx | 13 +- src/qqq/pages/entity-edit/index.tsx | 16 +-- src/qqq/pages/entity-list/index.tsx | 75 +++--------- .../components/ViewContents/index.tsx | 49 ++++---- src/qqq/pages/entity-view/index.tsx | 11 +- src/qqq/pages/process-run/index.tsx | 54 ++++---- src/qqq/utils/QClient.ts | 2 +- src/qqq/utils/QProcessUtils.ts | 2 +- src/qqq/utils/QTableUtils.ts | 2 +- src/qqq/utils/QValueUtils.ts | 2 +- 28 files changed, 350 insertions(+), 395 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index d9b248b..0adcb43 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -5,7 +5,8 @@ }, "extends": [ "plugin:react/recommended", - "airbnb" + "plugin:import/recommended", + "plugin:import/typescript" ], "globals": { "JSX": true @@ -27,7 +28,8 @@ }, "plugins": [ "react", - "@typescript-eslint" + "@typescript-eslint", + "import" ], "rules": { "brace-style": [ @@ -56,7 +58,26 @@ "devDependencies": true } ], - "import/order": "off", + "import/order": [ + "error", + { + "groups": [ + "builtin", // Built-in imports (come from NodeJS native) go first + "external", // <- External imports + "internal", // <- Absolute imports + ["sibling", "parent"], // <- Relative imports, the sibling and parent types they can be mingled together + "index", // <- index imports + "unknown" + ], + "newlines-between": "never", + "alphabetize": { + /* sort in ascending order. Options: ["ignore", "asc", "desc"] */ + "order": "asc", + /* ignore case. Options: [true, false] */ + "caseInsensitive": true + } + } + ], "max-len": "off", "no-console": "off", "no-constant-condition": "off", @@ -81,10 +102,7 @@ ] } ], - "react/jsx-indent": [ - "error", - 3 - ], + "react/jsx-indent": "off", "react/jsx-indent-props": [ "error", 3 @@ -94,6 +112,15 @@ "quotes": [ "error", "double" + ], + "sort-imports": [ + "error", + { + "ignoreCase": false, + "ignoreDeclarationSort": true, + "ignoreMemberSort": true, + "allowSeparatedGroups": false + } ] }, "settings": { diff --git a/src/App.tsx b/src/App.tsx index 9508ad8..169e7e5 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,56 +1,56 @@ -import React, { - JSXElementConstructor, Key, ReactElement, useEffect, useState, -} from "react"; - -// react-router components -import { - Navigate, Route, Routes, useLocation, -} from "react-router-dom"; +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2022. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ import {useAuth0} from "@auth0/auth0-react"; - -// @mui material components -import {LicenseInfo} from "@mui/x-license-pro"; -import {ThemeProvider} from "@mui/material/styles"; +import {QAppNodeType} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QAppNodeType"; +import {QAppTreeNode} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QAppTreeNode"; +import {QInstance} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QInstance"; import CssBaseline from "@mui/material/CssBaseline"; import Icon from "@mui/material/Icon"; - -// Material Dashboard 2 PRO React TS components -import MDBox from "components/MDBox"; - -// Material Dashboard 2 PRO React TS exampless -import Sidenav from "examples/Sidenav"; -import Configurator from "examples/Configurator"; - -// Material Dashboard 2 PRO React TS themes -import theme from "assets/theme"; - -// Material Dashboard 2 PRO React TS Dark Mode themes -import themeDark from "assets/theme-dark"; - -// Material Dashboard 2 PRO React TS contexts -import {setMiniSidenav, setOpenConfigurator, useMaterialUIController} from "context"; - -// Images -import nfLogo from "assets/images/nutrifresh_one_icon_white.png"; -import {Md5} from "ts-md5/dist/md5"; +import {ThemeProvider} from "@mui/material/styles"; +import {LicenseInfo} from "@mui/x-license-pro"; +import React, {JSXElementConstructor, Key, ReactElement, useEffect, useState,} from "react"; import {useCookies} from "react-cookie"; -import EntityCreate from "./qqq/pages/entity-create"; -import EntityList from "./qqq/pages/entity-list"; -import EntityView from "./qqq/pages/entity-view"; -import EntityEdit from "./qqq/pages/entity-edit"; -import ProcessRun from "./qqq/pages/process-run"; +import {Navigate, Route, Routes, useLocation,} from "react-router-dom"; +import {Md5} from "ts-md5/dist/md5"; +import nfLogo from "assets/images/nutrifresh_one_icon_white.png"; +import theme from "assets/theme"; +import themeDark from "assets/theme-dark"; +import MDBox from "components/MDBox"; +import {setMiniSidenav, setOpenConfigurator, useMaterialUIController} from "context"; +import Configurator from "examples/Configurator"; +import Sidenav from "examples/Sidenav"; import AppHome from "qqq/pages/app-home"; +import QProcessUtils from "qqq/utils/QProcessUtils"; import MDAvatar from "./components/MDAvatar"; -import ProfileOverview from "./layouts/pages/profile/profile-overview"; -import Settings from "./layouts/pages/account/settings"; import Analytics from "./layouts/dashboards/analytics"; import Sales from "./layouts/dashboards/sales"; +import Settings from "./layouts/pages/account/settings"; +import ProfileOverview from "./layouts/pages/profile/profile-overview"; +import EntityCreate from "./qqq/pages/entity-create"; +import EntityEdit from "./qqq/pages/entity-edit"; +import EntityList from "./qqq/pages/entity-list"; +import EntityView from "./qqq/pages/entity-view"; +import ProcessRun from "./qqq/pages/process-run"; import QClient from "./qqq/utils/QClient"; -import {QAppNodeType} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QAppNodeType"; -import {QInstance} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QInstance"; -import QProcessUtils from "qqq/utils/QProcessUtils"; -import {QAppTreeNode} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QAppTreeNode"; /////////////////////////////////////////////////////////////////////////////////////////////// // define the parts of the nav that are static - before the qqq tables etc get dynamic added // diff --git a/src/HandleAuthorizationError.tsx b/src/HandleAuthorizationError.tsx index ede41e3..6abbaf0 100644 --- a/src/HandleAuthorizationError.tsx +++ b/src/HandleAuthorizationError.tsx @@ -1,6 +1,27 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2022. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + import React, {useEffect} from "react"; -import {SESSION_ID_COOKIE_NAME} from "App"; import {useCookies} from "react-cookie"; +import {SESSION_ID_COOKIE_NAME} from "App"; interface Props { diff --git a/src/index.tsx b/src/index.tsx index 4f978af..55469ee 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,16 +1,35 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2022. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import {Auth0Provider} from "@auth0/auth0-react"; +import React from "react"; import ReactDOM from "react-dom"; import {BrowserRouter, useNavigate, useSearchParams} from "react-router-dom"; -import {Auth0Provider} from "@auth0/auth0-react"; import App from "App"; - -// Material Dashboard 2 PRO React TS Context Provider import {MaterialUIControllerProvider} from "context"; import "./qqq/styles/qqq-override-styles.css"; -import ProtectedRoute from "qqq/auth0/protected-route"; -import React from "react"; import HandleAuthorizationError from "HandleAuthorizationError"; +import ProtectedRoute from "qqq/auth0/protected-route"; + -// Auth0 params from env const domain = process.env.REACT_APP_AUTH0_DOMAIN; const clientId = process.env.REACT_APP_AUTH0_CLIENT_ID; diff --git a/src/qqq/components/BaseLayout/index.tsx b/src/qqq/components/BaseLayout/index.tsx index 8b450b3..1535a15 100644 --- a/src/qqq/components/BaseLayout/index.tsx +++ b/src/qqq/components/BaseLayout/index.tsx @@ -20,14 +20,10 @@ */ import {useState, useEffect, ReactNode} from "react"; - -// Material Dashboard 2 PRO React TS Base Styles import breakpoints from "assets/theme/base/breakpoints"; - -// Material Dashboard 2 PRO React TS examples components import DashboardLayout from "examples/LayoutContainers/DashboardLayout"; -import Navbar from "qqq/components/Navbar"; import Footer from "qqq/components/Footer"; +import Navbar from "qqq/components/Navbar"; import MDBox from "../../../components/MDBox"; // Declaring props types for BaseLayout diff --git a/src/qqq/components/Buttons/AuthenticationButton/index.tsx b/src/qqq/components/Buttons/AuthenticationButton/index.tsx index f8e89f0..d7b27c4 100644 --- a/src/qqq/components/Buttons/AuthenticationButton/index.tsx +++ b/src/qqq/components/Buttons/AuthenticationButton/index.tsx @@ -20,8 +20,8 @@ */ import {useAuth0} from "@auth0/auth0-react"; -import React from "react"; import {Button} from "@mui/material"; +import React from "react"; function AuthenticationButton() { diff --git a/src/qqq/components/EntityForm/index.tsx b/src/qqq/components/EntityForm/index.tsx index 2a2329a..5c950c4 100644 --- a/src/qqq/components/EntityForm/index.tsx +++ b/src/qqq/components/EntityForm/index.tsx @@ -1,36 +1,46 @@ -// react components -import {useParams, useNavigate, useLocation} from "react-router-dom"; -import React, {useReducer, useState} from "react"; +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2022. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ -// misc components -import * as Yup from "yup"; -import { - Form, Formik, useFormik, useFormikContext, -} from "formik"; - -// qqq components -import DynamicFormUtils from "qqq/components/QDynamicForm/utils/DynamicFormUtils"; -import QDynamicForm from "qqq/components/QDynamicForm"; import {QFieldMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldMetaData"; - -// @material-ui core components +import {QSection} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QSection"; +import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData"; +import {QRecord} from "@kingsrook/qqq-frontend-core/lib/model/QRecord"; +import {Alert} from "@mui/material"; +import Avatar from "@mui/material/Avatar"; import Card from "@mui/material/Card"; import Grid from "@mui/material/Grid"; -import {Alert} from "@mui/material"; - -// Material Dashboard 2 PRO React TS components +import Icon from "@mui/material/Icon"; +import {Form, Formik} from "formik"; +import React, {useReducer, useState} from "react"; +import {useParams, useNavigate, useLocation} from "react-router-dom"; +import * as Yup from "yup"; +import colors from "assets/theme/base/colors"; import MDBox from "components/MDBox"; import MDTypography from "components/MDTypography"; -import QClient from "qqq/utils/QClient"; -import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData"; import {QCancelButton, QSaveButton} from "qqq/components/QButtons"; -import {QRecord} from "@kingsrook/qqq-frontend-core/lib/model/QRecord"; -import Avatar from "@mui/material/Avatar"; -import Icon from "@mui/material/Icon"; +import QDynamicForm from "qqq/components/QDynamicForm"; +import DynamicFormUtils from "qqq/components/QDynamicForm/utils/DynamicFormUtils"; import QRecordSidebar from "qqq/components/QRecordSidebar"; +import QClient from "qqq/utils/QClient"; import QTableUtils from "qqq/utils/QTableUtils"; -import colors from "assets/theme/base/colors"; -import {QSection} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QSection"; interface Props { diff --git a/src/qqq/components/Footer/index.tsx b/src/qqq/components/Footer/index.tsx index e976520..6ebe4c7 100644 --- a/src/qqq/components/Footer/index.tsx +++ b/src/qqq/components/Footer/index.tsx @@ -19,28 +19,25 @@ * along with this program. If not, see . */ -// @mui material components import Link from "@mui/material/Link"; -import Icon from "@mui/material/Icon"; - -// Material Dashboard 2 PRO React TS components +import typography from "assets/theme/base/typography"; import MDBox from "components/MDBox"; import MDTypography from "components/MDTypography"; -// Material Dashboard 2 PRO React TS Base Styles -import typography from "assets/theme/base/typography"; // Declaring props types for Footer -interface Props { - company?: { - href: string; - name: string; - }; - links?: { - href: string; - name: string; - }[]; - [key: string]: any; +interface Props +{ + company?: { + href: string; + name: string; + }; + links?: { + href: string; + name: string; + }[]; + + [key: string]: any; } function Footer({company, links}: Props): JSX.Element @@ -85,9 +82,9 @@ function Footer({company, links}: Props): JSX.Element , -   +   {name} -  +   diff --git a/src/qqq/components/Navbar/index.tsx b/src/qqq/components/Navbar/index.tsx index 3b4ac40..9674fca 100644 --- a/src/qqq/components/Navbar/index.tsx +++ b/src/qqq/components/Navbar/index.tsx @@ -19,45 +19,19 @@ * along with this program. If not, see . */ -import {useState, useEffect} from "react"; - -// react-router components -import {useLocation} from "react-router-dom"; - -// @material-ui core components import AppBar from "@mui/material/AppBar"; -import Toolbar from "@mui/material/Toolbar"; +import Icon from "@mui/material/Icon"; import IconButton from "@mui/material/IconButton"; import Menu from "@mui/material/Menu"; -import Icon from "@mui/material/Icon"; - -// Material Dashboard 2 PRO React TS components +import Toolbar from "@mui/material/Toolbar"; +import {useState, useEffect} from "react"; +import {useLocation} from "react-router-dom"; +import MDBadge from "components/MDBadge"; import MDBox from "components/MDBox"; import MDInput from "components/MDInput"; -import MDBadge from "components/MDBadge"; - -// Material Dashboard 2 PRO React TS examples components +import {useMaterialUIController, setTransparentNavbar, setMiniSidenav, setOpenConfigurator,} from "context"; import NotificationItem from "examples/Items/NotificationItem"; - -// Custom styles for Navbar -import { - navbar, - navbarContainer, - navbarRow, - navbarIconButton, - navbarDesktopMenu, - navbarMobileMenu, -} from "qqq/components/Navbar/styles"; - -// Material Dashboard 2 PRO React context -import { - useMaterialUIController, - setTransparentNavbar, - setMiniSidenav, - setOpenConfigurator, -} from "context"; - -// qqq +import {navbar, navbarContainer, navbarRow, navbarIconButton, navbarDesktopMenu, navbarMobileMenu,} from "qqq/components/Navbar/styles"; import QBreadcrumbs, {routeToLabel} from "qqq/components/QBreadcrumbs"; // Declaring prop types for Navbar diff --git a/src/qqq/components/Navbar/styles.ts b/src/qqq/components/Navbar/styles.ts index 7fa3afc..999c130 100644 --- a/src/qqq/components/Navbar/styles.ts +++ b/src/qqq/components/Navbar/styles.ts @@ -19,7 +19,6 @@ * along with this program. If not, see . */ -// @mui material components import {Theme} from "@mui/material/styles"; function navbar(theme: Theme | any, ownerState: any) diff --git a/src/qqq/components/ProcessLinkCard/index.tsx b/src/qqq/components/ProcessLinkCard/index.tsx index 0be3bd4..c57936f 100644 --- a/src/qqq/components/ProcessLinkCard/index.tsx +++ b/src/qqq/components/ProcessLinkCard/index.tsx @@ -18,14 +18,11 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -import {ReactNode} from "react"; -// @mui material components import Card from "@mui/material/Card"; import Divider from "@mui/material/Divider"; import Icon from "@mui/material/Icon"; - -// Material Dashboard 2 PRO React TS components +import {ReactNode} from "react"; import MDBox from "components/MDBox"; import MDTypography from "components/MDTypography"; diff --git a/src/qqq/components/QBreadcrumbs/index.tsx b/src/qqq/components/QBreadcrumbs/index.tsx index 248cfa1..94587e7 100644 --- a/src/qqq/components/QBreadcrumbs/index.tsx +++ b/src/qqq/components/QBreadcrumbs/index.tsx @@ -19,11 +19,10 @@ * along with this program. If not, see . */ -import {ReactNode} from "react"; - -import {Link} from "react-router-dom"; import {Breadcrumbs as MuiBreadcrumbs} from "@mui/material"; import Icon from "@mui/material/Icon"; +import {ReactNode} from "react"; +import {Link} from "react-router-dom"; import MDBox from "components/MDBox"; import MDTypography from "components/MDTypography"; diff --git a/src/qqq/components/QButtons/index.tsx b/src/qqq/components/QButtons/index.tsx index 867ee66..030c827 100644 --- a/src/qqq/components/QButtons/index.tsx +++ b/src/qqq/components/QButtons/index.tsx @@ -19,11 +19,11 @@ * along with this program. If not, see . */ -import MDBox from "components/MDBox"; -import {Link} from "react-router-dom"; -import MDButton from "components/MDButton"; import Icon from "@mui/material/Icon"; import React from "react"; +import {Link} from "react-router-dom"; +import MDBox from "components/MDBox"; +import MDButton from "components/MDButton"; // eslint-disable import/prefer-default-export diff --git a/src/qqq/components/QDynamicForm/index.tsx b/src/qqq/components/QDynamicForm/index.tsx index 71c5d17..3516d30 100644 --- a/src/qqq/components/QDynamicForm/index.tsx +++ b/src/qqq/components/QDynamicForm/index.tsx @@ -19,23 +19,19 @@ * along with this program. If not, see . */ -// @mui material components import Grid from "@mui/material/Grid"; - -// Material Dashboard 2 PRO React TS components -import MDBox from "components/MDBox"; -import MDTypography from "components/MDTypography"; - -// NewUser page components import {useFormikContext} from "formik"; import React from "react"; +import MDBox from "components/MDBox"; +import MDTypography from "components/MDTypography"; import QDynamicFormField from "qqq/components/QDynamicFormField"; -interface Props { - formLabel?: string; - formData: any; - bulkEditMode?: boolean; - bulkEditSwitchChangeHandler?: any +interface Props +{ + formLabel?: string; + formData: any; + bulkEditMode?: boolean; + bulkEditSwitchChangeHandler?: any; } function QDynamicForm(props: Props): JSX.Element @@ -72,55 +68,55 @@ function QDynamicForm(props: Props): JSX.Element {formFields - && Object.keys(formFields).length > 0 - && Object.keys(formFields).map((fieldName: any) => - { - const field = formFields[fieldName]; - if (values[fieldName] === undefined) - { - values[fieldName] = ""; - } + && Object.keys(formFields).length > 0 + && Object.keys(formFields).map((fieldName: any) => + { + const field = formFields[fieldName]; + if (values[fieldName] === undefined) + { + values[fieldName] = ""; + } - if (field.type === "file") - { - return ( - - - + + ) => fileChanged(event, field)} + /> + + + {errors[fieldName] && You must select a file to proceed} + + + + + ); + } + + // todo? inputProps={{ autoComplete: "" }} + // todo? placeholder={password.placeholder} + return ( + + ) => fileChanged(event, field)} + displayFormat={field.displayFormat} + value={values[fieldName]} + error={errors[fieldName] && touched[fieldName]} + bulkEditMode={bulkEditMode} + bulkEditSwitchChangeHandler={bulkEditSwitchChanged} + success={`${values[fieldName]}` !== "" && !errors[fieldName] && touched[fieldName]} /> - - - {errors[fieldName] && You must select a file to proceed} - - - - - ); - } - - // todo? inputProps={{ autoComplete: "" }} - // todo? placeholder={password.placeholder} - return ( - - - - ); - })} + + ); + })} @@ -131,7 +127,8 @@ QDynamicForm.defaultProps = { formLabel: undefined, bulkEditMode: false, bulkEditSwitchChangeHandler: () => - {}, + { + }, }; export default QDynamicForm; diff --git a/src/qqq/components/QDynamicForm/utils/DynamicFormUtils.ts b/src/qqq/components/QDynamicForm/utils/DynamicFormUtils.ts index 812fd77..dbdead7 100644 --- a/src/qqq/components/QDynamicForm/utils/DynamicFormUtils.ts +++ b/src/qqq/components/QDynamicForm/utils/DynamicFormUtils.ts @@ -19,12 +19,9 @@ * along with this program. If not, see . */ -// misc imports -import * as Yup from "yup"; - -// qqq imports import {QFieldMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldMetaData"; import {QFieldType} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldType"; +import * as Yup from "yup"; /******************************************************************************* ** Meta-data to represent a single field in a table. diff --git a/src/qqq/components/QDynamicFormField/index.tsx b/src/qqq/components/QDynamicFormField/index.tsx index b521445..9540671 100644 --- a/src/qqq/components/QDynamicFormField/index.tsx +++ b/src/qqq/components/QDynamicFormField/index.tsx @@ -19,17 +19,14 @@ * along with this program. If not, see . */ -// formik components -import {ErrorMessage, Field} from "formik"; - -// Material Dashboard 2 PRO React TS components -import MDBox from "components/MDBox"; -import MDTypography from "components/MDTypography"; -import MDInput from "components/MDInput"; -import React, {useState} from "react"; +import {InputAdornment} from "@mui/material"; import Grid from "@mui/material/Grid"; import Switch from "@mui/material/Switch"; -import {InputAdornment} from "@mui/material"; +import {ErrorMessage, Field} from "formik"; +import React, {useState} from "react"; +import MDBox from "components/MDBox"; +import MDInput from "components/MDInput"; +import MDTypography from "components/MDTypography"; // Declaring props types for FormField interface Props diff --git a/src/qqq/components/QRecordSidebar/index.tsx b/src/qqq/components/QRecordSidebar/index.tsx index 85c5b04..d146825 100644 --- a/src/qqq/components/QRecordSidebar/index.tsx +++ b/src/qqq/components/QRecordSidebar/index.tsx @@ -19,16 +19,15 @@ * along with this program. If not, see . */ -import React, {ReactNode} from "react"; - -import {Link} from "react-router-dom"; +import {QSection} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QSection"; import {Breadcrumbs as MuiBreadcrumbs} from "@mui/material"; +import Card from "@mui/material/Card"; import Icon from "@mui/material/Icon"; +import {Theme} from "@mui/material/styles"; +import React, {ReactNode} from "react"; +import {Link} from "react-router-dom"; import MDBox from "components/MDBox"; import MDTypography from "components/MDTypography"; -import Card from "@mui/material/Card"; -import {Theme} from "@mui/material/styles"; -import {QSection} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QSection"; interface Props { tableSections: QSection[]; diff --git a/src/qqq/pages/app-home/index.tsx b/src/qqq/pages/app-home/index.tsx index cdb68b0..3df1022 100644 --- a/src/qqq/pages/app-home/index.tsx +++ b/src/qqq/pages/app-home/index.tsx @@ -19,28 +19,26 @@ * along with this program. If not, see . */ +import {QAppMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QAppMetaData"; +import {QAppNodeType} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QAppNodeType"; +import {QInstance} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QInstance"; +import {QProcessMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QProcessMetaData"; +import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData"; +import {Icon} from "@mui/material"; +import Card from "@mui/material/Card"; +import Grid from "@mui/material/Grid"; import React, {useEffect, useState} from "react"; import {Link, useLocation} from "react-router-dom"; - -// Material Dashboard 2 PRO React TS examples components -import QClient from "qqq/utils/QClient"; -import {QInstance} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QInstance"; -import {QAppMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QAppMetaData"; -import BaseLayout from "qqq/components/BaseLayout"; -import MDBox from "components/MDBox"; -import Grid from "@mui/material/Grid"; -import MiniStatisticsCard from "examples/Cards/StatisticsCards/MiniStatisticsCard"; -import {Icon} from "@mui/material"; -import MDTypography from "components/MDTypography"; -import Card from "@mui/material/Card"; -import DefaultLineChart from "examples/Charts/LineCharts/DefaultLineChart"; import MDBadgeDot from "components/MDBadgeDot"; -import ReportsBarChart from "examples/Charts/BarCharts/ReportsBarChart"; -import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData"; -import {QAppNodeType} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QAppNodeType"; -import {QProcessMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QProcessMetaData"; +import MDBox from "components/MDBox"; +import MDTypography from "components/MDTypography"; import DefaultInfoCard from "examples/Cards/InfoCards/DefaultInfoCard"; +import MiniStatisticsCard from "examples/Cards/StatisticsCards/MiniStatisticsCard"; +import ReportsBarChart from "examples/Charts/BarCharts/ReportsBarChart"; +import DefaultLineChart from "examples/Charts/LineCharts/DefaultLineChart"; +import BaseLayout from "qqq/components/BaseLayout"; import ProcessLinkCard from "qqq/components/ProcessLinkCard"; +import QClient from "qqq/utils/QClient"; const qController = QClient.getInstance(); @@ -55,7 +53,7 @@ function AppHome({app}: Props): JSX.Element const [tables, setTables] = useState([] as QTableMetaData[]); const [processes, setProcesses] = useState([] as QProcessMetaData[]); const [childApps, setChildApps] = useState([] as QAppMetaData[]); - const [tableCounts, setTableCounts] = useState(new Map()); + const [tableCounts, setTableCounts] = useState(new Map()); const [updatedTableCounts, setUpdatedTableCounts] = useState(new Date()); const [widgets, setWidgets] = useState([] as any[]); @@ -104,7 +102,7 @@ function AppHome({app}: Props): JSX.Element setProcesses(newProcesses); setChildApps(newChildApps); - const tableCounts = new Map(); + const tableCounts = new Map(); newTables.forEach((table) => { tableCounts.set(table.name, {isLoading: true, value: null}); diff --git a/src/qqq/pages/entity-create/index.tsx b/src/qqq/pages/entity-create/index.tsx index cef402b..930e41e 100644 --- a/src/qqq/pages/entity-create/index.tsx +++ b/src/qqq/pages/entity-create/index.tsx @@ -19,16 +19,11 @@ * along with this program. If not, see . */ -// @mui material components -import Grid from "@mui/material/Grid"; - -// Material Dashboard 2 PRO React TS components -import MDBox from "components/MDBox"; - -// Settings page components -import EntityForm from "qqq/components/EntityForm"; -import BaseLayout from "qqq/components/BaseLayout"; import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData"; +import Grid from "@mui/material/Grid"; +import MDBox from "components/MDBox"; +import BaseLayout from "qqq/components/BaseLayout"; +import EntityForm from "qqq/components/EntityForm"; interface Props { diff --git a/src/qqq/pages/entity-edit/index.tsx b/src/qqq/pages/entity-edit/index.tsx index e3a672f..96b9bf1 100644 --- a/src/qqq/pages/entity-edit/index.tsx +++ b/src/qqq/pages/entity-edit/index.tsx @@ -19,18 +19,12 @@ * along with this program. If not, see . */ -// @mui material components -import Grid from "@mui/material/Grid"; - -// Material Dashboard 2 PRO React TS components -import MDBox from "components/MDBox"; - -// Settings page components -import BaseLayout from "qqq/components/BaseLayout"; -import {useParams} from "react-router-dom"; -import EntityForm from "qqq/components/EntityForm"; import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData"; -import EntityList from "qqq/pages/entity-list"; +import Grid from "@mui/material/Grid"; +import {useParams} from "react-router-dom"; +import MDBox from "components/MDBox"; +import BaseLayout from "qqq/components/BaseLayout"; +import EntityForm from "qqq/components/EntityForm"; interface Props { diff --git a/src/qqq/pages/entity-list/index.tsx b/src/qqq/pages/entity-list/index.tsx index d184814..0d85039 100644 --- a/src/qqq/pages/entity-list/index.tsx +++ b/src/qqq/pages/entity-list/index.tsx @@ -19,68 +19,33 @@ * along with this program. If not, see . */ -import React, { - SyntheticEvent, - useCallback, - useEffect, useReducer, useRef, useState, -} from "react"; -import { - Link, useNavigate, useParams, useSearchParams, -} from "react-router-dom"; - -// @mui material components -import Card from "@mui/material/Card"; -import Icon from "@mui/material/Icon"; -import Menu from "@mui/material/Menu"; -import MenuItem from "@mui/material/MenuItem"; -import {Alert, Pagination, TablePagination} from "@mui/material"; -import { - DataGridPro, - GridCallbackDetails, - GridColDef, - GridColumnOrderChangeParams, - GridColumnVisibilityModel, - GridFilterModel, - GridRowId, - GridRowParams, - GridRowsProp, - GridSelectionModel, - GridSortItem, - GridSortModel, - GridToolbarColumnsButton, - GridToolbarContainer, - GridToolbarDensitySelector, - GridToolbarExportContainer, - GridToolbarFilterButton, - GridExportMenuItemProps, - MuiEvent, -} from "@mui/x-data-grid-pro"; - -// Material Dashboard 2 PRO React TS components -import DashboardLayout from "examples/LayoutContainers/DashboardLayout"; -import DashboardNavbar from "examples/Navbars/DashboardNavbar"; -import MDBox from "components/MDBox"; -import MDButton from "components/MDButton"; -import MDAlert from "components/MDAlert"; - -// QQQ +import {QFieldType} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldType"; import {QProcessMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QProcessMetaData"; import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData"; -import {QQueryFilter} from "@kingsrook/qqq-frontend-core/lib/model/query/QQueryFilter"; -import {QFilterOrderBy} from "@kingsrook/qqq-frontend-core/lib/model/query/QFilterOrderBy"; -import {QFilterCriteria} from "@kingsrook/qqq-frontend-core/lib/model/query/QFilterCriteria"; import {QCriteriaOperator} from "@kingsrook/qqq-frontend-core/lib/model/query/QCriteriaOperator"; -import {QFieldType} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldType"; -import QClient from "qqq/utils/QClient"; -import Navbar from "qqq/components/Navbar"; +import {QFilterCriteria} from "@kingsrook/qqq-frontend-core/lib/model/query/QFilterCriteria"; +import {QFilterOrderBy} from "@kingsrook/qqq-frontend-core/lib/model/query/QFilterOrderBy"; +import {QQueryFilter} from "@kingsrook/qqq-frontend-core/lib/model/query/QQueryFilter"; +import {Alert, TablePagination} from "@mui/material"; import Button from "@mui/material/Button"; +import Card from "@mui/material/Card"; +import Icon from "@mui/material/Icon"; +import LinearProgress from "@mui/material/LinearProgress"; +import Menu from "@mui/material/Menu"; +import MenuItem from "@mui/material/MenuItem"; +import {DataGridPro, GridCallbackDetails, GridColDef, GridColumnOrderChangeParams, GridColumnVisibilityModel, GridFilterModel, GridRowId, GridRowParams, GridRowsProp, GridSelectionModel, GridSortItem, GridSortModel, GridToolbarColumnsButton, GridToolbarContainer, GridToolbarDensitySelector, GridToolbarExportContainer, GridToolbarFilterButton, GridExportMenuItemProps, MuiEvent,} from "@mui/x-data-grid-pro"; +import React, {useCallback, useEffect, useReducer, useRef, useState,} from "react"; +import {Link, useNavigate, useParams, useSearchParams,} from "react-router-dom"; +import MDAlert from "components/MDAlert"; +import MDBox from "components/MDBox"; +import DashboardLayout from "examples/LayoutContainers/DashboardLayout"; +import Navbar from "qqq/components/Navbar"; +import {QActionsMenuButton, QCreateNewButton} from "qqq/components/QButtons"; +import QClient from "qqq/utils/QClient"; +import QValueUtils from "qqq/utils/QValueUtils"; import Footer from "../../components/Footer"; import QProcessUtils from "../../utils/QProcessUtils"; - import "./styles.css"; -import {QActionsMenuButton, QCreateNewButton} from "qqq/components/QButtons"; -import QValueUtils from "qqq/utils/QValueUtils"; -import LinearProgress from "@mui/material/LinearProgress"; const COLUMN_VISIBILITY_LOCAL_STORAGE_KEY_ROOT = "qqq.columnVisibility"; const COLUMN_SORT_LOCAL_STORAGE_KEY_ROOT = "qqq.columnSort"; diff --git a/src/qqq/pages/entity-view/components/ViewContents/index.tsx b/src/qqq/pages/entity-view/components/ViewContents/index.tsx index 1fec934..9bb62a2 100644 --- a/src/qqq/pages/entity-view/components/ViewContents/index.tsx +++ b/src/qqq/pages/entity-view/components/ViewContents/index.tsx @@ -19,41 +19,34 @@ * along with this program. If not, see . */ -// react components -import {useLocation, useNavigate, useSearchParams} from "react-router-dom"; -import React, {useReducer, useState} from "react"; - -// @material-ui core components +import {QProcessMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QProcessMetaData"; +import {QSection} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QSection"; +import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData"; +import {QRecord} from "@kingsrook/qqq-frontend-core/lib/model/QRecord"; +import Avatar from "@mui/material/Avatar"; +import Button from "@mui/material/Button"; import Card from "@mui/material/Card"; -import Grid from "@mui/material/Grid"; import Dialog from "@mui/material/Dialog"; -import DialogTitle from "@mui/material/DialogTitle"; +import DialogActions from "@mui/material/DialogActions"; import DialogContent from "@mui/material/DialogContent"; import DialogContentText from "@mui/material/DialogContentText"; -import DialogActions from "@mui/material/DialogActions"; -import Button from "@mui/material/Button"; - -// qqq imports -import {QProcessMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QProcessMetaData"; - -// Material Dashboard 2 PRO React TS components -import MDBox from "components/MDBox"; -import MDTypography from "components/MDTypography"; +import DialogTitle from "@mui/material/DialogTitle"; +import Grid from "@mui/material/Grid"; +import Icon from "@mui/material/Icon"; import Menu from "@mui/material/Menu"; import MenuItem from "@mui/material/MenuItem"; -import MDAlert from "components/MDAlert"; -import QProcessUtils from "../../../../utils/QProcessUtils"; -import QClient from "qqq/utils/QClient"; -import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData"; -import {QActionsMenuButton, QDeleteButton, QEditButton} from "qqq/components/QButtons"; -import QValueUtils from "qqq/utils/QValueUtils"; -import {QRecord} from "@kingsrook/qqq-frontend-core/lib/model/QRecord"; -import Icon from "@mui/material/Icon"; -import Avatar from "@mui/material/Avatar"; -import QRecordSidebar from "qqq/components/QRecordSidebar"; -import QTableUtils from "qqq/utils/QTableUtils"; +import React, {useReducer, useState} from "react"; +import {useLocation, useNavigate, useSearchParams} from "react-router-dom"; import colors from "assets/theme/base/colors"; -import {QSection} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QSection"; +import MDAlert from "components/MDAlert"; +import MDBox from "components/MDBox"; +import MDTypography from "components/MDTypography"; +import {QActionsMenuButton, QDeleteButton, QEditButton} from "qqq/components/QButtons"; +import QRecordSidebar from "qqq/components/QRecordSidebar"; +import QClient from "qqq/utils/QClient"; +import QTableUtils from "qqq/utils/QTableUtils"; +import QValueUtils from "qqq/utils/QValueUtils"; +import QProcessUtils from "../../../../utils/QProcessUtils"; const qController = QClient.getInstance(); diff --git a/src/qqq/pages/entity-view/index.tsx b/src/qqq/pages/entity-view/index.tsx index b2ff022..6313079 100644 --- a/src/qqq/pages/entity-view/index.tsx +++ b/src/qqq/pages/entity-view/index.tsx @@ -19,19 +19,12 @@ * along with this program. If not, see . */ -import {useParams} from "react-router-dom"; - -// @mui material components +import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData"; import Grid from "@mui/material/Grid"; - -// Material Dashboard 2 PRO React TS components +import {useParams} from "react-router-dom"; import MDBox from "components/MDBox"; - -// Settings page components import BaseLayout from "qqq/components/BaseLayout"; import ViewContents from "./components/ViewContents"; -import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData"; -import EntityList from "qqq/pages/entity-list"; interface Props { diff --git a/src/qqq/pages/process-run/index.tsx b/src/qqq/pages/process-run/index.tsx index 8d3137f..c2d7964 100644 --- a/src/qqq/pages/process-run/index.tsx +++ b/src/qqq/pages/process-run/index.tsx @@ -19,46 +19,34 @@ * along with this program. If not, see . */ -import React, {useEffect, useState, Fragment} from "react"; - -// formik components -import {Form, Formik, useFormikContext} from "formik"; - -// @mui material components -import Grid from "@mui/material/Grid"; -import Card from "@mui/material/Card"; -import Stepper from "@mui/material/Stepper"; -import Step from "@mui/material/Step"; -import StepLabel from "@mui/material/StepLabel"; - -// Material Dashboard 2 PRO React TS components -import MDBox from "components/MDBox"; -import MDButton from "components/MDButton"; - -// Material Dashboard 2 PRO React TS examples components -import DashboardLayout from "examples/LayoutContainers/DashboardLayout"; - -import * as Yup from "yup"; +import {QComponentType} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QComponentType"; import {QFieldMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldMetaData"; +import {QFrontendComponent} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFrontendComponent"; import {QFrontendStepMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFrontendStepMetaData"; -import {useLocation, useParams} from "react-router-dom"; -import DynamicFormUtils from "qqq/components/QDynamicForm/utils/DynamicFormUtils"; -import {QJobStarted} from "@kingsrook/qqq-frontend-core/lib/model/processes/QJobStarted"; +import {QProcessMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QProcessMetaData"; 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 {DataGridPro, GridColDef} from "@mui/x-data-grid-pro"; -import {QFrontendComponent} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFrontendComponent"; -import {QComponentType} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QComponentType"; -import FormData from "form-data"; -import QClient from "qqq/utils/QClient"; +import {QJobStarted} from "@kingsrook/qqq-frontend-core/lib/model/processes/QJobStarted"; import {CircularProgress, TablePagination} from "@mui/material"; -import QDynamicForm from "../../components/QDynamicForm"; -import MDTypography from "../../../components/MDTypography"; -import Footer from "examples/Footer"; -import {QProcessMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QProcessMetaData"; -import Navbar from "qqq/components/Navbar"; +import Card from "@mui/material/Card"; +import Grid from "@mui/material/Grid"; +import Step from "@mui/material/Step"; +import StepLabel from "@mui/material/StepLabel"; +import Stepper from "@mui/material/Stepper"; +import {DataGridPro, GridColDef} from "@mui/x-data-grid-pro"; +import FormData from "form-data"; +import {Form, Formik, useFormikContext} from "formik"; +import React, {useEffect, useState, Fragment} from "react"; +import {useLocation, useParams} from "react-router-dom"; +import * as Yup from "yup"; +import MDBox from "components/MDBox"; +import MDButton from "components/MDButton"; import BaseLayout from "qqq/components/BaseLayout"; +import DynamicFormUtils from "qqq/components/QDynamicForm/utils/DynamicFormUtils"; +import QClient from "qqq/utils/QClient"; +import MDTypography from "../../../components/MDTypography"; +import QDynamicForm from "../../components/QDynamicForm"; interface Props { diff --git a/src/qqq/utils/QClient.ts b/src/qqq/utils/QClient.ts index 960595d..f64ee93 100644 --- a/src/qqq/utils/QClient.ts +++ b/src/qqq/utils/QClient.ts @@ -19,9 +19,9 @@ * along with this program. If not, see . */ +import {useAuth0} from "@auth0/auth0-react"; import {QController} from "@kingsrook/qqq-frontend-core/lib/controllers/QController"; import {QException} from "@kingsrook/qqq-frontend-core/lib/exceptions/QException"; -import {useAuth0} from "@auth0/auth0-react"; /******************************************************************************* ** client wrapper of qqq backend diff --git a/src/qqq/utils/QProcessUtils.ts b/src/qqq/utils/QProcessUtils.ts index d706b65..c34a5a1 100644 --- a/src/qqq/utils/QProcessUtils.ts +++ b/src/qqq/utils/QProcessUtils.ts @@ -19,8 +19,8 @@ * along with this program. If not, see . */ -import {QProcessMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QProcessMetaData"; import {QInstance} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QInstance"; +import {QProcessMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QProcessMetaData"; /******************************************************************************* ** Utility class for working with QQQ Processes diff --git a/src/qqq/utils/QTableUtils.ts b/src/qqq/utils/QTableUtils.ts index d75cc73..ae828c6 100644 --- a/src/qqq/utils/QTableUtils.ts +++ b/src/qqq/utils/QTableUtils.ts @@ -19,8 +19,8 @@ * along with this program. If not, see . */ -import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData"; import {QSection} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QSection"; +import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData"; /******************************************************************************* ** Utility class for working with QQQ Tables diff --git a/src/qqq/utils/QValueUtils.ts b/src/qqq/utils/QValueUtils.ts index 00bdb87..56e5af4 100644 --- a/src/qqq/utils/QValueUtils.ts +++ b/src/qqq/utils/QValueUtils.ts @@ -20,8 +20,8 @@ */ import {QFieldMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldMetaData"; -import {QRecord} from "@kingsrook/qqq-frontend-core/lib/model/QRecord"; import {QFieldType} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldType"; +import {QRecord} from "@kingsrook/qqq-frontend-core/lib/model/QRecord"; import "datejs"; From 0882b92b27508965b1fc087e580e94d3388e312b Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Mon, 29 Aug 2022 13:30:29 -0500 Subject: [PATCH 03/11] Checkpoint - new validation & summary screens --- .eslintrc.json | 1 + src/qqq/components/EntityForm/index.tsx | 4 +- src/qqq/components/QButtons/index.tsx | 49 +- src/qqq/pages/entity-list/index.tsx | 114 +++-- .../components/QProcessSummaryResults.tsx | 95 ++++ .../components/QValidationReview.tsx | 286 ++++++++++++ src/qqq/pages/process-run/index.tsx | 418 ++++++++++++------ .../process-run/model/ProcessSummaryLine.tsx | 138 ++++++ src/qqq/utils/QFilterUtils.ts | 229 ++++++++++ src/qqq/utils/QValueUtils.ts | 5 + 10 files changed, 1129 insertions(+), 210 deletions(-) create mode 100644 src/qqq/pages/process-run/components/QProcessSummaryResults.tsx create mode 100644 src/qqq/pages/process-run/components/QValidationReview.tsx create mode 100644 src/qqq/pages/process-run/model/ProcessSummaryLine.tsx create mode 100644 src/qqq/utils/QFilterUtils.ts diff --git a/.eslintrc.json b/.eslintrc.json index d9b248b..bbc7b81 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -57,6 +57,7 @@ } ], "import/order": "off", + "jsx-one-expression-per-line": "off", "max-len": "off", "no-console": "off", "no-constant-condition": "off", diff --git a/src/qqq/components/EntityForm/index.tsx b/src/qqq/components/EntityForm/index.tsx index 2a2329a..1cec5b8 100644 --- a/src/qqq/components/EntityForm/index.tsx +++ b/src/qqq/components/EntityForm/index.tsx @@ -334,8 +334,8 @@ function EntityForm({table, id}: Props): JSX.Element - - + + diff --git a/src/qqq/components/QButtons/index.tsx b/src/qqq/components/QButtons/index.tsx index 867ee66..6f54183 100644 --- a/src/qqq/components/QButtons/index.tsx +++ b/src/qqq/components/QButtons/index.tsx @@ -24,6 +24,7 @@ import {Link} from "react-router-dom"; import MDButton from "components/MDButton"; import Icon from "@mui/material/Icon"; import React from "react"; +import EntityForm from "qqq/components/EntityForm"; // eslint-disable import/prefer-default-export @@ -42,11 +43,16 @@ export function QCreateNewButton(): JSX.Element ); } -export function QSaveButton(): JSX.Element +interface QSaveButtonProps +{ + disabled: boolean +} + +export function QSaveButton({disabled}: QSaveButtonProps): JSX.Element { return ( - save}> + save} disabled={disabled}> Save @@ -108,15 +114,48 @@ export function QActionsMenuButton({isOpen, onClickHandler}: QActionsMenuButtonP interface QCancelButtonProps { onClickHandler: any; + disabled: boolean; + label?: string; + iconName?: string } -export function QCancelButton({onClickHandler}: QCancelButtonProps): JSX.Element +export function QCancelButton({ + onClickHandler, disabled, label, iconName, +}: QCancelButtonProps): JSX.Element { return ( - cancel} onClick={onClickHandler}> - Cancel + {iconName}} onClick={onClickHandler} disabled={disabled}> + {label} ); } + +QCancelButton.defaultProps = { + label: "cancel", + iconName: "cancel", +}; + +interface QSubmitButtonProps +{ + label?: string + iconName?: string + disabled: boolean +} + +export function QSubmitButton({label, iconName, disabled}: QSubmitButtonProps): JSX.Element +{ + return ( + + {iconName}} disabled={disabled}> + {label} + + + ); +} + +QSubmitButton.defaultProps = { + label: "Submit", + iconName: "check", +}; diff --git a/src/qqq/pages/entity-list/index.tsx b/src/qqq/pages/entity-list/index.tsx index 3ac6cc4..3a6ceca 100644 --- a/src/qqq/pages/entity-list/index.tsx +++ b/src/qqq/pages/entity-list/index.tsx @@ -20,9 +20,7 @@ */ import React, { - SyntheticEvent, - useCallback, - useEffect, useReducer, useRef, useState, + useCallback, useEffect, useReducer, useRef, useState, } from "react"; import { Link, useNavigate, useParams, useSearchParams, @@ -33,13 +31,14 @@ import Card from "@mui/material/Card"; import Icon from "@mui/material/Icon"; import Menu from "@mui/material/Menu"; import MenuItem from "@mui/material/MenuItem"; -import {Alert, Pagination, TablePagination} from "@mui/material"; +import {Alert, TablePagination} from "@mui/material"; import { DataGridPro, GridCallbackDetails, GridColDef, GridColumnOrderChangeParams, GridColumnVisibilityModel, + GridExportMenuItemProps, GridFilterModel, GridRowId, GridRowParams, @@ -52,15 +51,12 @@ import { GridToolbarDensitySelector, GridToolbarExportContainer, GridToolbarFilterButton, - GridExportMenuItemProps, MuiEvent, } from "@mui/x-data-grid-pro"; // Material Dashboard 2 PRO React TS components import DashboardLayout from "examples/LayoutContainers/DashboardLayout"; -import DashboardNavbar from "examples/Navbars/DashboardNavbar"; import MDBox from "components/MDBox"; -import MDButton from "components/MDButton"; import MDAlert from "components/MDAlert"; // QQQ @@ -80,6 +76,7 @@ import QProcessUtils from "../../utils/QProcessUtils"; import {QActionsMenuButton, QCreateNewButton} from "qqq/components/QButtons"; import QValueUtils from "qqq/utils/QValueUtils"; import LinearProgress from "@mui/material/LinearProgress"; +import QFilterUtils from "qqq/utils/QFilterUtils"; const COLUMN_VISIBILITY_LOCAL_STORAGE_KEY_ROOT = "qqq.columnVisibility"; const COLUMN_SORT_LOCAL_STORAGE_KEY_ROOT = "qqq.columnSort"; @@ -90,6 +87,52 @@ interface Props table?: QTableMetaData; } +/******************************************************************************* + ** Get the default filter to use on the page - either from query string, or + ** local storage, or a default (empty). + *******************************************************************************/ +function getDefaultFilter(searchParams: URLSearchParams, filterLocalStorageKey: string): GridFilterModel +{ + if (searchParams.has("filter")) + { + try + { + const qQueryFilter = JSON.parse(searchParams.get("filter")) as QQueryFilter; + console.log(`Got a filter from the query string: ${JSON.stringify(qQueryFilter)}`); + + ////////////////////////////////////////////////////////////////// + // translate from a qqq-style filter to one that the grid wants // + ////////////////////////////////////////////////////////////////// + const defaultFilter = {items: []} as GridFilterModel; + let id = 1; + qQueryFilter.criteria.forEach((criteria) => + { + defaultFilter.items.push({ + columnField: criteria.fieldName, + operatorValue: QFilterUtils.qqqCriteriaOperatorToGrid(criteria.operator), + value: QFilterUtils.qqqCriteriaValuesToGrid(criteria.operator, criteria.values), + id: id++, // not sure what this id is!! + }); + }); + + return (defaultFilter); + } + catch (e) + { + console.warn("Error parsing filter from query string", e); + } + } + + if (localStorage.getItem(filterLocalStorageKey)) + { + const defaultFilter = JSON.parse(localStorage.getItem(filterLocalStorageKey)); + console.log(`Got default from LS: ${JSON.stringify(defaultFilter)}`); + return (defaultFilter); + } + + return ({items: []}); +} + function EntityList({table}: Props): JSX.Element { const tableNameParam = useParams().tableName; @@ -105,7 +148,7 @@ function EntityList({table}: Props): JSX.Element const filterLocalStorageKey = `${FILTER_LOCAL_STORAGE_KEY_ROOT}.${tableName}`; let defaultSort = [] as GridSortItem[]; let defaultVisibility = {}; - let _defaultFilter = {items: []} as GridFilterModel; + const _defaultFilter = getDefaultFilter(searchParams, filterLocalStorageKey); if (localStorage.getItem(sortLocalStorageKey)) { @@ -115,11 +158,6 @@ function EntityList({table}: Props): JSX.Element { defaultVisibility = JSON.parse(localStorage.getItem(columnVisibilityLocalStorageKey)); } - if (localStorage.getItem(filterLocalStorageKey)) - { - _defaultFilter = JSON.parse(localStorage.getItem(filterLocalStorageKey)); - console.log(`Got default from LS: ${JSON.stringify(_defaultFilter)}`); - } const [filterModel, setFilterModel] = useState(_defaultFilter); const [columnSortModel, setColumnSortModel] = useState(defaultSort); @@ -159,46 +197,6 @@ function EntityList({table}: Props): JSX.Element const openActionsMenu = (event: any) => setActionsMenu(event.currentTarget); const closeActionsMenu = () => setActionsMenu(null); - const translateCriteriaOperator = (operator: string) => - { - switch (operator) - { - case "contains": - return QCriteriaOperator.CONTAINS; - case "startsWith": - return QCriteriaOperator.STARTS_WITH; - case "endsWith": - return QCriteriaOperator.ENDS_WITH; - case "is": - case "equals": - case "=": - return QCriteriaOperator.EQUALS; - case "isNot": - case "!=": - return QCriteriaOperator.NOT_EQUALS; - case "after": - case ">": - return QCriteriaOperator.GREATER_THAN; - case "onOrAfter": - case ">=": - return QCriteriaOperator.GREATER_THAN_OR_EQUALS; - case "before": - case "<": - return QCriteriaOperator.LESS_THAN; - case "onOrBefore": - case "<=": - return QCriteriaOperator.LESS_THAN_OR_EQUALS; - case "isEmpty": - return QCriteriaOperator.IS_BLANK; - case "isNotEmpty": - return QCriteriaOperator.IS_NOT_BLANK; - // case "is any of": - // TODO: handle this case - default: - return QCriteriaOperator.EQUALS; - } - }; - const buildQFilter = () => { const qFilter = new QQueryFilter(); @@ -213,13 +211,9 @@ function EntityList({table}: Props): JSX.Element { filterModel.items.forEach((item) => { - const operator = translateCriteriaOperator(item.operatorValue); - let criteria = new QFilterCriteria(item.columnField, operator, [item.value]); - if (operator === QCriteriaOperator.IS_BLANK || operator === QCriteriaOperator.IS_NOT_BLANK) - { - criteria = new QFilterCriteria(item.columnField, operator, null); - } - qFilter.addCriteria(criteria); + const operator = QFilterUtils.gridCriteriaOperatorToQQQ(item.operatorValue); + const values = QFilterUtils.gridCriteriaValueToQQQ(operator, item.value); + qFilter.addCriteria(new QFilterCriteria(item.columnField, operator, values)); }); } diff --git a/src/qqq/pages/process-run/components/QProcessSummaryResults.tsx b/src/qqq/pages/process-run/components/QProcessSummaryResults.tsx new file mode 100644 index 0000000..f4a1ac5 --- /dev/null +++ b/src/qqq/pages/process-run/components/QProcessSummaryResults.tsx @@ -0,0 +1,95 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2022. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import React from "react"; +import {QInstance} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QInstance"; +import {QProcessMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QProcessMetaData"; +import {QFrontendStepMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFrontendStepMetaData"; +import List from "@mui/material/List"; +import {ListItem} from "@mui/material"; +import ListItemText from "@mui/material/ListItemText"; +import {ProcessSummaryLine} from "qqq/pages/process-run/model/ProcessSummaryLine"; +import MDBox from "components/MDBox"; +import Grid from "@mui/material/Grid"; +import Icon from "@mui/material/Icon"; +import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData"; + +interface Props +{ + qInstance: QInstance; + process: QProcessMetaData; + table: QTableMetaData; + processValues: any; + step: QFrontendStepMetaData; +} + +/******************************************************************************* + ** This is the process summary result component. + *******************************************************************************/ +function QProcessSummaryResults({ + qInstance, process, table = null, processValues, step, +}: Props): JSX.Element +{ + const resultValidationList = ( + + { + processValues?.recordCount && table && ( + + + {processValues.recordCount.toLocaleString()} + {" "} + {table.label} + {" "} + records were processed. + + + ) + } + + { + processValues.processResults && processValues.processResults.map((processSummaryLine: ProcessSummaryLine, i: number) => (new ProcessSummaryLine(processSummaryLine).getProcessSummaryListItem(i, table, qInstance, true))) + } + + + ); + + return ( + + + + + + + + {process.iconName} + {`${process.label} : ${step.label}`} + + + {resultValidationList} + + + + + + ); +} + +export default QProcessSummaryResults; diff --git a/src/qqq/pages/process-run/components/QValidationReview.tsx b/src/qqq/pages/process-run/components/QValidationReview.tsx new file mode 100644 index 0000000..d5f4357 --- /dev/null +++ b/src/qqq/pages/process-run/components/QValidationReview.tsx @@ -0,0 +1,286 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2022. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import List from "@mui/material/List"; +import { + Button, FormControlLabel, ListItem, Radio, RadioGroup, tooltipClasses, TooltipProps, +} from "@mui/material"; +import ListItemText from "@mui/material/ListItemText"; +import Icon from "@mui/material/Icon"; +import MDBox from "components/MDBox"; +import MDTypography from "components/MDTypography"; +import Grid from "@mui/material/Grid"; +import React, {useState} from "react"; +import {QFrontendStepMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFrontendStepMetaData"; +import {QInstance} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QInstance"; +import {QProcessMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QProcessMetaData"; +import QValueUtils from "qqq/utils/QValueUtils"; +import {QRecord} from "@kingsrook/qqq-frontend-core/lib/model/QRecord"; +import {ProcessSummaryLine} from "qqq/pages/process-run/model/ProcessSummaryLine"; +import {Field} from "formik"; +import Tooltip from "@mui/material/Tooltip"; +import IconButton from "@mui/material/IconButton"; +import {styled} from "@mui/material/styles"; +import QTableUtils from "qqq/utils/QTableUtils"; +import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData"; + +interface Props +{ + qInstance: QInstance; + process: QProcessMetaData; + table: QTableMetaData; + processValues: any; + step: QFrontendStepMetaData; + previewRecords: QRecord[]; + formValues: any; + doFullValidationRadioChangedHandler: any +} + +/******************************************************************************* + ** This is the process validation/review component - where the user may be prompted + ** to do a full validation or skip it. It's the same screen that shows validation + ** results when they are available. + *******************************************************************************/ +function QValidationReview({ + qInstance, process, table = null, processValues, step, previewRecords = [], formValues, doFullValidationRadioChangedHandler, +}: Props): JSX.Element +{ + const [previewRecordIndex, setPreviewRecordIndex] = useState(0); + + const updatePreviewRecordIndex = (offset: number) => + { + let newIndex = previewRecordIndex + offset; + if (newIndex < 0) + { + newIndex = 0; + } + if (newIndex >= previewRecords.length - 1) + { + newIndex = previewRecords.length - 1; + } + + setPreviewRecordIndex(newIndex); + }; + + const CustomWidthTooltip = styled(({className, ...props}: TooltipProps) => ( + + ))({ + [`& .${tooltipClasses.tooltip}`]: { + maxWidth: 500, + textAlign: "left", + }, + }); + + const preValidationList = ( + + { + processValues?.recordCount && table && ( + + + You selected + {` ${processValues.recordCount.toLocaleString()} ${table?.label} `} + records. + + + ) + } + { + processValues?.supportsFullValidation && formValues && formValues.doFullValidation !== undefined && ( + <> + + How would you like to proceed? + + + + + } + label={( + + Perform Validation on all records before processing. + + If you choose this option, a Validation step will run on all of the records that you selected. + You will then be told how many can process successfully, and how many have issues. +
+
+ Running this validation may take several minutes, depending on the complexity of the work, and the number of records. +
+
+ Choose this option if you want more information about what will happen, and you are willing to wait for that information. + + )} + > + info_outlined +
+
+ )} + /> +
+ + } + label={( + + Skip Validation. Submit the records for immediate processing. + + If you choose this option, the records you selected will immediately be processed. + You will be told how many records were successfully processed, and which ones had issues after the processing is completed. +
+
+ Choose this option if you feel that you do not need this information, or are not willing to wait for it. + + )} + > + info_outlined +
+
+ )} + /> +
+
+
+ + ) + } +
+ ); + + const postValidationList = ( + + { + processValues?.recordCount && table && ( + + + Validation complete on + {` ${processValues.recordCount.toLocaleString()} ${table?.label} `} + records. + + + ) + } + + { + processValues.validationSummary && processValues.validationSummary.map((processSummaryLine: ProcessSummaryLine, i: number) => (new ProcessSummaryLine(processSummaryLine).getProcessSummaryListItem(i, table, qInstance))) + } + + + ); + + const recordPreviewWidget = step.recordListFields && ( + + + Preview + + + + + { + previewRecords && previewRecords.length > 0 ? ( + <> + This is a preview of the records that will be created. + + Note that the number of preview records available may be fewer than the total number of records being processed. + + )} + > + info_outlined + + + ) : ( + <> + No record previews are available at this time. + + { + processValues.validationSummary ? ( + <> + It appears as though this process does not contain any valid records. + + ) : ( + <> + If you choose to Perform Validation, and there are any valid records, then you will see a preview here. + + ) + } + + )} + > + info_outlined + + + ) + } + + + + { + previewRecords && previewRecords[previewRecordIndex] && step.recordListFields.map((field) => ( + + {`${field.label}:`} + {" "} +   + {" "} + {QValueUtils.getDisplayValue(field, previewRecords[previewRecordIndex])} + + )) + } + { + previewRecords && previewRecords.length > 0 && ( + + + + {`Preview ${previewRecordIndex + 1} of ${previewRecords.length}`} + + + + ) + } + + + + ); + + return ( + + + + + {processValues.validationSummary ? postValidationList : preValidationList} + + + + {recordPreviewWidget} + + + + ); +} + +export default QValidationReview; diff --git a/src/qqq/pages/process-run/index.tsx b/src/qqq/pages/process-run/index.tsx index ccc034b..262b07a 100644 --- a/src/qqq/pages/process-run/index.tsx +++ b/src/qqq/pages/process-run/index.tsx @@ -19,47 +19,46 @@ * along with this program. If not, see . */ -import React, {useEffect, useState, Fragment} from "react"; +import * as Yup from "yup"; +import {CircularProgress, TablePagination} from "@mui/material"; +import {DataGridPro, GridColDef} from "@mui/x-data-grid-pro"; // formik components import {Form, Formik} from "formik"; +import React, {Fragment, useEffect, useState} from "react"; +import {useLocation, useNavigate, useParams} from "react-router-dom"; -// @mui material components -import Grid from "@mui/material/Grid"; +import BaseLayout from "qqq/components/BaseLayout"; import Card from "@mui/material/Card"; -import Stepper from "@mui/material/Stepper"; -import Step from "@mui/material/Step"; -import StepLabel from "@mui/material/StepLabel"; - +import DynamicFormUtils from "qqq/components/QDynamicForm/utils/DynamicFormUtils"; +import FormData from "form-data"; +import Grid from "@mui/material/Grid"; // Material Dashboard 2 PRO React TS components import MDBox from "components/MDBox"; import MDButton from "components/MDButton"; - -// Material Dashboard 2 PRO React TS examples components -import DashboardLayout from "examples/LayoutContainers/DashboardLayout"; - -import * as Yup from "yup"; +import MDProgress from "components/MDProgress"; +import MDTypography from "../../../components/MDTypography"; +import QClient from "qqq/utils/QClient"; +import {QComponentType} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QComponentType"; +import QDynamicForm from "../../components/QDynamicForm"; import {QFieldMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldMetaData"; +import {QFrontendComponent} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFrontendComponent"; import {QFrontendStepMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFrontendStepMetaData"; -import {useLocation, useParams} from "react-router-dom"; -import DynamicFormUtils from "qqq/components/QDynamicForm/utils/DynamicFormUtils"; -import {QJobStarted} from "@kingsrook/qqq-frontend-core/lib/model/processes/QJobStarted"; 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 {DataGridPro, GridColDef} from "@mui/x-data-grid-pro"; -import {QFrontendComponent} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFrontendComponent"; -import {QComponentType} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QComponentType"; -import FormData from "form-data"; -import QClient from "qqq/utils/QClient"; -import {CircularProgress, TablePagination} from "@mui/material"; -import QDynamicForm from "../../components/QDynamicForm"; -import MDTypography from "../../../components/MDTypography"; -import Footer from "examples/Footer"; +import {QJobStarted} from "@kingsrook/qqq-frontend-core/lib/model/processes/QJobStarted"; import {QProcessMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QProcessMetaData"; -import Navbar from "qqq/components/Navbar"; -import BaseLayout from "qqq/components/BaseLayout"; import {QException} from "@kingsrook/qqq-frontend-core/lib/exceptions/QException"; +import Step from "@mui/material/Step"; +import StepLabel from "@mui/material/StepLabel"; +import Stepper from "@mui/material/Stepper"; +import QValidationReview from "qqq/pages/process-run/components/QValidationReview"; +import {QInstance} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QInstance"; +import QProcessSummaryResults from "./components/QProcessSummaryResults"; +import {QRecord} from "@kingsrook/qqq-frontend-core/lib/model/QRecord"; +import {QCancelButton, QSubmitButton} from "qqq/components/QButtons"; +import {formatDate} from "@fullcalendar/react"; interface Props { @@ -89,12 +88,16 @@ function ProcessRun({process}: Props): JSX.Element const [steps, setSteps] = useState([] as QFrontendStepMetaData[]); const [needInitialLoad, setNeedInitialLoad] = useState(true); const [processMetaData, setProcessMetaData] = useState(null); + const [tableMetaData, setTableMetaData] = useState(null); + const [qInstance, setQInstance] = useState(null as QInstance); const [processValues, setProcessValues] = useState({} as any); const [processError, setProcessError] = useState(null as string); const [needToCheckJobStatus, setNeedToCheckJobStatus] = useState(false); const [lastProcessResponse, setLastProcessResponse] = useState( null as QJobStarted | QJobComplete | QJobError | QJobRunning, ); + + const [overrideOnLastStep, setOverrideOnLastStep] = useState(null as boolean); const onLastStep = activeStepIndex === steps.length - 2; const noMoreSteps = activeStepIndex === steps.length - 1; @@ -115,12 +118,16 @@ function ProcessRun({process}: Props): JSX.Element const [recordConfig, setRecordConfig] = useState({} as any); const [pageNumber, setPageNumber] = useState(0); const [rowsPerPage, setRowsPerPage] = useState(10); + const [records, setRecords] = useState([] as QRecord[]); ////////////////////////////// // state for bulk edit form // ////////////////////////////// const [disabledBulkEditFields, setDisabledBulkEditFields] = useState({} as any); + const navigate = useNavigate(); + const location = useLocation(); + const doesStepHaveComponent = (step: QFrontendStepMetaData, type: QComponentType): boolean => { if (step.components) @@ -171,7 +178,7 @@ function ProcessRun({process}: Props): JSX.Element { if (value === null || value === undefined) { - return ∅; + return  ; } if (typeof value === "string") @@ -202,13 +209,14 @@ function ProcessRun({process}: Props): JSX.Element processError: string, processValues: any, recordConfig: any, + setFieldValue: any, ): JSX.Element => { if (processError) { return ( <> - + Error @@ -218,44 +226,49 @@ function ProcessRun({process}: Props): JSX.Element ); } - if (qJobRunning) + if (qJobRunning || step === null) { return ( - <> - - {" "} - Working - - - - - - - - {qJobRunning?.message} -
- {qJobRunning.current && qJobRunning.total && ( -
{`${qJobRunning.current.toLocaleString()} of ${qJobRunning.total.toLocaleString()}`}
- )} - - {`Updated at ${qJobRunningDate.toLocaleTimeString()}`} - -
-
+ + + + + + + Working + + + + + + + + {qJobRunning?.message} +
+ {qJobRunning?.current && qJobRunning?.total && ( + <> +
{`${qJobRunning.current.toLocaleString()} of ${qJobRunning.total.toLocaleString()}`}
+ + + + + )} + { + qJobRunningDate && ({`Updated at ${qJobRunningDate?.toLocaleTimeString()}`}) + } +
+
+
+
+
- +
); } - if (step === null) - { - console.log("in getDynamicStepContent. No step yet, so returning 'loading'"); - return
Loading...
; - } - return ( <> - {step?.label} + {step?.label} {step.components && ( step.components.map((component: QFrontendComponent, index: number) => ( // eslint-disable-next-line react/no-array-index-key @@ -267,60 +280,97 @@ function ProcessRun({process}: Props): JSX.Element
) } + { + component.type === QComponentType.BULK_EDIT_FORM && ( + + ) + } + { + component.type === QComponentType.EDIT_FORM && ( + + ) + } + { + component.type === QComponentType.VIEW_FORM && step.viewFields && ( +
+ {step.viewFields.map((field: QFieldMetaData) => ( + + + {field.label} + :   + + + {formatViewValue(processValues[field.name])} + + + ))} +
+ ) + } + { + component.type === QComponentType.VALIDATION_REVIEW_SCREEN && ( + + { + const {value} = event.currentTarget; + + ////////////////////////////////////////////////////////////// + // call the formik function to set the value in this field. // + ////////////////////////////////////////////////////////////// + setFieldValue("doFullValidation", value); + + // eslint-disable-next-line no-unneeded-ternary + setOverrideOnLastStep(value === "true" ? false : true); + }} + /> + ) + } + { + component.type === QComponentType.PROCESS_SUMMARY_RESULTS && ( + + ) + } + { + component.type === QComponentType.RECORD_LIST && step.recordListFields && recordConfig.columns && ( +
+ Records + {" "} +
+ + row.__idForDataGridPro__} + paginationMode="server" + pagination + density="compact" + loading={recordConfig.loading} + disableColumnFilter + /> + +
+ ) + } )))} - {step.formFields && ( - - )} - {step.viewFields && ( -
- {step.viewFields.map((field: QFieldMetaData) => ( - - - {field.label} - :   - - - {formatViewValue(processValues[field.name])} - - - ))} -
- )} - {(step.recordListFields && recordConfig.columns) && ( -
- Records - {" "} -
- - row.__idForDataGridPro__} - paginationMode="server" - pagination - density="compact" - loading={recordConfig.loading} - disableColumnFilter - /> - -
- )} ); }; @@ -382,6 +432,7 @@ function ProcessRun({process}: Props): JSX.Element setProcessError(`Unknown process step ${newStep}.`); } setActiveStepIndex(newIndex); + setOverrideOnLastStep(null); if (steps) { @@ -424,6 +475,26 @@ function ProcessRun({process}: Props): JSX.Element setValidationScheme(Yup.object().shape(formValidations)); setValidationFunction(null); } + else if (doesStepHaveComponent(activeStep, QComponentType.VALIDATION_REVIEW_SCREEN)) + { + //////////////////////////////////////// + // this component requires this field // + //////////////////////////////////////// + const dynamicFormFields: any = {}; + dynamicFormFields.doFullValidation = {type: "radio"}; + + const initialValues: any = {}; + initialValues.doFullValidation = "true"; + setOverrideOnLastStep(false); + + const formValidations: any = {}; + formValidations.doFullValidation = null; + + setFormFields(dynamicFormFields); + setInitialValues(initialValues); + setValidationScheme(Yup.object().shape(formValidations)); + setValidationFunction(null); + } else { ///////////////////////////////////////////////////////////////////////// @@ -502,6 +573,7 @@ function ProcessRun({process}: Props): JSX.Element ); const {records} = response; + setRecords(records); ///////////////////////////////////////////////////////////////////////////////////////// // re-construct the recordConfig object, so the setState call triggers a new rendering // @@ -542,6 +614,12 @@ function ProcessRun({process}: Props): JSX.Element setJobUUID(null); setNewStep(qJobComplete.nextStep); setProcessValues(qJobComplete.values); + setQJobRunning(null); + + if (activeStep && activeStep.recordListFields) + { + setNeedRecords(true); + } } else if (lastProcessResponse instanceof QJobStarted) { @@ -562,6 +640,7 @@ function ProcessRun({process}: Props): JSX.Element console.log(`Got an error from the backend... ${qJobError.error}`); setJobUUID(null); setProcessError(qJobError.error); + setQJobRunning(null); } } }, [lastProcessResponse]); @@ -574,13 +653,18 @@ function ProcessRun({process}: Props): JSX.Element if (needToCheckJobStatus) { setNeedToCheckJobStatus(false); + if (!processUUID || !jobUUID) + { + console.log(`Missing processUUID[${processUUID}] or jobUUID[${jobUUID}], so returning without checking job status`); + return; + } + (async () => { setTimeout(async () => { try { - console.log("OK"); const processResponse = await QClient.getInstance().processJobStatus( processName, processUUID, @@ -624,8 +708,7 @@ function ProcessRun({process}: Props): JSX.Element setNeedInitialLoad(false); (async () => { - const {search} = useLocation(); - const urlSearchParams = new URLSearchParams(search); + const urlSearchParams = new URLSearchParams(location.search); let queryStringForInit = null; if (urlSearchParams.get("recordIds")) { @@ -644,11 +727,35 @@ function ProcessRun({process}: Props): JSX.Element // queryStringForInit = `recordsParam=filterId&filterId=${urlSearchParams.get("filterId")}` // } + try + { + const qInstance = await QClient.getInstance().loadMetaData(); + setQInstance(qInstance); + } + catch (e) + { + setProcessError("Error loading process definition."); + return; + } + try { const processMetaData = await QClient.getInstance().loadProcessMetaData(processName); setProcessMetaData(processMetaData); setSteps(processMetaData.frontendSteps); + if (processMetaData.tableName) + { + try + { + const tableMetaData = await QClient.getInstance().loadTableMetaData(processMetaData.tableName); + setTableMetaData(tableMetaData); + } + catch (e) + { + setProcessError("Error loading process's table definition."); + return; + } + } } catch (e) { @@ -714,6 +821,8 @@ function ProcessRun({process}: Props): JSX.Element "content-type": "multipart/form-data; boundary=--------------------------320289315924586491558366", }; + setProcessValues({}); + setRecords([]); setLastProcessResponse(new QJobRunning({message: "Working..."})); setTimeout(async () => @@ -729,15 +838,42 @@ function ProcessRun({process}: Props): JSX.Element }); }; + const handleCancelClicked = () => + { + const pathParts = location.pathname.split(/\//); + pathParts.pop(); + const path = pathParts.join("/"); + navigate(path, {replace: true}); + }; + + const mainCardStyles: any = {}; + mainCardStyles.minHeight = "calc(100vh - 400px)"; + if (qJobRunning || activeStep === null) + { + mainCardStyles.background = "none"; + mainCardStyles.boxShadow = "none"; + } + + let nextButtonLabel = "Next"; + let nextButtonIcon = "arrow_forward"; + if (overrideOnLastStep !== null) + { + if (overrideOnLastStep) + { + nextButtonLabel = "Submit"; + nextButtonIcon = "check"; + } + } + else if (onLastStep) + { + nextButtonLabel = "Submit"; + nextButtonIcon = "check"; + } + return ( - + {({ - values, errors, touched, isSubmitting, + values, errors, touched, isSubmitting, setFieldValue, }) => (
- + {steps.map((step) => ( @@ -760,6 +896,7 @@ function ProcessRun({process}: Props): JSX.Element ))} + {/*************************************************************************** @@ -777,28 +914,18 @@ function ProcessRun({process}: Props): JSX.Element processError, processValues, recordConfig, + setFieldValue, )} {/******************************** ** back &| next/submit buttons ** ********************************/} - + {true || activeStepIndex === 0 ? ( ) : ( - - back - + back )} - {noMoreSteps || processError || qJobRunning ? ( + {processError || qJobRunning || !activeStep ? ( ) : ( <> @@ -807,14 +934,19 @@ function ProcessRun({process}: Props): JSX.Element {formError} )} - - {onLastStep ? "submit" : "next"} - + { + noMoreSteps && + } + { + !noMoreSteps && ( + + + + + + + ) + } )} diff --git a/src/qqq/pages/process-run/model/ProcessSummaryLine.tsx b/src/qqq/pages/process-run/model/ProcessSummaryLine.tsx new file mode 100644 index 0000000..55dacf9 --- /dev/null +++ b/src/qqq/pages/process-run/model/ProcessSummaryLine.tsx @@ -0,0 +1,138 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2022. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData"; +import {QQueryFilter} from "@kingsrook/qqq-frontend-core/lib/model/query/QQueryFilter"; +import {QFilterCriteria} from "@kingsrook/qqq-frontend-core/lib/model/query/QFilterCriteria"; +import {QCriteriaOperator} from "@kingsrook/qqq-frontend-core/lib/model/query/QCriteriaOperator"; +import {ListItem} from "@mui/material"; +import Icon from "@mui/material/Icon"; +import ListItemText from "@mui/material/ListItemText"; +import Tooltip from "@mui/material/Tooltip"; +import {Link} from "react-router-dom"; +import IconButton from "@mui/material/IconButton"; +import React from "react"; +import {QInstance} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QInstance"; +import QTableUtils from "qqq/utils/QTableUtils"; +import MDBox from "components/MDBox"; + +/******************************************************************************* + ** Entity that corresponds to qqq backend's ProcessSummaryLine - with methods + ** to help display properly in a process review screen. + *******************************************************************************/ +// eslint-disable-next-line import/prefer-default-export +export class ProcessSummaryLine +{ + status: "OK" | "INFO" | "WARNING" | "ERROR"; + + count: number; + + message: string; + + primaryKeys: any[]; + + constructor(processSummaryLine: any) + { + this.status = processSummaryLine.status; + this.count = processSummaryLine.count; + this.message = processSummaryLine.message; + this.primaryKeys = processSummaryLine.primaryKeys; + } + + getProcessSummaryListItem(i: number, table: QTableMetaData, qInstance: QInstance, isResultScreen: boolean = false): JSX.Element + { + return ( + + + {this.getIcon(isResultScreen)} + + {this.count.toLocaleString()} + {" "} + {this.message} + + { + table && this.primaryKeys && ( + + + open_in_new + + + ) + } + + + ); + } + + private getColor(): "success" | "info" | "warning" | "error" | "secondary" + { + if (this.status === "OK") + { + return "success"; + } + else if (this.status === "INFO") + { + return "info"; + } + else if (this.status === "WARNING") + { + return "warning"; + } + else if (this.status === "ERROR") + { + return "error"; + } + else + { + return "secondary"; + } + } + + private getIcon(isResultScreen: boolean): string + { + if (this.status === "OK") + { + return isResultScreen ? "check" : "arrow_forward"; + } + else if (this.status === "INFO") + { + return "info"; + } + else if (this.status === "WARNING") + { + return "warning_amber"; + } + else if (this.status === "ERROR") + { + return "report"; + } + return ""; + } + + private getLinkToRecords(table: QTableMetaData, qInstance: QInstance): string + { + const tablePath = qInstance.getTablePath(table); + const filter = new QQueryFilter([new QFilterCriteria(table.primaryKeyField, QCriteriaOperator.IN, this.primaryKeys)]); + console.log("Link to records:"); + console.log(filter); + return (`${tablePath}?filter=${JSON.stringify(filter)}`); + } +} diff --git a/src/qqq/utils/QFilterUtils.ts b/src/qqq/utils/QFilterUtils.ts new file mode 100644 index 0000000..5728661 --- /dev/null +++ b/src/qqq/utils/QFilterUtils.ts @@ -0,0 +1,229 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2022. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import {QCriteriaOperator} from "@kingsrook/qqq-frontend-core/lib/model/query/QCriteriaOperator"; +import {QFieldType} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldType"; + +/******************************************************************************* + ** Utility class for working with QQQ Filters + ** + *******************************************************************************/ +class QFilterUtils +{ + /******************************************************************************* + ** Convert a grid operator to a QQQ Criteria Operator. + *******************************************************************************/ + public static gridCriteriaOperatorToQQQ = (operator: string): QCriteriaOperator => + { + switch (operator) + { + case "contains": + return QCriteriaOperator.CONTAINS; + case "startsWith": + return QCriteriaOperator.STARTS_WITH; + case "endsWith": + return QCriteriaOperator.ENDS_WITH; + case "is": + case "equals": + case "=": + return QCriteriaOperator.EQUALS; + case "isNot": + case "!=": + return QCriteriaOperator.NOT_EQUALS; + case "after": + case ">": + return QCriteriaOperator.GREATER_THAN; + case "onOrAfter": + case ">=": + return QCriteriaOperator.GREATER_THAN_OR_EQUALS; + case "before": + case "<": + return QCriteriaOperator.LESS_THAN; + case "onOrBefore": + case "<=": + return QCriteriaOperator.LESS_THAN_OR_EQUALS; + case "isEmpty": + return QCriteriaOperator.IS_BLANK; + case "isNotEmpty": + return QCriteriaOperator.IS_NOT_BLANK; + case "isAny": + return QCriteriaOperator.IN; + case "isNone": // todo - verify - not seen in UI + return QCriteriaOperator.NOT_IN; + default: + return QCriteriaOperator.EQUALS; + } + }; + + /******************************************************************************* + ** Convert a qqq criteria operator to one expected by the grid. + *******************************************************************************/ + public static qqqCriteriaOperatorToGrid = (operator: QCriteriaOperator, fieldType: QFieldType = QFieldType.STRING): string => + { + switch (operator) + { + case QCriteriaOperator.EQUALS: + switch (fieldType) + { + case QFieldType.INTEGER: + case QFieldType.DECIMAL: + return ("="); + case QFieldType.DATE: + case QFieldType.TIME: + case QFieldType.DATE_TIME: + return ("equals"); + case QFieldType.BOOLEAN: + case QFieldType.STRING: + case QFieldType.TEXT: + case QFieldType.HTML: + case QFieldType.PASSWORD: + case QFieldType.BLOB: + default: + return ("is"); + } + case QCriteriaOperator.NOT_EQUALS: + switch (fieldType) + { + case QFieldType.INTEGER: + case QFieldType.DECIMAL: + return ("!="); + case QFieldType.DATE: + case QFieldType.TIME: + case QFieldType.DATE_TIME: + case QFieldType.BOOLEAN: + case QFieldType.STRING: + case QFieldType.TEXT: + case QFieldType.HTML: + case QFieldType.PASSWORD: + case QFieldType.BLOB: + default: + return ("isNot"); + } + case QCriteriaOperator.IN: + return ("isAny"); + case QCriteriaOperator.NOT_IN: + return ("isNone"); // todo verify - not seen in UI + case QCriteriaOperator.STARTS_WITH: + return ("startsWith"); + case QCriteriaOperator.ENDS_WITH: + return ("endsWith"); + case QCriteriaOperator.CONTAINS: + return ("contains"); + case QCriteriaOperator.NOT_STARTS_WITH: + return (""); // todo - not supported in grid? + case QCriteriaOperator.NOT_ENDS_WITH: + return (""); // todo - not supported in grid? + case QCriteriaOperator.NOT_CONTAINS: + return (""); // todo - not supported in grid? + case QCriteriaOperator.LESS_THAN: + switch (fieldType) + { + case QFieldType.DATE: + case QFieldType.TIME: + case QFieldType.DATE_TIME: + return ("before"); + default: + return ("<"); + } + case QCriteriaOperator.LESS_THAN_OR_EQUALS: + switch (fieldType) + { + case QFieldType.DATE: + case QFieldType.TIME: + case QFieldType.DATE_TIME: + return ("onOrBefore"); + default: + return ("<="); + } + case QCriteriaOperator.GREATER_THAN: + switch (fieldType) + { + case QFieldType.DATE: + case QFieldType.TIME: + case QFieldType.DATE_TIME: + return ("after"); + default: + return (">"); + } + case QCriteriaOperator.GREATER_THAN_OR_EQUALS: + switch (fieldType) + { + case QFieldType.DATE: + case QFieldType.TIME: + case QFieldType.DATE_TIME: + return ("onOrAfter"); + default: + return (">="); + } + case QCriteriaOperator.IS_BLANK: + return ("isEmpty"); + case QCriteriaOperator.IS_NOT_BLANK: + return ("isNotEmpty"); + case QCriteriaOperator.BETWEEN: + return (""); // todo - not supported in grid? + case QCriteriaOperator.NOT_BETWEEN: + return (""); // todo - not supported in grid? + default: + console.warn(`Unhandled criteria operator: ${operator}`); + return ("="); + } + }; + + /******************************************************************************* + ** the values object needs handled differently based on cardinality of the operator. + ** that is - qqq always wants a list, but the grid provides it differently per-operator. + ** for single-values (the default), we must wrap it in an array. + ** for non-values (e.g., blank), set it to null. + ** for list-values, it's already in an array, so don't wrap it. + *******************************************************************************/ + public static gridCriteriaValueToQQQ = (operator: QCriteriaOperator, value: any): any[] => + { + if (operator === QCriteriaOperator.IS_BLANK || operator === QCriteriaOperator.IS_NOT_BLANK) + { + return (null); + } + else if (operator === QCriteriaOperator.IN || operator === QCriteriaOperator.NOT_IN) + { + return (value); + } + + return ([value]); + }; + + /******************************************************************************* + ** + *******************************************************************************/ + public static qqqCriteriaValuesToGrid = (operator: QCriteriaOperator, values: any[]): any | any[] => + { + if (operator === QCriteriaOperator.IS_BLANK || operator === QCriteriaOperator.IS_NOT_BLANK) + { + return (null); // todo - verify + } + else if (operator === QCriteriaOperator.IN || operator === QCriteriaOperator.NOT_IN) + { + return (values); + } + + return (values[0]); + }; +} + +export default QFilterUtils; diff --git a/src/qqq/utils/QValueUtils.ts b/src/qqq/utils/QValueUtils.ts index 00bdb87..062617c 100644 --- a/src/qqq/utils/QValueUtils.ts +++ b/src/qqq/utils/QValueUtils.ts @@ -56,6 +56,11 @@ class QValueUtils return (displayValue); } + if (displayValue === undefined && rawValue !== undefined) + { + return (rawValue); + } + return (displayValue); } } From 8b2364b596b3526e7c77a66bc8eaf13fbc20c972 Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Mon, 29 Aug 2022 14:38:59 -0500 Subject: [PATCH 04/11] Update to qqq-frontend-core 1.0.13 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b332395..7145fd9 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "@fullcalendar/interaction": "5.10.0", "@fullcalendar/react": "5.10.0", "@fullcalendar/timegrid": "5.10.0", - "@kingsrook/qqq-frontend-core": "1.0.12", + "@kingsrook/qqq-frontend-core": "1.0.13", "@mui/icons-material": "5.4.1", "@mui/material": "5.4.1", "@mui/styled-engine": "5.4.1", From 28253bd785799353375174e0adc05c25d0f25f69 Mon Sep 17 00:00:00 2001 From: Tim Chamberlain Date: Mon, 29 Aug 2022 21:48:38 -0500 Subject: [PATCH 05/11] QQQ-38: updated various styles --- public/apple-icon.png | Bin 2470 -> 8202 bytes public/favicon.png | Bin 2470 -> 8202 bytes src/App.tsx | 4 +- src/assets/images/favicon.png | Bin 2470 -> 8202 bytes src/index.tsx | 4 +- src/qqq/components/Widgets/BarChart.tsx | 99 ++++++++++++++++ .../Widgets/Configs/BarChartConfig.ts | 104 +++++++++++++++++ .../Widgets/Configs/PieChartConfigs.ts | 100 ++++++++++++++++ src/qqq/components/Widgets/PieChart.tsx | 110 ++++++++++++++++++ .../components/Widgets/QuickSightChart.tsx | 57 +++++++++ src/qqq/images/logo.png | Bin 0 -> 8202 bytes src/qqq/pages/app-home/index.tsx | 13 ++- src/qqq/pages/entity-list/index.tsx | 4 - src/qqq/pages/process-run/index.tsx | 2 +- src/qqq/utils/QValueUtils.ts | 1 - 15 files changed, 485 insertions(+), 13 deletions(-) create mode 100644 src/qqq/components/Widgets/BarChart.tsx create mode 100644 src/qqq/components/Widgets/Configs/BarChartConfig.ts create mode 100644 src/qqq/components/Widgets/Configs/PieChartConfigs.ts create mode 100644 src/qqq/components/Widgets/PieChart.tsx create mode 100644 src/qqq/components/Widgets/QuickSightChart.tsx create mode 100644 src/qqq/images/logo.png diff --git a/public/apple-icon.png b/public/apple-icon.png index 59c68b9103b9ab967b5e481843112202c7dd894b..d594bc60d13efdfd105f4b9d7d5aed014c3c918a 100644 GIT binary patch literal 8202 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 literal 2470 zcmV;X30d}uP)DO=Wi51haAg2tc42g3a^wOUVE_OLL`g(JRCwC$U2AL3O)dlr?v91JUoXzh21lEI=eeFJ9l^QNxr0+?%sRn+{by|vjBz+88T$Zff^#R z5xI2Ig&CFy$~NGj(EUuCmcmALG(ssrTc1soLBm}I2c)rIKilm zpA zYH5vP{G=`y=+68OtZB%>lO@_IiFR-&9ynH7meiuc;ClT66A5xDRBtkWlove*(=3Fg4HBhr!Iv03@4!2CMOwAl%B zgSHcfDfIjmNq+3uQDcQnILn4+VCtr{aCaoBz>P&P?^0OI?SnXa%EUlwl1$Z%mpUu# zgt9~*Mda0#j18+#8IwzmGbSal>ZsGc2F-y0_ZV1IDe5VNt7ef)G9~FVs zfj8doPARx?Ky*3_(wAlnUhD^-!<%Ek>d$YL#uGsAE)xmH_-Tyn5S`5cLo>kYc=RPn zthdm7D5RHf_AN=1Z4&czl**)a?*VfQT*xh9@cfhG^ubLCS7843XD@f?j1y!TH7uWh?xE zc#4^lC~Cp#XZV6}l{-Wsw&TJE$PJMBSzyh?IO{MOClQ|`?g9T3smT8m9wj5*<)qRC zG=@m0AYn4%RR~>ea=PsIJB026bOe907;?n~RJ-Mujz`cc!PL+AIj!!y5ubuOyLRb~yR(DTv}Wos#Hm)4FU>6NoU-A z4JLpXu~m%gtvJ=%I}sl$v@6xF*vhs=J4m2MSh}MtKo58HL@};EUFzJF)jbli4xDRG zkg7=uxq}m6TWA}Q$!8-goH1S`$dY|PS5{wPSAvt^{sThUfgSx?n5vLuU(kbOiu@NI zw@-eYyI7rcvMtC5M8T@jh%3E*(RrX}P=2t&z-r@DUu6H3u*T&ixU1Q=@T?}0u4ySv z_y`Ce+eDxHY`_}VT(GZ!p|ULs-5Ni-CT7h7kA3hl5DEPUAar-ksk~Y(+ZH1r&ZI3} zQxk6%S_>TyT@yS8l9TP(w%8Sg`{N)Rc`;Eh&@(xgR{OW0PvSsBwk;Mq*cP=AW7Y%- zlPb_NJ^eAo3e!PVuCp!H+8j~qVpUyry`VuZ<-f()hJrpsyR?Wc?sLM;5tAWDt4bEz z)8&fN5;3mjnpo^qQ{oZCmxzDF7)gVR*#$lxtd$LQxTGg9kdT#J5a5m+$%~-As*Q2^ z)l}D1*JxlDWGcorIf4C?q4p?ZB}4}1?N9|SDz4~yQG2QwQziIUG$ezyKvDo!MalGY zY{brlP~gUuP*IgC=p66_6bLr~z3?4AFnu%|RVN|36q%n`R(1ss%O#WHAxl2)bUU*i zP&s&;pGiz_#~NUH3@e>@K_1oHeB6Z>R6SX)hSCIsELS6g_D3Q9is{!wC?tD23Z@eu zwjriNsAx$-GV|ytdK=UQn}VuLo(I+d{6lfB{PHOLJ6jM_5H~?U^)i~+3^d4+WwONB z>NsqI_BPflF?|!_Iaa^5C{m(Z(LNcprz0d*ckno{X4O%AyaQ-j8HO?q_9`Y}WdMR% z6|{db#^dK3NNk+J6DkCC?NCy~Zb|T1WYB(B405SJ96z<7aluyA;J1T1lVDr|(C{M-OZO0;b$AJZ;)P5L_hfP-JRSV%>d+1(F2d`FgK+X5+25ASLS#+&w`FG;;Qy@i%cEf$F5jlAaDbJJQr7ggHP^F`Amz-f2gWd5X zKU)kbMmelyMDsINHLQVjFT?JXTjF-H&`DRj~9T?mEjPQ^F@FVxev4B9Ud|Cje5svy0p0V{=eJ_}>2IzoXJ z3jI;)A*GEUV!gha|6ay&!H#6m{y1E^AxX#2_hJHG21B+J(};C^62Q6qx0AL+>g{g9 z|$8$Jxvo6u%0J$lEQJ9Y1rjG6^fN!%XY#hF#)d`R{2NLNmwR)KO`r* z8aDYyBOElTU(E62zp8t%o(Y&~SmZwq2lZ9dN(WWekcUL}1;u^IB~Dq&rQmJl6hl!^ k2HOzk%r|7nkRgEo0XMNcESt%R>;M1&07*qoM6N<$f|2@zL;wH) diff --git a/public/favicon.png b/public/favicon.png index 59c68b9103b9ab967b5e481843112202c7dd894b..d594bc60d13efdfd105f4b9d7d5aed014c3c918a 100644 GIT binary patch literal 8202 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 literal 2470 zcmV;X30d}uP)DO=Wi51haAg2tc42g3a^wOUVE_OLL`g(JRCwC$U2AL3O)dlr?v91JUoXzh21lEI=eeFJ9l^QNxr0+?%sRn+{by|vjBz+88T$Zff^#R z5xI2Ig&CFy$~NGj(EUuCmcmALG(ssrTc1soLBm}I2c)rIKilm zpA zYH5vP{G=`y=+68OtZB%>lO@_IiFR-&9ynH7meiuc;ClT66A5xDRBtkWlove*(=3Fg4HBhr!Iv03@4!2CMOwAl%B zgSHcfDfIjmNq+3uQDcQnILn4+VCtr{aCaoBz>P&P?^0OI?SnXa%EUlwl1$Z%mpUu# zgt9~*Mda0#j18+#8IwzmGbSal>ZsGc2F-y0_ZV1IDe5VNt7ef)G9~FVs zfj8doPARx?Ky*3_(wAlnUhD^-!<%Ek>d$YL#uGsAE)xmH_-Tyn5S`5cLo>kYc=RPn zthdm7D5RHf_AN=1Z4&czl**)a?*VfQT*xh9@cfhG^ubLCS7843XD@f?j1y!TH7uWh?xE zc#4^lC~Cp#XZV6}l{-Wsw&TJE$PJMBSzyh?IO{MOClQ|`?g9T3smT8m9wj5*<)qRC zG=@m0AYn4%RR~>ea=PsIJB026bOe907;?n~RJ-Mujz`cc!PL+AIj!!y5ubuOyLRb~yR(DTv}Wos#Hm)4FU>6NoU-A z4JLpXu~m%gtvJ=%I}sl$v@6xF*vhs=J4m2MSh}MtKo58HL@};EUFzJF)jbli4xDRG zkg7=uxq}m6TWA}Q$!8-goH1S`$dY|PS5{wPSAvt^{sThUfgSx?n5vLuU(kbOiu@NI zw@-eYyI7rcvMtC5M8T@jh%3E*(RrX}P=2t&z-r@DUu6H3u*T&ixU1Q=@T?}0u4ySv z_y`Ce+eDxHY`_}VT(GZ!p|ULs-5Ni-CT7h7kA3hl5DEPUAar-ksk~Y(+ZH1r&ZI3} zQxk6%S_>TyT@yS8l9TP(w%8Sg`{N)Rc`;Eh&@(xgR{OW0PvSsBwk;Mq*cP=AW7Y%- zlPb_NJ^eAo3e!PVuCp!H+8j~qVpUyry`VuZ<-f()hJrpsyR?Wc?sLM;5tAWDt4bEz z)8&fN5;3mjnpo^qQ{oZCmxzDF7)gVR*#$lxtd$LQxTGg9kdT#J5a5m+$%~-As*Q2^ z)l}D1*JxlDWGcorIf4C?q4p?ZB}4}1?N9|SDz4~yQG2QwQziIUG$ezyKvDo!MalGY zY{brlP~gUuP*IgC=p66_6bLr~z3?4AFnu%|RVN|36q%n`R(1ss%O#WHAxl2)bUU*i zP&s&;pGiz_#~NUH3@e>@K_1oHeB6Z>R6SX)hSCIsELS6g_D3Q9is{!wC?tD23Z@eu zwjriNsAx$-GV|ytdK=UQn}VuLo(I+d{6lfB{PHOLJ6jM_5H~?U^)i~+3^d4+WwONB z>NsqI_BPflF?|!_Iaa^5C{m(Z(LNcprz0d*ckno{X4O%AyaQ-j8HO?q_9`Y}WdMR% z6|{db#^dK3NNk+J6DkCC?NCy~Zb|T1WYB(B405SJ96z<7aluyA;J1T1lVDr|(C{M-OZO0;b$AJZ;)P5L_hfP-JRSV%>d+1(F2d`FgK+X5+25ASLS#+&w`FG;;Qy@i%cEf$F5jlAaDbJJQr7ggHP^F`Amz-f2gWd5X zKU)kbMmelyMDsINHLQVjFT?JXTjF-H&`DRj~9T?mEjPQ^F@FVxev4B9Ud|Cje5svy0p0V{=eJ_}>2IzoXJ z3jI;)A*GEUV!gha|6ay&!H#6m{y1E^AxX#2_hJHG21B+J(};C^62Q6qx0AL+>g{g9 z|$8$Jxvo6u%0J$lEQJ9Y1rjG6^fN!%XY#hF#)d`R{2NLNmwR)KO`r* z8aDYyBOElTU(E62zp8t%o(Y&~SmZwq2lZ9dN(WWekcUL}1;u^IB~Dq&rQmJl6hl!^ k2HOzk%r|7nkRgEo0XMNcESt%R>;M1&07*qoM6N<$f|2@zL;wH) diff --git a/src/App.tsx b/src/App.tsx index 169e7e5..b4a564f 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -31,7 +31,6 @@ import React, {JSXElementConstructor, Key, ReactElement, useEffect, useState,} f import {useCookies} from "react-cookie"; import {Navigate, Route, Routes, useLocation,} from "react-router-dom"; import {Md5} from "ts-md5/dist/md5"; -import nfLogo from "assets/images/nutrifresh_one_icon_white.png"; import theme from "assets/theme"; import themeDark from "assets/theme-dark"; import MDBox from "components/MDBox"; @@ -45,6 +44,7 @@ import Analytics from "./layouts/dashboards/analytics"; import Sales from "./layouts/dashboards/sales"; import Settings from "./layouts/pages/account/settings"; import ProfileOverview from "./layouts/pages/profile/profile-overview"; +import Logo from "./qqq/images/logo.png"; import EntityCreate from "./qqq/pages/entity-create"; import EntityEdit from "./qqq/pages/entity-edit"; import EntityList from "./qqq/pages/entity-list"; @@ -436,7 +436,7 @@ export default function App() <> ;?RWoO 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 literal 2470 zcmV;X30d}uP)DO=Wi51haAg2tc42g3a^wOUVE_OLL`g(JRCwC$U2AL3O)dlr?v91JUoXzh21lEI=eeFJ9l^QNxr0+?%sRn+{by|vjBz+88T$Zff^#R z5xI2Ig&CFy$~NGj(EUuCmcmALG(ssrTc1soLBm}I2c)rIKilm zpA zYH5vP{G=`y=+68OtZB%>lO@_IiFR-&9ynH7meiuc;ClT66A5xDRBtkWlove*(=3Fg4HBhr!Iv03@4!2CMOwAl%B zgSHcfDfIjmNq+3uQDcQnILn4+VCtr{aCaoBz>P&P?^0OI?SnXa%EUlwl1$Z%mpUu# zgt9~*Mda0#j18+#8IwzmGbSal>ZsGc2F-y0_ZV1IDe5VNt7ef)G9~FVs zfj8doPARx?Ky*3_(wAlnUhD^-!<%Ek>d$YL#uGsAE)xmH_-Tyn5S`5cLo>kYc=RPn zthdm7D5RHf_AN=1Z4&czl**)a?*VfQT*xh9@cfhG^ubLCS7843XD@f?j1y!TH7uWh?xE zc#4^lC~Cp#XZV6}l{-Wsw&TJE$PJMBSzyh?IO{MOClQ|`?g9T3smT8m9wj5*<)qRC zG=@m0AYn4%RR~>ea=PsIJB026bOe907;?n~RJ-Mujz`cc!PL+AIj!!y5ubuOyLRb~yR(DTv}Wos#Hm)4FU>6NoU-A z4JLpXu~m%gtvJ=%I}sl$v@6xF*vhs=J4m2MSh}MtKo58HL@};EUFzJF)jbli4xDRG zkg7=uxq}m6TWA}Q$!8-goH1S`$dY|PS5{wPSAvt^{sThUfgSx?n5vLuU(kbOiu@NI zw@-eYyI7rcvMtC5M8T@jh%3E*(RrX}P=2t&z-r@DUu6H3u*T&ixU1Q=@T?}0u4ySv z_y`Ce+eDxHY`_}VT(GZ!p|ULs-5Ni-CT7h7kA3hl5DEPUAar-ksk~Y(+ZH1r&ZI3} zQxk6%S_>TyT@yS8l9TP(w%8Sg`{N)Rc`;Eh&@(xgR{OW0PvSsBwk;Mq*cP=AW7Y%- zlPb_NJ^eAo3e!PVuCp!H+8j~qVpUyry`VuZ<-f()hJrpsyR?Wc?sLM;5tAWDt4bEz z)8&fN5;3mjnpo^qQ{oZCmxzDF7)gVR*#$lxtd$LQxTGg9kdT#J5a5m+$%~-As*Q2^ z)l}D1*JxlDWGcorIf4C?q4p?ZB}4}1?N9|SDz4~yQG2QwQziIUG$ezyKvDo!MalGY zY{brlP~gUuP*IgC=p66_6bLr~z3?4AFnu%|RVN|36q%n`R(1ss%O#WHAxl2)bUU*i zP&s&;pGiz_#~NUH3@e>@K_1oHeB6Z>R6SX)hSCIsELS6g_D3Q9is{!wC?tD23Z@eu zwjriNsAx$-GV|ytdK=UQn}VuLo(I+d{6lfB{PHOLJ6jM_5H~?U^)i~+3^d4+WwONB z>NsqI_BPflF?|!_Iaa^5C{m(Z(LNcprz0d*ckno{X4O%AyaQ-j8HO?q_9`Y}WdMR% z6|{db#^dK3NNk+J6DkCC?NCy~Zb|T1WYB(B405SJ96z<7aluyA;J1T1lVDr|(C{M-OZO0;b$AJZ;)P5L_hfP-JRSV%>d+1(F2d`FgK+X5+25ASLS#+&w`FG;;Qy@i%cEf$F5jlAaDbJJQr7ggHP^F`Amz-f2gWd5X zKU)kbMmelyMDsINHLQVjFT?JXTjF-H&`DRj~9T?mEjPQ^F@FVxev4B9Ud|Cje5svy0p0V{=eJ_}>2IzoXJ z3jI;)A*GEUV!gha|6ay&!H#6m{y1E^AxX#2_hJHG21B+J(};C^62Q6qx0AL+>g{g9 z|$8$Jxvo6u%0J$lEQJ9Y1rjG6^fN!%XY#hF#)d`R{2NLNmwR)KO`r* z8aDYyBOElTU(E62zp8t%o(Y&~SmZwq2lZ9dN(WWekcUL}1;u^IB~Dq&rQmJl6hl!^ k2HOzk%r|7nkRgEo0XMNcESt%R>;M1&07*qoM6N<$f|2@zL;wH) diff --git a/src/index.tsx b/src/index.tsx index 55469ee..52adb68 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -21,7 +21,7 @@ import {Auth0Provider} from "@auth0/auth0-react"; import React from "react"; -import ReactDOM from "react-dom"; +import {render} from "react-dom"; import {BrowserRouter, useNavigate, useSearchParams} from "react-router-dom"; import App from "App"; import {MaterialUIControllerProvider} from "context"; @@ -62,7 +62,7 @@ function Auth0ProviderWithRedirectCallback({children, ...props}) } } -ReactDOM.render( +render( . + */ + +import Card from "@mui/material/Card"; +import Divider from "@mui/material/Divider"; +import Icon from "@mui/material/Icon"; +import {useMemo, ReactNode} from "react"; +import {Bar} from "react-chartjs-2"; +import MDBox from "components/MDBox"; +import MDTypography from "components/MDTypography"; +import configs from "qqq/components/Widgets/Configs/BarChartConfig"; + +// Declaring props types for ReportsBarChart +interface Props { + color?: "primary" | "secondary" | "info" | "success" | "warning" | "error" | "dark"; + title: string; + description?: string | ReactNode; + date: string; + chart: { + labels: string[]; + datasets: { + label: string; + data: number[]; + }; + }; + [key: string]: any; +} + +function ReportsBarChart({color, title, description, date, chart}: Props): JSX.Element +{ + const {data, options} = configs(chart.labels || [], chart.datasets || {}); + + return ( + + + {useMemo( + () => ( + + + + ), + [chart, color] + )} + + + {title} + + + {description} + + + + + schedule + + + {date} + + + + + + ); +} + +// Setting default values for the props of ReportsBarChart +ReportsBarChart.defaultProps = { + color: "dark", + description: "", +}; + +export default ReportsBarChart; diff --git a/src/qqq/components/Widgets/Configs/BarChartConfig.ts b/src/qqq/components/Widgets/Configs/BarChartConfig.ts new file mode 100644 index 0000000..6478675 --- /dev/null +++ b/src/qqq/components/Widgets/Configs/BarChartConfig.ts @@ -0,0 +1,104 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2022. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +function configs(labels: any, datasets: any) +{ + return { + data: { + labels, + datasets: [ + { + label: datasets.label, + tension: 0.4, + borderWidth: 0, + borderRadius: 4, + borderSkipped: false, + backgroundColor: "rgba(255, 255, 255, 0.8)", + data: datasets.data, + maxBarThickness: 6, + }, + ], + }, + options: { + responsive: true, + maintainAspectRatio: false, + plugins: { + legend: { + display: false, + }, + }, + interaction: { + intersect: false, + mode: "index", + }, + scales: { + y: { + grid: { + drawBorder: false, + display: true, + drawOnChartArea: true, + drawTicks: false, + borderDash: [5, 5], + color: "rgba(255, 255, 255, .2)", + }, + ticks: { + suggestedMin: 0, + suggestedMax: 500, + beginAtZero: true, + padding: 10, + font: { + size: 14, + weight: 300, + family: "Roboto", + style: "normal", + lineHeight: 2, + }, + color: "#fff", + }, + }, + x: { + grid: { + drawBorder: false, + display: true, + drawOnChartArea: true, + drawTicks: false, + borderDash: [5, 5], + color: "rgba(255, 255, 255, .2)", + }, + ticks: { + display: true, + color: "#f8f9fa", + padding: 10, + font: { + size: 14, + weight: 300, + family: "Roboto", + style: "normal", + lineHeight: 2, + }, + }, + }, + }, + }, + }; +} + +export default configs; diff --git a/src/qqq/components/Widgets/Configs/PieChartConfigs.ts b/src/qqq/components/Widgets/Configs/PieChartConfigs.ts new file mode 100644 index 0000000..eed60ec --- /dev/null +++ b/src/qqq/components/Widgets/Configs/PieChartConfigs.ts @@ -0,0 +1,100 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2022. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import colors from "assets/theme/base/colors"; + +const {gradients, dark} = colors; + +function configs(labels: any, datasets: any) +{ + const backgroundColors = []; + + if (datasets.backgroundColors) + { + datasets.backgroundColors.forEach((color: string) => + gradients[color] + ? backgroundColors.push(gradients[color].state) + : backgroundColors.push(dark.main) + ); + } + else + { + backgroundColors.push(dark.main); + } + + return { + data: { + labels, + datasets: [ + { + label: datasets.label, + weight: 9, + cutout: 0, + tension: 0.9, + pointRadius: 2, + borderWidth: 2, + backgroundColor: backgroundColors, + fill: false, + data: datasets.data, + }, + ], + }, + options: { + responsive: true, + maintainAspectRatio: false, + plugins: { + legend: { + display: false, + }, + }, + interaction: { + intersect: false, + mode: "index", + }, + scales: { + y: { + grid: { + drawBorder: false, + display: false, + drawOnChartArea: false, + drawTicks: false, + }, + ticks: { + display: false, + }, + }, + x: { + grid: { + drawBorder: false, + display: false, + drawOnChartArea: false, + drawTicks: false, + }, + ticks: { + display: false, + }, + }, + }, + }, + }; +} + +export default configs; diff --git a/src/qqq/components/Widgets/PieChart.tsx b/src/qqq/components/Widgets/PieChart.tsx new file mode 100644 index 0000000..f7d55cd --- /dev/null +++ b/src/qqq/components/Widgets/PieChart.tsx @@ -0,0 +1,110 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2022. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import Card from "@mui/material/Card"; +import Icon from "@mui/material/Icon"; +import {useMemo, ReactNode} from "react"; +import {Pie} from "react-chartjs-2"; +import MDBox from "components/MDBox"; +import MDTypography from "components/MDTypography"; +import configs from "qqq/components/Widgets/Configs/PieChartConfigs" + +// Declaring props types for PieChart +interface Props +{ + icon?: { + color?: "primary" | "secondary" | "info" | "success" | "warning" | "error" | "light" | "dark"; + component: ReactNode; + }; + title?: string; + description?: string | ReactNode; + height?: string | number; + chart: { + labels: string[]; + datasets: { + label: string; + backgroundColors: string[]; + data: number[]; + }; + }; + + [key: string]: any; +} + +function PieChart({icon, title, description, height, chart}: Props): JSX.Element +{ + const {data, options} = configs(chart.labels || [], chart.datasets || {}); + + const renderChart = ( + + {title || description ? ( + + {icon.component && ( + + {icon.component} + + )} + + {title && {title}} + + + {description} + + + + + ) : null} + {useMemo( + () => ( + + + + ), + [chart, height] + )} + + ); + + return title || description ? {renderChart} : renderChart; +} + +// Declaring default props for PieChart +PieChart.defaultProps = { + icon: {color: "info", component: ""}, + title: "", + description: "", + height: "19.125rem", +}; + +export default PieChart; diff --git a/src/qqq/components/Widgets/QuickSightChart.tsx b/src/qqq/components/Widgets/QuickSightChart.tsx new file mode 100644 index 0000000..845e356 --- /dev/null +++ b/src/qqq/components/Widgets/QuickSightChart.tsx @@ -0,0 +1,57 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2022. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import Card from "@mui/material/Card"; +import React from "react"; +import MDBox from "components/MDBox"; +import MDTypography from "components/MDTypography"; + +interface Props +{ + label: string; + url: string; +} + +interface IframeProps +{ + iframe: string; +} + +function Iframe({iframe}: IframeProps) +{ + return (
); +} + +function QuickSightChart({label, url}: Props): JSX.Element +{ + const iframe = `