SPRINT-17: updated some widgets to look less broken when data is 'not available now', checkpoint commit on 'real dashboards'

This commit is contained in:
Tim Chamberlain
2022-12-15 10:07:27 -06:00
parent e362e7be44
commit 630620b65f
22 changed files with 507 additions and 332 deletions

View File

@ -13,7 +13,7 @@
"@fullcalendar/interaction": "5.10.0", "@fullcalendar/interaction": "5.10.0",
"@fullcalendar/react": "5.10.0", "@fullcalendar/react": "5.10.0",
"@fullcalendar/timegrid": "5.10.0", "@fullcalendar/timegrid": "5.10.0",
"@kingsrook/qqq-frontend-core": "1.0.38", "@kingsrook/qqq-frontend-core": "1.0.39",
"@mui/icons-material": "5.4.1", "@mui/icons-material": "5.4.1",
"@mui/material": "5.4.1", "@mui/material": "5.4.1",
"@mui/styled-engine": "5.4.1", "@mui/styled-engine": "5.4.1",

BIN
public/flags/AU.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
public/flags/BR.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
public/flags/DE.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 927 B

BIN
public/flags/GB.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
public/flags/US.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -83,7 +83,6 @@ function getStaticRoutes()
}, },
], ],
}, },
{type: "divider", key: "divider-1"},
]; ];
} }
@ -411,14 +410,35 @@ export default function App()
const sideNavAppList = [] as any[]; const sideNavAppList = [] as any[];
const appRoutesList = [] as any[]; const appRoutesList = [] as any[];
const mainDashboardsApp = {} as any;
///////////////////////////////////////////////////////////////////////////////////
// iterate throught the list to find the 'main dashboard so we can put it first' //
///////////////////////////////////////////////////////////////////////////////////
for (let i = 0; i < metaData.appTree.length; i++) for (let i = 0; i < metaData.appTree.length; i++)
{ {
const app = metaData.appTree[i]; const app = metaData.appTree[i];
if(app.name === "mainDashboards")
{
addAppToSideNavList(app, sideNavAppList, "", 0);
addAppToAppRoutesList(metaData, app, appRoutesList, "", 0);
sideNavAppList.push({type: "divider", key: "divider-1"});
break;
}
}
for (let i = 0; i < metaData.appTree.length; i++)
{
const app = metaData.appTree[i];
if(app.name === "mainDashboards")
{
continue;
}
addAppToSideNavList(app, sideNavAppList, "", 0); addAppToSideNavList(app, sideNavAppList, "", 0);
addAppToAppRoutesList(metaData, app, appRoutesList, "", 0); addAppToAppRoutesList(metaData, app, appRoutesList, "", 0);
} }
const newSideNavRoutes = getStaticRoutes(); const newSideNavRoutes = []; // getStaticRoutes();
// @ts-ignore // @ts-ignore
newSideNavRoutes.unshift(profileRoutes); newSideNavRoutes.unshift(profileRoutes);
for (let i = 0; i < sideNavAppList.length; i++) for (let i = 0; i < sideNavAppList.length; i++)

View File

