mirror of
https://github.com/Kingsrook/qqq-frontend-material-dashboard.git
synced 2025-07-18 13:20:43 +00:00
SPRINT-18: fixed to dashboards, removed and moved around all the things
This commit is contained in:
412
src/qqq/components/widgets/DashboardWidgets.tsx
Normal file
412
src/qqq/components/widgets/DashboardWidgets.tsx
Normal file
@ -0,0 +1,412 @@
|
||||
/* 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 {QWidgetMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QWidgetMetaData";
|
||||
import {Skeleton} from "@mui/material";
|
||||
import Box from "@mui/material/Box";
|
||||
import Card from "@mui/material/Card";
|
||||
import Grid from "@mui/material/Grid";
|
||||
import parse from "html-react-parser";
|
||||
import React, {useEffect, useReducer, useState} from "react";
|
||||
import {useLocation} from "react-router-dom";
|
||||
import MDBadgeDot from "qqq/components/legacy/MDBadgeDot";
|
||||
import MDTypography from "qqq/components/legacy/MDTypography";
|
||||
import BarChart from "qqq/components/widgets/charts/barchart/BarChart";
|
||||
import HorizontalBarChart from "qqq/components/widgets/charts/barchart/HorizontalBarChart";
|
||||
import DefaultLineChart from "qqq/components/widgets/charts/linechart/DefaultLineChart";
|
||||
import SmallLineChart from "qqq/components/widgets/charts/linechart/SmallLineChart";
|
||||
import PieChartCard from "qqq/components/widgets/charts/piechart/PieChartCard";
|
||||
import DividerWidget from "qqq/components/widgets/misc/Divider";
|
||||
import FieldValueListWidget from "qqq/components/widgets/misc/FieldValueListWidget";
|
||||
import QuickSightChart from "qqq/components/widgets/misc/QuickSightChart";
|
||||
import RecordGridWidget from "qqq/components/widgets/misc/RecordGridWidget";
|
||||
import StepperCard from "qqq/components/widgets/misc/StepperCard";
|
||||
import USMapWidget from "qqq/components/widgets/misc/USMapWidget";
|
||||
import ParentWidget from "qqq/components/widgets/ParentWidget";
|
||||
import MultiStatisticsCard from "qqq/components/widgets/statistics/MultiStatisticsCard";
|
||||
import SimpleStatisticsCard from "qqq/components/widgets/statistics/SimpleStatisticsCard";
|
||||
import StatisticsCard from "qqq/components/widgets/statistics/StatisticsCard";
|
||||
import TableCard from "qqq/components/widgets/tables/TableCard";
|
||||
import Widget from "qqq/components/widgets/Widget";
|
||||
import ProcessRun from "qqq/pages/processes/ProcessRun";
|
||||
import Client from "qqq/utils/qqq/Client";
|
||||
import colors from "qqq/assets/theme/base/colors";
|
||||
|
||||
|
||||
const qController = Client.getInstance();
|
||||
|
||||
interface Props
|
||||
{
|
||||
widgetMetaDataList: QWidgetMetaData[];
|
||||
tableName?: string;
|
||||
entityPrimaryKey?: string;
|
||||
omitWrappingGridContainer: boolean;
|
||||
areChildren?: boolean
|
||||
childUrlParams?: string
|
||||
}
|
||||
|
||||
DashboardWidgets.defaultProps = {
|
||||
widgetMetaDataList: null,
|
||||
tableName: null,
|
||||
entityPrimaryKey: null,
|
||||
omitWrappingGridContainer: false,
|
||||
areChildren: false,
|
||||
childUrlParams: ""
|
||||
};
|
||||
|
||||
function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, omitWrappingGridContainer, areChildren, childUrlParams}: Props): JSX.Element
|
||||
{
|
||||
const location = useLocation();
|
||||
const [qInstance, setQInstance] = useState(null as QInstance);
|
||||
const [widgetData, setWidgetData] = useState([] as any[]);
|
||||
const [widgetCounter, setWidgetCounter] = useState(0);
|
||||
const [, forceUpdate] = useReducer((x) => x + 1, 0);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
(async () =>
|
||||
{
|
||||
const newQInstance = await qController.loadMetaData();
|
||||
setQInstance(newQInstance);
|
||||
})();
|
||||
}, []);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if (!qInstance)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
forceUpdate();
|
||||
for (let i = 0; i < widgetMetaDataList.length; i++)
|
||||
{
|
||||
widgetData[i] = {};
|
||||
(async () =>
|
||||
{
|
||||
widgetData[i] = await qController.widget(widgetMetaDataList[i].name, getQueryParams(null));
|
||||
setWidgetCounter(widgetCounter + 1);
|
||||
forceUpdate();
|
||||
})();
|
||||
}
|
||||
setWidgetData(widgetData);
|
||||
}, [qInstance, widgetMetaDataList]);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
setWidgetData([] as any[]);
|
||||
}, [location.pathname]);
|
||||
|
||||
const reloadWidget = (index: number, data: string) =>
|
||||
{
|
||||
setTimeout(async () =>
|
||||
{
|
||||
widgetData[index] = await qController.widget(widgetMetaDataList[index].name, getQueryParams(data));
|
||||
setWidgetCounter(widgetCounter + 1);
|
||||
}, 1);
|
||||
};
|
||||
|
||||
function getQueryParams(extraParams: string): string
|
||||
{
|
||||
let ampersand = "";
|
||||
let params = "";
|
||||
let foundParam = false;
|
||||
if(entityPrimaryKey)
|
||||
{
|
||||
params += `${ampersand}id=${entityPrimaryKey}`;
|
||||
ampersand = "&";
|
||||
}
|
||||
if(tableName)
|
||||
{
|
||||
params += `${ampersand}tableName=${tableName}`;
|
||||
ampersand = "&";
|
||||
}
|
||||
if(extraParams)
|
||||
{
|
||||
params += `${ampersand}${extraParams}`;
|
||||
ampersand = "&";
|
||||
}
|
||||
if(childUrlParams)
|
||||
{
|
||||
params += `${ampersand}${childUrlParams}`;
|
||||
}
|
||||
|
||||
return params;
|
||||
}
|
||||
|
||||
const widgetCount = widgetMetaDataList ? widgetMetaDataList.length : 0;
|
||||
|
||||
const renderWidget = (widgetMetaData: QWidgetMetaData, i: number): JSX.Element =>
|
||||
{
|
||||
return (
|
||||
<Box key={`${widgetMetaData.name}-${i}`} sx={{alignItems: "stretch", flexGrow: 1, display: "flex", marginTop: "0px", paddingTop: "0px", width: "100%", height: "100%"}}>
|
||||
{
|
||||
widgetMetaData.type === "parentWidget" && (
|
||||
<ParentWidget
|
||||
icon={widgetMetaData.icon}
|
||||
entityPrimaryKey={entityPrimaryKey}
|
||||
tableName={tableName}
|
||||
widgetIndex={i}
|
||||
label={widgetMetaData.label}
|
||||
data={widgetData[i]}
|
||||
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
|
||||
label={widgetData[i]?.processMetaData?.label}
|
||||
widgetData={widgetData[i]}
|
||||
reloadWidgetCallback={(data) => reloadWidget(i, data)}>
|
||||
<div>
|
||||
<ProcessRun process={widgetData[i]?.processMetaData} defaultProcessValues={widgetData[i]?.defaultValues} isWidget={true} forceReInit={widgetCounter} />
|
||||
</div>
|
||||
</Widget>
|
||||
)
|
||||
}
|
||||
{
|
||||
widgetMetaData.type === "stepper" && (
|
||||
<Card sx={{alignItems: "stretch", flexGrow: 1, display: "flex", marginTop: "0px", paddingTop: "0px"}}>
|
||||
<Box padding="1rem">
|
||||
{
|
||||
widgetMetaData.label && (
|
||||
<MDTypography variant="h5" textTransform="capitalize">
|
||||
{widgetMetaData.label}
|
||||
</MDTypography>
|
||||
)
|
||||
}
|
||||
<StepperCard data={widgetData[i]} />
|
||||
</Box>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
{
|
||||
widgetMetaData.type === "html" && (
|
||||
<Widget label={widgetMetaData.label}>
|
||||
<Box px={1} pt={0} pb={2}>
|
||||
<MDTypography component="div" variant="button" color="text" fontWeight="light">
|
||||
{
|
||||
widgetData && widgetData[i] && widgetData[i].html ? (
|
||||
parse(widgetData[i].html)
|
||||
) : <Skeleton />
|
||||
}
|
||||
</MDTypography>
|
||||
</Box>
|
||||
</Widget>
|
||||
)
|
||||
}
|
||||
{
|
||||
widgetMetaData.type === "multiStatistics" && (
|
||||
<MultiStatisticsCard
|
||||
color="info"
|
||||
title={widgetMetaData.label}
|
||||
data={widgetData[i]}
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
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={widgetData[i]?.title}
|
||||
data={widgetData[i]}
|
||||
increaseIsGood={widgetData[i].increaseIsGood}
|
||||
isCurrency={widgetData[i].isCurrency}
|
||||
/>
|
||||
)
|
||||
)
|
||||
}
|
||||
{
|
||||
widgetMetaData.type === "statistics" && (
|
||||
widgetData && widgetData[i] && (
|
||||
<StatisticsCard
|
||||
color={colors.info.main}
|
||||
icon={widgetMetaData.icon}
|
||||
data={widgetData[i]}
|
||||
increaseIsGood={true}
|
||||
/>
|
||||
)
|
||||
)
|
||||
}
|
||||
{
|
||||
widgetMetaData.type === "quickSightChart" && (
|
||||
<QuickSightChart url={widgetData[i]?.url} label={widgetMetaData.label} />
|
||||
)
|
||||
}
|
||||
{
|
||||
widgetMetaData.type === "barChart" && (
|
||||
<BarChart
|
||||
color={colors.info.main}
|
||||
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}
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
widgetMetaData.type === "divider" && (
|
||||
<Box>
|
||||
<DividerWidget />
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
{
|
||||
widgetMetaData.type === "horizontalBarChart" && (
|
||||
<HorizontalBarChart
|
||||
height={widgetData[i]?.height}
|
||||
title={widgetMetaData.label}
|
||||
data={widgetData[i]?.chartData}
|
||||
isCurrency={widgetData[i]?.isCurrency}
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
widgetMetaData.type === "lineChart" && (
|
||||
widgetData && widgetData[i] && widgetData[i].chartData && widgetData[i].chartData?.datasets ? (
|
||||
<DefaultLineChart sx={{alignItems: "center"}}
|
||||
title={widgetData[i].title}
|
||||
description={(
|
||||
<Box display="flex" justifyContent="space-between">
|
||||
<Box display="flex" ml={-1}>
|
||||
{
|
||||
widgetData[i].chartData.datasets.map((dataSet: any) => (
|
||||
<MDBadgeDot key={dataSet.label} color={dataSet.color} size="sm" badgeContent={dataSet.label} />
|
||||
))
|
||||
}
|
||||
</Box>
|
||||
<Box mt={-4} mr={-1} position="absolute" right="1.5rem" />
|
||||
</Box>
|
||||
)}
|
||||
data={widgetData[i].chartData as { labels: string[]; datasets: { label: string; color: "primary" | "secondary" | "info" | "success" | "warning" | "error" | "light" | "dark"; data: number[]; }[]; }}
|
||||
isYAxisCurrency={widgetData[i].isYAxisCurrency}
|
||||
isChild={areChildren}
|
||||
/>
|
||||
) : null
|
||||
)
|
||||
}
|
||||
{
|
||||
widgetMetaData.type === "childRecordList" && (
|
||||
widgetData && widgetData[i] &&
|
||||
<RecordGridWidget
|
||||
title={widgetMetaData.label}
|
||||
data={widgetData[i]}
|
||||
/>
|
||||
)
|
||||
|
||||
}
|
||||
{
|
||||
widgetMetaData.type === "fieldValueList" && (
|
||||
widgetData && widgetData[i] &&
|
||||
<FieldValueListWidget
|
||||
title={widgetMetaData.label}
|
||||
data={widgetData[i]}
|
||||
reloadWidgetCallback={(data) => reloadWidget(i, data)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
const body: JSX.Element =
|
||||
(
|
||||
<>
|
||||
{
|
||||
widgetMetaDataList.map((widgetMetaData, i) => (
|
||||
omitWrappingGridContainer
|
||||
? renderWidget(widgetMetaData, i)
|
||||
:
|
||||
<Grid id={widgetMetaData.name} key={`${widgetMetaData.name}-${i}`} item lg={widgetMetaData.gridColumns ? widgetMetaData.gridColumns : 12} xs={12} sx={{display: "flex", alignItems: "stretch", scrollMarginTop: "100px"}}>
|
||||
{renderWidget(widgetMetaData, i)}
|
||||
</Grid>
|
||||
))
|
||||
}
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
widgetCount > 0 ? (
|
||||
omitWrappingGridContainer ? body :
|
||||
(
|
||||
<Grid container spacing={3} pb={4}>
|
||||
{body}
|
||||
</Grid>
|
||||
)
|
||||
) : null
|
||||
);
|
||||
}
|
||||
|
||||
export default DashboardWidgets;
|
114
src/qqq/components/widgets/ParentWidget.tsx
Normal file
114
src/qqq/components/widgets/ParentWidget.tsx
Normal file
@ -0,0 +1,114 @@
|
||||
/*
|
||||
* 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 {QWidgetMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QWidgetMetaData";
|
||||
import {Box} from "@mui/material";
|
||||
import React, {useEffect, useState} from "react";
|
||||
import DashboardWidgets from "qqq/components/widgets/DashboardWidgets";
|
||||
import Widget from "qqq/components/widgets/Widget";
|
||||
import Client from "qqq/utils/qqq/Client";
|
||||
|
||||
|
||||
//////////////////////////////////////////////
|
||||
// structure of expected parent widget data //
|
||||
//////////////////////////////////////////////
|
||||
export interface ParentWidgetData
|
||||
{
|
||||
dropdownLabelList: string[];
|
||||
dropdownNameList: string[];
|
||||
dropdownDataList: {
|
||||
id: string,
|
||||
label: string
|
||||
}[][];
|
||||
childWidgetNameList: string[];
|
||||
dropdownNeedsSelectedText?: string;
|
||||
icon?: string;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////
|
||||
// define properties and defaults //
|
||||
////////////////////////////////////
|
||||
interface Props
|
||||
{
|
||||
widgetIndex: number;
|
||||
label: string;
|
||||
icon?: string;
|
||||
data: ParentWidgetData;
|
||||
reloadWidgetCallback?: (widgetIndex: number, params: string) => void;
|
||||
entityPrimaryKey?: string;
|
||||
tableName?: string;
|
||||
}
|
||||
|
||||
|
||||
const qController = Client.getInstance();
|
||||
function ParentWidget({widgetIndex, label, icon, data, reloadWidgetCallback, entityPrimaryKey, tableName}: Props, ): JSX.Element
|
||||
{
|
||||
const [childUrlParams, setChildUrlParams] = useState("");
|
||||
const [qInstance, setQInstance] = useState(null as QInstance);
|
||||
const [widgets, setWidgets] = useState([] as any[]);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
(async () =>
|
||||
{
|
||||
const newQInstance = await qController.loadMetaData();
|
||||
setQInstance(newQInstance);
|
||||
})();
|
||||
}, []);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(qInstance && data && data.childWidgetNameList)
|
||||
{
|
||||
let widgetMetaDataList = [] as QWidgetMetaData[];
|
||||
data?.childWidgetNameList.forEach((widgetName: string) =>
|
||||
{
|
||||
widgetMetaDataList.push(qInstance.widgets.get(widgetName));
|
||||
})
|
||||
setWidgets(widgetMetaDataList);
|
||||
}
|
||||
}, [qInstance, data]);
|
||||
|
||||
const parentReloadWidgetCallback = (data: string) =>
|
||||
{
|
||||
setChildUrlParams(data);
|
||||
|
||||
reloadWidgetCallback(widgetIndex, data);
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
return (
|
||||
<Widget
|
||||
icon={icon}
|
||||
label={label}
|
||||
widgetData={data}
|
||||
reloadWidgetCallback={parentReloadWidgetCallback}
|
||||
>
|
||||
<Box px={3} sx={{width: "100%"}}>
|
||||
<DashboardWidgets widgetMetaDataList={widgets} entityPrimaryKey={entityPrimaryKey} tableName={tableName} childUrlParams={childUrlParams} areChildren={true}/>
|
||||
</Box>
|
||||
</Widget>
|
||||
);
|
||||
}
|
||||
|
||||
export default ParentWidget;
|
319
src/qqq/components/widgets/Widget.tsx
Normal file
319
src/qqq/components/widgets/Widget.tsx
Normal file
@ -0,0 +1,319 @@
|
||||
/*
|
||||
* 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 {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData";
|
||||
import Box from "@mui/material/Box";
|
||||
import Button from "@mui/material/Button";
|
||||
import Card from "@mui/material/Card";
|
||||
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 colors from "qqq/components/legacy/colors";
|
||||
import DropdownMenu, {DropdownOption} from "qqq/components/widgets/components/DropdownMenu";
|
||||
|
||||
export interface WidgetData
|
||||
{
|
||||
dropdownLabelList?: string[];
|
||||
dropdownNameList?: string[];
|
||||
dropdownDataList?: {
|
||||
id: string,
|
||||
label: string
|
||||
}[][];
|
||||
dropdownNeedsSelectedText?: string;
|
||||
}
|
||||
|
||||
|
||||
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: [],
|
||||
labelAdditionalComponentsRight: [],
|
||||
};
|
||||
|
||||
|
||||
export class LabelComponent
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
export class HeaderLink extends LabelComponent
|
||||
{
|
||||
label: string;
|
||||
to: string
|
||||
|
||||
constructor(label: string, to: string)
|
||||
{
|
||||
super();
|
||||
this.label = label;
|
||||
this.to = to;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
export class AddNewRecordButton extends LabelComponent
|
||||
{
|
||||
table: QTableMetaData;
|
||||
label: string;
|
||||
defaultValues: any;
|
||||
disabledFields: any;
|
||||
|
||||
constructor(table: QTableMetaData, defaultValues: any, label: string = "Add new", disabledFields: any = defaultValues)
|
||||
{
|
||||
super();
|
||||
this.table = table;
|
||||
this.label = label;
|
||||
this.defaultValues = defaultValues;
|
||||
this.disabledFields = disabledFields;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class Dropdown extends LabelComponent
|
||||
{
|
||||
label: string;
|
||||
options: DropdownOption[];
|
||||
onChangeCallback: any
|
||||
|
||||
constructor(label: string, options: DropdownOption[], onChangeCallback: any)
|
||||
{
|
||||
super();
|
||||
this.label = label;
|
||||
this.options = options;
|
||||
this.onChangeCallback = onChangeCallback;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
function Widget(props: React.PropsWithChildren<Props>): JSX.Element
|
||||
{
|
||||
const navigate = useNavigate();
|
||||
const [dropdownData, setDropdownData] = useState([]);
|
||||
const [counter, setCounter] = useState(0);
|
||||
|
||||
function openEditForm(table: QTableMetaData, id: any = null, defaultValues: any, disabledFields: any)
|
||||
{
|
||||
navigate(`#/createChild=${table.name}/defaultValues=${JSON.stringify(defaultValues)}/disabledFields=${JSON.stringify(disabledFields)}`)
|
||||
}
|
||||
|
||||
function renderComponent(component: LabelComponent)
|
||||
{
|
||||
if(component instanceof HeaderLink)
|
||||
{
|
||||
const link = component as HeaderLink
|
||||
return (
|
||||
<Typography variant="body2" p={2} display="inline">
|
||||
{link.to ? <Link to={link.to}>{link.label}</Link> : null}
|
||||
</Typography>
|
||||
);
|
||||
}
|
||||
|
||||
if (component instanceof AddNewRecordButton)
|
||||
{
|
||||
const addNewRecordButton = component as AddNewRecordButton
|
||||
return (
|
||||
<Typography variant="body2" p={2} pr={1} display="inline">
|
||||
<Button onClick={() => openEditForm(addNewRecordButton.table, null, addNewRecordButton.defaultValues, addNewRecordButton.disabledFields)}>{addNewRecordButton.label}</Button>
|
||||
</Typography>
|
||||
);
|
||||
}
|
||||
|
||||
if (component instanceof Dropdown)
|
||||
{
|
||||
const dropdown = component as Dropdown
|
||||
return (
|
||||
<Box my={2} mr={2} sx={{float: "right"}}>
|
||||
<DropdownMenu
|
||||
sx={{width: 200, marginLeft: "15px"}}
|
||||
label={`Select ${dropdown.label}`}
|
||||
dropdownOptions={dropdown.options}
|
||||
onChangeCallback={dropdown.onChangeCallback}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
return (<div>Unsupported component type.</div>)
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
// make dropdowns from the widgetData appear as label-components //
|
||||
///////////////////////////////////////////////////////////////////
|
||||
const effectiveLabelAdditionalComponentsRight: LabelComponent[] = [];
|
||||
if(props.labelAdditionalComponentsRight)
|
||||
{
|
||||
props.labelAdditionalComponentsRight.map((component) => effectiveLabelAdditionalComponentsRight.push(component));
|
||||
}
|
||||
if(props.widgetData && props.widgetData.dropdownDataList)
|
||||
{
|
||||
props.widgetData.dropdownDataList?.map((dropdownData: any, index: number) =>
|
||||
{
|
||||
effectiveLabelAdditionalComponentsRight.push(new Dropdown(props.widgetData.dropdownLabelList[index], dropdownData, handleDataChange))
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function handleDataChange(dropdownLabel: string, changedData: any)
|
||||
{
|
||||
if(dropdownData)
|
||||
{
|
||||
///////////////////////////////////////////
|
||||
// find the index base on selected label //
|
||||
///////////////////////////////////////////
|
||||
const tableName = dropdownLabel.replace("Select ", "");
|
||||
let index = -1;
|
||||
for (let i = 0; i < props.widgetData.dropdownLabelList.length; i++)
|
||||
{
|
||||
if (tableName === props.widgetData.dropdownLabelList[i])
|
||||
{
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (index < 0)
|
||||
{
|
||||
throw(`Could not find table name for label ${tableName}`);
|
||||
}
|
||||
|
||||
dropdownData[index] = (changedData) ? changedData.id : null;
|
||||
setDropdownData(dropdownData);
|
||||
setCounter(counter + 1);
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(dropdownData)
|
||||
{
|
||||
let params = "";
|
||||
for (let i = 0; i < dropdownData.length; i++)
|
||||
{
|
||||
if (i > 0)
|
||||
{
|
||||
params += "&";
|
||||
}
|
||||
params += `${props.widgetData.dropdownNameList[i]}=`;
|
||||
if(dropdownData[i])
|
||||
{
|
||||
params += `${dropdownData[i]}`;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if(props.reloadWidgetCallback)
|
||||
{
|
||||
props.reloadWidgetCallback(params);
|
||||
}
|
||||
else
|
||||
{
|
||||
console.log(`No reload widget callback in ${props.label}`)
|
||||
}
|
||||
}
|
||||
}, [counter]);
|
||||
|
||||
const widgetContent =
|
||||
<Box sx={{width: "100%"}}>
|
||||
{
|
||||
(props.icon || props.label) && (
|
||||
<Box display="flex" justifyContent="space-between" alignItems="center" sx={{width: "100%"}}>
|
||||
<Box py={2}>
|
||||
{
|
||||
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>
|
||||
<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;
|
186
src/qqq/components/widgets/charts/barchart/BarChart.tsx
Normal file
186
src/qqq/components/widgets/charts/barchart/BarChart.tsx
Normal file
@ -0,0 +1,186 @@
|
||||
/*
|
||||
* 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 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 {Bar} from "react-chartjs-2";
|
||||
import MDTypography from "qqq/components/legacy/MDTypography";
|
||||
import {GenericChartDataSingleDataset} from "qqq/components/widgets/charts/datastructures/GenericChartDataSingleDataset";
|
||||
|
||||
|
||||
///////////////////////////
|
||||
// options for bar chart //
|
||||
///////////////////////////
|
||||
const options = {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
},
|
||||
interaction: {
|
||||
intersect: false,
|
||||
mode: "index",
|
||||
},
|
||||
scales: {
|
||||
y: {
|
||||
grid: {
|
||||
drawBorder: false,
|
||||
display: true,
|
||||
drawOnChartArea: true,
|
||||
drawTicks: false,
|
||||
borderDash: [5, 5],
|
||||
color: "rgba(255, 255, 255, .2)",
|
||||
},
|
||||
ticks: {
|
||||
suggestedMin: 0,
|
||||
suggestedMax: 500,
|
||||
beginAtZero: true,
|
||||
padding: 10,
|
||||
font: {
|
||||
size: 14,
|
||||
weight: 300,
|
||||
family: "Roboto",
|
||||
style: "normal",
|
||||
lineHeight: 2,
|
||||
},
|
||||
color: "#fff",
|
||||
},
|
||||
},
|
||||
x: {
|
||||
grid: {
|
||||
drawBorder: false,
|
||||
display: true,
|
||||
drawOnChartArea: true,
|
||||
drawTicks: false,
|
||||
borderDash: [5, 5],
|
||||
color: "rgba(255, 255, 255, .2)",
|
||||
},
|
||||
ticks: {
|
||||
display: true,
|
||||
color: "#f8f9fa",
|
||||
padding: 10,
|
||||
font: {
|
||||
size: 14,
|
||||
weight: 300,
|
||||
family: "Roboto",
|
||||
style: "normal",
|
||||
lineHeight: 2,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
////////////////////////////////////
|
||||
// define properties and defaults //
|
||||
////////////////////////////////////
|
||||
interface Props
|
||||
{
|
||||
color?: "primary" | "secondary" | "info" | "success" | "warning" | "error" | "dark";
|
||||
title: string;
|
||||
description?: string;
|
||||
date: string;
|
||||
data: GenericChartDataSingleDataset;
|
||||
}
|
||||
|
||||
BarChart.defaultProps = {
|
||||
color: "dark",
|
||||
description: "",
|
||||
};
|
||||
|
||||
function getChartData(labels: any, dataset: any)
|
||||
{
|
||||
return {
|
||||
chartData: {
|
||||
labels,
|
||||
datasets: [
|
||||
{
|
||||
label: dataset?.label,
|
||||
tension: 0.4,
|
||||
borderWidth: 0,
|
||||
borderRadius: 4,
|
||||
borderSkipped: false,
|
||||
backgroundColor: "rgba(255, 255, 255, 0.8)",
|
||||
data: dataset?.data,
|
||||
maxBarThickness: 6,
|
||||
},
|
||||
],
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function BarChart({color, title, description, date, data}: Props): JSX.Element
|
||||
{
|
||||
/////////////////////////////////////////////////////////
|
||||
// enrich data with expected customizations and styles //
|
||||
/////////////////////////////////////////////////////////
|
||||
const {chartData} = getChartData(data?.labels, data?.dataset);
|
||||
return (
|
||||
|
||||
<Box mt={3} sx={{flexGrow: 1, display: "flex"}}>
|
||||
<Card sx={{flexGrow: 1, display: "flex", height: "100%"}}>
|
||||
<Box padding="1rem">
|
||||
{useMemo(
|
||||
() => (
|
||||
<Box
|
||||
borderRadius="10px"
|
||||
py={2}
|
||||
pr={0.5}
|
||||
mt={-5}
|
||||
height="12.5rem"
|
||||
sx={{backgroundColor: color}}
|
||||
>
|
||||
<Bar data={chartData} options={options} />
|
||||
</Box>
|
||||
),
|
||||
[data, color]
|
||||
)}
|
||||
<Box 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 />
|
||||
<Box 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>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
</Card>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default BarChart;
|
@ -0,0 +1,229 @@
|
||||
/*
|
||||
* 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 Box from "@mui/material/Box";
|
||||
import Card from "@mui/material/Card";
|
||||
import Icon from "@mui/material/Icon";
|
||||
import {ReactNode, useMemo} from "react";
|
||||
import {Bar} from "react-chartjs-2";
|
||||
import colors from "qqq/components/legacy/colors";
|
||||
import MDTypography from "qqq/components/legacy/MDTypography";
|
||||
import {GenericChartData} from "qqq/components/widgets/charts/datastructures/GenericChartData";
|
||||
|
||||
|
||||
//////////////////
|
||||
// configuation //
|
||||
//////////////////
|
||||
const options = {
|
||||
indexAxis: "y",
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
legend: {
|
||||
display: true,
|
||||
},
|
||||
tooltip: {
|
||||
enabled: true,
|
||||
callbacks: {
|
||||
label: function(context:any)
|
||||
{
|
||||
return(context.parsed.x);
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
scales: {
|
||||
y: {
|
||||
grid: {
|
||||
drawBorder: false,
|
||||
display: true,
|
||||
drawOnChartArea: true,
|
||||
drawTicks: false,
|
||||
borderDash: [5, 5],
|
||||
color: "#c1c4ce5c",
|
||||
},
|
||||
ticks: {
|
||||
display: true,
|
||||
padding: 10,
|
||||
color: "#9ca2b7",
|
||||
font: {
|
||||
size: 14,
|
||||
weight: 300,
|
||||
family: "Roboto",
|
||||
style: "normal",
|
||||
lineHeight: 2,
|
||||
},
|
||||
},
|
||||
},
|
||||
x: {
|
||||
grid: {
|
||||
drawBorder: false,
|
||||
display: false,
|
||||
drawOnChartArea: true,
|
||||
drawTicks: true,
|
||||
color: "#c1c4ce5c",
|
||||
},
|
||||
ticks: {
|
||||
display: true,
|
||||
color: "#9ca2b7",
|
||||
padding: 0,
|
||||
font: {
|
||||
size: 14,
|
||||
weight: 300,
|
||||
family: "Roboto",
|
||||
style: "normal",
|
||||
lineHeight: 2,
|
||||
},
|
||||
callback: function(value: any, index: any, values: any)
|
||||
{
|
||||
return value;
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
/////////////////////////
|
||||
// inputs and defaults //
|
||||
/////////////////////////
|
||||
interface Props
|
||||
{
|
||||
icon?: {
|
||||
color?: "primary" | "secondary" | "info" | "success" | "warning" | "error" | "light" | "dark";
|
||||
component: ReactNode;
|
||||
};
|
||||
title?: string;
|
||||
description?: string | ReactNode;
|
||||
height?: string | number;
|
||||
isCurrency?: boolean;
|
||||
data: GenericChartData;
|
||||
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
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,
|
||||
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;
|
||||
if(isCurrency)
|
||||
{
|
||||
customOptions.scales.x.ticks =
|
||||
{
|
||||
... customOptions.scales.x.ticks,
|
||||
callback: function(value: any, index: any, values: any)
|
||||
{
|
||||
return value.toLocaleString("en-US", {style: "currency", currency: "USD", minimumFractionDigits: 0});
|
||||
}
|
||||
}
|
||||
customOptions.plugins.tooltip.callbacks =
|
||||
{
|
||||
... customOptions.plugins.tooltip.callbacks,
|
||||
label: function(context:any)
|
||||
{
|
||||
return context.parsed.x.toLocaleString("en-US", {style: "currency", currency: "USD", minimumFractionDigits: 0});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const renderChart = (
|
||||
<Box py={2} pr={2} pl={icon.component ? 1 : 2} sx={{alignItems: "stretch", flexGrow: 1, display: "flex", marginTop: "0px", paddingTop: "0px"}}>
|
||||
{title || description && (
|
||||
<Box display="flex" px={description ? 1 : 0} pt={description ? 1 : 0} sx={{alignItems: "stretch", flexGrow: 1, display: "flex", marginTop: "0px", paddingTop: "0px"}}>
|
||||
{icon.component && (
|
||||
<Box
|
||||
width="4rem"
|
||||
height="4rem"
|
||||
borderRadius="xl"
|
||||
display="flex"
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
color="white"
|
||||
mt={-5}
|
||||
mr={2}
|
||||
sx={{backgroundColor: icon.color || "info"}}
|
||||
>
|
||||
<Icon fontSize="medium">{icon.component}</Icon>
|
||||
</Box>
|
||||
)}
|
||||
<Box mt={icon.component ? -2 : 0}>
|
||||
{title && <MDTypography variant="h5">{title}</MDTypography>}
|
||||
<Box mb={2}>
|
||||
<MDTypography component="div" variant="button" color="text">
|
||||
{description}
|
||||
</MDTypography>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
{useMemo(
|
||||
() => (
|
||||
<Box 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>
|
||||
)
|
||||
}
|
||||
</Box>
|
||||
),
|
||||
[data, height]
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
|
||||
return title || description ? <Card>{renderChart}</Card> : renderChart;
|
||||
}
|
||||
|
||||
HorizontalBarChart.defaultProps = {
|
||||
icon: {color: "info", component: ""},
|
||||
title: "",
|
||||
description: "",
|
||||
height: "19.125rem",
|
||||
};
|
||||
|
||||
export default HorizontalBarChart;
|
@ -0,0 +1,35 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
|
||||
//////////////////////////////////////
|
||||
// structure of expected chart data //
|
||||
//////////////////////////////////////
|
||||
export interface GenericChartData
|
||||
{
|
||||
description?: string;
|
||||
labels: string[];
|
||||
datasets: {
|
||||
label: string;
|
||||
color?: "primary" | "secondary" | "info" | "success" | "warning" | "error" | "light" | "dark";
|
||||
data: number[];
|
||||
}[];
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
|
||||
//////////////////////////////////////
|
||||
// structure of expected chart data //
|
||||
//////////////////////////////////////
|
||||
export interface GenericChartDataSingleDataset
|
||||
{
|
||||
labels: string[];
|
||||
dataset: {
|
||||
label: string;
|
||||
color?: "primary" | "secondary" | "info" | "success" | "warning" | "error" | "light" | "dark";
|
||||
data: number[];
|
||||
};
|
||||
}
|
275
src/qqq/components/widgets/charts/linechart/DefaultLineChart.tsx
Normal file
275
src/qqq/components/widgets/charts/linechart/DefaultLineChart.tsx
Normal file
@ -0,0 +1,275 @@
|
||||
/*
|
||||
* 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 Box from "@mui/material/Box";
|
||||
import Card from "@mui/material/Card";
|
||||
import Icon from "@mui/material/Icon";
|
||||
import React, {ReactNode, useMemo} from "react";
|
||||
import {Line} from "react-chartjs-2";
|
||||
import colors from "qqq/assets/theme/base/colors";
|
||||
import MDBadgeDot from "qqq/components/legacy/MDBadgeDot";
|
||||
import MDTypography from "qqq/components/legacy/MDTypography";
|
||||
|
||||
//////////////////////////////////////////
|
||||
// structure of default line chart data //
|
||||
//////////////////////////////////////////
|
||||
export interface DefaultLineChartData
|
||||
{
|
||||
labels: string[];
|
||||
lineLabels?: string[];
|
||||
datasets: {
|
||||
label: string;
|
||||
color?: "primary" | "secondary" | "info" | "success" | "warning" | "error" | "light" | "dark";
|
||||
data: number[];
|
||||
}[];
|
||||
};
|
||||
|
||||
/////////////////////
|
||||
// display options //
|
||||
/////////////////////
|
||||
const options = {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
tooltip: {
|
||||
enabled: true,
|
||||
callbacks: {
|
||||
label: function(context:any)
|
||||
{
|
||||
return(" " + Number(context.parsed.y).toLocaleString());
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
interaction: {
|
||||
intersect: false,
|
||||
mode: "index",
|
||||
},
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
grid: {
|
||||
drawBorder: false,
|
||||
display: true,
|
||||
drawOnChartArea: true,
|
||||
drawTicks: false,
|
||||
borderDash: [5, 5],
|
||||
color: "#c1c4ce5c",
|
||||
},
|
||||
ticks: {
|
||||
display: true,
|
||||
padding: 10,
|
||||
color: "#9ca2b7",
|
||||
font: {
|
||||
size: 14,
|
||||
weight: 300,
|
||||
family: "Roboto",
|
||||
style: "normal",
|
||||
lineHeight: 2,
|
||||
},
|
||||
callback: function(value: any, index: any, values: any)
|
||||
{
|
||||
return value.toLocaleString();
|
||||
}
|
||||
},
|
||||
},
|
||||
x: {
|
||||
grid: {
|
||||
drawBorder: false,
|
||||
display: true,
|
||||
drawOnChartArea: true,
|
||||
drawTicks: true,
|
||||
borderDash: [5, 5],
|
||||
color: "#c1c4ce5c",
|
||||
},
|
||||
ticks: {
|
||||
display: true,
|
||||
color: "#9ca2b7",
|
||||
padding: 10,
|
||||
font: {
|
||||
size: 14,
|
||||
weight: 300,
|
||||
family: "Roboto",
|
||||
style: "normal",
|
||||
lineHeight: 2,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/////////////////////////
|
||||
// inputs and defaults //
|
||||
/////////////////////////
|
||||
interface Props
|
||||
{
|
||||
icon?: {
|
||||
color?: "primary" | "secondary" | "info" | "success" | "warning" | "error" | "light" | "dark";
|
||||
component: ReactNode;
|
||||
};
|
||||
title?: string;
|
||||
height?: string | number;
|
||||
isYAxisCurrency?: boolean;
|
||||
isChild?: boolean;
|
||||
data: DefaultLineChartData;
|
||||
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
DefaultLineChart.defaultProps = {
|
||||
icon: {color: "info", component: ""},
|
||||
title: "",
|
||||
height: "19.125rem",
|
||||
};
|
||||
|
||||
|
||||
function DefaultLineChart({icon, title, height, data, isYAxisCurrency, isChild}: Props): JSX.Element
|
||||
{
|
||||
const allBackgroundColors = ["info", "warning", "primary", "success", "error", "secondary", "dark"];
|
||||
if (data && data.datasets)
|
||||
{
|
||||
data.datasets.forEach((ds, index) =>
|
||||
{
|
||||
// @ts-ignore
|
||||
ds.color = allBackgroundColors[index];
|
||||
});
|
||||
}
|
||||
|
||||
const chartDatasets = data && data.datasets
|
||||
? data.datasets.map((dataset) => ({
|
||||
...dataset,
|
||||
tension: 0,
|
||||
pointRadius: 3,
|
||||
borderWidth: 4,
|
||||
backgroundColor: "transparent",
|
||||
fill: true,
|
||||
pointBackgroundColor: colors[dataset.color]
|
||||
? colors[dataset.color || "dark"].main
|
||||
: colors.dark.main,
|
||||
borderColor: colors[dataset.color]
|
||||
? colors[dataset.color || "dark"].main
|
||||
: colors.dark.main,
|
||||
maxBarThickness: 6,
|
||||
}))
|
||||
: [];
|
||||
|
||||
let customOptions = options;
|
||||
if(isYAxisCurrency)
|
||||
{
|
||||
customOptions.scales.y.ticks =
|
||||
{
|
||||
... customOptions.scales.y.ticks,
|
||||
callback: function(value: any, index: any, values: any)
|
||||
{
|
||||
return value.toLocaleString("en-US", {style: "currency", currency: "USD", minimumFractionDigits: 0});
|
||||
}
|
||||
}
|
||||
customOptions.plugins.tooltip.callbacks =
|
||||
{
|
||||
... customOptions.plugins.tooltip.callbacks,
|
||||
label: function(context:any)
|
||||
{
|
||||
return " " + context.parsed.y.toLocaleString("en-US", {style: "currency", currency: "USD", minimumFractionDigits: 2});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let fullData = {};
|
||||
if (data)
|
||||
{
|
||||
fullData = {
|
||||
labels: data.labels,
|
||||
datasets: chartDatasets
|
||||
};
|
||||
}
|
||||
|
||||
const renderChart = (
|
||||
<Box py={2} pr={2} pl={icon.component ? 1 : 2}>
|
||||
|
||||
{title ? (
|
||||
<Box display="flex" px={0} pt={0}>
|
||||
{icon.component && (
|
||||
<Box
|
||||
width="4rem"
|
||||
height="4rem"
|
||||
borderRadius="xl"
|
||||
display="flex"
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
color="white"
|
||||
mt={-5}
|
||||
mr={2}
|
||||
sx={{backgroundColor: icon.color || "info"}}
|
||||
>
|
||||
<Icon fontSize="medium">{icon.component}</Icon>
|
||||
</Box>
|
||||
)}
|
||||
<Box mt={icon.component ? -2 : 0}>
|
||||
{isChild ? (
|
||||
title && <MDTypography variant="h6">{title}</MDTypography>
|
||||
) : (
|
||||
title && <MDTypography variant="h5">{title}</MDTypography>
|
||||
)
|
||||
}
|
||||
<Box mb={2}>
|
||||
<MDTypography component="div" variant="button" color="text">
|
||||
<Box display="flex" justifyContent="space-between">
|
||||
<Box display="flex" ml={-1}>
|
||||
{
|
||||
data && data.lineLabels ? (
|
||||
(data.lineLabels.map((label: string, index: number) => (
|
||||
|
||||
<Box key={index}>
|
||||
<MDBadgeDot color={allBackgroundColors[index]} size="sm" badgeContent={label} />
|
||||
</Box>
|
||||
)
|
||||
))) : null
|
||||
}
|
||||
</Box>
|
||||
</Box>
|
||||
</MDTypography>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
) : null}
|
||||
{useMemo(
|
||||
() => (
|
||||
<Box height={height}>
|
||||
<Line data={fullData} options={options} />
|
||||
</Box>
|
||||
),
|
||||
|
||||
[data, height]
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
|
||||
return title ?
|
||||
<Card sx={{alignItems: "stretch", flexGrow: 1, display: "flex", marginTop: "0px", paddingTop: "0px"}}>
|
||||
{renderChart}
|
||||
</Card>
|
||||
: renderChart;
|
||||
}
|
||||
|
||||
export default DefaultLineChart;
|
104
src/qqq/components/widgets/charts/linechart/LineChartConfigs.ts
Normal file
104
src/qqq/components/widgets/charts/linechart/LineChartConfigs.ts
Normal file
@ -0,0 +1,104 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
function configs(labels: any, datasets: any)
|
||||
{
|
||||
return {
|
||||
data: {
|
||||
labels,
|
||||
datasets: [
|
||||
{
|
||||
label: datasets.label,
|
||||
tension: 0,
|
||||
pointRadius: 5,
|
||||
pointBorderColor: "transparent",
|
||||
pointBackgroundColor: "rgba(255, 255, 255, .8)",
|
||||
borderColor: "rgba(255, 255, 255, .8)",
|
||||
borderWidth: 4,
|
||||
backgroundColor: "transparent",
|
||||
fill: true,
|
||||
data: datasets.data,
|
||||
maxBarThickness: 6,
|
||||
},
|
||||
],
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
},
|
||||
interaction: {
|
||||
intersect: false,
|
||||
mode: "index",
|
||||
},
|
||||
scales: {
|
||||
y: {
|
||||
grid: {
|
||||
drawBorder: false,
|
||||
display: true,
|
||||
drawOnChartArea: true,
|
||||
drawTicks: false,
|
||||
borderDash: [5, 5],
|
||||
color: "rgba(255, 255, 255, .2)",
|
||||
},
|
||||
ticks: {
|
||||
display: true,
|
||||
color: "#f8f9fa",
|
||||
padding: 10,
|
||||
font: {
|
||||
size: 14,
|
||||
weight: 300,
|
||||
family: "Roboto",
|
||||
style: "normal",
|
||||
lineHeight: 2,
|
||||
},
|
||||
},
|
||||
},
|
||||
x: {
|
||||
grid: {
|
||||
drawBorder: false,
|
||||
display: false,
|
||||
drawOnChartArea: false,
|
||||
drawTicks: false,
|
||||
borderDash: [5, 5],
|
||||
},
|
||||
ticks: {
|
||||
display: true,
|
||||
color: "#f8f9fa",
|
||||
padding: 10,
|
||||
font: {
|
||||
size: 14,
|
||||
weight: 300,
|
||||
family: "Roboto",
|
||||
style: "normal",
|
||||
lineHeight: 2,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export default configs;
|
@ -0,0 +1,98 @@
|
||||
/*
|
||||
* 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 Box from "@mui/material/Box";
|
||||
import Card from "@mui/material/Card";
|
||||
import parse from "html-react-parser";
|
||||
import {useMemo} from "react";
|
||||
import {Line} from "react-chartjs-2";
|
||||
import MDTypography from "qqq/components/legacy/MDTypography";
|
||||
import configs from "qqq/components/widgets/charts/linechart/LineChartConfigs";
|
||||
import colors from "qqq/assets/theme/base/colors";
|
||||
|
||||
//////////////////////////////////////////
|
||||
// structure of expected bar chart data //
|
||||
//////////////////////////////////////////
|
||||
export interface SmallLineChartData
|
||||
{
|
||||
labels: string[];
|
||||
dataset: {
|
||||
label: string;
|
||||
data: number[];
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
interface Props
|
||||
{
|
||||
color?: "primary" | "secondary" | "info" | "success" | "warning" | "error" | "dark";
|
||||
title: string;
|
||||
description?: string;
|
||||
date: string;
|
||||
chart: SmallLineChartData;
|
||||
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
function SmallLineChart({color, title, description, date, chart}: Props): JSX.Element
|
||||
{
|
||||
const {data, options} = configs(chart?.labels || [], chart?.dataset || {});
|
||||
|
||||
return (
|
||||
|
||||
<Box mt={3} sx={{flexGrow: 1, display: "flex"}}>
|
||||
<Card sx={{height: "100%", flexGrow: 1, display: "flex"}}>
|
||||
<Box padding="1rem">
|
||||
|
||||
{useMemo(
|
||||
() => (
|
||||
<Box
|
||||
py={2}
|
||||
pr={0.5}
|
||||
mt={-5}
|
||||
height="12.5rem"
|
||||
sx={{borderRadius: "10px", backgroundColor: colors.dark.focus}}
|
||||
>
|
||||
<Line data={data} options={options} />
|
||||
</Box>
|
||||
),
|
||||
[chart, color]
|
||||
)}
|
||||
<Box 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>
|
||||
</Box>
|
||||
</Box>
|
||||
</Card>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
SmallLineChart.defaultProps = {
|
||||
color: "dark",
|
||||
description: "",
|
||||
};
|
||||
|
||||
export default SmallLineChart;
|
116
src/qqq/components/widgets/charts/piechart/PieChart.tsx
Normal file
116
src/qqq/components/widgets/charts/piechart/PieChart.tsx
Normal file
@ -0,0 +1,116 @@
|
||||
/*
|
||||
* 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 Box from "@mui/material/Box";
|
||||
import Card from "@mui/material/Card";
|
||||
import Icon from "@mui/material/Icon";
|
||||
import parse from "html-react-parser";
|
||||
import {ReactNode, useMemo} from "react";
|
||||
import {Pie} from "react-chartjs-2";
|
||||
import MDTypography from "qqq/components/legacy/MDTypography";
|
||||
import configs from "qqq/components/widgets/charts/piechart/PieChartConfigs";
|
||||
|
||||
//////////////////////////////////////////
|
||||
// structure of expected bar chart data //
|
||||
//////////////////////////////////////////
|
||||
export interface PieChartData
|
||||
{
|
||||
labels: string[];
|
||||
dataset: {
|
||||
label: string;
|
||||
backgroundColors?: string[];
|
||||
data: number[];
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
// Declaring props types for PieChart
|
||||
interface Props
|
||||
{
|
||||
icon?: {
|
||||
color?: "primary" | "secondary" | "info" | "success" | "warning" | "error" | "light" | "dark";
|
||||
component: ReactNode;
|
||||
};
|
||||
title?: string;
|
||||
description?: string;
|
||||
height?: string | number;
|
||||
chart: PieChartData;
|
||||
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
function PieChart({icon, title, description, height, chart}: Props): JSX.Element
|
||||
{
|
||||
const {data, options} = configs(chart?.labels || [], chart?.dataset || {});
|
||||
|
||||
const renderChart = (
|
||||
<Box py={2} pr={2} pl={icon.component ? 1 : 2}>
|
||||
{title || description ? (
|
||||
<Box display="flex" px={description ? 1 : 0} pt={description ? 1 : 0}>
|
||||
{icon.component && (
|
||||
<Box
|
||||
width="4rem"
|
||||
height="4rem"
|
||||
borderRadius="xl"
|
||||
display="flex"
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
color="white"
|
||||
mt={-5}
|
||||
mr={2}
|
||||
sx={{backgroundColor: icon.color || "info"}}
|
||||
>
|
||||
<Icon fontSize="medium">{icon.component}</Icon>
|
||||
</Box>
|
||||
)}
|
||||
<Box mt={icon.component ? -2 : 0}>
|
||||
{title && <MDTypography variant="h6">{title}</MDTypography>}
|
||||
<Box mb={2}>
|
||||
<MDTypography component="div" variant="button" color="text">
|
||||
{parse(description)}
|
||||
</MDTypography>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
) : null}
|
||||
{useMemo(
|
||||
() => (
|
||||
<Box height={height}>
|
||||
<Pie data={data} options={options} />
|
||||
</Box>
|
||||
),
|
||||
[chart, height]
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
|
||||
return title || description ? <Card>{renderChart}</Card> : renderChart;
|
||||
}
|
||||
|
||||
// Declaring default props for PieChart
|
||||
PieChart.defaultProps = {
|
||||
icon: {color: "info", component: ""},
|
||||
title: "",
|
||||
description: "",
|
||||
height: "19.125rem",
|
||||
};
|
||||
|
||||
export default PieChart;
|
93
src/qqq/components/widgets/charts/piechart/PieChartCard.tsx
Normal file
93
src/qqq/components/widgets/charts/piechart/PieChartCard.tsx
Normal file
@ -0,0 +1,93 @@
|
||||
/*
|
||||
* 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 Box from "@mui/material/Box";
|
||||
import Card from "@mui/material/Card";
|
||||
import Divider from "@mui/material/Divider";
|
||||
import Grid from "@mui/material/Grid";
|
||||
import parse from "html-react-parser";
|
||||
import MDBadgeDot from "qqq/components/legacy/MDBadgeDot";
|
||||
import MDTypography from "qqq/components/legacy/MDTypography";
|
||||
import PieChart, {PieChartData} from "qqq/components/widgets/charts/piechart/PieChart";
|
||||
|
||||
// Declaring props types for PieChart
|
||||
interface Props
|
||||
{
|
||||
title?: string;
|
||||
description?: string;
|
||||
data: PieChartData;
|
||||
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
function PieChartCard({title, description, data}: Props): JSX.Element
|
||||
{
|
||||
const allBackgroundColors = ["info", "warning", "primary", "success", "error", "secondary", "dark"];
|
||||
|
||||
if (data && data.dataset)
|
||||
{
|
||||
data.dataset.backgroundColors = allBackgroundColors;
|
||||
}
|
||||
|
||||
return (
|
||||
<Card sx={{height: "100%", width: "100%", display: "flex"}}>
|
||||
<Box display="flex" pt={2} px={2}>
|
||||
<MDTypography variant="h5">{title}</MDTypography>
|
||||
</Box>
|
||||
<Box mt={3}>
|
||||
<Grid container alignItems="center">
|
||||
<Grid item xs={7}>
|
||||
<PieChart chart={data} height="9.5rem" />
|
||||
</Grid>
|
||||
<Grid item xs={5}>
|
||||
<Box pr={1}>
|
||||
{
|
||||
data && data.labels ? (
|
||||
(data.labels.map((label: string, index: number) => (
|
||||
<Box key={index}>
|
||||
<MDBadgeDot color={allBackgroundColors[index]} size="sm" badgeContent={label} />
|
||||
</Box>
|
||||
)
|
||||
))) : null
|
||||
}
|
||||
</Box>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Divider />
|
||||
{
|
||||
description && (
|
||||
<Grid container>
|
||||
<Grid item xs={12}>
|
||||
<Box pb={2} px={2} display="flex" flexDirection={{xs: "column", sm: "row"}} mt="auto">
|
||||
<MDTypography variant="button" color="text" fontWeight="light">
|
||||
{parse(description)}
|
||||
</MDTypography>
|
||||
</Box>
|
||||
</Grid>
|
||||
</Grid>
|
||||
)
|
||||
}
|
||||
</Box>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
export default PieChartCard;
|
100
src/qqq/components/widgets/charts/piechart/PieChartConfigs.ts
Normal file
100
src/qqq/components/widgets/charts/piechart/PieChartConfigs.ts
Normal file
@ -0,0 +1,100 @@
|
||||
/*
|
||||
* 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 colors from "qqq/assets/theme/base/colors";
|
||||
|
||||
const {gradients, dark} = colors;
|
||||
|
||||
function configs(labels: any, datasets: any)
|
||||
{
|
||||
const backgroundColors = [];
|
||||
|
||||
if (datasets.backgroundColors)
|
||||
{
|
||||
datasets.backgroundColors.forEach((color: string) =>
|
||||
gradients[color]
|
||||
? backgroundColors.push(gradients[color].state)
|
||||
: backgroundColors.push(dark.main)
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
backgroundColors.push(dark.main);
|
||||
}
|
||||
|
||||
return {
|
||||
data: {
|
||||
labels,
|
||||
datasets: [
|
||||
{
|
||||
label: datasets.label,
|
||||
weight: 9,
|
||||
cutout: 0,
|
||||
tension: 0.9,
|
||||
pointRadius: 2,
|
||||
borderWidth: 2,
|
||||
backgroundColor: backgroundColors,
|
||||
fill: false,
|
||||
data: datasets.data,
|
||||
},
|
||||
],
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
},
|
||||
interaction: {
|
||||
intersect: false,
|
||||
mode: "index",
|
||||
},
|
||||
scales: {
|
||||
y: {
|
||||
grid: {
|
||||
drawBorder: false,
|
||||
display: false,
|
||||
drawOnChartArea: false,
|
||||
drawTicks: false,
|
||||
},
|
||||
ticks: {
|
||||
display: false,
|
||||
},
|
||||
},
|
||||
x: {
|
||||
grid: {
|
||||
drawBorder: false,
|
||||
display: false,
|
||||
drawOnChartArea: false,
|
||||
drawTicks: false,
|
||||
},
|
||||
ticks: {
|
||||
display: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export default configs;
|
73
src/qqq/components/widgets/components/DropdownMenu.tsx
Normal file
73
src/qqq/components/widgets/components/DropdownMenu.tsx
Normal file
@ -0,0 +1,73 @@
|
||||
/*
|
||||
* 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 {Theme} from "@mui/material";
|
||||
import Autocomplete from "@mui/material/Autocomplete";
|
||||
import TextField from "@mui/material/TextField";
|
||||
import {SxProps} from "@mui/system";
|
||||
import React from "react";
|
||||
|
||||
|
||||
export interface DropdownOption
|
||||
{
|
||||
id: string;
|
||||
label: string;
|
||||
}
|
||||
|
||||
/////////////////////////
|
||||
// inputs and defaults //
|
||||
/////////////////////////
|
||||
interface Props
|
||||
{
|
||||
label?: string;
|
||||
dropdownOptions?: DropdownOption[];
|
||||
onChangeCallback?: (dropdownLabel: string, data: any) => void;
|
||||
sx?: SxProps<Theme>;
|
||||
}
|
||||
|
||||
function DropdownMenu({label, dropdownOptions, onChangeCallback, sx}: Props): JSX.Element
|
||||
{
|
||||
const handleOnChange = (event: any, value: any, reason: string) =>
|
||||
{
|
||||
onChangeCallback(label, value);
|
||||
}
|
||||
|
||||
return (
|
||||
dropdownOptions ? (
|
||||
<span style={{whiteSpace: "nowrap"}}>
|
||||
<Autocomplete
|
||||
size="small"
|
||||
disablePortal
|
||||
id={`${label}-combo-box`}
|
||||
options={dropdownOptions}
|
||||
sx={{...sx, cursor: "pointer"}}
|
||||
onChange={handleOnChange}
|
||||
renderInput={(params: any) => <TextField {...params} label={label} />}
|
||||
renderOption={(props, option: DropdownOption) => (
|
||||
<li {...props} style={{whiteSpace: "normal"}}>{option.label}</li>
|
||||
)}
|
||||
/>
|
||||
</span>
|
||||
) : null
|
||||
)
|
||||
}
|
||||
|
||||
export default DropdownMenu;
|
32
src/qqq/components/widgets/misc/Divider.tsx
Normal file
32
src/qqq/components/widgets/misc/Divider.tsx
Normal file
@ -0,0 +1,32 @@
|
||||
/*
|
||||
* 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 Divider from "@mui/material/Divider";
|
||||
|
||||
|
||||
function DividerWidget(): JSX.Element
|
||||
{
|
||||
return (
|
||||
<Divider sx={{padding: "1px", background: "red"}}/>
|
||||
);
|
||||
}
|
||||
|
||||
export default DividerWidget;
|
108
src/qqq/components/widgets/misc/FieldValueListWidget.tsx
Normal file
108
src/qqq/components/widgets/misc/FieldValueListWidget.tsx
Normal file
@ -0,0 +1,108 @@
|
||||
/*
|
||||
* 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 {QFieldMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldMetaData";
|
||||
import {QRecord} from "@kingsrook/qqq-frontend-core/lib/model/QRecord";
|
||||
import {Skeleton} from "@mui/material";
|
||||
import Box from "@mui/material/Box";
|
||||
import Icon from "@mui/material/Icon";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import React from "react";
|
||||
import Widget from "qqq/components/widgets/Widget";
|
||||
import ValueUtils from "qqq/utils/qqq/ValueUtils";
|
||||
|
||||
interface Props
|
||||
{
|
||||
title: string;
|
||||
data: any;
|
||||
reloadWidgetCallback?: (params: string) => void;
|
||||
}
|
||||
|
||||
FieldValueListWidget.defaultProps = {};
|
||||
|
||||
function FieldValueListWidget({title, data, reloadWidgetCallback}: Props): JSX.Element
|
||||
{
|
||||
if(data?.dropdownNeedsSelectedText)
|
||||
{
|
||||
return (
|
||||
<Widget label={title} widgetData={data} reloadWidgetCallback={reloadWidgetCallback}>
|
||||
<br />
|
||||
</Widget>
|
||||
);
|
||||
}
|
||||
|
||||
if(!data.fields || !data.record)
|
||||
{
|
||||
const skeletons = [75, 50, 90];
|
||||
return (
|
||||
<Widget label={title}>
|
||||
<Box p={3} pt={0} display="flex" flexDirection="column">
|
||||
{skeletons.map((s) =>
|
||||
(
|
||||
<Box key={s} display="flex" flexDirection="row" pr={2}>
|
||||
<Typography variant="button" pr={2}>
|
||||
<Skeleton width={s + "px"} />
|
||||
</Typography>
|
||||
<Typography variant="button">
|
||||
<Skeleton width={2*(s + (100 - (1.25*s))) + "px"} />
|
||||
</Typography>
|
||||
</Box>
|
||||
))
|
||||
}
|
||||
</Box>
|
||||
</Widget>
|
||||
);
|
||||
}
|
||||
|
||||
const fields = data.fields.map((f: any) => new QFieldMetaData(f));
|
||||
const record = new QRecord(data.record);
|
||||
const fieldLabelPrefixIconNames = data.fieldLabelPrefixIconNames ?? {};
|
||||
const fieldLabelPrefixIconColors = data.fieldLabelPrefixIconColors ?? {};
|
||||
const fieldIndentLevels = data.fieldIndentLevels ?? {};
|
||||
|
||||
return (
|
||||
<Widget label={title} widgetData={data} reloadWidgetCallback={reloadWidgetCallback}>
|
||||
<Box p={3} pt={0} display="flex" flexDirection="column">
|
||||
{
|
||||
fields.map((field: QFieldMetaData, index: number) => (
|
||||
<Box key={field.label} flexDirection="row" pr={2} pl={3 * (fieldIndentLevels[field.name] ?? 0)}>
|
||||
{
|
||||
fieldLabelPrefixIconNames[field.name] &&
|
||||
<Icon color={fieldLabelPrefixIconColors[field.name] ?? "primary"} sx={{position: "relative", top: "4px", paddingRight: "8px", width: "24px"}}>{fieldLabelPrefixIconNames[field.name]}</Icon>
|
||||
}
|
||||
{
|
||||
field.label &&
|
||||
<Typography variant="button" fontWeight="bold" pr={1} sx={{textTransform: "none", color: "#344767"}}>
|
||||
{field.label}:
|
||||
</Typography>
|
||||
}
|
||||
<Typography variant="button" fontWeight="regular" color="text" sx={{textTransform: "none", color: "#7b809a", fontWeight: 400}}>
|
||||
{ValueUtils.getDisplayValue(field, record, "view")}
|
||||
</Typography>
|
||||
</Box>
|
||||
))
|
||||
}
|
||||
</Box>
|
||||
</Widget>
|
||||
);
|
||||
}
|
||||
|
||||
export default FieldValueListWidget;
|
57
src/qqq/components/widgets/misc/QuickSightChart.tsx
Normal file
57
src/qqq/components/widgets/misc/QuickSightChart.tsx
Normal file
@ -0,0 +1,57 @@
|
||||
/*
|
||||
* 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 Box from "@mui/material/Box";
|
||||
import Card from "@mui/material/Card";
|
||||
import React from "react";
|
||||
import MDTypography from "qqq/components/legacy/MDTypography";
|
||||
|
||||
interface Props
|
||||
{
|
||||
label: string;
|
||||
url: string;
|
||||
}
|
||||
|
||||
interface IframeProps
|
||||
{
|
||||
iframe: string;
|
||||
}
|
||||
|
||||
function Iframe({iframe}: IframeProps)
|
||||
{
|
||||
return (<div dangerouslySetInnerHTML={{__html: iframe || ""}} />);
|
||||
}
|
||||
|
||||
function QuickSightChart({label, url}: Props): JSX.Element
|
||||
{
|
||||
const iframe = `<iframe style='border: 0 solid #04aaef; height: 411px; width: 99%' title=${label} src=${url} />`;
|
||||
|
||||
return (
|
||||
<Card sx={{height: "100%"}}>
|
||||
<Box padding="1rem">
|
||||
<MDTypography variant="h5">{label}</MDTypography>
|
||||
<Iframe iframe={iframe} />
|
||||
</Box>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
export default QuickSightChart;
|
162
src/qqq/components/widgets/misc/RecordGridWidget.tsx
Normal file
162
src/qqq/components/widgets/misc/RecordGridWidget.tsx
Normal file
@ -0,0 +1,162 @@
|
||||
/*
|
||||
* 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 {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData";
|
||||
import {QRecord} from "@kingsrook/qqq-frontend-core/lib/model/QRecord";
|
||||
import {DataGridPro, GridCallbackDetails, GridRowParams, MuiEvent} from "@mui/x-data-grid-pro";
|
||||
import React, {useEffect, useState} from "react";
|
||||
import {useNavigate} from "react-router-dom";
|
||||
import Widget, {AddNewRecordButton, HeaderLink, LabelComponent} from "qqq/components/widgets/Widget";
|
||||
import DataGridUtils from "qqq/utils/DataGridUtils";
|
||||
import Client from "qqq/utils/qqq/Client";
|
||||
|
||||
interface Props
|
||||
{
|
||||
title: string;
|
||||
data: any;
|
||||
}
|
||||
|
||||
RecordGridWidget.defaultProps = {};
|
||||
|
||||
const qController = Client.getInstance();
|
||||
|
||||
function RecordGridWidget({title, data}: Props): JSX.Element
|
||||
{
|
||||
const [rows, setRows] = useState([]);
|
||||
const [columns, setColumns] = useState([]);
|
||||
const navigate = useNavigate();
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if (data && data.childTableMetaData && data.queryOutput)
|
||||
{
|
||||
const records: QRecord[] = [];
|
||||
const queryOutputRecords = data.queryOutput.records;
|
||||
if (queryOutputRecords)
|
||||
{
|
||||
for (let i = 0; i < queryOutputRecords.length; i++)
|
||||
{
|
||||
records.push(new QRecord(queryOutputRecords[i]));
|
||||
}
|
||||
}
|
||||
|
||||
const tableMetaData = new QTableMetaData(data.childTableMetaData);
|
||||
const {rows, columnsToRender} = DataGridUtils.makeRows(records, tableMetaData);
|
||||
|
||||
const childTablePath = data.tablePath + (data.tablePath.endsWith("/") ? "" : "/")
|
||||
const columns = DataGridUtils.setupGridColumns(tableMetaData, columnsToRender, childTablePath);
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
// do not not show the foreign-key column of the parent table //
|
||||
////////////////////////////////////////////////////////////////
|
||||
if(data.defaultValuesForNewChildRecords)
|
||||
{
|
||||
for (let i = 0; i < columns.length; i++)
|
||||
{
|
||||
if(data.defaultValuesForNewChildRecords[columns[i].field])
|
||||
{
|
||||
columns.splice(i, 1);
|
||||
i--
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setRows(rows);
|
||||
setColumns(columns);
|
||||
}
|
||||
}, [data]);
|
||||
|
||||
const labelAdditionalComponentsLeft: LabelComponent[] = []
|
||||
if(data && data.viewAllLink)
|
||||
{
|
||||
labelAdditionalComponentsLeft.push(new HeaderLink("View All", data.viewAllLink));
|
||||
}
|
||||
|
||||
const labelAdditionalComponentsRight: LabelComponent[] = []
|
||||
if(data && data.canAddChildRecord)
|
||||
{
|
||||
let disabledFields = data.disabledFieldsForNewChildRecords;
|
||||
if(!disabledFields)
|
||||
{
|
||||
disabledFields = data.defaultValuesForNewChildRecords;
|
||||
}
|
||||
labelAdditionalComponentsRight.push(new AddNewRecordButton(data.childTableMetaData, data.defaultValuesForNewChildRecords, "Add new", disabledFields))
|
||||
}
|
||||
|
||||
|
||||
const handleRowClick = (params: GridRowParams, event: MuiEvent<React.MouseEvent>, details: GridCallbackDetails) =>
|
||||
{
|
||||
(async () =>
|
||||
{
|
||||
const qInstance = await qController.loadMetaData()
|
||||
const tablePath = qInstance.getTablePathByName(data.childTableMetaData.name)
|
||||
if(tablePath)
|
||||
{
|
||||
navigate(`${tablePath}/${params.id}`);
|
||||
}
|
||||
})();
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<Widget
|
||||
label={title}
|
||||
labelAdditionalComponentsLeft={labelAdditionalComponentsLeft}
|
||||
labelAdditionalComponentsRight={labelAdditionalComponentsRight}
|
||||
>
|
||||
<DataGridPro
|
||||
autoHeight
|
||||
rows={rows}
|
||||
disableSelectionOnClick
|
||||
columns={columns}
|
||||
rowBuffer={10}
|
||||
getRowClassName={(params) => (params.indexRelativeToCurrentPage % 2 === 0 ? "even" : "odd")}
|
||||
onRowClick={handleRowClick}
|
||||
// getRowHeight={() => "auto"} // maybe nice? wraps values in cells...
|
||||
// components={{Toolbar: CustomToolbar, Pagination: CustomPagination, LoadingOverlay: Loading}}
|
||||
// pinnedColumns={pinnedColumns}
|
||||
// onPinnedColumnsChange={handlePinnedColumnsChange}
|
||||
// pagination
|
||||
// paginationMode="server"
|
||||
// sortingMode="server"
|
||||
// filterMode="server"
|
||||
// page={pageNumber}
|
||||
// checkboxSelection
|
||||
// rowCount={totalRecords === null ? 0 : totalRecords}
|
||||
// onPageSizeChange={handleRowsPerPageChange}
|
||||
// onStateChange={handleStateChange}
|
||||
// density={density}
|
||||
// loading={loading}
|
||||
// filterModel={filterModel}
|
||||
// onFilterModelChange={handleFilterChange}
|
||||
// columnVisibilityModel={columnVisibilityModel}
|
||||
// onColumnVisibilityModelChange={handleColumnVisibilityChange}
|
||||
// onColumnOrderChange={handleColumnOrderChange}
|
||||
// onSelectionModelChange={selectionChanged}
|
||||
// onSortModelChange={handleSortChange}
|
||||
// sortingOrder={[ "asc", "desc" ]}
|
||||
// sortModel={columnSortModel}
|
||||
/>
|
||||
</Widget>
|
||||
);
|
||||
}
|
||||
|
||||
export default RecordGridWidget;
|
153
src/qqq/components/widgets/misc/StepperCard.tsx
Normal file
153
src/qqq/components/widgets/misc/StepperCard.tsx
Normal file
@ -0,0 +1,153 @@
|
||||
/*
|
||||
* 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 {Check, Pending, RocketLaunch} from "@mui/icons-material";
|
||||
import {Icon, Skeleton, StepConnector} from "@mui/material";
|
||||
import Box from "@mui/material/Box";
|
||||
import Step from "@mui/material/Step";
|
||||
import StepLabel from "@mui/material/StepLabel";
|
||||
import Stepper from "@mui/material/Stepper";
|
||||
import {withStyles} from "@mui/styles";
|
||||
import React from "react";
|
||||
import {NavLink} from "react-router-dom";
|
||||
|
||||
|
||||
/////////////////////////////////////////////
|
||||
// structure of expected stepper card data //
|
||||
/////////////////////////////////////////////
|
||||
export interface StepperCardData
|
||||
{
|
||||
title: string;
|
||||
activeStep: number;
|
||||
steps: {
|
||||
label: string;
|
||||
linkText: string;
|
||||
linkURL: string;
|
||||
iconOverride: string;
|
||||
colorOverride: string;
|
||||
}[];
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////
|
||||
// define properties and defaults //
|
||||
////////////////////////////////////
|
||||
interface Props
|
||||
{
|
||||
data: StepperCardData;
|
||||
}
|
||||
|
||||
|
||||
function StepperCard({data}: Props): JSX.Element
|
||||
{
|
||||
const activeStep = data && data.activeStep ? data.activeStep : 0;
|
||||
|
||||
const CustomizedConnector = withStyles({
|
||||
line: {
|
||||
color: "#344767",
|
||||
marginTop: "9px",
|
||||
marginRight: "30px",
|
||||
marginLeft: "30px",
|
||||
}
|
||||
})(StepConnector);
|
||||
|
||||
// console.log(`data ${JSON.stringify(data)}`);
|
||||
|
||||
return (
|
||||
<Stepper connector={<CustomizedConnector />} activeStep={activeStep} alternativeLabel sx={{paddingBottom: "0px", boxShadow: "none", background: "white"}}>
|
||||
{
|
||||
data && data.steps ? (
|
||||
data.steps.map((step, index) => (
|
||||
<Step key={step.label}>
|
||||
{
|
||||
index < activeStep && (
|
||||
<Box>
|
||||
<StepLabel icon={step.iconOverride ? <Icon>{step.iconOverride}</Icon> : <Check />} sx={{
|
||||
color: step.colorOverride ?? "green",
|
||||
fontSize: "35px",
|
||||
"& .MuiStepLabel-label.Mui-completed.MuiStepLabel-alternativeLabel":
|
||||
{
|
||||
color: `${step.colorOverride ?? "green"} !important`,
|
||||
}
|
||||
}}>{step.label}</StepLabel>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
{
|
||||
index > activeStep && (
|
||||
<Box>
|
||||
<StepLabel icon={step.iconOverride ? <Icon>{step.iconOverride}</Icon> : <Pending />} sx={{
|
||||
color: step.colorOverride ?? "#ced4da",
|
||||
fontSize: "35px",
|
||||
"& .MuiStepLabel-label.MuiStepLabel-alternativeLabel":
|
||||
{
|
||||
color: `${step.colorOverride ?? "#ced4da"} !important`,
|
||||
}
|
||||
}}>{step.label}</StepLabel>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
{
|
||||
index === activeStep && (
|
||||
<Box>
|
||||
<StepLabel icon={step.iconOverride ? <Icon>{step.iconOverride}</Icon> : <RocketLaunch />} sx={{
|
||||
color: step.colorOverride ?? "#04aaef",
|
||||
fontSize: "35px",
|
||||
"& .MuiStepLabel-label.MuiStepLabel-alternativeLabel":
|
||||
{
|
||||
color: `${step.colorOverride ?? "#344767"} !important`,
|
||||
}
|
||||
}}>{step.label}</StepLabel>
|
||||
{
|
||||
step.linkURL && (
|
||||
<Box sx={{textAlign: "center", fontSize: "14px"}}>
|
||||
<NavLink to={step.linkURL}>{step.linkText}</NavLink>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
</Step>
|
||||
))
|
||||
) : (
|
||||
|
||||
Array(5).fill(0).map((_, i) =>
|
||||
<Step key={`step-${i}`}>
|
||||
<Box>
|
||||
<StepLabel icon={<Pending />} sx={{
|
||||
color: "#ced4da",
|
||||
fontSize: "35px",
|
||||
"& .MuiStepLabel-label.MuiStepLabel-alternativeLabel":
|
||||
{
|
||||
color: "#ced4da !important",
|
||||
}
|
||||
}}><Skeleton /></StepLabel>
|
||||
</Box>
|
||||
</Step>
|
||||
)
|
||||
)
|
||||
}
|
||||
</Stepper>
|
||||
);
|
||||
}
|
||||
|
||||
export default StepperCard;
|
144
src/qqq/components/widgets/misc/USMapWidget.tsx
Normal file
144
src/qqq/components/widgets/misc/USMapWidget.tsx
Normal file
@ -0,0 +1,144 @@
|
||||
/*
|
||||
* 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 Client from "qqq/utils/qqq/Client";
|
||||
|
||||
|
||||
////////////////////////////////////////////////
|
||||
// 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 = Client.getInstance();
|
||||
function USMapWidget(props: Props, ): JSX.Element
|
||||
{
|
||||
const [qInstance, setQInstance] = useState(null as QInstance);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
(async () =>
|
||||
{
|
||||
const newQInstance = await qController.loadMetaData();
|
||||
setQInstance(newQInstance);
|
||||
})();
|
||||
}, []);
|
||||
|
||||
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;
|
158
src/qqq/components/widgets/statistics/MiniStatisticsCard.tsx
Normal file
158
src/qqq/components/widgets/statistics/MiniStatisticsCard.tsx
Normal file
@ -0,0 +1,158 @@
|
||||
/*
|
||||
* 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 Box from "@mui/material/Box";
|
||||
import Card from "@mui/material/Card";
|
||||
import Grid from "@mui/material/Grid";
|
||||
import Icon from "@mui/material/Icon";
|
||||
import {ReactNode} from "react";
|
||||
import {useMaterialUIController} from "qqq/context";
|
||||
import MDTypography from "qqq/components/legacy/MDTypography";
|
||||
|
||||
|
||||
// Decalaring props types for MiniStatisticsCard
|
||||
interface Props {
|
||||
bgColor?: "white" | "primary" | "secondary" | "info" | "success" | "warning" | "error" | "dark";
|
||||
title?: {
|
||||
fontWeight?: "light" | "regular" | "medium" | "bold";
|
||||
text?: string;
|
||||
};
|
||||
count: string | number;
|
||||
percentage?: {
|
||||
color: "primary" | "secondary" | "info" | "success" | "warning" | "error" | "dark" | "white";
|
||||
text: string | number;
|
||||
};
|
||||
icon: {
|
||||
color: "primary" | "secondary" | "info" | "success" | "warning" | "error" | "dark";
|
||||
component: ReactNode;
|
||||
};
|
||||
direction?: "right" | "left";
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
function MiniStatisticsCard({
|
||||
bgColor,
|
||||
title,
|
||||
count,
|
||||
percentage,
|
||||
icon,
|
||||
direction,
|
||||
}: Props): JSX.Element
|
||||
{
|
||||
const [controller] = useMaterialUIController();
|
||||
const {darkMode} = controller;
|
||||
|
||||
return (
|
||||
<Card sx={{overflow: "hidden"}}>
|
||||
<Box
|
||||
sx={({palette: {background}}: { palette: any }) => ({
|
||||
background: darkMode && background.card,
|
||||
backgroundColor: bgColor
|
||||
})}
|
||||
>
|
||||
<Box p={2}>
|
||||
<Grid container alignItems="center">
|
||||
{direction === "left" ? (
|
||||
<Grid item xs={4}>
|
||||
<Box
|
||||
color={bgColor === "white" ? "white" : "dark"}
|
||||
width="4rem"
|
||||
height="4rem"
|
||||
borderRadius="md"
|
||||
display="flex"
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
sx={{backgroundColor: bgColor === "white" ? icon.color : "white"}}
|
||||
>
|
||||
<Icon fontSize="medium" color="inherit">
|
||||
{icon.component}
|
||||
</Icon>
|
||||
</Box>
|
||||
</Grid>
|
||||
) : null}
|
||||
<Grid item xs={8}>
|
||||
<Box
|
||||
ml={direction === "left" ? 2 : 0}
|
||||
lineHeight={1}
|
||||
textAlign={direction === "left" ? "right" : "left"}
|
||||
>
|
||||
<MDTypography
|
||||
variant="button"
|
||||
color={bgColor === "white" ? "text" : "white"}
|
||||
opacity={bgColor === "white" ? 1 : 0.7}
|
||||
textTransform="capitalize"
|
||||
fontWeight={title.fontWeight}
|
||||
>
|
||||
{title.text}
|
||||
</MDTypography>
|
||||
<MDTypography
|
||||
variant="h5"
|
||||
fontWeight="bold"
|
||||
color={bgColor === "white" ? "dark" : "white"}
|
||||
>
|
||||
{count}{" "}
|
||||
<MDTypography variant="button" color={percentage.color} fontWeight="bold">
|
||||
{percentage.text}
|
||||
</MDTypography>
|
||||
</MDTypography>
|
||||
</Box>
|
||||
</Grid>
|
||||
{direction === "right" ? (
|
||||
<Grid item xs={4}>
|
||||
<Box
|
||||
color={bgColor === "white" ? "white" : "dark"}
|
||||
width="4rem"
|
||||
height="4rem"
|
||||
marginLeft="auto"
|
||||
borderRadius="md"
|
||||
display="flex"
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
sx={{backgroundColor: bgColor === "white" ? icon.color : "white"}}
|
||||
>
|
||||
<Icon fontSize="medium" color="inherit">
|
||||
{icon.component}
|
||||
</Icon>
|
||||
</Box>
|
||||
</Grid>
|
||||
) : null}
|
||||
</Grid>
|
||||
</Box>
|
||||
</Box>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
// Declaring default props for MiniStatisticsCard
|
||||
MiniStatisticsCard.defaultProps = {
|
||||
bgColor: "white",
|
||||
title: {
|
||||
fontWeight: "light",
|
||||
text: "",
|
||||
},
|
||||
percentage: {
|
||||
color: "success",
|
||||
text: "",
|
||||
},
|
||||
direction: "right",
|
||||
};
|
||||
|
||||
export default MiniStatisticsCard;
|
153
src/qqq/components/widgets/statistics/MultiStatisticsCard.tsx
Normal file
153
src/qqq/components/widgets/statistics/MultiStatisticsCard.tsx
Normal file
@ -0,0 +1,153 @@
|
||||
/*
|
||||
* 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 {Skeleton} from "@mui/material";
|
||||
import Box from "@mui/material/Box";
|
||||
import Card from "@mui/material/Card";
|
||||
import Divider from "@mui/material/Divider";
|
||||
import Grid from "@mui/material/Grid";
|
||||
import Icon from "@mui/material/Icon";
|
||||
import React from "react";
|
||||
import {NavLink} from "react-router-dom";
|
||||
import MDTypography from "qqq/components/legacy/MDTypography";
|
||||
|
||||
/////////////////////////////////////
|
||||
// structure of location card data //
|
||||
/////////////////////////////////////
|
||||
export interface MultiStatisticsCardData
|
||||
{
|
||||
imageUrl: string;
|
||||
title: string;
|
||||
statisticsGroupData: {
|
||||
icon: string;
|
||||
iconColor: string;
|
||||
header: string;
|
||||
subheader: string;
|
||||
statisticList: {
|
||||
label: string;
|
||||
value: number;
|
||||
url?: string;
|
||||
}[]
|
||||
|
||||
}[];
|
||||
}
|
||||
|
||||
|
||||
/////////////////////////
|
||||
// inputs and defaults //
|
||||
/////////////////////////
|
||||
interface Props
|
||||
{
|
||||
title: string;
|
||||
data: MultiStatisticsCardData;
|
||||
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
function MultiStatisticsCard({title, data}: Props): JSX.Element
|
||||
{
|
||||
return (
|
||||
<Card sx={{alignItems: "stretch", flexGrow: 1, display: "flex", marginTop: "0px", paddingTop: "0px"}}>
|
||||
|
||||
<Grid container>
|
||||
<Grid item xs={12}>
|
||||
<Box pt={3} px={3}>
|
||||
<MDTypography variant="h5" fontWeight="medium">
|
||||
{title}
|
||||
</MDTypography>
|
||||
</Box>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid container>
|
||||
{
|
||||
data && data.statisticsGroupData ? (
|
||||
data.statisticsGroupData.map((statisticsGroup, i1) =>
|
||||
<Grid key={`statgroup-${i1}`} item xs={3} lg={3} sx={{textAlign: "center"}}>
|
||||
<Box p={3} pt={3} sx={{alignItems: "center"}}>
|
||||
{
|
||||
statisticsGroup.icon && (
|
||||
<Box>
|
||||
<MDTypography variant="h6">
|
||||
<Icon sx={{fontSize: "30px", margin: "5px", color: statisticsGroup.iconColor}} fontSize="medium">{statisticsGroup.icon}</Icon>
|
||||
</MDTypography>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
<Box>
|
||||
<MDTypography variant="h6">
|
||||
{statisticsGroup.header}
|
||||
</MDTypography>
|
||||
<MDTypography variant="subtitle2">
|
||||
{statisticsGroup.subheader}
|
||||
</MDTypography>
|
||||
</Box>
|
||||
<Divider sx={{margin: "10px"}}></Divider>
|
||||
<Box sx={{alignItems: "center"}}>
|
||||
{
|
||||
statisticsGroup.statisticList.map((stat, i2) =>
|
||||
<Box key={`stat-${i1}-${i2}`}>
|
||||
<MDTypography variant="subtitle2">
|
||||
{stat.label}: <NavLink to={stat.url}>{stat.value.toLocaleString()}</NavLink>
|
||||
</MDTypography>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
</Box>
|
||||
</Box>
|
||||
</Grid>
|
||||
)
|
||||
) : (
|
||||
Array(4).fill(0).map((_, i) =>
|
||||
<Grid key={`item-${i}`} item xs={3} lg={3} sx={{textAlign: "center"}}>
|
||||
<Box p={3} pt={3} sx={{alignItems: "center"}}>
|
||||
<Box>
|
||||
<MDTypography variant="h6">
|
||||
<Icon sx={{fontSize: "30px", margin: "5px", color: "grey"}} fontSize="medium">pending</Icon>
|
||||
</MDTypography>
|
||||
</Box>
|
||||
<Box>
|
||||
<MDTypography variant="h6">
|
||||
<Skeleton />
|
||||
</MDTypography>
|
||||
<MDTypography variant="subtitle2">
|
||||
<Skeleton />
|
||||
</MDTypography>
|
||||
</Box>
|
||||
<Divider sx={{margin: "10px"}}></Divider>
|
||||
<Box sx={{alignItems: "center"}}>
|
||||
<Box key={`stat-${i}`}>
|
||||
<MDTypography variant="subtitle2">
|
||||
<Skeleton />
|
||||
</MDTypography>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
</Grid>
|
||||
)
|
||||
)
|
||||
}
|
||||
</Grid>
|
||||
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
export default MultiStatisticsCard;
|
149
src/qqq/components/widgets/statistics/SimpleStatisticsCard.tsx
Normal file
149
src/qqq/components/widgets/statistics/SimpleStatisticsCard.tsx
Normal file
@ -0,0 +1,149 @@
|
||||
/*
|
||||
* 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 Box from "@mui/material/Box";
|
||||
import Card from "@mui/material/Card";
|
||||
import Grid from "@mui/material/Grid";
|
||||
import {ReactNode} from "react";
|
||||
import MDTypography from "qqq/components/legacy/MDTypography";
|
||||
import {StatisticsCardData} from "qqq/components/widgets/statistics/StatisticsCard";
|
||||
|
||||
interface Props
|
||||
{
|
||||
title: string;
|
||||
data: StatisticsCardData;
|
||||
increaseIsGood: boolean;
|
||||
isCurrency?: boolean;
|
||||
dropdown?: {
|
||||
action: (...args: any) => void;
|
||||
menu: ReactNode;
|
||||
value: string;
|
||||
};
|
||||
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
function SimpleStatisticsCard({title, data, increaseIsGood, isCurrency, dropdown}: Props): JSX.Element
|
||||
{
|
||||
const {count, percentageAmount, percentageLabel} = data;
|
||||
|
||||
let percentageString = "";
|
||||
if (percentageAmount)
|
||||
{
|
||||
percentageString = percentageAmount.toLocaleString() + "%";
|
||||
if (percentageAmount > 0)
|
||||
{
|
||||
percentageString = "+" + percentageString;
|
||||
}
|
||||
}
|
||||
|
||||
let percentColor: string;
|
||||
if (increaseIsGood)
|
||||
{
|
||||
percentColor = (percentageAmount > 0) ? "success" : "warning";
|
||||
}
|
||||
else
|
||||
{
|
||||
percentColor = (percentageAmount < 0) ? "success" : "warning";
|
||||
}
|
||||
|
||||
return (
|
||||
<Card sx={{height: "fit-content", alignItems: "stretch", flexGrow: 1, display: "flex", marginTop: "0px", paddingTop: "0px"}}>
|
||||
<Box p={2}>
|
||||
<Grid container>
|
||||
<Grid item xs={12}>
|
||||
<Box mb={0.5} lineHeight={1}>
|
||||
<MDTypography
|
||||
variant="button"
|
||||
fontWeight="medium"
|
||||
color="text"
|
||||
textTransform="capitalize"
|
||||
>
|
||||
{title}
|
||||
</MDTypography>
|
||||
</Box>
|
||||
<Box lineHeight={1}>
|
||||
{
|
||||
count !== undefined ? (
|
||||
isCurrency ? (
|
||||
<MDTypography variant="h5" fontWeight="bold">
|
||||
{count.toLocaleString("en-US", {style: "currency", currency: "USD"})}
|
||||
</MDTypography>
|
||||
) : (
|
||||
|
||||
<MDTypography variant="h5" fontWeight="bold">
|
||||
{count.toLocaleString()}
|
||||
</MDTypography>
|
||||
)
|
||||
) : null
|
||||
}
|
||||
{
|
||||
count !== undefined ? (
|
||||
<MDTypography variant="button" fontWeight="bold" color={percentColor}>
|
||||
{percentageString}
|
||||
<MDTypography
|
||||
variant="button"
|
||||
fontWeight="regular"
|
||||
color={"secondary"}
|
||||
>
|
||||
{percentageLabel}
|
||||
</MDTypography>
|
||||
</MDTypography>
|
||||
):(
|
||||
<MDTypography variant="button" fontWeight="regular">
|
||||
<i>Loading.</i>
|
||||
</MDTypography>
|
||||
)
|
||||
}
|
||||
</Box>
|
||||
</Grid>
|
||||
{dropdown && (
|
||||
<Grid item xs={5}>
|
||||
<Box width="100%" textAlign="right" lineHeight={1}>
|
||||
<MDTypography
|
||||
variant="caption"
|
||||
color="secondary"
|
||||
fontWeight="regular"
|
||||
sx={{cursor: "pointer"}}
|
||||
onClick={dropdown.action}
|
||||
>
|
||||
{dropdown.value}
|
||||
</MDTypography>
|
||||
{dropdown.menu}
|
||||
</Box>
|
||||
</Grid>
|
||||
)}
|
||||
</Grid>
|
||||
</Box>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
SimpleStatisticsCard.defaultProps = {
|
||||
percentage: {
|
||||
color: "success",
|
||||
value: "",
|
||||
label: "",
|
||||
},
|
||||
dropdown: false,
|
||||
};
|
||||
|
||||
export default SimpleStatisticsCard;
|
143
src/qqq/components/widgets/statistics/StatisticsCard.tsx
Normal file
143
src/qqq/components/widgets/statistics/StatisticsCard.tsx
Normal file
@ -0,0 +1,143 @@
|
||||
/*
|
||||
* 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 Box from "@mui/material/Box";
|
||||
import Card from "@mui/material/Card";
|
||||
import Divider from "@mui/material/Divider";
|
||||
import Icon from "@mui/material/Icon";
|
||||
import {ReactNode} from "react";
|
||||
import MDTypography from "qqq/components/legacy/MDTypography";
|
||||
|
||||
///////////////////////////////////////////
|
||||
// structure of expected stats card data //
|
||||
///////////////////////////////////////////
|
||||
export interface StatisticsCardData
|
||||
{
|
||||
title: string;
|
||||
count: number;
|
||||
percentageAmount: number;
|
||||
percentageLabel: string;
|
||||
}
|
||||
|
||||
/////////////////////////
|
||||
// inputs and defaults //
|
||||
/////////////////////////
|
||||
interface Props
|
||||
{
|
||||
data: StatisticsCardData;
|
||||
color?: "primary" | "secondary" | "info" | "success" | "warning" | "error" | "light" | "dark";
|
||||
icon: ReactNode;
|
||||
increaseIsGood: boolean;
|
||||
dropdown?: {
|
||||
action: (...args: any) => void;
|
||||
menu: ReactNode;
|
||||
value: string;
|
||||
};
|
||||
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
StatisticsCard.defaultProps = {
|
||||
color: "info",
|
||||
increaseIsGood: true
|
||||
};
|
||||
|
||||
function StatisticsCard({data, color, icon, increaseIsGood}: Props): JSX.Element
|
||||
{
|
||||
const {title, count, percentageAmount, percentageLabel} = data;
|
||||
|
||||
let percentageString = "";
|
||||
if (percentageAmount)
|
||||
{
|
||||
percentageString = percentageAmount.toLocaleString() + "%";
|
||||
if (percentageAmount > 0)
|
||||
{
|
||||
percentageString = "+" + percentageString;
|
||||
}
|
||||
}
|
||||
|
||||
let percentColor = "dark";
|
||||
if (percentageAmount !== 0)
|
||||
{
|
||||
if (increaseIsGood)
|
||||
{
|
||||
percentColor = (percentageAmount > 0) ? "success" : "warning";
|
||||
}
|
||||
else
|
||||
{
|
||||
percentColor = (percentageAmount < 0) ? "success" : "warning";
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
<Card sx={{height: "100%", alignItems: "stretch", flexGrow: 1, display: "flex", marginTop: 3, paddingTop: "0px"}}>
|
||||
<Box display="flex" justifyContent="space-between" pt={1} px={2}>
|
||||
<Box
|
||||
color="#ffffff"
|
||||
borderRadius="xl"
|
||||
display="flex"
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
width="4rem"
|
||||
height="4rem"
|
||||
mt={-3}
|
||||
sx={{borderRadius: "10px", backgroundColor: color}}
|
||||
>
|
||||
<Icon fontSize="medium" color="inherit">
|
||||
{icon}
|
||||
</Icon>
|
||||
</Box>
|
||||
<Box textAlign="right" lineHeight={1.25}>
|
||||
<MDTypography variant="button" fontWeight="light" color="text">
|
||||
{title}
|
||||
</MDTypography>
|
||||
{
|
||||
count !== undefined ? (
|
||||
<MDTypography variant="h4">{count.toLocaleString()}</MDTypography>
|
||||
) : null
|
||||
}
|
||||
</Box>
|
||||
</Box>
|
||||
{
|
||||
percentageAmount !== undefined && percentageAmount !== 0 ? (
|
||||
<Box px={2}>
|
||||
<Divider />
|
||||
<MDTypography component="p" variant="button" color="text" display="flex">
|
||||
<MDTypography
|
||||
component="span"
|
||||
variant="button"
|
||||
fontWeight="bold"
|
||||
color={percentColor}
|
||||
>
|
||||
{percentageString}
|
||||
</MDTypography>
|
||||
{percentageLabel}
|
||||
</MDTypography>
|
||||
</Box>
|
||||
) : null
|
||||
}
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
export default StatisticsCard;
|
378
src/qqq/components/widgets/tables/DataTable.tsx
Normal file
378
src/qqq/components/widgets/tables/DataTable.tsx
Normal file
@ -0,0 +1,378 @@
|
||||
/* 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 {tooltipClasses, TooltipProps} from "@mui/material";
|
||||
import Autocomplete from "@mui/material/Autocomplete";
|
||||
import Box from "@mui/material/Box";
|
||||
import Icon from "@mui/material/Icon";
|
||||
import {styled} from "@mui/material/styles";
|
||||
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 Tooltip from "@mui/material/Tooltip";
|
||||
import parse from "html-react-parser";
|
||||
import {useEffect, useMemo, useState} from "react";
|
||||
import {useAsyncDebounce, useGlobalFilter, usePagination, useSortBy, useTable} from "react-table";
|
||||
import MDInput from "qqq/components/legacy/MDInput";
|
||||
import MDPagination from "qqq/components/legacy/MDPagination";
|
||||
import MDTypography from "qqq/components/legacy/MDTypography";
|
||||
import DataTableBodyCell from "qqq/components/widgets/tables/cells/DataTableBodyCell";
|
||||
import DataTableHeadCell from "qqq/components/widgets/tables/cells/DataTableHeadCell";
|
||||
import DefaultCell from "qqq/components/widgets/tables/cells/DefaultCell";
|
||||
import ImageCell from "qqq/components/widgets/tables/cells/ImageCell";
|
||||
import {TableDataInput} from "qqq/components/widgets/tables/TableCard";
|
||||
|
||||
interface Props
|
||||
{
|
||||
entriesPerPage?:
|
||||
| false
|
||||
| {
|
||||
defaultValue: number;
|
||||
entries: number[];
|
||||
};
|
||||
canSearch?: boolean;
|
||||
showTotalEntries?: boolean;
|
||||
table: TableDataInput;
|
||||
pagination?: {
|
||||
variant: "contained" | "gradient";
|
||||
color: "primary" | "secondary" | "info" | "success" | "warning" | "error" | "dark" | "light";
|
||||
};
|
||||
isSorted?: boolean;
|
||||
noEndBorder?: boolean;
|
||||
}
|
||||
|
||||
const NoMaxWidthTooltip = styled(({className, ...props}: TooltipProps) => (
|
||||
<Tooltip {...props} classes={{popper: className}} />
|
||||
))({
|
||||
[`& .${tooltipClasses.tooltip}`]: {
|
||||
maxWidth: "none",
|
||||
textAlign: "left"
|
||||
},
|
||||
});
|
||||
|
||||
function DataTable({
|
||||
entriesPerPage,
|
||||
canSearch,
|
||||
showTotalEntries,
|
||||
table,
|
||||
pagination,
|
||||
isSorted,
|
||||
noEndBorder,
|
||||
}: Props): JSX.Element
|
||||
{
|
||||
let defaultValue: any;
|
||||
let entries: any[];
|
||||
|
||||
if (entriesPerPage)
|
||||
{
|
||||
defaultValue = entriesPerPage.defaultValue ? entriesPerPage.defaultValue : "10";
|
||||
entries = entriesPerPage.entries ? entriesPerPage.entries : ["10", "25", "50", "100"];
|
||||
}
|
||||
|
||||
const columns = useMemo<any>(() => table.columns, [table]);
|
||||
const data = useMemo<any>(() => table.rows, [table]);
|
||||
|
||||
if (!columns || !data)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
const tableInstance = useTable(
|
||||
{columns, data, initialState: {pageIndex: 0}},
|
||||
useGlobalFilter,
|
||||
useSortBy,
|
||||
usePagination
|
||||
);
|
||||
|
||||
const {
|
||||
getTableProps,
|
||||
getTableBodyProps,
|
||||
headerGroups,
|
||||
prepareRow,
|
||||
rows,
|
||||
page,
|
||||
pageOptions,
|
||||
canPreviousPage,
|
||||
canNextPage,
|
||||
gotoPage,
|
||||
nextPage,
|
||||
previousPage,
|
||||
setPageSize,
|
||||
setGlobalFilter,
|
||||
state: {pageIndex, pageSize, globalFilter},
|
||||
}: any = tableInstance;
|
||||
|
||||
// Set the default value for the entries per page when component mounts
|
||||
useEffect(() => setPageSize(defaultValue || 10), [defaultValue]);
|
||||
|
||||
// Set the entries per page value based on the select value
|
||||
const setEntriesPerPage = (value: any) => setPageSize(value);
|
||||
|
||||
// Render the paginations
|
||||
const renderPagination = pageOptions.map((option: any) => (
|
||||
<MDPagination
|
||||
item
|
||||
key={option}
|
||||
onClick={() => gotoPage(Number(option))}
|
||||
active={pageIndex === option}
|
||||
>
|
||||
{option + 1}
|
||||
</MDPagination>
|
||||
));
|
||||
|
||||
// Handler for the input to set the pagination index
|
||||
const handleInputPagination = ({target: {value}}: any) =>
|
||||
value > pageOptions.length || value < 0 ? gotoPage(0) : gotoPage(Number(value));
|
||||
|
||||
// Customized page options starting from 1
|
||||
const customizedPageOptions = pageOptions.map((option: any) => option + 1);
|
||||
|
||||
// Setting value for the pagination input
|
||||
const handleInputPaginationValue = ({target: value}: any) => gotoPage(Number(value.value - 1));
|
||||
|
||||
// Search input value state
|
||||
const [search, setSearch] = useState(globalFilter);
|
||||
|
||||
// Search input state handle
|
||||
const onSearchChange = useAsyncDebounce((value) =>
|
||||
{
|
||||
setGlobalFilter(value || undefined);
|
||||
}, 100);
|
||||
|
||||
// A function that sets the sorted value for the table
|
||||
const setSortedValue = (column: any) =>
|
||||
{
|
||||
let sortedValue;
|
||||
|
||||
if (isSorted && column.isSorted)
|
||||
{
|
||||
sortedValue = column.isSortedDesc ? "desc" : "asce";
|
||||
}
|
||||
else if (isSorted)
|
||||
{
|
||||
sortedValue = "none";
|
||||
}
|
||||
else
|
||||
{
|
||||
sortedValue = false;
|
||||
}
|
||||
|
||||
return sortedValue;
|
||||
};
|
||||
|
||||
// Setting the entries starting point
|
||||
const entriesStart = pageIndex === 0 ? pageIndex + 1 : pageIndex * pageSize + 1;
|
||||
|
||||
// Setting the entries ending point
|
||||
let entriesEnd;
|
||||
|
||||
if (pageIndex === 0)
|
||||
{
|
||||
entriesEnd = pageSize;
|
||||
}
|
||||
else if (pageIndex === pageOptions.length - 1)
|
||||
{
|
||||
entriesEnd = rows.length;
|
||||
}
|
||||
else
|
||||
{
|
||||
entriesEnd = pageSize * (pageIndex + 1);
|
||||
}
|
||||
|
||||
return (
|
||||
<TableContainer sx={{boxShadow: "none"}}>
|
||||
{entriesPerPage || canSearch ? (
|
||||
<Box display="flex" justifyContent="space-between" alignItems="center" p={3}>
|
||||
{entriesPerPage && (
|
||||
<Box display="flex" alignItems="center">
|
||||
<Autocomplete
|
||||
disableClearable
|
||||
value={pageSize.toString()}
|
||||
options={entries}
|
||||
onChange={(event, newValues) =>
|
||||
{
|
||||
setEntriesPerPage(parseInt(newValues[0], 10));
|
||||
}}
|
||||
size="small"
|
||||
sx={{width: "5rem"}}
|
||||
renderInput={(params) => <MDInput {...params} />}
|
||||
/>
|
||||
<MDTypography variant="caption" color="secondary">
|
||||
entries per page
|
||||
</MDTypography>
|
||||
</Box>
|
||||
)}
|
||||
{canSearch && (
|
||||
<Box width="12rem" ml="auto">
|
||||
<MDInput
|
||||
placeholder="Search..."
|
||||
value={search}
|
||||
size="small"
|
||||
fullWidth
|
||||
onChange={({currentTarget}: any) =>
|
||||
{
|
||||
setSearch(search);
|
||||
onSearchChange(currentTarget.value);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
) : null}
|
||||
<Table {...getTableProps()}>
|
||||
<Box component="thead">
|
||||
{headerGroups.map((headerGroup: any, i: number) => (
|
||||
<TableRow key={i} {...headerGroup.getHeaderGroupProps()}>
|
||||
{headerGroup.headers.map((column: any) => (
|
||||
column.type !== "hidden" && (
|
||||
<DataTableHeadCell
|
||||
key={i++}
|
||||
{...column.getHeaderProps(isSorted && column.getSortByToggleProps())}
|
||||
width={column.width ? column.width : "auto"}
|
||||
align={column.align ? column.align : "left"}
|
||||
sorted={setSortedValue(column)}
|
||||
>
|
||||
{column.render("header")}
|
||||
</DataTableHeadCell>
|
||||
)
|
||||
))}
|
||||
</TableRow>
|
||||
))}
|
||||
</Box>
|
||||
<TableBody {...getTableBodyProps()}>
|
||||
{page.map((row: any, key: any) =>
|
||||
{
|
||||
prepareRow(row);
|
||||
return (
|
||||
<TableRow sx={{verticalAlign: "top"}} key={key} {...row.getRowProps()}>
|
||||
{row.cells.map((cell: any) => (
|
||||
cell.column.type !== "hidden" && (
|
||||
<DataTableBodyCell
|
||||
key={key}
|
||||
noBorder={noEndBorder && rows.length - 1 === key}
|
||||
align={cell.column.align ? cell.column.align : "left"}
|
||||
{...cell.getCellProps()}
|
||||
>
|
||||
{
|
||||
cell.column.type === "default" && (
|
||||
cell.value && "number" === typeof cell.value ? (
|
||||
<DefaultCell>{cell.value.toLocaleString()}</DefaultCell>
|
||||
) : (<DefaultCell>{cell.render("Cell")}</DefaultCell>)
|
||||
)
|
||||
}
|
||||
{
|
||||
cell.column.type === "htmlAndTooltip" && (
|
||||
<DefaultCell>
|
||||
|
||||
<NoMaxWidthTooltip title={parse(row.values["tooltip"])}>
|
||||
<Box>
|
||||
{parse(cell.value)}
|
||||
</Box>
|
||||
</NoMaxWidthTooltip>
|
||||
</DefaultCell>
|
||||
)
|
||||
}
|
||||
{
|
||||
cell.column.type === "html" && (
|
||||
<DefaultCell>{parse(cell.value)}</DefaultCell>
|
||||
)
|
||||
}
|
||||
{
|
||||
cell.column.type === "image" && row.values["imageTotal"] && (
|
||||
<ImageCell imageUrl={row.values["imageUrl"]} label={row.values["imageLabel"]} total={row.values["imageTotal"].toLocaleString()} totalType={row.values["imageTotalType"]} />
|
||||
)
|
||||
}
|
||||
{
|
||||
cell.column.type === "image" && !row.values["imageTotal"] && (
|
||||
<ImageCell imageUrl={row.values["imageUrl"]} label={row.values["imageLabel"]} />
|
||||
)
|
||||
}
|
||||
</DataTableBodyCell>
|
||||
)
|
||||
))}
|
||||
</TableRow>
|
||||
);
|
||||
})}
|
||||
</TableBody>
|
||||
</Table>
|
||||
|
||||
<Box
|
||||
display="flex"
|
||||
flexDirection={{xs: "column", sm: "row"}}
|
||||
justifyContent="space-between"
|
||||
alignItems={{xs: "flex-start", sm: "center"}}
|
||||
p={!showTotalEntries && pageOptions.length === 1 ? 0 : 3}
|
||||
>
|
||||
{showTotalEntries && (
|
||||
<Box mb={{xs: 3, sm: 0}}>
|
||||
<MDTypography variant="button" color="secondary" fontWeight="regular">
|
||||
Showing {entriesStart} to {entriesEnd} of {rows.length} entries
|
||||
</MDTypography>
|
||||
</Box>
|
||||
)}
|
||||
{pageOptions.length > 1 && (
|
||||
<MDPagination
|
||||
variant={pagination.variant ? pagination.variant : "gradient"}
|
||||
color={pagination.color ? pagination.color : "info"}
|
||||
>
|
||||
{canPreviousPage && (
|
||||
<MDPagination item onClick={() => previousPage()}>
|
||||
<Icon sx={{fontWeight: "bold"}}>chevron_left</Icon>
|
||||
</MDPagination>
|
||||
)}
|
||||
{renderPagination.length > 6 ? (
|
||||
<Box width="5rem" mx={1}>
|
||||
<MDInput
|
||||
inputProps={{type: "number", min: 1, max: customizedPageOptions.length}}
|
||||
value={customizedPageOptions[pageIndex]}
|
||||
onChange={(event: any) =>
|
||||
{
|
||||
handleInputPagination(event);
|
||||
handleInputPaginationValue(event);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
) : (
|
||||
renderPagination
|
||||
)}
|
||||
{canNextPage && (
|
||||
<MDPagination item onClick={() => nextPage()}>
|
||||
<Icon sx={{fontWeight: "bold"}}>chevron_right</Icon>
|
||||
</MDPagination>
|
||||
)}
|
||||
</MDPagination>
|
||||
)}
|
||||
</Box>
|
||||
</TableContainer>
|
||||
);
|
||||
}
|
||||
|
||||
// Declaring default props for DataTable
|
||||
DataTable.defaultProps = {
|
||||
entriesPerPage: {defaultValue: 10, entries: ["5", "10", "15", "20", "25"]},
|
||||
canSearch: false,
|
||||
showTotalEntries: true,
|
||||
pagination: {variant: "gradient", color: "info"},
|
||||
isSorted: true,
|
||||
noEndBorder: false,
|
||||
};
|
||||
|
||||
export default DataTable;
|
135
src/qqq/components/widgets/tables/TableCard.tsx
Normal file
135
src/qqq/components/widgets/tables/TableCard.tsx
Normal file
@ -0,0 +1,135 @@
|
||||
/*
|
||||
* 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 {Skeleton} from "@mui/material";
|
||||
import Box from "@mui/material/Box";
|
||||
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 MDTypography from "qqq/components/legacy/MDTypography";
|
||||
import DataTableBodyCell from "qqq/components/widgets/tables/cells/DataTableBodyCell";
|
||||
import DataTableHeadCell from "qqq/components/widgets/tables/cells/DataTableHeadCell";
|
||||
import DefaultCell from "qqq/components/widgets/tables/cells/DefaultCell";
|
||||
import DataTable from "qqq/components/widgets/tables/DataTable";
|
||||
import Client from "qqq/utils/qqq/Client";
|
||||
|
||||
|
||||
//////////////////////////////////////
|
||||
// structure of expected table data //
|
||||
//////////////////////////////////////
|
||||
export interface TableDataInput
|
||||
{
|
||||
columns: { [key: string]: any }[];
|
||||
rows: { [key: string]: any }[];
|
||||
}
|
||||
|
||||
|
||||
/////////////////////////
|
||||
// inputs and defaults //
|
||||
/////////////////////////
|
||||
interface Props
|
||||
{
|
||||
title: string;
|
||||
linkText?: string;
|
||||
linkURL?: string;
|
||||
noRowsFoundHTML?: string;
|
||||
data: TableDataInput;
|
||||
reloadWidgetCallback?: (params: string) => void;
|
||||
widgetIndex?: number;
|
||||
isChild?: boolean;
|
||||
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
const qController = Client.getInstance();
|
||||
function TableCard({noRowsFoundHTML, data, reloadWidgetCallback}: Props): JSX.Element
|
||||
{
|
||||
const [qInstance, setQInstance] = useState(null as QInstance);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
(async () =>
|
||||
{
|
||||
const newQInstance = await qController.loadMetaData();
|
||||
setQInstance(newQInstance);
|
||||
})();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Box py={1}>
|
||||
{
|
||||
data && data.columns && !noRowsFoundHTML ?
|
||||
<DataTable
|
||||
table={data}
|
||||
entriesPerPage={false}
|
||||
showTotalEntries={false}
|
||||
isSorted={false}
|
||||
noEndBorder
|
||||
/>
|
||||
: noRowsFoundHTML ?
|
||||
<Box p={3} pt={1} pb={1} sx={{textAlign: "center"}}>
|
||||
<MDTypography
|
||||
variant="subtitle2"
|
||||
color="secondary"
|
||||
fontWeight="regular"
|
||||
>
|
||||
{
|
||||
noRowsFoundHTML ? (
|
||||
parse(noRowsFoundHTML)
|
||||
) : "No rows found"
|
||||
}
|
||||
</MDTypography>
|
||||
</Box>
|
||||
:
|
||||
<TableContainer sx={{boxShadow: "none"}}>
|
||||
<Table>
|
||||
<Box component="thead">
|
||||
<TableRow key="header">
|
||||
{Array(8).fill(0).map((_, i) =>
|
||||
<DataTableHeadCell key={`head-${i}`} sorted={false} width="auto" align="center">
|
||||
<Skeleton width="100%" />
|
||||
</DataTableHeadCell>
|
||||
)}
|
||||
</TableRow>
|
||||
</Box>
|
||||
<TableBody>
|
||||
{Array(5).fill(0).map((_, i) =>
|
||||
<TableRow sx={{verticalAlign: "top"}} key={`row-${i}`}>
|
||||
{Array(8).fill(0).map((_, j) =>
|
||||
<DataTableBodyCell key={`cell-${i}-${j}`} align="center">
|
||||
<DefaultCell><Skeleton /></DefaultCell>
|
||||
</DataTableBodyCell>
|
||||
)}
|
||||
</TableRow>
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default TableCard;
|
@ -0,0 +1,64 @@
|
||||
/*
|
||||
* 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 Box from "@mui/material/Box";
|
||||
import {Theme} from "@mui/material/styles";
|
||||
import {ReactNode} from "react";
|
||||
|
||||
// Declaring prop types for DataTableBodyCell
|
||||
interface Props
|
||||
{
|
||||
children: ReactNode;
|
||||
noBorder?: boolean;
|
||||
align?: "left" | "right" | "center";
|
||||
}
|
||||
|
||||
function DataTableBodyCell({noBorder, align, children}: Props): JSX.Element
|
||||
{
|
||||
return (
|
||||
<Box
|
||||
component="td"
|
||||
textAlign={align}
|
||||
py={1.5}
|
||||
px={3}
|
||||
sx={({palette: {light}, typography: {size}, borders: {borderWidth}}: Theme) => ({
|
||||
fontSize: size.sm,
|
||||
borderBottom: noBorder ? "none" : `${borderWidth[1]} solid ${light.main}`,
|
||||
})}
|
||||
>
|
||||
<Box
|
||||
display="initial"
|
||||
width="max-content"
|
||||
color="text"
|
||||
>
|
||||
{children}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
// Declaring default props for DataTableBodyCell
|
||||
DataTableBodyCell.defaultProps = {
|
||||
noBorder: false,
|
||||
align: "left",
|
||||
};
|
||||
|
||||
export default DataTableBodyCell;
|
110
src/qqq/components/widgets/tables/cells/DataTableHeadCell.tsx
Normal file
110
src/qqq/components/widgets/tables/cells/DataTableHeadCell.tsx
Normal file
@ -0,0 +1,110 @@
|
||||
/*
|
||||
* 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 Box from "@mui/material/Box";
|
||||
import Icon from "@mui/material/Icon";
|
||||
import {Theme} from "@mui/material/styles";
|
||||
import {ReactNode} from "react";
|
||||
import {useMaterialUIController} from "qqq/context";
|
||||
|
||||
// Declaring props types for DataTableHeadCell
|
||||
interface Props
|
||||
{
|
||||
width?: string | number;
|
||||
children: ReactNode;
|
||||
sorted?: false | "none" | "asce" | "desc";
|
||||
align?: "left" | "right" | "center";
|
||||
}
|
||||
|
||||
function DataTableHeadCell({width, children, sorted, align, ...rest}: Props): JSX.Element
|
||||
{
|
||||
const [controller] = useMaterialUIController();
|
||||
const {darkMode} = controller;
|
||||
|
||||
return (
|
||||
<Box
|
||||
component="th"
|
||||
width={width}
|
||||
py={1.5}
|
||||
px={3}
|
||||
sx={({palette: {light}, borders: {borderWidth}}: Theme) => ({
|
||||
borderBottom: `${borderWidth[1]} solid ${light.main}`,
|
||||
})}
|
||||
>
|
||||
<Box
|
||||
{...rest}
|
||||
sx={({typography: {size, fontWeightBold}}: Theme) => ({
|
||||
position: "relative",
|
||||
opacity: "0.7",
|
||||
textAlign: align,
|
||||
fontSize: size.xxs,
|
||||
fontWeight: fontWeightBold,
|
||||
textTransform: "uppercase",
|
||||
cursor: sorted && "pointer",
|
||||
userSelect: sorted && "none",
|
||||
})}
|
||||
>
|
||||
{children}
|
||||
{sorted && (
|
||||
<Box
|
||||
position="absolute"
|
||||
top={0}
|
||||
right={align !== "right" ? "16px" : 0}
|
||||
left={align === "right" ? "-5px" : "unset"}
|
||||
sx={({typography: {size}}: any) => ({
|
||||
fontSize: size.lg,
|
||||
})}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
position: "absolute",
|
||||
top: -6,
|
||||
color: sorted === "asce" ? "text" : "secondary",
|
||||
opacity: sorted === "asce" ? 1 : 0.5
|
||||
}}
|
||||
>
|
||||
<Icon>arrow_drop_up</Icon>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
color: sorted === "desc" ? "text" : "secondary",
|
||||
opacity: sorted === "desc" ? 1 : 0.5
|
||||
}}
|
||||
>
|
||||
<Icon>arrow_drop_down</Icon>
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
// Declaring default props for DataTableHeadCell
|
||||
DataTableHeadCell.defaultProps = {
|
||||
width: "auto",
|
||||
sorted: "none",
|
||||
align: "left",
|
||||
};
|
||||
|
||||
export default DataTableHeadCell;
|
28
src/qqq/components/widgets/tables/cells/DefaultCell.tsx
Normal file
28
src/qqq/components/widgets/tables/cells/DefaultCell.tsx
Normal file
@ -0,0 +1,28 @@
|
||||
/**
|
||||
=========================================================
|
||||
* Material Dashboard 2 PRO React TS - v1.0.0
|
||||
=========================================================
|
||||
|
||||
* Product Page: https://www.creative-tim.com/product/material-dashboard-2-pro-react-ts
|
||||
* Copyright 2022 Creative Tim (https://www.creative-tim.com)
|
||||
|
||||
Coded by www.creative-tim.com
|
||||
|
||||
=========================================================
|
||||
|
||||
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
*/
|
||||
|
||||
import {ReactNode} from "react";
|
||||
import MDTypography from "qqq/components/legacy/MDTypography";
|
||||
|
||||
function DefaultCell({children}: { children: ReactNode }): JSX.Element
|
||||
{
|
||||
return (
|
||||
<MDTypography variant="button" fontWeight="regular" color="text">
|
||||
{children}
|
||||
</MDTypography>
|
||||
);
|
||||
}
|
||||
|
||||
export default DefaultCell;
|
56
src/qqq/components/widgets/tables/cells/ImageCell.tsx
Normal file
56
src/qqq/components/widgets/tables/cells/ImageCell.tsx
Normal file
@ -0,0 +1,56 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
// Declaring props types for ProductCell
|
||||
import Box from "@mui/material/Box";
|
||||
import MDTypography from "qqq/components/legacy/MDTypography";
|
||||
|
||||
interface Props
|
||||
{
|
||||
imageUrl: string;
|
||||
label: string;
|
||||
total?: string | number;
|
||||
totalType?: string;
|
||||
}
|
||||
|
||||
function ImageCell({imageUrl, label, total, totalType}: Props): JSX.Element
|
||||
{
|
||||
return (
|
||||
<Box display="flex" alignItems="center" pr={2}>
|
||||
<Box mr={2}>
|
||||
<img src={imageUrl} alt={label} />
|
||||
</Box>
|
||||
<Box display="flex" flexDirection="column">
|
||||
<MDTypography variant="button" fontWeight="medium">
|
||||
{label}
|
||||
</MDTypography>
|
||||
<MDTypography variant="button" fontWeight="regular" color="secondary">
|
||||
<MDTypography component="span" variant="button" fontWeight="regular" color="success">
|
||||
{total}
|
||||
</MDTypography>{" "}
|
||||
{totalType}
|
||||
</MDTypography>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default ImageCell;
|
Reference in New Issue
Block a user