SPRINT-18: fixed to dashboards, removed and moved around all the things

This commit is contained in:
Tim Chamberlain
2023-01-04 11:40:21 -06:00
parent e49f178738
commit 267580b44b
460 changed files with 9717 additions and 11057 deletions

View 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;

View 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;

View 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;

View 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;

View File

@ -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;

View File

@ -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[];
}[];
}

View File

@ -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[];
};
}

View 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;

View 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;

View File

@ -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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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}&nbsp;
<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;

View 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>
&nbsp;{percentageLabel}
</MDTypography>
</Box>
) : null
}
</Card>
);
}
export default StatisticsCard;

View 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">
&nbsp;&nbsp;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;

View 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;

View File

@ -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;

View 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;

View 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;

View 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;