@ -37,11 +37,15 @@ import FieldValueListWidget from "qqq/pages/dashboards/Widgets/FieldValueListWid
import HorizontalBarChart from "qqq/pages/dashboards/Widgets/HorizontalBarChart"; import HorizontalBarChart from "qqq/pages/dashboards/Widgets/HorizontalBarChart";
import MultiStatisticsCard from "qqq/pages/dashboards/Widgets/MultiStatisticsCard"; import MultiStatisticsCard from "qqq/pages/dashboards/Widgets/MultiStatisticsCard";
import ParentWidget from "qqq/pages/dashboards/Widgets/ParentWidget"; import ParentWidget from "qqq/pages/dashboards/Widgets/ParentWidget";
import PieChartCard from "qqq/pages/dashboards/Widgets/PieChartCard";
import QuickSightChart from "qqq/pages/dashboards/Widgets/QuickSightChart"; import QuickSightChart from "qqq/pages/dashboards/Widgets/QuickSightChart";
import RecordGridWidget from "qqq/pages/dashboards/Widgets/RecordGridWidget"; import RecordGridWidget from "qqq/pages/dashboards/Widgets/RecordGridWidget";
import SimpleStatisticsCard from "qqq/pages/dashboards/Widgets/SimpleStatisticsCard"; import SimpleStatisticsCard from "qqq/pages/dashboards/Widgets/SimpleStatisticsCard";
import SmallLineChart from "qqq/pages/dashboards/Widgets/SmallLineChart";
import StatisticsCard from "qqq/pages/dashboards/Widgets/StatisticsCard";
import StepperCard from "qqq/pages/dashboards/Widgets/StepperCard"; import StepperCard from "qqq/pages/dashboards/Widgets/StepperCard";
import TableCard from "qqq/pages/dashboards/Widgets/TableCard"; import TableCard from "qqq/pages/dashboards/Widgets/TableCard";
import USMapWidget from "qqq/pages/dashboards/Widgets/USMapWidget";
import Widget from "qqq/pages/dashboards/Widgets/Widget"; import Widget from "qqq/pages/dashboards/Widgets/Widget";
import ProcessRun from "qqq/pages/process-run"; import ProcessRun from "qqq/pages/process-run";
import QClient from "qqq/utils/QClient"; import QClient from "qqq/utils/QClient";
@ -98,9 +102,9 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, omit
widgetData[i] = {}; widgetData[i] = {};
(async () => (async () =>
{ {
console.log(`widgets: ${getQueryParams(null)}`)
widgetData[i] = await qController.widget(widgetMetaDataList[i].name, getQueryParams(null)); widgetData[i] = await qController.widget(widgetMetaDataList[i].name, getQueryParams(null));
setWidgetCounter(widgetCounter + 1); setWidgetCounter(widgetCounter + 1);
console.log(`widget data: ${JSON.stringify(widgetData[i])}`)
forceUpdate(); forceUpdate();
})(); })();
} }
@ -147,7 +151,7 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, omit
} }
return params; return params;
}; }
const widgetCount = widgetMetaDataList ? widgetMetaDataList.length : 0; const widgetCount = widgetMetaDataList ? widgetMetaDataList.length : 0;
@ -158,6 +162,7 @@ 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}
@ -168,20 +173,38 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, omit
) )
} }
{ {
widgetMetaData.type === "table" && ( widgetMetaData.type === "usaMap" && (
<TableCard <USMapWidget
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={reloadWidget}
widgetIndex={i} widgetIndex={i}
label={widgetMetaData.label}
data={widgetData[i]}
reloadWidgetCallback={reloadWidget}
/> />
) )
} }
{
widgetMetaData.type === "table" && (
<Widget
label={widgetData[i]?.label}
isCard={widgetMetaData.isCard}
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 === "process" && widgetData[i]?.processMetaData && ( widgetMetaData.type === "process" && widgetData[i]?.processMetaData && (
<Widget <Widget
@ -235,10 +258,21 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, omit
) )
} }
{ {
widgetMetaData.type === "statistics" && ( widgetMetaData.type === "smallLineChart" && (
<SmallLineChart
color="dark"
title={widgetMetaData.label}
description={widgetData[i]?.description}
date=""
chart={widgetData[i]?.chartData}
/>
)
}
{
widgetMetaData.type === "simpleStatistics" && (
widgetData && widgetData[i] && ( widgetData && widgetData[i] && (
<SimpleStatisticsCard <SimpleStatisticsCard
title={widgetMetaData.label} title={widgetData[i]?.title}
data={widgetData[i]} data={widgetData[i]}
increaseIsGood={widgetData[i].increaseIsGood} increaseIsGood={widgetData[i].increaseIsGood}
isCurrency={widgetData[i].isCurrency} isCurrency={widgetData[i].isCurrency}
@ -246,6 +280,17 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, omit
) )
) )
} }
{
widgetMetaData.type === "statistics" && (
widgetData && widgetData[i] && (
<StatisticsCard
icon={widgetMetaData.icon}
data={widgetData[i]}
increaseIsGood={true}
/>
)
)
}
{ {
widgetMetaData.type === "quickSightChart" && ( widgetMetaData.type === "quickSightChart" && (
<QuickSightChart url={widgetData[i]?.url} label={widgetMetaData.label} /> <QuickSightChart url={widgetData[i]?.url} label={widgetMetaData.label} />
@ -255,8 +300,18 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, omit
widgetMetaData.type === "barChart" && ( widgetMetaData.type === "barChart" && (
<BarChart <BarChart
color="info" color="info"
title={widgetMetaData.label} title={widgetData[i]?.title}
date={`As of ${new Date().toDateString()}`} date={`As of ${new Date().toDateString()}`}
description={widgetData[i]?.description}
data={widgetData[i]?.chartData}
/>
)
}
{
widgetMetaData.type === "pieChart" && (
<PieChartCard
title={widgetData[i]?.label}
description={widgetData[i]?.description}
data={widgetData[i]?.chartData} data={widgetData[i]?.chartData}
/> />
) )
@ -270,14 +325,12 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, omit
} }
{ {
widgetMetaData.type === "horizontalBarChart" && ( widgetMetaData.type === "horizontalBarChart" && (
widgetData && widgetData[i] && widgetData[i].chartData && ( <HorizontalBarChart
<HorizontalBarChart height={widgetData[i]?.height}
height={widgetData[i].height} title={widgetMetaData.label}
title={widgetMetaData.label} data={widgetData[i]?.chartData}
data={widgetData[i]?.chartData} isCurrency={widgetData[i]?.isCurrency}
isCurrency={widgetData[i].isCurrency} />
/>
)
) )
} }
{ {

View File

@ -38,12 +38,7 @@ import MDInput from "qqq/components/Temporary/MDInput";
import MDPagination from "qqq/components/Temporary/MDPagination"; import MDPagination from "qqq/components/Temporary/MDPagination";
import MDTypography from "qqq/components/Temporary/MDTypography"; import MDTypography from "qqq/components/Temporary/MDTypography";
import ImageCell from "qqq/pages/dashboards/Tables/ImageCell"; import ImageCell from "qqq/pages/dashboards/Tables/ImageCell";
import {TableDataInput} from "qqq/pages/dashboards/Widgets/TableCard";
export interface TableDataInput
{
columns: { [key: string]: any }[];
rows: { [key: string]: any }[];
}
interface Props interface Props
{ {

View File

@ -29,7 +29,6 @@ import QContext from "QContext";
import DashboardLayout from "qqq/components/DashboardLayout"; import DashboardLayout from "qqq/components/DashboardLayout";
import Footer from "qqq/components/Footer"; import Footer from "qqq/components/Footer";
import Navbar from "qqq/components/Navbar"; import Navbar from "qqq/components/Navbar";
import {TableDataInput} from "qqq/components/Temporary/DataTable";
import MDBox from "qqq/components/Temporary/MDBox"; import MDBox from "qqq/components/Temporary/MDBox";
import ShipmentsByWarehouseTable from "qqq/pages/dashboards/Tables/ShipmentsByWarehouseTable"; import ShipmentsByWarehouseTable from "qqq/pages/dashboards/Tables/ShipmentsByWarehouseTable";
import {GenericChartData} from "qqq/pages/dashboards/Widgets/Data/GenericChartData"; import {GenericChartData} from "qqq/pages/dashboards/Widgets/Data/GenericChartData";
@ -40,7 +39,7 @@ import {PieChartData} from "qqq/pages/dashboards/Widgets/PieChart";
import PieChartCard from "qqq/pages/dashboards/Widgets/PieChartCard"; import PieChartCard from "qqq/pages/dashboards/Widgets/PieChartCard";
import SimpleStatisticsCard from "qqq/pages/dashboards/Widgets/SimpleStatisticsCard"; import SimpleStatisticsCard from "qqq/pages/dashboards/Widgets/SimpleStatisticsCard";
import {StatisticsCardData} from "qqq/pages/dashboards/Widgets/StatisticsCard"; import {StatisticsCardData} from "qqq/pages/dashboards/Widgets/StatisticsCard";
import TableCard from "qqq/pages/dashboards/Widgets/TableCard"; import TableCard, {TableDataInput} from "qqq/pages/dashboards/Widgets/TableCard";
import QClient from "qqq/utils/QClient"; import QClient from "qqq/utils/QClient";
const qController = QClient.getInstance(); const qController = QClient.getInstance();

View File

@ -47,7 +47,6 @@ function Overview(): JSX.Element
////////////////////////////////// //////////////////////////////////
// shipments by day widget data // // shipments by day widget data //
////////////////////////////////// //////////////////////////////////
const [shipmentsByDayTitle, setShipmentsByDayTitle] = useState("");
const [shipmentsByDayDescription, setShipmentsByDayDescription] = useState(""); const [shipmentsByDayDescription, setShipmentsByDayDescription] = useState("");
const [shipmentsByDayData, setShipmentsByDayData] = useState({} as GenericChartDataSingleDataset); const [shipmentsByDayData, setShipmentsByDayData] = useState({} as GenericChartDataSingleDataset);
@ -135,7 +134,6 @@ function Overview(): JSX.Element
} }
const description = "Over the last week there have been <strong>" + daysOverAverage.toLocaleString() + (daysOverAverage == 1 ? " day" : " days") + "</strong> with total shipments greater than the daily average of <strong>" + average.toLocaleString() + " shipments</strong>."; const description = "Over the last week there have been <strong>" + daysOverAverage.toLocaleString() + (daysOverAverage == 1 ? " day" : " days") + "</strong> with total shipments greater than the daily average of <strong>" + average.toLocaleString() + " shipments</strong>.";
setShipmentsByDayTitle(widgetData.title);
setShipmentsByDayData(widgetData.chartData); setShipmentsByDayData(widgetData.chartData);
setShipmentsByDayDescription(description); setShipmentsByDayDescription(description);
})(); })();

