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 })} >