SPRINT-19: upped core frontend version, updates to pie charts and stacked charts

This commit is contained in:
Tim Chamberlain
2023-01-25 19:12:43 -06:00
parent 895724b87e
commit 4ac1063bca
10 changed files with 107 additions and 85 deletions

26
package-lock.json generated
View File

@ -11,7 +11,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.45", "@kingsrook/qqq-frontend-core": "1.0.48",
"@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",
@ -3354,9 +3354,9 @@
} }
}, },
"node_modules/@kingsrook/qqq-frontend-core": { "node_modules/@kingsrook/qqq-frontend-core": {
"version": "1.0.45", "version": "1.0.48",
"resolved": "https://npm.pkg.github.com/download/@Kingsrook/qqq-frontend-core/1.0.45/adee99b089456ae72ee9a311d35f6def5d308486", "resolved": "https://npm.pkg.github.com/download/@Kingsrook/qqq-frontend-core/1.0.48/27c1a09d17eccc82cf07c76db8c74ee89a92161e",
"integrity": "sha512-eT/Kp+Y69926DLVTvaRwyeGHPjWYuYOCR2CqwUf4UMrbtZC4tHsrI8uKisZ/ulZeNxm/6N/m0A+ElrXAnt5+mA==", "integrity": "sha512-781sx4RxIh6x5azNh+Nh5wtP5dPZ8nprTYVPNrBH0XsPB397bxgF3+bjfdtDFpaBkRAWuCiKoVEcz5eJZtT9dg==",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"axios": "0.27.2", "axios": "0.27.2",
@ -6529,9 +6529,9 @@
} }
}, },
"node_modules/caniuse-lite": { "node_modules/caniuse-lite": {
"version": "1.0.30001447", "version": "1.0.30001448",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001447.tgz", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001448.tgz",
"integrity": "sha512-bdKU1BQDPeEXe9A39xJnGtY0uRq/z5osrnXUw0TcK+EYno45Y+U7QU9HhHEyzvMDffpYadFXi3idnSNkcwLkTw==", "integrity": "sha512-tq2YI+MJnooG96XpbTRYkBxLxklZPOdLmNIOdIhvf7SNJan6u5vCKum8iT7ZfCt70m1GPkuC7P3TtX6UuhupuA==",
"funding": [ "funding": [
{ {
"type": "opencollective", "type": "opencollective",
@ -23098,9 +23098,9 @@
} }
}, },
"@kingsrook/qqq-frontend-core": { "@kingsrook/qqq-frontend-core": {
"version": "1.0.45", "version": "1.0.48",
"resolved": "https://npm.pkg.github.com/download/@Kingsrook/qqq-frontend-core/1.0.45/adee99b089456ae72ee9a311d35f6def5d308486", "resolved": "https://npm.pkg.github.com/download/@Kingsrook/qqq-frontend-core/1.0.48/27c1a09d17eccc82cf07c76db8c74ee89a92161e",
"integrity": "sha512-eT/Kp+Y69926DLVTvaRwyeGHPjWYuYOCR2CqwUf4UMrbtZC4tHsrI8uKisZ/ulZeNxm/6N/m0A+ElrXAnt5+mA==", "integrity": "sha512-781sx4RxIh6x5azNh+Nh5wtP5dPZ8nprTYVPNrBH0XsPB397bxgF3+bjfdtDFpaBkRAWuCiKoVEcz5eJZtT9dg==",
"requires": { "requires": {
"axios": "0.27.2", "axios": "0.27.2",
"form-data": "4.0.0" "form-data": "4.0.0"
@ -25390,9 +25390,9 @@
} }
}, },
"caniuse-lite": { "caniuse-lite": {
"version": "1.0.30001447", "version": "1.0.30001448",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001447.tgz", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001448.tgz",
"integrity": "sha512-bdKU1BQDPeEXe9A39xJnGtY0uRq/z5osrnXUw0TcK+EYno45Y+U7QU9HhHEyzvMDffpYadFXi3idnSNkcwLkTw==" "integrity": "sha512-tq2YI+MJnooG96XpbTRYkBxLxklZPOdLmNIOdIhvf7SNJan6u5vCKum8iT7ZfCt70m1GPkuC7P3TtX6UuhupuA=="
}, },
"case-sensitive-paths-webpack-plugin": { "case-sensitive-paths-webpack-plugin": {
"version": "2.4.0", "version": "2.4.0",

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.45", "@kingsrook/qqq-frontend-core": "1.0.48",
"@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

@ -47,7 +47,7 @@ import MultiStatisticsCard from "qqq/components/widgets/statistics/MultiStatisti
import SimpleStatisticsCard from "qqq/components/widgets/statistics/SimpleStatisticsCard"; import SimpleStatisticsCard from "qqq/components/widgets/statistics/SimpleStatisticsCard";
import StatisticsCard from "qqq/components/widgets/statistics/StatisticsCard"; import StatisticsCard from "qqq/components/widgets/statistics/StatisticsCard";
import TableCard from "qqq/components/widgets/tables/TableCard"; import TableCard from "qqq/components/widgets/tables/TableCard";
import Widget from "qqq/components/widgets/Widget"; import Widget, {WIDGET_DROPDOWN_SELECTION_LOCAL_STORAGE_KEY_ROOT} 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";
@ -81,6 +81,9 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, omit
const [widgetCounter, setWidgetCounter] = useState(0); const [widgetCounter, setWidgetCounter] = useState(0);
const [, forceUpdate] = useReducer((x) => x + 1, 0); const [, forceUpdate] = useReducer((x) => x + 1, 0);
const [currentUrlParams, setCurrentUrlParams] = useState(null as string);
const [haveLoadedParams, setHaveLoadedParams] = useState(false);
useEffect(() => useEffect(() =>
{ {
(async () => (async () =>
@ -92,24 +95,23 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, omit
useEffect(() => useEffect(() =>
{ {
if (!qInstance)
{
return;
}
forceUpdate();
for (let i = 0; i < widgetMetaDataList.length; i++) for (let i = 0; i < widgetMetaDataList.length; i++)
{ {
const widgetMetaData = widgetMetaDataList[i];
const urlParams = getQueryParams(widgetMetaData, null);
setCurrentUrlParams(urlParams);
setHaveLoadedParams(true);
widgetData[i] = {}; widgetData[i] = {};
(async () => (async () =>
{ {
widgetData[i] = await qController.widget(widgetMetaDataList[i].name, getQueryParams(null)); widgetData[i] = await qController.widget(widgetMetaData.name, urlParams);
setWidgetCounter(widgetCounter + 1); setWidgetCounter(widgetCounter + 1);
forceUpdate(); forceUpdate();
})(); })();
} }
setWidgetData(widgetData); setWidgetData(widgetData);
}, [qInstance, widgetMetaDataList]); }, [widgetMetaDataList]);
useEffect(() => useEffect(() =>
{ {
@ -120,16 +122,15 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, omit
{ {
setTimeout(async () => setTimeout(async () =>
{ {
widgetData[index] = await qController.widget(widgetMetaDataList[index].name, getQueryParams(data)); widgetData[index] = await qController.widget(widgetMetaDataList[index].name, getQueryParams(null, data));
setWidgetCounter(widgetCounter + 1); setWidgetCounter(widgetCounter + 1);
}, 1); }, 1);
}; };
function getQueryParams(extraParams: string): string function getQueryParams(widgetMetaData: QWidgetMetaData, extraParams: string): string
{ {
let ampersand = ""; let ampersand = "";
let params = ""; let params = "";
let foundParam = false;
if(entityPrimaryKey) if(entityPrimaryKey)
{ {
params += `${ampersand}id=${entityPrimaryKey}`; params += `${ampersand}id=${entityPrimaryKey}`;
@ -148,6 +149,26 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, omit
if(childUrlParams) if(childUrlParams)
{ {
params += `${ampersand}${childUrlParams}`; params += `${ampersand}${childUrlParams}`;
ampersand = "&";
}
/////////////////////////////////////////////////////////////////////////////
// see if local storage is used for any widget dropdowns, if so, look them //
// up and append to the query string //
/////////////////////////////////////////////////////////////////////////////
if(widgetMetaData && widgetMetaData.storeDropdownSelections && widgetMetaData.dropdowns)
{
for(let i = 0; i< widgetMetaData.dropdowns.length; i++)
{
const dropdownName = widgetMetaData.dropdowns[i].possibleValueSourceName;
const localStorageKey = `${WIDGET_DROPDOWN_SELECTION_LOCAL_STORAGE_KEY_ROOT}.${widgetMetaData.name}.${dropdownName}`;
const json = JSON.parse(localStorage.getItem(localStorageKey));
if(json)
{
params += `${ampersand}${dropdownName}=${json.id}`;
ampersand = "&";
}
}
} }
return params; return params;
@ -160,8 +181,9 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, omit
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%"}}>
{ {
widgetMetaData.type === "parentWidget" && ( haveLoadedParams && widgetMetaData.type === "parentWidget" && (
<ParentWidget <ParentWidget
urlParams={currentUrlParams}
entityPrimaryKey={entityPrimaryKey} entityPrimaryKey={entityPrimaryKey}
tableName={tableName} tableName={tableName}
widgetIndex={i} widgetIndex={i}

View File

@ -50,6 +50,7 @@ export interface ParentWidgetData
//////////////////////////////////// ////////////////////////////////////
interface Props interface Props
{ {
urlParams?: string;
widgetMetaData?: QWidgetMetaData; widgetMetaData?: QWidgetMetaData;
widgetIndex: number; widgetIndex: number;
data: ParentWidgetData; data: ParentWidgetData;
@ -61,9 +62,9 @@ interface Props
const qController = Client.getInstance(); const qController = Client.getInstance();
function ParentWidget({widgetMetaData, widgetIndex, data, reloadWidgetCallback, entityPrimaryKey, tableName, storeDropdownSelections}: Props, ): JSX.Element function ParentWidget({urlParams, widgetMetaData, widgetIndex, data, reloadWidgetCallback, entityPrimaryKey, tableName, storeDropdownSelections}: Props, ): JSX.Element
{ {
const [childUrlParams, setChildUrlParams] = useState(""); const [childUrlParams, setChildUrlParams] = useState((urlParams) ? urlParams : "");
const [qInstance, setQInstance] = useState(null as QInstance); const [qInstance, setQInstance] = useState(null as QInstance);
const [widgets, setWidgets] = useState([] as any[]); const [widgets, setWidgets] = useState([] as any[]);
@ -87,7 +88,7 @@ function ParentWidget({widgetMetaData, widgetIndex, data, reloadWidgetCallback,
}) })
setWidgets(widgetMetaDataList); setWidgets(widgetMetaDataList);
} }
}, [qInstance, data]); }, [qInstance, data, childUrlParams]);
const parentReloadWidgetCallback = (data: string) => const parentReloadWidgetCallback = (data: string) =>
{ {
@ -98,16 +99,18 @@ function ParentWidget({widgetMetaData, widgetIndex, data, reloadWidgetCallback,
// @ts-ignore // @ts-ignore
return ( return (
<Widget qInstance && data ? (
widgetMetaData={widgetMetaData} <Widget
widgetData={data} widgetMetaData={widgetMetaData}
storeDropdownSelections={storeDropdownSelections} widgetData={data}
reloadWidgetCallback={parentReloadWidgetCallback} storeDropdownSelections={storeDropdownSelections}
> reloadWidgetCallback={parentReloadWidgetCallback}
<Box sx={{height: "100%", width: "100%"}}> >
<DashboardWidgets widgetMetaDataList={widgets} entityPrimaryKey={entityPrimaryKey} tableName={tableName} childUrlParams={childUrlParams} areChildren={true}/> <Box sx={{height: "100%", width: "100%"}}>
</Box> <DashboardWidgets widgetMetaDataList={widgets} entityPrimaryKey={entityPrimaryKey} tableName={tableName} childUrlParams={childUrlParams} areChildren={true} />
</Widget> </Box>
</Widget>
) : null
); );
} }

View File

@ -33,6 +33,7 @@ import DropdownMenu, {DropdownOption} from "qqq/components/widgets/components/Dr
export interface WidgetData export interface WidgetData
{ {
label?: string;
dropdownLabelList?: string[]; dropdownLabelList?: string[];
dropdownNameList?: string[]; dropdownNameList?: string[];
dropdownDataList?: { dropdownDataList?: {
@ -120,7 +121,7 @@ export class Dropdown extends LabelComponent
} }
const WIDGET_DROPDOWN_SELECTION_LOCAL_STORAGE_KEY_ROOT = "qqq.widgets.dropdownData"; export 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
@ -254,7 +255,7 @@ function Widget(props: React.PropsWithChildren<Props>): JSX.Element
useEffect(() => useEffect(() =>
{ {
if(dropdownData) if(dropdownData && counter > 0)
{ {
let params = ""; let params = "";
for (let i = 0; i < dropdownData.length; i++) for (let i = 0; i < dropdownData.length; i++)
@ -311,10 +312,19 @@ function Widget(props: React.PropsWithChildren<Props>): JSX.Element
) )
} }
{ {
props.widgetMetaData?.label && ( //////////////////////////////////////////////////////////////////////////////////////////
// first look for a label in the widget data, which would override that in the metadata //
//////////////////////////////////////////////////////////////////////////////////////////
props.widgetData?.label? (
<Typography variant="h5" fontWeight="medium" pl={3} display="inline"> <Typography variant="h5" fontWeight="medium" pl={3} display="inline">
{props.widgetMetaData.label} {props.widgetData.label}
</Typography> </Typography>
) : (
props.widgetMetaData?.label && (
<Typography variant="h5" fontWeight="medium" pl={3} display="inline">
{props.widgetMetaData.label}
</Typography>
)
) )
} }
{ {

View File

@ -28,6 +28,7 @@ export const chartColors = ["info", "warning", "primary", "success", "error", "s
export interface DefaultChartData export interface DefaultChartData
{ {
labels: string[]; labels: string[];
urls?: string[];
datasets: [ datasets: [
{ {
label: string; label: string;

View File

@ -22,8 +22,9 @@
import Box from "@mui/material/Box"; import Box from "@mui/material/Box";
import {BarElement, CategoryScale, Chart as ChartJS, Legend, LinearScale, Title, Tooltip,} from "chart.js"; import {BarElement, CategoryScale, Chart as ChartJS, Legend, LinearScale, Title, Tooltip,} from "chart.js";
import React from "react"; import React, {useEffect} from "react";
import {Bar} from "react-chartjs-2"; import {Bar} from "react-chartjs-2";
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";
@ -56,29 +57,36 @@ interface Props
const {gradients} = colors; const {gradients} = colors;
function StackedBarChart({data}: Props): JSX.Element function StackedBarChart({data}: Props): JSX.Element
{ {
const navigate = useNavigate();
const handleClick = (e: Array<{}>) => const handleClick = (e: Array<{}>) =>
{ {
/* if(e && e.length > 0 && data?.urls && data?.urls.length)
if(e && e.length > 0 && data?.dataset?.urls && data?.dataset?.urls.length)
{ {
// @ts-ignore // @ts-ignore
navigate(chartData.dataset.urls[e[0]["index"]]); navigate(data.urls[e[0]["index"]]);
} }
*/
console.log(e); console.log(e);
} }
data?.datasets.forEach((dataset: any, index: number) => useEffect(() =>
{ {
if(! dataset.backgroundColor) if(data)
{ {
dataset.backgroundColor = gradients[chartColors[index]].state; data?.datasets.forEach((dataset: any, index: number) =>
{
if (!dataset.backgroundColor)
{
dataset.backgroundColor = gradients[chartColors[index]].state;
}
});
} }
}); }, [data]);
return <Box p={3}><Bar data={data} options={options} getElementsAtEvent={handleClick} /></Box>;
return data ? (
<Box p={3}><Bar data={data} options={options} getElementsAtEvent={handleClick} /></Box>
) : null;
} }
export default StackedBarChart; export default StackedBarChart;

View File

@ -27,7 +27,6 @@ import parse from "html-react-parser";
import React, {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 {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 {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";
@ -56,6 +55,7 @@ interface Props
[key: string]: any; [key: string]: any;
} }
function PieChart({description, chartData}: Props): JSX.Element function PieChart({description, chartData}: Props): JSX.Element
{ {
const navigate = useNavigate(); const navigate = useNavigate();
@ -80,9 +80,8 @@ function PieChart({description, chartData}: Props): JSX.Element
<Card sx={{boxShadow: "none", height: "100%", width: "100%", display: "flex", flexGrow: 1}}> <Card sx={{boxShadow: "none", height: "100%", width: "100%", display: "flex", flexGrow: 1}}>
<Box mt={3}> <Box mt={3}>
<Grid container alignItems="center"> <Grid container alignItems="center">
<Grid item xs={5}> <Grid item xs={12} justifyContent="center">
<Box width="100%" height="80%" py={2} pr={2} pl={2}>
<Box py={2} pr={2} pl={2}>
{useMemo( {useMemo(
() => ( () => (
<Pie data={data} options={options} getElementsAtEvent={handleClick} /> <Pie data={data} options={options} getElementsAtEvent={handleClick} />
@ -91,19 +90,6 @@ function PieChart({description, chartData}: Props): JSX.Element
)} )}
</Box> </Box>
</Grid> </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> </Grid>
<Divider /> <Divider />
{ {

View File

@ -58,11 +58,12 @@ function configs(labels: any, datasets: any)
], ],
}, },
options: { options: {
responsive: true,
maintainAspectRatio: true, maintainAspectRatio: true,
responsive: true,
aspectRatio: 2,
plugins: { plugins: {
legend: { legend: {
display: false, position: "bottom",
}, },
}, },
scales: { scales: {

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, {useEffect} from "react"; import React from "react";
export interface DropdownOption export interface DropdownOption
@ -47,15 +47,6 @@ interface Props
function DropdownMenu({localStorageKey, defaultValue, 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);