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",
"@emotion/react": "11.7.1",
"@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/material": "5.11.1",
"@mui/styles": "5.11.1",
@ -27,6 +27,7 @@
"chroma-js": "2.4.2",
"datejs": "1.0.0-rc3",
"downshift": "3.2.10",
"faker": "5.5.3",
"form-data": "4.0.0",
"formik": "2.2.9",
"html-react-parser": "1.4.8",
@ -77,6 +78,7 @@
"devDependencies": {
"@svgr/webpack": "6.5.1",
"@types/chroma-js": "2.1.3",
"@types/faker": "5.5.9",
"@types/react-table": "7.7.9",
"@typescript-eslint/eslint-plugin": "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
* Copyright 2022 Creative Tim (https://www.creative-tim.com)
* Product Page: https://www.creative-tim.com/product/material-dashboard-2-pro-react-ts
* Copyright 2022 Creative Tim (https://www.creative-tim.com)
Coded by www.creative-tim.com
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.
@ -20,27 +20,32 @@ Coded by www.creative-tim.com
*/
// types
interface ColorsTypes {
interface ColorsTypes
{
main: string;
focus: string;
}
interface GradientsTypes {
interface GradientsTypes
{
main: string;
state: string;
}
interface SocialMediaColorsTypes {
interface SocialMediaColorsTypes
{
main: string;
dark: string;
}
interface BadgeColorsTypes {
interface BadgeColorsTypes
{
background: string;
text: string;
}
interface Types {
interface Types
{
background:
| {
default: string;
@ -98,6 +103,11 @@ interface Types {
error: GradientsTypes;
light: GradientsTypes;
dark: GradientsTypes;
custom1: GradientsTypes;
custom2: GradientsTypes;
custom3: GradientsTypes;
custom4: GradientsTypes;
custom5: GradientsTypes;
}
| any;
socialMediaColors:
@ -262,6 +272,31 @@ const colors: Types = {
main: "#42424a",
state: "#191919",
},
custom1: {
main: "#3aaf85",
state: "#8c28c2",
},
custom2: {
main: "#0077b5",
state: "#ffe120",
},
custom3: {
main: "#ea4c89",
state: "#000000",
},
custom4: {
main: "#0077b5",
state: "#747474",
},
custom5: {
main: "#0077b5",
state: "#ffcefa",
},
},
socialMediaColors: {
@ -382,7 +417,7 @@ const colors: Types = {
inputBorderColor: "#d2d6da",
tabs: {
indicator: { boxShadow: "#ddd" },
indicator: {boxShadow: "#ddd"},
},
};

View File

@ -80,6 +80,11 @@ const MDBadgeDot: FC<Props> = forwardRef(
"error",
"light",
"dark",
"custom1",
"custom2",
"custom3",
"custom4",
"custom5"
];
const colorValues = {
@ -90,10 +95,14 @@ const MDBadgeDot: FC<Props> = forwardRef(
"warning": "#fb8c00",
"error": "#F44335",
"light": "#f0f2f5",
"dark": "#344767"
"dark": "#344767",
"custom1": "#8c28c2",
"custom2": "#ffe120",
"custom3": "#000000",
"custom4": "#747474",
"custom5": "#ffcefa"
} as any;
const validColorIndex = validColors.findIndex((el) => el === color);
return (
<Box ref={ref} display="flex" alignItems="center" p={padding} {...rest}>
<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 DefaultLineChart from "qqq/components/widgets/charts/linechart/DefaultLineChart";
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 FieldValueListWidget from "qqq/components/widgets/misc/FieldValueListWidget";
import QuickSightChart from "qqq/components/widgets/misc/QuickSightChart";
@ -161,13 +162,13 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, omit
{
widgetMetaData.type === "parentWidget" && (
<ParentWidget
icon={widgetMetaData.icon}
entityPrimaryKey={entityPrimaryKey}
tableName={tableName}
widgetIndex={i}
label={widgetMetaData.label}
widgetMetaData={widgetMetaData}
data={widgetData[i]}
reloadWidgetCallback={reloadWidget}
storeDropdownSelections={widgetMetaData.storeDropdownSelections}
/>
)
}
@ -184,30 +185,34 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, omit
{
widgetMetaData.type === "table" && (
<Widget
label={widgetData[i]?.label}
isCard={widgetMetaData.isCard}
widgetMetaData={widgetMetaData}
widgetData={widgetData[i]}
reloadWidgetCallback={(data) => reloadWidget(i, data)}
isChild={areChildren}
>
<TableCard
color="info"
title={widgetMetaData.label}
linkText={widgetData[i]?.linkText}
linkURL={widgetData[i]?.linkURL}
noRowsFoundHTML={widgetData[i]?.noRowsFoundHTML}
data={widgetData[i]}
dropdownOptions={widgetData[i]?.dropdownOptions}
reloadWidgetCallback={(data) => reloadWidget(i, data)}
widgetIndex={i}
/>
</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 && (
<Widget
label={widgetData[i]?.processMetaData?.label}
widgetMetaData={widgetMetaData}
widgetData={widgetData[i]}
reloadWidgetCallback={(data) => reloadWidget(i, data)}>
<div>
@ -234,7 +239,7 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, omit
}
{
widgetMetaData.type === "html" && (
<Widget label={widgetMetaData.label}>
<Widget widgetMetaData={widgetMetaData}>
<Box px={1} pt={0} pb={2}>
<MDTypography component="div" variant="button" color="text" fontWeight="light">
{
@ -247,15 +252,6 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, omit
</Widget>
)
}
{
widgetMetaData.type === "multiStatistics" && (
<MultiStatisticsCard
color="info"
title={widgetMetaData.label}
data={widgetData[i]}
/>
)
}
{
widgetMetaData.type === "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" && (
widgetData && widgetData[i] && (
@ -280,17 +295,13 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, omit
)
}
{
widgetMetaData.type === "statistics" && (
widgetData && widgetData[i] && (
<StatisticsCard
widgetMetaData.type === "multiStatistics" && (
<MultiStatisticsCard
color="info"
title={widgetMetaData.label}
color={colors.info.main}
icon={widgetMetaData.icon}
data={widgetData[i]}
increaseIsGood={true}
/>
)
)
}
{
widgetMetaData.type === "quickSightChart" && (
@ -310,11 +321,19 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, omit
}
{
widgetMetaData.type === "pieChart" && (
<PieChartCard
title={widgetMetaData.label}
<Widget
widgetMetaData={widgetMetaData}
widgetData={widgetData[i]}
isChild={areChildren}
reloadWidgetCallback={(data) => reloadWidget(i, data)}>
<div>
<PieChart
chartData={widgetData[i]?.chartData}
description={widgetData[i]?.description}
data={widgetData[i]?.chartData}
/>
</div>
</Widget>
)
}
{
@ -362,7 +381,7 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, omit
widgetMetaData.type === "childRecordList" && (
widgetData && widgetData[i] &&
<RecordGridWidget
title={widgetMetaData.label}
widgetMetaData={widgetMetaData}
data={widgetData[i]}
/>
)
@ -372,7 +391,7 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, omit
widgetMetaData.type === "fieldValueList" && (
widgetData && widgetData[i] &&
<FieldValueListWidget
title={widgetMetaData.label}
widgetMetaData={widgetMetaData}
data={widgetData[i]}
reloadWidgetCallback={(data) => reloadWidget(i, data)}
/>

View File

@ -50,18 +50,18 @@ export interface ParentWidgetData
////////////////////////////////////
interface Props
{
widgetMetaData?: QWidgetMetaData;
widgetIndex: number;
label: string;
icon?: string;
data: ParentWidgetData;
reloadWidgetCallback?: (widgetIndex: number, params: string) => void;
entityPrimaryKey?: string;
tableName?: string;
storeDropdownSelections?: boolean;
}
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 [qInstance, setQInstance] = useState(null as QInstance);
@ -99,12 +99,12 @@ function ParentWidget({widgetIndex, label, icon, data, reloadWidgetCallback, ent
// @ts-ignore
return (
<Widget
icon={icon}
label={label}
widgetMetaData={widgetMetaData}
widgetData={data}
storeDropdownSelections={storeDropdownSelections}
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}/>
</Box>
</Widget>

View File

@ -20,6 +20,7 @@
*/
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 Button from "@mui/material/Button";
import Card from "@mui/material/Card";
@ -44,21 +45,19 @@ export interface WidgetData
interface Props
{
icon?: string;
label: string;
labelAdditionalComponentsLeft: LabelComponent[];
labelAdditionalComponentsRight: LabelComponent[];
widgetMetaData?: QWidgetMetaData;
widgetData?: WidgetData;
children: JSX.Element;
reloadWidgetCallback?: (params: string) => void;
isChild?: boolean;
isCard?: boolean;
storeDropdownSelections?: boolean;
}
Widget.defaultProps = {
isCard: true,
isChild: false,
label: null,
widgetMetaData: {},
widgetData: {},
labelAdditionalComponentsLeft: [],
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
{
@ -133,7 +134,7 @@ function Widget(props: React.PropsWithChildren<Props>): JSX.Element
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)
{
@ -157,10 +158,23 @@ function Widget(props: React.PropsWithChildren<Props>): JSX.Element
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
return (
<Box my={2} mr={2} sx={{float: "right"}}>
<DropdownMenu
localStorageKey={localStorageKey}
defaultValue={defaultValue}
sx={{width: 200, marginLeft: "15px"}}
label={`Select ${dropdown.label}`}
dropdownOptions={dropdown.options}
@ -199,12 +213,14 @@ function Widget(props: React.PropsWithChildren<Props>): JSX.Element
// find the index base on selected label //
///////////////////////////////////////////
const tableName = dropdownLabel.replace("Select ", "");
let dropdownName = "";
let index = -1;
for (let i = 0; i < props.widgetData.dropdownLabelList.length; i++)
{
if (tableName === props.widgetData.dropdownLabelList[i])
{
index = i;
dropdownName = props.widgetData.dropdownNameList[i];
break;
}
}
@ -217,6 +233,22 @@ function Widget(props: React.PropsWithChildren<Props>): JSX.Element
dropdownData[index] = (changedData) ? changedData.id : null;
setDropdownData(dropdownData);
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,19 +277,17 @@ function Widget(props: React.PropsWithChildren<Props>): JSX.Element
}
else
{
console.log(`No reload widget callback in ${props.label}`)
console.log(`No reload widget callback in ${props.widgetMetaData.label}`)
}
}
}, [counter]);
const widgetContent =
<Box sx={{width: "100%"}}>
{
(props.icon || props.label) && (
<Box display="flex" justifyContent="space-between" alignItems="center" sx={{width: "100%"}}>
<Box py={2}>
<Box pt={2}>
{
props.icon && (
props.widgetMetaData?.icon && (
<Box
ml={3}
mt={-4}
@ -274,32 +304,35 @@ function Widget(props: React.PropsWithChildren<Props>): JSX.Element
}}
>
<Icon fontSize="medium" color="inherit">
{props.icon}
{props.widgetMetaData.icon}
</Icon>
</Box>
)
}
<Typography variant={props.isChild ? "h6" : "h5"} fontWeight="medium" p={3} display="inline">
{props.label}
{
props.widgetMetaData?.label && (
<Typography variant="h5" fontWeight="medium" pl={3} display="inline">
{props.widgetMetaData.label}
</Typography>
)
}
{
props.labelAdditionalComponentsLeft.map((component, i) =>
{
return (<span key={i}>{renderComponent(component)}</span>);
return (<span key={i}>{renderComponent(component, i)}</span>);
})
}
</Box>
<Box pr={1}>
<Box>
{
effectiveLabelAdditionalComponentsRight.map((component, i) =>
{
return (<span key={i}>{renderComponent(component)}</span>);
return (<span key={i}>{renderComponent(component, i)}</span>);
})
}
</Box>
</Box>
)
}
{
props.widgetData?.dropdownNeedsSelectedText ? (
<Box pb={3} pr={3} sx={{width: "100%", textAlign: "right"}}>
@ -313,7 +346,7 @@ function Widget(props: React.PropsWithChildren<Props>): JSX.Element
}
</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;

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/>.
*/
import {Card} from "@mui/material";
import Box from "@mui/material/Box";
import Card from "@mui/material/Card";
import Icon from "@mui/material/Icon";
import Divider from "@mui/material/Divider";
import Grid from "@mui/material/Grid";
import parse from "html-react-parser";
import {ReactNode, useMemo} from "react";
import React, {useMemo} from "react";
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 {chartColors} from "qqq/components/widgets/charts/DefaultChartData";
import configs from "qqq/components/widgets/charts/piechart/PieChartConfigs";
//////////////////////////////////////////
@ -38,6 +42,7 @@ export interface PieChartData
label: string;
backgroundColors?: string[];
data: number[];
urls?: string[];
};
}
@ -45,64 +50,78 @@ export interface PieChartData
// Declaring props types for PieChart
interface Props
{
icon?: {
color?: "primary" | "secondary" | "info" | "success" | "warning" | "error" | "light" | "dark";
component: ReactNode;
};
title?: string;
description?: string;
height?: string | number;
chart: PieChartData;
chartData: PieChartData;
[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 = (
<Box py={2} pr={2} pl={icon.component ? 1 : 2}>
{title || description ? (
<Box display="flex" px={description ? 1 : 0} pt={description ? 1 : 0}>
{icon.component && (
<Box
width="4rem"
height="4rem"
borderRadius="xl"
display="flex"
justifyContent="center"
alignItems="center"
color="white"
mt={-5}
mr={2}
sx={{backgroundColor: icon.color || "info"}}
>
<Icon fontSize="medium">{icon.component}</Icon>
</Box>
if (chartData && chartData.dataset)
{
chartData.dataset.backgroundColors = chartColors;
}
const {data, options} = configs(chartData?.labels || [], chartData?.dataset || {});
const handleClick = (e: Array<{}>) =>
{
if(e && e.length > 0 && chartData?.dataset?.urls && chartData?.dataset?.urls.length)
{
// @ts-ignore
navigate(chartData.dataset.urls[e[0]["index"]]);
}
}
return (
<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 mt={icon.component ? -2 : 0}>
{title && <MDTypography variant="h6">{title}</MDTypography>}
<Box mb={2}>
<MDTypography component="div" variant="button" color="text">
</Box>
</Grid>
<Grid item xs={7}>
<Box pr={1}>
{
data && data.labels ? (
(data.labels.map((label: string, index: number) => (
<Box key={index}>
<MDBadgeDot color={chartColors[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>
</Box>
) : null}
{useMemo(
() => (
<Box height={height}>
<Pie data={data} options={options} />
</Box>
),
[chart, height]
)}
</Box>
</Card>
);
return title || description ? <Card>{renderChart}</Card> : renderChart;
}
// 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: {
responsive: true,
maintainAspectRatio: false,
maintainAspectRatio: true,
plugins: {
legend: {
display: false,
},
},
interaction: {
intersect: false,
mode: "index",
},
scales: {
y: {
grid: {

View File

@ -23,7 +23,7 @@ import {Theme} from "@mui/material";
import Autocomplete from "@mui/material/Autocomplete";
import TextField from "@mui/material/TextField";
import {SxProps} from "@mui/system";
import React from "react";
import React, {useEffect} from "react";
export interface DropdownOption
@ -37,14 +37,25 @@ export interface DropdownOption
/////////////////////////
interface Props
{
defaultValue?: any;
localStorageKey?: string;
label?: string;
dropdownOptions?: DropdownOption[];
onChangeCallback?: (dropdownLabel: string, data: any) => void;
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) =>
{
onChangeCallback(label, value);
@ -54,12 +65,14 @@ function DropdownMenu({label, dropdownOptions, onChangeCallback, sx}: Props): JS
dropdownOptions ? (
<span style={{whiteSpace: "nowrap"}}>
<Autocomplete
defaultValue={defaultValue}
size="small"
disablePortal
id={`${label}-combo-box`}
options={dropdownOptions}
sx={{...sx, cursor: "pointer"}}
onChange={handleOnChange}
isOptionEqualToValue={(option, value) => option.id === value.id}
renderInput={(params: any) => <TextField {...params} label={label} />}
renderOption={(props, option: DropdownOption) => (
<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 {QWidgetMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QWidgetMetaData";
import {QRecord} from "@kingsrook/qqq-frontend-core/lib/model/QRecord";
import {Skeleton} from "@mui/material";
import Box from "@mui/material/Box";
@ -31,19 +32,19 @@ import ValueUtils from "qqq/utils/qqq/ValueUtils";
interface Props
{
title: string;
widgetMetaData: QWidgetMetaData;
data: any;
reloadWidgetCallback?: (params: string) => void;
}
FieldValueListWidget.defaultProps = {};
function FieldValueListWidget({title, data, reloadWidgetCallback}: Props): JSX.Element
function FieldValueListWidget({widgetMetaData, data, reloadWidgetCallback}: Props): JSX.Element
{
if(data?.dropdownNeedsSelectedText)
{
return (
<Widget label={title} widgetData={data} reloadWidgetCallback={reloadWidgetCallback}>
<Widget widgetMetaData={widgetMetaData} widgetData={data} reloadWidgetCallback={reloadWidgetCallback}>
<br />
</Widget>
);
@ -53,7 +54,7 @@ function FieldValueListWidget({title, data, reloadWidgetCallback}: Props): JSX.E
{
const skeletons = [75, 50, 90];
return (
<Widget label={title}>
<Widget widgetMetaData={widgetMetaData}>
<Box p={3} pt={0} display="flex" flexDirection="column">
{skeletons.map((s) =>
(
@ -79,7 +80,7 @@ function FieldValueListWidget({title, data, reloadWidgetCallback}: Props): JSX.E
const fieldIndentLevels = data.fieldIndentLevels ?? {};
return (
<Widget label={title} widgetData={data} reloadWidgetCallback={reloadWidgetCallback}>
<Widget widgetMetaData={widgetMetaData} widgetData={data} reloadWidgetCallback={reloadWidgetCallback}>
<Box p={3} pt={0} display="flex" flexDirection="column">
{
fields.map((field: QFieldMetaData, index: number) => (

View File

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

View File

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

View File

@ -51,20 +51,12 @@ export interface TableDataInput
/////////////////////////
interface Props
{
title: string;
linkText?: string;
linkURL?: string;
noRowsFoundHTML?: string;
data: TableDataInput;
reloadWidgetCallback?: (params: string) => void;
widgetIndex?: number;
isChild?: boolean;
[key: string]: any;
}
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);

View File

@ -114,6 +114,14 @@ async function getDefaultFilter(tableMetaData: QTableMetaData, searchParams: URL
{
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({