mirror of
https://github.com/Kingsrook/qqq-frontend-material-dashboard.git
synced 2025-07-22 07:08:44 +00:00
Compare commits
33 Commits
snapshot-f
...
snapshot-f
Author | SHA1 | Date | |
---|---|---|---|
0879cb4f80 | |||
f1b0618e9d | |||
95f1fa83bb | |||
4b0e12ba47 | |||
6cfb1e04ed | |||
0d763cbfc8 | |||
279840e77a | |||
51b2f5bb5a | |||
02fe351084 | |||
25fa2e82ea | |||
24b4674208 | |||
04630fd154 | |||
1503e2a1d5 | |||
3d7502531d | |||
d0a7db28fe | |||
95244a8aba | |||
45f247785c | |||
9e6d5c10fb | |||
4e0b13ad02 | |||
7f57a11e00 | |||
83da3a3a0a | |||
b59ed8c8c1 | |||
7101420124 | |||
b903e6bef9 | |||
970c9f262c | |||
9313988f9b | |||
123d1742e7 | |||
47fca52437 | |||
44b92690ab | |||
64fe2305ad | |||
91d38a1d15 | |||
60a8baff35 | |||
81b46408b4 |
@ -6,7 +6,7 @@
|
|||||||
"@auth0/auth0-react": "1.10.2",
|
"@auth0/auth0-react": "1.10.2",
|
||||||
"@emotion/react": "11.7.1",
|
"@emotion/react": "11.7.1",
|
||||||
"@emotion/styled": "11.6.0",
|
"@emotion/styled": "11.6.0",
|
||||||
"@kingsrook/qqq-frontend-core": "1.0.82",
|
"@kingsrook/qqq-frontend-core": "1.0.83",
|
||||||
"@mui/icons-material": "5.4.1",
|
"@mui/icons-material": "5.4.1",
|
||||||
"@mui/material": "5.11.1",
|
"@mui/material": "5.11.1",
|
||||||
"@mui/styles": "5.11.1",
|
"@mui/styles": "5.11.1",
|
||||||
|
2
pom.xml
2
pom.xml
@ -29,7 +29,7 @@
|
|||||||
<packaging>jar</packaging>
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<revision>0.19.0-SNAPSHOT</revision>
|
<revision>0.20.0-SNAPSHOT</revision>
|
||||||
|
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||||
|
@ -0,0 +1,31 @@
|
|||||||
|
/*
|
||||||
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
|
* Copyright (C) 2021-2023. 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.kingsrook.qqq.frontend.materialdashboard.model.metadata;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public interface MaterialDashboardIconRoleNames
|
||||||
|
{
|
||||||
|
String TOP_RIGHT_INSIDE_CARD = "topRightInsideCard";
|
||||||
|
}
|
@ -31,7 +31,7 @@ type Types = any;
|
|||||||
|
|
||||||
const card: Types = {
|
const card: Types = {
|
||||||
defaultProps: {
|
defaultProps: {
|
||||||
elevation: 3
|
elevation: 0
|
||||||
},
|
},
|
||||||
styleOverrides: {
|
styleOverrides: {
|
||||||
root: {
|
root: {
|
||||||
@ -42,7 +42,7 @@ const card: Types = {
|
|||||||
wordWrap: "break-word",
|
wordWrap: "break-word",
|
||||||
backgroundColor: white.main,
|
backgroundColor: white.main,
|
||||||
backgroundClip: "border-box",
|
backgroundClip: "border-box",
|
||||||
border: `${borderWidth[0]} solid ${rgba(black.main, 0.125)}`,
|
border: `${borderWidth[1]} solid ${rgba(black.main, 0.25)}`,
|
||||||
borderRadius: borderRadius.xl,
|
borderRadius: borderRadius.xl,
|
||||||
overflow: "visible",
|
overflow: "visible",
|
||||||
},
|
},
|
||||||
|
@ -1,29 +1,25 @@
|
|||||||
/**
|
/**
|
||||||
=========================================================
|
=========================================================
|
||||||
* Material Dashboard 2 PRO React TS - v1.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
|
* Product Page: https://www.creative-tim.com/product/material-dashboard-2-pro-react-ts
|
||||||
* Copyright 2022 Creative Tim (https://www.creative-tim.com)
|
* Copyright 2022 Creative Tim (https://www.creative-tim.com)
|
||||||
|
|
||||||
Coded by 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.
|
* 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 Base Styles
|
// Material Dashboard 2 PRO React TS Base Styles
|
||||||
import colors from "qqq/assets/theme/base/colors";
|
import colors from "qqq/assets/theme/base/colors";
|
||||||
import borders from "qqq/assets/theme/base/borders";
|
|
||||||
import boxShadows from "qqq/assets/theme/base/boxShadows";
|
|
||||||
|
|
||||||
// Material Dashboard 2 PRO React TS Helper Functions
|
// Material Dashboard 2 PRO React TS Helper Functions
|
||||||
import pxToRem from "qqq/assets/theme/functions/pxToRem";
|
import pxToRem from "qqq/assets/theme/functions/pxToRem";
|
||||||
|
|
||||||
const { grey, white } = colors;
|
const {grey, white} = colors;
|
||||||
const { borderRadius } = borders;
|
|
||||||
const { tabsBoxShadow } = boxShadows;
|
|
||||||
|
|
||||||
// types
|
// types
|
||||||
type Types = any;
|
type Types = any;
|
||||||
@ -32,15 +28,22 @@ const tabs: Types = {
|
|||||||
styleOverrides: {
|
styleOverrides: {
|
||||||
root: {
|
root: {
|
||||||
position: "relative",
|
position: "relative",
|
||||||
backgroundColor: grey[100],
|
borderRadius: 0,
|
||||||
borderRadius: borderRadius.xl,
|
borderBottom: "1px solid",
|
||||||
|
borderBottomColor: grey[400],
|
||||||
minHeight: "unset",
|
minHeight: "unset",
|
||||||
padding: pxToRem(4),
|
padding: "0",
|
||||||
|
margin: "0"
|
||||||
|
},
|
||||||
|
|
||||||
|
scroller: {
|
||||||
|
marginLeft: "0.5rem"
|
||||||
},
|
},
|
||||||
|
|
||||||
flexContainer: {
|
flexContainer: {
|
||||||
height: "100%",
|
height: "100%",
|
||||||
position: "relative",
|
position: "relative",
|
||||||
|
width: "fit-content",
|
||||||
zIndex: 10,
|
zIndex: 10,
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -57,9 +60,10 @@ const tabs: Types = {
|
|||||||
|
|
||||||
indicator: {
|
indicator: {
|
||||||
height: "100%",
|
height: "100%",
|
||||||
borderRadius: borderRadius.lg,
|
borderRadius: 0,
|
||||||
backgroundColor: white.main,
|
backgroundColor: white.main,
|
||||||
boxShadow: tabsBoxShadow.indicator,
|
borderBottom: "2px solid",
|
||||||
|
borderBottomColor: colors.info.main,
|
||||||
transition: "all 500ms ease",
|
transition: "all 500ms ease",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1,17 +1,17 @@
|
|||||||
/**
|
/**
|
||||||
=========================================================
|
=========================================================
|
||||||
* Material Dashboard 2 PRO React TS - v1.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
|
* Product Page: https://www.creative-tim.com/product/material-dashboard-2-pro-react-ts
|
||||||
* Copyright 2022 Creative Tim (https://www.creative-tim.com)
|
* Copyright 2022 Creative Tim (https://www.creative-tim.com)
|
||||||
|
|
||||||
Coded by 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.
|
* 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 Base Styles
|
// Material Dashboard 2 PRO React TS Base Styles
|
||||||
import typography from "qqq/assets/theme/base/typography";
|
import typography from "qqq/assets/theme/base/typography";
|
||||||
@ -21,9 +21,9 @@ import colors from "qqq/assets/theme/base/colors";
|
|||||||
// Material Dashboard 2 PRO React TS Helper Functions
|
// Material Dashboard 2 PRO React TS Helper Functions
|
||||||
import pxToRem from "qqq/assets/theme/functions/pxToRem";
|
import pxToRem from "qqq/assets/theme/functions/pxToRem";
|
||||||
|
|
||||||
const { size, fontWeightRegular } = typography;
|
const {size, fontWeightRegular} = typography;
|
||||||
const { borderRadius } = borders;
|
const {borderRadius} = borders;
|
||||||
const { dark } = colors;
|
const {dark} = colors;
|
||||||
|
|
||||||
// types
|
// types
|
||||||
type Types = any;
|
type Types = any;
|
||||||
@ -43,8 +43,10 @@ const tab: Types = {
|
|||||||
fontWeight: fontWeightRegular,
|
fontWeight: fontWeightRegular,
|
||||||
textTransform: "none",
|
textTransform: "none",
|
||||||
lineHeight: "inherit",
|
lineHeight: "inherit",
|
||||||
padding: pxToRem(4),
|
padding: "0.75rem 0.5rem 0.5rem",
|
||||||
borderRadius: borderRadius.lg,
|
margin: "0 0.5rem",
|
||||||
|
borderRadius: 0,
|
||||||
|
border: 0,
|
||||||
color: `${dark.main} !important`,
|
color: `${dark.main} !important`,
|
||||||
opacity: "1 !important",
|
opacity: "1 !important",
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@ import borders from "qqq/assets/theme/base/borders";
|
|||||||
// Material Dashboard 2 PRO React TS Helper Functions
|
// Material Dashboard 2 PRO React TS Helper Functions
|
||||||
import pxToRem from "qqq/assets/theme/functions/pxToRem";
|
import pxToRem from "qqq/assets/theme/functions/pxToRem";
|
||||||
|
|
||||||
const { black, light } = colors;
|
const { black, light, white, dark } = colors;
|
||||||
const { size, fontWeightRegular } = typography;
|
const { size, fontWeightRegular } = typography;
|
||||||
const { borderRadius } = borders;
|
const { borderRadius } = borders;
|
||||||
|
|
||||||
@ -39,19 +39,20 @@ const tooltip: Types = {
|
|||||||
|
|
||||||
styleOverrides: {
|
styleOverrides: {
|
||||||
tooltip: {
|
tooltip: {
|
||||||
maxWidth: pxToRem(200),
|
maxWidth: pxToRem(300),
|
||||||
backgroundColor: black.main,
|
backgroundColor: white.main,
|
||||||
color: light.main,
|
color: dark.main,
|
||||||
fontSize: size.sm,
|
fontSize: size.sm,
|
||||||
fontWeight: fontWeightRegular,
|
fontWeight: fontWeightRegular,
|
||||||
textAlign: "center",
|
textAlign: "left",
|
||||||
borderRadius: borderRadius.md,
|
borderRadius: borderRadius.md,
|
||||||
opacity: 0.7,
|
opacity: 0.7,
|
||||||
padding: `${pxToRem(5)} ${pxToRem(8)} ${pxToRem(4)}`,
|
padding: "1rem",
|
||||||
|
boxShadow: "rgba(0, 0, 0, 0.2) 0px 3px 3px -2px, rgba(0, 0, 0, 0.14) 0px 3px 4px 0px, rgba(0, 0, 0, 0.12) 0px 1px 8px 0px"
|
||||||
},
|
},
|
||||||
|
|
||||||
arrow: {
|
arrow: {
|
||||||
color: black.main,
|
color: white.main,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -19,13 +19,14 @@
|
|||||||
* 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 {InputLabel} from "@mui/material";
|
import {Box, InputLabel} from "@mui/material";
|
||||||
import Stack from "@mui/material/Stack";
|
import Stack from "@mui/material/Stack";
|
||||||
import {styled} from "@mui/material/styles";
|
import {styled} from "@mui/material/styles";
|
||||||
import Switch from "@mui/material/Switch";
|
import Switch from "@mui/material/Switch";
|
||||||
import Typography from "@mui/material/Typography";
|
import Typography from "@mui/material/Typography";
|
||||||
import {useFormikContext} from "formik";
|
import {useFormikContext} from "formik";
|
||||||
import React, {SyntheticEvent} from "react";
|
import React, {SyntheticEvent} from "react";
|
||||||
|
import colors from "qqq/assets/theme/base/colors";
|
||||||
|
|
||||||
const AntSwitch = styled(Switch)(({theme}) => ({
|
const AntSwitch = styled(Switch)(({theme}) => ({
|
||||||
width: 28,
|
width: 28,
|
||||||
@ -60,6 +61,9 @@ const AntSwitch = styled(Switch)(({theme}) => ({
|
|||||||
duration: 200,
|
duration: 200,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
"&.nullSwitch .MuiSwitch-thumb": {
|
||||||
|
width: 24,
|
||||||
|
},
|
||||||
"& .MuiSwitch-track": {
|
"& .MuiSwitch-track": {
|
||||||
borderRadius: 16 / 2,
|
borderRadius: 16 / 2,
|
||||||
opacity: 1,
|
opacity: 1,
|
||||||
@ -78,6 +82,7 @@ interface Props
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function BooleanFieldSwitch({name, label, value, isDisabled}: Props) : JSX.Element
|
function BooleanFieldSwitch({name, label, value, isDisabled}: Props) : JSX.Element
|
||||||
{
|
{
|
||||||
const {setFieldValue} = useFormikContext();
|
const {setFieldValue} = useFormikContext();
|
||||||
@ -96,8 +101,10 @@ function BooleanFieldSwitch({name, label, value, isDisabled}: Props) : JSX.Eleme
|
|||||||
setFieldValue(name, !value);
|
setFieldValue(name, !value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const classNullSwitch = (value === null || value == undefined || `${value}` == "") ? "nullSwitch" : "";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<Box bgcolor={isDisabled ? colors.grey[200] : ""}>
|
||||||
<InputLabel shrink={true}>{label}</InputLabel>
|
<InputLabel shrink={true}>{label}</InputLabel>
|
||||||
<Stack direction="row" spacing={1} alignItems="center">
|
<Stack direction="row" spacing={1} alignItems="center">
|
||||||
<Typography
|
<Typography
|
||||||
@ -107,7 +114,7 @@ function BooleanFieldSwitch({name, label, value, isDisabled}: Props) : JSX.Eleme
|
|||||||
sx={{cursor: value === false || isDisabled ? "inherit" : "pointer"}}>
|
sx={{cursor: value === false || isDisabled ? "inherit" : "pointer"}}>
|
||||||
No
|
No
|
||||||
</Typography>
|
</Typography>
|
||||||
<AntSwitch name={name} checked={value} onClick={toggleSwitch} disabled={isDisabled} />
|
<AntSwitch className={classNullSwitch} name={name} checked={value} onClick={toggleSwitch} disabled={isDisabled} />
|
||||||
<Typography
|
<Typography
|
||||||
fontSize="0.875rem"
|
fontSize="0.875rem"
|
||||||
color={value === true ? "auto" : "#bfbfbf"}
|
color={value === true ? "auto" : "#bfbfbf"}
|
||||||
@ -116,7 +123,7 @@ function BooleanFieldSwitch({name, label, value, isDisabled}: Props) : JSX.Eleme
|
|||||||
Yes
|
Yes
|
||||||
</Typography>
|
</Typography>
|
||||||
</Stack>
|
</Stack>
|
||||||
</>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -88,7 +88,14 @@ function QDynamicFormField({
|
|||||||
if (type === "checkbox")
|
if (type === "checkbox")
|
||||||
{
|
{
|
||||||
getsBulkEditHtmlLabel = false;
|
getsBulkEditHtmlLabel = false;
|
||||||
field = (<BooleanFieldSwitch name={name} label={label} value={value} isDisabled={isDisabled} />);
|
field = (<>
|
||||||
|
<BooleanFieldSwitch name={name} label={label} value={value} isDisabled={isDisabled} />
|
||||||
|
<Box mt={0.75}>
|
||||||
|
<MDTypography component="div" variant="caption" color="error" fontWeight="regular">
|
||||||
|
{!isDisabled && <div className="fieldErrorMessage"><ErrorMessage name={name} render={msg => <span data-field-error="true">{msg}</span>} /></div>}
|
||||||
|
</MDTypography>
|
||||||
|
</Box>
|
||||||
|
</>);
|
||||||
}
|
}
|
||||||
else if (type === "ace")
|
else if (type === "ace")
|
||||||
{
|
{
|
||||||
|
@ -426,6 +426,11 @@ function EntityForm(props: Props): JSX.Element
|
|||||||
actions.setSubmitting(true);
|
actions.setSubmitting(true);
|
||||||
await (async () =>
|
await (async () =>
|
||||||
{
|
{
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// we will be manipulating the values sent to the backend, so clone values so they remained unchanged for the form widgets //
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
const valuesToPost = JSON.parse(JSON.stringify(values));
|
||||||
|
|
||||||
for(let fieldName of tableMetaData.fields.keys())
|
for(let fieldName of tableMetaData.fields.keys())
|
||||||
{
|
{
|
||||||
const fieldMetaData = tableMetaData.fields.get(fieldName);
|
const fieldMetaData = tableMetaData.fields.get(fieldName);
|
||||||
@ -438,17 +443,17 @@ function EntityForm(props: Props): JSX.Element
|
|||||||
// changing from, say, 12:15:30 to just 12:15:00... this seems to get around that, for cases when the //
|
// changing from, say, 12:15:30 to just 12:15:00... this seems to get around that, for cases when the //
|
||||||
// user didn't change the value in the field (but if the user did change the value, then we will submit it) //
|
// user didn't change the value in the field (but if the user did change the value, then we will submit it) //
|
||||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
if(fieldMetaData.type === QFieldType.DATE_TIME && values[fieldName])
|
if(fieldMetaData.type === QFieldType.DATE_TIME && valuesToPost[fieldName])
|
||||||
{
|
{
|
||||||
console.log(`DateTime ${fieldName}: Initial value: [${initialValues[fieldName]}] -> [${values[fieldName]}]`)
|
console.log(`DateTime ${fieldName}: Initial value: [${initialValues[fieldName]}] -> [${valuesToPost[fieldName]}]`)
|
||||||
if (initialValues[fieldName] == values[fieldName])
|
if (initialValues[fieldName] == valuesToPost[fieldName])
|
||||||
{
|
{
|
||||||
console.log(" - Is the same, so, deleting from the post");
|
console.log(" - Is the same, so, deleting from the post");
|
||||||
delete (values[fieldName]);
|
delete (valuesToPost[fieldName]);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
values[fieldName] = ValueUtils.frontendLocalZoneDateTimeStringToUTCStringForBackend(values[fieldName]);
|
valuesToPost[fieldName] = ValueUtils.frontendLocalZoneDateTimeStringToUTCStringForBackend(valuesToPost[fieldName]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -461,10 +466,10 @@ function EntityForm(props: Props): JSX.Element
|
|||||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
if(fieldMetaData.type === QFieldType.BLOB)
|
if(fieldMetaData.type === QFieldType.BLOB)
|
||||||
{
|
{
|
||||||
if(typeof values[fieldName] === "string")
|
if(typeof valuesToPost[fieldName] === "string")
|
||||||
{
|
{
|
||||||
console.log(`${fieldName} value was a string, so, we're deleting it from the values array, to not submit it to the backend, to not change it.`);
|
console.log(`${fieldName} value was a string, so, we're deleting it from the values array, to not submit it to the backend, to not change it.`);
|
||||||
delete(values[fieldName]);
|
delete(valuesToPost[fieldName]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -473,7 +478,7 @@ function EntityForm(props: Props): JSX.Element
|
|||||||
{
|
{
|
||||||
// todo - audit that it's a dupe
|
// todo - audit that it's a dupe
|
||||||
await qController
|
await qController
|
||||||
.update(tableName, props.id, values)
|
.update(tableName, props.id, valuesToPost)
|
||||||
.then((record) =>
|
.then((record) =>
|
||||||
{
|
{
|
||||||
if (props.isModal)
|
if (props.isModal)
|
||||||
@ -506,7 +511,7 @@ function EntityForm(props: Props): JSX.Element
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
await qController
|
await qController
|
||||||
.create(tableName, values)
|
.create(tableName, valuesToPost)
|
||||||
.then((record) =>
|
.then((record) =>
|
||||||
{
|
{
|
||||||
if (props.isModal)
|
if (props.isModal)
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
* 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 {Popper} from "@mui/material";
|
import {Popper, InputAdornment} from "@mui/material";
|
||||||
import AppBar from "@mui/material/AppBar";
|
import AppBar from "@mui/material/AppBar";
|
||||||
import Autocomplete from "@mui/material/Autocomplete";
|
import Autocomplete from "@mui/material/Autocomplete";
|
||||||
import Badge from "@mui/material/Badge";
|
import Badge from "@mui/material/Badge";
|
||||||
@ -34,8 +34,8 @@ import React, {useContext, useEffect, useState} from "react";
|
|||||||
import {useLocation, useNavigate} from "react-router-dom";
|
import {useLocation, useNavigate} from "react-router-dom";
|
||||||
import QContext from "QContext";
|
import QContext from "QContext";
|
||||||
import QBreadcrumbs, {routeToLabel} from "qqq/components/horseshoe/Breadcrumbs";
|
import QBreadcrumbs, {routeToLabel} from "qqq/components/horseshoe/Breadcrumbs";
|
||||||
import {navbar, navbarContainer, navbarIconButton, navbarRow,} from "qqq/components/horseshoe/Styles";
|
import {navbar, navbarContainer, navbarRow, navbarMobileMenu, recentlyViewedMenu,} from "qqq/components/horseshoe/Styles";
|
||||||
import {setTransparentNavbar, useMaterialUIController,} from "qqq/context";
|
import {setTransparentNavbar, useMaterialUIController, setMiniSidenav} from "qqq/context";
|
||||||
import HistoryUtils from "qqq/utils/HistoryUtils";
|
import HistoryUtils from "qqq/utils/HistoryUtils";
|
||||||
|
|
||||||
// Declaring prop types for NavBar
|
// Declaring prop types for NavBar
|
||||||
@ -57,7 +57,7 @@ function NavBar({absolute, light, isMini}: Props): JSX.Element
|
|||||||
{
|
{
|
||||||
const [navbarType, setNavbarType] = useState<"fixed" | "absolute" | "relative" | "static" | "sticky">();
|
const [navbarType, setNavbarType] = useState<"fixed" | "absolute" | "relative" | "static" | "sticky">();
|
||||||
const [controller, dispatch] = useMaterialUIController();
|
const [controller, dispatch] = useMaterialUIController();
|
||||||
const {transparentNavbar, fixedNavbar, darkMode,} = controller;
|
const {miniSidenav, transparentNavbar, fixedNavbar, darkMode,} = controller;
|
||||||
const [openMenu, setOpenMenu] = useState<any>(false);
|
const [openMenu, setOpenMenu] = useState<any>(false);
|
||||||
const [history, setHistory] = useState<any>([] as HistoryEntry[]);
|
const [history, setHistory] = useState<any>([] as HistoryEntry[]);
|
||||||
const [autocompleteValue, setAutocompleteValue] = useState<any>(null);
|
const [autocompleteValue, setAutocompleteValue] = useState<any>(null);
|
||||||
@ -105,6 +105,8 @@ function NavBar({absolute, light, isMini}: Props): JSX.Element
|
|||||||
return () => window.removeEventListener("scroll", handleTransparentNavbar);
|
return () => window.removeEventListener("scroll", handleTransparentNavbar);
|
||||||
}, [dispatch, fixedNavbar]);
|
}, [dispatch, fixedNavbar]);
|
||||||
|
|
||||||
|
const handleMiniSidenav = () => setMiniSidenav(dispatch, !miniSidenav);
|
||||||
|
|
||||||
const goToHistory = (path: string) =>
|
const goToHistory = (path: string) =>
|
||||||
{
|
{
|
||||||
navigate(path);
|
navigate(path);
|
||||||
@ -162,7 +164,15 @@ function NavBar({absolute, light, isMini}: Props): JSX.Element
|
|||||||
onChange={handleAutocompleteOnChange}
|
onChange={handleAutocompleteOnChange}
|
||||||
PopperComponent={CustomPopper}
|
PopperComponent={CustomPopper}
|
||||||
isOptionEqualToValue={(option, value) => option.id === value.id}
|
isOptionEqualToValue={(option, value) => option.id === value.id}
|
||||||
renderInput={(params) => <TextField {...params} label="Recently Viewed Records" />}
|
sx={recentlyViewedMenu}
|
||||||
|
renderInput={(params) => <TextField {...params} label="Recently Viewed Records" InputProps={{
|
||||||
|
...params.InputProps,
|
||||||
|
endAdornment: (
|
||||||
|
<InputAdornment position="end">
|
||||||
|
<Icon sx={{position: "relative", right: "-1rem"}}>keyboard_arrow_down</Icon>
|
||||||
|
</InputAdornment>
|
||||||
|
)
|
||||||
|
}} />}
|
||||||
renderOption={(props, option: HistoryEntry) => (
|
renderOption={(props, option: HistoryEntry) => (
|
||||||
<Box {...props} component="li" key={option.id} sx={{width: "auto"}}>
|
<Box {...props} component="li" key={option.id} sx={{width: "auto"}}>
|
||||||
<Box sx={{width: "auto", px: "8px", whiteSpace: "overflow"}} key={option.id}>
|
<Box sx={{width: "auto", px: "8px", whiteSpace: "overflow"}} key={option.id}>
|
||||||
@ -175,22 +185,6 @@ function NavBar({absolute, light, isMini}: Props): JSX.Element
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Render the notifications menu
|
|
||||||
const renderMenu = () => (
|
|
||||||
<Menu
|
|
||||||
anchorEl={openMenu}
|
|
||||||
anchorReference={null}
|
|
||||||
anchorOrigin={{
|
|
||||||
vertical: "bottom",
|
|
||||||
horizontal: "left",
|
|
||||||
}}
|
|
||||||
open={Boolean(openMenu)}
|
|
||||||
onClose={handleCloseMenu}
|
|
||||||
sx={{mt: 2}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
// Styles for the navbar icons
|
// Styles for the navbar icons
|
||||||
const iconsStyle = ({
|
const iconsStyle = ({
|
||||||
palette: {dark, white, text},
|
palette: {dark, white, text},
|
||||||
@ -240,26 +234,22 @@ function NavBar({absolute, light, isMini}: Props): JSX.Element
|
|||||||
>
|
>
|
||||||
<Toolbar sx={navbarContainer}>
|
<Toolbar sx={navbarContainer}>
|
||||||
<Box color="inherit" mb={{xs: 1, md: 0}} sx={(theme) => navbarRow(theme, {isMini})}>
|
<Box color="inherit" mb={{xs: 1, md: 0}} sx={(theme) => navbarRow(theme, {isMini})}>
|
||||||
|
<IconButton
|
||||||
|
size="small"
|
||||||
|
disableRipple
|
||||||
|
color="inherit"
|
||||||
|
sx={navbarMobileMenu}
|
||||||
|
onClick={handleMiniSidenav}
|
||||||
|
>
|
||||||
|
<Icon sx={iconsStyle} fontSize="large">menu</Icon>
|
||||||
|
</IconButton>
|
||||||
<QBreadcrumbs icon="home" title={breadcrumbTitle} route={route} light={light} />
|
<QBreadcrumbs icon="home" title={breadcrumbTitle} route={route} light={light} />
|
||||||
</Box>
|
</Box>
|
||||||
{isMini ? null : (
|
{isMini ? null : (
|
||||||
<Box sx={(theme) => navbarRow(theme, {isMini})}>
|
<Box sx={(theme) => navbarRow(theme, {isMini})}>
|
||||||
<Box pr={1}>
|
<Box pr={0} mr={-2} mt={-4}>
|
||||||
{renderHistory()}
|
{renderHistory()}
|
||||||
</Box>
|
</Box>
|
||||||
<Box color={light ? "white" : "inherit"}>
|
|
||||||
<IconButton
|
|
||||||
size="small"
|
|
||||||
color="inherit"
|
|
||||||
sx={navbarIconButton}
|
|
||||||
onClick={handleOpenMenu}
|
|
||||||
>
|
|
||||||
<Badge badgeContent={0} color="error" variant="dot">
|
|
||||||
<Icon sx={iconsStyle}>notifications</Icon>
|
|
||||||
</Badge>
|
|
||||||
</IconButton>
|
|
||||||
{renderMenu()}
|
|
||||||
</Box>
|
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
</Toolbar>
|
</Toolbar>
|
||||||
|
@ -110,11 +110,10 @@ const navbarContainer = ({breakpoints}: Theme): any => ({
|
|||||||
const navbarRow = ({breakpoints}: Theme, {isMini}: any) => ({
|
const navbarRow = ({breakpoints}: Theme, {isMini}: any) => ({
|
||||||
display: "flex",
|
display: "flex",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
justifyContent: "space-between",
|
|
||||||
width: "100%",
|
width: "100%",
|
||||||
|
|
||||||
[breakpoints.up("md")]: {
|
[breakpoints.up("md")]: {
|
||||||
justifyContent: isMini ? "space-between" : "stretch",
|
justifyContent: "stretch",
|
||||||
width: isMini ? "100%" : "max-content",
|
width: isMini ? "100%" : "max-content",
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -146,12 +145,27 @@ const navbarDesktopMenu = ({breakpoints}: Theme) => ({
|
|||||||
display: "none !important",
|
display: "none !important",
|
||||||
cursor: "pointer",
|
cursor: "pointer",
|
||||||
|
|
||||||
[breakpoints.up("xl")]: {
|
[breakpoints.down("sm")]: {
|
||||||
display: "inline-block !important",
|
display: "inline-block !important",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const recentlyViewedMenu = ({breakpoints}: Theme) => ({
|
||||||
|
"& .MuiOutlinedInput-root": {
|
||||||
|
borderRadius: "0",
|
||||||
|
padding: "0"
|
||||||
|
},
|
||||||
|
"& .MuiOutlinedInput-root .MuiOutlinedInput-notchedOutline": {
|
||||||
|
border: "0"
|
||||||
|
},
|
||||||
|
display: "block",
|
||||||
|
[breakpoints.down("md")]: {
|
||||||
|
display: "none !important",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const navbarMobileMenu = ({breakpoints}: Theme) => ({
|
const navbarMobileMenu = ({breakpoints}: Theme) => ({
|
||||||
|
left: "-0.75rem",
|
||||||
display: "inline-block",
|
display: "inline-block",
|
||||||
lineHeight: 0,
|
lineHeight: 0,
|
||||||
|
|
||||||
@ -167,4 +181,5 @@ export {
|
|||||||
navbarIconButton,
|
navbarIconButton,
|
||||||
navbarDesktopMenu,
|
navbarDesktopMenu,
|
||||||
navbarMobileMenu,
|
navbarMobileMenu,
|
||||||
|
recentlyViewedMenu
|
||||||
};
|
};
|
||||||
|
@ -27,7 +27,7 @@ export default styled(Drawer)(({theme, ownerState}: { theme?: Theme | any; owner
|
|||||||
const {palette, boxShadows, transitions, breakpoints, functions} = theme;
|
const {palette, boxShadows, transitions, breakpoints, functions} = theme;
|
||||||
const {transparentSidenav, whiteSidenav, miniSidenav, darkMode} = ownerState;
|
const {transparentSidenav, whiteSidenav, miniSidenav, darkMode} = ownerState;
|
||||||
|
|
||||||
const sidebarWidth = 250;
|
const sidebarWidth = 275;
|
||||||
const {transparent, gradients, white, background} = palette;
|
const {transparent, gradients, white, background} = palette;
|
||||||
const {xxl} = boxShadows;
|
const {xxl} = boxShadows;
|
||||||
const {pxToRem, linearGradient} = functions;
|
const {pxToRem, linearGradient} = functions;
|
||||||
@ -94,6 +94,9 @@ export default styled(Drawer)(({theme, ownerState}: { theme?: Theme | any; owner
|
|||||||
"& .MuiDrawer-paper": {
|
"& .MuiDrawer-paper": {
|
||||||
boxShadow: xxl,
|
boxShadow: xxl,
|
||||||
border: "none",
|
border: "none",
|
||||||
|
margin: "0",
|
||||||
|
borderRadius: "0",
|
||||||
|
height: "100%",
|
||||||
|
|
||||||
...(miniSidenav ? drawerCloseStyles() : drawerOpenStyles()),
|
...(miniSidenav ? drawerCloseStyles() : drawerOpenStyles()),
|
||||||
},
|
},
|
||||||
|
@ -28,11 +28,12 @@ interface TabPanelProps
|
|||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
index: number;
|
index: number;
|
||||||
value: number;
|
value: number;
|
||||||
|
style?: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function TabPanel(props: TabPanelProps)
|
export default function TabPanel(props: TabPanelProps)
|
||||||
{
|
{
|
||||||
const {children, value, index, ...other} = props;
|
const {children, value, index, style, ...other} = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@ -40,6 +41,7 @@ export default function TabPanel(props: TabPanelProps)
|
|||||||
hidden={value !== index}
|
hidden={value !== index}
|
||||||
id={`simple-tabpanel-${index}`}
|
id={`simple-tabpanel-${index}`}
|
||||||
aria-labelledby={`simple-tab-${index}`}
|
aria-labelledby={`simple-tab-${index}`}
|
||||||
|
style={style}
|
||||||
{...other}
|
{...other}
|
||||||
>
|
>
|
||||||
{value === index && (
|
{value === index && (
|
||||||
|
@ -155,7 +155,7 @@ function ValidationReview({
|
|||||||
"false",
|
"false",
|
||||||
"Skip Validation. Submit the records for immediate processing", (
|
"Skip Validation. Submit the records for immediate processing", (
|
||||||
<div>
|
<div>
|
||||||
If you choose this option, the records input records will immediately be processed.
|
If you choose this option, the input records will immediately be processed.
|
||||||
You will be told how many records were successfully processed, and which ones had issues after the processing is completed.
|
You will be told how many records were successfully processed, and which ones had issues after the processing is completed.
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
|
@ -22,11 +22,13 @@ import {QWidgetMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/Q
|
|||||||
import {Skeleton} from "@mui/material";
|
import {Skeleton} from "@mui/material";
|
||||||
import Box from "@mui/material/Box";
|
import Box from "@mui/material/Box";
|
||||||
import Grid from "@mui/material/Grid";
|
import Grid from "@mui/material/Grid";
|
||||||
|
import Tab from "@mui/material/Tab";
|
||||||
|
import Tabs from "@mui/material/Tabs";
|
||||||
import parse from "html-react-parser";
|
import parse from "html-react-parser";
|
||||||
import React, {useContext, useEffect, useReducer, useState} from "react";
|
import React, {useContext, useEffect, useReducer, useState} from "react";
|
||||||
import {useLocation} from "react-router-dom";
|
|
||||||
import QContext from "QContext";
|
import QContext from "QContext";
|
||||||
import MDTypography from "qqq/components/legacy/MDTypography";
|
import MDTypography from "qqq/components/legacy/MDTypography";
|
||||||
|
import TabPanel from "qqq/components/misc/TabPanel";
|
||||||
import BarChart from "qqq/components/widgets/charts/barchart/BarChart";
|
import BarChart from "qqq/components/widgets/charts/barchart/BarChart";
|
||||||
import HorizontalBarChart from "qqq/components/widgets/charts/barchart/HorizontalBarChart";
|
import HorizontalBarChart from "qqq/components/widgets/charts/barchart/HorizontalBarChart";
|
||||||
import DefaultLineChart from "qqq/components/widgets/charts/linechart/DefaultLineChart";
|
import DefaultLineChart from "qqq/components/widgets/charts/linechart/DefaultLineChart";
|
||||||
@ -44,7 +46,7 @@ import USMapWidget from "qqq/components/widgets/misc/USMapWidget";
|
|||||||
import ParentWidget from "qqq/components/widgets/ParentWidget";
|
import ParentWidget from "qqq/components/widgets/ParentWidget";
|
||||||
import MultiStatisticsCard from "qqq/components/widgets/statistics/MultiStatisticsCard";
|
import MultiStatisticsCard from "qqq/components/widgets/statistics/MultiStatisticsCard";
|
||||||
import StatisticsCard from "qqq/components/widgets/statistics/StatisticsCard";
|
import StatisticsCard from "qqq/components/widgets/statistics/StatisticsCard";
|
||||||
import Widget, {WIDGET_DROPDOWN_SELECTION_LOCAL_STORAGE_KEY_ROOT} from "qqq/components/widgets/Widget";
|
import Widget, {HeaderIcon, WIDGET_DROPDOWN_SELECTION_LOCAL_STORAGE_KEY_ROOT, LabelComponent} from "qqq/components/widgets/Widget";
|
||||||
import ProcessRun from "qqq/pages/processes/ProcessRun";
|
import ProcessRun from "qqq/pages/processes/ProcessRun";
|
||||||
import Client from "qqq/utils/qqq/Client";
|
import Client from "qqq/utils/qqq/Client";
|
||||||
import TableWidget from "./tables/TableWidget";
|
import TableWidget from "./tables/TableWidget";
|
||||||
@ -58,9 +60,10 @@ interface Props
|
|||||||
tableName?: string;
|
tableName?: string;
|
||||||
entityPrimaryKey?: string;
|
entityPrimaryKey?: string;
|
||||||
omitWrappingGridContainer: boolean;
|
omitWrappingGridContainer: boolean;
|
||||||
areChildren?: boolean
|
areChildren?: boolean;
|
||||||
childUrlParams?: string
|
childUrlParams?: string;
|
||||||
parentWidgetMetaData?: QWidgetMetaData
|
parentWidgetMetaData?: QWidgetMetaData;
|
||||||
|
wrapWidgetsInTabPanels: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
DashboardWidgets.defaultProps = {
|
DashboardWidgets.defaultProps = {
|
||||||
@ -70,12 +73,12 @@ DashboardWidgets.defaultProps = {
|
|||||||
omitWrappingGridContainer: false,
|
omitWrappingGridContainer: false,
|
||||||
areChildren: false,
|
areChildren: false,
|
||||||
childUrlParams: "",
|
childUrlParams: "",
|
||||||
parentWidgetMetaData: null
|
parentWidgetMetaData: null,
|
||||||
|
wrapWidgetsInTabPanels: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, omitWrappingGridContainer, areChildren, childUrlParams, parentWidgetMetaData}: Props): JSX.Element
|
function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, omitWrappingGridContainer, areChildren, childUrlParams, parentWidgetMetaData, wrapWidgetsInTabPanels}: Props): JSX.Element
|
||||||
{
|
{
|
||||||
const location = useLocation();
|
|
||||||
const [widgetData, setWidgetData] = useState([] as any[]);
|
const [widgetData, setWidgetData] = useState([] as any[]);
|
||||||
const [widgetCounter, setWidgetCounter] = useState(0);
|
const [widgetCounter, setWidgetCounter] = useState(0);
|
||||||
const [, forceUpdate] = useReducer((x) => x + 1, 0);
|
const [, forceUpdate] = useReducer((x) => x + 1, 0);
|
||||||
@ -84,6 +87,24 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, omit
|
|||||||
const [haveLoadedParams, setHaveLoadedParams] = useState(false);
|
const [haveLoadedParams, setHaveLoadedParams] = useState(false);
|
||||||
const {accentColor} = useContext(QContext);
|
const {accentColor} = useContext(QContext);
|
||||||
|
|
||||||
|
let initialSelectedTab = 0;
|
||||||
|
let selectedTabKey: string = null;
|
||||||
|
if(parentWidgetMetaData && wrapWidgetsInTabPanels)
|
||||||
|
{
|
||||||
|
selectedTabKey = `qqq.widgets.selectedTabs.${parentWidgetMetaData.name}`
|
||||||
|
if (localStorage.getItem(selectedTabKey))
|
||||||
|
{
|
||||||
|
initialSelectedTab = Number(localStorage.getItem(selectedTabKey));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const [selectedTab, setSelectedTab] = useState(initialSelectedTab);
|
||||||
|
|
||||||
|
const changeTab = (newValue: number) =>
|
||||||
|
{
|
||||||
|
setSelectedTab(newValue);
|
||||||
|
localStorage.setItem(selectedTabKey, String(newValue));
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() =>
|
useEffect(() =>
|
||||||
{
|
{
|
||||||
setWidgetData([]);
|
setWidgetData([]);
|
||||||
@ -102,15 +123,15 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, omit
|
|||||||
widgetData[i] = await qController.widget(widgetMetaData.name, urlParams);
|
widgetData[i] = await qController.widget(widgetMetaData.name, urlParams);
|
||||||
setWidgetData(widgetData);
|
setWidgetData(widgetData);
|
||||||
setWidgetCounter(widgetCounter + 1);
|
setWidgetCounter(widgetCounter + 1);
|
||||||
if(widgetData[i])
|
if (widgetData[i])
|
||||||
{
|
{
|
||||||
widgetData[i]["errorLoading"] = false;
|
widgetData[i]["errorLoading"] = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch(e)
|
catch (e)
|
||||||
{
|
{
|
||||||
console.error(e);
|
console.error(e);
|
||||||
if(widgetData[i])
|
if (widgetData[i])
|
||||||
{
|
{
|
||||||
widgetData[i]["errorLoading"] = true;
|
widgetData[i]["errorLoading"] = true;
|
||||||
}
|
}
|
||||||
@ -123,7 +144,7 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, omit
|
|||||||
|
|
||||||
const reloadWidget = async (index: number, data: string) =>
|
const reloadWidget = async (index: number, data: string) =>
|
||||||
{
|
{
|
||||||
(async() =>
|
(async () =>
|
||||||
{
|
{
|
||||||
const urlParams = getQueryParams(widgetMetaDataList[index], data);
|
const urlParams = getQueryParams(widgetMetaDataList[index], data);
|
||||||
setCurrentUrlParams(urlParams);
|
setCurrentUrlParams(urlParams);
|
||||||
@ -140,7 +161,7 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, omit
|
|||||||
widgetData[index]["errorLoading"] = false;
|
widgetData[index]["errorLoading"] = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch(e)
|
catch (e)
|
||||||
{
|
{
|
||||||
console.error(e);
|
console.error(e);
|
||||||
if (widgetData[index])
|
if (widgetData[index])
|
||||||
@ -151,7 +172,7 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, omit
|
|||||||
|
|
||||||
forceUpdate();
|
forceUpdate();
|
||||||
})();
|
})();
|
||||||
}
|
};
|
||||||
|
|
||||||
function getQueryParams(widgetMetaData: QWidgetMetaData, extraParams: string): string
|
function getQueryParams(widgetMetaData: QWidgetMetaData, extraParams: string): string
|
||||||
{
|
{
|
||||||
@ -178,36 +199,36 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, omit
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(entityPrimaryKey)
|
if (entityPrimaryKey)
|
||||||
{
|
{
|
||||||
paramMap.set("id", entityPrimaryKey);
|
paramMap.set("id", entityPrimaryKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(tableName)
|
if (tableName)
|
||||||
{
|
{
|
||||||
paramMap.set("tableName", tableName);
|
paramMap.set("tableName", tableName);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(extraParams)
|
if (extraParams)
|
||||||
{
|
{
|
||||||
let pairs = extraParams.split("&");
|
let pairs = extraParams.split("&");
|
||||||
for (let i = 0; i < pairs.length; i++)
|
for (let i = 0; i < pairs.length; i++)
|
||||||
{
|
{
|
||||||
let nameValue = pairs[i].split("=");
|
let nameValue = pairs[i].split("=");
|
||||||
if(nameValue.length == 2)
|
if (nameValue.length == 2)
|
||||||
{
|
{
|
||||||
paramMap.set(nameValue[0], nameValue[1]);
|
paramMap.set(nameValue[0], nameValue[1]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(childUrlParams)
|
if (childUrlParams)
|
||||||
{
|
{
|
||||||
let pairs = childUrlParams.split("&");
|
let pairs = childUrlParams.split("&");
|
||||||
for (let i = 0; i < pairs.length; i++)
|
for (let i = 0; i < pairs.length; i++)
|
||||||
{
|
{
|
||||||
let nameValue = pairs[i].split("=");
|
let nameValue = pairs[i].split("=");
|
||||||
if(nameValue.length == 2)
|
if (nameValue.length == 2)
|
||||||
{
|
{
|
||||||
paramMap.set(nameValue[0], nameValue[1]);
|
paramMap.set(nameValue[0], nameValue[1]);
|
||||||
}
|
}
|
||||||
@ -227,6 +248,16 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, omit
|
|||||||
|
|
||||||
const renderWidget = (widgetMetaData: QWidgetMetaData, i: number): JSX.Element =>
|
const renderWidget = (widgetMetaData: QWidgetMetaData, i: number): JSX.Element =>
|
||||||
{
|
{
|
||||||
|
const labelAdditionalComponentsRight: LabelComponent[] = [];
|
||||||
|
if (widgetMetaData && widgetMetaData.icons)
|
||||||
|
{
|
||||||
|
const topRightInsideCardIcon = widgetMetaData.icons.get("topRightInsideCard");
|
||||||
|
if (topRightInsideCardIcon)
|
||||||
|
{
|
||||||
|
labelAdditionalComponentsRight.push(new HeaderIcon(topRightInsideCardIcon.name, topRightInsideCardIcon.color));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box key={`${widgetMetaData.name}-${i}`} sx={{alignItems: "stretch", flexGrow: 1, display: "flex", marginTop: "0px", paddingTop: "0px", width: "100%", height: "100%"}}>
|
<Box key={`${widgetMetaData.name}-${i}`} sx={{alignItems: "stretch", flexGrow: 1, display: "flex", marginTop: "0px", paddingTop: "0px", width: "100%", height: "100%"}}>
|
||||||
{
|
{
|
||||||
@ -238,7 +269,7 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, omit
|
|||||||
widgetIndex={i}
|
widgetIndex={i}
|
||||||
widgetMetaData={widgetMetaData}
|
widgetMetaData={widgetMetaData}
|
||||||
data={widgetData[i]}
|
data={widgetData[i]}
|
||||||
reloadWidgetCallback={reloadWidget}
|
reloadWidgetCallback={(data) => reloadWidget(i, data)}
|
||||||
storeDropdownSelections={widgetMetaData.storeDropdownSelections}
|
storeDropdownSelections={widgetMetaData.storeDropdownSelections}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
@ -270,8 +301,9 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, omit
|
|||||||
widgetData={widgetData[i]}
|
widgetData={widgetData[i]}
|
||||||
reloadWidgetCallback={(data) => reloadWidget(i, data)}
|
reloadWidgetCallback={(data) => reloadWidget(i, data)}
|
||||||
isChild={areChildren}
|
isChild={areChildren}
|
||||||
|
labelAdditionalComponentsRight={labelAdditionalComponentsRight}
|
||||||
>
|
>
|
||||||
<StackedBarChart data={widgetData[i]?.chartData}/>
|
<StackedBarChart data={widgetData[i]?.chartData} chartSubheaderData={widgetData[i]?.chartSubheaderData} />
|
||||||
</Widget>
|
</Widget>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -381,10 +413,12 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, omit
|
|||||||
widgetData={widgetData[i]}
|
widgetData={widgetData[i]}
|
||||||
reloadWidgetCallback={(data) => reloadWidget(i, data)}
|
reloadWidgetCallback={(data) => reloadWidget(i, data)}
|
||||||
isChild={areChildren}
|
isChild={areChildren}
|
||||||
|
labelAdditionalComponentsRight={labelAdditionalComponentsRight}
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<PieChart
|
<PieChart
|
||||||
chartData={widgetData[i]?.chartData}
|
chartData={widgetData[i]?.chartData}
|
||||||
|
chartSubheaderData={widgetData[i]?.chartSubheaderData}
|
||||||
description={widgetData[i]?.description}
|
description={widgetData[i]?.description}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -461,32 +495,62 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, omit
|
|||||||
}
|
}
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
const body: JSX.Element =
|
const body: JSX.Element =
|
||||||
(
|
(
|
||||||
<>
|
<>
|
||||||
{
|
{
|
||||||
widgetMetaDataList.map((widgetMetaData, i) => (
|
widgetMetaDataList.map((widgetMetaData, i) =>
|
||||||
omitWrappingGridContainer
|
{
|
||||||
? widgetMetaData && renderWidget(widgetMetaData, i)
|
let renderedWidget = widgetMetaData ? renderWidget(widgetMetaData, i) : (<></>);
|
||||||
:
|
|
||||||
widgetMetaData && <Grid id={widgetMetaData.name} key={`${widgetMetaData.name}-${i}`} item lg={widgetMetaData.gridColumns ? widgetMetaData.gridColumns : 12} xs={12} sx={{display: "flex", alignItems: "stretch", scrollMarginTop: "100px"}}>
|
if (!omitWrappingGridContainer)
|
||||||
{renderWidget(widgetMetaData, i)}
|
{
|
||||||
</Grid>
|
// @ts-ignore
|
||||||
))
|
renderedWidget = (<Grid id={widgetMetaData.name} item xxl={widgetMetaData.gridColumns ? widgetMetaData.gridColumns : 12} xs={12} sx={{display: "flex", alignItems: "stretch", scrollMarginTop: "100px"}}>
|
||||||
|
{renderedWidget}
|
||||||
|
</Grid>);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wrapWidgetsInTabPanels)
|
||||||
|
{
|
||||||
|
renderedWidget = (<TabPanel index={i} value={selectedTab} style={{padding: "1rem 0 0 1.5rem", width: "100%", marginBottom: "-1.5rem"}}>
|
||||||
|
{renderedWidget}
|
||||||
|
</TabPanel>);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (<React.Fragment key={`${widgetMetaData.name}-${i}`}>{renderedWidget}</React.Fragment>)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const tabs = widgetMetaDataList && wrapWidgetsInTabPanels ?
|
||||||
|
<Tabs
|
||||||
|
sx={{m: 0, mb: 1.5}}
|
||||||
|
value={selectedTab}
|
||||||
|
onChange={(event, newValue) => changeTab(newValue)}
|
||||||
|
variant="standard"
|
||||||
|
>
|
||||||
|
{widgetMetaDataList.map((widgetMetaData, i) => (
|
||||||
|
<Tab key={widgetMetaData.name} label={widgetMetaData.label} />
|
||||||
|
))}
|
||||||
|
</Tabs>
|
||||||
|
: <></>
|
||||||
|
|
||||||
return (
|
return (
|
||||||
widgetCount > 0 ? (
|
widgetCount > 0 ? (
|
||||||
omitWrappingGridContainer ? body :
|
<>
|
||||||
(
|
{tabs}
|
||||||
|
{
|
||||||
|
omitWrappingGridContainer ? body : (
|
||||||
<Grid container spacing={3} pb={4}>
|
<Grid container spacing={3} pb={4}>
|
||||||
{body}
|
{body}
|
||||||
</Grid>
|
</Grid>
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
</>
|
||||||
) : null
|
) : null
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -43,6 +43,7 @@ export interface ParentWidgetData
|
|||||||
dropdownNeedsSelectedText?: string;
|
dropdownNeedsSelectedText?: string;
|
||||||
storeDropdownSelections?: boolean;
|
storeDropdownSelections?: boolean;
|
||||||
icon?: string;
|
icon?: string;
|
||||||
|
layoutType: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -55,7 +56,7 @@ interface Props
|
|||||||
widgetMetaData?: QWidgetMetaData;
|
widgetMetaData?: QWidgetMetaData;
|
||||||
widgetIndex: number;
|
widgetIndex: number;
|
||||||
data: ParentWidgetData;
|
data: ParentWidgetData;
|
||||||
reloadWidgetCallback?: (widgetIndex: number, params: string) => void;
|
reloadWidgetCallback?: (params: string) => void;
|
||||||
entityPrimaryKey?: string;
|
entityPrimaryKey?: string;
|
||||||
tableName?: string;
|
tableName?: string;
|
||||||
storeDropdownSelections?: boolean;
|
storeDropdownSelections?: boolean;
|
||||||
@ -91,10 +92,15 @@ function ParentWidget({urlParams, widgetMetaData, widgetIndex, data, reloadWidge
|
|||||||
}
|
}
|
||||||
}, [qInstance, data, childUrlParams]);
|
}, [qInstance, data, childUrlParams]);
|
||||||
|
|
||||||
|
useEffect(() =>
|
||||||
|
{
|
||||||
|
setChildUrlParams(urlParams)
|
||||||
|
}, [urlParams]);
|
||||||
|
|
||||||
const parentReloadWidgetCallback = (data: string) =>
|
const parentReloadWidgetCallback = (data: string) =>
|
||||||
{
|
{
|
||||||
setChildUrlParams(data);
|
setChildUrlParams(data);
|
||||||
reloadWidgetCallback(widgetIndex, data);
|
reloadWidgetCallback(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
@ -112,7 +118,7 @@ function ParentWidget({urlParams, widgetMetaData, widgetIndex, data, reloadWidge
|
|||||||
reloadWidgetCallback={parentReloadWidgetCallback}
|
reloadWidgetCallback={parentReloadWidgetCallback}
|
||||||
>
|
>
|
||||||
<Box sx={{height: "100%", width: "100%"}} px={px}>
|
<Box sx={{height: "100%", width: "100%"}} px={px}>
|
||||||
<DashboardWidgets widgetMetaDataList={widgets} entityPrimaryKey={entityPrimaryKey} tableName={tableName} childUrlParams={childUrlParams} areChildren={true} parentWidgetMetaData={widgetMetaData}/>
|
<DashboardWidgets widgetMetaDataList={widgets} entityPrimaryKey={entityPrimaryKey} tableName={tableName} childUrlParams={childUrlParams} areChildren={true} parentWidgetMetaData={widgetMetaData} wrapWidgetsInTabPanels={data.layoutType == "TABS"}/>
|
||||||
</Box>
|
</Box>
|
||||||
</Widget>
|
</Widget>
|
||||||
) : null
|
) : null
|
||||||
|
@ -96,6 +96,49 @@ export class LabelComponent
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
export class HeaderIcon extends LabelComponent
|
||||||
|
{
|
||||||
|
iconName: string;
|
||||||
|
color: string;
|
||||||
|
coloredBG: boolean;
|
||||||
|
|
||||||
|
iconColor: string;
|
||||||
|
bgColor: string;
|
||||||
|
|
||||||
|
constructor(iconName: string, color: string, coloredBG: boolean = true)
|
||||||
|
{
|
||||||
|
super();
|
||||||
|
this.iconName = iconName;
|
||||||
|
this.color = color;
|
||||||
|
this.coloredBG = coloredBG;
|
||||||
|
|
||||||
|
this.iconColor = this.coloredBG ? "#FFFFFF" : this.color;
|
||||||
|
this.bgColor = this.coloredBG ? this.color : "none";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
render = (args: LabelComponentRenderArgs): JSX.Element =>
|
||||||
|
{
|
||||||
|
return (
|
||||||
|
<Icon sx={{
|
||||||
|
m: 2,
|
||||||
|
mr: 0,
|
||||||
|
mb: 0,
|
||||||
|
width: "1.75rem",
|
||||||
|
height: "1.75rem",
|
||||||
|
color: this.iconColor,
|
||||||
|
backgroundColor: this.bgColor,
|
||||||
|
padding: "0.25rem",
|
||||||
|
borderRadius: "0.25rem"
|
||||||
|
}} fontSize="small">{this.iconName}</Icon>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@ -406,18 +449,35 @@ function Widget(props: React.PropsWithChildren<Props>): JSX.Element
|
|||||||
needLabelBox ||= isSet(props.widgetMetaData?.label);
|
needLabelBox ||= isSet(props.widgetMetaData?.label);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// first look for a label in the widget data, which would override that in the metadata //
|
||||||
|
// note - previously this had a ?: and one was pl={2}, the other was pl={3}... //
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
const labelToUse = props.widgetData?.label ?? props.widgetMetaData?.label
|
||||||
|
let labelElement = (
|
||||||
|
<Typography sx={{position: "relative", top: -4, cursor: "default"}} variant="h6" fontWeight="medium" display="inline">
|
||||||
|
{labelToUse}
|
||||||
|
</Typography>
|
||||||
|
);
|
||||||
|
|
||||||
|
if(props.widgetMetaData.tooltip)
|
||||||
|
{
|
||||||
|
labelElement = <Tooltip title={props.widgetMetaData.tooltip} arrow={false} followCursor={true} placement="bottom-start">{labelElement}</Tooltip>
|
||||||
|
}
|
||||||
|
|
||||||
const errorLoading = props.widgetData?.errorLoading !== undefined && props.widgetData?.errorLoading === true;
|
const errorLoading = props.widgetData?.errorLoading !== undefined && props.widgetData?.errorLoading === true;
|
||||||
const widgetContent =
|
const widgetContent =
|
||||||
<Box sx={{width: "100%", height: "100%", minHeight: props.widgetMetaData?.minHeight ? props.widgetMetaData?.minHeight : "initial"}}>
|
<Box sx={{width: "100%", height: "100%", minHeight: props.widgetMetaData?.minHeight ? props.widgetMetaData?.minHeight : "initial"}}>
|
||||||
{
|
{
|
||||||
needLabelBox &&
|
needLabelBox &&
|
||||||
<Box pr={2} display="flex" justifyContent="space-between" alignItems="flex-start" sx={{width: "100%"}} height={"3.5rem"}>
|
<Box pr={2} display="flex" justifyContent="space-between" alignItems="flex-start" sx={{width: "100%"}} minHeight={"3.5rem"}>
|
||||||
<Box pt={2} pb={1}>
|
<Box pt={2} pb={1} ml={2}>
|
||||||
{
|
{
|
||||||
hasPermission ?
|
hasPermission ?
|
||||||
props.widgetMetaData?.icon && (
|
props.widgetMetaData?.icon && (
|
||||||
<Box
|
<Box
|
||||||
ml={3}
|
ml={1}
|
||||||
|
mr={2}
|
||||||
mt={-4}
|
mt={-4}
|
||||||
sx={{
|
sx={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
@ -457,20 +517,7 @@ function Widget(props: React.PropsWithChildren<Props>): JSX.Element
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
//////////////////////////////////////////////////////////////////////////////////////////
|
hasPermission && labelToUse && (labelElement)
|
||||||
// first look for a label in the widget data, which would override that in the metadata //
|
|
||||||
//////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
hasPermission && props.widgetData?.label ? (
|
|
||||||
<Typography sx={{position: "relative", top: -4}} variant="h6" fontWeight="medium" pl={2} display="inline-block">
|
|
||||||
{props.widgetData.label}
|
|
||||||
</Typography>
|
|
||||||
) : (
|
|
||||||
hasPermission && props.widgetMetaData?.label && (
|
|
||||||
<Typography sx={{position: "relative", top: -4}} variant="h6" fontWeight="medium" pl={3} display="inline-block">
|
|
||||||
{props.widgetMetaData.label}
|
|
||||||
</Typography>
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
hasPermission && (
|
hasPermission && (
|
||||||
@ -530,7 +577,7 @@ function Widget(props: React.PropsWithChildren<Props>): JSX.Element
|
|||||||
? <Card sx={{marginTop: props.widgetMetaData?.icon ? 2 : 0, width: "100%"}} className={fullScreenWidgetClassName}>
|
? <Card sx={{marginTop: props.widgetMetaData?.icon ? 2 : 0, width: "100%"}} className={fullScreenWidgetClassName}>
|
||||||
{widgetContent}
|
{widgetContent}
|
||||||
</Card>
|
</Card>
|
||||||
: widgetContent;
|
: <span style={{width: "100%"}}>{widgetContent}</span>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Widget;
|
export default Widget;
|
||||||
|
@ -28,6 +28,7 @@ import {Bar} from "react-chartjs-2";
|
|||||||
import {useNavigate} from "react-router-dom";
|
import {useNavigate} from "react-router-dom";
|
||||||
import colors from "qqq/assets/theme/base/colors";
|
import colors from "qqq/assets/theme/base/colors";
|
||||||
import {chartColors, DefaultChartData} from "qqq/components/widgets/charts/DefaultChartData";
|
import {chartColors, DefaultChartData} from "qqq/components/widgets/charts/DefaultChartData";
|
||||||
|
import ChartSubheaderWithData, {ChartSubheaderData} from "qqq/components/widgets/components/ChartSubheaderWithData";
|
||||||
|
|
||||||
ChartJS.register(
|
ChartJS.register(
|
||||||
CategoryScale,
|
CategoryScale,
|
||||||
@ -39,18 +40,40 @@ ChartJS.register(
|
|||||||
);
|
);
|
||||||
|
|
||||||
export const options = {
|
export const options = {
|
||||||
|
maintainAspectRatio: false,
|
||||||
responsive: true,
|
responsive: true,
|
||||||
animation: {
|
animation: {
|
||||||
duration: 0
|
duration: 0
|
||||||
},
|
},
|
||||||
|
plugins: {
|
||||||
|
tooltip: {
|
||||||
|
// todo - some configs around this
|
||||||
|
enabled: false
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
position: "bottom",
|
||||||
|
labels: {
|
||||||
|
usePointStyle: true,
|
||||||
|
pointStyle: "circle",
|
||||||
|
boxHeight: 8,
|
||||||
|
boxWidth: 8,
|
||||||
|
padding: 12,
|
||||||
|
font: {
|
||||||
|
size: 14
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
scales: {
|
scales: {
|
||||||
x: {
|
x: {
|
||||||
stacked: true,
|
stacked: true,
|
||||||
grid: {offset: false},
|
grid: {display: false},
|
||||||
ticks: {autoSkip: false, maxRotation: 90}
|
ticks: {autoSkip: false, maxRotation: 90}
|
||||||
},
|
},
|
||||||
y: {
|
y: {
|
||||||
stacked: true,
|
stacked: true,
|
||||||
|
position: "right",
|
||||||
|
ticks: {precision: 0}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@ -58,10 +81,12 @@ export const options = {
|
|||||||
interface Props
|
interface Props
|
||||||
{
|
{
|
||||||
data: DefaultChartData;
|
data: DefaultChartData;
|
||||||
|
chartSubheaderData?: ChartSubheaderData;
|
||||||
}
|
}
|
||||||
|
|
||||||
const {gradients} = colors;
|
const {gradients} = colors;
|
||||||
function StackedBarChart({data}: Props): JSX.Element
|
|
||||||
|
function StackedBarChart({data, chartSubheaderData}: Props): JSX.Element
|
||||||
{
|
{
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
@ -70,24 +95,31 @@ function StackedBarChart({data}: Props): JSX.Element
|
|||||||
|
|
||||||
const handleClick = (e: Array<{}>) =>
|
const handleClick = (e: Array<{}>) =>
|
||||||
{
|
{
|
||||||
if(e && e.length > 0 && data?.urls && data?.urls.length)
|
if (e && e.length > 0 && data?.urls && data?.urls.length)
|
||||||
{
|
{
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
navigate(data.urls[e[0]["index"]]);
|
navigate(data.urls[e[0]["index"]]);
|
||||||
}
|
}
|
||||||
console.log(e);
|
console.log(e);
|
||||||
}
|
};
|
||||||
|
|
||||||
useEffect(() =>
|
useEffect(() =>
|
||||||
{
|
{
|
||||||
if(data)
|
if (data)
|
||||||
{
|
{
|
||||||
data?.datasets.forEach((dataset: any, index: number) =>
|
data?.datasets.forEach((dataset: any, index: number) =>
|
||||||
{
|
{
|
||||||
if (!dataset.backgroundColor)
|
if (!dataset.backgroundColor)
|
||||||
|
{
|
||||||
|
if (gradients[chartColors[index]])
|
||||||
{
|
{
|
||||||
dataset.backgroundColor = gradients[chartColors[index]].state;
|
dataset.backgroundColor = gradients[chartColors[index]].state;
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
dataset.backgroundColor = chartColors[index];
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
setStateData(stateData);
|
setStateData(stateData);
|
||||||
}
|
}
|
||||||
@ -95,8 +127,13 @@ function StackedBarChart({data}: Props): JSX.Element
|
|||||||
|
|
||||||
|
|
||||||
return data ? (
|
return data ? (
|
||||||
<Box p={3}><Bar data={data} options={options} getElementsAtEvent={handleClick} /></Box>
|
<Box p={3} pt={1}>
|
||||||
) : <Skeleton sx={{marginLeft: "20px", marginRight: "20px", height: "200px"}} /> ;
|
{chartSubheaderData && (<ChartSubheaderWithData chartSubheaderData={chartSubheaderData} />)}
|
||||||
|
<Box width="100%" height="300px">
|
||||||
|
<Bar data={data} options={options} getElementsAtEvent={handleClick} />
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
) : <Skeleton sx={{marginLeft: "20px", marginRight: "20px", height: "200px"}} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default StackedBarChart;
|
export default StackedBarChart;
|
||||||
|
@ -30,6 +30,7 @@ import {useNavigate} from "react-router-dom";
|
|||||||
import MDTypography from "qqq/components/legacy/MDTypography";
|
import MDTypography from "qqq/components/legacy/MDTypography";
|
||||||
import {chartColors} from "qqq/components/widgets/charts/DefaultChartData";
|
import {chartColors} from "qqq/components/widgets/charts/DefaultChartData";
|
||||||
import configs from "qqq/components/widgets/charts/piechart/PieChartConfigs";
|
import configs from "qqq/components/widgets/charts/piechart/PieChartConfigs";
|
||||||
|
import ChartSubheaderWithData, {ChartSubheaderData} from "qqq/components/widgets/components/ChartSubheaderWithData";
|
||||||
|
|
||||||
//////////////////////////////////////////
|
//////////////////////////////////////////
|
||||||
// structure of expected bar chart data //
|
// structure of expected bar chart data //
|
||||||
@ -51,25 +52,29 @@ interface Props
|
|||||||
{
|
{
|
||||||
description?: string;
|
description?: string;
|
||||||
chartData: PieChartData;
|
chartData: PieChartData;
|
||||||
|
chartSubheaderData?: ChartSubheaderData;
|
||||||
|
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function PieChart({description, chartData}: Props): JSX.Element
|
function PieChart({description, chartData, chartSubheaderData}: Props): JSX.Element
|
||||||
{
|
{
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [dataLoaded, setDataLoaded] = useState(false);
|
const [dataLoaded, setDataLoaded] = useState(false);
|
||||||
|
|
||||||
if (chartData && chartData.dataset)
|
if (chartData && chartData.dataset)
|
||||||
|
{
|
||||||
|
if(!chartData.dataset.backgroundColors)
|
||||||
{
|
{
|
||||||
chartData.dataset.backgroundColors = chartColors;
|
chartData.dataset.backgroundColors = chartColors;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
const {data, options} = configs(chartData?.labels || [], chartData?.dataset || {});
|
const {data, options} = configs(chartData?.labels || [], chartData?.dataset || {});
|
||||||
|
|
||||||
useEffect(() =>
|
useEffect(() =>
|
||||||
{
|
{
|
||||||
if(chartData)
|
if (chartData)
|
||||||
{
|
{
|
||||||
setDataLoaded(true);
|
setDataLoaded(true);
|
||||||
}
|
}
|
||||||
@ -77,19 +82,22 @@ function PieChart({description, chartData}: Props): JSX.Element
|
|||||||
|
|
||||||
const handleClick = (e: Array<{}>) =>
|
const handleClick = (e: Array<{}>) =>
|
||||||
{
|
{
|
||||||
if(e && e.length > 0 && chartData?.dataset?.urls && chartData?.dataset?.urls.length)
|
if (e && e.length > 0 && chartData?.dataset?.urls && chartData?.dataset?.urls.length)
|
||||||
{
|
{
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
navigate(chartData.dataset.urls[e[0]["index"]]);
|
navigate(chartData.dataset.urls[e[0]["index"]]);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card sx={{minHeight: "400px", boxShadow: "none", height: "100%", width: "100%", display: "flex", flexGrow: 1}}>
|
<Card sx={{boxShadow: "none", height: "100%", width: "100%", display: "flex", flexGrow: 1, border: 0}}>
|
||||||
<Box mt={3}>
|
<Box mt={1}>
|
||||||
|
<Box px={3}>
|
||||||
|
{chartSubheaderData && (<ChartSubheaderWithData chartSubheaderData={chartSubheaderData} />)}
|
||||||
|
</Box>
|
||||||
<Grid container alignItems="center">
|
<Grid container alignItems="center">
|
||||||
<Grid item xs={12} justifyContent="center">
|
<Grid item xs={12} justifyContent="center">
|
||||||
<Box width="100%" height="80%" py={2} pr={2} pl={2}>
|
<Box width="100%" height="300px" py={2} pr={2} pl={2}>
|
||||||
{useMemo(
|
{useMemo(
|
||||||
() => (
|
() => (
|
||||||
<Pie data={data} options={options} getElementsAtEvent={handleClick} />
|
<Pie data={data} options={options} getElementsAtEvent={handleClick} />
|
||||||
@ -98,23 +106,25 @@ function PieChart({description, chartData}: Props): JSX.Element
|
|||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
{
|
{
|
||||||
! chartData && (
|
!chartData && (
|
||||||
<Box sx={{
|
<Box sx={{
|
||||||
position: "absolute",
|
position: "absolute",
|
||||||
top: "40%",
|
top: "40%",
|
||||||
left: "50%",
|
left: "50%",
|
||||||
transform: "translate(-50%, -50%)",
|
transform: "translate(-50%, -50%)",
|
||||||
display: "flex",
|
display: "flex",
|
||||||
justifyContent: "center"}}>
|
justifyContent: "center"
|
||||||
<Skeleton sx={{width: "150px", height: "150px"}} variant="circular"/>
|
}}>
|
||||||
|
<Skeleton sx={{width: "150px", height: "150px"}} variant="circular" />
|
||||||
</Box>
|
</Box>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Divider />
|
|
||||||
{
|
{
|
||||||
description && (
|
description && (
|
||||||
|
<>
|
||||||
|
<Divider />
|
||||||
<Grid container>
|
<Grid container>
|
||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
<Box pb={2} px={2} display="flex" flexDirection={{xs: "column", sm: "row"}} mt="auto">
|
<Box pb={2} px={2} display="flex" flexDirection={{xs: "column", sm: "row"}} mt="auto">
|
||||||
@ -124,6 +134,7 @@ function PieChart({description, chartData}: Props): JSX.Element
|
|||||||
</Box>
|
</Box>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
</Box>
|
</Box>
|
||||||
|
@ -30,10 +30,16 @@ function configs(labels: any, datasets: any)
|
|||||||
if (datasets.backgroundColors)
|
if (datasets.backgroundColors)
|
||||||
{
|
{
|
||||||
datasets.backgroundColors.forEach((color: string) =>
|
datasets.backgroundColors.forEach((color: string) =>
|
||||||
gradients[color]
|
{
|
||||||
? backgroundColors.push(gradients[color].state)
|
if (gradients[color])
|
||||||
: backgroundColors.push(dark.main)
|
{
|
||||||
);
|
backgroundColors.push(gradients[color].state);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
backgroundColors.push(color);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -58,12 +64,33 @@ function configs(labels: any, datasets: any)
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
options: {
|
options: {
|
||||||
maintainAspectRatio: true,
|
maintainAspectRatio: false,
|
||||||
responsive: true,
|
responsive: true,
|
||||||
aspectRatio: 2,
|
|
||||||
plugins: {
|
plugins: {
|
||||||
|
tooltip: {
|
||||||
|
callbacks: {
|
||||||
|
label: function(context: any)
|
||||||
|
{
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// our labels already have the value in them - so just use the label in the //
|
||||||
|
// tooltip (lib by default puts label + value, so we were duplicating value!) //
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
return context.label;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
legend: {
|
legend: {
|
||||||
position: "bottom",
|
position: "bottom",
|
||||||
|
labels: {
|
||||||
|
usePointStyle: true,
|
||||||
|
pointStyle: "circle",
|
||||||
|
padding: 12,
|
||||||
|
boxHeight: 8,
|
||||||
|
boxWidth: 8,
|
||||||
|
font: {
|
||||||
|
size: 14
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
scales: {
|
scales: {
|
||||||
|
105
src/qqq/components/widgets/components/ChartSubheaderWithData.tsx
Normal file
105
src/qqq/components/widgets/components/ChartSubheaderWithData.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 {Skeleton} from "@mui/material";
|
||||||
|
import Box from "@mui/material/Box";
|
||||||
|
import Icon from "@mui/material/Icon";
|
||||||
|
import Typography from "@mui/material/Typography";
|
||||||
|
import React from "react";
|
||||||
|
import {Link} from "react-router-dom";
|
||||||
|
import colors from "qqq/assets/theme/base/colors";
|
||||||
|
import ValueUtils from "qqq/utils/qqq/ValueUtils";
|
||||||
|
|
||||||
|
export interface ChartSubheaderData
|
||||||
|
{
|
||||||
|
mainNumber: number;
|
||||||
|
vsPreviousPercent: number;
|
||||||
|
vsPreviousNumber: number;
|
||||||
|
isUpVsPrevious: boolean;
|
||||||
|
isGoodVsPrevious: boolean;
|
||||||
|
vsDescription: string;
|
||||||
|
mainNumberUrl: string;
|
||||||
|
previousNumberUrl: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props
|
||||||
|
{
|
||||||
|
chartSubheaderData: ChartSubheaderData;
|
||||||
|
}
|
||||||
|
|
||||||
|
const GOOD_COLOR = colors.success.main;
|
||||||
|
const BAD_COLOR = colors.error.main;
|
||||||
|
const UP_ICON = "arrow_drop_up";
|
||||||
|
const DOWN_ICON = "arrow_drop_down";
|
||||||
|
|
||||||
|
function StackedBarChart({chartSubheaderData}: Props): JSX.Element
|
||||||
|
{
|
||||||
|
let color = "black";
|
||||||
|
if (chartSubheaderData && chartSubheaderData.isGoodVsPrevious != null)
|
||||||
|
{
|
||||||
|
color = chartSubheaderData.isGoodVsPrevious ? GOOD_COLOR : BAD_COLOR;
|
||||||
|
}
|
||||||
|
|
||||||
|
let iconName: string = null;
|
||||||
|
if (chartSubheaderData && chartSubheaderData.isUpVsPrevious != null)
|
||||||
|
{
|
||||||
|
iconName = chartSubheaderData.isUpVsPrevious ? UP_ICON : DOWN_ICON;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mainNumberElement = <Typography variant="h2" display="inline">{ValueUtils.getFormattedNumber(chartSubheaderData.mainNumber)}</Typography>;
|
||||||
|
if(chartSubheaderData.mainNumberUrl)
|
||||||
|
{
|
||||||
|
mainNumberElement = <Link to={chartSubheaderData.mainNumberUrl}>{mainNumberElement}</Link>
|
||||||
|
}
|
||||||
|
mainNumberElement = <Box pr={1}>{mainNumberElement}</Box>
|
||||||
|
|
||||||
|
let previousNumberElement = (
|
||||||
|
<>
|
||||||
|
<Typography display="inline" variant="body2" sx={{color: colors.black.main}}>
|
||||||
|
{chartSubheaderData.vsDescription}
|
||||||
|
{chartSubheaderData.vsPreviousNumber && (<> ({ValueUtils.getFormattedNumber(chartSubheaderData.vsPreviousNumber)})</>)}
|
||||||
|
</Typography>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
|
||||||
|
if(chartSubheaderData.previousNumberUrl)
|
||||||
|
{
|
||||||
|
previousNumberElement = <Link to={chartSubheaderData.previousNumberUrl}>{previousNumberElement}</Link>
|
||||||
|
}
|
||||||
|
|
||||||
|
return chartSubheaderData ? (
|
||||||
|
<Box display="inline-flex" alignItems="flex-end" flexWrap="wrap">
|
||||||
|
{mainNumberElement}
|
||||||
|
{
|
||||||
|
chartSubheaderData.vsPreviousPercent != null && iconName != null && (
|
||||||
|
<Box display="inline-flex" alignItems="flex-end" pb={1} ml={-0.5}>
|
||||||
|
<Icon fontSize="medium" sx={{color: color}}>{iconName}</Icon>
|
||||||
|
<Typography display="inline" variant="body2" sx={{color: color}}>{chartSubheaderData.vsPreviousPercent}%</Typography>
|
||||||
|
{previousNumberElement}
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</Box>
|
||||||
|
) : <Skeleton sx={{marginLeft: "20px", marginRight: "20px", height: "12px"}} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default StackedBarChart;
|
@ -286,18 +286,15 @@ export default function DataBagViewer({dataBagId}: Props): JSX.Element
|
|||||||
<Grid container spacing={3}>
|
<Grid container spacing={3}>
|
||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
<>
|
<>
|
||||||
<Box display="flex" alignItems="center" justifyContent="space-between" gap={2} mt={-6}>
|
|
||||||
<Typography variant="h5" p={2}></Typography>
|
|
||||||
<Tabs
|
<Tabs
|
||||||
sx={{m: 1}}
|
sx={{m: 0, mb: 1, mt: -3}}
|
||||||
value={selectedTab}
|
value={selectedTab}
|
||||||
onChange={(event, newValue) => changeTab(newValue)}
|
onChange={(event, newValue) => changeTab(newValue)}
|
||||||
variant="standard"
|
variant="standard"
|
||||||
>
|
>
|
||||||
<Tab label="Raw Data" id="simple-tab-0" aria-controls="simple-tabpanel-0" sx={{width: "150px"}} />
|
<Tab label="Raw Data" id="simple-tab-0" aria-controls="simple-tabpanel-0" />
|
||||||
<Tab label="Data Preview" id="simple-tab-1" aria-controls="simple-tabpanel-1" sx={{width: "150px"}} />
|
<Tab label="Data Preview" id="simple-tab-1" aria-controls="simple-tabpanel-1" />
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</Box>
|
|
||||||
|
|
||||||
<TabPanel index={0} value={selectedTab}>
|
<TabPanel index={0} value={selectedTab}>
|
||||||
<Grid container>
|
<Grid container>
|
||||||
|
@ -430,20 +430,17 @@ export default function ScriptViewer({scriptId, associatedScriptTableName, assoc
|
|||||||
<Grid container spacing={3}>
|
<Grid container spacing={3}>
|
||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
<>
|
<>
|
||||||
<Box display="flex" alignItems="center" justifyContent="space-between" gap={2} mt={-6}>
|
|
||||||
<Typography variant="h5" p={2}></Typography>
|
|
||||||
<Tabs
|
<Tabs
|
||||||
sx={{m: 1}}
|
sx={{m: 0, mb: 1, mt: -3}}
|
||||||
value={selectedTab}
|
value={selectedTab}
|
||||||
onChange={(event, newValue) => changeTab(newValue)}
|
onChange={(event, newValue) => changeTab(newValue)}
|
||||||
variant="standard"
|
variant="standard"
|
||||||
>
|
>
|
||||||
<Tab label="Code" id="simple-tab-0" aria-controls="simple-tabpanel-0" sx={{width: "100px"}} />
|
<Tab label="Code" id="simple-tab-0" aria-controls="simple-tabpanel-0" />
|
||||||
<Tab label="Logs" id="simple-tab-1" aria-controls="simple-tabpanel-1" sx={{width: "100px"}} />
|
<Tab label="Logs" id="simple-tab-1" aria-controls="simple-tabpanel-1" />
|
||||||
<Tab label="Test" id="simple-tab-1" aria-controls="simple-tabpanel-2" sx={{width: "100px"}} />
|
<Tab label="Test" id="simple-tab-1" aria-controls="simple-tabpanel-2" />
|
||||||
<Tab label="Docs" id="simple-tab-1" aria-controls="simple-tabpanel-3" sx={{width: "100px"}} />
|
<Tab label="Docs" id="simple-tab-1" aria-controls="simple-tabpanel-3" />
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</Box>
|
|
||||||
|
|
||||||
<TabPanel index={0} value={selectedTab}>
|
<TabPanel index={0} value={selectedTab}>
|
||||||
<Grid container>
|
<Grid container>
|
||||||
@ -498,7 +495,7 @@ export default function ScriptViewer({scriptId, associatedScriptTableName, assoc
|
|||||||
editorProps={{$blockScrolling: true}}
|
editorProps={{$blockScrolling: true}}
|
||||||
setOptions={{useWorker: false}}
|
setOptions={{useWorker: false}}
|
||||||
width="100%"
|
width="100%"
|
||||||
height="368px"
|
height="400px"
|
||||||
value={getSelectedFileCode()}
|
value={getSelectedFileCode()}
|
||||||
style={{borderTop: "1px solid lightgray", borderBottomRightRadius: "1rem"}}
|
style={{borderTop: "1px solid lightgray", borderBottomRightRadius: "1rem"}}
|
||||||
/>
|
/>
|
||||||
|
@ -30,7 +30,7 @@ import TableRow from "@mui/material/TableRow";
|
|||||||
import Tooltip from "@mui/material/Tooltip";
|
import Tooltip from "@mui/material/Tooltip";
|
||||||
import parse from "html-react-parser";
|
import parse from "html-react-parser";
|
||||||
import {useEffect, useMemo, useState} from "react";
|
import {useEffect, useMemo, useState} from "react";
|
||||||
import {useAsyncDebounce, useGlobalFilter, usePagination, useSortBy, useTable} from "react-table";
|
import {useAsyncDebounce, useGlobalFilter, usePagination, useSortBy, useTable, useExpanded} from "react-table";
|
||||||
import MDInput from "qqq/components/legacy/MDInput";
|
import MDInput from "qqq/components/legacy/MDInput";
|
||||||
import MDPagination from "qqq/components/legacy/MDPagination";
|
import MDPagination from "qqq/components/legacy/MDPagination";
|
||||||
import MDTypography from "qqq/components/legacy/MDTypography";
|
import MDTypography from "qqq/components/legacy/MDTypography";
|
||||||
@ -47,6 +47,8 @@ interface Props
|
|||||||
canSearch?: boolean;
|
canSearch?: boolean;
|
||||||
showTotalEntries?: boolean;
|
showTotalEntries?: boolean;
|
||||||
hidePaginationDropdown?: boolean;
|
hidePaginationDropdown?: boolean;
|
||||||
|
fixedStickyLastRow?: boolean;
|
||||||
|
fixedHeight?: number;
|
||||||
table: TableDataInput;
|
table: TableDataInput;
|
||||||
pagination?: {
|
pagination?: {
|
||||||
variant: "contained" | "gradient";
|
variant: "contained" | "gradient";
|
||||||
@ -56,6 +58,18 @@ interface Props
|
|||||||
noEndBorder?: boolean;
|
noEndBorder?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DataTable.defaultProps = {
|
||||||
|
entriesPerPage: 10,
|
||||||
|
entriesPerPageOptions: ["5", "10", "15", "20", "25"],
|
||||||
|
canSearch: false,
|
||||||
|
showTotalEntries: true,
|
||||||
|
fixedStickyLastRow: false,
|
||||||
|
fixedHeight: null,
|
||||||
|
pagination: {variant: "gradient", color: "info"},
|
||||||
|
isSorted: true,
|
||||||
|
noEndBorder: false,
|
||||||
|
};
|
||||||
|
|
||||||
const NoMaxWidthTooltip = styled(({className, ...props}: TooltipProps) => (
|
const NoMaxWidthTooltip = styled(({className, ...props}: TooltipProps) => (
|
||||||
<Tooltip {...props} classes={{popper: className}} />
|
<Tooltip {...props} classes={{popper: className}} />
|
||||||
))({
|
))({
|
||||||
@ -71,6 +85,8 @@ function DataTable({
|
|||||||
hidePaginationDropdown,
|
hidePaginationDropdown,
|
||||||
canSearch,
|
canSearch,
|
||||||
showTotalEntries,
|
showTotalEntries,
|
||||||
|
fixedStickyLastRow,
|
||||||
|
fixedHeight,
|
||||||
table,
|
table,
|
||||||
pagination,
|
pagination,
|
||||||
isSorted,
|
isSorted,
|
||||||
@ -83,8 +99,77 @@ function DataTable({
|
|||||||
defaultValue = (entriesPerPage) ? entriesPerPage : "10";
|
defaultValue = (entriesPerPage) ? entriesPerPage : "10";
|
||||||
entries = entriesPerPageOptions ? entriesPerPageOptions : ["10", "25", "50", "100"];
|
entries = entriesPerPageOptions ? entriesPerPageOptions : ["10", "25", "50", "100"];
|
||||||
|
|
||||||
const columns = useMemo<any>(() => table.columns, [table]);
|
let widths = [];
|
||||||
|
for(let i = 0; i<table.columns.length; i++)
|
||||||
|
{
|
||||||
|
const column = table.columns[i];
|
||||||
|
if(column.type !== "hidden")
|
||||||
|
{
|
||||||
|
widths.push(table.columns[i].width ?? "1fr");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let showExpandColumn = false;
|
||||||
|
if(table.rows)
|
||||||
|
{
|
||||||
|
for (let i = 0; i < table.rows.length; i++)
|
||||||
|
{
|
||||||
|
if (table.rows[i].subRows)
|
||||||
|
{
|
||||||
|
showExpandColumn = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const columnsToMemo = [...table.columns];
|
||||||
|
if(showExpandColumn)
|
||||||
|
{
|
||||||
|
widths.push("60px");
|
||||||
|
columnsToMemo.push(
|
||||||
|
{
|
||||||
|
///////////////////////////////
|
||||||
|
// Build our expander column //
|
||||||
|
///////////////////////////////
|
||||||
|
id: "__expander",
|
||||||
|
width: 60,
|
||||||
|
|
||||||
|
////////////////////////////////////////////////
|
||||||
|
// use this block if we want to do expand-all //
|
||||||
|
////////////////////////////////////////////////
|
||||||
|
// @ts-ignore
|
||||||
|
// header: ({getToggleAllRowsExpandedProps, isAllRowsExpanded}) => (
|
||||||
|
// <span {...getToggleAllRowsExpandedProps()}>
|
||||||
|
// {isAllRowsExpanded ? "yes" : "no"}
|
||||||
|
// </span>
|
||||||
|
// ),
|
||||||
|
header: () => (<span />),
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
cell: ({row}) =>
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Use the row.canExpand and row.getToggleRowExpandedProps prop getter to build the toggle for expanding a row //
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
row.canExpand ? (
|
||||||
|
<span
|
||||||
|
{...row.getToggleRowExpandedProps({
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// We could use the row.depth property and paddingLeft to indicate the depth of the row //
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// style: {paddingLeft: `${row.depth * 2}rem`,},
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{/* float this icon to keep it "out of the flow" - in other words, to keep it from making the row taller than it otherwise would be... */}
|
||||||
|
<Icon fontSize="medium" sx={{float: "left"}}>{row.isExpanded ? "expand_less" : "chevron_right"}</Icon>
|
||||||
|
</span>
|
||||||
|
) : null,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const columns = useMemo<any>(() => columnsToMemo, [table]);
|
||||||
const data = useMemo<any>(() => table.rows, [table]);
|
const data = useMemo<any>(() => table.rows, [table]);
|
||||||
|
const gridTemplateColumns = widths.join(" ");
|
||||||
|
|
||||||
if (!columns || !data)
|
if (!columns || !data)
|
||||||
{
|
{
|
||||||
@ -95,6 +180,7 @@ function DataTable({
|
|||||||
{columns, data, initialState: {pageIndex: 0}},
|
{columns, data, initialState: {pageIndex: 0}},
|
||||||
useGlobalFilter,
|
useGlobalFilter,
|
||||||
useSortBy,
|
useSortBy,
|
||||||
|
useExpanded,
|
||||||
usePagination
|
usePagination
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -113,7 +199,7 @@ function DataTable({
|
|||||||
previousPage,
|
previousPage,
|
||||||
setPageSize,
|
setPageSize,
|
||||||
setGlobalFilter,
|
setGlobalFilter,
|
||||||
state: {pageIndex, pageSize, globalFilter},
|
state: {pageIndex, pageSize, globalFilter, expanded},
|
||||||
}: any = tableInstance;
|
}: any = tableInstance;
|
||||||
|
|
||||||
// Set the default value for the entries per page when component mounts
|
// Set the default value for the entries per page when component mounts
|
||||||
@ -193,63 +279,27 @@ function DataTable({
|
|||||||
entriesEnd = pageSize * (pageIndex + 1);
|
entriesEnd = pageSize * (pageIndex + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
function getTable(includeHead: boolean, rows: any, isFooter: boolean)
|
||||||
<TableContainer sx={{boxShadow: "none"}}>
|
|
||||||
{entriesPerPage && ((hidePaginationDropdown !== undefined && ! hidePaginationDropdown) || canSearch) ? (
|
|
||||||
<Box display="flex" justifyContent="space-between" alignItems="center" p={3}>
|
|
||||||
{entriesPerPage && (hidePaginationDropdown === undefined || ! hidePaginationDropdown) && (
|
|
||||||
<Box display="flex" alignItems="center">
|
|
||||||
<Autocomplete
|
|
||||||
disableClearable
|
|
||||||
value={pageSize.toString()}
|
|
||||||
options={entries}
|
|
||||||
onChange={(event, newValues: any) =>
|
|
||||||
{
|
{
|
||||||
if(typeof newValues === "string")
|
let boxStyle = {};
|
||||||
|
if(fixedStickyLastRow)
|
||||||
{
|
{
|
||||||
setEntriesPerPage(parseInt(newValues, 10));
|
boxStyle = isFooter ? {overflowY: "visible", borderTop: "0.0625rem solid #f0f2f5;"} : {height: fixedHeight ? `${fixedHeight}px` : "360px", overflowY: "auto"};
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
const className = isFooter ? "hideScrollbars" : "";
|
||||||
setEntriesPerPage(parseInt(newValues[0], 10));
|
return <Box sx={boxStyle} className={className}>
|
||||||
}
|
|
||||||
}}
|
|
||||||
size="small"
|
|
||||||
sx={{width: "5rem"}}
|
|
||||||
renderInput={(params) => <MDInput {...params} />}
|
|
||||||
/>
|
|
||||||
<MDTypography variant="caption" color="secondary">
|
|
||||||
entries per page
|
|
||||||
</MDTypography>
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
{canSearch && (
|
|
||||||
<Box width="12rem" ml="auto">
|
|
||||||
<MDInput
|
|
||||||
placeholder="Search..."
|
|
||||||
value={search}
|
|
||||||
size="small"
|
|
||||||
fullWidth
|
|
||||||
onChange={({currentTarget}: any) =>
|
|
||||||
{
|
|
||||||
setSearch(search);
|
|
||||||
onSearchChange(currentTarget.value);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
) : null}
|
|
||||||
<Table {...getTableProps()}>
|
<Table {...getTableProps()}>
|
||||||
<Box component="thead">
|
{
|
||||||
|
includeHead && (
|
||||||
|
<Box component="thead" sx={{position: "sticky", top: 0, background: "white"}}>
|
||||||
{headerGroups.map((headerGroup: any, i: number) => (
|
{headerGroups.map((headerGroup: any, i: number) => (
|
||||||
<TableRow key={i} {...headerGroup.getHeaderGroupProps()}>
|
<TableRow key={i} {...headerGroup.getHeaderGroupProps()} sx={{display: "grid", gridTemplateColumns: gridTemplateColumns}}>
|
||||||
{headerGroup.headers.map((column: any) => (
|
{headerGroup.headers.map((column: any) => (
|
||||||
column.type !== "hidden" && (
|
column.type !== "hidden" && (
|
||||||
<DataTableHeadCell
|
<DataTableHeadCell
|
||||||
key={i++}
|
key={i++}
|
||||||
{...column.getHeaderProps(isSorted && column.getSortByToggleProps())}
|
{...column.getHeaderProps(isSorted && column.getSortByToggleProps())}
|
||||||
width={column.width ? column.width : "auto"}
|
|
||||||
align={column.align ? column.align : "left"}
|
align={column.align ? column.align : "left"}
|
||||||
sorted={setSortedValue(column)}
|
sorted={setSortedValue(column)}
|
||||||
>
|
>
|
||||||
@ -260,12 +310,14 @@ function DataTable({
|
|||||||
</TableRow>
|
</TableRow>
|
||||||
))}
|
))}
|
||||||
</Box>
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
<TableBody {...getTableBodyProps()}>
|
<TableBody {...getTableBodyProps()}>
|
||||||
{page.map((row: any, key: any) =>
|
{rows.map((row: any, key: any) =>
|
||||||
{
|
{
|
||||||
prepareRow(row);
|
prepareRow(row);
|
||||||
return (
|
return (
|
||||||
<TableRow sx={{verticalAlign: "top"}} key={key} {...row.getRowProps()}>
|
<TableRow sx={{verticalAlign: "top", display: "grid", gridTemplateColumns: gridTemplateColumns, background: (row.depth > 0 ? "#FAFAFA" : "initial")}} key={key} {...row.getRowProps()}>
|
||||||
{row.cells.map((cell: any) => (
|
{row.cells.map((cell: any) => (
|
||||||
cell.column.type !== "hidden" && (
|
cell.column.type !== "hidden" && (
|
||||||
<DataTableBodyCell
|
<DataTableBodyCell
|
||||||
@ -308,6 +360,9 @@ function DataTable({
|
|||||||
<ImageCell imageUrl={row.values["imageUrl"]} label={row.values["imageLabel"]} />
|
<ImageCell imageUrl={row.values["imageUrl"]} label={row.values["imageLabel"]} />
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
(cell.column.id === "__expander") && cell.render("cell")
|
||||||
|
}
|
||||||
</DataTableBodyCell>
|
</DataTableBodyCell>
|
||||||
)
|
)
|
||||||
))}
|
))}
|
||||||
@ -316,6 +371,65 @@ function DataTable({
|
|||||||
})}
|
})}
|
||||||
</TableBody>
|
</TableBody>
|
||||||
</Table>
|
</Table>
|
||||||
|
</Box>
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TableContainer sx={{boxShadow: "none"}}>
|
||||||
|
{entriesPerPage && ((hidePaginationDropdown !== undefined && !hidePaginationDropdown) || canSearch) ? (
|
||||||
|
<Box display="flex" justifyContent="space-between" alignItems="center" p={3}>
|
||||||
|
{entriesPerPage && (hidePaginationDropdown === undefined || !hidePaginationDropdown) && (
|
||||||
|
<Box display="flex" alignItems="center">
|
||||||
|
<Autocomplete
|
||||||
|
disableClearable
|
||||||
|
value={pageSize.toString()}
|
||||||
|
options={entries}
|
||||||
|
onChange={(event, newValues: any) =>
|
||||||
|
{
|
||||||
|
if (typeof newValues === "string")
|
||||||
|
{
|
||||||
|
setEntriesPerPage(parseInt(newValues, 10));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
setEntriesPerPage(parseInt(newValues[0], 10));
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
size="small"
|
||||||
|
sx={{width: "5rem"}}
|
||||||
|
renderInput={(params) => <MDInput {...params} />}
|
||||||
|
/>
|
||||||
|
<MDTypography variant="caption" color="secondary">
|
||||||
|
entries per page
|
||||||
|
</MDTypography>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
{canSearch && (
|
||||||
|
<Box width="12rem" ml="auto">
|
||||||
|
<MDInput
|
||||||
|
placeholder="Search..."
|
||||||
|
value={search}
|
||||||
|
size="small"
|
||||||
|
fullWidth
|
||||||
|
onChange={({currentTarget}: any) =>
|
||||||
|
{
|
||||||
|
setSearch(search);
|
||||||
|
onSearchChange(currentTarget.value);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{
|
||||||
|
fixedStickyLastRow ? (
|
||||||
|
<>
|
||||||
|
{getTable(true, page.slice(0, page.length -1), false)}
|
||||||
|
{getTable(false, page.slice(page.length-1), true)}
|
||||||
|
</>
|
||||||
|
) : getTable(true, page, false)
|
||||||
|
}
|
||||||
|
|
||||||
<Box
|
<Box
|
||||||
display="flex"
|
display="flex"
|
||||||
@ -368,15 +482,4 @@ function DataTable({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Declaring default props for DataTable
|
|
||||||
DataTable.defaultProps = {
|
|
||||||
entriesPerPage: 10,
|
|
||||||
entriesPerPageOptions: ["5", "10", "15", "20", "25"],
|
|
||||||
canSearch: false,
|
|
||||||
showTotalEntries: true,
|
|
||||||
pagination: {variant: "gradient", color: "info"},
|
|
||||||
isSorted: true,
|
|
||||||
noEndBorder: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default DataTable;
|
export default DataTable;
|
||||||
|
@ -54,11 +54,13 @@ interface Props
|
|||||||
noRowsFoundHTML?: string;
|
noRowsFoundHTML?: string;
|
||||||
rowsPerPage?: number;
|
rowsPerPage?: number;
|
||||||
hidePaginationDropdown?: boolean;
|
hidePaginationDropdown?: boolean;
|
||||||
|
fixedStickyLastRow?: boolean;
|
||||||
|
fixedHeight?: number;
|
||||||
data: TableDataInput;
|
data: TableDataInput;
|
||||||
}
|
}
|
||||||
|
|
||||||
const qController = Client.getInstance();
|
const qController = Client.getInstance();
|
||||||
function TableCard({noRowsFoundHTML, data, rowsPerPage, hidePaginationDropdown}: Props): JSX.Element
|
function TableCard({noRowsFoundHTML, data, rowsPerPage, hidePaginationDropdown, fixedStickyLastRow, fixedHeight}: Props): JSX.Element
|
||||||
{
|
{
|
||||||
const [qInstance, setQInstance] = useState(null as QInstance);
|
const [qInstance, setQInstance] = useState(null as QInstance);
|
||||||
|
|
||||||
@ -79,6 +81,8 @@ function TableCard({noRowsFoundHTML, data, rowsPerPage, hidePaginationDropdown}:
|
|||||||
table={data}
|
table={data}
|
||||||
entriesPerPage={rowsPerPage}
|
entriesPerPage={rowsPerPage}
|
||||||
hidePaginationDropdown={hidePaginationDropdown}
|
hidePaginationDropdown={hidePaginationDropdown}
|
||||||
|
fixedStickyLastRow={fixedStickyLastRow}
|
||||||
|
fixedHeight={fixedHeight}
|
||||||
showTotalEntries={false}
|
showTotalEntries={false}
|
||||||
isSorted={false}
|
isSorted={false}
|
||||||
noEndBorder
|
noEndBorder
|
||||||
|
@ -143,6 +143,8 @@ function TableWidget(props: Props): JSX.Element
|
|||||||
noRowsFoundHTML={props.widgetData?.noRowsFoundHTML}
|
noRowsFoundHTML={props.widgetData?.noRowsFoundHTML}
|
||||||
rowsPerPage={props.widgetData?.rowsPerPage}
|
rowsPerPage={props.widgetData?.rowsPerPage}
|
||||||
hidePaginationDropdown={props.widgetData?.hidePaginationDropdown}
|
hidePaginationDropdown={props.widgetData?.hidePaginationDropdown}
|
||||||
|
fixedStickyLastRow={props.widgetData?.fixedStickyLastRow}
|
||||||
|
fixedHeight={props.widgetData?.fixedHeight}
|
||||||
data={{columns: props.widgetData?.columns, rows: props.widgetData?.rows}}
|
data={{columns: props.widgetData?.columns, rows: props.widgetData?.rows}}
|
||||||
/>
|
/>
|
||||||
</Widget>
|
</Widget>
|
||||||
|
@ -323,7 +323,7 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is
|
|||||||
<Grid m={3} mt={9} container>
|
<Grid m={3} mt={9} container>
|
||||||
<Grid item xs={0} lg={3} />
|
<Grid item xs={0} lg={3} />
|
||||||
<Grid item xs={12} lg={6}>
|
<Grid item xs={12} lg={6}>
|
||||||
<Card elevation={5}>
|
<Card>
|
||||||
<Box p={3}>
|
<Box p={3}>
|
||||||
<MDTypography variant="h5" component="div">
|
<MDTypography variant="h5" component="div">
|
||||||
Working
|
Working
|
||||||
@ -1278,6 +1278,7 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is
|
|||||||
{
|
{
|
||||||
mainCardStyles.background = "none";
|
mainCardStyles.background = "none";
|
||||||
mainCardStyles.boxShadow = "none";
|
mainCardStyles.boxShadow = "none";
|
||||||
|
mainCardStyles.border = "none";
|
||||||
mainCardStyles.minHeight = "";
|
mainCardStyles.minHeight = "";
|
||||||
mainCardStyles.alignItems = "stretch";
|
mainCardStyles.alignItems = "stretch";
|
||||||
mainCardStyles.flexGrow = 1;
|
mainCardStyles.flexGrow = 1;
|
||||||
|
@ -2017,7 +2017,7 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
|||||||
page={pageNumber}
|
page={pageNumber}
|
||||||
checkboxSelection
|
checkboxSelection
|
||||||
disableSelectionOnClick
|
disableSelectionOnClick
|
||||||
autoHeight
|
autoHeight={false}
|
||||||
rows={rows}
|
rows={rows}
|
||||||
// getRowHeight={() => "auto"} // maybe nice? wraps values in cells...
|
// getRowHeight={() => "auto"} // maybe nice? wraps values in cells...
|
||||||
columns={columnsModel}
|
columns={columnsModel}
|
||||||
@ -2041,6 +2041,7 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
|||||||
getRowId={(row) => row.__rowIndex}
|
getRowId={(row) => row.__rowIndex}
|
||||||
selectionModel={rowSelectionModel}
|
selectionModel={rowSelectionModel}
|
||||||
hideFooterSelectedRowCount={true}
|
hideFooterSelectedRowCount={true}
|
||||||
|
sx={{border: 0, height: "calc(100vh - 250px)"}}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
</Card>
|
</Card>
|
||||||
|
@ -190,7 +190,7 @@ function RecordDeveloperView({table}: Props): JSX.Element
|
|||||||
return (
|
return (
|
||||||
<div key={fieldName}>
|
<div key={fieldName}>
|
||||||
<Card sx={{mb: 3}}>
|
<Card sx={{mb: 3}}>
|
||||||
<Typography variant="h6" p={2} pl={3} pb={1}>{field?.label}</Typography>
|
<Typography variant="h6" p={2} pl={3} pb={3}>{field?.label}</Typography>
|
||||||
|
|
||||||
<Box display="flex" alignItems="center" justifyContent="space-between" gap={2}>
|
<Box display="flex" alignItems="center" justifyContent="space-between" gap={2}>
|
||||||
{scriptId ?
|
{scriptId ?
|
||||||
|
@ -564,3 +564,14 @@ input[type="search"]::-webkit-search-results-decoration { display: none; }
|
|||||||
display: inline;
|
display: inline;
|
||||||
right: .5rem
|
right: .5rem
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.hideScrollbars::-webkit-scrollbar {
|
||||||
|
background: transparent; /* Chrome/Safari/Webkit */
|
||||||
|
width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hideScrollbars {
|
||||||
|
padding-right: 8px; /* pad-right for about half the width of a scrollbar.. */
|
||||||
|
scrollbar-width: none; /* Firefox */
|
||||||
|
-ms-overflow-style: none; /* IE 10+ */
|
||||||
|
}
|
Reference in New Issue
Block a user