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/react": "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/material": "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 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++)
{
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);
addAppToAppRoutesList(metaData, app, appRoutesList, "", 0);
}
const newSideNavRoutes = getStaticRoutes();
const newSideNavRoutes = []; // getStaticRoutes();
// @ts-ignore
newSideNavRoutes.unshift(profileRoutes);
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 MultiStatisticsCard from "qqq/pages/dashboards/Widgets/MultiStatisticsCard";
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 RecordGridWidget from "qqq/pages/dashboards/Widgets/RecordGridWidget";
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 TableCard from "qqq/pages/dashboards/Widgets/TableCard";
import USMapWidget from "qqq/pages/dashboards/Widgets/USMapWidget";
import Widget from "qqq/pages/dashboards/Widgets/Widget";
import ProcessRun from "qqq/pages/process-run";
import QClient from "qqq/utils/QClient";
@ -98,9 +102,9 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, omit
widgetData[i] = {};
(async () =>
{
console.log(`widgets: ${getQueryParams(null)}`)
widgetData[i] = await qController.widget(widgetMetaDataList[i].name, getQueryParams(null));
setWidgetCounter(widgetCounter + 1);
console.log(`widget data: ${JSON.stringify(widgetData[i])}`)
forceUpdate();
})();
}
@ -147,7 +151,7 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, omit
}
return params;
};
}
const widgetCount = widgetMetaDataList ? widgetMetaDataList.length : 0;
@ -158,6 +162,7 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, omit
{
widgetMetaData.type === "parentWidget" && (
<ParentWidget
icon={widgetMetaData.icon}
entityPrimaryKey={entityPrimaryKey}
tableName={tableName}
widgetIndex={i}
@ -168,20 +173,38 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, omit
)
}
{
widgetMetaData.type === "table" && (
<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={reloadWidget}
widgetMetaData.type === "usaMap" && (
<USMapWidget
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 && (
<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] && (
<SimpleStatisticsCard
title={widgetMetaData.label}
title={widgetData[i]?.title}
data={widgetData[i]}
increaseIsGood={widgetData[i].increaseIsGood}
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" && (
<QuickSightChart url={widgetData[i]?.url} label={widgetMetaData.label} />
@ -255,8 +300,18 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, omit
widgetMetaData.type === "barChart" && (
<BarChart
color="info"
title={widgetMetaData.label}
title={widgetData[i]?.title}
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}
/>
)
@ -270,14 +325,12 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, omit
}
{
widgetMetaData.type === "horizontalBarChart" && (
widgetData && widgetData[i] && widgetData[i].chartData && (
<HorizontalBarChart
height={widgetData[i].height}
title={widgetMetaData.label}
data={widgetData[i]?.chartData}
isCurrency={widgetData[i].isCurrency}
/>
)
<HorizontalBarChart
height={widgetData[i]?.height}
title={widgetMetaData.label}
data={widgetData[i]?.chartData}
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 MDTypography from "qqq/components/Temporary/MDTypography";
import ImageCell from "qqq/pages/dashboards/Tables/ImageCell";
export interface TableDataInput
{
columns: { [key: string]: any }[];
rows: { [key: string]: any }[];
}
import {TableDataInput} from "qqq/pages/dashboards/Widgets/TableCard";
interface Props
{

View File

@ -29,7 +29,6 @@ import QContext from "QContext";
import DashboardLayout from "qqq/components/DashboardLayout";
import Footer from "qqq/components/Footer";
import Navbar from "qqq/components/Navbar";
import {TableDataInput} from "qqq/components/Temporary/DataTable";
import MDBox from "qqq/components/Temporary/MDBox";
import ShipmentsByWarehouseTable from "qqq/pages/dashboards/Tables/ShipmentsByWarehouseTable";
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 SimpleStatisticsCard from "qqq/pages/dashboards/Widgets/SimpleStatisticsCard";
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";
const qController = QClient.getInstance();

View File

@ -47,7 +47,6 @@ function Overview(): JSX.Element
//////////////////////////////////
// shipments by day widget data //
//////////////////////////////////
const [shipmentsByDayTitle, setShipmentsByDayTitle] = useState("");
const [shipmentsByDayDescription, setShipmentsByDayDescription] = useState("");
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>.";
setShipmentsByDayTitle(widgetData.title);
setShipmentsByDayData(widgetData.chartData);
setShipmentsByDayDescription(description);
})();

View File

@ -19,6 +19,7 @@
* 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 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);
return (
<Card sx={{height: "100%"}}>
<MDBox padding="1rem">
{useMemo(
() => (
<MDBox
variant="gradient"
bgColor={color}
borderRadius="lg"
coloredShadow={color}
py={2}
pr={0.5}
mt={-5}
height="12.5rem"
>
<Bar data={chartData} options={options} />
<Box mt={3} sx={{flexGrow: 1, display: "flex"}}>
<Card sx={{flexGrow: 1, display: "flex", height: "100%"}}>
<MDBox padding="1rem">
{useMemo(
() => (
<MDBox
variant="gradient"
bgColor={color}
borderRadius="lg"
coloredShadow={color}
py={2}
pr={0.5}
mt={-5}
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>
),
[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>
</Card>
</Card>
</Box>
);
}

View File

@ -57,7 +57,7 @@ const options = {
callbacks: {
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)
{
return value;
return value.toLocaleString();
}
},
},

View File

@ -19,6 +19,7 @@
* 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 Icon from "@mui/material/Icon";
import {ReactNode, useMemo} from "react";
@ -48,7 +49,7 @@ const options = {
return(context.parsed.x);
}
}
}
},
},
scales: {
y: {
@ -122,27 +123,30 @@ interface Props
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 = {};
if (data)
if(data && data.datasets)
{
fullData = {
labels: data.labels,
datasets: chartDatasets
};
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,
}))
: [];
if (data)
{
fullData = {
labels: data.labels,
datasets: chartDatasets
};
}
}
let customOptions = options;
@ -166,10 +170,9 @@ function HorizontalBarChart({icon, title, description, height, data, isCurrency}
}
}
const renderChart = (
<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"}}>
{icon.component && (
<MDBox
@ -198,11 +201,17 @@ function HorizontalBarChart({icon, title, description, height, data, isCurrency}
</MDBox>
</MDBox>
</MDBox>
) : null}
)}
{useMemo(
() => (
<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>
),
[data, height]

View File

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

View File

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

View File

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

View File

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

View File

@ -88,7 +88,8 @@ function StatisticsCard({data, color, icon, increaseIsGood}: Props): JSX.Element
}
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
variant="gradient"
@ -118,10 +119,10 @@ function StatisticsCard({data, color, icon, increaseIsGood}: Props): JSX.Element
}
</MDBox>
</MDBox>
<Divider />
{
percentageAmount !== undefined && percentageAmount !== 0 ? (
<MDBox pb={2} px={2}>
<MDBox px={2}>
<Divider />
<MDTypography component="p" variant="button" color="text" display="flex">
<MDTypography
component="span"

View File

@ -19,24 +19,31 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {Icon, Skeleton} from "@mui/material";
import Card from "@mui/material/Card";
import Grid from "@mui/material/Grid";
import Menu from "@mui/material/Menu";
import MenuItem from "@mui/material/MenuItem";
import {QInstance} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QInstance";
import {Skeleton} from "@mui/material";
import Table from "@mui/material/Table";
import TableBody from "@mui/material/TableBody";
import TableContainer from "@mui/material/TableContainer";
import TableRow from "@mui/material/TableRow";
import parse from "html-react-parser";
import React, {useEffect, useState} from "react";
import {NavLink} from "react-router-dom";
import DataTable, {TableDataInput} from "qqq/components/Temporary/DataTable";
import DataTable from "qqq/components/Temporary/DataTable";
import DataTableBodyCell from "qqq/components/Temporary/DataTable/DataTableBodyCell";
import DataTableHeadCell from "qqq/components/Temporary/DataTable/DataTableHeadCell";
import DefaultCell from "qqq/components/Temporary/DefaultCell";
import MDBox from "qqq/components/Temporary/MDBox";
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;
noRowsFoundHTML?: string;
data: TableDataInput;
dropdownOptions?: {
id: string,
name: string
}[];
reloadWidgetCallback?: (widgetIndex: number, params: string) => void;
reloadWidgetCallback?: (params: string) => void;
widgetIndex?: number;
isChild?: boolean;
[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 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>
)
);
const [qInstance, setQInstance] = useState(null as QInstance);
useEffect(() =>
{
if (dropdownOptions)
(async () =>
{
setDropdownValue(dropdownOptions[0]["id"]);
setDropdownLabel(dropdownOptions[0]["name"]);
}
}, [dropdownOptions]);
const newQInstance = await qController.loadMetaData();
setQInstance(newQInstance);
})();
}, []);
return (
<Card sx={{alignItems: "stretch", flexGrow: 1, display: "flex", marginTop: "0px", paddingTop: "0px"}}>
<Grid container>
<Grid item xs={6}>
<MDBox pt={3} px={3}>
<MDTypography variant={isChild ? "h6" : "h5"} fontWeight="medium">
{title}
</MDTypography>
</MDBox>
</Grid>
<Grid item xs={6}>
{
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 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"}}>
<MDTypography
variant="subtitle2"
@ -186,7 +102,7 @@ function TableCard({title, linkText, linkURL, noRowsFoundHTML, data, dropdownOpt
}
</MDTypography>
</MDBox>
) : (
:
<TableContainer sx={{boxShadow: "none"}}>
<Table>
<MDBox component="thead">
@ -211,10 +127,8 @@ function TableCard({title, linkText, linkURL, noRowsFoundHTML, data, dropdownOpt
</TableBody>
</Table>
</TableContainer>
)
}
</MDBox>
</Card>
}
</MDBox>
);
}

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