View File

@ -19,6 +19,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import Box from "@mui/material/Box";
import Card from "@mui/material/Card"; 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 Icon from "@mui/material/Icon";
@ -142,44 +143,47 @@ function BarChart({color, title, description, date, data}: Props): JSX.Element
const {chartData} = getChartData(data?.labels, data?.dataset); const {chartData} = getChartData(data?.labels, data?.dataset);
return ( return (
<Card sx={{height: "100%"}}>
<MDBox padding="1rem"> <Box mt={3} sx={{flexGrow: 1, display: "flex"}}>
{useMemo( <Card sx={{flexGrow: 1, display: "flex", height: "100%"}}>
() => ( <MDBox padding="1rem">
<MDBox {useMemo(
variant="gradient" () => (
bgColor={color} <MDBox
borderRadius="lg" variant="gradient"
coloredShadow={color} bgColor={color}
py={2} borderRadius="lg"
pr={0.5} coloredShadow={color}
mt={-5} py={2}
height="12.5rem" pr={0.5}
> mt={-5}
<Bar data={chartData} options={options} /> height="12.5rem"
>
<Bar data={chartData} options={options} />
</MDBox>
),
[data, color]
)}
<MDBox pt={3} pb={1} px={1}>
<MDTypography variant="h5" textTransform="capitalize">
{title}
</MDTypography>
<MDTypography component="div" variant="button" color="text" fontWeight="light">
{parse(description)}
</MDTypography>
<Divider />
<MDBox display="flex" alignItems="center">
<MDTypography variant="button" color="text" lineHeight={1} sx={{mt: 0.15, mr: 0.5}}>
<Icon>schedule</Icon>
</MDTypography>
<MDTypography variant="button" color="text" fontWeight="light">
{date}
</MDTypography>
</MDBox> </MDBox>
),
[data, color]
)}
<MDBox pt={3} pb={1} px={1}>
<MDTypography variant="h5" textTransform="capitalize">
{title}
</MDTypography>
<MDTypography component="div" variant="button" color="text" fontWeight="light">
{parse(description)}
</MDTypography>
<Divider />
<MDBox display="flex" alignItems="center">
<MDTypography variant="button" color="text" lineHeight={1} sx={{mt: 0.15, mr: 0.5}}>
<Icon>schedule</Icon>
</MDTypography>
<MDTypography variant="button" color="text" fontWeight="light">
{date}
</MDTypography>
</MDBox> </MDBox>
</MDBox> </MDBox>
</MDBox> </Card>
</Card> </Box>
); );
} }

View File

@ -57,7 +57,7 @@ const options = {
callbacks: { callbacks: {
label: function(context:any) label: function(context:any)
{ {
return(context.parsed.x); return(" " + Number(context.parsed.y).toLocaleString());
} }
} }
} }
@ -90,7 +90,7 @@ const options = {
}, },
callback: function(value: any, index: any, values: any) callback: function(value: any, index: any, values: any)
{ {
return value; return value.toLocaleString();
} }
}, },
}, },

