mirror of
https://github.com/Kingsrook/qqq-frontend-material-dashboard.git
synced 2025-07-17 12:50:43 +00:00
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:
@ -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
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
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
BIN
public/flags/DE.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 927 B |
BIN
public/flags/GB.png
Normal file
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
BIN
public/flags/US.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.1 KiB |
24
src/App.tsx
24
src/App.tsx
@ -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++)
|
||||
|
@ -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}
|
||||
@ -167,8 +172,25 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, omit
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
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}
|
||||
@ -177,9 +199,10 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, omit
|
||||
noRowsFoundHTML={widgetData[i]?.noRowsFoundHTML}
|
||||
data={widgetData[i]}
|
||||
dropdownOptions={widgetData[i]?.dropdownOptions}
|
||||
reloadWidgetCallback={reloadWidget}
|
||||
reloadWidgetCallback={(data) => reloadWidget(i, data)}
|
||||
widgetIndex={i}
|
||||
/>
|
||||
</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,15 +325,13 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, omit
|
||||
}
|
||||
{
|
||||
widgetMetaData.type === "horizontalBarChart" && (
|
||||
widgetData && widgetData[i] && widgetData[i].chartData && (
|
||||
<HorizontalBarChart
|
||||
height={widgetData[i].height}
|
||||
height={widgetData[i]?.height}
|
||||
title={widgetMetaData.label}
|
||||
data={widgetData[i]?.chartData}
|
||||
isCurrency={widgetData[i].isCurrency}
|
||||
isCurrency={widgetData[i]?.isCurrency}
|
||||
/>
|
||||
)
|
||||
)
|
||||
}
|
||||
{
|
||||
widgetMetaData.type === "lineChart" && (
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
})();
|
||||
|
@ -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,7 +143,9 @@ function BarChart({color, title, description, date, data}: Props): JSX.Element
|
||||
const {chartData} = getChartData(data?.labels, data?.dataset);
|
||||
|
||||
return (
|
||||
<Card sx={{height: "100%"}}>
|
||||
|
||||
<Box mt={3} sx={{flexGrow: 1, display: "flex"}}>
|
||||
<Card sx={{flexGrow: 1, display: "flex", height: "100%"}}>
|
||||
<MDBox padding="1rem">
|
||||
{useMemo(
|
||||
() => (
|
||||
@ -180,6 +183,7 @@ function BarChart({color, title, description, date, data}: Props): JSX.Element
|
||||
</MDBox>
|
||||
</MDBox>
|
||||
</Card>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
},
|
||||
},
|
||||
|
@ -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,6 +123,9 @@ interface Props
|
||||
|
||||
function HorizontalBarChart({icon, title, description, height, data, isCurrency}: Props): JSX.Element
|
||||
{
|
||||
let fullData = {};
|
||||
if(data && data.datasets)
|
||||
{
|
||||
const chartDatasets = data.datasets
|
||||
? data.datasets.map((dataset) => ({
|
||||
...dataset,
|
||||
@ -136,7 +140,6 @@ function HorizontalBarChart({icon, title, description, height, data, isCurrency}
|
||||
}))
|
||||
: [];
|
||||
|
||||
let fullData = {};
|
||||
if (data)
|
||||
{
|
||||
fullData = {
|
||||
@ -144,6 +147,7 @@ function HorizontalBarChart({icon, title, description, height, data, isCurrency}
|
||||
datasets: chartDatasets
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
let customOptions = options;
|
||||
if(isCurrency)
|
||||
@ -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"}}>
|
||||
{
|
||||
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]
|
||||
|
@ -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>
|
||||
|
@ -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,6 +72,8 @@ function PieChartCard({title, description, data}: Props): JSX.Element
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Divider />
|
||||
{
|
||||
description && (
|
||||
<Grid container>
|
||||
<Grid item xs={12}>
|
||||
<MDBox pb={2} px={2} display="flex" flexDirection={{xs: "column", sm: "row"}} mt="auto">
|
||||
@ -81,6 +83,8 @@ function PieChartCard({title, description, data}: Props): JSX.Element
|
||||
</MDBox>
|
||||
</Grid>
|
||||
</Grid>
|
||||
)
|
||||
}
|
||||
</MDBox>
|
||||
</Card>
|
||||
);
|
||||
|
@ -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,11 +90,13 @@ function SimpleStatisticsCard({title, data, increaseIsGood, isCurrency, dropdown
|
||||
) : (
|
||||
|
||||
<MDTypography variant="h5" fontWeight="bold">
|
||||
{count.toLocaleString("en-US", {style: "currency", currency: "USD"})}
|
||||
{count.toLocaleString()}
|
||||
</MDTypography>
|
||||
)
|
||||
) : null
|
||||
}
|
||||
{
|
||||
count !== undefined ? (
|
||||
<MDTypography variant="button" fontWeight="bold" color={percentColor}>
|
||||
{percentageString}
|
||||
<MDTypography
|
||||
@ -105,6 +107,12 @@ function SimpleStatisticsCard({title, data, increaseIsGood, isCurrency, dropdown
|
||||
{percentageLabel}
|
||||
</MDTypography>
|
||||
</MDTypography>
|
||||
):(
|
||||
<MDTypography variant="button" fontWeight="regular">
|
||||
<i>Loading.</i>
|
||||
</MDTypography>
|
||||
)
|
||||
}
|
||||
</MDBox>
|
||||
</Grid>
|
||||
{dropdown && (
|
||||
|
@ -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,11 +56,12 @@ 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 (
|
||||
|
||||
<Box mt={3} sx={{flexGrow: 1, display: "flex"}}>
|
||||
<Card sx={{height: "100%"}}>
|
||||
<MDBox padding="1rem">
|
||||
|
||||
{useMemo(
|
||||
() => (
|
||||
<MDBox
|
||||
@ -86,18 +86,10 @@ function SmallLineChart({color, title, description, date, chart}: Props): JSX.El
|
||||
<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>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -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"
|
||||
|
@ -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,122 +56,31 @@ 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 ? (
|
||||
data && data.columns && !noRowsFoundHTML ?
|
||||
<DataTable
|
||||
table={data}
|
||||
entriesPerPage={false}
|
||||
@ -172,7 +88,7 @@ function TableCard({title, linkText, linkURL, noRowsFoundHTML, data, dropdownOpt
|
||||
isSorted={false}
|
||||
noEndBorder
|
||||
/>
|
||||
) : noRowsFoundHTML ? (
|
||||
: 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>
|
||||
);
|
||||
}
|
||||
|
||||
|
146
src/qqq/pages/dashboards/Widgets/USMapWidget.tsx
Normal file
146
src/qqq/pages/dashboards/Widgets/USMapWidget.tsx
Normal 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;
|
@ -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,12 +250,36 @@ function Widget(props: React.PropsWithChildren<Props>): JSX.Element
|
||||
}
|
||||
}, [counter]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Card sx={{width: "100%"}}>
|
||||
<Box display="flex" justifyContent="space-between" alignItems="center">
|
||||
const widgetContent =
|
||||
<Box sx={{width: "100%"}}>
|
||||
{
|
||||
(props.icon || props.label) && (
|
||||
<Box display="flex" justifyContent="space-between" alignItems="center" sx={{width: "100%"}}>
|
||||
<Box py={2}>
|
||||
<Typography variant="h5" fontWeight="medium" p={3} display="inline">
|
||||
{
|
||||
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>
|
||||
{
|
||||
@ -270,6 +298,8 @@ function Widget(props: React.PropsWithChildren<Props>): JSX.Element
|
||||
}
|
||||
</Box>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
{
|
||||
props.widgetData?.dropdownNeedsSelectedText ? (
|
||||
<Box pb={3} pr={3} sx={{width: "100%", textAlign: "right"}}>
|
||||
@ -281,9 +311,9 @@ function Widget(props: React.PropsWithChildren<Props>): JSX.Element
|
||||
props.children
|
||||
)
|
||||
}
|
||||
</Card>
|
||||
</>
|
||||
);
|
||||
</Box>;
|
||||
|
||||
return props.isCard ? <Card sx={{width: "100%"}}>{widgetContent}</Card> : widgetContent;
|
||||
}
|
||||
|
||||
export default Widget;
|
||||
|
Reference in New Issue
Block a user