mirror of
https://github.com/Kingsrook/qqq-frontend-material-dashboard.git
synced 2025-07-18 05:10:45 +00:00
Merge pull request #6 from Kingsrook/QQQ-32-frontend-fixes-spike
Qqq 32 frontend fixes spike
This commit is contained in:
@ -60,6 +60,7 @@
|
|||||||
"max-len": "off",
|
"max-len": "off",
|
||||||
"no-console": "off",
|
"no-console": "off",
|
||||||
"no-constant-condition": "off",
|
"no-constant-condition": "off",
|
||||||
|
"no-continue": "off",
|
||||||
"no-shadow": "off",
|
"no-shadow": "off",
|
||||||
"no-unused-vars": "off",
|
"no-unused-vars": "off",
|
||||||
"no-plusplus": "off",
|
"no-plusplus": "off",
|
||||||
|
2175
package-lock.json
generated
2175
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -13,7 +13,7 @@
|
|||||||
"@fullcalendar/interaction": "5.10.0",
|
"@fullcalendar/interaction": "5.10.0",
|
||||||
"@fullcalendar/react": "5.10.0",
|
"@fullcalendar/react": "5.10.0",
|
||||||
"@fullcalendar/timegrid": "5.10.0",
|
"@fullcalendar/timegrid": "5.10.0",
|
||||||
"@kingsrook/qqq-frontend-core": "1.0.6",
|
"@kingsrook/qqq-frontend-core": "1.0.9",
|
||||||
"@mui/icons-material": "5.4.1",
|
"@mui/icons-material": "5.4.1",
|
||||||
"@mui/material": "5.4.1",
|
"@mui/material": "5.4.1",
|
||||||
"@mui/styled-engine": "5.4.1",
|
"@mui/styled-engine": "5.4.1",
|
||||||
@ -31,6 +31,7 @@
|
|||||||
"@types/react-dom": "17.0.11",
|
"@types/react-dom": "17.0.11",
|
||||||
"chart.js": "3.4.1",
|
"chart.js": "3.4.1",
|
||||||
"chroma-js": "2.4.2",
|
"chroma-js": "2.4.2",
|
||||||
|
"datejs": "1.0.0-rc3",
|
||||||
"dropzone": "5.9.2",
|
"dropzone": "5.9.2",
|
||||||
"flatpickr": "4.6.9",
|
"flatpickr": "4.6.9",
|
||||||
"form-data": "4.0.0",
|
"form-data": "4.0.0",
|
||||||
|
258
src/App.tsx
258
src/App.tsx
@ -1,14 +1,10 @@
|
|||||||
import React, {
|
import React, {
|
||||||
useState,
|
JSXElementConstructor, Key, ReactElement, useEffect, useState,
|
||||||
useEffect,
|
|
||||||
JSXElementConstructor,
|
|
||||||
Key,
|
|
||||||
ReactElement,
|
|
||||||
} from "react";
|
} from "react";
|
||||||
|
|
||||||
// react-router components
|
// react-router components
|
||||||
import {
|
import {
|
||||||
Routes, Route, Navigate, useLocation,
|
Navigate, Route, Routes, useLocation,
|
||||||
} from "react-router-dom";
|
} from "react-router-dom";
|
||||||
|
|
||||||
import {useAuth0} from "@auth0/auth0-react";
|
import {useAuth0} from "@auth0/auth0-react";
|
||||||
@ -33,26 +29,28 @@ import theme from "assets/theme";
|
|||||||
import themeDark from "assets/theme-dark";
|
import themeDark from "assets/theme-dark";
|
||||||
|
|
||||||
// Material Dashboard 2 PRO React TS contexts
|
// Material Dashboard 2 PRO React TS contexts
|
||||||
import {useMaterialUIController, setMiniSidenav, setOpenConfigurator} from "context";
|
import {setMiniSidenav, setOpenConfigurator, useMaterialUIController} from "context";
|
||||||
|
|
||||||
// Images
|
// Images
|
||||||
import nfLogo from "assets/images/nutrifresh_one_icon_white.png";
|
import nfLogo from "assets/images/nutrifresh_one_icon_white.png";
|
||||||
import {Md5} from "ts-md5/dist/md5";
|
import {Md5} from "ts-md5/dist/md5";
|
||||||
import AuthenticationButton from "qqq/components/buttons/AuthenticationButton";
|
|
||||||
import {useCookies} from "react-cookie";
|
import {useCookies} from "react-cookie";
|
||||||
import {QInstance} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QInstance";
|
|
||||||
import EntityCreate from "./qqq/pages/entity-create";
|
import EntityCreate from "./qqq/pages/entity-create";
|
||||||
import EntityList from "./qqq/pages/entity-list";
|
import EntityList from "./qqq/pages/entity-list";
|
||||||
import EntityView from "./qqq/pages/entity-view";
|
import EntityView from "./qqq/pages/entity-view";
|
||||||
import EntityEdit from "./qqq/pages/entity-edit";
|
import EntityEdit from "./qqq/pages/entity-edit";
|
||||||
import ProcessRun from "./qqq/pages/process-run";
|
import ProcessRun from "./qqq/pages/process-run";
|
||||||
|
import AppHome from "qqq/pages/app-home";
|
||||||
import MDAvatar from "./components/MDAvatar";
|
import MDAvatar from "./components/MDAvatar";
|
||||||
import ProfileOverview from "./layouts/pages/profile/profile-overview";
|
import ProfileOverview from "./layouts/pages/profile/profile-overview";
|
||||||
import Settings from "./layouts/pages/account/settings";
|
import Settings from "./layouts/pages/account/settings";
|
||||||
import SignInBasic from "./layouts/authentication/sign-in/basic";
|
|
||||||
import Analytics from "./layouts/dashboards/analytics";
|
import Analytics from "./layouts/dashboards/analytics";
|
||||||
import Sales from "./layouts/dashboards/sales";
|
import Sales from "./layouts/dashboards/sales";
|
||||||
import QClient from "./qqq/utils/QClient";
|
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 //
|
// define the parts of the nav that are static - before the qqq tables etc get dynamic added //
|
||||||
@ -82,21 +80,21 @@ function getStaticRoutes()
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
{type: "divider", key: "divider-1"},
|
{type: "divider", key: "divider-1"},
|
||||||
{type: "title", title: "Tables", key: "title-docs"},
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
const SESSION_ID_COOKIE_NAME = "sessionId";
|
export const SESSION_ID_COOKIE_NAME = "sessionId";
|
||||||
LicenseInfo.setLicenseKey(process.env.REACT_APP_MATERIAL_UI_LICENSE_KEY);
|
LicenseInfo.setLicenseKey(process.env.REACT_APP_MATERIAL_UI_LICENSE_KEY);
|
||||||
|
|
||||||
export default function App()
|
export default function App()
|
||||||
{
|
{
|
||||||
const [, setCookie] = useCookies([SESSION_ID_COOKIE_NAME]);
|
const [, setCookie] = useCookies([SESSION_ID_COOKIE_NAME]);
|
||||||
const {
|
const {
|
||||||
user, getAccessTokenSilently, getIdTokenClaims, logout,
|
user, getAccessTokenSilently, getIdTokenClaims, logout, loginWithRedirect,
|
||||||
} = useAuth0();
|
} = useAuth0();
|
||||||
const [loadingToken, setLoadingToken] = useState(false);
|
const [loadingToken, setLoadingToken] = useState(false);
|
||||||
const [isFullyAuthenticated, setIsFullyAuthenticated] = useState(false);
|
const [isFullyAuthenticated, setIsFullyAuthenticated] = useState(false);
|
||||||
|
const [profileRoutes, setProfileRoutes] = useState({});
|
||||||
|
|
||||||
useEffect(() =>
|
useEffect(() =>
|
||||||
{
|
{
|
||||||
@ -110,7 +108,7 @@ export default function App()
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
console.log("Loading token...");
|
console.log("Loading token...");
|
||||||
const accessToken = await getAccessTokenSilently();
|
await getAccessTokenSilently();
|
||||||
const idToken = await getIdTokenClaims();
|
const idToken = await getIdTokenClaims();
|
||||||
setCookie(SESSION_ID_COOKIE_NAME, idToken.__raw, {path: "/"});
|
setCookie(SESSION_ID_COOKIE_NAME, idToken.__raw, {path: "/"});
|
||||||
setIsFullyAuthenticated(true);
|
setIsFullyAuthenticated(true);
|
||||||
@ -131,15 +129,14 @@ export default function App()
|
|||||||
layout,
|
layout,
|
||||||
openConfigurator,
|
openConfigurator,
|
||||||
sidenavColor,
|
sidenavColor,
|
||||||
transparentSidenav,
|
|
||||||
whiteSidenav,
|
|
||||||
darkMode,
|
darkMode,
|
||||||
} = controller;
|
} = controller;
|
||||||
const [onMouseEnter, setOnMouseEnter] = useState(false);
|
const [onMouseEnter, setOnMouseEnter] = useState(false);
|
||||||
const {pathname} = useLocation();
|
const {pathname} = useLocation();
|
||||||
|
|
||||||
const [needToLoadRoutes, setNeedToLoadRoutes] = useState(true);
|
const [needToLoadRoutes, setNeedToLoadRoutes] = useState(true);
|
||||||
const [routes, setRoutes] = useState(getStaticRoutes());
|
const [sideNavRoutes, setSideNavRoutes] = useState(getStaticRoutes());
|
||||||
|
const [appRoutes, setAppRoutes] = useState(null as any);
|
||||||
|
|
||||||
////////////////////////////////////////////
|
////////////////////////////////////////////
|
||||||
// load qqq meta data to make more routes //
|
// load qqq meta data to make more routes //
|
||||||
@ -154,38 +151,147 @@ export default function App()
|
|||||||
|
|
||||||
(async () =>
|
(async () =>
|
||||||
{
|
{
|
||||||
try
|
function addAppToSideNavList(app: QAppTreeNode, appList: any[], parentPath: string, depth: number)
|
||||||
{
|
{
|
||||||
console.log("ok now loading qqq things");
|
const path = `${parentPath}/${app.name}`;
|
||||||
const metaData = await QClient.loadMetaData();
|
if (app.type !== QAppNodeType.APP)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// get the keys sorted
|
if (depth > 2)
|
||||||
const keys = [...metaData.tables.keys()].sort((a, b): number =>
|
|
||||||
{
|
{
|
||||||
const labelA = metaData.tables.get(a).label;
|
console.warn("App depth is greater than 2 - not including app in side nav...");
|
||||||
const labelB = metaData.tables.get(b).label;
|
return;
|
||||||
return (labelA.localeCompare(labelB));
|
}
|
||||||
|
|
||||||
|
const childList: any[] = [];
|
||||||
|
app.children.forEach((child: QAppTreeNode) =>
|
||||||
|
{
|
||||||
|
addAppToSideNavList(child, childList, path, depth + 1);
|
||||||
});
|
});
|
||||||
const tableList = [] as any[];
|
|
||||||
keys.forEach((key) =>
|
if (childList.length === 0)
|
||||||
{
|
{
|
||||||
const table = metaData.tables.get(key);
|
if (depth === 0)
|
||||||
if (!table.isHidden)
|
|
||||||
{
|
{
|
||||||
tableList.push({
|
/////////////////////////////////////////////////////
|
||||||
name: `${table.label}`,
|
// at level 0, the entry must always be a collapse //
|
||||||
key: table.name,
|
/////////////////////////////////////////////////////
|
||||||
route: `/${table.name}`,
|
appList.push({
|
||||||
component: <EntityList table={table} />,
|
type: "collapse",
|
||||||
|
name: app.label,
|
||||||
|
key: app.name,
|
||||||
|
route: path,
|
||||||
|
icon: <Icon fontSize="medium">{app.iconName}</Icon>,
|
||||||
|
noCollapse: true,
|
||||||
|
component: <AppHome />,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
appList.push({
|
||||||
|
name: app.label,
|
||||||
|
key: app.name,
|
||||||
|
route: path,
|
||||||
|
icon: <Icon fontSize="medium">{app.iconName}</Icon>,
|
||||||
|
component: <AppHome />,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
appList.push({
|
||||||
|
type: "collapse",
|
||||||
|
name: app.label,
|
||||||
|
key: app.name,
|
||||||
|
dropdown: true,
|
||||||
|
icon: <Icon fontSize="medium">{app.iconName}</Icon>,
|
||||||
|
collapse: childList,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function addAppToAppRoutesList(metaData: QInstance, app: QAppTreeNode, routeList: any[], parentPath: string, depth: number)
|
||||||
|
{
|
||||||
|
const path = `${parentPath}/${app.name}`;
|
||||||
|
if (app.type === QAppNodeType.APP)
|
||||||
|
{
|
||||||
|
app.children.forEach((child: QAppTreeNode) =>
|
||||||
|
{
|
||||||
|
addAppToAppRoutesList(metaData, child, routeList, path, depth + 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
let profileRoute = {};
|
routeList.push({
|
||||||
|
name: `${app.label}`,
|
||||||
|
key: app.name,
|
||||||
|
route: path,
|
||||||
|
component: <AppHome app={app} />,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else if (app.type === QAppNodeType.TABLE)
|
||||||
|
{
|
||||||
|
const table = metaData.tables.get(app.name);
|
||||||
|
routeList.push({
|
||||||
|
name: `${app.label}`,
|
||||||
|
key: app.name,
|
||||||
|
route: path,
|
||||||
|
component: <EntityList table={table} />,
|
||||||
|
});
|
||||||
|
|
||||||
|
routeList.push({
|
||||||
|
name: `${app.label} Create`,
|
||||||
|
key: `${app.name}.create`,
|
||||||
|
route: `${path}/create`,
|
||||||
|
component: <EntityCreate table={table} />,
|
||||||
|
});
|
||||||
|
|
||||||
|
routeList.push({
|
||||||
|
name: `${app.label} View`,
|
||||||
|
key: `${app.name}.view`,
|
||||||
|
route: `${path}/:id`,
|
||||||
|
component: <EntityView table={table} />,
|
||||||
|
});
|
||||||
|
|
||||||
|
routeList.push({
|
||||||
|
name: `${app.label}`,
|
||||||
|
key: `${app.name}.edit`,
|
||||||
|
route: `${path}/:id/edit`,
|
||||||
|
component: <EntityEdit table={table} />,
|
||||||
|
});
|
||||||
|
|
||||||
|
const processesForTable = QProcessUtils.getProcessesForTable(metaData, table.name, true);
|
||||||
|
processesForTable.forEach((process) =>
|
||||||
|
{
|
||||||
|
routeList.push({
|
||||||
|
name: process.label,
|
||||||
|
key: process.name,
|
||||||
|
route: `${path}/${process.name}`,
|
||||||
|
component: <ProcessRun process={process} />,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else if (app.type === QAppNodeType.PROCESS)
|
||||||
|
{
|
||||||
|
const process = metaData.processes.get(app.name);
|
||||||
|
routeList.push({
|
||||||
|
name: `${app.label}`,
|
||||||
|
key: app.name,
|
||||||
|
route: path,
|
||||||
|
component: <ProcessRun process={process} />,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
const metaData = await QClient.getInstance().loadMetaData();
|
||||||
|
|
||||||
|
let profileRoutes = {};
|
||||||
const gravatarBase = "http://www.gravatar.com/avatar/";
|
const gravatarBase = "http://www.gravatar.com/avatar/";
|
||||||
const hash = Md5.hashStr(user.email);
|
const hash = Md5.hashStr(user.email);
|
||||||
const profilePicture = `${gravatarBase}${hash}`;
|
const profilePicture = `${gravatarBase}${hash}`;
|
||||||
profileRoute = {
|
profileRoutes = {
|
||||||
type: "collapse",
|
type: "collapse",
|
||||||
name: user.name,
|
name: user.name,
|
||||||
key: user.name,
|
key: user.name,
|
||||||
@ -203,28 +309,29 @@ export default function App()
|
|||||||
route: "/pages/account/settings",
|
route: "/pages/account/settings",
|
||||||
component: <Settings />,
|
component: <Settings />,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: "Logout",
|
|
||||||
key: "logout",
|
|
||||||
route: "/authentication/sign-in/basic",
|
|
||||||
component: <SignInBasic />,
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
setProfileRoutes(profileRoutes);
|
||||||
|
|
||||||
const tables = {
|
const sideNavAppList = [] as any[];
|
||||||
type: "collapse",
|
const appRoutesList = [] as any[];
|
||||||
name: "Tables",
|
for (let i = 0; i < metaData.appTree.length; i++)
|
||||||
key: "tables",
|
{
|
||||||
icon: <Icon fontSize="medium">dashboard</Icon>,
|
const app = metaData.appTree[i];
|
||||||
collapse: tableList,
|
addAppToSideNavList(app, sideNavAppList, "", 0);
|
||||||
};
|
addAppToAppRoutesList(metaData, app, appRoutesList, "", 0);
|
||||||
|
}
|
||||||
|
|
||||||
const newDynamicRoutes = getStaticRoutes();
|
const newSideNavRoutes = getStaticRoutes();
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
newDynamicRoutes.unshift(profileRoute);
|
newSideNavRoutes.unshift(profileRoutes);
|
||||||
newDynamicRoutes.push(tables);
|
for (let i = 0; i < sideNavAppList.length; i++)
|
||||||
setRoutes(newDynamicRoutes);
|
{
|
||||||
|
newSideNavRoutes.push(sideNavAppList[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
setSideNavRoutes(newSideNavRoutes);
|
||||||
|
setAppRoutes(appRoutesList);
|
||||||
}
|
}
|
||||||
catch (e)
|
catch (e)
|
||||||
{
|
{
|
||||||
@ -272,6 +379,9 @@ export default function App()
|
|||||||
document.scrollingElement.scrollTop = 0;
|
document.scrollingElement.scrollTop = 0;
|
||||||
}, [pathname]);
|
}, [pathname]);
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// convert an object that works for the Sidenav into one that works for the react-router //
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
const getRoutes = (allRoutes: any[]): any => allRoutes.map(
|
const getRoutes = (allRoutes: any[]): any => allRoutes.map(
|
||||||
(route: {
|
(route: {
|
||||||
collapse: any;
|
collapse: any;
|
||||||
@ -294,27 +404,6 @@ export default function App()
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
const authButton = (
|
|
||||||
<MDBox
|
|
||||||
display="flex"
|
|
||||||
justifyContent="center"
|
|
||||||
alignItems="center"
|
|
||||||
width="3.25rem"
|
|
||||||
height="3.25rem"
|
|
||||||
bgColor="white"
|
|
||||||
shadow="sm"
|
|
||||||
borderRadius="50%"
|
|
||||||
position="fixed"
|
|
||||||
right="2rem"
|
|
||||||
bottom="2rem"
|
|
||||||
zIndex={99}
|
|
||||||
color="dark"
|
|
||||||
sx={{cursor: "pointer"}}
|
|
||||||
>
|
|
||||||
<AuthenticationButton />
|
|
||||||
</MDBox>
|
|
||||||
);
|
|
||||||
|
|
||||||
const configsButton = (
|
const configsButton = (
|
||||||
<MDBox
|
<MDBox
|
||||||
display="flex"
|
display="flex"
|
||||||
@ -339,13 +428,8 @@ export default function App()
|
|||||||
</MDBox>
|
</MDBox>
|
||||||
);
|
);
|
||||||
|
|
||||||
const entityListElement = <EntityList />;
|
|
||||||
const entityCreateElement = <EntityCreate />;
|
|
||||||
const entityViewElement = <EntityView />;
|
|
||||||
const entityEditElement = <EntityEdit />;
|
|
||||||
const processRunElement = <ProcessRun />;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
appRoutes && (
|
||||||
<ThemeProvider theme={darkMode ? themeDark : theme}>
|
<ThemeProvider theme={darkMode ? themeDark : theme}>
|
||||||
<CssBaseline />
|
<CssBaseline />
|
||||||
{layout === "dashboard" && (
|
{layout === "dashboard" && (
|
||||||
@ -354,24 +438,20 @@ export default function App()
|
|||||||
color={sidenavColor}
|
color={sidenavColor}
|
||||||
brand={nfLogo}
|
brand={nfLogo}
|
||||||
brandName="Nutrifresh One"
|
brandName="Nutrifresh One"
|
||||||
routes={routes}
|
routes={sideNavRoutes}
|
||||||
onMouseEnter={handleOnMouseEnter}
|
onMouseEnter={handleOnMouseEnter}
|
||||||
onMouseLeave={handleOnMouseLeave}
|
onMouseLeave={handleOnMouseLeave}
|
||||||
/>
|
/>
|
||||||
<Configurator />
|
<Configurator />
|
||||||
{configsButton}
|
|
||||||
{authButton}
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="*" element={<Navigate to="/dashboards/analytics" />} />
|
<Route path="*" element={<Navigate to="/dashboards/analytics" />} />
|
||||||
<Route path="/:tableName" element={entityListElement} key="entity-list" />
|
{appRoutes && getRoutes(appRoutes)}
|
||||||
<Route path="/:tableName/create" element={entityCreateElement} key="entity-create" />
|
{getRoutes(getStaticRoutes())}
|
||||||
<Route path="/processes/:processName" element={processRunElement} key="process-run" />
|
{profileRoutes && getRoutes([profileRoutes])}
|
||||||
<Route path="/:tableName/:id" element={entityViewElement} key="entity-view" />
|
|
||||||
<Route path="/:tableName/:id/edit" element={entityEditElement} key="entity-edit" />
|
|
||||||
{getRoutes(routes)}
|
|
||||||
</Routes>
|
</Routes>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
28
src/HandleAuthorizationError.tsx
Normal file
28
src/HandleAuthorizationError.tsx
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import React, {useEffect} from "react";
|
||||||
|
import {SESSION_ID_COOKIE_NAME} from "App";
|
||||||
|
import {useCookies} from "react-cookie";
|
||||||
|
|
||||||
|
interface Props
|
||||||
|
{
|
||||||
|
errorMessage?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function HandleAuthorizationError({errorMessage}: Props)
|
||||||
|
{
|
||||||
|
const [, , removeCookie] = useCookies([SESSION_ID_COOKIE_NAME]);
|
||||||
|
|
||||||
|
useEffect(() =>
|
||||||
|
{
|
||||||
|
removeCookie(SESSION_ID_COOKIE_NAME, {path: "/"});
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>{errorMessage}</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
HandleAuthorizationError.defaultProps = {
|
||||||
|
errorMessage: "User authorization error.",
|
||||||
|
};
|
||||||
|
|
||||||
|
export default HandleAuthorizationError;
|
@ -44,6 +44,7 @@ import {
|
|||||||
setTransparentSidenav,
|
setTransparentSidenav,
|
||||||
setWhiteSidenav,
|
setWhiteSidenav,
|
||||||
} from "context";
|
} from "context";
|
||||||
|
import AuthenticationButton from "qqq/components/Buttons/AuthenticationButton";
|
||||||
|
|
||||||
// Declaring props types for Sidenav
|
// Declaring props types for Sidenav
|
||||||
interface Props {
|
interface Props {
|
||||||
@ -55,6 +56,10 @@ interface Props {
|
|||||||
| ReactNode
|
| ReactNode
|
||||||
| string
|
| string
|
||||||
| {
|
| {
|
||||||
|
[key: string]:
|
||||||
|
| ReactNode
|
||||||
|
| string
|
||||||
|
| {
|
||||||
[key: string]:
|
[key: string]:
|
||||||
| ReactNode
|
| ReactNode
|
||||||
| string
|
| string
|
||||||
@ -63,6 +68,7 @@ interface Props {
|
|||||||
}[];
|
}[];
|
||||||
}[];
|
}[];
|
||||||
}[];
|
}[];
|
||||||
|
}[];
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -314,6 +320,7 @@ function Sidenav({ color, brand, brandName, routes, ...rest }: Props): JSX.Eleme
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<List>{renderRoutes}</List>
|
<List>{renderRoutes}</List>
|
||||||
|
<AuthenticationButton />
|
||||||
</SidenavRoot>
|
</SidenavRoot>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import ReactDOM from "react-dom";
|
import ReactDOM from "react-dom";
|
||||||
import {BrowserRouter, useNavigate} from "react-router-dom";
|
import {BrowserRouter, useNavigate, useSearchParams} from "react-router-dom";
|
||||||
import {Auth0Provider} from "@auth0/auth0-react";
|
import {Auth0Provider} from "@auth0/auth0-react";
|
||||||
import App from "App";
|
import App from "App";
|
||||||
|
|
||||||
@ -8,39 +8,39 @@ import {MaterialUIControllerProvider} from "context";
|
|||||||
import "./qqq/styles/qqq-override-styles.css";
|
import "./qqq/styles/qqq-override-styles.css";
|
||||||
import ProtectedRoute from "qqq/auth0/protected-route";
|
import ProtectedRoute from "qqq/auth0/protected-route";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
import HandleAuthorizationError from "HandleAuthorizationError";
|
||||||
|
|
||||||
// Auth0 params from env
|
// Auth0 params from env
|
||||||
const domain = process.env.REACT_APP_AUTH0_DOMAIN;
|
const domain = process.env.REACT_APP_AUTH0_DOMAIN;
|
||||||
const clientId = process.env.REACT_APP_AUTH0_CLIENT_ID;
|
const clientId = process.env.REACT_APP_AUTH0_CLIENT_ID;
|
||||||
|
|
||||||
/*
|
|
||||||
ReactDOM.render(
|
|
||||||
<BrowserRouter>
|
|
||||||
<MaterialUIControllerProvider>
|
|
||||||
<ProtectedRoute component={App} />
|
|
||||||
</MaterialUIControllerProvider>
|
|
||||||
</BrowserRouter>,
|
|
||||||
document.getElementById("root"),
|
|
||||||
);
|
|
||||||
*/
|
|
||||||
|
|
||||||
console.log("what");
|
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
function Auth0ProviderWithRedirectCallback({children, ...props})
|
function Auth0ProviderWithRedirectCallback({children, ...props})
|
||||||
{
|
{
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const [searchParams] = useSearchParams();
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const onRedirectCallback = (appState) =>
|
const onRedirectCallback = (appState) =>
|
||||||
{
|
{
|
||||||
navigate((appState && appState.returnTo) || window.location.pathname);
|
navigate((appState && appState.returnTo) || window.location.pathname);
|
||||||
};
|
};
|
||||||
|
if (searchParams.get("error"))
|
||||||
|
{
|
||||||
|
return (
|
||||||
|
// @ts-ignore
|
||||||
|
<HandleAuthorizationError errorMessage={searchParams.get("error_description")} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
return (
|
return (
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
<Auth0Provider onRedirectCallback={onRedirectCallback} {...props}>
|
<Auth0Provider onRedirectCallback={onRedirectCallback} {...props}>
|
||||||
{children}
|
{children}
|
||||||
</Auth0Provider>
|
</Auth0Provider>
|
||||||
);
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
@ -57,5 +57,3 @@ ReactDOM.render(
|
|||||||
</BrowserRouter>,
|
</BrowserRouter>,
|
||||||
document.getElementById("root"),
|
document.getElementById("root"),
|
||||||
);
|
);
|
||||||
|
|
||||||
export * from "components/MDButton";
|
|
||||||
|
@ -1,3 +1,24 @@
|
|||||||
|
/*
|
||||||
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
|
* Copyright (C) 2021-2022. Kingsrook, LLC
|
||||||
|
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||||
|
* contact@kingsrook.com
|
||||||
|
* https://github.com/Kingsrook/
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
interface CodeSnippetProps
|
interface CodeSnippetProps
|
||||||
|
@ -1,3 +1,24 @@
|
|||||||
|
/*
|
||||||
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
|
* Copyright (C) 2021-2022. Kingsrook, LLC
|
||||||
|
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||||
|
* contact@kingsrook.com
|
||||||
|
* https://github.com/Kingsrook/
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
function Loader() : JSX.Element
|
function Loader() : JSX.Element
|
||||||
|
@ -1,3 +1,24 @@
|
|||||||
|
/*
|
||||||
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
|
* Copyright (C) 2021-2022. Kingsrook, LLC
|
||||||
|
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||||
|
* contact@kingsrook.com
|
||||||
|
* https://github.com/Kingsrook/
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
import {useAuth0} from "@auth0/auth0-react";
|
import {useAuth0} from "@auth0/auth0-react";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import CodeSnippet from "./code-snippet";
|
import CodeSnippet from "./code-snippet";
|
||||||
|
@ -1,3 +1,24 @@
|
|||||||
|
/*
|
||||||
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
|
* Copyright (C) 2021-2022. Kingsrook, LLC
|
||||||
|
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||||
|
* contact@kingsrook.com
|
||||||
|
* https://github.com/Kingsrook/
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
import {withAuthenticationRequired} from "@auth0/auth0-react";
|
import {withAuthenticationRequired} from "@auth0/auth0-react";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import Loader from "./loader";
|
import Loader from "./loader";
|
||||||
|
@ -1,16 +1,22 @@
|
|||||||
/**
|
/*
|
||||||
=========================================================
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
* Material Dashboard 2 PRO React TS - v1.0.0
|
* Copyright (C) 2021-2022. Kingsrook, LLC
|
||||||
=========================================================
|
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||||
|
* contact@kingsrook.com
|
||||||
* Product Page: https://www.creative-tim.com/product/material-dashboard-2-pro-react-ts
|
* https://github.com/Kingsrook/
|
||||||
* Copyright 2022 Creative Tim (https://www.creative-tim.com)
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
Coded by www.creative-tim.com
|
* 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.
|
||||||
|
*
|
||||||
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {useState, useEffect, ReactNode} from "react";
|
import {useState, useEffect, ReactNode} from "react";
|
||||||
@ -22,7 +28,6 @@ import breakpoints from "assets/theme/base/breakpoints";
|
|||||||
import DashboardLayout from "examples/LayoutContainers/DashboardLayout";
|
import DashboardLayout from "examples/LayoutContainers/DashboardLayout";
|
||||||
import Navbar from "qqq/components/Navbar";
|
import Navbar from "qqq/components/Navbar";
|
||||||
import Footer from "qqq/components/Footer";
|
import Footer from "qqq/components/Footer";
|
||||||
import DashboardNavbar from "examples/Navbars/DashboardNavbar";
|
|
||||||
import MDBox from "../../../components/MDBox";
|
import MDBox from "../../../components/MDBox";
|
||||||
|
|
||||||
// Declaring props types for BaseLayout
|
// Declaring props types for BaseLayout
|
||||||
@ -60,7 +65,7 @@ function BaseLayout({stickyNavbar, children}: Props): JSX.Element
|
|||||||
return (
|
return (
|
||||||
<DashboardLayout>
|
<DashboardLayout>
|
||||||
<Navbar />
|
<Navbar />
|
||||||
<MDBox mt={stickyNavbar ? 3 : 10}>{children}</MDBox>
|
<MDBox mt={stickyNavbar ? 3 : 6}>{children}</MDBox>
|
||||||
<Footer />
|
<Footer />
|
||||||
</DashboardLayout>
|
</DashboardLayout>
|
||||||
);
|
);
|
||||||
|
38
src/qqq/components/Buttons/AuthenticationButton/index.tsx
Normal file
38
src/qqq/components/Buttons/AuthenticationButton/index.tsx
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
/*
|
||||||
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
|
* Copyright (C) 2021-2022. Kingsrook, LLC
|
||||||
|
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||||
|
* contact@kingsrook.com
|
||||||
|
* https://github.com/Kingsrook/
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {useAuth0} from "@auth0/auth0-react";
|
||||||
|
import React from "react";
|
||||||
|
import {Button} from "@mui/material";
|
||||||
|
|
||||||
|
function AuthenticationButton()
|
||||||
|
{
|
||||||
|
const {loginWithRedirect, logout, isAuthenticated} = useAuth0();
|
||||||
|
|
||||||
|
if (isAuthenticated)
|
||||||
|
{
|
||||||
|
return <Button onClick={() => logout({returnTo: window.location.origin})}>Log Out</Button>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <Button onClick={() => loginWithRedirect()}>Log In</Button>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AuthenticationButton;
|
@ -1,57 +0,0 @@
|
|||||||
/**
|
|
||||||
=========================================================
|
|
||||||
* Material Dashboard 2 PRO React TS - v1.0.0
|
|
||||||
=========================================================
|
|
||||||
|
|
||||||
* Product Page: https://www.creative-tim.com/product/material-dashboard-2-pro-react-ts
|
|
||||||
* Copyright 2022 Creative Tim (https://www.creative-tim.com)
|
|
||||||
|
|
||||||
Coded by www.creative-tim.com
|
|
||||||
|
|
||||||
=========================================================
|
|
||||||
|
|
||||||
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Material Dashboard 2 PRO React TS components
|
|
||||||
import MDBox from "components/MDBox";
|
|
||||||
import MDTypography from "components/MDTypography";
|
|
||||||
import MDAvatar from "components/MDAvatar";
|
|
||||||
|
|
||||||
// Declaring props types for CustomerCell
|
|
||||||
interface Props {
|
|
||||||
image?: string;
|
|
||||||
name: string;
|
|
||||||
color?:
|
|
||||||
| "transparent"
|
|
||||||
| "primary"
|
|
||||||
| "secondary"
|
|
||||||
| "info"
|
|
||||||
| "success"
|
|
||||||
| "warning"
|
|
||||||
| "error"
|
|
||||||
| "light"
|
|
||||||
| "dark";
|
|
||||||
}
|
|
||||||
|
|
||||||
function CustomerCell({image, name, color}: Props): JSX.Element
|
|
||||||
{
|
|
||||||
return (
|
|
||||||
<MDBox display="flex" alignItems="center">
|
|
||||||
<MDBox mr={1}>
|
|
||||||
<MDAvatar bgColor={color} src={image} alt={name} size="xs" />
|
|
||||||
</MDBox>
|
|
||||||
<MDTypography variant="caption" fontWeight="medium" color="text" sx={{lineHeight: 0}}>
|
|
||||||
{name}
|
|
||||||
</MDTypography>
|
|
||||||
</MDBox>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Declaring default props for CustomerCell
|
|
||||||
CustomerCell.defaultProps = {
|
|
||||||
image: "",
|
|
||||||
color: "dark",
|
|
||||||
};
|
|
||||||
|
|
||||||
export default CustomerCell;
|
|
@ -1,45 +0,0 @@
|
|||||||
/**
|
|
||||||
=========================================================
|
|
||||||
* Material Dashboard 2 PRO React TS - v1.0.0
|
|
||||||
=========================================================
|
|
||||||
|
|
||||||
* Product Page: https://www.creative-tim.com/product/material-dashboard-2-pro-react-ts
|
|
||||||
* Copyright 2022 Creative Tim (https://www.creative-tim.com)
|
|
||||||
|
|
||||||
Coded by www.creative-tim.com
|
|
||||||
|
|
||||||
=========================================================
|
|
||||||
|
|
||||||
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Material Dashboard 2 PRO React TS components
|
|
||||||
import MDTypography from "components/MDTypography";
|
|
||||||
|
|
||||||
// Declaring props types for DefaultCell
|
|
||||||
interface Props {
|
|
||||||
value: string;
|
|
||||||
suffix?: string | boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
function DefaultCell({value, suffix}: Props): JSX.Element
|
|
||||||
{
|
|
||||||
return (
|
|
||||||
<MDTypography variant="caption" fontWeight="medium" color="text">
|
|
||||||
{value}
|
|
||||||
{suffix && (
|
|
||||||
<MDTypography variant="caption" fontWeight="medium" color="secondary">
|
|
||||||
|
|
||||||
{suffix}
|
|
||||||
</MDTypography>
|
|
||||||
)}
|
|
||||||
</MDTypography>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Declaring default props for DefaultCell
|
|
||||||
DefaultCell.defaultProps = {
|
|
||||||
suffix: "",
|
|
||||||
};
|
|
||||||
|
|
||||||
export default DefaultCell;
|
|
@ -1,54 +0,0 @@
|
|||||||
/**
|
|
||||||
=========================================================
|
|
||||||
* Material Dashboard 2 PRO React TS - v1.0.0
|
|
||||||
=========================================================
|
|
||||||
|
|
||||||
* Product Page: https://www.creative-tim.com/product/material-dashboard-2-pro-react-ts
|
|
||||||
* Copyright 2022 Creative Tim (https://www.creative-tim.com)
|
|
||||||
|
|
||||||
Coded by www.creative-tim.com
|
|
||||||
|
|
||||||
=========================================================
|
|
||||||
|
|
||||||
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
||||||
*/
|
|
||||||
|
|
||||||
// @mui material components
|
|
||||||
import Checkbox from "@mui/material/Checkbox";
|
|
||||||
|
|
||||||
// Material Dashboard 2 PRO React TS components
|
|
||||||
import MDBox from "components/MDBox";
|
|
||||||
import MDTypography from "components/MDTypography";
|
|
||||||
import Link from "@mui/material/Link";
|
|
||||||
|
|
||||||
// Declaring props types for IdCell
|
|
||||||
interface Props {
|
|
||||||
id: string;
|
|
||||||
checked?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
function IdCell({id, checked}: Props): JSX.Element
|
|
||||||
{
|
|
||||||
const pathParts = window.location.pathname.split("/");
|
|
||||||
const tableName = pathParts[1];
|
|
||||||
const href = `/${tableName}/${id}`;
|
|
||||||
const link = <Link href={href}>{id}</Link>;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<MDBox display="flex" alignItems="center">
|
|
||||||
<Checkbox defaultChecked={checked} />
|
|
||||||
<MDBox ml={1}>
|
|
||||||
<MDTypography variant="caption" fontWeight="medium" color="text">
|
|
||||||
{link}
|
|
||||||
</MDTypography>
|
|
||||||
</MDBox>
|
|
||||||
</MDBox>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Declaring default props for IdCell
|
|
||||||
IdCell.defaultProps = {
|
|
||||||
checked: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default IdCell;
|
|
@ -1,57 +0,0 @@
|
|||||||
/**
|
|
||||||
=========================================================
|
|
||||||
* Material Dashboard 2 PRO React TS - v1.0.0
|
|
||||||
=========================================================
|
|
||||||
|
|
||||||
* Product Page: https://www.creative-tim.com/product/material-dashboard-2-pro-react-ts
|
|
||||||
* Copyright 2022 Creative Tim (https://www.creative-tim.com)
|
|
||||||
|
|
||||||
Coded by www.creative-tim.com
|
|
||||||
|
|
||||||
=========================================================
|
|
||||||
|
|
||||||
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
||||||
*/
|
|
||||||
|
|
||||||
// @mui material components
|
|
||||||
import Icon from "@mui/material/Icon";
|
|
||||||
|
|
||||||
// Material Dashboard 2 PRO React TS components
|
|
||||||
import MDBox from "components/MDBox";
|
|
||||||
import MDTypography from "components/MDTypography";
|
|
||||||
import MDButton from "components/MDButton";
|
|
||||||
|
|
||||||
// Declaring props types for StatusCell
|
|
||||||
interface Props {
|
|
||||||
icon: string;
|
|
||||||
color:
|
|
||||||
| "primary"
|
|
||||||
| "secondary"
|
|
||||||
| "info"
|
|
||||||
| "success"
|
|
||||||
| "warning"
|
|
||||||
| "error"
|
|
||||||
| "dark"
|
|
||||||
| "light"
|
|
||||||
| "white"
|
|
||||||
| "default";
|
|
||||||
status: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
function StatusCell({icon, color, status}: Props): JSX.Element
|
|
||||||
{
|
|
||||||
return (
|
|
||||||
<MDBox display="flex" alignItems="center">
|
|
||||||
<MDBox mr={1}>
|
|
||||||
<MDButton variant="outlined" color={color} size="small" iconOnly circular>
|
|
||||||
<Icon sx={{fontWeight: "bold"}}>{icon}</Icon>
|
|
||||||
</MDButton>
|
|
||||||
</MDBox>
|
|
||||||
<MDTypography variant="caption" fontWeight="medium" color="text" sx={{lineHeight: 0}}>
|
|
||||||
{status}
|
|
||||||
</MDTypography>
|
|
||||||
</MDBox>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default StatusCell;
|
|
@ -1,193 +0,0 @@
|
|||||||
/**
|
|
||||||
=========================================================
|
|
||||||
* Material Dashboard 2 PRO React TS - v1.0.0
|
|
||||||
=========================================================
|
|
||||||
|
|
||||||
* Product Page: https://www.creative-tim.com/product/material-dashboard-2-pro-react-ts
|
|
||||||
* Copyright 2022 Creative Tim (https://www.creative-tim.com)
|
|
||||||
|
|
||||||
Coded by www.creative-tim.com
|
|
||||||
|
|
||||||
=========================================================
|
|
||||||
|
|
||||||
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
||||||
*/
|
|
||||||
|
|
||||||
const selectData = {
|
|
||||||
gender: ["Male", "Female"],
|
|
||||||
birthDate: [
|
|
||||||
"January",
|
|
||||||
"February",
|
|
||||||
"March",
|
|
||||||
"April",
|
|
||||||
"May",
|
|
||||||
"June",
|
|
||||||
"July",
|
|
||||||
"August",
|
|
||||||
"September",
|
|
||||||
"October",
|
|
||||||
"November",
|
|
||||||
"December",
|
|
||||||
],
|
|
||||||
days: [
|
|
||||||
"1",
|
|
||||||
"2",
|
|
||||||
"3",
|
|
||||||
"4",
|
|
||||||
"5",
|
|
||||||
"6",
|
|
||||||
"7",
|
|
||||||
"8",
|
|
||||||
"9",
|
|
||||||
"10",
|
|
||||||
"11",
|
|
||||||
"12",
|
|
||||||
"13",
|
|
||||||
"14",
|
|
||||||
"15",
|
|
||||||
"16",
|
|
||||||
"17",
|
|
||||||
"18",
|
|
||||||
"19",
|
|
||||||
"20",
|
|
||||||
"21",
|
|
||||||
"22",
|
|
||||||
"23",
|
|
||||||
"24",
|
|
||||||
"25",
|
|
||||||
"26",
|
|
||||||
"27",
|
|
||||||
"28",
|
|
||||||
"29",
|
|
||||||
"30",
|
|
||||||
],
|
|
||||||
years: [
|
|
||||||
"1900",
|
|
||||||
"1901",
|
|
||||||
"1902",
|
|
||||||
"1903",
|
|
||||||
"1904",
|
|
||||||
"1905",
|
|
||||||
"1906",
|
|
||||||
"1907",
|
|
||||||
"1908",
|
|
||||||
"1909",
|
|
||||||
"1910",
|
|
||||||
"1911",
|
|
||||||
"1912",
|
|
||||||
"1913",
|
|
||||||
"1914",
|
|
||||||
"1915",
|
|
||||||
"1915",
|
|
||||||
"1915",
|
|
||||||
"1916",
|
|
||||||
"1917",
|
|
||||||
"1918",
|
|
||||||
"1919",
|
|
||||||
"1920",
|
|
||||||
"1921",
|
|
||||||
"1922",
|
|
||||||
"1923",
|
|
||||||
"1924",
|
|
||||||
"1925",
|
|
||||||
"1926",
|
|
||||||
"1927",
|
|
||||||
"1928",
|
|
||||||
"1929",
|
|
||||||
"1930",
|
|
||||||
"1931",
|
|
||||||
"1932",
|
|
||||||
"1933",
|
|
||||||
"1934",
|
|
||||||
"1935",
|
|
||||||
"1936",
|
|
||||||
"1937",
|
|
||||||
"1938",
|
|
||||||
"1939",
|
|
||||||
"1940",
|
|
||||||
"1941",
|
|
||||||
"1942",
|
|
||||||
"1943",
|
|
||||||
"1944",
|
|
||||||
"1945",
|
|
||||||
"1946",
|
|
||||||
"1947",
|
|
||||||
"1948",
|
|
||||||
"1949",
|
|
||||||
"1950",
|
|
||||||
"1951",
|
|
||||||
"1952",
|
|
||||||
"1953",
|
|
||||||
"1954",
|
|
||||||
"1955",
|
|
||||||
"1956",
|
|
||||||
"1957",
|
|
||||||
"1958",
|
|
||||||
"1959",
|
|
||||||
"1960",
|
|
||||||
"1961",
|
|
||||||
"1962",
|
|
||||||
"1963",
|
|
||||||
"1964",
|
|
||||||
"1965",
|
|
||||||
"1966",
|
|
||||||
"1967",
|
|
||||||
"1968",
|
|
||||||
"1969",
|
|
||||||
"1970",
|
|
||||||
"1971",
|
|
||||||
"1972",
|
|
||||||
"1973",
|
|
||||||
"1974",
|
|
||||||
"1975",
|
|
||||||
"1976",
|
|
||||||
"1977",
|
|
||||||
"1978",
|
|
||||||
"1979",
|
|
||||||
"1980",
|
|
||||||
"1981",
|
|
||||||
"1982",
|
|
||||||
"1983",
|
|
||||||
"1984",
|
|
||||||
"1985",
|
|
||||||
"1986",
|
|
||||||
"1987",
|
|
||||||
"1988",
|
|
||||||
"1989",
|
|
||||||
"1990",
|
|
||||||
"1991",
|
|
||||||
"1992",
|
|
||||||
"1993",
|
|
||||||
"1994",
|
|
||||||
"1995",
|
|
||||||
"1996",
|
|
||||||
"1997",
|
|
||||||
"1998",
|
|
||||||
"1999",
|
|
||||||
"2000",
|
|
||||||
"2001",
|
|
||||||
"2002",
|
|
||||||
"2003",
|
|
||||||
"2004",
|
|
||||||
"2005",
|
|
||||||
"2006",
|
|
||||||
"2007",
|
|
||||||
"2008",
|
|
||||||
"2009",
|
|
||||||
"2010",
|
|
||||||
"2011",
|
|
||||||
"2012",
|
|
||||||
"2013",
|
|
||||||
"2014",
|
|
||||||
"2015",
|
|
||||||
"2016",
|
|
||||||
"2017",
|
|
||||||
"2018",
|
|
||||||
"2019",
|
|
||||||
"2020",
|
|
||||||
"2021",
|
|
||||||
],
|
|
||||||
skills: ["react", "vue", "angular", "svelte", "javascript"],
|
|
||||||
};
|
|
||||||
|
|
||||||
export default selectData;
|
|
@ -1,13 +1,14 @@
|
|||||||
// react components
|
// react components
|
||||||
import {useParams, useSearchParams} from "react-router-dom";
|
import {useParams, useNavigate, useLocation} from "react-router-dom";
|
||||||
import React, {useReducer, useState} from "react";
|
import React, {useReducer, useState} from "react";
|
||||||
|
|
||||||
// misc components
|
// misc components
|
||||||
import * as Yup from "yup";
|
import * as Yup from "yup";
|
||||||
import {Form, Formik} from "formik";
|
import {
|
||||||
|
Form, Formik, useFormik, useFormikContext,
|
||||||
|
} from "formik";
|
||||||
|
|
||||||
// qqq components
|
// qqq components
|
||||||
import {QController} from "@kingsrook/qqq-frontend-core/lib/controllers/QController";
|
|
||||||
import DynamicFormUtils from "qqq/components/QDynamicForm/utils/DynamicFormUtils";
|
import DynamicFormUtils from "qqq/components/QDynamicForm/utils/DynamicFormUtils";
|
||||||
import QDynamicForm from "qqq/components/QDynamicForm";
|
import QDynamicForm from "qqq/components/QDynamicForm";
|
||||||
import {QFieldMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldMetaData";
|
import {QFieldMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldMetaData";
|
||||||
@ -20,45 +21,65 @@ import {Alert} from "@mui/material";
|
|||||||
// Material Dashboard 2 PRO React TS components
|
// Material Dashboard 2 PRO React TS components
|
||||||
import MDBox from "components/MDBox";
|
import MDBox from "components/MDBox";
|
||||||
import MDTypography from "components/MDTypography";
|
import MDTypography from "components/MDTypography";
|
||||||
import MDButton from "../../../components/MDButton";
|
import QClient from "qqq/utils/QClient";
|
||||||
|
import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData";
|
||||||
|
import {QCancelButton, QSaveButton} from "qqq/components/QButtons";
|
||||||
|
import {QRecord} from "@kingsrook/qqq-frontend-core/lib/model/QRecord";
|
||||||
|
import Avatar from "@mui/material/Avatar";
|
||||||
|
import Icon from "@mui/material/Icon";
|
||||||
|
import QRecordSidebar from "qqq/components/QRecordSidebar";
|
||||||
|
import QTableUtils from "qqq/utils/QTableUtils";
|
||||||
|
import colors from "assets/theme/base/colors";
|
||||||
|
import {QSection} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QSection";
|
||||||
|
|
||||||
// Declaring props types for EntityForm
|
|
||||||
interface Props
|
interface Props
|
||||||
{
|
{
|
||||||
id?: string;
|
id?: string;
|
||||||
|
table?: QTableMetaData;
|
||||||
}
|
}
|
||||||
|
|
||||||
function EntityForm({id}: Props): JSX.Element
|
function EntityForm({table, id}: Props): JSX.Element
|
||||||
{
|
{
|
||||||
const qController = new QController("");
|
const qController = QClient.getInstance();
|
||||||
const {tableName} = useParams();
|
const tableNameParam = useParams().tableName;
|
||||||
|
const tableName = table === null ? tableNameParam : table.name;
|
||||||
|
|
||||||
const [validations, setValidations] = useState({});
|
const [validations, setValidations] = useState({});
|
||||||
const [initialValues, setInitialValues] = useState({} as { [key: string]: string });
|
const [initialValues, setInitialValues] = useState({} as { [key: string]: string });
|
||||||
const [formFields, setFormFields] = useState({});
|
const [formFields, setFormFields] = useState(null as Map<string, any>);
|
||||||
|
const [t1sectionName, setT1SectionName] = useState(null as string);
|
||||||
|
const [nonT1Sections, setNonT1Sections] = useState([] as QSection[]);
|
||||||
|
|
||||||
const [alertContent, setAlertContent] = useState("");
|
const [alertContent, setAlertContent] = useState("");
|
||||||
|
|
||||||
const [asyncLoadInited, setAsyncLoadInited] = useState(false);
|
const [asyncLoadInited, setAsyncLoadInited] = useState(false);
|
||||||
const [formValues, setFormValues] = useState({} as { [key: string]: string });
|
const [formValues, setFormValues] = useState({} as { [key: string]: string });
|
||||||
const [tableMetaData, setTableMetaData] = useState(null);
|
const [tableMetaData, setTableMetaData] = useState(null as QTableMetaData);
|
||||||
|
const [record, setRecord] = useState(null as QRecord);
|
||||||
|
const [tableSections, setTableSections] = useState(null as QSection[]);
|
||||||
const [, forceUpdate] = useReducer((x) => x + 1, 0);
|
const [, forceUpdate] = useReducer((x) => x + 1, 0);
|
||||||
|
|
||||||
function getDynamicStepContent(formData: any): JSX.Element
|
const navigate = useNavigate();
|
||||||
|
const location = useLocation();
|
||||||
|
|
||||||
|
function getFormSection(values: any, touched: any, formFields: any, errors: any): JSX.Element
|
||||||
{
|
{
|
||||||
const {
|
const formData: any = {};
|
||||||
formFields,
|
formData.values = values;
|
||||||
values,
|
formData.touched = touched;
|
||||||
errors,
|
formData.errors = errors;
|
||||||
touched,
|
formData.formFields = {};
|
||||||
} = formData;
|
console.log(formFields);
|
||||||
|
for (let i = 0; i < formFields.length; i++)
|
||||||
|
{
|
||||||
|
formData.formFields[formFields[i].name] = formFields[i];
|
||||||
|
}
|
||||||
|
|
||||||
if (!Object.keys(formFields).length)
|
if (!Object.keys(formFields).length)
|
||||||
{
|
{
|
||||||
return <div>Loading...</div>;
|
return <div>Loading...</div>;
|
||||||
}
|
}
|
||||||
|
return <QDynamicForm formData={formData} />;
|
||||||
return <QDynamicForm formData={formData} primaryKeyId={tableMetaData.primaryKeyField} />;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!asyncLoadInited)
|
if (!asyncLoadInited)
|
||||||
@ -69,20 +90,27 @@ function EntityForm({id}: Props): JSX.Element
|
|||||||
const tableMetaData = await qController.loadTableMetaData(tableName);
|
const tableMetaData = await qController.loadTableMetaData(tableName);
|
||||||
setTableMetaData(tableMetaData);
|
setTableMetaData(tableMetaData);
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////
|
||||||
|
// define the sections, e.g., for the left-bar //
|
||||||
|
/////////////////////////////////////////////////
|
||||||
|
const tableSections = QTableUtils.getSectionsForRecordSidebar(tableMetaData);
|
||||||
|
setTableSections(tableSections);
|
||||||
|
|
||||||
const fieldArray = [] as QFieldMetaData[];
|
const fieldArray = [] as QFieldMetaData[];
|
||||||
const sortedKeys = [...tableMetaData.fields.keys()].sort();
|
const sortedKeys = [...tableMetaData.fields.keys()].sort();
|
||||||
sortedKeys.forEach((key) =>
|
sortedKeys.forEach((key) =>
|
||||||
{
|
{
|
||||||
const fieldMetaData = tableMetaData.fields.get(key);
|
const fieldMetaData = tableMetaData.fields.get(key);
|
||||||
if (fieldMetaData.isEditable)
|
|
||||||
{
|
|
||||||
fieldArray.push(fieldMetaData);
|
fieldArray.push(fieldMetaData);
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// if doing an edit, fetch the record and pre-populate the form values from it //
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////
|
||||||
if (id !== null)
|
if (id !== null)
|
||||||
{
|
{
|
||||||
const record = await qController.get(tableName, id);
|
const record = await qController.get(tableName, id);
|
||||||
|
setRecord(record);
|
||||||
|
|
||||||
tableMetaData.fields.forEach((fieldMetaData, key) =>
|
tableMetaData.fields.forEach((fieldMetaData, key) =>
|
||||||
{
|
{
|
||||||
@ -91,20 +119,96 @@ function EntityForm({id}: Props): JSX.Element
|
|||||||
|
|
||||||
setFormValues(formValues);
|
setFormValues(formValues);
|
||||||
}
|
}
|
||||||
|
setInitialValues(initialValues);
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////
|
||||||
|
// get formField and formValidation objects for Formik //
|
||||||
|
/////////////////////////////////////////////////////////
|
||||||
const {
|
const {
|
||||||
dynamicFormFields,
|
dynamicFormFields,
|
||||||
formValidations,
|
formValidations,
|
||||||
} = DynamicFormUtils.getFormData(fieldArray);
|
} = DynamicFormUtils.getFormData(fieldArray);
|
||||||
setInitialValues(initialValues);
|
|
||||||
setFormFields(dynamicFormFields);
|
/////////////////////////////////////
|
||||||
setValidations(Yup.object()
|
// group the formFields by section //
|
||||||
.shape(formValidations));
|
/////////////////////////////////////
|
||||||
|
const dynamicFormFieldsBySection = new Map<string, any>();
|
||||||
|
let t1sectionName;
|
||||||
|
const nonT1Sections: QSection[] = [];
|
||||||
|
for (let i = 0; i < tableSections.length; i++)
|
||||||
|
{
|
||||||
|
const section = tableSections[i];
|
||||||
|
const sectionDynamicFormFields: any[] = [];
|
||||||
|
|
||||||
|
for (let j = 0; j < section.fieldNames.length; j++)
|
||||||
|
{
|
||||||
|
const fieldName = section.fieldNames[j];
|
||||||
|
const field = tableMetaData.fields.get(fieldName);
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// if id !== null - means we're on the edit screen -- show all fields on the edit screen. //
|
||||||
|
// || (or) we're on the insert screen in which case, only show editable fields. //
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
if (id !== null || field.isEditable)
|
||||||
|
{
|
||||||
|
sectionDynamicFormFields.push(dynamicFormFields[fieldName]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sectionDynamicFormFields.length === 0)
|
||||||
|
{
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// in case there are no active fields in this section, remove it from the tableSections array //
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
tableSections.splice(i, 1);
|
||||||
|
i--;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
dynamicFormFieldsBySection.set(section.name, sectionDynamicFormFields);
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////////////////
|
||||||
|
// capture the tier1 section's name //
|
||||||
|
//////////////////////////////////////
|
||||||
|
if (section.tier === "T1")
|
||||||
|
{
|
||||||
|
t1sectionName = section.name;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
nonT1Sections.push(section);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setT1SectionName(t1sectionName);
|
||||||
|
setNonT1Sections(nonT1Sections);
|
||||||
|
setFormFields(dynamicFormFieldsBySection);
|
||||||
|
setValidations(Yup.object().shape(formValidations));
|
||||||
|
|
||||||
forceUpdate();
|
forceUpdate();
|
||||||
})();
|
})();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleCancelClicked = () =>
|
||||||
|
{
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// todo - we might have rather just done a navigate(-1) (to keep history clean) //
|
||||||
|
// but if the user used the anchors on the page, this doesn't effectively cancel... //
|
||||||
|
// what we have here pushed a new history entry (I think?), so could be better //
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
if (id !== null)
|
||||||
|
{
|
||||||
|
const path = `${location.pathname.replace(/\/edit$/, "")}`;
|
||||||
|
navigate(path, {replace: true});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const path = `${location.pathname.replace(/\/create$/, "")}`;
|
||||||
|
navigate(path, {replace: true});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleSubmit = async (values: any, actions: any) =>
|
const handleSubmit = async (values: any, actions: any) =>
|
||||||
{
|
{
|
||||||
actions.setSubmitting(true);
|
actions.setSubmitting(true);
|
||||||
@ -116,9 +220,8 @@ function EntityForm({id}: Props): JSX.Element
|
|||||||
.update(tableName, id, values)
|
.update(tableName, id, values)
|
||||||
.then((record) =>
|
.then((record) =>
|
||||||
{
|
{
|
||||||
window.location.href = `/${tableName}/${record.values.get(
|
const path = `${location.pathname.replace(/\/edit$/, "")}?updateSuccess=true`;
|
||||||
tableMetaData.primaryKeyField,
|
navigate(path);
|
||||||
)}?updateSuccess=true`;
|
|
||||||
})
|
})
|
||||||
.catch((error) =>
|
.catch((error) =>
|
||||||
{
|
{
|
||||||
@ -131,9 +234,8 @@ function EntityForm({id}: Props): JSX.Element
|
|||||||
.create(tableName, values)
|
.create(tableName, values)
|
||||||
.then((record) =>
|
.then((record) =>
|
||||||
{
|
{
|
||||||
window.location.href = `/${tableName}/${record.values.get(
|
const path = `${location.pathname.replace(/create$/, record.values.get(tableMetaData.primaryKeyField))}?createSuccess=true`;
|
||||||
tableMetaData.primaryKeyField,
|
navigate(path);
|
||||||
)}?createSuccess=true`;
|
|
||||||
})
|
})
|
||||||
.catch((error) =>
|
.catch((error) =>
|
||||||
{
|
{
|
||||||
@ -143,8 +245,20 @@ function EntityForm({id}: Props): JSX.Element
|
|||||||
})();
|
})();
|
||||||
};
|
};
|
||||||
|
|
||||||
const formTitle = id != null ? `Edit ${tableMetaData?.label} (${id})` : `Create New ${tableMetaData?.label}`;
|
let formTitle = "";
|
||||||
const formId = id != null ? `edit-${tableMetaData?.label}-form` : `create-${tableMetaData?.label}-form`;
|
if (tableMetaData)
|
||||||
|
{
|
||||||
|
if (id == null)
|
||||||
|
{
|
||||||
|
formTitle = `Create new ${tableMetaData?.label}`;
|
||||||
|
}
|
||||||
|
else if (record != null)
|
||||||
|
{
|
||||||
|
formTitle = `Edit ${tableMetaData?.label}: ${record?.recordLabel}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const formId = id != null ? `edit-${tableMetaData?.name}-form` : `create-${tableMetaData?.name}-form`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MDBox mb={3}>
|
<MDBox mb={3}>
|
||||||
@ -154,15 +268,15 @@ function EntityForm({id}: Props): JSX.Element
|
|||||||
<MDBox mb={3}>
|
<MDBox mb={3}>
|
||||||
<Alert severity="error">{alertContent}</Alert>
|
<Alert severity="error">{alertContent}</Alert>
|
||||||
</MDBox>
|
</MDBox>
|
||||||
) : (
|
) : ("")}
|
||||||
""
|
</Grid>
|
||||||
)}
|
</Grid>
|
||||||
<Card id="edit-form-container" sx={{overflow: "visible"}}>
|
<Grid container spacing={3}>
|
||||||
<MDBox p={3}>
|
<Grid item xs={12} lg={3}>
|
||||||
<MDTypography variant="h5">{formTitle}</MDTypography>
|
<QRecordSidebar tableSections={tableSections} />
|
||||||
</MDBox>
|
</Grid>
|
||||||
<MDBox pb={3} px={3}>
|
<Grid item xs={12} lg={9}>
|
||||||
<Grid key="fields-grid" container spacing={3}>
|
|
||||||
<Formik
|
<Formik
|
||||||
initialValues={initialValues}
|
initialValues={initialValues}
|
||||||
validationSchema={validations}
|
validationSchema={validations}
|
||||||
@ -175,41 +289,69 @@ function EntityForm({id}: Props): JSX.Element
|
|||||||
isSubmitting,
|
isSubmitting,
|
||||||
}) => (
|
}) => (
|
||||||
<Form id={formId} autoComplete="off">
|
<Form id={formId} autoComplete="off">
|
||||||
<MDBox p={3} width="100%">
|
|
||||||
{/***************************************************************************
|
<MDBox pb={3} pt={0}>
|
||||||
** step content - e.g., the appropriate form or other screen for the step **
|
<Card id={`${t1sectionName}`} sx={{overflow: "visible"}}>
|
||||||
***************************************************************************/}
|
<MDBox display="flex" p={3} pb={1}>
|
||||||
{getDynamicStepContent({
|
<MDBox mr={1.5}>
|
||||||
values,
|
<Avatar sx={{bgcolor: colors.info.main}}>
|
||||||
touched,
|
<Icon>
|
||||||
formFields,
|
{tableMetaData?.iconName}
|
||||||
errors,
|
</Icon>
|
||||||
})}
|
</Avatar>
|
||||||
<Grid key="buttonGrid" container spacing={3}>
|
|
||||||
<MDBox mt={5} ml="auto">
|
|
||||||
<MDButton type="submit" variant="gradient" color="dark" size="small">
|
|
||||||
save
|
|
||||||
{" "}
|
|
||||||
{tableMetaData?.label}
|
|
||||||
</MDButton>
|
|
||||||
</MDBox>
|
</MDBox>
|
||||||
|
<MDBox display="flex" alignItems="center">
|
||||||
|
<MDTypography variant="h5">{formTitle}</MDTypography>
|
||||||
|
</MDBox>
|
||||||
|
</MDBox>
|
||||||
|
{
|
||||||
|
t1sectionName && formFields ? (
|
||||||
|
<MDBox pb={3} px={3}>
|
||||||
|
<MDBox p={3} width="100%">
|
||||||
|
{getFormSection(values, touched, formFields.get(t1sectionName), errors)}
|
||||||
|
</MDBox>
|
||||||
|
</MDBox>
|
||||||
|
) : null
|
||||||
|
}
|
||||||
|
</Card>
|
||||||
|
</MDBox>
|
||||||
|
{formFields && nonT1Sections.length ? nonT1Sections.map((section: QSection) => (
|
||||||
|
<MDBox key={`edit-card-${section.name}`} pb={3}>
|
||||||
|
<Card id={section.name} sx={{overflow: "visible"}}>
|
||||||
|
<MDTypography variant="h5" p={3} pb={1}>
|
||||||
|
{section.label}
|
||||||
|
</MDTypography>
|
||||||
|
<MDBox pb={3} px={3}>
|
||||||
|
<MDBox p={3} width="100%">
|
||||||
|
{
|
||||||
|
getFormSection(values, touched, formFields.get(section.name), errors)
|
||||||
|
}
|
||||||
|
</MDBox>
|
||||||
|
</MDBox>
|
||||||
|
</Card>
|
||||||
|
</MDBox>
|
||||||
|
)) : null}
|
||||||
|
|
||||||
|
<MDBox component="div" p={3}>
|
||||||
|
<Grid container justifyContent="flex-end" spacing={3}>
|
||||||
|
<QCancelButton onClickHandler={handleCancelClicked} />
|
||||||
|
<QSaveButton />
|
||||||
</Grid>
|
</Grid>
|
||||||
</MDBox>
|
</MDBox>
|
||||||
|
|
||||||
</Form>
|
</Form>
|
||||||
)}
|
)}
|
||||||
</Formik>
|
</Formik>
|
||||||
</Grid>
|
|
||||||
</MDBox>
|
|
||||||
</Card>
|
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
</MDBox>
|
</MDBox>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Declaring default props for DefaultCell
|
|
||||||
EntityForm.defaultProps = {
|
EntityForm.defaultProps = {
|
||||||
id: null,
|
id: null,
|
||||||
|
table: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default EntityForm;
|
export default EntityForm;
|
||||||
|
@ -1,17 +1,23 @@
|
|||||||
/**
|
/*
|
||||||
=========================================================
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
* Material Dashboard 2 PRO React TS - v1.0.0
|
* Copyright (C) 2021-2022. Kingsrook, LLC
|
||||||
=========================================================
|
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||||
|
* contact@kingsrook.com
|
||||||
* Product Page: https://www.creative-tim.com/product/material-dashboard-2-pro-react-ts
|
* https://github.com/Kingsrook/
|
||||||
* Copyright 2022 Creative Tim (https://www.creative-tim.com)
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
Coded by www.creative-tim.com
|
* 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.
|
||||||
|
*
|
||||||
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
* This program is distributed in the hope that it will be useful,
|
||||||
*/
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
// @mui material components
|
// @mui material components
|
||||||
import Link from "@mui/material/Link";
|
import Link from "@mui/material/Link";
|
||||||
@ -60,6 +66,9 @@ function Footer({company, links}: Props): JSX.Element
|
|||||||
justifyContent="space-between"
|
justifyContent="space-between"
|
||||||
alignItems="center"
|
alignItems="center"
|
||||||
px={1.5}
|
px={1.5}
|
||||||
|
style={{
|
||||||
|
position: "fixed", bottom: "0px", zIndex: -1, marginBottom: "10px",
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<MDBox
|
<MDBox
|
||||||
display="flex"
|
display="flex"
|
||||||
|
@ -1,7 +1,28 @@
|
|||||||
|
/*
|
||||||
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
|
* Copyright (C) 2021-2022. Kingsrook, LLC
|
||||||
|
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||||
|
* contact@kingsrook.com
|
||||||
|
* https://github.com/Kingsrook/
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
import {useState, useEffect} from "react";
|
import {useState, useEffect} from "react";
|
||||||
|
|
||||||
// react-router components
|
// react-router components
|
||||||
import {useLocation, Link} from "react-router-dom";
|
import {useLocation} from "react-router-dom";
|
||||||
|
|
||||||
// @material-ui core components
|
// @material-ui core components
|
||||||
import AppBar from "@mui/material/AppBar";
|
import AppBar from "@mui/material/AppBar";
|
||||||
@ -16,7 +37,6 @@ import MDInput from "components/MDInput";
|
|||||||
import MDBadge from "components/MDBadge";
|
import MDBadge from "components/MDBadge";
|
||||||
|
|
||||||
// Material Dashboard 2 PRO React TS examples components
|
// Material Dashboard 2 PRO React TS examples components
|
||||||
import Breadcrumbs from "examples/Breadcrumbs";
|
|
||||||
import NotificationItem from "examples/Items/NotificationItem";
|
import NotificationItem from "examples/Items/NotificationItem";
|
||||||
|
|
||||||
// Custom styles for Navbar
|
// Custom styles for Navbar
|
||||||
@ -38,7 +58,7 @@ import {
|
|||||||
} from "context";
|
} from "context";
|
||||||
|
|
||||||
// qqq
|
// qqq
|
||||||
import AuthenticationButton from "qqq/components/buttons/AuthenticationButton";
|
import QBreadcrumbs, {routeToLabel} from "qqq/components/QBreadcrumbs";
|
||||||
|
|
||||||
// Declaring prop types for Navbar
|
// Declaring prop types for Navbar
|
||||||
interface Props
|
interface Props
|
||||||
@ -107,9 +127,11 @@ function Navbar({absolute, light, isMini}: Props): JSX.Element
|
|||||||
onClose={handleCloseMenu}
|
onClose={handleCloseMenu}
|
||||||
sx={{mt: 2}}
|
sx={{mt: 2}}
|
||||||
>
|
>
|
||||||
<NotificationItem icon={<Icon>email</Icon>} title="Check new messages" />
|
<NotificationItem icon={<Icon>email</Icon>} title="0 messages available" />
|
||||||
|
{/*
|
||||||
<NotificationItem icon={<Icon>podcasts</Icon>} title="Manage Podcast sessions" />
|
<NotificationItem icon={<Icon>podcasts</Icon>} title="Manage Podcast sessions" />
|
||||||
<NotificationItem icon={<Icon>shopping_cart</Icon>} title="Payment successfully completed" />
|
<NotificationItem icon={<Icon>shopping_cart</Icon>} title="Payment successfully completed" />
|
||||||
|
*/}
|
||||||
</Menu>
|
</Menu>
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -134,7 +156,7 @@ function Navbar({absolute, light, isMini}: Props): JSX.Element
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const breadcrumbTitle = route[route.length - 1].replace(/([A-Z])/g, " $1").trim();
|
const breadcrumbTitle = routeToLabel(route[route.length - 1]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AppBar
|
<AppBar
|
||||||
@ -146,7 +168,7 @@ function Navbar({absolute, light, isMini}: Props): JSX.Element
|
|||||||
>
|
>
|
||||||
<Toolbar sx={navbarContainer}>
|
<Toolbar sx={navbarContainer}>
|
||||||
<MDBox color="inherit" mb={{xs: 1, md: 0}} sx={(theme) => navbarRow(theme, {isMini})}>
|
<MDBox color="inherit" mb={{xs: 1, md: 0}} sx={(theme) => navbarRow(theme, {isMini})}>
|
||||||
<Breadcrumbs icon="home" title={breadcrumbTitle} route={route} light={light} />
|
<QBreadcrumbs icon="home" title={breadcrumbTitle} route={route} light={light} />
|
||||||
<IconButton sx={navbarDesktopMenu} onClick={handleMiniSidenav} size="small" disableRipple>
|
<IconButton sx={navbarDesktopMenu} onClick={handleMiniSidenav} size="small" disableRipple>
|
||||||
<Icon fontSize="medium" sx={iconsStyle}>
|
<Icon fontSize="medium" sx={iconsStyle}>
|
||||||
{miniSidenav ? "menu_open" : "menu"}
|
{miniSidenav ? "menu_open" : "menu"}
|
||||||
@ -159,14 +181,6 @@ function Navbar({absolute, light, isMini}: Props): JSX.Element
|
|||||||
<MDInput label="Search here" />
|
<MDInput label="Search here" />
|
||||||
</MDBox>
|
</MDBox>
|
||||||
<MDBox color={light ? "white" : "inherit"}>
|
<MDBox color={light ? "white" : "inherit"}>
|
||||||
<AuthenticationButton />
|
|
||||||
{ /*
|
|
||||||
<Link to="/authentication/sign-in/basic">
|
|
||||||
<IconButton sx={navbarIconButton} size="small" disableRipple>
|
|
||||||
<Icon sx={iconsStyle}>account_circle</Icon>
|
|
||||||
</IconButton>
|
|
||||||
</Link>
|
|
||||||
*/ }
|
|
||||||
<IconButton
|
<IconButton
|
||||||
size="small"
|
size="small"
|
||||||
disableRipple
|
disableRipple
|
||||||
@ -193,7 +207,7 @@ function Navbar({absolute, light, isMini}: Props): JSX.Element
|
|||||||
sx={navbarIconButton}
|
sx={navbarIconButton}
|
||||||
onClick={handleOpenMenu}
|
onClick={handleOpenMenu}
|
||||||
>
|
>
|
||||||
<MDBadge badgeContent={9} color="error" size="xs" circular>
|
<MDBadge badgeContent={0} color="error" size="xs" circular>
|
||||||
<Icon sx={iconsStyle}>notifications</Icon>
|
<Icon sx={iconsStyle}>notifications</Icon>
|
||||||
</MDBadge>
|
</MDBadge>
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
@ -1,16 +1,22 @@
|
|||||||
/**
|
/*
|
||||||
=========================================================
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
* Material Dashboard 2 PRO React TS - v1.0.0
|
* Copyright (C) 2021-2022. Kingsrook, LLC
|
||||||
=========================================================
|
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||||
|
* contact@kingsrook.com
|
||||||
* Product Page: https://www.creative-tim.com/product/material-dashboard-2-pro-react-ts
|
* https://github.com/Kingsrook/
|
||||||
* Copyright 2022 Creative Tim (https://www.creative-tim.com)
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
Coded by www.creative-tim.com
|
* 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.
|
||||||
|
*
|
||||||
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// @mui material components
|
// @mui material components
|
||||||
|
105
src/qqq/components/ProcessLinkCard/index.tsx
Normal file
105
src/qqq/components/ProcessLinkCard/index.tsx
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
/*
|
||||||
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
|
* Copyright (C) 2021-2022. Kingsrook, LLC
|
||||||
|
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||||
|
* contact@kingsrook.com
|
||||||
|
* https://github.com/Kingsrook/
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
import {ReactNode} from "react";
|
||||||
|
|
||||||
|
// @mui material components
|
||||||
|
import Card from "@mui/material/Card";
|
||||||
|
import Divider from "@mui/material/Divider";
|
||||||
|
import Icon from "@mui/material/Icon";
|
||||||
|
|
||||||
|
// Material Dashboard 2 PRO React TS components
|
||||||
|
import MDBox from "components/MDBox";
|
||||||
|
import MDTypography from "components/MDTypography";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
color?: "primary" | "secondary" | "info" | "success" | "warning" | "error" | "light" | "dark";
|
||||||
|
title: string;
|
||||||
|
percentage?: {
|
||||||
|
color: "primary" | "secondary" | "info" | "success" | "warning" | "error" | "dark" | "white";
|
||||||
|
amount: string | number;
|
||||||
|
label: string;
|
||||||
|
};
|
||||||
|
icon: ReactNode;
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
function ProcessLinkCard({
|
||||||
|
color, title, percentage, icon,
|
||||||
|
}: Props): JSX.Element
|
||||||
|
{
|
||||||
|
return (
|
||||||
|
<Card>
|
||||||
|
<MDBox display="flex" justifyContent="space-between" pt={1} px={2}>
|
||||||
|
<MDBox
|
||||||
|
variant="gradient"
|
||||||
|
bgColor={color}
|
||||||
|
color={color === "light" ? "dark" : "white"}
|
||||||
|
coloredShadow={color}
|
||||||
|
borderRadius="xl"
|
||||||
|
display="flex"
|
||||||
|
justifyContent="center"
|
||||||
|
alignItems="center"
|
||||||
|
width="4rem"
|
||||||
|
height="4rem"
|
||||||
|
mt={-3}
|
||||||
|
>
|
||||||
|
<Icon fontSize="medium" color="inherit">
|
||||||
|
{icon}
|
||||||
|
</Icon>
|
||||||
|
</MDBox>
|
||||||
|
<MDBox textAlign="right" lineHeight={1.25}>
|
||||||
|
<MDTypography variant="button" fontWeight="bold" color="text">
|
||||||
|
{title}
|
||||||
|
</MDTypography>
|
||||||
|
</MDBox>
|
||||||
|
</MDBox>
|
||||||
|
<Divider />
|
||||||
|
<MDBox pb={2} px={2}>
|
||||||
|
<MDTypography component="p" variant="button" color="text" display="flex">
|
||||||
|
<MDTypography
|
||||||
|
component="span"
|
||||||
|
variant="button"
|
||||||
|
fontWeight="bold"
|
||||||
|
color={percentage.color}
|
||||||
|
>
|
||||||
|
{percentage.amount}
|
||||||
|
</MDTypography>
|
||||||
|
Click here to run the process called
|
||||||
|
{" "}
|
||||||
|
{title}
|
||||||
|
.
|
||||||
|
{percentage.label}
|
||||||
|
</MDTypography>
|
||||||
|
</MDBox>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
ProcessLinkCard.defaultProps = {
|
||||||
|
color: "info",
|
||||||
|
percentage: {
|
||||||
|
color: "success",
|
||||||
|
text: "",
|
||||||
|
label: "",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ProcessLinkCard;
|
138
src/qqq/components/QBreadcrumbs/index.tsx
Normal file
138
src/qqq/components/QBreadcrumbs/index.tsx
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {ReactNode} from "react";
|
||||||
|
|
||||||
|
import {Link} from "react-router-dom";
|
||||||
|
import {Breadcrumbs as MuiBreadcrumbs} from "@mui/material";
|
||||||
|
import Icon from "@mui/material/Icon";
|
||||||
|
import MDBox from "components/MDBox";
|
||||||
|
import MDTypography from "components/MDTypography";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
icon: ReactNode;
|
||||||
|
title: string;
|
||||||
|
route: string | string[];
|
||||||
|
light?: boolean;
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ucFirst = (input: string): string =>
|
||||||
|
{
|
||||||
|
if (!input)
|
||||||
|
{
|
||||||
|
return (input);
|
||||||
|
}
|
||||||
|
return (input.substring(0, 1).toUpperCase() + input.substring(1));
|
||||||
|
};
|
||||||
|
|
||||||
|
export const routeToLabel = (route: string): string =>
|
||||||
|
{
|
||||||
|
const label = ucFirst(route
|
||||||
|
.replace(".", " ")
|
||||||
|
.replace("-", " ")
|
||||||
|
.replace("_", " ")
|
||||||
|
.replace(/([a-z])([A-Z]+)/g, "$1 $2") // transform personUSA => person USA
|
||||||
|
.replace(/^([A-Z]+)([A-Z])([a-z])/, "$1 $2$3")); // transform USAPerson => USA Person
|
||||||
|
return (label);
|
||||||
|
};
|
||||||
|
|
||||||
|
function QBreadcrumbs({
|
||||||
|
icon, title, route, light,
|
||||||
|
}: Props): JSX.Element
|
||||||
|
{
|
||||||
|
const routes: string[] | any = route.slice(0, -1);
|
||||||
|
|
||||||
|
let pageTitle = "Nutrifresh One";
|
||||||
|
const fullRoutes: string[] = [];
|
||||||
|
let accumulatedPath = "";
|
||||||
|
for (let i = 0; i < routes.length; i++)
|
||||||
|
{
|
||||||
|
accumulatedPath = `${accumulatedPath}/${routes[i]}`;
|
||||||
|
fullRoutes.push(accumulatedPath);
|
||||||
|
pageTitle = `${routeToLabel(routes[i])} | ${pageTitle}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
document.title = `${ucFirst(title)} | ${pageTitle}`;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MDBox mr={{xs: 0, xl: 8}}>
|
||||||
|
<MuiBreadcrumbs
|
||||||
|
sx={{
|
||||||
|
"& .MuiBreadcrumbs-separator": {
|
||||||
|
color: ({palette: {white, grey}}) => (light ? white.main : grey[600]),
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Link to="/">
|
||||||
|
<MDTypography
|
||||||
|
component="span"
|
||||||
|
variant="body2"
|
||||||
|
color={light ? "white" : "dark"}
|
||||||
|
opacity={light ? 0.8 : 0.5}
|
||||||
|
sx={{lineHeight: 0}}
|
||||||
|
>
|
||||||
|
<Icon>{icon}</Icon>
|
||||||
|
</MDTypography>
|
||||||
|
</Link>
|
||||||
|
{fullRoutes.map((fullRoute: string) => (
|
||||||
|
<Link to={fullRoute} key={fullRoute}>
|
||||||
|
<MDTypography
|
||||||
|
component="span"
|
||||||
|
variant="button"
|
||||||
|
fontWeight="regular"
|
||||||
|
textTransform="capitalize"
|
||||||
|
color={light ? "white" : "dark"}
|
||||||
|
opacity={light ? 0.8 : 0.5}
|
||||||
|
sx={{lineHeight: 0}}
|
||||||
|
>
|
||||||
|
{routeToLabel(fullRoute.replace(/.*\//, ""))}
|
||||||
|
</MDTypography>
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
<MDTypography
|
||||||
|
variant="button"
|
||||||
|
fontWeight="regular"
|
||||||
|
textTransform="capitalize"
|
||||||
|
color={light ? "white" : "dark"}
|
||||||
|
sx={{lineHeight: 0}}
|
||||||
|
>
|
||||||
|
{routeToLabel(title)}
|
||||||
|
</MDTypography>
|
||||||
|
</MuiBreadcrumbs>
|
||||||
|
<MDTypography
|
||||||
|
fontWeight="bold"
|
||||||
|
textTransform="capitalize"
|
||||||
|
variant="h6"
|
||||||
|
color={light ? "white" : "dark"}
|
||||||
|
noWrap
|
||||||
|
>
|
||||||
|
{routeToLabel(title)}
|
||||||
|
</MDTypography>
|
||||||
|
</MDBox>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
QBreadcrumbs.defaultProps = {
|
||||||
|
light: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default QBreadcrumbs;
|
122
src/qqq/components/QButtons/index.tsx
Normal file
122
src/qqq/components/QButtons/index.tsx
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
/*
|
||||||
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
|
* Copyright (C) 2021-2022. Kingsrook, LLC
|
||||||
|
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||||
|
* contact@kingsrook.com
|
||||||
|
* https://github.com/Kingsrook/
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import MDBox from "components/MDBox";
|
||||||
|
import {Link} from "react-router-dom";
|
||||||
|
import MDButton from "components/MDButton";
|
||||||
|
import Icon from "@mui/material/Icon";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
// eslint-disable import/prefer-default-export
|
||||||
|
|
||||||
|
const standardWidth = "150px";
|
||||||
|
|
||||||
|
export function QCreateNewButton(): JSX.Element
|
||||||
|
{
|
||||||
|
return (
|
||||||
|
<MDBox ml={3} mr={2} width={standardWidth}>
|
||||||
|
<Link to="create">
|
||||||
|
<MDButton variant="gradient" color="info" fullWidth startIcon={<Icon>add</Icon>}>
|
||||||
|
Create New
|
||||||
|
</MDButton>
|
||||||
|
</Link>
|
||||||
|
</MDBox>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function QSaveButton(): JSX.Element
|
||||||
|
{
|
||||||
|
return (
|
||||||
|
<MDBox ml={3} width={standardWidth}>
|
||||||
|
<MDButton type="submit" variant="gradient" color="info" size="small" fullWidth startIcon={<Icon>save</Icon>}>
|
||||||
|
Save
|
||||||
|
</MDButton>
|
||||||
|
</MDBox>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface QDeleteButtonProps
|
||||||
|
{
|
||||||
|
onClickHandler: any
|
||||||
|
}
|
||||||
|
|
||||||
|
export function QDeleteButton({onClickHandler}: QDeleteButtonProps): JSX.Element
|
||||||
|
{
|
||||||
|
return (
|
||||||
|
<MDBox ml={3} mr={3} width={standardWidth}>
|
||||||
|
<MDButton variant="gradient" color="primary" size="small" onClick={onClickHandler} fullWidth startIcon={<Icon>delete</Icon>}>
|
||||||
|
Delete
|
||||||
|
</MDButton>
|
||||||
|
</MDBox>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function QEditButton(): JSX.Element
|
||||||
|
{
|
||||||
|
return (
|
||||||
|
<MDBox width={standardWidth}>
|
||||||
|
<Link to="edit">
|
||||||
|
<MDButton variant="gradient" color="dark" size="small" fullWidth startIcon={<Icon>edit</Icon>}>
|
||||||
|
Edit
|
||||||
|
</MDButton>
|
||||||
|
</Link>
|
||||||
|
</MDBox>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface QActionsMenuButtonProps
|
||||||
|
{
|
||||||
|
isOpen: boolean;
|
||||||
|
onClickHandler: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function QActionsMenuButton({isOpen, onClickHandler}: QActionsMenuButtonProps): JSX.Element
|
||||||
|
{
|
||||||
|
return (
|
||||||
|
<MDBox width={standardWidth}>
|
||||||
|
<MDButton
|
||||||
|
variant={isOpen ? "contained" : "outlined"}
|
||||||
|
color="dark"
|
||||||
|
onClick={onClickHandler}
|
||||||
|
fullWidth
|
||||||
|
>
|
||||||
|
actions
|
||||||
|
<Icon>keyboard_arrow_down</Icon>
|
||||||
|
</MDButton>
|
||||||
|
</MDBox>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface QCancelButtonProps
|
||||||
|
{
|
||||||
|
onClickHandler: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function QCancelButton({onClickHandler}: QCancelButtonProps): JSX.Element
|
||||||
|
{
|
||||||
|
return (
|
||||||
|
<MDBox ml="auto" width={standardWidth}>
|
||||||
|
<MDButton type="button" variant="outlined" color="dark" size="small" fullWidth startIcon={<Icon>cancel</Icon>} onClick={onClickHandler}>
|
||||||
|
Cancel
|
||||||
|
</MDButton>
|
||||||
|
</MDBox>
|
||||||
|
);
|
||||||
|
}
|
@ -34,7 +34,6 @@ import QDynamicFormField from "qqq/components/QDynamicFormField";
|
|||||||
interface Props {
|
interface Props {
|
||||||
formLabel?: string;
|
formLabel?: string;
|
||||||
formData: any;
|
formData: any;
|
||||||
primaryKeyId?: string;
|
|
||||||
bulkEditMode?: boolean;
|
bulkEditMode?: boolean;
|
||||||
bulkEditSwitchChangeHandler?: any
|
bulkEditSwitchChangeHandler?: any
|
||||||
}
|
}
|
||||||
@ -42,7 +41,7 @@ interface Props {
|
|||||||
function QDynamicForm(props: Props): JSX.Element
|
function QDynamicForm(props: Props): JSX.Element
|
||||||
{
|
{
|
||||||
const {
|
const {
|
||||||
formData, formLabel, primaryKeyId, bulkEditMode, bulkEditSwitchChangeHandler,
|
formData, formLabel, bulkEditMode, bulkEditSwitchChangeHandler,
|
||||||
} = props;
|
} = props;
|
||||||
const {
|
const {
|
||||||
formFields, values, errors, touched,
|
formFields, values, errors, touched,
|
||||||
@ -77,10 +76,6 @@ function QDynamicForm(props: Props): JSX.Element
|
|||||||
&& Object.keys(formFields).map((fieldName: any) =>
|
&& Object.keys(formFields).map((fieldName: any) =>
|
||||||
{
|
{
|
||||||
const field = formFields[fieldName];
|
const field = formFields[fieldName];
|
||||||
if (primaryKeyId && fieldName === primaryKeyId)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (values[fieldName] === undefined)
|
if (values[fieldName] === undefined)
|
||||||
{
|
{
|
||||||
values[fieldName] = "";
|
values[fieldName] = "";
|
||||||
@ -109,17 +104,18 @@ function QDynamicForm(props: Props): JSX.Element
|
|||||||
|
|
||||||
// todo? inputProps={{ autoComplete: "" }}
|
// todo? inputProps={{ autoComplete: "" }}
|
||||||
// todo? placeholder={password.placeholder}
|
// todo? placeholder={password.placeholder}
|
||||||
// todo? success={!errors[fieldName] && touched[fieldName]}
|
|
||||||
return (
|
return (
|
||||||
<Grid item xs={12} sm={6} key={fieldName}>
|
<Grid item xs={12} sm={6} key={fieldName}>
|
||||||
<QDynamicFormField
|
<QDynamicFormField
|
||||||
type={field.type}
|
type={field.type}
|
||||||
label={field.label}
|
label={field.label}
|
||||||
|
isEditable={field.isEditable}
|
||||||
name={fieldName}
|
name={fieldName}
|
||||||
value={values[fieldName]}
|
value={values[fieldName]}
|
||||||
error={errors[fieldName] && touched[fieldName]}
|
error={errors[fieldName] && touched[fieldName]}
|
||||||
bulkEditMode={bulkEditMode}
|
bulkEditMode={bulkEditMode}
|
||||||
bulkEditSwitchChangeHandler={bulkEditSwitchChanged}
|
bulkEditSwitchChangeHandler={bulkEditSwitchChanged}
|
||||||
|
success={`${values[fieldName]}` !== "" && !errors[fieldName] && touched[fieldName]}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
);
|
);
|
||||||
@ -132,7 +128,6 @@ function QDynamicForm(props: Props): JSX.Element
|
|||||||
|
|
||||||
QDynamicForm.defaultProps = {
|
QDynamicForm.defaultProps = {
|
||||||
formLabel: undefined,
|
formLabel: undefined,
|
||||||
primaryKeyId: undefined,
|
|
||||||
bulkEditMode: false,
|
bulkEditMode: false,
|
||||||
bulkEditSwitchChangeHandler: () =>
|
bulkEditSwitchChangeHandler: () =>
|
||||||
{},
|
{},
|
||||||
|
@ -66,6 +66,9 @@ class DynamicFormUtils
|
|||||||
case QFieldType.BLOB:
|
case QFieldType.BLOB:
|
||||||
fieldType = "file";
|
fieldType = "file";
|
||||||
break;
|
break;
|
||||||
|
case QFieldType.BOOLEAN:
|
||||||
|
fieldType = "checkbox";
|
||||||
|
break;
|
||||||
case QFieldType.TEXT:
|
case QFieldType.TEXT:
|
||||||
case QFieldType.HTML:
|
case QFieldType.HTML:
|
||||||
case QFieldType.STRING:
|
case QFieldType.STRING:
|
||||||
@ -80,6 +83,7 @@ class DynamicFormUtils
|
|||||||
name: field.name,
|
name: field.name,
|
||||||
label: label,
|
label: label,
|
||||||
isRequired: field.isRequired,
|
isRequired: field.isRequired,
|
||||||
|
isEditable: field.isEditable,
|
||||||
type: fieldType,
|
type: fieldType,
|
||||||
// todo invalidMsg: "Zipcode is not valid (e.g. 70000).",
|
// todo invalidMsg: "Zipcode is not valid (e.g. 70000).",
|
||||||
});
|
});
|
||||||
|
@ -26,7 +26,6 @@ import {ErrorMessage, Field} from "formik";
|
|||||||
import MDBox from "components/MDBox";
|
import MDBox from "components/MDBox";
|
||||||
import MDTypography from "components/MDTypography";
|
import MDTypography from "components/MDTypography";
|
||||||
import MDInput from "components/MDInput";
|
import MDInput from "components/MDInput";
|
||||||
import QDynamicForm from "qqq/components/QDynamicForm";
|
|
||||||
import React, {useState} from "react";
|
import React, {useState} from "react";
|
||||||
import Grid from "@mui/material/Grid";
|
import Grid from "@mui/material/Grid";
|
||||||
import Switch from "@mui/material/Switch";
|
import Switch from "@mui/material/Switch";
|
||||||
@ -36,24 +35,33 @@ interface Props
|
|||||||
{
|
{
|
||||||
label: string;
|
label: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
type: string;
|
||||||
|
isEditable?: boolean;
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
bulkEditMode?: boolean;
|
bulkEditMode?: boolean;
|
||||||
bulkEditSwitchChangeHandler?: any
|
bulkEditSwitchChangeHandler?: any
|
||||||
}
|
}
|
||||||
|
|
||||||
function QDynamicFormField({
|
function QDynamicFormField({
|
||||||
label, name, bulkEditMode, bulkEditSwitchChangeHandler, ...rest
|
label, name, bulkEditMode, bulkEditSwitchChangeHandler, type, isEditable, ...rest
|
||||||
}: Props): JSX.Element
|
}: Props): JSX.Element
|
||||||
{
|
{
|
||||||
const [switchChecked, setSwitchChecked] = useState(false);
|
const [switchChecked, setSwitchChecked] = useState(false);
|
||||||
const [isDisabled, setIsDisabled] = useState(bulkEditMode);
|
const [isDisabled, setIsDisabled] = useState(!isEditable || bulkEditMode);
|
||||||
|
|
||||||
|
const inputLabelProps = {};
|
||||||
|
if (type.toLowerCase().match("(date|time)"))
|
||||||
|
{
|
||||||
|
// @ts-ignore
|
||||||
|
inputLabelProps.shrink = true;
|
||||||
|
}
|
||||||
|
|
||||||
const field = () => (
|
const field = () => (
|
||||||
<>
|
<>
|
||||||
<Field {...rest} name={name} as={MDInput} variant="standard" label={label} fullWidth disabled={isDisabled} />
|
<Field {...rest} name={name} type={type} as={MDInput} variant="standard" label={label} InputLabelProps={inputLabelProps} fullWidth disabled={isDisabled} />
|
||||||
<MDBox mt={0.75}>
|
<MDBox mt={0.75}>
|
||||||
<MDTypography component="div" variant="caption" color="error" fontWeight="regular">
|
<MDTypography component="div" variant="caption" color="error" fontWeight="regular">
|
||||||
{!isDisabled && <ErrorMessage name={name} />}
|
{!isDisabled && <div className="fieldErrorMessage"><ErrorMessage name={name} /></div>}
|
||||||
</MDTypography>
|
</MDTypography>
|
||||||
</MDBox>
|
</MDBox>
|
||||||
</>
|
</>
|
||||||
@ -100,6 +108,7 @@ function QDynamicFormField({
|
|||||||
|
|
||||||
QDynamicFormField.defaultProps = {
|
QDynamicFormField.defaultProps = {
|
||||||
bulkEditMode: false,
|
bulkEditMode: false,
|
||||||
|
isEditable: true,
|
||||||
bulkEditSwitchChangeHandler: () =>
|
bulkEditSwitchChangeHandler: () =>
|
||||||
{},
|
{},
|
||||||
};
|
};
|
||||||
|
85
src/qqq/components/QRecordSidebar/index.tsx
Normal file
85
src/qqq/components/QRecordSidebar/index.tsx
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
/*
|
||||||
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
|
* Copyright (C) 2021-2022. Kingsrook, LLC
|
||||||
|
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||||
|
* contact@kingsrook.com
|
||||||
|
* https://github.com/Kingsrook/
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React, {ReactNode} from "react";
|
||||||
|
|
||||||
|
import {Link} from "react-router-dom";
|
||||||
|
import {Breadcrumbs as MuiBreadcrumbs} from "@mui/material";
|
||||||
|
import Icon from "@mui/material/Icon";
|
||||||
|
import MDBox from "components/MDBox";
|
||||||
|
import MDTypography from "components/MDTypography";
|
||||||
|
import Card from "@mui/material/Card";
|
||||||
|
import {Theme} from "@mui/material/styles";
|
||||||
|
import {QSection} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QSection";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
tableSections: QSection[];
|
||||||
|
light?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
function QRecordSidebar({tableSections, light}: Props): JSX.Element
|
||||||
|
{
|
||||||
|
return (
|
||||||
|
<Card sx={{borderRadius: ({borders: {borderRadius}}) => borderRadius.lg, position: "sticky", top: "1%"}}>
|
||||||
|
<MDBox component="ul" display="flex" flexDirection="column" p={2} m={0} sx={{listStyle: "none"}}>
|
||||||
|
{
|
||||||
|
tableSections ? tableSections.map((section: QSection, key: number) => (
|
||||||
|
<MDBox key={`section-${section.name}`} component="li" pt={key === 0 ? 0 : 1}>
|
||||||
|
<MDTypography
|
||||||
|
component="a"
|
||||||
|
href={`#${section.name}`}
|
||||||
|
variant="button"
|
||||||
|
fontWeight="regular"
|
||||||
|
sx={({
|
||||||
|
borders: {borderRadius}, functions: {pxToRem}, palette: {light}, transitions,
|
||||||
|
}: Theme) => ({
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
borderRadius: borderRadius.md,
|
||||||
|
padding: `${pxToRem(10)} ${pxToRem(16)}`,
|
||||||
|
transition: transitions.create("background-color", {
|
||||||
|
easing: transitions.easing.easeInOut,
|
||||||
|
duration: transitions.duration.shorter,
|
||||||
|
}),
|
||||||
|
|
||||||
|
"&:hover": {
|
||||||
|
backgroundColor: light.main,
|
||||||
|
},
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<MDBox mr={1.5} lineHeight={1} color="black">
|
||||||
|
<Icon fontSize="small">{section.iconName}</Icon>
|
||||||
|
</MDBox>
|
||||||
|
{section.label}
|
||||||
|
</MDTypography>
|
||||||
|
</MDBox>
|
||||||
|
)) : null
|
||||||
|
}
|
||||||
|
</MDBox>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
QRecordSidebar.defaultProps = {
|
||||||
|
light: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default QRecordSidebar;
|
@ -1,17 +0,0 @@
|
|||||||
import {useAuth0} from "@auth0/auth0-react";
|
|
||||||
import React from "react";
|
|
||||||
import {Button} from "@mui/material";
|
|
||||||
|
|
||||||
function AuthenticationButton()
|
|
||||||
{
|
|
||||||
const {loginWithRedirect, logout, isAuthenticated} = useAuth0();
|
|
||||||
|
|
||||||
if (isAuthenticated)
|
|
||||||
{
|
|
||||||
return <Button onClick={() => logout({returnTo: window.location.origin})}>Log Out</Button>;
|
|
||||||
}
|
|
||||||
|
|
||||||
return <Button onClick={() => loginWithRedirect()}>Log In</Button>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default AuthenticationButton;
|
|
264
src/qqq/pages/app-home/index.tsx
Normal file
264
src/qqq/pages/app-home/index.tsx
Normal file
@ -0,0 +1,264 @@
|
|||||||
|
/*
|
||||||
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
|
* Copyright (C) 2021-2022. Kingsrook, LLC
|
||||||
|
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||||
|
* contact@kingsrook.com
|
||||||
|
* https://github.com/Kingsrook/
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React, {useEffect, useState} from "react";
|
||||||
|
import {Link, useLocation} from "react-router-dom";
|
||||||
|
|
||||||
|
// Material Dashboard 2 PRO React TS examples components
|
||||||
|
import QClient from "qqq/utils/QClient";
|
||||||
|
import {QInstance} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QInstance";
|
||||||
|
import {QAppMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QAppMetaData";
|
||||||
|
import BaseLayout from "qqq/components/BaseLayout";
|
||||||
|
import MDBox from "components/MDBox";
|
||||||
|
import Grid from "@mui/material/Grid";
|
||||||
|
import MiniStatisticsCard from "examples/Cards/StatisticsCards/MiniStatisticsCard";
|
||||||
|
import {Icon} from "@mui/material";
|
||||||
|
import MDTypography from "components/MDTypography";
|
||||||
|
import Card from "@mui/material/Card";
|
||||||
|
import ComplexStatisticsCard from "examples/Cards/StatisticsCards/ComplexStatisticsCard";
|
||||||
|
import ReportsLineChart from "examples/Charts/LineCharts/ReportsLineChart";
|
||||||
|
import DefaultLineChart from "examples/Charts/LineCharts/DefaultLineChart";
|
||||||
|
import MDBadgeDot from "components/MDBadgeDot";
|
||||||
|
import ReportsBarChart from "examples/Charts/BarCharts/ReportsBarChart";
|
||||||
|
import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData";
|
||||||
|
import {QAppNodeType} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QAppNodeType";
|
||||||
|
import {QProcessMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QProcessMetaData";
|
||||||
|
import DefaultInfoCard from "examples/Cards/InfoCards/DefaultInfoCard";
|
||||||
|
import ProcessLinkCard from "qqq/components/ProcessLinkCard";
|
||||||
|
|
||||||
|
const qController = QClient.getInstance();
|
||||||
|
|
||||||
|
interface Props
|
||||||
|
{
|
||||||
|
app?: QAppMetaData;
|
||||||
|
}
|
||||||
|
|
||||||
|
function AppHome({app}: Props): JSX.Element
|
||||||
|
{
|
||||||
|
const [qInstance, setQInstance] = useState(null as QInstance);
|
||||||
|
const [tables, setTables] = useState([] as QTableMetaData[]);
|
||||||
|
const [processes, setProcesses] = useState([] as QProcessMetaData[]);
|
||||||
|
const [childApps, setChildApps] = useState([] as QAppMetaData[]);
|
||||||
|
|
||||||
|
const location = useLocation();
|
||||||
|
|
||||||
|
useEffect(() =>
|
||||||
|
{
|
||||||
|
(async () =>
|
||||||
|
{
|
||||||
|
const newQInstance = await qController.loadMetaData();
|
||||||
|
setQInstance(newQInstance);
|
||||||
|
})();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() =>
|
||||||
|
{
|
||||||
|
if (!qInstance)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newTables: QTableMetaData[] = [];
|
||||||
|
const newProcesses: QProcessMetaData[] = [];
|
||||||
|
const newChildApps: QAppMetaData[] = [];
|
||||||
|
|
||||||
|
app.children.forEach((child) =>
|
||||||
|
{
|
||||||
|
switch (child.type)
|
||||||
|
{
|
||||||
|
// todo - filter out hidden
|
||||||
|
case QAppNodeType.TABLE:
|
||||||
|
newTables.push(qInstance.tables.get(child.name));
|
||||||
|
break;
|
||||||
|
case QAppNodeType.PROCESS:
|
||||||
|
newProcesses.push(qInstance.processes.get(child.name));
|
||||||
|
break;
|
||||||
|
case QAppNodeType.APP:
|
||||||
|
newChildApps.push(qInstance.apps.get(child.name));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
console.log(`Unexpected child type: ${child.type}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
setTables(newTables);
|
||||||
|
setProcesses(newProcesses);
|
||||||
|
setChildApps(newChildApps);
|
||||||
|
}, [qInstance, location]);
|
||||||
|
|
||||||
|
const reportsBarChartData = {
|
||||||
|
labels: ["M", "T", "W", "T", "F", "S", "S"],
|
||||||
|
datasets: {label: "Sales", data: [50, 20, 10, 22, 50, 10, 40]},
|
||||||
|
};
|
||||||
|
|
||||||
|
interface Types {
|
||||||
|
labels: string[];
|
||||||
|
datasets: {
|
||||||
|
label: string;
|
||||||
|
color: "primary" | "secondary" | "info" | "success" | "warning" | "error" | "light" | "dark";
|
||||||
|
data: number[];
|
||||||
|
}[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const demoLineChartData: Types = {
|
||||||
|
labels: ["Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"],
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: "Facebook Ads",
|
||||||
|
color: "info",
|
||||||
|
data: [50, 100, 200, 190, 400, 350, 500, 450, 700],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Google Ads",
|
||||||
|
color: "dark",
|
||||||
|
data: [10, 30, 40, 120, 150, 220, 280, 250, 280],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<BaseLayout>
|
||||||
|
<MDBox mt={4}>
|
||||||
|
<Grid container spacing={3}>
|
||||||
|
|
||||||
|
<Grid item xs={6} lg={6}>
|
||||||
|
<Grid container spacing={3}>
|
||||||
|
|
||||||
|
<Grid item xs={6} lg={6}>
|
||||||
|
<MDBox mb={3}>
|
||||||
|
<MDBox mb={3}>
|
||||||
|
<ReportsBarChart
|
||||||
|
color="info"
|
||||||
|
title="Packages Shipped"
|
||||||
|
description="Total outbound shipments"
|
||||||
|
date="Updated at 7:04 a.m. ET"
|
||||||
|
chart={reportsBarChartData}
|
||||||
|
/>
|
||||||
|
</MDBox>
|
||||||
|
</MDBox>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Grid item xs={6} lg={6}>
|
||||||
|
<DefaultLineChart
|
||||||
|
title="Revenue"
|
||||||
|
description={(
|
||||||
|
<MDBox display="flex" justifyContent="space-between">
|
||||||
|
<MDBox display="flex" ml={-1}>
|
||||||
|
<MDBadgeDot color="info" size="sm" badgeContent="UPS" />
|
||||||
|
<MDBadgeDot color="dark" size="sm" badgeContent="FedEx" />
|
||||||
|
</MDBox>
|
||||||
|
<MDBox mt={-4} mr={-1} position="absolute" right="1.5rem" />
|
||||||
|
</MDBox>
|
||||||
|
)}
|
||||||
|
chart={demoLineChartData}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Grid item xs={6} lg={6}>
|
||||||
|
{
|
||||||
|
tables.length ? (
|
||||||
|
<MDBox mb={3}>
|
||||||
|
<Card id="basic-info" sx={{overflow: "visible"}}>
|
||||||
|
<MDBox p={3}>
|
||||||
|
<MDTypography variant="h5">Tables</MDTypography>
|
||||||
|
</MDBox>
|
||||||
|
<Grid container spacing={3} padding={3} paddingTop={0}>
|
||||||
|
{tables.map((table) => (
|
||||||
|
<Grid key={table.name} item xs={12} md={12} lg={6}>
|
||||||
|
<Link to={table.name}>
|
||||||
|
<MDBox mb={3}>
|
||||||
|
<MiniStatisticsCard
|
||||||
|
title={{fontWeight: "bold", text: table.label}}
|
||||||
|
count="17,013"
|
||||||
|
percentage={{color: "info", text: "total records"}}
|
||||||
|
icon={{color: "info", component: <Icon>{table.iconName || app.iconName}</Icon>}}
|
||||||
|
direction="right"
|
||||||
|
/>
|
||||||
|
</MDBox>
|
||||||
|
</Link>
|
||||||
|
</Grid>
|
||||||
|
))}
|
||||||
|
</Grid>
|
||||||
|
</Card>
|
||||||
|
</MDBox>
|
||||||
|
) : null
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
processes.length ? (
|
||||||
|
<MDBox mb={3}>
|
||||||
|
<Card id="basic-info" sx={{overflow: "visible"}}>
|
||||||
|
<MDBox p={3}>
|
||||||
|
<MDTypography variant="h5">Processes</MDTypography>
|
||||||
|
</MDBox>
|
||||||
|
<Grid container spacing={3} padding={3} paddingTop={3}>
|
||||||
|
{processes.map((process) => (
|
||||||
|
<Grid key={process.name} item xs={12} md={12} lg={6}>
|
||||||
|
<Link to={process.name}>
|
||||||
|
<ProcessLinkCard
|
||||||
|
icon={process.iconName || app.iconName}
|
||||||
|
title={process.label}
|
||||||
|
/>
|
||||||
|
</Link>
|
||||||
|
</Grid>
|
||||||
|
))}
|
||||||
|
</Grid>
|
||||||
|
</Card>
|
||||||
|
</MDBox>
|
||||||
|
) : null
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
childApps.length ? (
|
||||||
|
<MDBox mb={3}>
|
||||||
|
<Card id="basic-info" sx={{overflow: "visible"}}>
|
||||||
|
<Grid container spacing={3} padding={3} paddingTop={3}>
|
||||||
|
{childApps.map((childApp) => (
|
||||||
|
<Grid key={childApp.name} item xs={12} md={12} lg={6}>
|
||||||
|
<Link to={childApp.name}>
|
||||||
|
<DefaultInfoCard
|
||||||
|
icon={childApp.iconName || app.iconName}
|
||||||
|
title={childApp.label}
|
||||||
|
/>
|
||||||
|
</Link>
|
||||||
|
</Grid>
|
||||||
|
))}
|
||||||
|
</Grid>
|
||||||
|
</Card>
|
||||||
|
</MDBox>
|
||||||
|
) : null
|
||||||
|
}
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
</Grid>
|
||||||
|
</MDBox>
|
||||||
|
</BaseLayout>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
AppHome.defaultProps = {
|
||||||
|
app: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AppHome;
|
@ -1,16 +1,22 @@
|
|||||||
/**
|
/*
|
||||||
=========================================================
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
* Material Dashboard 2 PRO React TS - v1.0.0
|
* Copyright (C) 2021-2022. Kingsrook, LLC
|
||||||
=========================================================
|
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||||
|
* contact@kingsrook.com
|
||||||
* Product Page: https://www.creative-tim.com/product/material-dashboard-2-pro-react-ts
|
* https://github.com/Kingsrook/
|
||||||
* Copyright 2022 Creative Tim (https://www.creative-tim.com)
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
Coded by www.creative-tim.com
|
* 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.
|
||||||
|
*
|
||||||
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// @mui material components
|
// @mui material components
|
||||||
@ -22,15 +28,21 @@ import MDBox from "components/MDBox";
|
|||||||
// Settings page components
|
// Settings page components
|
||||||
import EntityForm from "qqq/components/EntityForm";
|
import EntityForm from "qqq/components/EntityForm";
|
||||||
import BaseLayout from "qqq/components/BaseLayout";
|
import BaseLayout from "qqq/components/BaseLayout";
|
||||||
|
import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData";
|
||||||
|
|
||||||
function EntityCreate(): JSX.Element
|
interface Props
|
||||||
|
{
|
||||||
|
table?: QTableMetaData;
|
||||||
|
}
|
||||||
|
|
||||||
|
function EntityCreate({table}: Props): JSX.Element
|
||||||
{
|
{
|
||||||
return (
|
return (
|
||||||
<BaseLayout>
|
<BaseLayout>
|
||||||
<MDBox mt={4}>
|
<MDBox mt={4}>
|
||||||
<Grid container spacing={3}>
|
<Grid container spacing={3}>
|
||||||
<Grid item xs={12} lg={12}>
|
<Grid item xs={12} lg={12}>
|
||||||
<EntityForm />
|
<EntityForm table={table} />
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
</MDBox>
|
</MDBox>
|
||||||
@ -38,4 +50,8 @@ function EntityCreate(): JSX.Element
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
EntityCreate.defaultProps = {
|
||||||
|
table: null,
|
||||||
|
};
|
||||||
|
|
||||||
export default EntityCreate;
|
export default EntityCreate;
|
||||||
|
@ -1,16 +1,22 @@
|
|||||||
/**
|
/*
|
||||||
=========================================================
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
* Material Dashboard 2 PRO React TS - v1.0.0
|
* Copyright (C) 2021-2022. Kingsrook, LLC
|
||||||
=========================================================
|
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||||
|
* contact@kingsrook.com
|
||||||
* Product Page: https://www.creative-tim.com/product/material-dashboard-2-pro-react-ts
|
* https://github.com/Kingsrook/
|
||||||
* Copyright 2022 Creative Tim (https://www.creative-tim.com)
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
Coded by www.creative-tim.com
|
* 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.
|
||||||
|
*
|
||||||
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// @mui material components
|
// @mui material components
|
||||||
@ -23,8 +29,15 @@ import MDBox from "components/MDBox";
|
|||||||
import BaseLayout from "qqq/components/BaseLayout";
|
import BaseLayout from "qqq/components/BaseLayout";
|
||||||
import {useParams} from "react-router-dom";
|
import {useParams} from "react-router-dom";
|
||||||
import EntityForm from "qqq/components/EntityForm";
|
import EntityForm from "qqq/components/EntityForm";
|
||||||
|
import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData";
|
||||||
|
import EntityList from "qqq/pages/entity-list";
|
||||||
|
|
||||||
function EntityEdit(): JSX.Element
|
interface Props
|
||||||
|
{
|
||||||
|
table?: QTableMetaData;
|
||||||
|
}
|
||||||
|
|
||||||
|
function EntityEdit({table}: Props): JSX.Element
|
||||||
{
|
{
|
||||||
const {id} = useParams();
|
const {id} = useParams();
|
||||||
|
|
||||||
@ -36,7 +49,7 @@ function EntityEdit(): JSX.Element
|
|||||||
<MDBox mb={3}>
|
<MDBox mb={3}>
|
||||||
<Grid container spacing={3}>
|
<Grid container spacing={3}>
|
||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
<EntityForm id={id} />
|
<EntityForm table={table} id={id} />
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
</MDBox>
|
</MDBox>
|
||||||
@ -47,4 +60,8 @@ function EntityEdit(): JSX.Element
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
EntityEdit.defaultProps = {
|
||||||
|
table: null,
|
||||||
|
};
|
||||||
|
|
||||||
export default EntityEdit;
|
export default EntityEdit;
|
||||||
|
@ -1,26 +1,39 @@
|
|||||||
/**
|
/*
|
||||||
=========================================================
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
* Material Dashboard 2 PRO React TS - v1.0.0
|
* Copyright (C) 2021-2022. Kingsrook, LLC
|
||||||
=========================================================
|
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||||
* Product Page: https://www.creative-tim.com/product/material-dashboard-2-pro-react-ts
|
* contact@kingsrook.com
|
||||||
* Copyright 2022 Creative Tim (https://www.creative-tim.com)
|
* https://github.com/Kingsrook/
|
||||||
Coded by www.creative-tim.com
|
*
|
||||||
=========================================================
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* The above copyright notice and this permission notice shall be included in all copies or
|
* it under the terms of the GNU Affero General Public License as
|
||||||
* substantial portions of the Software.
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
/* eslint-disable react/no-unstable-nested-components */
|
|
||||||
|
|
||||||
import React, {useEffect, useReducer, useState} from "react";
|
import React, {
|
||||||
import {useParams, useSearchParams} from "react-router-dom";
|
SyntheticEvent,
|
||||||
|
useCallback,
|
||||||
|
useEffect, useReducer, useRef, useState,
|
||||||
|
} from "react";
|
||||||
|
import {
|
||||||
|
Link, useNavigate, useParams, useSearchParams,
|
||||||
|
} from "react-router-dom";
|
||||||
|
|
||||||
// @mui material components
|
// @mui material components
|
||||||
import Card from "@mui/material/Card";
|
import Card from "@mui/material/Card";
|
||||||
import Icon from "@mui/material/Icon";
|
import Icon from "@mui/material/Icon";
|
||||||
import Menu from "@mui/material/Menu";
|
import Menu from "@mui/material/Menu";
|
||||||
import MenuItem from "@mui/material/MenuItem";
|
import MenuItem from "@mui/material/MenuItem";
|
||||||
import Link from "@mui/material/Link";
|
import {Alert, Pagination, TablePagination} from "@mui/material";
|
||||||
import {Alert, TablePagination} from "@mui/material";
|
|
||||||
import {
|
import {
|
||||||
DataGridPro,
|
DataGridPro,
|
||||||
GridCallbackDetails,
|
GridCallbackDetails,
|
||||||
@ -40,6 +53,7 @@ import {
|
|||||||
GridToolbarExportContainer,
|
GridToolbarExportContainer,
|
||||||
GridToolbarFilterButton,
|
GridToolbarFilterButton,
|
||||||
GridExportMenuItemProps,
|
GridExportMenuItemProps,
|
||||||
|
MuiEvent,
|
||||||
} from "@mui/x-data-grid-pro";
|
} from "@mui/x-data-grid-pro";
|
||||||
|
|
||||||
// Material Dashboard 2 PRO React TS components
|
// Material Dashboard 2 PRO React TS components
|
||||||
@ -64,11 +78,13 @@ import Footer from "../../components/Footer";
|
|||||||
import QProcessUtils from "../../utils/QProcessUtils";
|
import QProcessUtils from "../../utils/QProcessUtils";
|
||||||
|
|
||||||
import "./styles.css";
|
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_VISIBILITY_LOCAL_STORAGE_KEY_ROOT = "qqq.columnVisibility";
|
||||||
const COLUMN_SORT_LOCAL_STORAGE_KEY_ROOT = "qqq.columnSort";
|
const COLUMN_SORT_LOCAL_STORAGE_KEY_ROOT = "qqq.columnSort";
|
||||||
|
|
||||||
// Declaring props types for DefaultCell
|
|
||||||
interface Props
|
interface Props
|
||||||
{
|
{
|
||||||
table?: QTableMetaData;
|
table?: QTableMetaData;
|
||||||
@ -87,6 +103,7 @@ function EntityList({table}: Props): JSX.Element
|
|||||||
const columnVisibilityLocalStorageKey = `${COLUMN_VISIBILITY_LOCAL_STORAGE_KEY_ROOT}.${tableName}`;
|
const columnVisibilityLocalStorageKey = `${COLUMN_VISIBILITY_LOCAL_STORAGE_KEY_ROOT}.${tableName}`;
|
||||||
let defaultSort = [] as GridSortItem[];
|
let defaultSort = [] as GridSortItem[];
|
||||||
let defaultVisibility = {};
|
let defaultVisibility = {};
|
||||||
|
const qController = QClient.getInstance();
|
||||||
|
|
||||||
if (localStorage.getItem(sortLocalStorageKey))
|
if (localStorage.getItem(sortLocalStorageKey))
|
||||||
{
|
{
|
||||||
@ -97,7 +114,6 @@ function EntityList({table}: Props): JSX.Element
|
|||||||
defaultVisibility = JSON.parse(localStorage.getItem(columnVisibilityLocalStorageKey));
|
defaultVisibility = JSON.parse(localStorage.getItem(columnVisibilityLocalStorageKey));
|
||||||
}
|
}
|
||||||
|
|
||||||
const [buttonText, setButtonText] = useState("");
|
|
||||||
const [tableState, setTableState] = useState("");
|
const [tableState, setTableState] = useState("");
|
||||||
const [tableMetaData, setTableMetaData] = useState(null as QTableMetaData);
|
const [tableMetaData, setTableMetaData] = useState(null as QTableMetaData);
|
||||||
const [, setFiltersMenu] = useState(null);
|
const [, setFiltersMenu] = useState(null);
|
||||||
@ -116,6 +132,10 @@ function EntityList({table}: Props): JSX.Element
|
|||||||
const [tableLabel, setTableLabel] = useState("");
|
const [tableLabel, setTableLabel] = useState("");
|
||||||
const [columnSortModel, setColumnSortModel] = useState(defaultSort);
|
const [columnSortModel, setColumnSortModel] = useState(defaultSort);
|
||||||
const [columnVisibilityModel, setColumnVisibilityModel] = useState(defaultVisibility);
|
const [columnVisibilityModel, setColumnVisibilityModel] = useState(defaultVisibility);
|
||||||
|
const [gridMouseDownX, setGridMouseDownX] = useState(0);
|
||||||
|
const [gridMouseDownY, setGridMouseDownY] = useState(0);
|
||||||
|
const [pinnedColumns, setPinnedColumns] = useState({left: ["__check__", "id"]});
|
||||||
|
const instance = useRef({timer: null});
|
||||||
|
|
||||||
const [, forceUpdate] = useReducer((x) => x + 1, 0);
|
const [, forceUpdate] = useReducer((x) => x + 1, 0);
|
||||||
|
|
||||||
@ -191,29 +211,30 @@ function EntityList({table}: Props): JSX.Element
|
|||||||
|
|
||||||
const updateTable = () =>
|
const updateTable = () =>
|
||||||
{
|
{
|
||||||
|
setRows([]);
|
||||||
(async () =>
|
(async () =>
|
||||||
{
|
{
|
||||||
const newTableMetaData = await QClient.loadTableMetaData(tableName);
|
const tableMetaData = await qController.loadTableMetaData(tableName);
|
||||||
setTableMetaData(newTableMetaData);
|
setTableMetaData(tableMetaData);
|
||||||
if (columnSortModel.length === 0)
|
if (columnSortModel.length === 0)
|
||||||
{
|
{
|
||||||
columnSortModel.push({
|
columnSortModel.push({
|
||||||
field: newTableMetaData.primaryKeyField,
|
field: tableMetaData.primaryKeyField,
|
||||||
sort: "desc",
|
sort: "desc",
|
||||||
});
|
});
|
||||||
setColumnSortModel(columnSortModel);
|
setColumnSortModel(columnSortModel);
|
||||||
}
|
}
|
||||||
|
setPinnedColumns({left: ["__check__", tableMetaData.primaryKeyField]});
|
||||||
|
|
||||||
const qFilter = buildQFilter();
|
const qFilter = buildQFilter();
|
||||||
|
|
||||||
const count = await QClient.count(tableName, qFilter);
|
const count = await qController.count(tableName, qFilter);
|
||||||
setTotalRecords(count);
|
setTotalRecords(count);
|
||||||
setButtonText(`new ${newTableMetaData.label}`);
|
setTableLabel(tableMetaData.label);
|
||||||
setTableLabel(newTableMetaData.label);
|
|
||||||
|
|
||||||
const columns = [] as GridColDef[];
|
const columns = [] as GridColDef[];
|
||||||
|
|
||||||
const results = await QClient.query(
|
const results = await qController.query(
|
||||||
tableName,
|
tableName,
|
||||||
qFilter,
|
qFilter,
|
||||||
rowsPerPage,
|
rowsPerPage,
|
||||||
@ -232,47 +253,75 @@ function EntityList({table}: Props): JSX.Element
|
|||||||
throw error;
|
throw error;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const fields = [...tableMetaData.fields.values()];
|
||||||
|
|
||||||
const rows = [] as any[];
|
const rows = [] as any[];
|
||||||
results.forEach((record) =>
|
results.forEach((record) =>
|
||||||
{
|
{
|
||||||
rows.push(Object.fromEntries(record.values.entries()));
|
const row: any = {};
|
||||||
|
fields.forEach((field) =>
|
||||||
|
{
|
||||||
|
row[field.name] = QValueUtils.getDisplayValue(field, record);
|
||||||
});
|
});
|
||||||
|
|
||||||
const sortedKeys = [...newTableMetaData.fields.keys()].sort();
|
rows.push(row);
|
||||||
|
});
|
||||||
|
|
||||||
|
const sortedKeys: string[] = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < tableMetaData.sections.length; i++)
|
||||||
|
{
|
||||||
|
const section = tableMetaData.sections[i];
|
||||||
|
for (let j = 0; j < section.fieldNames.length; j++)
|
||||||
|
{
|
||||||
|
sortedKeys.push(section.fieldNames[j]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
sortedKeys.forEach((key) =>
|
sortedKeys.forEach((key) =>
|
||||||
{
|
{
|
||||||
const field = newTableMetaData.fields.get(key);
|
const field = tableMetaData.fields.get(key);
|
||||||
|
|
||||||
let columnType = "string";
|
let columnType = "string";
|
||||||
|
let columnWidth = 200;
|
||||||
switch (field.type)
|
switch (field.type)
|
||||||
{
|
{
|
||||||
case QFieldType.DECIMAL:
|
case QFieldType.DECIMAL:
|
||||||
case QFieldType.INTEGER:
|
case QFieldType.INTEGER:
|
||||||
columnType = "number";
|
columnType = "number";
|
||||||
|
columnWidth = 100;
|
||||||
|
|
||||||
|
if (key === tableMetaData.primaryKeyField && field.label.length < 3)
|
||||||
|
{
|
||||||
|
columnWidth = 75;
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case QFieldType.DATE:
|
case QFieldType.DATE:
|
||||||
columnType = "date";
|
columnType = "date";
|
||||||
|
columnWidth = 100;
|
||||||
break;
|
break;
|
||||||
case QFieldType.DATE_TIME:
|
case QFieldType.DATE_TIME:
|
||||||
columnType = "dateTime";
|
columnType = "dateTime";
|
||||||
|
columnWidth = 200;
|
||||||
break;
|
break;
|
||||||
case QFieldType.BOOLEAN:
|
case QFieldType.BOOLEAN:
|
||||||
columnType = "boolean";
|
columnType = "boolean";
|
||||||
|
columnWidth = 75;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
// noop
|
// noop - leave as string
|
||||||
}
|
}
|
||||||
|
|
||||||
const column = {
|
const column = {
|
||||||
field: field.name,
|
field: field.name,
|
||||||
type: columnType,
|
type: columnType,
|
||||||
headerName: field.label,
|
headerName: field.label,
|
||||||
width: 200,
|
width: columnWidth,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (key === newTableMetaData.primaryKeyField)
|
if (key === tableMetaData.primaryKeyField)
|
||||||
{
|
{
|
||||||
column.width = 75;
|
|
||||||
columns.splice(0, 0, column);
|
columns.splice(0, 0, column);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -298,11 +347,49 @@ function EntityList({table}: Props): JSX.Element
|
|||||||
setRowsPerPage(size);
|
setRowsPerPage(size);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRowClick = (params: GridRowParams) =>
|
const navigate = useNavigate();
|
||||||
|
const handleRowClick = (params: GridRowParams, event: MuiEvent<React.MouseEvent>, details: GridCallbackDetails) =>
|
||||||
{
|
{
|
||||||
document.location.href = `/${tableName}/${params.id}`;
|
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// strategy for when to trigger or not trigger a row click: //
|
||||||
|
// To avoid a drag-event that highlighted text in a cell: //
|
||||||
|
// - we capture the x & y upon mouse-down - then compare them in this method (which fires when the mouse is up) //
|
||||||
|
// if they are more than 5 pixels away from the mouse-down, then assume it's a drag, not a click. //
|
||||||
|
// - avoid clicking the row upon double-click, by setting a 500ms timer here - and in the onDoubleClick handler, //
|
||||||
|
// cancelling the timer. //
|
||||||
|
// - also avoid a click, then click-again-and-start-dragging, by always cancelling the timer in mouse-down. //
|
||||||
|
// All in, these seem to have good results - the only downside being the half-second delay... //
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
navigate(`${params.id}`);
|
||||||
|
/*
|
||||||
|
const diff = Math.max(Math.abs(event.clientX - gridMouseDownX), Math.abs(event.clientY - gridMouseDownY));
|
||||||
|
if (diff < 5)
|
||||||
|
{
|
||||||
|
clearTimeout(instance.current.timer);
|
||||||
|
instance.current.timer = setTimeout(() =>
|
||||||
|
{
|
||||||
|
navigate(`${params.id}`);
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
console.log(`row-click mouse-up happened ${diff} x or y pixels away from the mouse-down - so not considering it a click.`);
|
||||||
|
}
|
||||||
|
*/
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleGridMouseDown = useCallback((event: any) =>
|
||||||
|
{
|
||||||
|
setGridMouseDownX(event.clientX);
|
||||||
|
setGridMouseDownY(event.clientY);
|
||||||
|
clearTimeout(instance.current.timer);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleGridDoubleClick = useCallback((event: any) =>
|
||||||
|
{
|
||||||
|
clearTimeout(instance.current.timer);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const handleFilterChange = (filterModel: GridFilterModel) =>
|
const handleFilterChange = (filterModel: GridFilterModel) =>
|
||||||
{
|
{
|
||||||
setFilterModel(filterModel);
|
setFilterModel(filterModel);
|
||||||
@ -362,7 +449,7 @@ function EntityList({table}: Props): JSX.Element
|
|||||||
setTableState(tableName);
|
setTableState(tableName);
|
||||||
setFilterModel(null);
|
setFilterModel(null);
|
||||||
setFiltersMenu(null);
|
setFiltersMenu(null);
|
||||||
const metaData = await QClient.loadMetaData();
|
const metaData = await qController.loadMetaData();
|
||||||
|
|
||||||
setTableProcesses(QProcessUtils.getProcessesForTable(metaData, tableName));
|
setTableProcesses(QProcessUtils.getProcessesForTable(metaData, tableName));
|
||||||
|
|
||||||
@ -376,6 +463,8 @@ function EntityList({table}: Props): JSX.Element
|
|||||||
format: string;
|
format: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// todo - figure out what's up here...
|
||||||
|
// eslint-disable-next-line react/no-unstable-nested-components
|
||||||
function ExportMenuItem(props: QExportMenuItemProps)
|
function ExportMenuItem(props: QExportMenuItemProps)
|
||||||
{
|
{
|
||||||
const {format, hideMenu} = props;
|
const {format, hideMenu} = props;
|
||||||
@ -475,7 +564,7 @@ function EntityList({table}: Props): JSX.Element
|
|||||||
|
|
||||||
const bulkLoadClicked = () =>
|
const bulkLoadClicked = () =>
|
||||||
{
|
{
|
||||||
document.location.href = `/processes/${tableName}.bulkInsert`;
|
navigate(`${tableName}.bulkInsert`);
|
||||||
};
|
};
|
||||||
|
|
||||||
const bulkEditClicked = () =>
|
const bulkEditClicked = () =>
|
||||||
@ -485,7 +574,7 @@ function EntityList({table}: Props): JSX.Element
|
|||||||
setAlertContent("No records were selected to Bulk Edit.");
|
setAlertContent("No records were selected to Bulk Edit.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
document.location.href = `/processes/${tableName}.bulkEdit${getRecordsQueryString()}`;
|
navigate(`${tableName}.bulkEdit${getRecordsQueryString()}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
const bulkDeleteClicked = () =>
|
const bulkDeleteClicked = () =>
|
||||||
@ -495,12 +584,21 @@ function EntityList({table}: Props): JSX.Element
|
|||||||
setAlertContent("No records were selected to Bulk Delete.");
|
setAlertContent("No records were selected to Bulk Delete.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
document.location.href = `/processes/${tableName}.bulkDelete${getRecordsQueryString()}`;
|
navigate(`${tableName}.bulkDelete${getRecordsQueryString()}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
const processClicked = (process: QProcessMetaData) =>
|
||||||
|
{
|
||||||
|
// todo - let the process specify that it needs initial rows - err if none selected.
|
||||||
|
// alternatively, let a process itself have an initial screen to select rows...
|
||||||
|
navigate(`${process.name}${getRecordsQueryString()}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const defaultLabelDisplayedRows = ({from, to, count}) => `${from.toLocaleString()}–${to.toLocaleString()} of ${count !== -1 ? count.toLocaleString() : `more than ${to.toLocaleString()}`}`;
|
const defaultLabelDisplayedRows = ({from, to, count}) => `Showing ${from.toLocaleString()} to ${to.toLocaleString()} of ${count !== -1 ? `${count.toLocaleString()} records` : `more than ${to.toLocaleString()} records`}`;
|
||||||
|
|
||||||
|
// todo - figure out what's up here...
|
||||||
|
// eslint-disable-next-line react/no-unstable-nested-components
|
||||||
function CustomPagination()
|
function CustomPagination()
|
||||||
{
|
{
|
||||||
return (
|
return (
|
||||||
@ -517,21 +615,19 @@ function EntityList({table}: Props): JSX.Element
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// todo - figure out what's up here...
|
||||||
|
// eslint-disable-next-line react/no-unstable-nested-components
|
||||||
|
function Loading()
|
||||||
|
{
|
||||||
|
return (
|
||||||
|
<LinearProgress color="info" />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// todo - figure out what's up here...
|
||||||
|
// eslint-disable-next-line react/no-unstable-nested-components
|
||||||
function CustomToolbar()
|
function CustomToolbar()
|
||||||
{
|
{
|
||||||
const [bulkActionsMenuAnchor, setBulkActionsMenuAnchor] = useState(null as HTMLElement);
|
|
||||||
const bulkActionsMenuOpen = Boolean(bulkActionsMenuAnchor);
|
|
||||||
|
|
||||||
const openBulkActionsMenu = (event: React.MouseEvent<HTMLElement>) =>
|
|
||||||
{
|
|
||||||
setBulkActionsMenuAnchor(event.currentTarget);
|
|
||||||
};
|
|
||||||
|
|
||||||
const closeBulkActionsMenu = () =>
|
|
||||||
{
|
|
||||||
setBulkActionsMenuAnchor(null);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<GridToolbarContainer>
|
<GridToolbarContainer>
|
||||||
<div>
|
<div>
|
||||||
@ -550,29 +646,6 @@ function EntityList({table}: Props): JSX.Element
|
|||||||
<ExportMenuItem format="csv" />
|
<ExportMenuItem format="csv" />
|
||||||
<ExportMenuItem format="xlsx" />
|
<ExportMenuItem format="xlsx" />
|
||||||
</GridToolbarExportContainer>
|
</GridToolbarExportContainer>
|
||||||
<div>
|
|
||||||
<Button
|
|
||||||
id="bulk-actions-button"
|
|
||||||
onClick={openBulkActionsMenu}
|
|
||||||
aria-controls={bulkActionsMenuOpen ? "basic-menu" : undefined}
|
|
||||||
aria-haspopup="true"
|
|
||||||
aria-expanded={bulkActionsMenuOpen ? "true" : undefined}
|
|
||||||
startIcon={<Icon>table_rows</Icon>}
|
|
||||||
>
|
|
||||||
Bulk Actions
|
|
||||||
</Button>
|
|
||||||
<Menu
|
|
||||||
id="bulk-actions-menu"
|
|
||||||
open={bulkActionsMenuOpen}
|
|
||||||
anchorEl={bulkActionsMenuAnchor}
|
|
||||||
onClose={closeBulkActionsMenu}
|
|
||||||
MenuListProps={{"aria-labelledby": "bulk-actions-button"}}
|
|
||||||
>
|
|
||||||
<MenuItem onClick={bulkLoadClicked}>Bulk Load</MenuItem>
|
|
||||||
<MenuItem onClick={bulkEditClicked}>Bulk Edit</MenuItem>
|
|
||||||
<MenuItem onClick={bulkDeleteClicked}>Bulk Delete</MenuItem>
|
|
||||||
</Menu>
|
|
||||||
</div>
|
|
||||||
<div>
|
<div>
|
||||||
{
|
{
|
||||||
selectFullFilterState === "checked" && (
|
selectFullFilterState === "checked" && (
|
||||||
@ -620,29 +693,38 @@ function EntityList({table}: Props): JSX.Element
|
|||||||
anchorEl={actionsMenu}
|
anchorEl={actionsMenu}
|
||||||
anchorOrigin={{
|
anchorOrigin={{
|
||||||
vertical: "bottom",
|
vertical: "bottom",
|
||||||
horizontal: "left",
|
horizontal: "right",
|
||||||
}}
|
}}
|
||||||
transformOrigin={{
|
transformOrigin={{
|
||||||
vertical: "top",
|
vertical: "top",
|
||||||
horizontal: "left",
|
horizontal: "right",
|
||||||
}}
|
}}
|
||||||
open={Boolean(actionsMenu)}
|
open={Boolean(actionsMenu)}
|
||||||
onClose={closeActionsMenu}
|
onClose={closeActionsMenu}
|
||||||
keepMounted
|
keepMounted
|
||||||
>
|
>
|
||||||
|
<MenuItem onClick={bulkLoadClicked}>Bulk Load</MenuItem>
|
||||||
|
<MenuItem onClick={bulkEditClicked}>Bulk Edit</MenuItem>
|
||||||
|
<MenuItem onClick={bulkDeleteClicked}>Bulk Delete</MenuItem>
|
||||||
|
{tableProcesses.length > 0 && <MenuItem divider />}
|
||||||
{tableProcesses.map((process) => (
|
{tableProcesses.map((process) => (
|
||||||
<MenuItem key={process.name}>
|
<MenuItem key={process.name} onClick={() => processClicked(process)}>{process.label}</MenuItem>
|
||||||
<Link href={`/processes/${process.name}${getRecordsQueryString()}`}>{process.label}</Link>
|
|
||||||
</MenuItem>
|
|
||||||
))}
|
))}
|
||||||
</Menu>
|
</Menu>
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() =>
|
useEffect(() =>
|
||||||
{
|
{
|
||||||
|
setLoading(true);
|
||||||
updateTable();
|
updateTable();
|
||||||
}, [pageNumber, rowsPerPage, tableState, columnSortModel, filterModel]);
|
}, [pageNumber, rowsPerPage, tableState, columnSortModel, filterModel]);
|
||||||
|
|
||||||
|
useEffect(() =>
|
||||||
|
{
|
||||||
|
document.documentElement.scrollTop = 0;
|
||||||
|
document.scrollingElement.scrollTop = 0;
|
||||||
|
}, [pageNumber, rowsPerPage]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DashboardLayout>
|
<DashboardLayout>
|
||||||
<Navbar />
|
<Navbar />
|
||||||
@ -667,39 +749,25 @@ function EntityList({table}: Props): JSX.Element
|
|||||||
<MDAlert color="success" dismissible>
|
<MDAlert color="success" dismissible>
|
||||||
{`${tableLabel} successfully deleted`}
|
{`${tableLabel} successfully deleted`}
|
||||||
</MDAlert>
|
</MDAlert>
|
||||||
) : ("")
|
) : null
|
||||||
}
|
}
|
||||||
<MDBox display="flex" justifyContent="space-between" alignItems="flex-start" mb={2}>
|
<MDBox display="flex" justifyContent="flex-end" alignItems="flex-start" mb={2}>
|
||||||
{buttonText ? (
|
|
||||||
<Link href={`/${tableName}/create`}>
|
|
||||||
<MDButton variant="gradient" color="info">
|
|
||||||
{
|
|
||||||
buttonText
|
|
||||||
}
|
|
||||||
</MDButton>
|
|
||||||
</Link>
|
|
||||||
) : (
|
|
||||||
""
|
|
||||||
)}
|
|
||||||
|
|
||||||
<MDBox display="flex">
|
<MDBox display="flex" width="150px">
|
||||||
{tableProcesses.length > 0 && (
|
<QActionsMenuButton isOpen={actionsMenu} onClickHandler={openActionsMenu} />
|
||||||
<MDButton
|
|
||||||
variant={actionsMenu ? "contained" : "outlined"}
|
|
||||||
color="dark"
|
|
||||||
onClick={openActionsMenu}
|
|
||||||
>
|
|
||||||
actions
|
|
||||||
<Icon>keyboard_arrow_down</Icon>
|
|
||||||
</MDButton>
|
|
||||||
)}
|
|
||||||
{renderActionsMenu}
|
{renderActionsMenu}
|
||||||
</MDBox>
|
</MDBox>
|
||||||
|
|
||||||
|
<QCreateNewButton />
|
||||||
|
|
||||||
</MDBox>
|
</MDBox>
|
||||||
<Card>
|
<Card>
|
||||||
|
{/* with these turned on, the toolbar & pagination controls become very flaky...
|
||||||
|
onMouseDown={(e) => handleGridMouseDown(e)} onDoubleClick={(e) => handleGridDoubleClick(e)} */}
|
||||||
<MDBox height="100%">
|
<MDBox height="100%">
|
||||||
<DataGridPro
|
<DataGridPro
|
||||||
components={{Toolbar: CustomToolbar, Pagination: CustomPagination}}
|
components={{Toolbar: CustomToolbar, Pagination: CustomPagination, LoadingOverlay: Loading}}
|
||||||
|
initialState={{pinnedColumns: pinnedColumns}}
|
||||||
pagination
|
pagination
|
||||||
paginationMode="server"
|
paginationMode="server"
|
||||||
sortingMode="server"
|
sortingMode="server"
|
||||||
@ -714,7 +782,7 @@ function EntityList({table}: Props): JSX.Element
|
|||||||
rowCount={totalRecords}
|
rowCount={totalRecords}
|
||||||
onPageSizeChange={handleRowsPerPageChange}
|
onPageSizeChange={handleRowsPerPageChange}
|
||||||
onRowClick={handleRowClick}
|
onRowClick={handleRowClick}
|
||||||
density="compact"
|
density="standard"
|
||||||
loading={loading}
|
loading={loading}
|
||||||
onFilterModelChange={handleFilterChange}
|
onFilterModelChange={handleFilterChange}
|
||||||
columnVisibilityModel={columnVisibilityModel}
|
columnVisibilityModel={columnVisibilityModel}
|
||||||
|
@ -1,23 +1,31 @@
|
|||||||
/**
|
/*
|
||||||
=========================================================
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
* Material Dashboard 2 PRO React TS - v1.0.0
|
* Copyright (C) 2021-2022. Kingsrook, LLC
|
||||||
=========================================================
|
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||||
* Product Page: https://www.creative-tim.com/product/material-dashboard-2-pro-react-ts
|
* contact@kingsrook.com
|
||||||
* Copyright 2022 Creative Tim (https://www.creative-tim.com)
|
* https://github.com/Kingsrook/
|
||||||
Coded by www.creative-tim.com
|
*
|
||||||
=========================================================
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* The above copyright notice and this permission notice shall be included in all copies or
|
* it under the terms of the GNU Affero General Public License as
|
||||||
* substantial portions of the Software.
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// react components
|
// react components
|
||||||
import {useParams, useSearchParams} from "react-router-dom";
|
import {useLocation, useNavigate, useSearchParams} from "react-router-dom";
|
||||||
import React, {useReducer, useState} from "react";
|
import React, {useReducer, useState} from "react";
|
||||||
|
|
||||||
// @material-ui core components
|
// @material-ui core components
|
||||||
import Card from "@mui/material/Card";
|
import Card from "@mui/material/Card";
|
||||||
import Grid from "@mui/material/Grid";
|
import Grid from "@mui/material/Grid";
|
||||||
import Link from "@mui/material/Link";
|
|
||||||
import Dialog from "@mui/material/Dialog";
|
import Dialog from "@mui/material/Dialog";
|
||||||
import DialogTitle from "@mui/material/DialogTitle";
|
import DialogTitle from "@mui/material/DialogTitle";
|
||||||
import DialogContent from "@mui/material/DialogContent";
|
import DialogContent from "@mui/material/DialogContent";
|
||||||
@ -26,7 +34,6 @@ import DialogActions from "@mui/material/DialogActions";
|
|||||||
import Button from "@mui/material/Button";
|
import Button from "@mui/material/Button";
|
||||||
|
|
||||||
// qqq imports
|
// qqq imports
|
||||||
import {QController} from "@kingsrook/qqq-frontend-core/lib/controllers/QController";
|
|
||||||
import {QProcessMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QProcessMetaData";
|
import {QProcessMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QProcessMetaData";
|
||||||
|
|
||||||
// Material Dashboard 2 PRO React TS components
|
// Material Dashboard 2 PRO React TS components
|
||||||
@ -34,30 +41,50 @@ import MDBox from "components/MDBox";
|
|||||||
import MDTypography from "components/MDTypography";
|
import MDTypography from "components/MDTypography";
|
||||||
import Menu from "@mui/material/Menu";
|
import Menu from "@mui/material/Menu";
|
||||||
import MenuItem from "@mui/material/MenuItem";
|
import MenuItem from "@mui/material/MenuItem";
|
||||||
import Icon from "@mui/material/Icon";
|
|
||||||
import MDAlert from "components/MDAlert";
|
import MDAlert from "components/MDAlert";
|
||||||
import MDButton from "../../../../../components/MDButton";
|
|
||||||
import QProcessUtils from "../../../../utils/QProcessUtils";
|
import QProcessUtils from "../../../../utils/QProcessUtils";
|
||||||
|
import QClient from "qqq/utils/QClient";
|
||||||
|
import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData";
|
||||||
|
import {QActionsMenuButton, QDeleteButton, QEditButton} from "qqq/components/QButtons";
|
||||||
|
import QValueUtils from "qqq/utils/QValueUtils";
|
||||||
|
import {QRecord} from "@kingsrook/qqq-frontend-core/lib/model/QRecord";
|
||||||
|
import Icon from "@mui/material/Icon";
|
||||||
|
import Avatar from "@mui/material/Avatar";
|
||||||
|
import QRecordSidebar from "qqq/components/QRecordSidebar";
|
||||||
|
import QTableUtils from "qqq/utils/QTableUtils";
|
||||||
|
import colors from "assets/theme/base/colors";
|
||||||
|
import {QSection} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QSection";
|
||||||
|
|
||||||
const qController = new QController("");
|
const qController = QClient.getInstance();
|
||||||
|
|
||||||
// Declaring props types for ViewForm
|
// Declaring props types for ViewForm
|
||||||
interface Props
|
interface Props
|
||||||
{
|
{
|
||||||
id: string;
|
id: string;
|
||||||
|
table?: QTableMetaData;
|
||||||
}
|
}
|
||||||
|
|
||||||
function ViewContents({id}: Props): JSX.Element
|
function ViewContents({id, table}: Props): JSX.Element
|
||||||
{
|
{
|
||||||
const {tableName} = useParams();
|
const location = useLocation();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const pathParts = location.pathname.split("/");
|
||||||
|
const tableName = table ? table.name : pathParts[pathParts.length - 2];
|
||||||
|
|
||||||
const [asyncLoadInited, setAsyncLoadInited] = useState(false);
|
const [asyncLoadInited, setAsyncLoadInited] = useState(false);
|
||||||
const [nameValues, setNameValues] = useState([] as JSX.Element[]);
|
const [nameValues, setNameValues] = useState([] as JSX.Element[]);
|
||||||
const [open, setOpen] = useState(false);
|
const [sectionFieldElements, setSectionFieldElements] = useState(null as Map<string, JSX.Element[]>);
|
||||||
|
const [deleteConfirmationOpen, setDeleteConfirmationOpen] = useState(false);
|
||||||
const [tableMetaData, setTableMetaData] = useState(null);
|
const [tableMetaData, setTableMetaData] = useState(null);
|
||||||
|
const [record, setRecord] = useState(null as QRecord);
|
||||||
|
const [tableSections, setTableSections] = useState([] as QSection[]);
|
||||||
|
const [t1SectionName, setT1SectionName] = useState(null as string);
|
||||||
|
const [t1SectionElement, setT1SectionElement] = useState(null as JSX.Element);
|
||||||
|
const [nonT1TableSections, setNonT1TableSections] = useState([] as QSection[]);
|
||||||
const [tableProcesses, setTableProcesses] = useState([] as QProcessMetaData[]);
|
const [tableProcesses, setTableProcesses] = useState([] as QProcessMetaData[]);
|
||||||
const [actionsMenu, setActionsMenu] = useState(null);
|
const [actionsMenu, setActionsMenu] = useState(null);
|
||||||
const [searchParams, setSearchParams] = useSearchParams();
|
const [searchParams] = useSearchParams();
|
||||||
const [, forceUpdate] = useReducer((x) => x + 1, 0);
|
const [, forceUpdate] = useReducer((x) => x + 1, 0);
|
||||||
|
|
||||||
const openActionsMenu = (event: any) => setActionsMenu(event.currentTarget);
|
const openActionsMenu = (event: any) => setActionsMenu(event.currentTarget);
|
||||||
@ -69,60 +96,83 @@ function ViewContents({id}: Props): JSX.Element
|
|||||||
|
|
||||||
(async () =>
|
(async () =>
|
||||||
{
|
{
|
||||||
const tableMetaData = await qController.loadTableMetaData(tableName);
|
//////////////////////////////////////////
|
||||||
|
// load the table meta-data (if needed) //
|
||||||
|
//////////////////////////////////////////
|
||||||
|
const tableMetaData = table || await qController.loadTableMetaData(tableName);
|
||||||
setTableMetaData(tableMetaData);
|
setTableMetaData(tableMetaData);
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////
|
||||||
|
// load top-level meta-data (e.g., to find processes for table) //
|
||||||
|
//////////////////////////////////////////////////////////////////
|
||||||
const metaData = await qController.loadMetaData();
|
const metaData = await qController.loadMetaData();
|
||||||
setTableProcesses(QProcessUtils.getProcessesForTable(metaData, tableName));
|
setTableProcesses(QProcessUtils.getProcessesForTable(metaData, tableName));
|
||||||
|
|
||||||
const foundRecord = await qController.get(tableName, id);
|
/////////////////////
|
||||||
|
// load the record //
|
||||||
|
/////////////////////
|
||||||
|
const record = await qController.get(tableName, id);
|
||||||
|
setRecord(record);
|
||||||
|
|
||||||
nameValues.push(
|
/////////////////////////////////////////////////
|
||||||
<MDBox key={tableMetaData.primaryKeyField} display="flex" py={1} pr={2}>
|
// define the sections, e.g., for the left-bar //
|
||||||
<MDTypography variant="button" fontWeight="bold" textTransform="capitalize">
|
/////////////////////////////////////////////////
|
||||||
{tableMetaData.primaryKeyField}
|
const tableSections = QTableUtils.getSectionsForRecordSidebar(tableMetaData);
|
||||||
|
setTableSections(tableSections);
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////
|
||||||
|
// make elements with the values for each section //
|
||||||
|
////////////////////////////////////////////////////
|
||||||
|
const sectionFieldElements = new Map();
|
||||||
|
const nonT1TableSections = [];
|
||||||
|
for (let i = 0; i < tableSections.length; i++)
|
||||||
|
{
|
||||||
|
const section = tableSections[i];
|
||||||
|
sectionFieldElements.set(
|
||||||
|
section.name,
|
||||||
|
<MDBox key={section.name} display="flex" flexDirection="column" py={1} pr={2}>
|
||||||
|
{
|
||||||
|
section.fieldNames.map((fieldName: string) => (
|
||||||
|
<MDBox key={fieldName} flexDirection="row" pr={2}>
|
||||||
|
<MDTypography variant="button" fontWeight="bold">
|
||||||
|
{tableMetaData.fields.get(fieldName).label}
|
||||||
:
|
:
|
||||||
</MDTypography>
|
</MDTypography>
|
||||||
<MDTypography variant="button" fontWeight="regular" color="text">
|
<MDTypography variant="button" fontWeight="regular" color="text">
|
||||||
|
|
||||||
{id}
|
{QValueUtils.getDisplayValue(tableMetaData.fields.get(fieldName), record)}
|
||||||
</MDTypography>
|
</MDTypography>
|
||||||
</MDBox>,
|
</MDBox>
|
||||||
);
|
))
|
||||||
|
|
||||||
const sortedKeys = [...foundRecord.values.keys()].sort();
|
|
||||||
sortedKeys.forEach((key) =>
|
|
||||||
{
|
|
||||||
if (key !== tableMetaData.primaryKeyField)
|
|
||||||
{
|
|
||||||
nameValues.push(
|
|
||||||
<MDBox key={key} display="flex" py={1} pr={2}>
|
|
||||||
<MDTypography variant="button" fontWeight="bold" textTransform="capitalize">
|
|
||||||
{tableMetaData.fields.get(key).label}
|
|
||||||
:
|
|
||||||
</MDTypography>
|
|
||||||
<MDTypography variant="button" fontWeight="regular" color="text">
|
|
||||||
|
|
||||||
{foundRecord.values.get(key)}
|
|
||||||
</MDTypography>
|
|
||||||
</MDBox>,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
});
|
</MDBox>,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (section.tier === "T1")
|
||||||
|
{
|
||||||
|
setT1SectionElement(sectionFieldElements.get(section.name));
|
||||||
|
setT1SectionName(section.name);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
nonT1TableSections.push(tableSections[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setSectionFieldElements(sectionFieldElements);
|
||||||
|
setNonT1TableSections(nonT1TableSections);
|
||||||
|
|
||||||
setNameValues(nameValues);
|
|
||||||
forceUpdate();
|
forceUpdate();
|
||||||
})();
|
})();
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleClickConfirmOpen = () =>
|
const handleClickDeleteButton = () =>
|
||||||
{
|
{
|
||||||
setOpen(true);
|
setDeleteConfirmationOpen(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleClickConfirmClose = () =>
|
const handleDeleteConfirmClose = () =>
|
||||||
{
|
{
|
||||||
setOpen(false);
|
setDeleteConfirmationOpen(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDelete = (event: { preventDefault: () => void }) =>
|
const handleDelete = (event: { preventDefault: () => void }) =>
|
||||||
@ -133,32 +183,45 @@ function ViewContents({id}: Props): JSX.Element
|
|||||||
await qController.delete(tableName, id)
|
await qController.delete(tableName, id)
|
||||||
.then(() =>
|
.then(() =>
|
||||||
{
|
{
|
||||||
window.location.href = `/${tableName}?deleteSuccess=true`;
|
const path = `${pathParts.slice(0, -1).join("/")}?deleteSuccess=true`;
|
||||||
|
navigate(path);
|
||||||
});
|
});
|
||||||
})();
|
})();
|
||||||
};
|
};
|
||||||
|
|
||||||
const editPath = `/${tableName}/${id}/edit`;
|
function processClicked(process: QProcessMetaData)
|
||||||
|
{
|
||||||
|
const path = `${pathParts.slice(0, -1).join("/")}/${process.name}?recordIds=${id}`;
|
||||||
|
navigate(path);
|
||||||
|
}
|
||||||
|
|
||||||
const renderActionsMenu = (
|
const renderActionsMenu = (
|
||||||
<Menu
|
<Menu
|
||||||
anchorEl={actionsMenu}
|
anchorEl={actionsMenu}
|
||||||
anchorOrigin={{
|
anchorOrigin={{
|
||||||
vertical: "bottom",
|
vertical: "bottom",
|
||||||
horizontal: "left",
|
horizontal: "right",
|
||||||
}}
|
}}
|
||||||
transformOrigin={{
|
transformOrigin={{
|
||||||
vertical: "top",
|
vertical: "top",
|
||||||
horizontal: "left",
|
horizontal: "right",
|
||||||
}}
|
}}
|
||||||
open={Boolean(actionsMenu)}
|
open={Boolean(actionsMenu)}
|
||||||
onClose={closeActionsMenu}
|
onClose={closeActionsMenu}
|
||||||
keepMounted
|
keepMounted
|
||||||
>
|
>
|
||||||
{tableProcesses.map((process) => (
|
<MenuItem onClick={() => navigate("edit")}>Edit</MenuItem>
|
||||||
<MenuItem key={process.name}>
|
<MenuItem onClick={() =>
|
||||||
<Link href={`/processes/${process.name}?recordIds=${id}`}>{process.label}</Link>
|
{
|
||||||
|
setActionsMenu(null);
|
||||||
|
handleClickDeleteButton();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Delete
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
{tableProcesses.length > 0 && <MenuItem divider />}
|
||||||
|
{tableProcesses.map((process) => (
|
||||||
|
<MenuItem key={process.name} onClick={() => processClicked(process)}>{process.label}</MenuItem>
|
||||||
))}
|
))}
|
||||||
</Menu>
|
</Menu>
|
||||||
);
|
);
|
||||||
@ -178,48 +241,62 @@ function ViewContents({id}: Props): JSX.Element
|
|||||||
</MDAlert>
|
</MDAlert>
|
||||||
) : ("")
|
) : ("")
|
||||||
}
|
}
|
||||||
<Card id="basic-info" sx={{overflow: "visible"}}>
|
|
||||||
<MDBox p={3}>
|
<Grid container spacing={3}>
|
||||||
<MDBox display="flex" justifyContent="space-between">
|
<Grid item xs={12} lg={3}>
|
||||||
|
<QRecordSidebar tableSections={tableSections} />
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={12} lg={9}>
|
||||||
|
|
||||||
|
<Grid container spacing={3}>
|
||||||
|
<Grid item xs={12} mb={3}>
|
||||||
|
<Card id={t1SectionName}>
|
||||||
|
<MDBox display="flex" p={3} pb={1}>
|
||||||
|
<MDBox mr={1.5}>
|
||||||
|
<Avatar sx={{bgcolor: colors.info.main}}>
|
||||||
|
<Icon>
|
||||||
|
{tableMetaData?.iconName}
|
||||||
|
</Icon>
|
||||||
|
</Avatar>
|
||||||
|
</MDBox>
|
||||||
|
<MDBox display="flex" justifyContent="space-between" width="100%" alignItems="center">
|
||||||
<MDTypography variant="h5">
|
<MDTypography variant="h5">
|
||||||
Viewing
|
{tableMetaData && record ? `Viewing ${tableMetaData?.label}: ${record?.recordLabel}` : ""}
|
||||||
{" "}
|
|
||||||
{tableMetaData?.label}
|
|
||||||
{" "}
|
|
||||||
(
|
|
||||||
{id}
|
|
||||||
)
|
|
||||||
</MDTypography>
|
</MDTypography>
|
||||||
{tableProcesses.length > 0 && (
|
<QActionsMenuButton isOpen={actionsMenu} onClickHandler={openActionsMenu} />
|
||||||
<MDButton
|
|
||||||
variant={actionsMenu ? "contained" : "outlined"}
|
|
||||||
color="dark"
|
|
||||||
onClick={openActionsMenu}
|
|
||||||
>
|
|
||||||
actions
|
|
||||||
<Icon>keyboard_arrow_down</Icon>
|
|
||||||
</MDButton>
|
|
||||||
)}
|
|
||||||
{renderActionsMenu}
|
{renderActionsMenu}
|
||||||
</MDBox>
|
</MDBox>
|
||||||
</MDBox>
|
</MDBox>
|
||||||
<MDBox p={3}>{nameValues}</MDBox>
|
{t1SectionElement ? (<MDBox p={3} pt={0}>{t1SectionElement}</MDBox>) : null}
|
||||||
<MDBox component="form" pb={3} px={3}>
|
</Card>
|
||||||
<Grid key="tres" container spacing={3}>
|
</Grid>
|
||||||
<MDBox ml="auto" mr={3}>
|
</Grid>
|
||||||
<MDButton
|
{nonT1TableSections.length > 0 ? nonT1TableSections.map(({
|
||||||
variant="gradient"
|
iconName, label, name, fieldNames, tier,
|
||||||
color="primary"
|
}: any) => (
|
||||||
size="small"
|
<MDBox mb={3} key={name}>
|
||||||
onClick={handleClickConfirmOpen}
|
<Card key={name} id={name} sx={{overflow: "visible"}}>
|
||||||
>
|
<MDTypography variant="h5" p={3} pb={1}>
|
||||||
delete
|
{label}
|
||||||
{" "}
|
</MDTypography>
|
||||||
{tableMetaData?.label}
|
<MDBox p={3} pt={0} flexDirection="column">{sectionFieldElements.get(name)}</MDBox>
|
||||||
</MDButton>
|
</Card>
|
||||||
|
</MDBox>
|
||||||
|
)) : null}
|
||||||
|
<MDBox component="form" p={3}>
|
||||||
|
<Grid container justifyContent="flex-end" spacing={3}>
|
||||||
|
<QDeleteButton onClickHandler={handleClickDeleteButton} />
|
||||||
|
<QEditButton />
|
||||||
|
</Grid>
|
||||||
|
</MDBox>
|
||||||
|
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
{/* Delete confirmation Dialog */}
|
||||||
<Dialog
|
<Dialog
|
||||||
open={open}
|
open={deleteConfirmationOpen}
|
||||||
onClose={handleClickConfirmClose}
|
onClose={handleDeleteConfirmClose}
|
||||||
aria-labelledby="alert-dialog-title"
|
aria-labelledby="alert-dialog-title"
|
||||||
aria-describedby="alert-dialog-description"
|
aria-describedby="alert-dialog-description"
|
||||||
>
|
>
|
||||||
@ -230,25 +307,19 @@ function ViewContents({id}: Props): JSX.Element
|
|||||||
</DialogContentText>
|
</DialogContentText>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={handleClickConfirmClose}>No</Button>
|
<Button onClick={handleDeleteConfirmClose}>No</Button>
|
||||||
<Button onClick={handleDelete} autoFocus>
|
<Button onClick={handleDelete} autoFocus>
|
||||||
Yes
|
Yes
|
||||||
</Button>
|
</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
</MDBox>
|
</MDBox>
|
||||||
<MDBox>
|
|
||||||
<MDButton variant="gradient" color="dark" size="small">
|
|
||||||
<Link href={editPath}>
|
|
||||||
{`edit ${tableMetaData?.label}`}
|
|
||||||
</Link>
|
|
||||||
</MDButton>
|
|
||||||
</MDBox>
|
|
||||||
</Grid>
|
|
||||||
</MDBox>
|
|
||||||
</Card>
|
|
||||||
</MDBox>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ViewContents.defaultProps = {
|
||||||
|
table: null,
|
||||||
|
};
|
||||||
|
|
||||||
export default ViewContents;
|
export default ViewContents;
|
||||||
|
@ -1,16 +1,22 @@
|
|||||||
/**
|
/*
|
||||||
=========================================================
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
* Material Dashboard 2 PRO React TS - v1.0.0
|
* Copyright (C) 2021-2022. Kingsrook, LLC
|
||||||
=========================================================
|
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||||
|
* contact@kingsrook.com
|
||||||
* Product Page: https://www.creative-tim.com/product/material-dashboard-2-pro-react-ts
|
* https://github.com/Kingsrook/
|
||||||
* Copyright 2022 Creative Tim (https://www.creative-tim.com)
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
Coded by www.creative-tim.com
|
* 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.
|
||||||
|
*
|
||||||
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {useParams} from "react-router-dom";
|
import {useParams} from "react-router-dom";
|
||||||
@ -24,22 +30,25 @@ import MDBox from "components/MDBox";
|
|||||||
// Settings page components
|
// Settings page components
|
||||||
import BaseLayout from "qqq/components/BaseLayout";
|
import BaseLayout from "qqq/components/BaseLayout";
|
||||||
import ViewContents from "./components/ViewContents";
|
import ViewContents from "./components/ViewContents";
|
||||||
|
import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData";
|
||||||
|
import EntityList from "qqq/pages/entity-list";
|
||||||
|
|
||||||
function EntityView(): JSX.Element
|
interface Props
|
||||||
|
{
|
||||||
|
table?: QTableMetaData;
|
||||||
|
}
|
||||||
|
|
||||||
|
function EntityView({table}: Props): JSX.Element
|
||||||
{
|
{
|
||||||
const {id} = useParams();
|
const {id} = useParams();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BaseLayout>
|
<BaseLayout>
|
||||||
<MDBox mt={4}>
|
<MDBox>
|
||||||
<Grid container spacing={3}>
|
<Grid container>
|
||||||
<Grid item xs={12} lg={12}>
|
|
||||||
<MDBox mb={3}>
|
|
||||||
<Grid container spacing={3}>
|
|
||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
|
<MDBox mb={3}>
|
||||||
<ViewContents id={id} />
|
<ViewContents id={id} />
|
||||||
</Grid>
|
|
||||||
</Grid>
|
|
||||||
</MDBox>
|
</MDBox>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
@ -48,4 +57,8 @@ function EntityView(): JSX.Element
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
EntityView.defaultProps = {
|
||||||
|
table: null,
|
||||||
|
};
|
||||||
|
|
||||||
export default EntityView;
|
export default EntityView;
|
||||||
|
@ -1,16 +1,22 @@
|
|||||||
/**
|
/*
|
||||||
=========================================================
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
* Material Dashboard 2 PRO React TS - v1.0.0
|
* Copyright (C) 2021-2022. Kingsrook, LLC
|
||||||
=========================================================
|
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||||
|
* contact@kingsrook.com
|
||||||
* Product Page: https://www.creative-tim.com/product/material-dashboard-2-pro-react-ts
|
* https://github.com/Kingsrook/
|
||||||
* Copyright 2022 Creative Tim (https://www.creative-tim.com)
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
Coded by www.creative-tim.com
|
* 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.
|
||||||
|
*
|
||||||
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, {useEffect, useState, Fragment} from "react";
|
import React, {useEffect, useState, Fragment} from "react";
|
||||||
@ -31,9 +37,7 @@ import MDButton from "components/MDButton";
|
|||||||
|
|
||||||
// Material Dashboard 2 PRO React TS examples components
|
// Material Dashboard 2 PRO React TS examples components
|
||||||
import DashboardLayout from "examples/LayoutContainers/DashboardLayout";
|
import DashboardLayout from "examples/LayoutContainers/DashboardLayout";
|
||||||
import DashboardNavbar from "examples/Navbars/DashboardNavbar";
|
|
||||||
|
|
||||||
// ProcessRun layout schemas for form and form fields
|
|
||||||
import * as Yup from "yup";
|
import * as Yup from "yup";
|
||||||
import {QFieldMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldMetaData";
|
import {QFieldMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldMetaData";
|
||||||
import {QFrontendStepMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFrontendStepMetaData";
|
import {QFrontendStepMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFrontendStepMetaData";
|
||||||
@ -52,10 +56,18 @@ import {CircularProgress, TablePagination} from "@mui/material";
|
|||||||
import QDynamicForm from "../../components/QDynamicForm";
|
import QDynamicForm from "../../components/QDynamicForm";
|
||||||
import MDTypography from "../../../components/MDTypography";
|
import MDTypography from "../../../components/MDTypography";
|
||||||
import Footer from "examples/Footer";
|
import Footer from "examples/Footer";
|
||||||
|
import {QProcessMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QProcessMetaData";
|
||||||
|
import Navbar from "qqq/components/Navbar";
|
||||||
|
|
||||||
function ProcessRun(): JSX.Element
|
interface Props
|
||||||
{
|
{
|
||||||
const {processName} = useParams();
|
process?: QProcessMetaData;
|
||||||
|
}
|
||||||
|
|
||||||
|
function ProcessRun({process}: Props): JSX.Element
|
||||||
|
{
|
||||||
|
const processNameParam = useParams().processName;
|
||||||
|
const processName = process === null ? processNameParam : process.name;
|
||||||
|
|
||||||
///////////////////
|
///////////////////
|
||||||
// process state //
|
// process state //
|
||||||
@ -686,7 +698,7 @@ function ProcessRun(): JSX.Element
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<DashboardLayout>
|
<DashboardLayout>
|
||||||
<DashboardNavbar />
|
<Navbar />
|
||||||
<MDBox py={3} mb={20}>
|
<MDBox py={3} mb={20}>
|
||||||
<Grid
|
<Grid
|
||||||
container
|
container
|
||||||
@ -788,4 +800,8 @@ function ProcessRun(): JSX.Element
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ProcessRun.defaultProps = {
|
||||||
|
process: null,
|
||||||
|
};
|
||||||
|
|
||||||
export default ProcessRun;
|
export default ProcessRun;
|
||||||
|
@ -19,6 +19,83 @@
|
|||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.MuiDrawer-docked .MuiPaper-elevation {
|
.MuiDrawer-docked .MuiPaper-elevation
|
||||||
|
{
|
||||||
white-space: normal;
|
white-space: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Disable red outlines on clicked cells */
|
||||||
|
.MuiDataGrid-cell:focus,
|
||||||
|
.MuiDataGrid-columnHeader:focus,
|
||||||
|
.MuiDataGrid-columnHeader:focus-within,
|
||||||
|
.MuiDataGrid-cell:focus-within
|
||||||
|
{
|
||||||
|
outline: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* lighten & shrink cell font */
|
||||||
|
.MuiDataGrid-cell
|
||||||
|
{
|
||||||
|
color: rgb(45, 45, 45);
|
||||||
|
font-size: 0.85rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* lighten & shrink header font */
|
||||||
|
.MuiDataGrid-columnHeaderTitle
|
||||||
|
{
|
||||||
|
color: rgb(95, 95, 95);
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 500 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* tighten widths in headers */
|
||||||
|
.MuiDataGrid-iconButtonContainer
|
||||||
|
{
|
||||||
|
width: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* tighten widths in headers */
|
||||||
|
.MuiDataGrid-iconButtonContainer .MuiIconButton-root
|
||||||
|
{
|
||||||
|
padding: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* tighten widths in headers */
|
||||||
|
.MuiDataGrid-menuIcon
|
||||||
|
{
|
||||||
|
margin-left: -5px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* tighten widths in headers */
|
||||||
|
.MuiDataGrid-menuIcon .MuiDataGrid-menuIconButton
|
||||||
|
{
|
||||||
|
padding: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* when checked-column is pinned, its checkboxes disappear as gray-on-gray - this helps. */
|
||||||
|
.MuiCheckbox-root .MuiSvgIcon-root
|
||||||
|
{
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* shrink font on in the pagination control */
|
||||||
|
.MuiTablePagination-displayedRows,
|
||||||
|
.MuiTablePagination-selectLabel,
|
||||||
|
.MuiTablePagination-select.MuiSelect-select.MuiSelect-standard,
|
||||||
|
.MuiDataGrid-selectedRowCount
|
||||||
|
{
|
||||||
|
font-size: 0.85rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* try to make the pagination select box look like one */
|
||||||
|
.MuiTablePagination-select
|
||||||
|
{
|
||||||
|
border: 1px solid rgb(175 175 175) !important;
|
||||||
|
border-radius: 5px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* move the green check / red x down to align with the calendar icon */
|
||||||
|
.MuiFormControl-root
|
||||||
|
{
|
||||||
|
background-position-y: 1.4rem !important;
|
||||||
|
}
|
@ -19,9 +19,9 @@
|
|||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {QFieldMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldMetaData";
|
|
||||||
import {QController} from "@kingsrook/qqq-frontend-core/lib/controllers/QController";
|
import {QController} from "@kingsrook/qqq-frontend-core/lib/controllers/QController";
|
||||||
import {QQueryFilter} from "@kingsrook/qqq-frontend-core/lib/model/query/QQueryFilter";
|
import {QException} from "@kingsrook/qqq-frontend-core/lib/exceptions/QException";
|
||||||
|
import {useAuth0} from "@auth0/auth0-react";
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** client wrapper of qqq backend
|
** client wrapper of qqq backend
|
||||||
@ -31,40 +31,25 @@ class QClient
|
|||||||
{
|
{
|
||||||
private static qController: QController;
|
private static qController: QController;
|
||||||
|
|
||||||
|
private static handleException(exception: QException)
|
||||||
|
{
|
||||||
|
console.log(`Caught Exception: ${JSON.stringify(exception)}`);
|
||||||
|
const {logout} = useAuth0();
|
||||||
|
if (exception.status === "401")
|
||||||
|
{
|
||||||
|
logout();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static getInstance()
|
public static getInstance()
|
||||||
{
|
{
|
||||||
if (this.qController == null)
|
if (this.qController == null)
|
||||||
{
|
{
|
||||||
this.qController = new QController("");
|
this.qController = new QController("", this.handleException);
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.qController;
|
return this.qController;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static loadTableMetaData(tableName: string)
|
|
||||||
{
|
|
||||||
return this.getInstance().loadTableMetaData(tableName);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static loadMetaData()
|
|
||||||
{
|
|
||||||
return this.getInstance().loadMetaData();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static query(tableName: string, filter: QQueryFilter, limit: number, skip: number)
|
|
||||||
{
|
|
||||||
return this.getInstance()
|
|
||||||
.query(tableName, filter, limit, skip)
|
|
||||||
.catch((error) =>
|
|
||||||
{
|
|
||||||
throw error;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public static count(tableName: string, filter: QQueryFilter)
|
|
||||||
{
|
|
||||||
return this.getInstance().count(tableName, filter);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default QClient;
|
export default QClient;
|
||||||
|
@ -28,14 +28,14 @@ import {QInstance} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QInstan
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
class QProcessUtils
|
class QProcessUtils
|
||||||
{
|
{
|
||||||
public static getProcessesForTable(metaData: QInstance, tableName: string): QProcessMetaData[]
|
public static getProcessesForTable(metaData: QInstance, tableName: string, includeHidden = false): QProcessMetaData[]
|
||||||
{
|
{
|
||||||
const matchingProcesses: QProcessMetaData[] = [];
|
const matchingProcesses: QProcessMetaData[] = [];
|
||||||
const processKeys = [...metaData.processes.keys()];
|
const processKeys = [...metaData.processes.keys()];
|
||||||
processKeys.forEach((key) =>
|
processKeys.forEach((key) =>
|
||||||
{
|
{
|
||||||
const process = metaData.processes.get(key);
|
const process = metaData.processes.get(key);
|
||||||
if (!process.isHidden && process.tableName === tableName)
|
if (process.tableName === tableName && (includeHidden || !process.isHidden))
|
||||||
{
|
{
|
||||||
matchingProcesses.push(process);
|
matchingProcesses.push(process);
|
||||||
}
|
}
|
||||||
|
46
src/qqq/utils/QTableUtils.ts
Normal file
46
src/qqq/utils/QTableUtils.ts
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
/*
|
||||||
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
|
* Copyright (C) 2021-2022. Kingsrook, LLC
|
||||||
|
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||||
|
* contact@kingsrook.com
|
||||||
|
* https://github.com/Kingsrook/
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData";
|
||||||
|
import {QSection} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QSection";
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Utility class for working with QQQ Tables
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
class QTableUtils
|
||||||
|
{
|
||||||
|
public static getSectionsForRecordSidebar(tableMetaData: QTableMetaData): QSection[]
|
||||||
|
{
|
||||||
|
if (tableMetaData.sections)
|
||||||
|
{
|
||||||
|
return (tableMetaData.sections);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return ([new QSection({
|
||||||
|
iconName: "description", label: "All Fields", name: "allFields", fieldNames: [...tableMetaData.fields.keys()],
|
||||||
|
})]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default QTableUtils;
|
63
src/qqq/utils/QValueUtils.ts
Normal file
63
src/qqq/utils/QValueUtils.ts
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
/*
|
||||||
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
|
* Copyright (C) 2021-2022. Kingsrook, LLC
|
||||||
|
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||||
|
* contact@kingsrook.com
|
||||||
|
* https://github.com/Kingsrook/
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {QFieldMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldMetaData";
|
||||||
|
import {QRecord} from "@kingsrook/qqq-frontend-core/lib/model/QRecord";
|
||||||
|
import {QFieldType} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldType";
|
||||||
|
|
||||||
|
import "datejs";
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Utility class for working with QQQ Values
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
class QValueUtils
|
||||||
|
{
|
||||||
|
public static getDisplayValue(field: QFieldMetaData, record: QRecord): string
|
||||||
|
{
|
||||||
|
const displayValue = record.displayValues ? record.displayValues.get(field.name) : undefined;
|
||||||
|
const rawValue = record.values ? record.values.get(field.name) : undefined;
|
||||||
|
|
||||||
|
if (field.type === QFieldType.DATE_TIME)
|
||||||
|
{
|
||||||
|
if (!rawValue)
|
||||||
|
{
|
||||||
|
return ("");
|
||||||
|
}
|
||||||
|
const date = new Date(rawValue);
|
||||||
|
// @ts-ignore
|
||||||
|
return (`${date.toString("yyyy-MM-dd hh:mm:ss")} ${date.getHours() < 12 ? "AM" : "PM"} ${date.getTimezone()}`);
|
||||||
|
}
|
||||||
|
else if (field.type === QFieldType.DATE)
|
||||||
|
{
|
||||||
|
// unclear if we need any customization for DATE or TIME, but leaving blocks for them just in case
|
||||||
|
return (displayValue);
|
||||||
|
}
|
||||||
|
else if (field.type === QFieldType.TIME)
|
||||||
|
{
|
||||||
|
return (displayValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (displayValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default QValueUtils;
|
Reference in New Issue
Block a user