View File

@ -19,6 +19,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import Box from "@mui/material/Box";
import Card from "@mui/material/Card"; import Card from "@mui/material/Card";
import Icon from "@mui/material/Icon"; import Icon from "@mui/material/Icon";
import {ReactNode, useMemo} from "react"; import {ReactNode, useMemo} from "react";
@ -48,7 +49,7 @@ const options = {
return(context.parsed.x); return(context.parsed.x);
} }
} }
} },
}, },
scales: { scales: {
y: { y: {
@ -122,27 +123,30 @@ interface Props
function HorizontalBarChart({icon, title, description, height, data, isCurrency}: Props): JSX.Element function HorizontalBarChart({icon, title, description, height, data, isCurrency}: Props): JSX.Element
{ {
const chartDatasets = data.datasets
? data.datasets.map((dataset) => ({
...dataset,
weight: 5,
borderWidth: 0,
borderRadius: 4,
backgroundColor: dataset?.color
? dataset.color
: colors.info.main,
fill: false,
maxBarThickness: 15,
}))
: [];
let fullData = {}; let fullData = {};
if (data) if(data && data.datasets)
{ {
fullData = { const chartDatasets = data.datasets
labels: data.labels, ? data.datasets.map((dataset) => ({
datasets: chartDatasets ...dataset,
}; weight: 5,
borderWidth: 0,
borderRadius: 4,
backgroundColor: dataset?.color
? dataset.color
: colors.info.main,
fill: false,
maxBarThickness: 15,
}))
: [];
if (data)
{
fullData = {
labels: data.labels,
datasets: chartDatasets
};
}
} }
let customOptions = options; let customOptions = options;
@ -166,10 +170,9 @@ function HorizontalBarChart({icon, title, description, height, data, isCurrency}
} }
} }
const renderChart = ( const renderChart = (
<MDBox py={2} pr={2} pl={icon.component ? 1 : 2} sx={{alignItems: "stretch", flexGrow: 1, display: "flex", marginTop: "0px", paddingTop: "0px"}}> <MDBox py={2} pr={2} pl={icon.component ? 1 : 2} sx={{alignItems: "stretch", flexGrow: 1, display: "flex", marginTop: "0px", paddingTop: "0px"}}>
{title || description ? ( {title || description && (
<MDBox display="flex" px={description ? 1 : 0} pt={description ? 1 : 0} sx={{alignItems: "stretch", flexGrow: 1, display: "flex", marginTop: "0px", paddingTop: "0px"}}> <MDBox display="flex" px={description ? 1 : 0} pt={description ? 1 : 0} sx={{alignItems: "stretch", flexGrow: 1, display: "flex", marginTop: "0px", paddingTop: "0px"}}>
{icon.component && ( {icon.component && (
<MDBox <MDBox
@ -198,11 +201,17 @@ function HorizontalBarChart({icon, title, description, height, data, isCurrency}
</MDBox> </MDBox>
</MDBox> </MDBox>
</MDBox> </MDBox>
) : null} )}
{useMemo( {useMemo(
() => ( () => (
<MDBox height={height} sx={{alignItems: "stretch", flexGrow: 1, display: "flex", marginTop: "0px", paddingTop: "0px"}}> <MDBox height={height} sx={{alignItems: "stretch", flexGrow: 1, display: "flex", marginTop: "0px", paddingTop: "0px"}}>
<Bar data={fullData} options={options} /> {
data && data?.datasets && data?.datasets.length > 0 ?(
<Bar data={fullData} options={options} />
):(
<Box mt={2} sx={{width: "100%", textAlign: "center"}}><i>No data was provided to this chart</i></Box>
)
}
</MDBox> </MDBox>
), ),
[data, height] [data, height]

View File

@ -21,13 +21,10 @@
import {QInstance} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QInstance"; import {QInstance} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QInstance";
import {QWidgetMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QWidgetMetaData"; import {QWidgetMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QWidgetMetaData";
import {Box, Typography} from "@mui/material"; import {Box} from "@mui/material";
import Card from "@mui/material/Card";
import Grid from "@mui/material/Grid";
import React, {useEffect, useState} from "react"; import React, {useEffect, useState} from "react";
import DashboardWidgets from "qqq/components/DashboardWidgets"; import DashboardWidgets from "qqq/components/DashboardWidgets";
import DropdownMenu from "qqq/pages/dashboards/Widgets/Components/DropdownMenu"; import Widget from "qqq/pages/dashboards/Widgets/Widget";
import Widget, {Dropdown, LabelComponent} from "qqq/pages/dashboards/Widgets/Widget";
import QClient from "qqq/utils/QClient"; import QClient from "qqq/utils/QClient";
@ -44,6 +41,7 @@ export interface ParentWidgetData
}[][]; }[][];
childWidgetNameList: string[]; childWidgetNameList: string[];
dropdownNeedsSelectedText?: string; dropdownNeedsSelectedText?: string;
icon?: string;
} }
@ -54,6 +52,7 @@ interface Props
{ {
widgetIndex: number; widgetIndex: number;
label: string; label: string;
icon?: string;
data: ParentWidgetData; data: ParentWidgetData;
reloadWidgetCallback?: (widgetIndex: number, params: string) => void; reloadWidgetCallback?: (widgetIndex: number, params: string) => void;
entityPrimaryKey?: string; entityPrimaryKey?: string;
@ -62,7 +61,7 @@ interface Props
const qController = QClient.getInstance(); const qController = QClient.getInstance();
function ParentWidget({widgetIndex, label, data, reloadWidgetCallback, entityPrimaryKey, tableName}: Props, ): JSX.Element function ParentWidget({widgetIndex, label, icon, data, reloadWidgetCallback, entityPrimaryKey, tableName}: 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);
@ -93,16 +92,19 @@ function ParentWidget({widgetIndex, label, data, reloadWidgetCallback, entityPri
const parentReloadWidgetCallback = (data: string) => const parentReloadWidgetCallback = (data: string) =>
{ {
setChildUrlParams(data); setChildUrlParams(data);
reloadWidgetCallback(widgetIndex, data); reloadWidgetCallback(widgetIndex, data);
} }
// @ts-ignore
return ( return (
<Widget <Widget
icon={icon}
label={label} label={label}
widgetData={data} widgetData={data}
reloadWidgetCallback={parentReloadWidgetCallback} reloadWidgetCallback={parentReloadWidgetCallback}
> >
<Box px={3}> <Box px={3} sx={{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

@ -48,8 +48,8 @@ function PieChartCard({title, description, data}: Props): JSX.Element
} }
return ( return (
<Card sx={{height: "100%"}}> <Card sx={{height: "100%", width: "100%", display: "flex"}}>
<MDBox display="flex" justifyContent="space-between" alignItems="center" pt={2} px={2}> <MDBox display="flex" pt={2} px={2}>
<MDTypography variant="h5">{title}</MDTypography> <MDTypography variant="h5">{title}</MDTypography>
</MDBox> </MDBox>
<MDBox mt={3}> <MDBox mt={3}>
@ -72,15 +72,19 @@ function PieChartCard({title, description, data}: Props): JSX.Element
</Grid> </Grid>
</Grid> </Grid>
<Divider /> <Divider />
<Grid container> {
<Grid item xs={12}> description && (
<MDBox pb={2} px={2} display="flex" flexDirection={{xs: "column", sm: "row"}} mt="auto"> <Grid container>
<MDTypography variant="button" color="text" fontWeight="light"> <Grid item xs={12}>
{parse(description)} <MDBox pb={2} px={2} display="flex" flexDirection={{xs: "column", sm: "row"}} mt="auto">
</MDTypography> <MDTypography variant="button" color="text" fontWeight="light">
</MDBox> {parse(description)}
</Grid> </MDTypography>
</Grid> </MDBox>
</Grid>
</Grid>
)
}
</MDBox> </MDBox>
</Card> </Card>
); );

View File

@ -82,7 +82,7 @@ function SimpleStatisticsCard({title, data, increaseIsGood, isCurrency, dropdown
</MDBox> </MDBox>
<MDBox lineHeight={1}> <MDBox lineHeight={1}>
{ {
count ? ( count !== undefined ? (
isCurrency ? ( isCurrency ? (
<MDTypography variant="h5" fontWeight="bold"> <MDTypography variant="h5" fontWeight="bold">
{count.toLocaleString("en-US", {style: "currency", currency: "USD"})} {count.toLocaleString("en-US", {style: "currency", currency: "USD"})}
@ -90,21 +90,29 @@ function SimpleStatisticsCard({title, data, increaseIsGood, isCurrency, dropdown
) : ( ) : (
<MDTypography variant="h5" fontWeight="bold"> <MDTypography variant="h5" fontWeight="bold">
{count.toLocaleString("en-US", {style: "currency", currency: "USD"})} {count.toLocaleString()}
</MDTypography> </MDTypography>
) )
) : null ) : null
} }
<MDTypography variant="button" fontWeight="bold" color={percentColor}> {
{percentageString}&nbsp; count !== undefined ? (
<MDTypography <MDTypography variant="button" fontWeight="bold" color={percentColor}>
variant="button" {percentageString}&nbsp;
fontWeight="regular" <MDTypography
color={"secondary"} variant="button"
> fontWeight="regular"
{percentageLabel} color={"secondary"}
</MDTypography> >
</MDTypography> {percentageLabel}
</MDTypography>
</MDTypography>
):(
<MDTypography variant="button" fontWeight="regular">
<i>Loading.</i>
</MDTypography>
)
}
</MDBox> </MDBox>
</Grid> </Grid>
{dropdown && ( {dropdown && (

View File

@ -19,9 +19,8 @@
* 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 Box from "@mui/material/Box";
import Card from "@mui/material/Card"; import Card from "@mui/material/Card";
import Divider from "@mui/material/Divider";
import Icon from "@mui/material/Icon";
import parse from "html-react-parser"; import parse from "html-react-parser";
import {useMemo} from "react"; import {useMemo} from "react";
import {Line} from "react-chartjs-2"; import {Line} from "react-chartjs-2";
@ -57,47 +56,40 @@ function SmallLineChart({color, title, description, date, chart}: Props): JSX.El
{ {
const {data, options} = configs(chart?.labels || [], chart?.dataset || {}); const {data, options} = configs(chart?.labels || [], chart?.dataset || {});
console.log(`DATA: ${JSON.stringify(data)}`);
return ( return (
<Card sx={{height: "100%"}}>
<MDBox padding="1rem"> <Box mt={3} sx={{flexGrow: 1, display: "flex"}}>
{useMemo( <Card sx={{height: "100%"}}>
() => ( <MDBox padding="1rem">
<MDBox
variant="gradient" {useMemo(
bgColor={color} () => (
borderRadius="lg" <MDBox
coloredShadow={color} variant="gradient"
py={2} bgColor={color}
pr={0.5} borderRadius="lg"
mt={-5} coloredShadow={color}
height="12.5rem" py={2}
> pr={0.5}
<Line data={data} options={options} /> mt={-5}
</MDBox> height="12.5rem"
), >
[chart, color] <Line data={data} options={options} />
)} </MDBox>
<MDBox pt={3} pb={1} px={1}> ),
<MDTypography variant="h5" textTransform="capitalize"> [chart, color]
{title} )}
</MDTypography> <MDBox pt={3} pb={1} px={1}>
<MDTypography component="div" variant="button" color="text" fontWeight="light"> <MDTypography variant="h5" textTransform="capitalize">
{parse(description)} {title}
</MDTypography>
<Divider />
<MDBox display="flex" alignItems="center">
<MDTypography variant="button" color="text" lineHeight={1} sx={{mt: 0.15, mr: 0.5}}>
<Icon>schedule</Icon>
</MDTypography> </MDTypography>
<MDTypography variant="button" color="text" fontWeight="light"> <MDTypography component="div" variant="button" color="text" fontWeight="light">
{date} {parse(description)}
</MDTypography> </MDTypography>
</MDBox> </MDBox>
</MDBox> </MDBox>
</MDBox> </Card>
</Card> </Box>
); );
} }

View File

@ -88,7 +88,8 @@ function StatisticsCard({data, color, icon, increaseIsGood}: Props): JSX.Element
} }
return ( return (
<Card>
<Card sx={{height: "100%", alignItems: "stretch", flexGrow: 1, display: "flex", marginTop: 3, paddingTop: "0px"}}>
<MDBox display="flex" justifyContent="space-between" pt={1} px={2}> <MDBox display="flex" justifyContent="space-between" pt={1} px={2}>
<MDBox <MDBox
variant="gradient" variant="gradient"
@ -118,10 +119,10 @@ function StatisticsCard({data, color, icon, increaseIsGood}: Props): JSX.Element
} }
</MDBox> </MDBox>
</MDBox> </MDBox>
<Divider />
{ {
percentageAmount !== undefined && percentageAmount !== 0 ? ( percentageAmount !== undefined && percentageAmount !== 0 ? (
<MDBox pb={2} px={2}> <MDBox px={2}>
<Divider />
<MDTypography component="p" variant="button" color="text" display="flex"> <MDTypography component="p" variant="button" color="text" display="flex">
<MDTypography <MDTypography
component="span" component="span"

View File

@ -19,24 +19,31 @@
* 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 {Icon, Skeleton} from "@mui/material"; import {QInstance} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QInstance";
import Card from "@mui/material/Card"; import {Skeleton} from "@mui/material";
import Grid from "@mui/material/Grid";
import Menu from "@mui/material/Menu";
import MenuItem from "@mui/material/MenuItem";
import Table from "@mui/material/Table"; import Table from "@mui/material/Table";
import TableBody from "@mui/material/TableBody"; import TableBody from "@mui/material/TableBody";
import TableContainer from "@mui/material/TableContainer"; import TableContainer from "@mui/material/TableContainer";
import TableRow from "@mui/material/TableRow"; import TableRow from "@mui/material/TableRow";
import parse from "html-react-parser"; import parse from "html-react-parser";
import React, {useEffect, useState} from "react"; import React, {useEffect, useState} from "react";
import {NavLink} from "react-router-dom"; import DataTable from "qqq/components/Temporary/DataTable";
import DataTable, {TableDataInput} from "qqq/components/Temporary/DataTable";
import DataTableBodyCell from "qqq/components/Temporary/DataTable/DataTableBodyCell"; import DataTableBodyCell from "qqq/components/Temporary/DataTable/DataTableBodyCell";
import DataTableHeadCell from "qqq/components/Temporary/DataTable/DataTableHeadCell"; import DataTableHeadCell from "qqq/components/Temporary/DataTable/DataTableHeadCell";
import DefaultCell from "qqq/components/Temporary/DefaultCell"; import DefaultCell from "qqq/components/Temporary/DefaultCell";
import MDBox from "qqq/components/Temporary/MDBox"; import MDBox from "qqq/components/Temporary/MDBox";
import MDTypography from "qqq/components/Temporary/MDTypography"; import MDTypography from "qqq/components/Temporary/MDTypography";
import QClient from "qqq/utils/QClient";
//////////////////////////////////////
// structure of expected table data //
//////////////////////////////////////
export interface TableDataInput
{
columns: { [key: string]: any }[];
rows: { [key: string]: any }[];
}
///////////////////////// /////////////////////////
@ -49,130 +56,39 @@ interface Props
linkURL?: string; linkURL?: string;
noRowsFoundHTML?: string; noRowsFoundHTML?: string;
data: TableDataInput; data: TableDataInput;
dropdownOptions?: { reloadWidgetCallback?: (params: string) => void;
id: string,
name: string
}[];
reloadWidgetCallback?: (widgetIndex: number, params: string) => void;
widgetIndex?: number; widgetIndex?: number;
isChild?: boolean; isChild?: boolean;
[key: string]: any; [key: string]: any;
} }
function TableCard({title, linkText, linkURL, noRowsFoundHTML, data, dropdownOptions, reloadWidgetCallback, widgetIndex, isChild}: Props): JSX.Element const qController = QClient.getInstance();
function TableCard({noRowsFoundHTML, data, reloadWidgetCallback}: Props): JSX.Element
{ {
const openArrowIcon = "arrow_drop_down"; const [qInstance, setQInstance] = useState(null as QInstance);
const closeArrowIcon = "arrow_drop_up";
const [dropdown, setDropdown] = useState<string | null>(null);
const [dropdownValue, setDropdownValue] = useState("");
const [dropdownLabel, setDropdownLabel] = useState<string>("");
const [dropdownIcon, setDropdownIcon] = useState<string>(openArrowIcon);
// console.log(`data: ${JSON.stringify(data?.rows)}`);
// console.log(`bool: ${data && data?.columns && !data?.rows}`);
// console.log(`norowsfound: ${noRowsFoundHTML}`);
const openDropdown = ({currentTarget}: any) =>
{
setDropdown(currentTarget);
setDropdownIcon(closeArrowIcon);
};
const closeDropdown = ({currentTarget}: any) =>
{
setDropdown(null);
setDropdownValue(currentTarget.innerText || dropdownValue);
setDropdownIcon(openArrowIcon);
reloadWidgetCallback(widgetIndex, null);
};
const renderMenu = (state: any, open: any, close: any, icon: string) => (
dropdownOptions && (
<span style={{whiteSpace: "nowrap"}}>
<Icon onClick={open} fontSize={"medium"} style={{cursor: "pointer", float: "right"}}>{icon}</Icon>
<Menu
anchorEl={state}
transformOrigin={{vertical: "top", horizontal: "center"}}
open={Boolean(state)}
onClose={close}
keepMounted
disableAutoFocusItem
>
{
dropdownOptions.map((optionMap, index: number) =>
<MenuItem id={optionMap["id"]} key={index} onClick={close}>{optionMap["name"]}</MenuItem>
)
}
</Menu>
</span>
)
);
useEffect(() => useEffect(() =>
{ {
if (dropdownOptions) (async () =>
{ {
setDropdownValue(dropdownOptions[0]["id"]); const newQInstance = await qController.loadMetaData();
setDropdownLabel(dropdownOptions[0]["name"]); setQInstance(newQInstance);
})();
} }, []);
}, [dropdownOptions]);
return ( return (
<Card sx={{alignItems: "stretch", flexGrow: 1, display: "flex", marginTop: "0px", paddingTop: "0px"}}> <MDBox py={1}>
<Grid container> {
<Grid item xs={6}> data && data.columns && !noRowsFoundHTML ?
<MDBox pt={3} px={3}> <DataTable
<MDTypography variant={isChild ? "h6" : "h5"} fontWeight="medium"> table={data}
{title} entriesPerPage={false}
</MDTypography> showTotalEntries={false}
</MDBox> isSorted={false}
</Grid> noEndBorder
<Grid item xs={6}> />
{ : noRowsFoundHTML ?
linkText && (
<MDBox sx={{textAlign: "right", fontSize: "14px"}} pt={3} px={3}>
<NavLink to={linkURL}>{linkText}</NavLink>
</MDBox>
)
}
</Grid>
<Grid item xs={5}>
{dropdownOptions && (
<MDBox p={2} width="100%" textAlign="right" lineHeight={1}>
<MDTypography
variant="caption"
color="secondary"
fontWeight="regular"
>
<strong>Billing Period: </strong>
</MDTypography>
<MDTypography
variant="caption"
color="secondary"
fontWeight="regular"
sx={{cursor: "pointer"}}
onClick={openDropdown}
>
{dropdownLabel}
</MDTypography>
{renderMenu(dropdown, openDropdown, closeDropdown, dropdownIcon)}
</MDBox>
)}
</Grid>
</Grid>
<MDBox py={1}>
{
data && data.columns && !noRowsFoundHTML ? (
<DataTable
table={data}
entriesPerPage={false}
showTotalEntries={false}
isSorted={false}
noEndBorder
/>
) : noRowsFoundHTML ? (
<MDBox p={3} pt={1} pb={1} sx={{textAlign: "center"}}> <MDBox p={3} pt={1} pb={1} sx={{textAlign: "center"}}>
<MDTypography <MDTypography
variant="subtitle2" variant="subtitle2"
@ -186,7 +102,7 @@ function TableCard({title, linkText, linkURL, noRowsFoundHTML, data, dropdownOpt
} }
</MDTypography> </MDTypography>
</MDBox> </MDBox>
) : ( :
<TableContainer sx={{boxShadow: "none"}}> <TableContainer sx={{boxShadow: "none"}}>
<Table> <Table>
<MDBox component="thead"> <MDBox component="thead">
@ -211,10 +127,8 @@ function TableCard({title, linkText, linkURL, noRowsFoundHTML, data, dropdownOpt
</TableBody> </TableBody>
</Table> </Table>
</TableContainer> </TableContainer>
) }
} </MDBox>
</MDBox>
</Card>
); );
} }

View File

@ -0,0 +1,146 @@
/*
* 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 {QInstance} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QInstance";
import {Box, Grid} from "@mui/material";
import {VectorMap} from "@react-jvectormap/core";
import {usAea} from "@react-jvectormap/unitedstates";
import React, {useEffect, useState} from "react";
import QClient from "qqq/utils/QClient";
////////////////////////////////////////////////
// structure of expected US and A widget data //
////////////////////////////////////////////////
export interface MapMarkerData
{
name: string;
latitude: number;
longitude: number;
}
export interface USMapWidgetData
{
height: string;
markers?: MapMarkerData[];
}
////////////////////////////////////
// define properties and defaults //
////////////////////////////////////
interface Props
{
widgetIndex: number;
label: string;
icon?: string;
reloadWidgetCallback?: (widgetIndex: number, params: string) => void;
data: USMapWidgetData;
}
const qController = QClient.getInstance();
function USMapWidget(props: Props, ): JSX.Element
{
const [qInstance, setQInstance] = useState(null as QInstance);
useEffect(() =>
{
(async () =>
{
const newQInstance = await qController.loadMetaData();
setQInstance(newQInstance);
})();
}, []);
console.log(JSON.stringify(props))
return (
<Grid container>
<Grid item xs={12} sx={{height: props.data?.height}}>
{
props.data?.height && (
<Box mt={3} sx={{height: "100%"}}>
<VectorMap
map={usAea}
zoomOnScroll={false}
zoomButtons={false}
markersSelectable
backgroundColor="transparent"
markers={[
{
name: "edison",
latLng: [40.5274, -74.3933],
},
{
name: "stockton",
latLng: [37.975556, -121.300833],
},
{
name: "patterson",
latLng: [37.473056, -121.132778],
},
]}
regionStyle={{
initial: {
fill: "#dee2e7",
"fill-opacity": 1,
stroke: "none",
"stroke-width": 0,
"stroke-opacity": 0,
},
}}
markerStyle={{
initial: {
fill: "#e91e63",
stroke: "#ffffff",
"stroke-width": 5,
"stroke-opacity": 0.5,
r: 7,
},
hover: {
fill: "E91E63",
stroke: "#ffffff",
"stroke-width": 5,
"stroke-opacity": 0.5,
},
selected: {
fill: "E91E63",
stroke: "#ffffff",
"stroke-width": 5,
"stroke-opacity": 0.5,
},
}}
style={{
marginTop: "-1.5rem",
}}
onRegionTipShow={() => false}
onMarkerTipShow={() => false}
/>
</Box>
)
}
</Grid>
</Grid>
);
}
export default USMapWidget;

View File

@ -23,19 +23,18 @@ import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QT
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";
import Modal from "@mui/material/Modal"; import Icon from "@mui/material/Icon";
import Typography from "@mui/material/Typography"; import Typography from "@mui/material/Typography";
import React, {useEffect, useState} from "react"; import React, {useEffect, useState} from "react";
import {Link, useNavigate} from "react-router-dom"; import {Link, useNavigate} from "react-router-dom";
import DashboardWidgets from "qqq/components/DashboardWidgets"; import colors from "qqq/components/Temporary/colors";
import EntityForm from "qqq/components/EntityForm";
import DropdownMenu, {DropdownOption} from "qqq/pages/dashboards/Widgets/Components/DropdownMenu"; import DropdownMenu, {DropdownOption} from "qqq/pages/dashboards/Widgets/Components/DropdownMenu";
export interface WidgetData export interface WidgetData
{ {
dropdownLabelList: string[]; dropdownLabelList?: string[];
dropdownNameList: string[]; dropdownNameList?: string[];
dropdownDataList: { dropdownDataList?: {
id: string, id: string,
label: string label: string
}[][]; }[][];
@ -45,15 +44,20 @@ export interface WidgetData
interface Props interface Props
{ {
icon?: string;
label: string; label: string;
labelAdditionalComponentsLeft: LabelComponent[]; labelAdditionalComponentsLeft: LabelComponent[];
labelAdditionalComponentsRight: LabelComponent[]; labelAdditionalComponentsRight: LabelComponent[];
widgetData?: WidgetData; widgetData?: WidgetData;
children: JSX.Element; children: JSX.Element;
reloadWidgetCallback?: (params: string) => void; reloadWidgetCallback?: (params: string) => void;
isChild?: boolean;
isCard?: boolean;
} }
Widget.defaultProps = { Widget.defaultProps = {
isCard: true,
isChild: false,
label: null, label: null,
widgetData: {}, widgetData: {},
labelAdditionalComponentsLeft: [], labelAdditionalComponentsLeft: [],
@ -246,44 +250,70 @@ function Widget(props: React.PropsWithChildren<Props>): JSX.Element
} }
}, [counter]); }, [counter]);
return ( const widgetContent =
<> <Box sx={{width: "100%"}}>
<Card sx={{width: "100%"}}> {
<Box display="flex" justifyContent="space-between" alignItems="center"> (props.icon || props.label) && (
<Box py={2}> <Box display="flex" justifyContent="space-between" alignItems="center" sx={{width: "100%"}}>
<Typography variant="h5" fontWeight="medium" p={3} display="inline"> <Box py={2}>
{props.label}
</Typography>
{
props.labelAdditionalComponentsLeft.map((component, i) =>
{ {
return (<span key={i}>{renderComponent(component)}</span>); props.icon && (
}) <Box
} ml={3}
</Box> mt={-4}
<Box pr={1}> sx={{
{ display: "flex",
effectiveLabelAdditionalComponentsRight.map((component, i) => justifyContent: "center",
{ alignItems: "center",
return (<span key={i}>{renderComponent(component)}</span>); width: "64px",
}) height: "64px",
} borderRadius: "8px",
</Box> background: colors.info.main,
</Box> color: "#ffffff",
{ float: "left"
props.widgetData?.dropdownNeedsSelectedText ? ( }}
<Box pb={3} pr={3} sx={{width: "100%", textAlign: "right"}}> >
<Typography variant="body2"> <Icon fontSize="medium" color="inherit">
{props.widgetData?.dropdownNeedsSelectedText} {props.icon}
</Icon>
</Box>
)
}
<Typography variant={props.isChild ? "h6" : "h5"} fontWeight="medium" p={3} display="inline">
{props.label}
</Typography> </Typography>
{
props.labelAdditionalComponentsLeft.map((component, i) =>
{
return (<span key={i}>{renderComponent(component)}</span>);
})
}
</Box> </Box>
) : ( <Box pr={1}>
props.children {
) effectiveLabelAdditionalComponentsRight.map((component, i) =>
} {
</Card> return (<span key={i}>{renderComponent(component)}</span>);
</> })
); }
</Box>
</Box>
)
}
{
props.widgetData?.dropdownNeedsSelectedText ? (
<Box pb={3} pr={3} sx={{width: "100%", textAlign: "right"}}>
<Typography variant="body2">
{props.widgetData?.dropdownNeedsSelectedText}
</Typography>
</Box>
) : (
props.children
)
}
</Box>;
return props.isCard ? <Card sx={{width: "100%"}}>{widgetContent}</Card> : widgetContent;
} }
export default Widget; export default Widget;