CE-1240: updated composite widget to have flex column ability, support for 'multi table' widget,

This commit is contained in:
Tim Chamberlain
2024-05-03 20:26:36 -05:00
parent 1859dd603d
commit f925ad9116
6 changed files with 69 additions and 46 deletions

View File

@ -22,16 +22,16 @@
import {QWidgetMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QWidgetMetaData";
import {Box, Skeleton} from "@mui/material";
import React from "react";
import {BlockData} from "qqq/components/widgets/blocks/BlockModels";
import WidgetBlock from "qqq/components/widgets/WidgetBlock";
import React from "react";
interface CompositeData
{
blocks: BlockData[];
styleOverrides?: any;
layout?: string
layout?: string;
}
@ -57,7 +57,14 @@ export default function CompositeWidget({widgetMetaData, data}: CompositeWidgetP
////////////////////////////////////////////////////////////////////////////////////
let layout = data?.layout;
let boxStyle: any = {};
if (layout == "FLEX_ROW_WRAPPED")
if (layout == "FLEX_COLUMN")
{
boxStyle.display = "flex";
boxStyle.flexDirection = "column";
boxStyle.flexWrap = "wrap";
boxStyle.gap = "0.5rem";
}
else if (layout == "FLEX_ROW_WRAPPED")
{
boxStyle.display = "flex";
boxStyle.flexDirection = "row";
@ -68,7 +75,7 @@ export default function CompositeWidget({widgetMetaData, data}: CompositeWidgetP
{
boxStyle.display = "flex";
boxStyle.flexDirection = "row";
boxStyle.justifyContent = "space-between"
boxStyle.justifyContent = "space-between";
boxStyle.gap = "0.25rem";
}
else if (layout == "TABLE_SUB_ROW_DETAILS")

View File

@ -50,7 +50,7 @@ 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} from "qqq/components/widgets/Widget";
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";
@ -258,11 +258,11 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, reco
** helper function, to convert values from a QRecord values map to a regular old
** js object
*******************************************************************************/
function convertQRecordValuesFromMapToObject(record: QRecord): {[name: string]: any}
function convertQRecordValuesFromMapToObject(record: QRecord): { [name: string]: any }
{
const rs: {[name: string]: any} = {};
const rs: { [name: string]: any } = {};
if(record && record.values)
if (record && record.values)
{
record.values.forEach((value, key) => rs[key] = value);
}
@ -293,7 +293,7 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, reco
}
return (
<Box key={`${widgetMetaData.name}-${i}`} sx={{alignItems: "stretch", flexGrow: 1, display: "flex", marginTop: "0px", paddingTop: "0px", width: "100%", height: "100%"}}>
<Box key={`${widgetMetaData.name}-${i}`} sx={{alignItems: "stretch", flexGrow: 1, display: "flex", marginTop: "0px", paddingTop: "0px", width: "100%", height: "100%", flexDirection: widgetMetaData.type == "multiTable" ? "column" : "row"}}>
{
haveLoadedParams && widgetMetaData.type === "parentWidget" && (
<ParentWidget
@ -343,6 +343,20 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, reco
/>
)
}
{
widgetMetaData.type === "multiTable" && (
widgetData[i]?.tableDataList?.map((tableData: WidgetData, index: number) =>
<Box pb={3} key={`${widgetMetaData.type}-${index}`}>
<TableWidget
widgetMetaData={widgetMetaData}
widgetData={tableData}
reloadWidgetCallback={(data) => reloadWidget(i, data)}
isChild={areChildren}
/>
</Box>
)
)
}
{
widgetMetaData.type === "stackedBarChart" && (
<Widget
@ -587,14 +601,16 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, reco
widgetMetaData.type === "reportSetup" && (
widgetData && widgetData[i] && widgetData[i].queryParams &&
<ReportSetupWidget isEditable={false} widgetMetaData={widgetMetaData} recordValues={convertQRecordValuesFromMapToObject(record)} onSaveCallback={() =>
{}} />
{
}} />
)
}
{
widgetMetaData.type === "pivotTableSetup" && (
widgetData && widgetData[i] && widgetData[i].queryParams &&
<PivotTableSetupWidget isEditable={false} widgetMetaData={widgetMetaData} recordValues={convertQRecordValuesFromMapToObject(record)} onSaveCallback={() =>
{}} />
{
}} />
)
}
{

View File

@ -41,7 +41,7 @@ export default function NumberIconBadgeBlock({widgetMetaData, data}: StandardBlo
{
data.values.iconName &&
<BlockElementWrapper metaData={widgetMetaData} data={data} slot="icon">
<Icon style={{color: data.styles.color, fontSize: "1rem", position: "relative", top: "3px"}}>{data.values.iconName}</Icon>
<Icon style={{color: data.styles.color, fontSize: "1rem", marginLeft: "2px", position: "relative", top: "4px"}}>{data.values.iconName}</Icon>
</BlockElementWrapper>
}
</div>);

View File

@ -30,8 +30,6 @@ 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 React, {useEffect, useMemo, useState} from "react";
import {useAsyncDebounce, useExpanded, useGlobalFilter, usePagination, useSortBy, useTable} from "react-table";
import colors from "qqq/assets/theme/base/colors";
import MDInput from "qqq/components/legacy/MDInput";
import MDPagination from "qqq/components/legacy/MDPagination";
@ -43,6 +41,8 @@ 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";
import WidgetBlock from "qqq/components/widgets/WidgetBlock";
import React, {useEffect, useMemo, useState} from "react";
import {useAsyncDebounce, useExpanded, useGlobalFilter, usePagination, useSortBy, useTable} from "react-table";
interface Props
{
@ -106,17 +106,17 @@ function DataTable({
entries = entriesPerPageOptions ? entriesPerPageOptions : ["10", "25", "50", "100"];
let widths = [];
for(let i = 0; i<table.columns.length; i++)
for (let i = 0; i < table.columns.length; i++)
{
const column = table.columns[i];
if(column.type !== "hidden")
if (column.type !== "hidden")
{
widths.push(table.columns[i].width ?? "1fr");
}
}
let showExpandColumn = false;
if(table.rows)
if (table.rows)
{
for (let i = 0; i < table.rows.length; i++)
{
@ -129,7 +129,7 @@ function DataTable({
}
const columnsToMemo = [...table.columns];
if(showExpandColumn)
if (showExpandColumn)
{
widths.push("60px");
columnsToMemo.push(
@ -173,11 +173,11 @@ function DataTable({
);
}
if(table.columnHeaderTooltips)
if (table.columnHeaderTooltips)
{
for (let column of columnsToMemo)
{
if(table.columnHeaderTooltips[column.accessor])
if (table.columnHeaderTooltips[column.accessor])
{
column.tooltip = table.columnHeaderTooltips[column.accessor];
}
@ -297,7 +297,7 @@ function DataTable({
}
let visibleFooterRows = 1;
if(expanded && expanded[`${table.rows.length-1}`])
if (expanded && expanded[`${table.rows.length - 1}`])
{
//////////////////////////////////////////////////
// todo - should count how many are expanded... //
@ -308,7 +308,7 @@ function DataTable({
function getTable(includeHead: boolean, rows: any, isFooter: boolean)
{
let boxStyle = {};
if(fixedStickyLastRow)
if (fixedStickyLastRow)
{
boxStyle = isFooter
? {borderTop: `0.0625rem solid ${colors.grayLines.main};`, backgroundColor: "#EEEEEE"}
@ -316,7 +316,7 @@ function DataTable({
}
let innerBoxStyle = {};
if(fixedStickyLastRow && isFooter)
if (fixedStickyLastRow && isFooter)
{
innerBoxStyle = {overflowY: "auto", scrollbarGutter: "stable"};
}
@ -327,7 +327,7 @@ function DataTable({
includeHead && (
<Box component="thead" sx={{position: "sticky", top: 0, background: "white", zIndex: 10}}>
{headerGroups.map((headerGroup: any, i: number) => (
<TableRow key={i} {...headerGroup.getHeaderGroupProps()} sx={{display: "grid", gridTemplateColumns: gridTemplateColumns}}>
<TableRow key={i} {...headerGroup.getHeaderGroupProps()} sx={{display: "grid", alignItems: "flex-end", gridTemplateColumns: gridTemplateColumns}}>
{headerGroup.headers.map((column: any) => (
column.type !== "hidden" && (
<DataTableHeadCell
@ -356,10 +356,10 @@ function DataTable({
//////////////////////////////////////////////////////////////////////////////////
// don't do an end-border on nested rows - unless they're the last one in a set //
//////////////////////////////////////////////////////////////////////////////////
if(row.depth > 0)
if (row.depth > 0)
{
overrideNoEndBorder = true;
if(key + 1 < rows.length && rows[key + 1].depth == 0)
if (key + 1 < rows.length && rows[key + 1].depth == 0)
{
overrideNoEndBorder = false;
}
@ -368,17 +368,17 @@ function DataTable({
///////////////////////////////////////
// don't do end-border on the footer //
///////////////////////////////////////
if(isFooter)
if (isFooter)
{
overrideNoEndBorder = true;
}
let background = "initial";
if(isFooter)
if (isFooter)
{
background = "#EEEEEE";
}
else if(row.depth > 0 || row.isExpanded)
else if (row.depth > 0 || row.isExpanded)
{
background = "#FAFAFA";
}
@ -453,7 +453,7 @@ function DataTable({
</TableBody>
</Table>
</Box></Box>
</Box></Box>;
}
return (

View File

@ -28,13 +28,13 @@ 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";
import React, {useEffect, useState} from "react";
//////////////////////////////////////
@ -43,7 +43,7 @@ import Client from "qqq/utils/qqq/Client";
export interface TableDataInput
{
columns: { [key: string]: any }[];
columnHeaderTooltips?: { [columnName: string]: string | JSX.Element }
columnHeaderTooltips?: { [columnName: string]: string | JSX.Element };
rows: { [key: string]: any }[];
}
@ -63,6 +63,7 @@ interface Props
}
const qController = Client.getInstance();
function TableCard({noRowsFoundHTML, data, rowsPerPage, hidePaginationDropdown, fixedStickyLastRow, fixedHeight, widgetMetaData}: Props): JSX.Element
{
const [qInstance, setQInstance] = useState(null as QInstance);
@ -108,7 +109,7 @@ function TableCard({noRowsFoundHTML, data, rowsPerPage, hidePaginationDropdown,
<TableContainer sx={{boxShadow: "none"}}>
<Table>
<Box component="thead">
<TableRow key="header">
<TableRow sx={{alignItems: "flex-end"}} key="header">
{Array(8).fill(0).map((_, i) =>
<DataTableHeadCell key={`head-${i}`} sorted={false} width="auto" align="center">
<Skeleton width="100%" />

View File

@ -23,7 +23,6 @@
import {QWidgetMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QWidgetMetaData";
// @ts-ignore
import {htmlToText} from "html-to-text";
import React, {useContext, useEffect, useState} from "react";
import QContext from "QContext";
import HelpContent, {hasHelpContent} from "qqq/components/misc/HelpContent";
import TableCard from "qqq/components/widgets/tables/TableCard";
@ -31,6 +30,7 @@ import Widget, {WidgetData} from "qqq/components/widgets/Widget";
import {WidgetUtils} from "qqq/components/widgets/WidgetUtils";
import HtmlUtils from "qqq/utils/HtmlUtils";
import ValueUtils from "qqq/utils/qqq/ValueUtils";
import React, {useContext, useEffect, useState} from "react";
interface Props
{
@ -40,8 +40,7 @@ interface Props
isChild?: boolean;
}
TableWidget.defaultProps = {
};
TableWidget.defaultProps = {};
function TableWidget(props: Props): JSX.Element
{
@ -86,7 +85,7 @@ function TableWidget(props: Props): JSX.Element
const cell = rows[i][columns[j].accessor];
let text = cell;
if(columns[j].type != "default")
if (columns[j].type != "default")
{
text = htmlToText(cell,
{
@ -105,7 +104,7 @@ function TableWidget(props: Props): JSX.Element
setCsv(csv);
const fileName = WidgetUtils.makeExportFileName(props.widgetData, props.widgetMetaData);
setFileName(fileName)
setFileName(fileName);
console.log(`useEffect, setting fileName ${fileName}`);
}
@ -114,24 +113,24 @@ function TableWidget(props: Props): JSX.Element
const onExportClick = () =>
{
if(props.widgetData?.csvData)
if (props.widgetData?.csvData)
{
const csv = WidgetUtils.widgetCsvDataToString(props.widgetData);
const fileName = WidgetUtils.makeExportFileName(props.widgetData, props.widgetMetaData);
HtmlUtils.download(fileName, csv);
}
else if(csv)
else if (csv)
{
HtmlUtils.download(fileName, csv);
}
else
{
alert("There is no data available to export.")
alert("There is no data available to export.");
}
}
};
const labelAdditionalElementsLeft: JSX.Element[] = [];
if(props.widgetMetaData?.showExportButton)
if (props.widgetMetaData?.showExportButton)
{
labelAdditionalElementsLeft.push(WidgetUtils.generateExportButton(onExportClick));
}
@ -139,14 +138,14 @@ function TableWidget(props: Props): JSX.Element
//////////////////////////////////////////////////////
// look for column-header tooltips from helpContent //
//////////////////////////////////////////////////////
const columnHeaderTooltips: {[columnName: string]: JSX.Element} = {}
const columnHeaderTooltips: { [columnName: string]: JSX.Element } = {};
for (let column of props.widgetData?.columns ?? [])
{
const helpRoles = ["ALL_SCREENS"]
const helpRoles = ["ALL_SCREENS"];
const slotName = `columnHeader=${column.accessor}`;
const showHelp = helpHelpActive || hasHelpContent(props.widgetMetaData?.helpContent?.get(slotName), helpRoles);
if(showHelp)
if (showHelp)
{
const formattedHelpContent = <HelpContent helpContents={props.widgetMetaData?.helpContent?.get(slotName)} roles={helpRoles} helpContentKey={`widget:${props.widgetMetaData?.name};slot:${slotName}`} />;
columnHeaderTooltips[column.accessor] = formattedHelpContent;