SPRINT-19: updates to various widgets for new dashboard, cleanups

This commit is contained in:
Tim Chamberlain
2023-01-24 15:50:44 -06:00
parent 2609748047
commit 895724b87e
18 changed files with 1534 additions and 1096 deletions

1233
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -7,7 +7,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.44", "@kingsrook/qqq-frontend-core": "1.0.45",
"@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",
@ -27,6 +27,7 @@
"chroma-js": "2.4.2", "chroma-js": "2.4.2",
"datejs": "1.0.0-rc3", "datejs": "1.0.0-rc3",
"downshift": "3.2.10", "downshift": "3.2.10",
"faker": "5.5.3",
"form-data": "4.0.0", "form-data": "4.0.0",
"formik": "2.2.9", "formik": "2.2.9",
"html-react-parser": "1.4.8", "html-react-parser": "1.4.8",
@ -77,6 +78,7 @@
"devDependencies": { "devDependencies": {
"@svgr/webpack": "6.5.1", "@svgr/webpack": "6.5.1",
"@types/chroma-js": "2.1.3", "@types/chroma-js": "2.1.3",
"@types/faker": "5.5.9",
"@types/react-table": "7.7.9", "@types/react-table": "7.7.9",
"@typescript-eslint/eslint-plugin": "5.10.2", "@typescript-eslint/eslint-plugin": "5.10.2",
"@typescript-eslint/parser": "5.10.2", "@typescript-eslint/parser": "5.10.2",

View File

@ -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.
*/ */
/** /**
* The base colors for the Material Dashboard 2 PRO React TSUI Dashboard PRO Material. * The base colors for the Material Dashboard 2 PRO React TSUI Dashboard PRO Material.
@ -20,370 +20,405 @@ Coded by www.creative-tim.com
*/ */
// types // types
interface ColorsTypes { interface ColorsTypes
main: string; {
focus: string; main: string;
focus: string;
} }
interface GradientsTypes { interface GradientsTypes
main: string; {
state: string; main: string;
state: string;
} }
interface SocialMediaColorsTypes { interface SocialMediaColorsTypes
main: string; {
dark: string; main: string;
dark: string;
} }
interface BadgeColorsTypes { interface BadgeColorsTypes
background: string; {
text: string; background: string;
text: string;
} }
interface Types { interface Types
background: {
| { background:
default: string; | {
sidenav?: string; default: string;
card?: string; sidenav?: string;
card?: string;
}
| any;
white:
| {
main: string;
focus: string;
}
| any;
text:
| {
main: string;
focus: string;
primary?: string;
secondary?: string;
disabled?: string;
}
| any;
transparent:
| {
main: string;
}
| any;
black:
| {
light: string;
main: string;
focus: string;
}
| any;
primary: ColorsTypes | any;
secondary: ColorsTypes | any;
info: ColorsTypes | any;
success: ColorsTypes | any;
warning: ColorsTypes | any;
error: ColorsTypes | any;
light: ColorsTypes | any;
dark: ColorsTypes | any;
grey:
| {
[key: string | number]: string;
}
| any;
gradients:
| {
primary: GradientsTypes;
secondary: GradientsTypes;
info: GradientsTypes;
success: GradientsTypes;
warning: GradientsTypes;
error: GradientsTypes;
light: GradientsTypes;
dark: GradientsTypes;
custom1: GradientsTypes;
custom2: GradientsTypes;
custom3: GradientsTypes;
custom4: GradientsTypes;
custom5: GradientsTypes;
}
| any;
socialMediaColors:
| {
facebook: SocialMediaColorsTypes;
twitter: SocialMediaColorsTypes;
instagram: SocialMediaColorsTypes;
linkedin: SocialMediaColorsTypes;
pinterest: SocialMediaColorsTypes;
youtube: SocialMediaColorsTypes;
vimeo: SocialMediaColorsTypes;
slack: SocialMediaColorsTypes;
dribbble: SocialMediaColorsTypes;
github: SocialMediaColorsTypes;
reddit: SocialMediaColorsTypes;
tumblr: SocialMediaColorsTypes;
}
| any;
badgeColors:
| {
primary: BadgeColorsTypes;
secondary: BadgeColorsTypes;
info: BadgeColorsTypes;
success: BadgeColorsTypes;
warning: BadgeColorsTypes;
error: BadgeColorsTypes;
light: BadgeColorsTypes;
dark: BadgeColorsTypes;
}
| any;
coloredShadows:
| {
[key: string]: string;
}
| any;
inputBorderColor: string;
tabs:
| {
indicator:
| {
boxShadow: string;
} }
| any; | any;
white: }
| { | any;
main: string;
focus: string;
}
| any;
text:
| {
main: string;
focus: string;
primary?: string;
secondary?: string;
disabled?: string;
}
| any;
transparent:
| {
main: string;
}
| any;
black:
| {
light: string;
main: string;
focus: string;
}
| any;
primary: ColorsTypes | any;
secondary: ColorsTypes | any;
info: ColorsTypes | any;
success: ColorsTypes | any;
warning: ColorsTypes | any;
error: ColorsTypes | any;
light: ColorsTypes | any;
dark: ColorsTypes | any;
grey:
| {
[key: string | number]: string;
}
| any;
gradients:
| {
primary: GradientsTypes;
secondary: GradientsTypes;
info: GradientsTypes;
success: GradientsTypes;
warning: GradientsTypes;
error: GradientsTypes;
light: GradientsTypes;
dark: GradientsTypes;
}
| any;
socialMediaColors:
| {
facebook: SocialMediaColorsTypes;
twitter: SocialMediaColorsTypes;
instagram: SocialMediaColorsTypes;
linkedin: SocialMediaColorsTypes;
pinterest: SocialMediaColorsTypes;
youtube: SocialMediaColorsTypes;
vimeo: SocialMediaColorsTypes;
slack: SocialMediaColorsTypes;
dribbble: SocialMediaColorsTypes;
github: SocialMediaColorsTypes;
reddit: SocialMediaColorsTypes;
tumblr: SocialMediaColorsTypes;
}
| any;
badgeColors:
| {
primary: BadgeColorsTypes;
secondary: BadgeColorsTypes;
info: BadgeColorsTypes;
success: BadgeColorsTypes;
warning: BadgeColorsTypes;
error: BadgeColorsTypes;
light: BadgeColorsTypes;
dark: BadgeColorsTypes;
}
| any;
coloredShadows:
| {
[key: string]: string;
}
| any;
inputBorderColor: string;
tabs:
| {
indicator:
| {
boxShadow: string;
}
| any;
}
| any;
} }
const colors: Types = { const colors: Types = {
background: { background: {
default: "#f0f2f5", default: "#f0f2f5",
}, },
text: { text: {
main: "#7b809a", main: "#7b809a",
focus: "#7b809a", focus: "#7b809a",
}, },
transparent: { transparent: {
main: "transparent", main: "transparent",
}, },
white: { white: {
main: "#ffffff", main: "#ffffff",
focus: "#ffffff", focus: "#ffffff",
}, },
black: { black: {
light: "#000000", light: "#000000",
main: "#000000", main: "#000000",
focus: "#000000", focus: "#000000",
}, },
primary: { primary: {
main: "#e91e63", main: "#e91e63",
focus: "#e91e63", focus: "#e91e63",
}, },
secondary: { secondary: {
main: "#7b809a", main: "#7b809a",
focus: "#8f93a9", focus: "#8f93a9",
}, },
info: { info: {
main: "#04aaef", main: "#04aaef",
focus: "#1662C4", focus: "#1662C4",
}, },
success: { success: {
main: "#4CAF50", main: "#4CAF50",
focus: "#67bb6a", focus: "#67bb6a",
}, },
warning: { warning: {
main: "#fb8c00", main: "#fb8c00",
focus: "#fc9d26", focus: "#fc9d26",
}, },
error: { error: {
main: "#F44335", main: "#F44335",
focus: "#f65f53", focus: "#f65f53",
}, },
light: { light: {
main: "#f0f2f5", main: "#f0f2f5",
focus: "#f0f2f5", focus: "#f0f2f5",
}, },
dark: { dark: {
main: "#344767", main: "#344767",
focus: "#2c3c58", focus: "#2c3c58",
}, },
grey: { grey: {
100: "#f8f9fa", 100: "#f8f9fa",
200: "#f0f2f5", 200: "#f0f2f5",
300: "#dee2e6", 300: "#dee2e6",
400: "#ced4da", 400: "#ced4da",
500: "#adb5bd", 500: "#adb5bd",
600: "#6c757d", 600: "#6c757d",
700: "#495057", 700: "#495057",
800: "#343a40", 800: "#343a40",
900: "#212529", 900: "#212529",
}, },
gradients: { gradients: {
primary: { primary: {
main: "#EC407A", main: "#EC407A",
state: "#D81B60", state: "#D81B60",
}, },
secondary: { secondary: {
main: "#747b8a", main: "#747b8a",
state: "#495361", state: "#495361",
}, },
info: { info: {
main: "#49a3f1", main: "#49a3f1",
state: "#04aaef", state: "#04aaef",
}, },
success: { success: {
main: "#66BB6A", main: "#66BB6A",
state: "#43A047", state: "#43A047",
}, },
warning: { warning: {
main: "#FFA726", main: "#FFA726",
state: "#FB8C00", state: "#FB8C00",
}, },
error: { error: {
main: "#EF5350", main: "#EF5350",
state: "#E53935", state: "#E53935",
}, },
light: { light: {
main: "#EBEFF4", main: "#EBEFF4",
state: "#CED4DA", state: "#CED4DA",
}, },
dark: { dark: {
main: "#42424a", main: "#42424a",
state: "#191919", state: "#191919",
}, },
},
socialMediaColors: { custom1: {
facebook: { main: "#3aaf85",
main: "#3b5998", state: "#8c28c2",
dark: "#344e86", },
},
twitter: { custom2: {
main: "#55acee", main: "#0077b5",
dark: "#3ea1ec", state: "#ffe120",
}, },
instagram: { custom3: {
main: "#125688", main: "#ea4c89",
dark: "#0e456d", state: "#000000",
}, },
linkedin: { custom4: {
main: "#0077b5", main: "#0077b5",
dark: "#00669c", state: "#747474",
}, },
pinterest: { custom5: {
main: "#cc2127", main: "#0077b5",
dark: "#b21d22", state: "#ffcefa",
}, },
},
youtube: { socialMediaColors: {
main: "#e52d27", facebook: {
dark: "#d41f1a", main: "#3b5998",
}, dark: "#344e86",
},
vimeo: { twitter: {
main: "#1ab7ea", main: "#55acee",
dark: "#13a3d2", dark: "#3ea1ec",
}, },
slack: { instagram: {
main: "#3aaf85", main: "#125688",
dark: "#329874", dark: "#0e456d",
}, },
dribbble: { linkedin: {
main: "#ea4c89", main: "#0077b5",
dark: "#e73177", dark: "#00669c",
}, },
github: { pinterest: {
main: "#24292e", main: "#cc2127",
dark: "#171a1d", dark: "#b21d22",
}, },
reddit: { youtube: {
main: "#ff4500", main: "#e52d27",
dark: "#e03d00", dark: "#d41f1a",
}, },
tumblr: { vimeo: {
main: "#35465c", main: "#1ab7ea",
dark: "#2a3749", dark: "#13a3d2",
}, },
},
badgeColors: { slack: {
primary: { main: "#3aaf85",
background: "#f8b3ca", dark: "#329874",
text: "#cc084b", },
},
secondary: { dribbble: {
background: "#d7d9e1", main: "#ea4c89",
text: "#6c757d", dark: "#e73177",
}, },
info: { github: {
background: "#aecef7", main: "#24292e",
text: "#095bc6", dark: "#171a1d",
}, },
success: { reddit: {
background: "#bce2be", main: "#ff4500",
text: "#339537", dark: "#e03d00",
}, },
warning: { tumblr: {
background: "#ffd59f", main: "#35465c",
text: "#c87000", dark: "#2a3749",
}, },
},
error: { badgeColors: {
background: "#fcd3d0", primary: {
text: "#f61200", background: "#f8b3ca",
}, text: "#cc084b",
},
light: { secondary: {
background: "#ffffff", background: "#d7d9e1",
text: "#c7d3de", text: "#6c757d",
}, },
dark: { info: {
background: "#8097bf", background: "#aecef7",
text: "#1e2e4a", text: "#095bc6",
}, },
},
coloredShadows: { success: {
primary: "#e91e62", background: "#bce2be",
secondary: "#110e0e", text: "#339537",
info: "#00bbd4", },
success: "#4caf4f",
warning: "#ff9900",
error: "#f44336",
light: "#adb5bd",
dark: "#404040",
},
inputBorderColor: "#d2d6da", warning: {
background: "#ffd59f",
text: "#c87000",
},
tabs: { error: {
indicator: { boxShadow: "#ddd" }, background: "#fcd3d0",
}, text: "#f61200",
},
light: {
background: "#ffffff",
text: "#c7d3de",
},
dark: {
background: "#8097bf",
text: "#1e2e4a",
},
},
coloredShadows: {
primary: "#e91e62",
secondary: "#110e0e",
info: "#00bbd4",
success: "#4caf4f",
warning: "#ff9900",
error: "#f44336",
light: "#adb5bd",
dark: "#404040",
},
inputBorderColor: "#d2d6da",
tabs: {
indicator: {boxShadow: "#ddd"},
},
}; };
export default colors; export default colors;

View File

@ -80,6 +80,11 @@ const MDBadgeDot: FC<Props> = forwardRef(
"error", "error",
"light", "light",
"dark", "dark",
"custom1",
"custom2",
"custom3",
"custom4",
"custom5"
]; ];
const colorValues = { const colorValues = {
@ -90,10 +95,14 @@ const MDBadgeDot: FC<Props> = forwardRef(
"warning": "#fb8c00", "warning": "#fb8c00",
"error": "#F44335", "error": "#F44335",
"light": "#f0f2f5", "light": "#f0f2f5",
"dark": "#344767" "dark": "#344767",
"custom1": "#8c28c2",
"custom2": "#ffe120",
"custom3": "#000000",
"custom4": "#747474",
"custom5": "#ffcefa"
} as any; } as any;
const validColorIndex = validColors.findIndex((el) => el === color);
return ( return (
<Box ref={ref} display="flex" alignItems="center" p={padding} {...rest}> <Box ref={ref} display="flex" alignItems="center" p={padding} {...rest}>
<Box <Box

View File

@ -34,7 +34,8 @@ 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";
import SmallLineChart from "qqq/components/widgets/charts/linechart/SmallLineChart"; import SmallLineChart from "qqq/components/widgets/charts/linechart/SmallLineChart";
import PieChartCard from "qqq/components/widgets/charts/piechart/PieChartCard"; import PieChart from "qqq/components/widgets/charts/piechart/PieChart";
import StackedBarChart from "qqq/components/widgets/charts/StackedBarChart";
import DividerWidget from "qqq/components/widgets/misc/Divider"; import DividerWidget from "qqq/components/widgets/misc/Divider";
import FieldValueListWidget from "qqq/components/widgets/misc/FieldValueListWidget"; import FieldValueListWidget from "qqq/components/widgets/misc/FieldValueListWidget";
import QuickSightChart from "qqq/components/widgets/misc/QuickSightChart"; import QuickSightChart from "qqq/components/widgets/misc/QuickSightChart";
@ -161,13 +162,13 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, omit
{ {
widgetMetaData.type === "parentWidget" && ( widgetMetaData.type === "parentWidget" && (
<ParentWidget <ParentWidget
icon={widgetMetaData.icon}
entityPrimaryKey={entityPrimaryKey} entityPrimaryKey={entityPrimaryKey}
tableName={tableName} tableName={tableName}
widgetIndex={i} widgetIndex={i}
label={widgetMetaData.label} widgetMetaData={widgetMetaData}
data={widgetData[i]} data={widgetData[i]}
reloadWidgetCallback={reloadWidget} reloadWidgetCallback={reloadWidget}
storeDropdownSelections={widgetMetaData.storeDropdownSelections}
/> />
) )
} }
@ -184,30 +185,34 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, omit
{ {
widgetMetaData.type === "table" && ( widgetMetaData.type === "table" && (
<Widget <Widget
label={widgetData[i]?.label} widgetMetaData={widgetMetaData}
isCard={widgetMetaData.isCard}
widgetData={widgetData[i]} widgetData={widgetData[i]}
reloadWidgetCallback={(data) => reloadWidget(i, data)} reloadWidgetCallback={(data) => reloadWidget(i, data)}
isChild={areChildren} isChild={areChildren}
> >
<TableCard <TableCard
color="info"
title={widgetMetaData.label}
linkText={widgetData[i]?.linkText}
linkURL={widgetData[i]?.linkURL}
noRowsFoundHTML={widgetData[i]?.noRowsFoundHTML} noRowsFoundHTML={widgetData[i]?.noRowsFoundHTML}
data={widgetData[i]} data={widgetData[i]}
dropdownOptions={widgetData[i]?.dropdownOptions}
reloadWidgetCallback={(data) => reloadWidget(i, data)}
widgetIndex={i}
/> />
</Widget> </Widget>
) )
} }
{
widgetMetaData.type === "stackedBarChart" && (
<Widget
widgetMetaData={widgetMetaData}
widgetData={widgetData[i]}
reloadWidgetCallback={(data) => reloadWidget(i, data)}
isChild={areChildren}
>
<StackedBarChart data={widgetData[i]?.chartData}/>
</Widget>
)
}
{ {
widgetMetaData.type === "process" && widgetData[i]?.processMetaData && ( widgetMetaData.type === "process" && widgetData[i]?.processMetaData && (
<Widget <Widget
label={widgetData[i]?.processMetaData?.label} widgetMetaData={widgetMetaData}
widgetData={widgetData[i]} widgetData={widgetData[i]}
reloadWidgetCallback={(data) => reloadWidget(i, data)}> reloadWidgetCallback={(data) => reloadWidget(i, data)}>
<div> <div>
@ -234,7 +239,7 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, omit
} }
{ {
widgetMetaData.type === "html" && ( widgetMetaData.type === "html" && (
<Widget label={widgetMetaData.label}> <Widget widgetMetaData={widgetMetaData}>
<Box px={1} pt={0} pb={2}> <Box px={1} pt={0} pb={2}>
<MDTypography component="div" variant="button" color="text" fontWeight="light"> <MDTypography component="div" variant="button" color="text" fontWeight="light">
{ {
@ -247,15 +252,6 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, omit
</Widget> </Widget>
) )
} }
{
widgetMetaData.type === "multiStatistics" && (
<MultiStatisticsCard
color="info"
title={widgetMetaData.label}
data={widgetData[i]}
/>
)
}
{ {
widgetMetaData.type === "smallLineChart" && ( widgetMetaData.type === "smallLineChart" && (
<SmallLineChart <SmallLineChart
@ -267,6 +263,25 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, omit
/> />
) )
} }
{
widgetMetaData.type === "statistics" && (
widgetData && widgetData[i] && (
<Widget
widgetMetaData={widgetMetaData}
widgetData={widgetData[i]}
isChild={areChildren}
reloadWidgetCallback={(data) => reloadWidget(i, data)}>
<StatisticsCard
title={widgetMetaData.label}
color={colors.info.main}
icon={widgetMetaData.icon}
data={widgetData[i]}
increaseIsGood={true}
/>
</Widget>
)
)
}
{ {
widgetMetaData.type === "simpleStatistics" && ( widgetMetaData.type === "simpleStatistics" && (
widgetData && widgetData[i] && ( widgetData && widgetData[i] && (
@ -280,16 +295,12 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, omit
) )
} }
{ {
widgetMetaData.type === "statistics" && ( widgetMetaData.type === "multiStatistics" && (
widgetData && widgetData[i] && ( <MultiStatisticsCard
<StatisticsCard color="info"
title={widgetMetaData.label} title={widgetMetaData.label}
color={colors.info.main} data={widgetData[i]}
icon={widgetMetaData.icon} />
data={widgetData[i]}
increaseIsGood={true}
/>
)
) )
} }
{ {
@ -310,11 +321,19 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, omit
} }
{ {
widgetMetaData.type === "pieChart" && ( widgetMetaData.type === "pieChart" && (
<PieChartCard <Widget
title={widgetMetaData.label} widgetMetaData={widgetMetaData}
description={widgetData[i]?.description} widgetData={widgetData[i]}
data={widgetData[i]?.chartData} isChild={areChildren}
/> reloadWidgetCallback={(data) => reloadWidget(i, data)}>
<div>
<PieChart
chartData={widgetData[i]?.chartData}
description={widgetData[i]?.description}
/>
</div>
</Widget>
) )
} }
{ {
@ -362,7 +381,7 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, omit
widgetMetaData.type === "childRecordList" && ( widgetMetaData.type === "childRecordList" && (
widgetData && widgetData[i] && widgetData && widgetData[i] &&
<RecordGridWidget <RecordGridWidget
title={widgetMetaData.label} widgetMetaData={widgetMetaData}
data={widgetData[i]} data={widgetData[i]}
/> />
) )
@ -372,7 +391,7 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, omit
widgetMetaData.type === "fieldValueList" && ( widgetMetaData.type === "fieldValueList" && (
widgetData && widgetData[i] && widgetData && widgetData[i] &&
<FieldValueListWidget <FieldValueListWidget
title={widgetMetaData.label} widgetMetaData={widgetMetaData}
data={widgetData[i]} data={widgetData[i]}
reloadWidgetCallback={(data) => reloadWidget(i, data)} reloadWidgetCallback={(data) => reloadWidget(i, data)}
/> />

View File

@ -50,18 +50,18 @@ export interface ParentWidgetData
//////////////////////////////////// ////////////////////////////////////
interface Props interface Props
{ {
widgetMetaData?: QWidgetMetaData;
widgetIndex: number; widgetIndex: number;
label: string;
icon?: string;
data: ParentWidgetData; data: ParentWidgetData;
reloadWidgetCallback?: (widgetIndex: number, params: string) => void; reloadWidgetCallback?: (widgetIndex: number, params: string) => void;
entityPrimaryKey?: string; entityPrimaryKey?: string;
tableName?: string; tableName?: string;
storeDropdownSelections?: boolean;
} }
const qController = Client.getInstance(); const qController = Client.getInstance();
function ParentWidget({widgetIndex, label, icon, data, reloadWidgetCallback, entityPrimaryKey, tableName}: Props, ): JSX.Element function ParentWidget({widgetMetaData, widgetIndex, data, reloadWidgetCallback, entityPrimaryKey, tableName, storeDropdownSelections}: Props, ): JSX.Element
{ {
const [childUrlParams, setChildUrlParams] = useState(""); const [childUrlParams, setChildUrlParams] = useState("");
const [qInstance, setQInstance] = useState(null as QInstance); const [qInstance, setQInstance] = useState(null as QInstance);
@ -99,12 +99,12 @@ function ParentWidget({widgetIndex, label, icon, data, reloadWidgetCallback, ent
// @ts-ignore // @ts-ignore
return ( return (
<Widget <Widget
icon={icon} widgetMetaData={widgetMetaData}
label={label}
widgetData={data} widgetData={data}
storeDropdownSelections={storeDropdownSelections}
reloadWidgetCallback={parentReloadWidgetCallback} reloadWidgetCallback={parentReloadWidgetCallback}
> >
<Box px={3} sx={{width: "100%"}}> <Box sx={{height: "100%", width: "100%"}}>
<DashboardWidgets widgetMetaDataList={widgets} entityPrimaryKey={entityPrimaryKey} tableName={tableName} childUrlParams={childUrlParams} areChildren={true}/> <DashboardWidgets widgetMetaDataList={widgets} entityPrimaryKey={entityPrimaryKey} tableName={tableName} childUrlParams={childUrlParams} areChildren={true}/>
</Box> </Box>
</Widget> </Widget>

View File

@ -20,6 +20,7 @@
*/ */
import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData"; import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData";
import {QWidgetMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QWidgetMetaData";
import Box from "@mui/material/Box"; import Box from "@mui/material/Box";
import Button from "@mui/material/Button"; import Button from "@mui/material/Button";
import Card from "@mui/material/Card"; import Card from "@mui/material/Card";
@ -44,21 +45,19 @@ export interface WidgetData
interface Props interface Props
{ {
icon?: string;
label: string;
labelAdditionalComponentsLeft: LabelComponent[]; labelAdditionalComponentsLeft: LabelComponent[];
labelAdditionalComponentsRight: LabelComponent[]; labelAdditionalComponentsRight: LabelComponent[];
widgetMetaData?: QWidgetMetaData;
widgetData?: WidgetData; widgetData?: WidgetData;
children: JSX.Element; children: JSX.Element;
reloadWidgetCallback?: (params: string) => void; reloadWidgetCallback?: (params: string) => void;
isChild?: boolean; isChild?: boolean;
isCard?: boolean; storeDropdownSelections?: boolean;
} }
Widget.defaultProps = { Widget.defaultProps = {
isCard: true,
isChild: false, isChild: false,
label: null, widgetMetaData: {},
widgetData: {}, widgetData: {},
labelAdditionalComponentsLeft: [], labelAdditionalComponentsLeft: [],
labelAdditionalComponentsRight: [], labelAdditionalComponentsRight: [],
@ -121,6 +120,8 @@ export class Dropdown extends LabelComponent
} }
const WIDGET_DROPDOWN_SELECTION_LOCAL_STORAGE_KEY_ROOT = "qqq.widgets.dropdownData";
function Widget(props: React.PropsWithChildren<Props>): JSX.Element function Widget(props: React.PropsWithChildren<Props>): JSX.Element
{ {
@ -133,7 +134,7 @@ function Widget(props: React.PropsWithChildren<Props>): JSX.Element
navigate(`#/createChild=${table.name}/defaultValues=${JSON.stringify(defaultValues)}/disabledFields=${JSON.stringify(disabledFields)}`) navigate(`#/createChild=${table.name}/defaultValues=${JSON.stringify(defaultValues)}/disabledFields=${JSON.stringify(disabledFields)}`)
} }
function renderComponent(component: LabelComponent) function renderComponent(component: LabelComponent, index: number)
{ {
if(component instanceof HeaderLink) if(component instanceof HeaderLink)
{ {
@ -157,10 +158,23 @@ function Widget(props: React.PropsWithChildren<Props>): JSX.Element
if (component instanceof Dropdown) if (component instanceof Dropdown)
{ {
let defaultValue = null;
const dropdownName = props.widgetData.dropdownNameList[index];
const localStorageKey = `${WIDGET_DROPDOWN_SELECTION_LOCAL_STORAGE_KEY_ROOT}.${props.widgetMetaData.name}.${dropdownName}`;
if(props.storeDropdownSelections)
{
///////////////////////////////////////////////////////////////////////////////////////
// see if an existing value is stored in local storage, and if so set it in dropdown //
///////////////////////////////////////////////////////////////////////////////////////
defaultValue = JSON.parse(localStorage.getItem(localStorageKey));
}
const dropdown = component as Dropdown const dropdown = component as Dropdown
return ( return (
<Box my={2} mr={2} sx={{float: "right"}}> <Box my={2} mr={2} sx={{float: "right"}}>
<DropdownMenu <DropdownMenu
localStorageKey={localStorageKey}
defaultValue={defaultValue}
sx={{width: 200, marginLeft: "15px"}} sx={{width: 200, marginLeft: "15px"}}
label={`Select ${dropdown.label}`} label={`Select ${dropdown.label}`}
dropdownOptions={dropdown.options} dropdownOptions={dropdown.options}
@ -199,12 +213,14 @@ function Widget(props: React.PropsWithChildren<Props>): JSX.Element
// find the index base on selected label // // find the index base on selected label //
/////////////////////////////////////////// ///////////////////////////////////////////
const tableName = dropdownLabel.replace("Select ", ""); const tableName = dropdownLabel.replace("Select ", "");
let dropdownName = "";
let index = -1; let index = -1;
for (let i = 0; i < props.widgetData.dropdownLabelList.length; i++) for (let i = 0; i < props.widgetData.dropdownLabelList.length; i++)
{ {
if (tableName === props.widgetData.dropdownLabelList[i]) if (tableName === props.widgetData.dropdownLabelList[i])
{ {
index = i; index = i;
dropdownName = props.widgetData.dropdownNameList[i];
break; break;
} }
} }
@ -217,6 +233,22 @@ function Widget(props: React.PropsWithChildren<Props>): JSX.Element
dropdownData[index] = (changedData) ? changedData.id : null; dropdownData[index] = (changedData) ? changedData.id : null;
setDropdownData(dropdownData); setDropdownData(dropdownData);
setCounter(counter + 1); setCounter(counter + 1);
/////////////////////////////////////////////////
// if should store in local storage, do so now //
// or remove if dropdown was cleared out //
/////////////////////////////////////////////////
if(props.storeDropdownSelections)
{
if (changedData?.id)
{
localStorage.setItem(`${WIDGET_DROPDOWN_SELECTION_LOCAL_STORAGE_KEY_ROOT}.${props.widgetMetaData.name}.${dropdownName}`, JSON.stringify(changedData));
}
else
{
localStorage.removeItem(`${WIDGET_DROPDOWN_SELECTION_LOCAL_STORAGE_KEY_ROOT}.${props.widgetMetaData.name}.${dropdownName}`);
}
}
} }
} }
@ -245,61 +277,62 @@ function Widget(props: React.PropsWithChildren<Props>): JSX.Element
} }
else else
{ {
console.log(`No reload widget callback in ${props.label}`) console.log(`No reload widget callback in ${props.widgetMetaData.label}`)
} }
} }
}, [counter]); }, [counter]);
const widgetContent = const widgetContent =
<Box sx={{width: "100%"}}> <Box sx={{width: "100%"}}>
{ <Box display="flex" justifyContent="space-between" alignItems="center" sx={{width: "100%"}}>
(props.icon || props.label) && ( <Box pt={2}>
<Box display="flex" justifyContent="space-between" alignItems="center" sx={{width: "100%"}}> {
<Box py={2}> props.widgetMetaData?.icon && (
{ <Box
props.icon && ( ml={3}
<Box mt={-4}
ml={3} sx={{
mt={-4} display: "flex",
sx={{ justifyContent: "center",
display: "flex", alignItems: "center",
justifyContent: "center", width: "64px",
alignItems: "center", height: "64px",
width: "64px", borderRadius: "8px",
height: "64px", background: colors.info.main,
borderRadius: "8px", color: "#ffffff",
background: colors.info.main, float: "left"
color: "#ffffff", }}
float: "left" >
}} <Icon fontSize="medium" color="inherit">
> {props.widgetMetaData.icon}
<Icon fontSize="medium" color="inherit"> </Icon>
{props.icon} </Box>
</Icon>
</Box> )
) }
} {
<Typography variant={props.isChild ? "h6" : "h5"} fontWeight="medium" p={3} display="inline"> props.widgetMetaData?.label && (
{props.label} <Typography variant="h5" fontWeight="medium" pl={3} display="inline">
{props.widgetMetaData.label}
</Typography> </Typography>
{ )
props.labelAdditionalComponentsLeft.map((component, i) => }
{ {
return (<span key={i}>{renderComponent(component)}</span>); props.labelAdditionalComponentsLeft.map((component, i) =>
}) {
} return (<span key={i}>{renderComponent(component, i)}</span>);
</Box> })
<Box pr={1}> }
{ </Box>
effectiveLabelAdditionalComponentsRight.map((component, i) => <Box>
{ {
return (<span key={i}>{renderComponent(component)}</span>); effectiveLabelAdditionalComponentsRight.map((component, i) =>
}) {
} return (<span key={i}>{renderComponent(component, i)}</span>);
</Box> })
</Box> }
) </Box>
} </Box>
{ {
props.widgetData?.dropdownNeedsSelectedText ? ( props.widgetData?.dropdownNeedsSelectedText ? (
<Box pb={3} pr={3} sx={{width: "100%", textAlign: "right"}}> <Box pb={3} pr={3} sx={{width: "100%", textAlign: "right"}}>
@ -313,7 +346,7 @@ function Widget(props: React.PropsWithChildren<Props>): JSX.Element
} }
</Box>; </Box>;
return props.isCard ? <Card sx={{width: "100%"}}>{widgetContent}</Card> : widgetContent; return props.widgetMetaData?.isCard ? <Card sx={{marginTop: props.widgetMetaData?.icon ? 2 : 0, width: "100%"}}>{widgetContent}</Card> : widgetContent;
} }
export default Widget; export default Widget;

View File

@ -0,0 +1,37 @@
/*
* 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/>.
*/
export const chartColors = ["info", "warning", "primary", "success", "error", "secondary", "dark", "custom1", "custom2", "custom3", "custom4", "custom5"];
//////////////////////////////////////////
// structure of default chart data //
//////////////////////////////////////////
export interface DefaultChartData
{
labels: string[];
datasets: [
{
label: string;
data: number[];
backgroundColor: string;
} ]
};

View File

@ -0,0 +1,84 @@
/*
* 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 Box from "@mui/material/Box";
import {BarElement, CategoryScale, Chart as ChartJS, Legend, LinearScale, Title, Tooltip,} from "chart.js";
import React from "react";
import {Bar} from "react-chartjs-2";
import colors from "qqq/assets/theme/base/colors";
import {chartColors, DefaultChartData} from "qqq/components/widgets/charts/DefaultChartData";
ChartJS.register(
CategoryScale,
LinearScale,
BarElement,
Title,
Tooltip,
Legend
);
export const options = {
responsive: true,
scales: {
x: {
stacked: true,
},
y: {
stacked: true,
},
},
};
interface Props
{
data: DefaultChartData;
}
const {gradients} = colors;
function StackedBarChart({data}: Props): JSX.Element
{
const handleClick = (e: Array<{}>) =>
{
/*
if(e && e.length > 0 && data?.dataset?.urls && data?.dataset?.urls.length)
{
// @ts-ignore
navigate(chartData.dataset.urls[e[0]["index"]]);
}
*/
console.log(e);
}
data?.datasets.forEach((dataset: any, index: number) =>
{
if(! dataset.backgroundColor)
{
dataset.backgroundColor = gradients[chartColors[index]].state;
}
});
return <Box p={3}><Bar data={data} options={options} getElementsAtEvent={handleClick} /></Box>;
}
export default StackedBarChart;

View File

@ -19,13 +19,17 @@
* 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 {Card} from "@mui/material";
import Box from "@mui/material/Box"; import Box from "@mui/material/Box";
import Card from "@mui/material/Card"; import Divider from "@mui/material/Divider";
import Icon from "@mui/material/Icon"; import Grid from "@mui/material/Grid";
import parse from "html-react-parser"; import parse from "html-react-parser";
import {ReactNode, useMemo} from "react"; import React, {useMemo} from "react";
import {Pie} from "react-chartjs-2"; import {Pie} from "react-chartjs-2";
import {useNavigate} from "react-router-dom";
import MDBadgeDot from "qqq/components/legacy/MDBadgeDot";
import MDTypography from "qqq/components/legacy/MDTypography"; import MDTypography from "qqq/components/legacy/MDTypography";
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";
////////////////////////////////////////// //////////////////////////////////////////
@ -38,6 +42,7 @@ export interface PieChartData
label: string; label: string;
backgroundColors?: string[]; backgroundColors?: string[];
data: number[]; data: number[];
urls?: string[];
}; };
} }
@ -45,64 +50,78 @@ export interface PieChartData
// Declaring props types for PieChart // Declaring props types for PieChart
interface Props interface Props
{ {
icon?: {
color?: "primary" | "secondary" | "info" | "success" | "warning" | "error" | "light" | "dark";
component: ReactNode;
};
title?: string;
description?: string; description?: string;
height?: string | number; chartData: PieChartData;
chart: PieChartData;
[key: string]: any; [key: string]: any;
} }
function PieChart({icon, title, description, height, chart}: Props): JSX.Element function PieChart({description, chartData}: Props): JSX.Element
{ {
const {data, options} = configs(chart?.labels || [], chart?.dataset || {}); const navigate = useNavigate();
const renderChart = ( if (chartData && chartData.dataset)
<Box py={2} pr={2} pl={icon.component ? 1 : 2}> {
{title || description ? ( chartData.dataset.backgroundColors = chartColors;
<Box display="flex" px={description ? 1 : 0} pt={description ? 1 : 0}> }
{icon.component && (
<Box const {data, options} = configs(chartData?.labels || [], chartData?.dataset || {});
width="4rem"
height="4rem" const handleClick = (e: Array<{}>) =>
borderRadius="xl" {
display="flex" if(e && e.length > 0 && chartData?.dataset?.urls && chartData?.dataset?.urls.length)
justifyContent="center" {
alignItems="center" // @ts-ignore
color="white" navigate(chartData.dataset.urls[e[0]["index"]]);
mt={-5} }
mr={2} }
sx={{backgroundColor: icon.color || "info"}}
> return (
<Icon fontSize="medium">{icon.component}</Icon> <Card sx={{boxShadow: "none", height: "100%", width: "100%", display: "flex", flexGrow: 1}}>
<Box mt={3}>
<Grid container alignItems="center">
<Grid item xs={5}>
<Box py={2} pr={2} pl={2}>
{useMemo(
() => (
<Pie data={data} options={options} getElementsAtEvent={handleClick} />
),
[chartData]
)}
</Box> </Box>
)} </Grid>
<Box mt={icon.component ? -2 : 0}> <Grid item xs={7}>
{title && <MDTypography variant="h6">{title}</MDTypography>} <Box pr={1}>
<Box mb={2}> {
<MDTypography component="div" variant="button" color="text"> data && data.labels ? (
{parse(description)} (data.labels.map((label: string, index: number) => (
</MDTypography> <Box key={index}>
<MDBadgeDot color={chartColors[index]} size="sm" badgeContent={label} />
</Box>
)
))) : null
}
</Box> </Box>
</Box> </Grid>
</Box> </Grid>
) : null} <Divider />
{useMemo( {
() => ( description && (
<Box height={height}> <Grid container>
<Pie data={data} options={options} /> <Grid item xs={12}>
</Box> <Box pb={2} px={2} display="flex" flexDirection={{xs: "column", sm: "row"}} mt="auto">
), <MDTypography variant="button" color="text" fontWeight="light">
[chart, height] {parse(description)}
)} </MDTypography>
</Box> </Box>
</Grid>
</Grid>
)
}
</Box>
</Card>
); );
return title || description ? <Card>{renderChart}</Card> : renderChart;
} }
// Declaring default props for PieChart // Declaring default props for PieChart

View File

@ -1,93 +0,0 @@
/*
* 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 Box from "@mui/material/Box";
import Card from "@mui/material/Card";
import Divider from "@mui/material/Divider";
import Grid from "@mui/material/Grid";
import parse from "html-react-parser";
import MDBadgeDot from "qqq/components/legacy/MDBadgeDot";
import MDTypography from "qqq/components/legacy/MDTypography";
import PieChart, {PieChartData} from "qqq/components/widgets/charts/piechart/PieChart";
// Declaring props types for PieChart
interface Props
{
title?: string;
description?: string;
data: PieChartData;
[key: string]: any;
}
function PieChartCard({title, description, data}: Props): JSX.Element
{
const allBackgroundColors = ["info", "warning", "primary", "success", "error", "secondary", "dark"];
if (data && data.dataset)
{
data.dataset.backgroundColors = allBackgroundColors;
}
return (
<Card sx={{height: "100%", width: "100%", display: "flex"}}>
<Box display="flex" pt={2} px={2}>
<MDTypography variant="h5">{title}</MDTypography>
</Box>
<Box mt={3}>
<Grid container alignItems="center">
<Grid item xs={7}>
<PieChart chart={data} height="9.5rem" />
</Grid>
<Grid item xs={5}>
<Box pr={1}>
{
data && data.labels ? (
(data.labels.map((label: string, index: number) => (
<Box key={index}>
<MDBadgeDot color={allBackgroundColors[index]} size="sm" badgeContent={label} />
</Box>
)
))) : null
}
</Box>
</Grid>
</Grid>
<Divider />
{
description && (
<Grid container>
<Grid item xs={12}>
<Box pb={2} px={2} display="flex" flexDirection={{xs: "column", sm: "row"}} mt="auto">
<MDTypography variant="button" color="text" fontWeight="light">
{parse(description)}
</MDTypography>
</Box>
</Grid>
</Grid>
)
}
</Box>
</Card>
);
}
export default PieChartCard;

View File

@ -59,16 +59,12 @@ function configs(labels: any, datasets: any)
}, },
options: { options: {
responsive: true, responsive: true,
maintainAspectRatio: false, maintainAspectRatio: true,
plugins: { plugins: {
legend: { legend: {
display: false, display: false,
}, },
}, },
interaction: {
intersect: false,
mode: "index",
},
scales: { scales: {
y: { y: {
grid: { grid: {

View File

@ -23,7 +23,7 @@ import {Theme} from "@mui/material";
import Autocomplete from "@mui/material/Autocomplete"; import Autocomplete from "@mui/material/Autocomplete";
import TextField from "@mui/material/TextField"; import TextField from "@mui/material/TextField";
import {SxProps} from "@mui/system"; import {SxProps} from "@mui/system";
import React from "react"; import React, {useEffect} from "react";
export interface DropdownOption export interface DropdownOption
@ -37,14 +37,25 @@ export interface DropdownOption
///////////////////////// /////////////////////////
interface Props interface Props
{ {
defaultValue?: any;
localStorageKey?: string;
label?: string; label?: string;
dropdownOptions?: DropdownOption[]; dropdownOptions?: DropdownOption[];
onChangeCallback?: (dropdownLabel: string, data: any) => void; onChangeCallback?: (dropdownLabel: string, data: any) => void;
sx?: SxProps<Theme>; sx?: SxProps<Theme>;
} }
function DropdownMenu({label, dropdownOptions, onChangeCallback, sx}: Props): JSX.Element function DropdownMenu({localStorageKey, defaultValue, label, dropdownOptions, onChangeCallback, sx}: Props): JSX.Element
{ {
useEffect(() =>
{
if(defaultValue)
{
console.log("CALLING CALLBACK...")
onChangeCallback(label, JSON.parse(localStorage.getItem(localStorageKey)));
}
}, []);
const handleOnChange = (event: any, value: any, reason: string) => const handleOnChange = (event: any, value: any, reason: string) =>
{ {
onChangeCallback(label, value); onChangeCallback(label, value);
@ -54,12 +65,14 @@ function DropdownMenu({label, dropdownOptions, onChangeCallback, sx}: Props): JS
dropdownOptions ? ( dropdownOptions ? (
<span style={{whiteSpace: "nowrap"}}> <span style={{whiteSpace: "nowrap"}}>
<Autocomplete <Autocomplete
defaultValue={defaultValue}
size="small" size="small"
disablePortal disablePortal
id={`${label}-combo-box`} id={`${label}-combo-box`}
options={dropdownOptions} options={dropdownOptions}
sx={{...sx, cursor: "pointer"}} sx={{...sx, cursor: "pointer"}}
onChange={handleOnChange} onChange={handleOnChange}
isOptionEqualToValue={(option, value) => option.id === value.id}
renderInput={(params: any) => <TextField {...params} label={label} />} renderInput={(params: any) => <TextField {...params} label={label} />}
renderOption={(props, option: DropdownOption) => ( renderOption={(props, option: DropdownOption) => (
<li {...props} style={{whiteSpace: "normal"}}>{option.label}</li> <li {...props} style={{whiteSpace: "normal"}}>{option.label}</li>

View File

@ -20,6 +20,7 @@
*/ */
import {QFieldMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldMetaData"; import {QFieldMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldMetaData";
import {QWidgetMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QWidgetMetaData";
import {QRecord} from "@kingsrook/qqq-frontend-core/lib/model/QRecord"; import {QRecord} from "@kingsrook/qqq-frontend-core/lib/model/QRecord";
import {Skeleton} from "@mui/material"; import {Skeleton} from "@mui/material";
import Box from "@mui/material/Box"; import Box from "@mui/material/Box";
@ -31,19 +32,19 @@ import ValueUtils from "qqq/utils/qqq/ValueUtils";
interface Props interface Props
{ {
title: string; widgetMetaData: QWidgetMetaData;
data: any; data: any;
reloadWidgetCallback?: (params: string) => void; reloadWidgetCallback?: (params: string) => void;
} }
FieldValueListWidget.defaultProps = {}; FieldValueListWidget.defaultProps = {};
function FieldValueListWidget({title, data, reloadWidgetCallback}: Props): JSX.Element function FieldValueListWidget({widgetMetaData, data, reloadWidgetCallback}: Props): JSX.Element
{ {
if(data?.dropdownNeedsSelectedText) if(data?.dropdownNeedsSelectedText)
{ {
return ( return (
<Widget label={title} widgetData={data} reloadWidgetCallback={reloadWidgetCallback}> <Widget widgetMetaData={widgetMetaData} widgetData={data} reloadWidgetCallback={reloadWidgetCallback}>
<br /> <br />
</Widget> </Widget>
); );
@ -53,7 +54,7 @@ function FieldValueListWidget({title, data, reloadWidgetCallback}: Props): JSX.E
{ {
const skeletons = [75, 50, 90]; const skeletons = [75, 50, 90];
return ( return (
<Widget label={title}> <Widget widgetMetaData={widgetMetaData}>
<Box p={3} pt={0} display="flex" flexDirection="column"> <Box p={3} pt={0} display="flex" flexDirection="column">
{skeletons.map((s) => {skeletons.map((s) =>
( (
@ -79,7 +80,7 @@ function FieldValueListWidget({title, data, reloadWidgetCallback}: Props): JSX.E
const fieldIndentLevels = data.fieldIndentLevels ?? {}; const fieldIndentLevels = data.fieldIndentLevels ?? {};
return ( return (
<Widget label={title} widgetData={data} reloadWidgetCallback={reloadWidgetCallback}> <Widget widgetMetaData={widgetMetaData} widgetData={data} reloadWidgetCallback={reloadWidgetCallback}>
<Box p={3} pt={0} display="flex" flexDirection="column"> <Box p={3} pt={0} display="flex" flexDirection="column">
{ {
fields.map((field: QFieldMetaData, index: number) => ( fields.map((field: QFieldMetaData, index: number) => (

View File

@ -20,6 +20,7 @@
*/ */
import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData"; import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData";
import {QWidgetMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QWidgetMetaData";
import {QRecord} from "@kingsrook/qqq-frontend-core/lib/model/QRecord"; import {QRecord} from "@kingsrook/qqq-frontend-core/lib/model/QRecord";
import {DataGridPro, GridCallbackDetails, GridRowParams, MuiEvent} from "@mui/x-data-grid-pro"; import {DataGridPro, GridCallbackDetails, GridRowParams, MuiEvent} from "@mui/x-data-grid-pro";
import React, {useEffect, useState} from "react"; import React, {useEffect, useState} from "react";
@ -30,7 +31,7 @@ import Client from "qqq/utils/qqq/Client";
interface Props interface Props
{ {
title: string; widgetMetaData: QWidgetMetaData;
data: any; data: any;
} }
@ -38,7 +39,7 @@ RecordGridWidget.defaultProps = {};
const qController = Client.getInstance(); const qController = Client.getInstance();
function RecordGridWidget({title, data}: Props): JSX.Element function RecordGridWidget({widgetMetaData, data}: Props): JSX.Element
{ {
const [rows, setRows] = useState([]); const [rows, setRows] = useState([]);
const [columns, setColumns] = useState([]); const [columns, setColumns] = useState([]);
@ -121,7 +122,7 @@ function RecordGridWidget({title, data}: Props): JSX.Element
return ( return (
<Widget <Widget
label={title} widgetMetaData={widgetMetaData}
labelAdditionalComponentsLeft={labelAdditionalComponentsLeft} labelAdditionalComponentsLeft={labelAdditionalComponentsLeft}
labelAdditionalComponentsRight={labelAdditionalComponentsRight} labelAdditionalComponentsRight={labelAdditionalComponentsRight}
> >

View File

@ -19,11 +19,11 @@
* 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 {CircularProgress, Typography} from "@mui/material";
import Box from "@mui/material/Box"; import Box from "@mui/material/Box";
import Card from "@mui/material/Card";
import Divider from "@mui/material/Divider"; import Divider from "@mui/material/Divider";
import Icon from "@mui/material/Icon"; import React, {ReactNode} from "react";
import {ReactNode} from "react"; import {NavLink} from "react-router-dom";
import MDTypography from "qqq/components/legacy/MDTypography"; import MDTypography from "qqq/components/legacy/MDTypography";
/////////////////////////////////////////// ///////////////////////////////////////////
@ -31,8 +31,9 @@ import MDTypography from "qqq/components/legacy/MDTypography";
/////////////////////////////////////////// ///////////////////////////////////////////
export interface StatisticsCardData export interface StatisticsCardData
{ {
title: string;
count: number; count: number;
countFontSize: string;
countURL?: string;
percentageAmount: number; percentageAmount: number;
percentageLabel: string; percentageLabel: string;
} }
@ -42,7 +43,6 @@ export interface StatisticsCardData
///////////////////////// /////////////////////////
interface Props interface Props
{ {
title: string
data: StatisticsCardData; data: StatisticsCardData;
color?: "primary" | "secondary" | "info" | "success" | "warning" | "error" | "light" | "dark"; color?: "primary" | "secondary" | "info" | "success" | "warning" | "error" | "light" | "dark";
icon: ReactNode; icon: ReactNode;
@ -61,7 +61,7 @@ StatisticsCard.defaultProps = {
increaseIsGood: true increaseIsGood: true
}; };
function StatisticsCard({title, data, color, icon, increaseIsGood}: Props): JSX.Element function StatisticsCard({data, color, icon, increaseIsGood}: Props): JSX.Element
{ {
const {count, percentageAmount, percentageLabel} = data; const {count, percentageAmount, percentageLabel} = data;
@ -90,39 +90,30 @@ function StatisticsCard({title, data, color, icon, increaseIsGood}: Props): JSX.
return ( return (
<Card sx={{height: "100%", alignItems: "stretch", flexGrow: 1, display: "flex", marginTop: 3, paddingTop: "0px"}}> <Box mt={0} sx={{height: "100%", flexGrow: 1, flexDirection: "column", display: "flex", paddingTop: "0px"}}>
<Box display="flex" justifyContent="space-between" pt={1} px={2}> <Box mt={0} display="flex" justifyContent="center">
<Box {
color="#ffffff" count !== undefined ? (
borderRadius="xl" <Typography mt={0} sx={{color: "#344767", display: "flex", alignContent: "flex-end", fontSize: data?.countFontSize ? data?.countFontSize : "40px"}}>
display="flex" {
justifyContent="center" data.countURL ? (
alignItems="center" <NavLink to={data.countURL}>{count.toLocaleString()}</NavLink>
width="4rem" ) : (
height="4rem" count.toLocaleString()
mt={-3} )
sx={{borderRadius: "10px", backgroundColor: color}} }
> </Typography>
<Icon fontSize="medium" color="inherit"> ) : (
{icon} <CircularProgress sx={{marginTop: "1rem", marginBottom: "20px"}} color="inherit" size={data?.countFontSize ? data.countFontSize : 30}/>
</Icon> )
</Box> }
<Box textAlign="right" lineHeight={1.25}>
<MDTypography variant="button" fontWeight="light" color="text">
{title}
</MDTypography>
{
count !== undefined ? (
<MDTypography variant="h4">{count.toLocaleString()}</MDTypography>
) : null
}
</Box>
</Box> </Box>
{ {
percentageAmount !== undefined && percentageAmount !== 0 ? ( percentageAmount !== undefined && percentageAmount !== 0 ? (
<Box px={2}> <Box pb={2}>
<Divider />
<MDTypography component="p" variant="button" color="text" display="flex"> <Divider sx={{marginTop: "0px"}} />
<MDTypography pl={3} component="p" variant="button" color="text" display="flex">
<MDTypography <MDTypography
component="span" component="span"
variant="button" variant="button"
@ -136,7 +127,7 @@ function StatisticsCard({title, data, color, icon, increaseIsGood}: Props): JSX.
</Box> </Box>
) : null ) : null
} }
</Card> </Box>
); );
} }

View File

@ -51,20 +51,12 @@ export interface TableDataInput
///////////////////////// /////////////////////////
interface Props interface Props
{ {
title: string;
linkText?: string;
linkURL?: string;
noRowsFoundHTML?: string; noRowsFoundHTML?: string;
data: TableDataInput; data: TableDataInput;
reloadWidgetCallback?: (params: string) => void;
widgetIndex?: number;
isChild?: boolean;
[key: string]: any;
} }
const qController = Client.getInstance(); const qController = Client.getInstance();
function TableCard({noRowsFoundHTML, data, reloadWidgetCallback}: Props): JSX.Element function TableCard({noRowsFoundHTML, data}: Props): JSX.Element
{ {
const [qInstance, setQInstance] = useState(null as QInstance); const [qInstance, setQInstance] = useState(null as QInstance);

View File

@ -114,6 +114,14 @@ async function getDefaultFilter(tableMetaData: QTableMetaData, searchParams: URL
{ {
values = await qController.possibleValues(tableMetaData.name, field.name, "", values); values = await qController.possibleValues(tableMetaData.name, field.name, "", values);
} }
////////////////////////////////////////////
// log message if no values were returned //
////////////////////////////////////////////
if (! values || values.length === 0)
{
console.warn("WARNING: No possible values were returned for [" + field.possibleValueSourceName + "] for values [" + criteria.values + "].");
}
} }
defaultFilter.items.push({ defaultFilter.items.push({