Compare commits

..

33 Commits

Author SHA1 Message Date
0879cb4f80 CE-604 Let data from backend drive backgroundColors if it wants 2023-11-03 19:23:53 -05:00
f1b0618e9d CE-604 Disable tooltips for now (our only active use case doesn't want them) 2023-11-03 19:23:38 -05:00
95f1fa83bb CE-604 Change to use xxl instead of lg for using default grid sizing (more likely to go 12 now) 2023-11-03 09:53:53 -05:00
4b0e12ba47 CE-604 Adjust a height in light of other redesign updates 2023-11-03 09:12:28 -05:00
6cfb1e04ed CE-604 Adjust a padding in light of tabs 2023-11-03 09:11:45 -05:00
0d763cbfc8 CE-604 Adjust legend dot size, padding, and font 2023-11-02 20:14:56 -05:00
279840e77a CE-604 Increase height of pie & stacked bar chart from 250 to 300 - surprisingly seems better than just a 50px improvement 2023-11-02 20:04:51 -05:00
51b2f5bb5a CE-604 Better wrapping (flex-wrap!) 2023-11-02 19:56:55 -05:00
02fe351084 make sure rows exist in table data before iterating over them 2023-11-02 14:40:29 -05:00
25fa2e82ea Merged main into feature/CE-604-complete-shipment-sla-updates-and-local-tnt-rules 2023-10-27 16:05:08 -05:00
24b4674208 Merged feature/remove-old-auth-header into feature/CE-604-complete-shipment-sla-updates-and-local-tnt-rules 2023-10-27 16:04:50 -05:00
04630fd154 CE-604 make grid always fit on screen, so h-scrollbar & column headers are always present (by turning off autoHeight and adding a set height 2023-10-27 14:28:57 -05:00
1503e2a1d5 CE-604 Elevation on status modal 2023-10-27 14:25:09 -05:00
3d7502531d CE-604 Remove redundant numeric value in pie labels 2023-10-27 14:24:59 -05:00
d0a7db28fe CE-604 make subgrows gray; make expansion arrow icon not make rows taller 2023-10-27 14:24:37 -05:00
95244a8aba CE-604 Make tab parents remember current selection; fix changing data passing through multiple levels of parents 2023-10-27 14:24:14 -05:00
45f247785c fixed bug where datetimes were cleared when posting, resulting in empty form fields for next submit 2023-10-26 12:23:30 -05:00
9e6d5c10fb CE-604 Updates to DataTable for fixed-sticky footer and expandable rows; Misc style updates going along with e.g., card border change 2023-10-24 11:36:10 -05:00
4e0b13ad02 upped revision to 0.20.0-SNAPSHOT 2023-10-23 11:05:41 -05:00
7f57a11e00 CE-604 Update revision to 0.20.0-SNAPSHOT 2023-10-20 10:41:03 -05:00
83da3a3a0a CE-604 Update qqq-frontend-core to 1.0.83 2023-10-20 10:40:39 -05:00
b59ed8c8c1 CE-604 Fix some grammar error (but leave in all the extra commas, Tim) 2023-10-20 10:40:16 -05:00
7101420124 CE-604 Update tooltip styles - wider, dark on light, left-align, box-shadow. 2023-10-20 10:39:57 -05:00
b903e6bef9 CE-604 Adding chartSubheaderData; updating styles 2023-10-20 10:32:00 -05:00
970c9f262c CE-604 Add support for layoutType TABS 2023-10-20 10:30:51 -05:00
9313988f9b CE-604 Add HeaderIcon component; minor style updates for supporting tabs 2023-10-20 10:30:31 -05:00
123d1742e7 CE-604 Update global tab-styles 2023-10-20 10:29:46 -05:00
47fca52437 CE-604 Add topRightInsideCardIcon as a right-component and chartSubheaderData in StackedBarChart and PieChart; Add support for tabs; 2023-10-20 10:27:33 -05:00
44b92690ab CE-604 Initial checkin 2023-10-20 10:21:10 -05:00
64fe2305ad CE-604 Add error box below BooleanFieldSwitch 2023-10-20 10:15:09 -05:00
91d38a1d15 CE-604 Show null values as a switch that isn't leaning towards yes or no; add bgcolor gray when field is disabled 2023-10-20 10:14:37 -05:00
60a8baff35 Style updates per paul-designs (turn off general card elevation in favor of card borders; remove margin around left-nav;) 2023-10-18 10:42:04 -05:00
81b46408b4 Turning hot-dog menu button (to show menu when in mobile) back on;
Hiding recently viewed on smallest screens
Updating style on recently viewed to match new paul design
2023-10-18 10:40:41 -05:00
31 changed files with 841 additions and 361 deletions

View File

@ -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",

View File

@ -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>

View File

@ -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";
}

View File

@ -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",
}, },

