mirror of
https://github.com/Kingsrook/qqq-frontend-material-dashboard.git
synced 2025-07-18 05:10:45 +00:00
CE-604 Add topRightInsideCardIcon as a right-component and chartSubheaderData in StackedBarChart and PieChart; Add support for tabs;
This commit is contained in:
@ -0,0 +1,31 @@
|
|||||||
|
/*
|
||||||
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
|
* Copyright (C) 2021-2023. Kingsrook, LLC
|
||||||
|
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||||
|
* contact@kingsrook.com
|
||||||
|
* https://github.com/Kingsrook/
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.kingsrook.qqq.frontend.materialdashboard.model.metadata;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public interface MaterialDashboardIconRoleNames
|
||||||
|
{
|
||||||
|
String TOP_RIGHT_INSIDE_CARD = "topRightInsideCard";
|
||||||
|
}
|
@ -22,11 +22,14 @@ import {QWidgetMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/Q
|
|||||||
import {Skeleton} from "@mui/material";
|
import {Skeleton} from "@mui/material";
|
||||||
import Box from "@mui/material/Box";
|
import Box from "@mui/material/Box";
|
||||||
import Grid from "@mui/material/Grid";
|
import Grid from "@mui/material/Grid";
|
||||||
|
import Tab from "@mui/material/Tab";
|
||||||
|
import Tabs from "@mui/material/Tabs";
|
||||||
import parse from "html-react-parser";
|
import parse from "html-react-parser";
|
||||||
import React, {useContext, useEffect, useReducer, useState} from "react";
|
import React, {useContext, useEffect, useReducer, useState} from "react";
|
||||||
import {useLocation} from "react-router-dom";
|
import {useLocation} from "react-router-dom";
|
||||||
import QContext from "QContext";
|
import QContext from "QContext";
|
||||||
import MDTypography from "qqq/components/legacy/MDTypography";
|
import MDTypography from "qqq/components/legacy/MDTypography";
|
||||||
|
import TabPanel from "qqq/components/misc/TabPanel";
|
||||||
import BarChart from "qqq/components/widgets/charts/barchart/BarChart";
|
import BarChart from "qqq/components/widgets/charts/barchart/BarChart";
|
||||||
import HorizontalBarChart from "qqq/components/widgets/charts/barchart/HorizontalBarChart";
|
import HorizontalBarChart from "qqq/components/widgets/charts/barchart/HorizontalBarChart";
|
||||||
import DefaultLineChart from "qqq/components/widgets/charts/linechart/DefaultLineChart";
|
import DefaultLineChart from "qqq/components/widgets/charts/linechart/DefaultLineChart";
|
||||||
@ -44,7 +47,7 @@ import USMapWidget from "qqq/components/widgets/misc/USMapWidget";
|
|||||||
import ParentWidget from "qqq/components/widgets/ParentWidget";
|
import ParentWidget from "qqq/components/widgets/ParentWidget";
|
||||||
import MultiStatisticsCard from "qqq/components/widgets/statistics/MultiStatisticsCard";
|
import MultiStatisticsCard from "qqq/components/widgets/statistics/MultiStatisticsCard";
|
||||||
import StatisticsCard from "qqq/components/widgets/statistics/StatisticsCard";
|
import StatisticsCard from "qqq/components/widgets/statistics/StatisticsCard";
|
||||||
import Widget, {WIDGET_DROPDOWN_SELECTION_LOCAL_STORAGE_KEY_ROOT} from "qqq/components/widgets/Widget";
|
import Widget, {HeaderIcon, WIDGET_DROPDOWN_SELECTION_LOCAL_STORAGE_KEY_ROOT, LabelComponent} from "qqq/components/widgets/Widget";
|
||||||
import ProcessRun from "qqq/pages/processes/ProcessRun";
|
import ProcessRun from "qqq/pages/processes/ProcessRun";
|
||||||
import Client from "qqq/utils/qqq/Client";
|
import Client from "qqq/utils/qqq/Client";
|
||||||
import TableWidget from "./tables/TableWidget";
|
import TableWidget from "./tables/TableWidget";
|
||||||
@ -58,9 +61,10 @@ interface Props
|
|||||||
tableName?: string;
|
tableName?: string;
|
||||||
entityPrimaryKey?: string;
|
entityPrimaryKey?: string;
|
||||||
omitWrappingGridContainer: boolean;
|
omitWrappingGridContainer: boolean;
|
||||||
areChildren?: boolean
|
areChildren?: boolean;
|
||||||
childUrlParams?: string
|
childUrlParams?: string;
|
||||||
parentWidgetMetaData?: QWidgetMetaData
|
parentWidgetMetaData?: QWidgetMetaData;
|
||||||
|
wrapWidgetsInTabPanels: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
DashboardWidgets.defaultProps = {
|
DashboardWidgets.defaultProps = {
|
||||||
@ -70,10 +74,11 @@ DashboardWidgets.defaultProps = {
|
|||||||
omitWrappingGridContainer: false,
|
omitWrappingGridContainer: false,
|
||||||
areChildren: false,
|
areChildren: false,
|
||||||
childUrlParams: "",
|
childUrlParams: "",
|
||||||
parentWidgetMetaData: null
|
parentWidgetMetaData: null,
|
||||||
|
wrapWidgetsInTabPanels: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, omitWrappingGridContainer, areChildren, childUrlParams, parentWidgetMetaData}: Props): JSX.Element
|
function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, omitWrappingGridContainer, areChildren, childUrlParams, parentWidgetMetaData, wrapWidgetsInTabPanels}: Props): JSX.Element
|
||||||
{
|
{
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const [widgetData, setWidgetData] = useState([] as any[]);
|
const [widgetData, setWidgetData] = useState([] as any[]);
|
||||||
@ -84,6 +89,13 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, omit
|
|||||||
const [haveLoadedParams, setHaveLoadedParams] = useState(false);
|
const [haveLoadedParams, setHaveLoadedParams] = useState(false);
|
||||||
const {accentColor} = useContext(QContext);
|
const {accentColor} = useContext(QContext);
|
||||||
|
|
||||||
|
const [selectedTab, setSelectedTab] = useState(0);
|
||||||
|
|
||||||
|
const changeTab = (newValue: number) =>
|
||||||
|
{
|
||||||
|
setSelectedTab(newValue);
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() =>
|
useEffect(() =>
|
||||||
{
|
{
|
||||||
setWidgetData([]);
|
setWidgetData([]);
|
||||||
@ -102,15 +114,15 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, omit
|
|||||||
widgetData[i] = await qController.widget(widgetMetaData.name, urlParams);
|
widgetData[i] = await qController.widget(widgetMetaData.name, urlParams);
|
||||||
setWidgetData(widgetData);
|
setWidgetData(widgetData);
|
||||||
setWidgetCounter(widgetCounter + 1);
|
setWidgetCounter(widgetCounter + 1);
|
||||||
if(widgetData[i])
|
if (widgetData[i])
|
||||||
{
|
{
|
||||||
widgetData[i]["errorLoading"] = false;
|
widgetData[i]["errorLoading"] = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch(e)
|
catch (e)
|
||||||
{
|
{
|
||||||
console.error(e);
|
console.error(e);
|
||||||
if(widgetData[i])
|
if (widgetData[i])
|
||||||
{
|
{
|
||||||
widgetData[i]["errorLoading"] = true;
|
widgetData[i]["errorLoading"] = true;
|
||||||
}
|
}
|
||||||
@ -123,7 +135,7 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, omit
|
|||||||
|
|
||||||
const reloadWidget = async (index: number, data: string) =>
|
const reloadWidget = async (index: number, data: string) =>
|
||||||
{
|
{
|
||||||
(async() =>
|
(async () =>
|
||||||
{
|
{
|
||||||
const urlParams = getQueryParams(widgetMetaDataList[index], data);
|
const urlParams = getQueryParams(widgetMetaDataList[index], data);
|
||||||
setCurrentUrlParams(urlParams);
|
setCurrentUrlParams(urlParams);
|
||||||
@ -140,7 +152,7 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, omit
|
|||||||
widgetData[index]["errorLoading"] = false;
|
widgetData[index]["errorLoading"] = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch(e)
|
catch (e)
|
||||||
{
|
{
|
||||||
console.error(e);
|
console.error(e);
|
||||||
if (widgetData[index])
|
if (widgetData[index])
|
||||||
@ -151,7 +163,7 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, omit
|
|||||||
|
|
||||||
forceUpdate();
|
forceUpdate();
|
||||||
})();
|
})();
|
||||||
}
|
};
|
||||||
|
|
||||||
function getQueryParams(widgetMetaData: QWidgetMetaData, extraParams: string): string
|
function getQueryParams(widgetMetaData: QWidgetMetaData, extraParams: string): string
|
||||||
{
|
{
|
||||||
@ -178,36 +190,36 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, omit
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(entityPrimaryKey)
|
if (entityPrimaryKey)
|
||||||
{
|
{
|
||||||
paramMap.set("id", entityPrimaryKey);
|
paramMap.set("id", entityPrimaryKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(tableName)
|
if (tableName)
|
||||||
{
|
{
|
||||||
paramMap.set("tableName", tableName);
|
paramMap.set("tableName", tableName);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(extraParams)
|
if (extraParams)
|
||||||
{
|
{
|
||||||
let pairs = extraParams.split("&");
|
let pairs = extraParams.split("&");
|
||||||
for (let i = 0; i < pairs.length; i++)
|
for (let i = 0; i < pairs.length; i++)
|
||||||
{
|
{
|
||||||
let nameValue = pairs[i].split("=");
|
let nameValue = pairs[i].split("=");
|
||||||
if(nameValue.length == 2)
|
if (nameValue.length == 2)
|
||||||
{
|
{
|
||||||
paramMap.set(nameValue[0], nameValue[1]);
|
paramMap.set(nameValue[0], nameValue[1]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(childUrlParams)
|
if (childUrlParams)
|
||||||
{
|
{
|
||||||
let pairs = childUrlParams.split("&");
|
let pairs = childUrlParams.split("&");
|
||||||
for (let i = 0; i < pairs.length; i++)
|
for (let i = 0; i < pairs.length; i++)
|
||||||
{
|
{
|
||||||
let nameValue = pairs[i].split("=");
|
let nameValue = pairs[i].split("=");
|
||||||
if(nameValue.length == 2)
|
if (nameValue.length == 2)
|
||||||
{
|
{
|
||||||
paramMap.set(nameValue[0], nameValue[1]);
|
paramMap.set(nameValue[0], nameValue[1]);
|
||||||
}
|
}
|
||||||
@ -227,6 +239,16 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, omit
|
|||||||
|
|
||||||
const renderWidget = (widgetMetaData: QWidgetMetaData, i: number): JSX.Element =>
|
const renderWidget = (widgetMetaData: QWidgetMetaData, i: number): JSX.Element =>
|
||||||
{
|
{
|
||||||
|
const labelAdditionalComponentsRight: LabelComponent[] = [];
|
||||||
|
if (widgetMetaData && widgetMetaData.icons)
|
||||||
|
{
|
||||||
|
const topRightInsideCardIcon = widgetMetaData.icons.get("topRightInsideCard");
|
||||||
|
if (topRightInsideCardIcon)
|
||||||
|
{
|
||||||
|
labelAdditionalComponentsRight.push(new HeaderIcon(topRightInsideCardIcon.name, topRightInsideCardIcon.color));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box key={`${widgetMetaData.name}-${i}`} sx={{alignItems: "stretch", flexGrow: 1, display: "flex", marginTop: "0px", paddingTop: "0px", width: "100%", height: "100%"}}>
|
<Box key={`${widgetMetaData.name}-${i}`} sx={{alignItems: "stretch", flexGrow: 1, display: "flex", marginTop: "0px", paddingTop: "0px", width: "100%", height: "100%"}}>
|
||||||
{
|
{
|
||||||
@ -270,8 +292,9 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, omit
|
|||||||
widgetData={widgetData[i]}
|
widgetData={widgetData[i]}
|
||||||
reloadWidgetCallback={(data) => reloadWidget(i, data)}
|
reloadWidgetCallback={(data) => reloadWidget(i, data)}
|
||||||
isChild={areChildren}
|
isChild={areChildren}
|
||||||
|
labelAdditionalComponentsRight={labelAdditionalComponentsRight}
|
||||||
>
|
>
|
||||||
<StackedBarChart data={widgetData[i]?.chartData}/>
|
<StackedBarChart data={widgetData[i]?.chartData} chartSubheaderData={widgetData[i]?.chartSubheaderData} />
|
||||||
</Widget>
|
</Widget>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -381,10 +404,12 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, omit
|
|||||||
widgetData={widgetData[i]}
|
widgetData={widgetData[i]}
|
||||||
reloadWidgetCallback={(data) => reloadWidget(i, data)}
|
reloadWidgetCallback={(data) => reloadWidget(i, data)}
|
||||||
isChild={areChildren}
|
isChild={areChildren}
|
||||||
|
labelAdditionalComponentsRight={labelAdditionalComponentsRight}
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<PieChart
|
<PieChart
|
||||||
chartData={widgetData[i]?.chartData}
|
chartData={widgetData[i]?.chartData}
|
||||||
|
chartSubheaderData={widgetData[i]?.chartSubheaderData}
|
||||||
description={widgetData[i]?.description}
|
description={widgetData[i]?.description}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -436,11 +461,11 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, omit
|
|||||||
{
|
{
|
||||||
widgetMetaData.type === "fieldValueList" && (
|
widgetMetaData.type === "fieldValueList" && (
|
||||||
widgetData && widgetData[i] &&
|
widgetData && widgetData[i] &&
|
||||||
<FieldValueListWidget
|
<FieldValueListWidget
|
||||||
widgetMetaData={widgetMetaData}
|
widgetMetaData={widgetMetaData}
|
||||||
data={widgetData[i]}
|
data={widgetData[i]}
|
||||||
reloadWidgetCallback={(data) => reloadWidget(i, data)}
|
reloadWidgetCallback={(data) => reloadWidget(i, data)}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
@ -461,32 +486,61 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, omit
|
|||||||
}
|
}
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
const body: JSX.Element =
|
const body: JSX.Element =
|
||||||
(
|
(
|
||||||
<>
|
<>
|
||||||
{
|
{
|
||||||
widgetMetaDataList.map((widgetMetaData, i) => (
|
widgetMetaDataList.map((widgetMetaData, i) =>
|
||||||
omitWrappingGridContainer
|
{
|
||||||
? widgetMetaData && renderWidget(widgetMetaData, i)
|
let renderedWidget = widgetMetaData ? renderWidget(widgetMetaData, i) : (<></>);
|
||||||
:
|
|
||||||
widgetMetaData && <Grid id={widgetMetaData.name} key={`${widgetMetaData.name}-${i}`} item lg={widgetMetaData.gridColumns ? widgetMetaData.gridColumns : 12} xs={12} sx={{display: "flex", alignItems: "stretch", scrollMarginTop: "100px"}}>
|
if (!omitWrappingGridContainer)
|
||||||
{renderWidget(widgetMetaData, i)}
|
{
|
||||||
</Grid>
|
renderedWidget = (<Grid id={widgetMetaData.name} item lg={widgetMetaData.gridColumns ? widgetMetaData.gridColumns : 12} xs={12} sx={{display: "flex", alignItems: "stretch", scrollMarginTop: "100px"}}>
|
||||||
))
|
{renderedWidget}
|
||||||
|
</Grid>);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wrapWidgetsInTabPanels)
|
||||||
|
{
|
||||||
|
renderedWidget = (<TabPanel index={i} value={selectedTab} style={{padding: "1rem 1.5rem 0 1.5rem", width: "100%"}}>
|
||||||
|
{renderedWidget}
|
||||||
|
</TabPanel>);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (<React.Fragment key={`${widgetMetaData.name}-${i}`}>{renderedWidget}</React.Fragment>)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const tabs = widgetMetaDataList && wrapWidgetsInTabPanels ?
|
||||||
|
<Tabs
|
||||||
|
sx={{m: 0, mb: 1.5}}
|
||||||
|
value={selectedTab}
|
||||||
|
onChange={(event, newValue) => changeTab(newValue)}
|
||||||
|
variant="standard"
|
||||||
|
>
|
||||||
|
{widgetMetaDataList.map((widgetMetaData, i) => (
|
||||||
|
<Tab key={widgetMetaData.name} label={widgetMetaData.label} />
|
||||||
|
))}
|
||||||
|
</Tabs>
|
||||||
|
: <></>
|
||||||
|
|
||||||
return (
|
return (
|
||||||
widgetCount > 0 ? (
|
widgetCount > 0 ? (
|
||||||
omitWrappingGridContainer ? body :
|
<>
|
||||||
(
|
{tabs}
|
||||||
<Grid container spacing={3} pb={4}>
|
{
|
||||||
{body}
|
omitWrappingGridContainer ? body : (
|
||||||
</Grid>
|
<Grid container spacing={3} pb={4}>
|
||||||
)
|
{body}
|
||||||
|
</Grid>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</>
|
||||||
) : null
|
) : null
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user