/* 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 . */ import {QWidgetMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QWidgetMetaData"; import {QRecord} from "@kingsrook/qqq-frontend-core/lib/model/QRecord"; import {Alert, Skeleton} from "@mui/material"; import Box from "@mui/material/Box"; import Grid from "@mui/material/Grid"; import Tab from "@mui/material/Tab"; import Tabs from "@mui/material/Tabs"; import parse from "html-react-parser"; import QContext from "QContext"; import MDTypography from "qqq/components/legacy/MDTypography"; import TabPanel from "qqq/components/misc/TabPanel"; 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 PieChart from "qqq/components/widgets/charts/piechart/PieChart"; import StackedBarChart from "qqq/components/widgets/charts/StackedBarChart"; import CompositeWidget from "qqq/components/widgets/CompositeWidget"; import DataBagViewer from "qqq/components/widgets/misc/DataBagViewer"; import DividerWidget from "qqq/components/widgets/misc/Divider"; import DynamicFormWidget from "qqq/components/widgets/misc/DynamicFormWidget"; import FieldValueListWidget from "qqq/components/widgets/misc/FieldValueListWidget"; import PivotTableSetupWidget from "qqq/components/widgets/misc/PivotTableSetupWidget"; import QuickSightChart from "qqq/components/widgets/misc/QuickSightChart"; import RecordGridWidget from "qqq/components/widgets/misc/RecordGridWidget"; import ReportSetupWidget from "qqq/components/widgets/misc/ReportSetupWidget"; import ScriptViewer from "qqq/components/widgets/misc/ScriptViewer"; 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 StatisticsCard from "qqq/components/widgets/statistics/StatisticsCard"; import Widget, {HeaderIcon, LabelComponent, WIDGET_DROPDOWN_SELECTION_LOCAL_STORAGE_KEY_ROOT, WidgetData} from "qqq/components/widgets/Widget"; import WidgetBlock from "qqq/components/widgets/WidgetBlock"; import ProcessRun from "qqq/pages/processes/ProcessRun"; import Client from "qqq/utils/qqq/Client"; import React, {useContext, useEffect, useReducer, useState} from "react"; import TableWidget from "./tables/TableWidget"; const qController = Client.getInstance(); interface Props { widgetMetaDataList: QWidgetMetaData[]; tableName?: string; entityPrimaryKey?: string; record?: QRecord; omitWrappingGridContainer: boolean; areChildren?: boolean; childUrlParams?: string; parentWidgetMetaData?: QWidgetMetaData; wrapWidgetsInTabPanels: boolean; } DashboardWidgets.defaultProps = { widgetMetaDataList: null, tableName: null, entityPrimaryKey: null, omitWrappingGridContainer: false, areChildren: false, childUrlParams: "", parentWidgetMetaData: null, wrapWidgetsInTabPanels: false, }; function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, record, omitWrappingGridContainer, areChildren, childUrlParams, parentWidgetMetaData, wrapWidgetsInTabPanels}: Props): JSX.Element { const [widgetData, setWidgetData] = useState([] as any[]); const [widgetCounter, setWidgetCounter] = useState(0); const [, forceUpdate] = useReducer((x) => x + 1, 0); const [currentUrlParams, setCurrentUrlParams] = useState(null as string); const [haveLoadedParams, setHaveLoadedParams] = useState(false); const {accentColor} = useContext(QContext); let initialSelectedTab = 0; let selectedTabKey: string = null; if (parentWidgetMetaData && wrapWidgetsInTabPanels) { selectedTabKey = `qqq.widgets.selectedTabs.${parentWidgetMetaData.name}`; if (localStorage.getItem(selectedTabKey)) { initialSelectedTab = Number(localStorage.getItem(selectedTabKey)); } } const [selectedTab, setSelectedTab] = useState(initialSelectedTab); const changeTab = (newValue: number) => { setSelectedTab(newValue); localStorage.setItem(selectedTabKey, String(newValue)); }; useEffect(() => { setWidgetData([]); for (let i = 0; i < widgetMetaDataList.length; i++) { const widgetMetaData = widgetMetaDataList[i]; const urlParams = getQueryParams(widgetMetaData, null); setCurrentUrlParams(urlParams); setHaveLoadedParams(true); widgetData[i] = {}; (async () => { try { widgetData[i] = await qController.widget(widgetMetaData.name, urlParams); setWidgetData(widgetData); setWidgetCounter(widgetCounter + 1); if (widgetData[i]) { widgetData[i]["errorLoading"] = false; } } catch (e) { console.error(e); if (widgetData[i]) { widgetData[i]["errorLoading"] = true; } } forceUpdate(); })(); } }, [widgetMetaDataList]); const reloadWidget = async (index: number, data: string) => { (async () => { const urlParams = getQueryParams(widgetMetaDataList[index], data); setCurrentUrlParams(urlParams); widgetData[index] = {}; try { widgetData[index] = await qController.widget(widgetMetaDataList[index].name, urlParams); setWidgetCounter(widgetCounter + 1); setWidgetData(widgetData); if (widgetData[index]) { widgetData[index]["errorLoading"] = false; } } catch (e) { console.error(e); if (widgetData[index]) { widgetData[index]["errorLoading"] = true; } } forceUpdate(); })(); }; function getQueryParams(widgetMetaData: QWidgetMetaData, extraParams: string): string { let paramMap = new Map(); ///////////////////////////////////////////////////////////////////////////// // see if local storage is used for any widget dropdowns, if so, look them // // up and append to the query string // ///////////////////////////////////////////////////////////////////////////// let thisWidgetHasDropdowns = widgetMetaData && widgetMetaData.storeDropdownSelections && widgetMetaData.dropdowns; let parentWidgetHasDropdowns = parentWidgetMetaData && parentWidgetMetaData.storeDropdownSelections && parentWidgetMetaData.dropdowns; if (thisWidgetHasDropdowns || parentWidgetHasDropdowns) { const metaDataToUse = (thisWidgetHasDropdowns) ? widgetMetaData : parentWidgetMetaData; for (let i = 0; i < metaDataToUse.dropdowns.length; i++) { const dropdownName = metaDataToUse.dropdowns[i].possibleValueSourceName ?? metaDataToUse.dropdowns[i].name; const localStorageKey = `${WIDGET_DROPDOWN_SELECTION_LOCAL_STORAGE_KEY_ROOT}.${metaDataToUse.name}.${dropdownName}`; const json = JSON.parse(localStorage.getItem(localStorageKey)); if (json) { paramMap.set(dropdownName, json.id); } } } if (entityPrimaryKey) { paramMap.set("id", entityPrimaryKey); } if (tableName) { paramMap.set("tableName", tableName); } if (extraParams) { let pairs = extraParams.split("&"); for (let i = 0; i < pairs.length; i++) { let nameValue = pairs[i].split("="); if (nameValue.length == 2) { paramMap.set(nameValue[0], nameValue[1]); } } } if (childUrlParams) { let pairs = childUrlParams.split("&"); for (let i = 0; i < pairs.length; i++) { let nameValue = pairs[i].split("="); if (nameValue.length == 2) { paramMap.set(nameValue[0], nameValue[1]); } } } let paramsFromMap = ""; for (let key of paramMap.keys()) { paramsFromMap += `${key}=${paramMap.get(key)}&`; } return paramsFromMap; } const widgetCount = widgetMetaDataList ? widgetMetaDataList.length : 0; /******************************************************************************* ** helper function, to convert values from a QRecord values map to a regular old ** js object *******************************************************************************/ function convertQRecordValuesFromMapToObject(record: QRecord): { [name: string]: any } { const rs: { [name: string]: any } = {}; if (record && record.values) { record.values.forEach((value, key) => rs[key] = value); } return (rs); } const renderWidget = (widgetMetaData: QWidgetMetaData, i: number): JSX.Element => { const labelAdditionalComponentsRight: LabelComponent[] = []; if (widgetMetaData && widgetMetaData.icons) { const topRightInsideCardIcon = widgetMetaData.icons.get("topRightInsideCard"); if (topRightInsideCardIcon) { labelAdditionalComponentsRight.push(new HeaderIcon(topRightInsideCardIcon.name, topRightInsideCardIcon.path, topRightInsideCardIcon.color, "topRightInsideCard")); } } const labelAdditionalComponentsLeft: LabelComponent[] = []; if (widgetMetaData && widgetMetaData.icons) { const topLeftInsideCardIcon = widgetMetaData.icons.get("topLeftInsideCard"); if (topLeftInsideCardIcon) { labelAdditionalComponentsLeft.push(new HeaderIcon(topLeftInsideCardIcon.name, topLeftInsideCardIcon.path, topLeftInsideCardIcon.color, "topLeftInsideCard")); } } return ( { haveLoadedParams && widgetMetaData.type === "parentWidget" && ( reloadWidget(i, data)} storeDropdownSelections={widgetMetaData.storeDropdownSelections} /> ) } { widgetMetaData.type === "alert" && widgetData[i]?.html && ( reloadWidget(i, data)} isChild={areChildren} labelAdditionalComponentsRight={labelAdditionalComponentsRight} labelAdditionalComponentsLeft={labelAdditionalComponentsLeft} > {parse(widgetData[i]?.html)} ) } { widgetMetaData.type === "usaMap" && ( ) } { widgetMetaData.type === "table" && ( reloadWidget(i, data)} isChild={areChildren} /> ) } { widgetMetaData.type === "multiTable" && ( widgetData[i]?.tableDataList?.map((tableData: WidgetData, index: number) => reloadWidget(i, data)} isChild={areChildren} /> ) ) } { widgetMetaData.type === "stackedBarChart" && ( reloadWidget(i, data)} isChild={areChildren} labelAdditionalComponentsRight={labelAdditionalComponentsRight} labelAdditionalComponentsLeft={labelAdditionalComponentsLeft} > ) } { widgetMetaData.type === "process" && widgetData[i]?.processMetaData && ( reloadWidget(i, data)} showReloadControl={false} labelAdditionalComponentsRight={labelAdditionalComponentsRight} labelAdditionalComponentsLeft={labelAdditionalComponentsLeft} >
) } { widgetMetaData.type === "stepper" && ( reloadWidget(i, data)} labelAdditionalComponentsRight={labelAdditionalComponentsRight} labelAdditionalComponentsLeft={labelAdditionalComponentsLeft} > ) } { widgetMetaData.type === "html" && ( reloadWidget(i, data)} widgetData={widgetData[i]} labelAdditionalComponentsRight={labelAdditionalComponentsRight} labelAdditionalComponentsLeft={labelAdditionalComponentsLeft} > { widgetData && widgetData[i] && widgetData[i].html ? ( parse(widgetData[i].html) ) : } ) } { widgetMetaData.type === "smallLineChart" && ( ) } { widgetMetaData.type === "statistics" && ( reloadWidget(i, data)} labelAdditionalComponentsRight={labelAdditionalComponentsRight} labelAdditionalComponentsLeft={labelAdditionalComponentsLeft} > ) } { widgetMetaData.type === "multiStatistics" && ( ) } { widgetMetaData.type === "quickSightChart" && ( ) } { widgetMetaData.type === "barChart" && ( ) } { widgetMetaData.type === "pieChart" && ( reloadWidget(i, data)} isChild={areChildren} labelAdditionalComponentsRight={labelAdditionalComponentsRight} labelAdditionalComponentsLeft={labelAdditionalComponentsLeft} >
) } { widgetMetaData.type === "divider" && ( ) } { widgetMetaData.type === "horizontalBarChart" && ( ) } { widgetMetaData.type === "lineChart" && ( reloadWidget(i, data)} isChild={areChildren} labelAdditionalComponentsRight={labelAdditionalComponentsRight} labelAdditionalComponentsLeft={labelAdditionalComponentsLeft} > ) } { widgetMetaData.type === "childRecordList" && ( widgetData && widgetData[i] && ) } { widgetMetaData.type === "fieldValueList" && ( widgetData && widgetData[i] && reloadWidget(i, data)} /> ) } { widgetMetaData.type === "composite" && ( reloadWidget(i, data)} isChild={areChildren} labelAdditionalComponentsRight={labelAdditionalComponentsRight} labelAdditionalComponentsLeft={labelAdditionalComponentsLeft} > ) } { widgetMetaData.type === "block" && ( reloadWidget(i, data)} isChild={areChildren} labelAdditionalComponentsRight={labelAdditionalComponentsRight} labelAdditionalComponentsLeft={labelAdditionalComponentsLeft} > ) } { widgetMetaData.type === "dataBagViewer" && ( widgetData && widgetData[i] && widgetData[i].queryParams && ) } { widgetMetaData.type === "scriptViewer" && ( widgetData && widgetData[i] && widgetData[i].queryParams && ) } { widgetMetaData.type === "reportSetup" && ( widgetData && widgetData[i] && widgetData[i].queryParams && { }} /> ) } { widgetMetaData.type === "pivotTableSetup" && ( widgetData && widgetData[i] && widgetData[i].queryParams && { }} /> ) } { widgetMetaData.type === "dynamicForm" && ( widgetData && widgetData[i] && ) }
); }; if (wrapWidgetsInTabPanels) { omitWrappingGridContainer = true; } const body: JSX.Element = ( <> { widgetMetaDataList.map((widgetMetaData, i) => { let renderedWidget = widgetMetaData ? renderWidget(widgetMetaData, i) : (<>); if (!omitWrappingGridContainer) { // @ts-ignore renderedWidget = ( {renderedWidget} ); } if (wrapWidgetsInTabPanels) { renderedWidget = ( {renderedWidget} ); } return ({renderedWidget}); }) } ); const tabs = widgetMetaDataList && wrapWidgetsInTabPanels ? changeTab(newValue)} variant="standard" > {widgetMetaDataList.map((widgetMetaData, i) => ( ))} : <>; return ( widgetCount > 0 ? ( <> {tabs} { omitWrappingGridContainer ? body : ( {body} ) } ) : null ); } export default DashboardWidgets;