View File

@ -15,15 +15,11 @@ Coded by www.creative-tim.com
// 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",
}, },
}, },

View File

@ -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",

View File

@ -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,
}, },
}, },
}; };

View File

@ -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>
); );
} }

View File

@ -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")
{ {

View File

@ -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)

View File

@ -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>

View File

@ -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
}; };

View File

@ -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()),
}, },

View File

@ -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 && (

View File

@ -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 />

View File

@ -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([]);
@ -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
{ {
@ -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
); );
} }

View File

@ -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

View File

@ -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;

View File

@ -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();
@ -76,7 +101,7 @@ function StackedBarChart({data}: Props): JSX.Element
navigate(data.urls[e[0]["index"]]); navigate(data.urls[e[0]["index"]]);
} }
console.log(e); console.log(e);
} };
useEffect(() => useEffect(() =>
{ {
@ -85,9 +110,16 @@ function StackedBarChart({data}: Props): JSX.Element
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,7 +127,12 @@ 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}>
{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"}} />; ) : <Skeleton sx={{marginLeft: "20px", marginRight: "20px", height: "200px"}} />;
} }

View File

@ -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,20 +52,24 @@ 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(() =>
@ -82,14 +87,17 @@ function PieChart({description, chartData}: Props): JSX.Element
// @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} />
@ -105,16 +113,18 @@ function PieChart({description, chartData}: Props): JSX.Element
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>

View File

@ -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: {

View 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}}>
&nbsp;{chartSubheaderData.vsDescription}
{chartSubheaderData.vsPreviousNumber && (<>&nbsp;({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;

View File

@ -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>

View File

@ -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"}}
/> />

View File

@ -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,6 +279,101 @@ function DataTable({
entriesEnd = pageSize * (pageIndex + 1); entriesEnd = pageSize * (pageIndex + 1);
} }
function getTable(includeHead: boolean, rows: any, isFooter: boolean)
{
let boxStyle = {};
if(fixedStickyLastRow)
{
boxStyle = isFooter ? {overflowY: "visible", borderTop: "0.0625rem solid #f0f2f5;"} : {height: fixedHeight ? `${fixedHeight}px` : "360px", overflowY: "auto"};
}
const className = isFooter ? "hideScrollbars" : "";
return <Box sx={boxStyle} className={className}>
<Table {...getTableProps()}>
{
includeHead && (
<Box component="thead" sx={{position: "sticky", top: 0, background: "white"}}>
{headerGroups.map((headerGroup: any, i: number) => (
<TableRow key={i} {...headerGroup.getHeaderGroupProps()} sx={{display: "grid", gridTemplateColumns: gridTemplateColumns}}>
{headerGroup.headers.map((column: any) => (
column.type !== "hidden" && (
<DataTableHeadCell
key={i++}
{...column.getHeaderProps(isSorted && column.getSortByToggleProps())}
align={column.align ? column.align : "left"}
sorted={setSortedValue(column)}
>
{column.render("header")}
</DataTableHeadCell>
)
))}
</TableRow>
))}
</Box>
)
}
<TableBody {...getTableBodyProps()}>
{rows.map((row: any, key: any) =>
{
prepareRow(row);
return (
<TableRow sx={{verticalAlign: "top", display: "grid", gridTemplateColumns: gridTemplateColumns, background: (row.depth > 0 ? "#FAFAFA" : "initial")}} key={key} {...row.getRowProps()}>
{row.cells.map((cell: any) => (
cell.column.type !== "hidden" && (
<DataTableBodyCell
key={key}
noBorder={noEndBorder && rows.length - 1 === key}
align={cell.column.align ? cell.column.align : "left"}
{...cell.getCellProps()}
>
{
cell.column.type === "default" && (
cell.value && "number" === typeof cell.value ? (
<DefaultCell>{cell.value.toLocaleString()}</DefaultCell>
) : (<DefaultCell>{cell.render("Cell")}</DefaultCell>)
)
}
{
cell.column.type === "htmlAndTooltip" && (
<DefaultCell>
<NoMaxWidthTooltip title={parse(row.values["tooltip"])}>
<Box>
{parse(cell.value)}
</Box>
</NoMaxWidthTooltip>
</DefaultCell>
)
}
{
cell.column.type === "html" && (
<DefaultCell>{parse(cell.value)}</DefaultCell>
)
}
{
cell.column.type === "image" && row.values["imageTotal"] && (
<ImageCell imageUrl={row.values["imageUrl"]} label={row.values["imageLabel"]} total={row.values["imageTotal"].toLocaleString()} totalType={row.values["imageTotalType"]} />
)
}
{
cell.column.type === "image" && !row.values["imageTotal"] && (
<ImageCell imageUrl={row.values["imageUrl"]} label={row.values["imageLabel"]} />
)
}
{
(cell.column.id === "__expander") && cell.render("cell")
}
</DataTableBodyCell>
)
))}
</TableRow>
);
})}
</TableBody>
</Table>
</Box>
}
return ( return (
<TableContainer sx={{boxShadow: "none"}}> <TableContainer sx={{boxShadow: "none"}}>
{entriesPerPage && ((hidePaginationDropdown !== undefined && !hidePaginationDropdown) || canSearch) ? ( {entriesPerPage && ((hidePaginationDropdown !== undefined && !hidePaginationDropdown) || canSearch) ? (
@ -240,82 +421,15 @@ function DataTable({
)} )}
</Box> </Box>
) : null} ) : null}
<Table {...getTableProps()}>
<Box component="thead">
{headerGroups.map((headerGroup: any, i: number) => (
<TableRow key={i} {...headerGroup.getHeaderGroupProps()}>
{headerGroup.headers.map((column: any) => (
column.type !== "hidden" && (
<DataTableHeadCell
key={i++}
{...column.getHeaderProps(isSorted && column.getSortByToggleProps())}
width={column.width ? column.width : "auto"}
align={column.align ? column.align : "left"}
sorted={setSortedValue(column)}
>
{column.render("header")}
</DataTableHeadCell>
)
))}
</TableRow>
))}
</Box>
<TableBody {...getTableBodyProps()}>
{page.map((row: any, key: any) =>
{
prepareRow(row);
return (
<TableRow sx={{verticalAlign: "top"}} key={key} {...row.getRowProps()}>
{row.cells.map((cell: any) => (
cell.column.type !== "hidden" && (
<DataTableBodyCell
key={key}
noBorder={noEndBorder && rows.length - 1 === key}
align={cell.column.align ? cell.column.align : "left"}
{...cell.getCellProps()}
>
{
cell.column.type === "default" && (
cell.value && "number" === typeof cell.value ? (
<DefaultCell>{cell.value.toLocaleString()}</DefaultCell>
) : (<DefaultCell>{cell.render("Cell")}</DefaultCell>)
)
}
{
cell.column.type === "htmlAndTooltip" && (
<DefaultCell>
<NoMaxWidthTooltip title={parse(row.values["tooltip"])}>
<Box>
{parse(cell.value)}
</Box>
</NoMaxWidthTooltip>
</DefaultCell>
)
}
{ {
cell.column.type === "html" && ( fixedStickyLastRow ? (
<DefaultCell>{parse(cell.value)}</DefaultCell> <>
) {getTable(true, page.slice(0, page.length -1), false)}
{getTable(false, page.slice(page.length-1), true)}
</>
) : getTable(true, page, false)
} }
{
cell.column.type === "image" && row.values["imageTotal"] && (
<ImageCell imageUrl={row.values["imageUrl"]} label={row.values["imageLabel"]} total={row.values["imageTotal"].toLocaleString()} totalType={row.values["imageTotalType"]} />
)
}
{
cell.column.type === "image" && !row.values["imageTotal"] && (
<ImageCell imageUrl={row.values["imageUrl"]} label={row.values["imageLabel"]} />
)
}
</DataTableBodyCell>
)
))}
</TableRow>
);
})}
</TableBody>
</Table>
<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;

View File

@ -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

View File

@ -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>

View File

@ -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;

View File

@ -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>

View File

@ -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 ?

View File

@ -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+ */
}