diff --git a/.circleci/config.yml b/.circleci/config.yml
index 94997c2..af15303 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -115,7 +115,7 @@ workflows:
context: [ qqq-maven-registry-credentials, kingsrook-slack, build-qqq-sample-app ]
filters:
branches:
- ignore: /(main|integration.*)/
+ ignore: /(main|dev|integration.*)/
tags:
ignore: /(version|snapshot)-.*/
deploy:
@@ -124,7 +124,7 @@ workflows:
context: [ qqq-maven-registry-credentials, kingsrook-slack, build-qqq-sample-app ]
filters:
branches:
- only: /(main|integration.*)/
+ only: /(main|dev|integration.*)/
tags:
only: /(version|snapshot)-.*/
diff --git a/pom.xml b/pom.xml
index 2646896..cf3d63d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -29,7 +29,7 @@
jar
- 0.20.0
+ 0.21.0
UTF-8
UTF-8
@@ -66,7 +66,7 @@
com.kingsrook.qqq
qqq-backend-core
- 0.20.0-20240308.165846-65
+ 0.21.0
org.slf4j
@@ -159,6 +159,7 @@
1
revision
true
+ true
diff --git a/src/qqq/components/horseshoe/NavBar.tsx b/src/qqq/components/horseshoe/NavBar.tsx
index bcd1e07..24e3f94 100644
--- a/src/qqq/components/horseshoe/NavBar.tsx
+++ b/src/qqq/components/horseshoe/NavBar.tsx
@@ -25,6 +25,7 @@ import Autocomplete from "@mui/material/Autocomplete";
import Icon from "@mui/material/Icon";
import IconButton from "@mui/material/IconButton";
import ListItemIcon from "@mui/material/ListItemIcon";
+import {Theme} from "@mui/material/styles";
import TextField from "@mui/material/TextField";
import Toolbar from "@mui/material/Toolbar";
import React, {useContext, useEffect, useRef, useState} from "react";
@@ -225,6 +226,19 @@ function NavBar({absolute, light, isMini}: Props): JSX.Element
const breadcrumbTitle = fullPathToLabel(fullPath, route[route.length - 1]);
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // set the right-half of the navbar up so that below the 'md' breakpoint, it just disappears //
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ const navbarRowRight = (theme: Theme, {isMini}: any) =>
+ {
+ return {
+ [theme.breakpoints.down("md")]: {
+ display: "none",
+ },
+ ...navbarRow(theme, isMini)
+ }
+ };
+
return (
{isMini ? null : (
- navbarRow(theme, {isMini})}>
+ navbarRowRight(theme, {isMini})}>
{renderHistory()}
diff --git a/src/qqq/components/widgets/CompositeWidget.tsx b/src/qqq/components/widgets/CompositeWidget.tsx
index b345487..b7347ff 100644
--- a/src/qqq/components/widgets/CompositeWidget.tsx
+++ b/src/qqq/components/widgets/CompositeWidget.tsx
@@ -22,16 +22,19 @@
import {QWidgetMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QWidgetMetaData";
import {Box, Skeleton} from "@mui/material";
+import parse from "html-react-parser";
import {BlockData} from "qqq/components/widgets/blocks/BlockModels";
import WidgetBlock from "qqq/components/widgets/WidgetBlock";
import React from "react";
-interface CompositeData
+export interface CompositeData
{
blocks: BlockData[];
styleOverrides?: any;
layout?: string;
+ overlayHtml?: string;
+ overlayStyleOverrides?: any;
}
@@ -97,20 +100,34 @@ export default function CompositeWidget({widgetMetaData, data}: CompositeWidgetP
boxStyle.borderRadius = "0.5rem";
boxStyle.background = "#FFFFFF";
}
-
if (data?.styleOverrides)
{
boxStyle = {...boxStyle, ...data.styleOverrides};
}
- return (
- {
- data.blocks.map((block: BlockData, index) => (
-
-
-
- ))
- }
- );
+ let overlayStyle: any = {};
+
+ if (data?.overlayStyleOverrides)
+ {
+ overlayStyle = {...overlayStyle, ...data.overlayStyleOverrides};
+ }
+
+ return (
+ <>
+ {
+ data?.overlayHtml &&
+ {parse(data.overlayHtml)}
+ }
+
+ {
+ data.blocks.map((block: BlockData, index) => (
+
+
+
+ ))
+ }
+
+ >
+ );
}
diff --git a/src/qqq/components/widgets/DashboardWidgets.tsx b/src/qqq/components/widgets/DashboardWidgets.tsx
index f2d1dc0..38fe772 100644
--- a/src/qqq/components/widgets/DashboardWidgets.tsx
+++ b/src/qqq/components/widgets/DashboardWidgets.tsx
@@ -638,8 +638,28 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, reco
if (!omitWrappingGridContainer)
{
- // @ts-ignore
- renderedWidget = (
+ const gridProps: {[key: string]: any} = {};
+
+ for(let size of ["xs", "sm", "md", "lg", "xl", "xxl"])
+ {
+ const key = `gridCols:sizeClass:${size}`
+ if(widgetMetaData?.defaultValues?.has(key))
+ {
+ gridProps[size] = widgetMetaData?.defaultValues.get(key);
+ }
+ }
+
+ if(!gridProps["xxl"])
+ {
+ gridProps["xxl"] = widgetMetaData.gridColumns ? widgetMetaData.gridColumns : 12;
+ }
+
+ if(!gridProps["xs"])
+ {
+ gridProps["xs"] = 12;
+ }
+
+ renderedWidget = (
{renderedWidget}
);
}
diff --git a/src/qqq/components/widgets/blocks/BlockElementWrapper.tsx b/src/qqq/components/widgets/blocks/BlockElementWrapper.tsx
index dbfcce2..965522d 100644
--- a/src/qqq/components/widgets/blocks/BlockElementWrapper.tsx
+++ b/src/qqq/components/widgets/blocks/BlockElementWrapper.tsx
@@ -21,18 +21,19 @@
import {QWidgetMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QWidgetMetaData";
-import {Tooltip} from "@mui/material";
-import React, {ReactElement, useContext} from "react";
-import {Link} from "react-router-dom";
+import {Box, Tooltip} from "@mui/material";
import QContext from "QContext";
import HelpContent, {hasHelpContent} from "qqq/components/misc/HelpContent";
import {BlockData, BlockLink, BlockTooltip} from "qqq/components/widgets/blocks/BlockModels";
+import CompositeWidget from "qqq/components/widgets/CompositeWidget";
+import React, {ReactElement, useContext} from "react";
+import {Link} from "react-router-dom";
interface BlockElementWrapperProps
{
data: BlockData;
metaData: QWidgetMetaData;
- slot: string
+ slot: string;
linkProps?: any;
children: ReactElement;
}
@@ -47,16 +48,16 @@ export default function BlockElementWrapper({data, metaData, slot, linkProps, ch
let link: BlockLink;
let tooltip: BlockTooltip;
- if(slot)
+ if (slot)
{
link = data.linkMap && data.linkMap[slot.toUpperCase()];
- if(!link)
+ if (!link)
{
link = data.link;
}
tooltip = data.tooltipMap && data.tooltipMap[slot.toUpperCase()];
- if(!tooltip)
+ if (!tooltip)
{
tooltip = data.tooltip;
}
@@ -67,9 +68,9 @@ export default function BlockElementWrapper({data, metaData, slot, linkProps, ch
tooltip = data.tooltip;
}
- if(!tooltip)
+ if (!tooltip)
{
- const helpRoles = ["ALL_SCREENS"]
+ const helpRoles = ["ALL_SCREENS"];
///////////////////////////////////////////////////////////////////////////////////////////////
// the full keys in the helpContent table will look like: //
@@ -80,26 +81,39 @@ export default function BlockElementWrapper({data, metaData, slot, linkProps, ch
const key = data.blockId ? `${data.blockId},${slot}` : slot;
const showHelp = helpHelpActive || hasHelpContent(metaData?.helpContent?.get(key), helpRoles);
- if(showHelp)
+ if (showHelp)
{
const formattedHelpContent = ;
- tooltip = {title: formattedHelpContent, placement: "bottom"}
+ tooltip = {title: formattedHelpContent, placement: "bottom"};
}
}
let rs = children;
- if(link)
+ if (link && link.href)
{
- rs = {rs}
+ rs = {rs};
}
- if(tooltip)
+ if (tooltip)
{
- let placement = tooltip.placement ? tooltip.placement.toLowerCase() : "bottom"
+ let placement = tooltip.placement ? tooltip.placement.toLowerCase() : "bottom";
// @ts-ignore - placement possible values
- rs = {rs}
+ if (tooltip.blockData)
+ {
+ // @ts-ignore - special case for composite type block...
+ rs =
+
+
+ }>{rs};
+ }
+ else
+ {
+ // @ts-ignore - placement possible values
+ rs = {rs};
+ }
}
return (rs);
diff --git a/src/qqq/components/widgets/blocks/BlockModels.ts b/src/qqq/components/widgets/blocks/BlockModels.ts
index 22a7c1e..c29f77c 100644
--- a/src/qqq/components/widgets/blocks/BlockModels.ts
+++ b/src/qqq/components/widgets/blocks/BlockModels.ts
@@ -20,6 +20,7 @@
*/
import {QWidgetMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QWidgetMetaData";
+import {CompositeData} from "qqq/components/widgets/CompositeWidget";
export interface BlockData
@@ -29,8 +30,8 @@ export interface BlockData
tooltip?: BlockTooltip;
link?: BlockLink;
- tooltipMap?: {[slot: string]: BlockTooltip};
- linkMap?: {[slot: string]: BlockLink};
+ tooltipMap?: { [slot: string]: BlockTooltip };
+ linkMap?: { [slot: string]: BlockLink };
values: any;
styles?: any;
@@ -39,6 +40,7 @@ export interface BlockData
export interface BlockTooltip
{
+ blockData?: CompositeData;
title: string | JSX.Element;
placement: string;
}
diff --git a/src/qqq/components/widgets/tables/DataTable.tsx b/src/qqq/components/widgets/tables/DataTable.tsx
index d039c2a..34772ce 100644
--- a/src/qqq/components/widgets/tables/DataTable.tsx
+++ b/src/qqq/components/widgets/tables/DataTable.tsx
@@ -19,15 +19,12 @@
*/
import {QWidgetMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QWidgetMetaData";
-import {tooltipClasses, TooltipProps} from "@mui/material";
+import {Box, 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 colors from "qqq/assets/theme/base/colors";
@@ -166,7 +163,7 @@ function DataTable({
})}
>
{/* float this icon to keep it "out of the flow" - in other words, to keep it from making the row taller than it otherwise would be... */}
- {row.isExpanded ? "expand_less" : "chevron_right"}
+ {row.isExpanded ? "expand_less" : "chevron_left"}
) : null,
},
@@ -312,7 +309,7 @@ function DataTable({
{
boxStyle = isFooter
? {borderTop: `0.0625rem solid ${colors.grayLines.main};`, backgroundColor: "#EEEEEE"}
- : {flexGrow: 1, overflowY: "scroll", scrollbarGutter: "stable", marginBottom: "-1px"};
+ : {height: fixedHeight ? `${fixedHeight}px` : "auto", flexGrow: 1, overflowY: "scroll", scrollbarGutter: "stable", marginBottom: "-1px"};
}
let innerBoxStyle = {};
@@ -321,143 +318,139 @@ function DataTable({
innerBoxStyle = {overflowY: "auto", scrollbarGutter: "stable"};
}
+ ///////////////////////////////////////////////////////////////////////////////////
+ // note - at one point, we had the table's sx including: whiteSpace: "nowrap"... //
+ ///////////////////////////////////////////////////////////////////////////////////
return
-
+
{
includeHead && (
-
- {headerGroups.map((headerGroup: any, i: number) => (
-
- {headerGroup.headers.map((column: any) => (
- column.type !== "hidden" && (
-
- {column.render("header")}
-
- )
- ))}
-
- ))}
-
+ headerGroups.map((headerGroup: any, i: number) => (
+ headerGroup.headers.map((column: any) => (
+ column.type !== "hidden" && (
+
+ {column.render("header")}
+
+ )
+ ))
+ ))
)
}
-
- {rows.map((row: any, key: any) =>
+ {rows.map((row: any, key: any) =>
+ {
+ prepareRow(row);
+
+ let overrideNoEndBorder = false;
+
+ //////////////////////////////////////////////////////////////////////////////////
+ // don't do an end-border on nested rows - unless they're the last one in a set //
+ //////////////////////////////////////////////////////////////////////////////////
+ if (row.depth > 0)
{
- prepareRow(row);
-
- let overrideNoEndBorder = false;
-
- //////////////////////////////////////////////////////////////////////////////////
- // don't do an end-border on nested rows - unless they're the last one in a set //
- //////////////////////////////////////////////////////////////////////////////////
- if (row.depth > 0)
+ overrideNoEndBorder = true;
+ if (key + 1 < rows.length && rows[key + 1].depth == 0)
{
- overrideNoEndBorder = true;
- if (key + 1 < rows.length && rows[key + 1].depth == 0)
- {
- overrideNoEndBorder = false;
- }
+ overrideNoEndBorder = false;
}
+ }
- ///////////////////////////////////////
- // don't do end-border on the footer //
- ///////////////////////////////////////
- if (isFooter)
- {
- overrideNoEndBorder = true;
- }
+ ///////////////////////////////////////
+ // don't do end-border on the footer //
+ ///////////////////////////////////////
+ if (isFooter)
+ {
+ overrideNoEndBorder = true;
+ }
- let background = "initial";
- if (isFooter)
- {
- background = "#EEEEEE";
- }
- else if (row.depth > 0 || row.isExpanded)
- {
- background = "#FAFAFA";
- }
+ let background = "initial";
+ if (isFooter)
+ {
+ background = "#EEEEEE";
+ }
+ else if (row.depth > 0 || row.isExpanded)
+ {
+ background = "#FAFAFA";
+ }
- return (
-
- {row.cells.map((cell: any) => (
- cell.column.type !== "hidden" && (
-
- {
- cell.column.type === "default" && (
- cell.value && "number" === typeof cell.value ? (
- {cell.value.toLocaleString()}
- ) : ({cell.render("Cell")})
- )
- }
- {
- cell.column.type === "htmlAndTooltip" && (
-
-
-
- {parse(cell.value)}
-
-
-
- )
- }
- {
- cell.column.type === "html" && (
- {parse(cell.value ?? "")}
- )
- }
- {
- cell.column.type === "composite" && (
-
-
-
- )
- }
- {
- cell.column.type === "block" && (
-
-
-
- )
- }
- {
- cell.column.type === "image" && row.values["imageTotal"] && (
-
- )
- }
- {
- cell.column.type === "image" && !row.values["imageTotal"] && (
-
- )
- }
- {
- (cell.column.id === "__expander") && cell.render("cell")
- }
-
- )
- ))}
-
- );
- })}
-
-
+ return (
+ row.cells.map((cell: any) => (
+ cell.column.type !== "hidden" && (
+
+ {
+ cell.column.type === "default" && (
+ cell.value && "number" === typeof cell.value ? (
+ {cell.value.toLocaleString()}
+ ) : ({cell.render("Cell")})
+ )
+ }
+ {
+ cell.column.type === "htmlAndTooltip" && (
+
+
+
+ {parse(cell.value)}
+
+
+
+ )
+ }
+ {
+ cell.column.type === "html" && (
+ {parse(cell.value ?? "")}
+ )
+ }
+ {
+ cell.column.type === "composite" && (
+
+
+
+ )
+ }
+ {
+ cell.column.type === "block" && (
+
+
+
+ )
+ }
+ {
+ cell.column.type === "image" && row.values["imageTotal"] && (
+
+ )
+ }
+ {
+ cell.column.type === "image" && !row.values["imageTotal"] && (
+
+ )
+ }
+ {
+ (cell.column.id === "__expander") && cell.render("cell")
+ }
+
+ )
+ ))
+ );
+ })}
;
}
return (
-
+
{entriesPerPage && ((hidePaginationDropdown !== undefined && !hidePaginationDropdown) || canSearch) ? (
{entriesPerPage && (hidePaginationDropdown === undefined || !hidePaginationDropdown) && (
diff --git a/src/qqq/components/widgets/tables/TableCard.tsx b/src/qqq/components/widgets/tables/TableCard.tsx
index cdfcaba..2240fb2 100644
--- a/src/qqq/components/widgets/tables/TableCard.tsx
+++ b/src/qqq/components/widgets/tables/TableCard.tsx
@@ -93,41 +93,25 @@ function TableCard({noRowsFoundHTML, data, rowsPerPage, hidePaginationDropdown,
/>
: noRowsFoundHTML ?
-
- {
- noRowsFoundHTML ? (
- parse(noRowsFoundHTML)
- ) : "No rows found"
- }
+
+ {noRowsFoundHTML ? (parse(noRowsFoundHTML)) : "No rows found"}
:
-
-
-
- {Array(8).fill(0).map((_, i) =>
-
-
-
- )}
-
-
-
- {Array(5).fill(0).map((_, i) =>
-
- {Array(8).fill(0).map((_, j) =>
-
-
-
- )}
-
- )}
-
+
+ {Array(8).fill(0).map((_, i) =>
+
+
+
+ )}
+ {Array(5).fill(0).map((_, i) =>
+ Array(8).fill(0).map((_, j) =>
+
+
+
+ )
+ )}
}
diff --git a/src/qqq/components/widgets/tables/cells/DataTableBodyCell.tsx b/src/qqq/components/widgets/tables/cells/DataTableBodyCell.tsx
index 5f22d53..7623de3 100644
--- a/src/qqq/components/widgets/tables/cells/DataTableBodyCell.tsx
+++ b/src/qqq/components/widgets/tables/cells/DataTableBodyCell.tsx
@@ -19,7 +19,7 @@
* along with this program. If not, see .
*/
-import Box from "@mui/material/Box";
+import {Box} from "@mui/material";
import {Theme} from "@mui/material/styles";
import colors from "qqq/assets/theme/base/colors";
import {ReactNode} from "react";
@@ -30,13 +30,14 @@ interface Props
children: ReactNode;
noBorder?: boolean;
align?: "left" | "right" | "center";
+ sx?: any;
}
-function DataTableBodyCell({noBorder, align, children}: Props): JSX.Element
+function DataTableBodyCell({noBorder, align, sx, children}: Props): JSX.Element
{
return (
({
borderBottom: `${borderWidth[1]} solid ${colors.grayLines.main}`,
- "&:nth-of-type(1)": {
- paddingLeft: "1rem"
- },
- "&:last-child": {
- paddingRight: "1rem"
- },
+ position: "sticky", top: 0, background: "white",
+ zIndex: 1 // so if body rows scroll behind it, they don't show through
})}
>
scheduleUpdated at 2023-10-17 09:11:38 AM CDT",
"columns": [
- { "type": "html", "header": "Id", "accessor": "id", "width": "1%" },
- { "type": "html", "header": "Name", "accessor": "name", "width": "99%" }
+ { "type": "html", "header": "Id", "accessor": "id", "width": "30px" },
+ { "type": "html", "header": "Name", "accessor": "name", "width": "1fr" }
],
"rows": [
{ "id": "1", "name": "Homer S." },
@@ -83,7 +83,7 @@ public class DashboardTableWidgetExportTest extends QBaseSeleniumTest
// assert that the table widget rendered its header and some contents //
////////////////////////////////////////////////////////////////////////
qSeleniumLib.waitForSelectorContaining("#SampleTableWidget h6", "Sample Table Widget");
- qSeleniumLib.waitForSelectorContaining("#SampleTableWidget table a", "Homer S.");
+ qSeleniumLib.waitForSelectorContaining("#SampleTableWidget a", "Homer S.");
qSeleniumLib.waitForSelectorContaining("#SampleTableWidget div", "Updated at 2023-10-17 09:11:38 AM CDT");
/////////////////////////////