mirror of
https://github.com/Kingsrook/qqq-frontend-material-dashboard.git
synced 2025-07-17 21:00:45 +00:00
Merge branch 'release/0.21.0'
This commit is contained in:
@ -115,7 +115,7 @@ workflows:
|
|||||||
context: [ qqq-maven-registry-credentials, kingsrook-slack, build-qqq-sample-app ]
|
context: [ qqq-maven-registry-credentials, kingsrook-slack, build-qqq-sample-app ]
|
||||||
filters:
|
filters:
|
||||||
branches:
|
branches:
|
||||||
ignore: /(main|integration.*)/
|
ignore: /(main|dev|integration.*)/
|
||||||
tags:
|
tags:
|
||||||
ignore: /(version|snapshot)-.*/
|
ignore: /(version|snapshot)-.*/
|
||||||
deploy:
|
deploy:
|
||||||
@ -124,7 +124,7 @@ workflows:
|
|||||||
context: [ qqq-maven-registry-credentials, kingsrook-slack, build-qqq-sample-app ]
|
context: [ qqq-maven-registry-credentials, kingsrook-slack, build-qqq-sample-app ]
|
||||||
filters:
|
filters:
|
||||||
branches:
|
branches:
|
||||||
only: /(main|integration.*)/
|
only: /(main|dev|integration.*)/
|
||||||
tags:
|
tags:
|
||||||
only: /(version|snapshot)-.*/
|
only: /(version|snapshot)-.*/
|
||||||
|
|
||||||
|
5
pom.xml
5
pom.xml
@ -29,7 +29,7 @@
|
|||||||
<packaging>jar</packaging>
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<revision>0.20.0</revision>
|
<revision>0.21.0</revision>
|
||||||
|
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||||
@ -66,7 +66,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.kingsrook.qqq</groupId>
|
<groupId>com.kingsrook.qqq</groupId>
|
||||||
<artifactId>qqq-backend-core</artifactId>
|
<artifactId>qqq-backend-core</artifactId>
|
||||||
<version>0.20.0-20240308.165846-65</version>
|
<version>0.21.0</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.slf4j</groupId>
|
<groupId>org.slf4j</groupId>
|
||||||
@ -159,6 +159,7 @@
|
|||||||
<versionDigitToIncrement>1</versionDigitToIncrement> <!-- In general, we update the minor -->
|
<versionDigitToIncrement>1</versionDigitToIncrement> <!-- In general, we update the minor -->
|
||||||
<versionProperty>revision</versionProperty>
|
<versionProperty>revision</versionProperty>
|
||||||
<skipUpdateVersion>true</skipUpdateVersion>
|
<skipUpdateVersion>true</skipUpdateVersion>
|
||||||
|
<skipTestProject>true</skipTestProject> <!-- we allow CI to do the tests -->
|
||||||
</configuration>
|
</configuration>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
|
||||||
|
@ -25,6 +25,7 @@ import Autocomplete from "@mui/material/Autocomplete";
|
|||||||
import Icon from "@mui/material/Icon";
|
import Icon from "@mui/material/Icon";
|
||||||
import IconButton from "@mui/material/IconButton";
|
import IconButton from "@mui/material/IconButton";
|
||||||
import ListItemIcon from "@mui/material/ListItemIcon";
|
import ListItemIcon from "@mui/material/ListItemIcon";
|
||||||
|
import {Theme} from "@mui/material/styles";
|
||||||
import TextField from "@mui/material/TextField";
|
import TextField from "@mui/material/TextField";
|
||||||
import Toolbar from "@mui/material/Toolbar";
|
import Toolbar from "@mui/material/Toolbar";
|
||||||
import React, {useContext, useEffect, useRef, useState} from "react";
|
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]);
|
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 (
|
return (
|
||||||
<AppBar
|
<AppBar
|
||||||
position={absolute ? "absolute" : navbarType}
|
position={absolute ? "absolute" : navbarType}
|
||||||
@ -241,7 +255,7 @@ function NavBar({absolute, light, isMini}: Props): JSX.Element
|
|||||||
<QBreadcrumbs icon="home" title={breadcrumbTitle} route={route} light={light} />
|
<QBreadcrumbs icon="home" title={breadcrumbTitle} route={route} light={light} />
|
||||||
</Box>
|
</Box>
|
||||||
{isMini ? null : (
|
{isMini ? null : (
|
||||||
<Box sx={(theme) => navbarRow(theme, {isMini})}>
|
<Box sx={(theme) => navbarRowRight(theme, {isMini})}>
|
||||||
<Box mt={"-0.25rem"} pb={"0.75rem"} pr={2} mr={-2} sx={{"& *": {cursor: "pointer !important"}}}>
|
<Box mt={"-0.25rem"} pb={"0.75rem"} pr={2} mr={-2} sx={{"& *": {cursor: "pointer !important"}}}>
|
||||||
{renderHistory()}
|
{renderHistory()}
|
||||||
</Box>
|
</Box>
|
||||||
|
@ -22,16 +22,19 @@
|
|||||||
|
|
||||||
import {QWidgetMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QWidgetMetaData";
|
import {QWidgetMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QWidgetMetaData";
|
||||||
import {Box, Skeleton} from "@mui/material";
|
import {Box, Skeleton} from "@mui/material";
|
||||||
|
import parse from "html-react-parser";
|
||||||
import {BlockData} from "qqq/components/widgets/blocks/BlockModels";
|
import {BlockData} from "qqq/components/widgets/blocks/BlockModels";
|
||||||
import WidgetBlock from "qqq/components/widgets/WidgetBlock";
|
import WidgetBlock from "qqq/components/widgets/WidgetBlock";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
|
|
||||||
interface CompositeData
|
export interface CompositeData
|
||||||
{
|
{
|
||||||
blocks: BlockData[];
|
blocks: BlockData[];
|
||||||
styleOverrides?: any;
|
styleOverrides?: any;
|
||||||
layout?: string;
|
layout?: string;
|
||||||
|
overlayHtml?: string;
|
||||||
|
overlayStyleOverrides?: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -97,20 +100,34 @@ export default function CompositeWidget({widgetMetaData, data}: CompositeWidgetP
|
|||||||
boxStyle.borderRadius = "0.5rem";
|
boxStyle.borderRadius = "0.5rem";
|
||||||
boxStyle.background = "#FFFFFF";
|
boxStyle.background = "#FFFFFF";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data?.styleOverrides)
|
if (data?.styleOverrides)
|
||||||
{
|
{
|
||||||
boxStyle = {...boxStyle, ...data.styleOverrides};
|
boxStyle = {...boxStyle, ...data.styleOverrides};
|
||||||
}
|
}
|
||||||
|
|
||||||
return (<Box sx={boxStyle} className="compositeWidget">
|
let overlayStyle: any = {};
|
||||||
{
|
|
||||||
data.blocks.map((block: BlockData, index) => (
|
if (data?.overlayStyleOverrides)
|
||||||
<React.Fragment key={index}>
|
{
|
||||||
<WidgetBlock widgetMetaData={widgetMetaData} block={block} />
|
overlayStyle = {...overlayStyle, ...data.overlayStyleOverrides};
|
||||||
</React.Fragment>
|
}
|
||||||
))
|
|
||||||
}
|
return (
|
||||||
</Box>);
|
<>
|
||||||
|
{
|
||||||
|
data?.overlayHtml &&
|
||||||
|
<Box sx={overlayStyle} className="blockWidgetOverlay">{parse(data.overlayHtml)}</Box>
|
||||||
|
}
|
||||||
|
<Box sx={boxStyle} className="compositeWidget">
|
||||||
|
{
|
||||||
|
data.blocks.map((block: BlockData, index) => (
|
||||||
|
<React.Fragment key={index}>
|
||||||
|
<WidgetBlock widgetMetaData={widgetMetaData} block={block} />
|
||||||
|
</React.Fragment>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</Box>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -638,8 +638,28 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, reco
|
|||||||
|
|
||||||
if (!omitWrappingGridContainer)
|
if (!omitWrappingGridContainer)
|
||||||
{
|
{
|
||||||
// @ts-ignore
|
const gridProps: {[key: string]: any} = {};
|
||||||
renderedWidget = (<Grid id={widgetMetaData.name} item xxl={widgetMetaData.gridColumns ? widgetMetaData.gridColumns : 12} xs={12} sx={{display: "flex", alignItems: "stretch", scrollMarginTop: "100px"}}>
|
|
||||||
|
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 = (<Grid id={widgetMetaData.name} item {...gridProps} sx={{display: "flex", alignItems: "stretch", scrollMarginTop: "100px"}}>
|
||||||
{renderedWidget}
|
{renderedWidget}
|
||||||
</Grid>);
|
</Grid>);
|
||||||
}
|
}
|
||||||
|
@ -21,18 +21,19 @@
|
|||||||
|
|
||||||
|
|
||||||
import {QWidgetMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QWidgetMetaData";
|
import {QWidgetMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QWidgetMetaData";
|
||||||
import {Tooltip} from "@mui/material";
|
import {Box, Tooltip} from "@mui/material";
|
||||||
import React, {ReactElement, useContext} from "react";
|
|
||||||
import {Link} from "react-router-dom";
|
|
||||||
import QContext from "QContext";
|
import QContext from "QContext";
|
||||||
import HelpContent, {hasHelpContent} from "qqq/components/misc/HelpContent";
|
import HelpContent, {hasHelpContent} from "qqq/components/misc/HelpContent";
|
||||||
import {BlockData, BlockLink, BlockTooltip} from "qqq/components/widgets/blocks/BlockModels";
|
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
|
interface BlockElementWrapperProps
|
||||||
{
|
{
|
||||||
data: BlockData;
|
data: BlockData;
|
||||||
metaData: QWidgetMetaData;
|
metaData: QWidgetMetaData;
|
||||||
slot: string
|
slot: string;
|
||||||
linkProps?: any;
|
linkProps?: any;
|
||||||
children: ReactElement;
|
children: ReactElement;
|
||||||
}
|
}
|
||||||
@ -47,16 +48,16 @@ export default function BlockElementWrapper({data, metaData, slot, linkProps, ch
|
|||||||
let link: BlockLink;
|
let link: BlockLink;
|
||||||
let tooltip: BlockTooltip;
|
let tooltip: BlockTooltip;
|
||||||
|
|
||||||
if(slot)
|
if (slot)
|
||||||
{
|
{
|
||||||
link = data.linkMap && data.linkMap[slot.toUpperCase()];
|
link = data.linkMap && data.linkMap[slot.toUpperCase()];
|
||||||
if(!link)
|
if (!link)
|
||||||
{
|
{
|
||||||
link = data.link;
|
link = data.link;
|
||||||
}
|
}
|
||||||
|
|
||||||
tooltip = data.tooltipMap && data.tooltipMap[slot.toUpperCase()];
|
tooltip = data.tooltipMap && data.tooltipMap[slot.toUpperCase()];
|
||||||
if(!tooltip)
|
if (!tooltip)
|
||||||
{
|
{
|
||||||
tooltip = data.tooltip;
|
tooltip = data.tooltip;
|
||||||
}
|
}
|
||||||
@ -67,9 +68,9 @@ export default function BlockElementWrapper({data, metaData, slot, linkProps, ch
|
|||||||
tooltip = data.tooltip;
|
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: //
|
// 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 key = data.blockId ? `${data.blockId},${slot}` : slot;
|
||||||
const showHelp = helpHelpActive || hasHelpContent(metaData?.helpContent?.get(key), helpRoles);
|
const showHelp = helpHelpActive || hasHelpContent(metaData?.helpContent?.get(key), helpRoles);
|
||||||
|
|
||||||
if(showHelp)
|
if (showHelp)
|
||||||
{
|
{
|
||||||
const formattedHelpContent = <HelpContent helpContents={metaData?.helpContent?.get(key)} roles={helpRoles} helpContentKey={`widget:${metaData?.name};slot:${key}`} />;
|
const formattedHelpContent = <HelpContent helpContents={metaData?.helpContent?.get(key)} roles={helpRoles} helpContentKey={`widget:${metaData?.name};slot:${key}`} />;
|
||||||
tooltip = {title: formattedHelpContent, placement: "bottom"}
|
tooltip = {title: formattedHelpContent, placement: "bottom"};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let rs = children;
|
let rs = children;
|
||||||
|
|
||||||
if(link)
|
if (link && link.href)
|
||||||
{
|
{
|
||||||
rs = <Link to={link.href} target={link.target} style={{color: "#546E7A"}} {...linkProps}>{rs}</Link>
|
rs = <Link to={link.href} target={link.target} style={{color: "#546E7A"}} {...linkProps}>{rs}</Link>;
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
// @ts-ignore - placement possible values
|
||||||
rs = <Tooltip title={tooltip.title} placement={placement}>{rs}</Tooltip>
|
if (tooltip.blockData)
|
||||||
|
{
|
||||||
|
// @ts-ignore - special case for composite type block...
|
||||||
|
rs = <Tooltip title={
|
||||||
|
<Box sx={{width: "200px"}}>
|
||||||
|
<CompositeWidget widgetMetaData={metaData} data={tooltip?.blockData} />
|
||||||
|
</Box>
|
||||||
|
}>{rs}</Tooltip>;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// @ts-ignore - placement possible values
|
||||||
|
rs = <Tooltip title={tooltip.title} placement={placement}>{rs}</Tooltip>;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (rs);
|
return (rs);
|
||||||
|
@ -20,6 +20,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {QWidgetMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QWidgetMetaData";
|
import {QWidgetMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QWidgetMetaData";
|
||||||
|
import {CompositeData} from "qqq/components/widgets/CompositeWidget";
|
||||||
|
|
||||||
|
|
||||||
export interface BlockData
|
export interface BlockData
|
||||||
@ -29,8 +30,8 @@ export interface BlockData
|
|||||||
|
|
||||||
tooltip?: BlockTooltip;
|
tooltip?: BlockTooltip;
|
||||||
link?: BlockLink;
|
link?: BlockLink;
|
||||||
tooltipMap?: {[slot: string]: BlockTooltip};
|
tooltipMap?: { [slot: string]: BlockTooltip };
|
||||||
linkMap?: {[slot: string]: BlockLink};
|
linkMap?: { [slot: string]: BlockLink };
|
||||||
|
|
||||||
values: any;
|
values: any;
|
||||||
styles?: any;
|
styles?: any;
|
||||||
@ -39,6 +40,7 @@ export interface BlockData
|
|||||||
|
|
||||||
export interface BlockTooltip
|
export interface BlockTooltip
|
||||||
{
|
{
|
||||||
|
blockData?: CompositeData;
|
||||||
title: string | JSX.Element;
|
title: string | JSX.Element;
|
||||||
placement: string;
|
placement: string;
|
||||||
}
|
}
|
||||||
|
@ -19,15 +19,12 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {QWidgetMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QWidgetMetaData";
|
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 Autocomplete from "@mui/material/Autocomplete";
|
||||||
import Box from "@mui/material/Box";
|
|
||||||
import Icon from "@mui/material/Icon";
|
import Icon from "@mui/material/Icon";
|
||||||
import {styled} from "@mui/material/styles";
|
import {styled} from "@mui/material/styles";
|
||||||
import Table from "@mui/material/Table";
|
import Table from "@mui/material/Table";
|
||||||
import TableBody from "@mui/material/TableBody";
|
|
||||||
import TableContainer from "@mui/material/TableContainer";
|
import TableContainer from "@mui/material/TableContainer";
|
||||||
import TableRow from "@mui/material/TableRow";
|
|
||||||
import Tooltip from "@mui/material/Tooltip";
|
import Tooltip from "@mui/material/Tooltip";
|
||||||
import parse from "html-react-parser";
|
import parse from "html-react-parser";
|
||||||
import colors from "qqq/assets/theme/base/colors";
|
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... */}
|
{/* 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... */}
|
||||||
<Icon fontSize="medium" sx={{float: "left"}}>{row.isExpanded ? "expand_less" : "chevron_right"}</Icon>
|
<Icon fontSize="medium" sx={{float: "left"}}>{row.isExpanded ? "expand_less" : "chevron_left"}</Icon>
|
||||||
</span>
|
</span>
|
||||||
) : null,
|
) : null,
|
||||||
},
|
},
|
||||||
@ -312,7 +309,7 @@ function DataTable({
|
|||||||
{
|
{
|
||||||
boxStyle = isFooter
|
boxStyle = isFooter
|
||||||
? {borderTop: `0.0625rem solid ${colors.grayLines.main};`, backgroundColor: "#EEEEEE"}
|
? {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 = {};
|
let innerBoxStyle = {};
|
||||||
@ -321,143 +318,139 @@ function DataTable({
|
|||||||
innerBoxStyle = {overflowY: "auto", scrollbarGutter: "stable"};
|
innerBoxStyle = {overflowY: "auto", scrollbarGutter: "stable"};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// note - at one point, we had the table's sx including: whiteSpace: "nowrap"... //
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////
|
||||||
return <Box sx={boxStyle}><Box sx={innerBoxStyle}>
|
return <Box sx={boxStyle}><Box sx={innerBoxStyle}>
|
||||||
<Table {...getTableProps()}>
|
<Table {...getTableProps()} component="div" sx={{display: "grid", gridTemplateRows: "auto", gridTemplateColumns: gridTemplateColumns}}>
|
||||||
{
|
{
|
||||||
includeHead && (
|
includeHead && (
|
||||||
<Box component="thead" sx={{position: "sticky", top: 0, background: "white", zIndex: 10}}>
|
headerGroups.map((headerGroup: any, i: number) => (
|
||||||
{headerGroups.map((headerGroup: any, i: number) => (
|
headerGroup.headers.map((column: any) => (
|
||||||
<TableRow key={i} {...headerGroup.getHeaderGroupProps()} sx={{display: "grid", alignItems: "flex-end", gridTemplateColumns: gridTemplateColumns}}>
|
column.type !== "hidden" && (
|
||||||
{headerGroup.headers.map((column: any) => (
|
<DataTableHeadCell
|
||||||
column.type !== "hidden" && (
|
sx={{position: "sticky", top: 0, background: "white", zIndex: 10, alignItems: "flex-end"}}
|
||||||
<DataTableHeadCell
|
key={i++}
|
||||||
key={i++}
|
{...column.getHeaderProps(isSorted && column.getSortByToggleProps())}
|
||||||
{...column.getHeaderProps(isSorted && column.getSortByToggleProps())}
|
align={column.align ? column.align : "left"}
|
||||||
align={column.align ? column.align : "left"}
|
sorted={setSortedValue(column)}
|
||||||
sorted={setSortedValue(column)}
|
tooltip={column.tooltip}
|
||||||
tooltip={column.tooltip}
|
>
|
||||||
>
|
{column.render("header")}
|
||||||
{column.render("header")}
|
</DataTableHeadCell>
|
||||||
</DataTableHeadCell>
|
)
|
||||||
)
|
))
|
||||||
))}
|
))
|
||||||
</TableRow>
|
|
||||||
))}
|
|
||||||
</Box>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
<TableBody {...getTableBodyProps()}>
|
{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);
|
overrideNoEndBorder = true;
|
||||||
|
if (key + 1 < rows.length && rows[key + 1].depth == 0)
|
||||||
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;
|
overrideNoEndBorder = false;
|
||||||
if (key + 1 < rows.length && rows[key + 1].depth == 0)
|
|
||||||
{
|
|
||||||
overrideNoEndBorder = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
///////////////////////////////////////
|
///////////////////////////////////////
|
||||||
// don't do end-border on the footer //
|
// don't do end-border on the footer //
|
||||||
///////////////////////////////////////
|
///////////////////////////////////////
|
||||||
if (isFooter)
|
if (isFooter)
|
||||||
{
|
{
|
||||||
overrideNoEndBorder = true;
|
overrideNoEndBorder = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
let background = "initial";
|
let background = "initial";
|
||||||
if (isFooter)
|
if (isFooter)
|
||||||
{
|
{
|
||||||
background = "#EEEEEE";
|
background = "#EEEEEE";
|
||||||
}
|
}
|
||||||
else if (row.depth > 0 || row.isExpanded)
|
else if (row.depth > 0 || row.isExpanded)
|
||||||
{
|
{
|
||||||
background = "#FAFAFA";
|
background = "#FAFAFA";
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TableRow sx={{verticalAlign: "top", display: "grid", gridTemplateColumns: gridTemplateColumns, background: background}} key={key} {...row.getRowProps()}>
|
row.cells.map((cell: any) => (
|
||||||
{row.cells.map((cell: any) => (
|
cell.column.type !== "hidden" && (
|
||||||
cell.column.type !== "hidden" && (
|
<DataTableBodyCell
|
||||||
<DataTableBodyCell
|
key={key}
|
||||||
key={key}
|
sx={{verticalAlign: "top", background: background}}
|
||||||
noBorder={noEndBorder || overrideNoEndBorder || row.isExpanded}
|
noBorder={noEndBorder || overrideNoEndBorder || row.isExpanded}
|
||||||
depth={row.depth}
|
depth={row.depth}
|
||||||
align={cell.column.align ? cell.column.align : "left"}
|
align={cell.column.align ? cell.column.align : "left"}
|
||||||
{...cell.getCellProps()}
|
{...cell.getCellProps()}
|
||||||
>
|
>
|
||||||
{
|
{
|
||||||
cell.column.type === "default" && (
|
cell.column.type === "default" && (
|
||||||
cell.value && "number" === typeof cell.value ? (
|
cell.value && "number" === typeof cell.value ? (
|
||||||
<DefaultCell isFooter={isFooter}>{cell.value.toLocaleString()}</DefaultCell>
|
<DefaultCell isFooter={isFooter}>{cell.value.toLocaleString()}</DefaultCell>
|
||||||
) : (<DefaultCell isFooter={isFooter}>{cell.render("Cell")}</DefaultCell>)
|
) : (<DefaultCell isFooter={isFooter}>{cell.render("Cell")}</DefaultCell>)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
cell.column.type === "htmlAndTooltip" && (
|
cell.column.type === "htmlAndTooltip" && (
|
||||||
<DefaultCell isFooter={isFooter}>
|
<DefaultCell isFooter={isFooter}>
|
||||||
<NoMaxWidthTooltip title={parse(row.values["tooltip"])}>
|
<NoMaxWidthTooltip title={parse(row.values["tooltip"])}>
|
||||||
<Box>
|
<Box>
|
||||||
{parse(cell.value)}
|
{parse(cell.value)}
|
||||||
</Box>
|
</Box>
|
||||||
</NoMaxWidthTooltip>
|
</NoMaxWidthTooltip>
|
||||||
</DefaultCell>
|
</DefaultCell>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
cell.column.type === "html" && (
|
cell.column.type === "html" && (
|
||||||
<DefaultCell isFooter={isFooter}>{parse(cell.value ?? "")}</DefaultCell>
|
<DefaultCell isFooter={isFooter}>{parse(cell.value ?? "")}</DefaultCell>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
cell.column.type === "composite" && (
|
cell.column.type === "composite" && (
|
||||||
<DefaultCell isFooter={isFooter}>
|
<DefaultCell isFooter={isFooter}>
|
||||||
<CompositeWidget widgetMetaData={widgetMetaData} data={cell.value} />
|
<CompositeWidget widgetMetaData={widgetMetaData} data={cell.value} />
|
||||||
</DefaultCell>
|
</DefaultCell>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
cell.column.type === "block" && (
|
cell.column.type === "block" && (
|
||||||
<DefaultCell isFooter={isFooter}>
|
<DefaultCell isFooter={isFooter}>
|
||||||
<WidgetBlock widgetMetaData={widgetMetaData} block={cell.value} />
|
<WidgetBlock widgetMetaData={widgetMetaData} block={cell.value} />
|
||||||
</DefaultCell>
|
</DefaultCell>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
cell.column.type === "image" && row.values["imageTotal"] && (
|
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"]} />
|
<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"] && (
|
cell.column.type === "image" && !row.values["imageTotal"] && (
|
||||||
<ImageCell imageUrl={row.values["imageUrl"]} label={row.values["imageLabel"]} />
|
<ImageCell imageUrl={row.values["imageUrl"]} label={row.values["imageLabel"]} />
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
(cell.column.id === "__expander") && cell.render("cell")
|
(cell.column.id === "__expander") && cell.render("cell")
|
||||||
}
|
}
|
||||||
</DataTableBodyCell>
|
</DataTableBodyCell>
|
||||||
)
|
)
|
||||||
))}
|
))
|
||||||
</TableRow>
|
);
|
||||||
);
|
})}
|
||||||
})}
|
|
||||||
|
|
||||||
</TableBody>
|
|
||||||
</Table>
|
</Table>
|
||||||
</Box></Box>;
|
</Box></Box>;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TableContainer sx={{boxShadow: "none", height: fixedHeight ? `${fixedHeight}px` : "auto"}}>
|
<TableContainer sx={{boxShadow: "none", height: (fixedHeight && !fixedStickyLastRow) ? `${fixedHeight}px` : "auto"}}>
|
||||||
{entriesPerPage && ((hidePaginationDropdown !== undefined && !hidePaginationDropdown) || canSearch) ? (
|
{entriesPerPage && ((hidePaginationDropdown !== undefined && !hidePaginationDropdown) || canSearch) ? (
|
||||||
<Box display="flex" justifyContent="space-between" alignItems="center" p={3}>
|
<Box display="flex" justifyContent="space-between" alignItems="center" p={3}>
|
||||||
{entriesPerPage && (hidePaginationDropdown === undefined || !hidePaginationDropdown) && (
|
{entriesPerPage && (hidePaginationDropdown === undefined || !hidePaginationDropdown) && (
|
||||||
|
@ -93,41 +93,25 @@ function TableCard({noRowsFoundHTML, data, rowsPerPage, hidePaginationDropdown,
|
|||||||
/>
|
/>
|
||||||
: noRowsFoundHTML ?
|
: noRowsFoundHTML ?
|
||||||
<Box p={3} pt={0} pb={3} sx={{textAlign: "center"}}>
|
<Box p={3} pt={0} pb={3} sx={{textAlign: "center"}}>
|
||||||
<MDTypography
|
<MDTypography variant="subtitle2" color="secondary" fontWeight="regular">
|
||||||
variant="subtitle2"
|
{noRowsFoundHTML ? (parse(noRowsFoundHTML)) : "No rows found"}
|
||||||
color="secondary"
|
|
||||||
fontWeight="regular"
|
|
||||||
>
|
|
||||||
{
|
|
||||||
noRowsFoundHTML ? (
|
|
||||||
parse(noRowsFoundHTML)
|
|
||||||
) : "No rows found"
|
|
||||||
}
|
|
||||||
</MDTypography>
|
</MDTypography>
|
||||||
</Box>
|
</Box>
|
||||||
:
|
:
|
||||||
<TableContainer sx={{boxShadow: "none"}}>
|
<TableContainer sx={{boxShadow: "none"}}>
|
||||||
<Table>
|
<Table component="div" sx={{display: "grid", gridTemplateRows: "auto", gridTemplateColumns: "1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr"}}>
|
||||||
<Box component="thead">
|
{Array(8).fill(0).map((_, i) =>
|
||||||
<TableRow sx={{alignItems: "flex-end"}} key="header">
|
<DataTableHeadCell key={`head-${i}`} sorted={false} width="auto" align="center">
|
||||||
{Array(8).fill(0).map((_, i) =>
|
<Skeleton width="100%" />
|
||||||
<DataTableHeadCell key={`head-${i}`} sorted={false} width="auto" align="center">
|
</DataTableHeadCell>
|
||||||
<Skeleton width="100%" />
|
)}
|
||||||
</DataTableHeadCell>
|
{Array(5).fill(0).map((_, i) =>
|
||||||
)}
|
Array(8).fill(0).map((_, j) =>
|
||||||
</TableRow>
|
<DataTableBodyCell key={`cell-${i}-${j}`} align="center">
|
||||||
</Box>
|
<DefaultCell isFooter={false}><Skeleton /></DefaultCell>
|
||||||
<TableBody>
|
</DataTableBodyCell>
|
||||||
{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 isFooter={false}><Skeleton /></DefaultCell>
|
|
||||||
</DataTableBodyCell>
|
|
||||||
)}
|
|
||||||
</TableRow>
|
|
||||||
)}
|
|
||||||
</TableBody>
|
|
||||||
</Table>
|
</Table>
|
||||||
</TableContainer>
|
</TableContainer>
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import Box from "@mui/material/Box";
|
import {Box} from "@mui/material";
|
||||||
import {Theme} from "@mui/material/styles";
|
import {Theme} from "@mui/material/styles";
|
||||||
import colors from "qqq/assets/theme/base/colors";
|
import colors from "qqq/assets/theme/base/colors";
|
||||||
import {ReactNode} from "react";
|
import {ReactNode} from "react";
|
||||||
@ -30,13 +30,14 @@ interface Props
|
|||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
noBorder?: boolean;
|
noBorder?: boolean;
|
||||||
align?: "left" | "right" | "center";
|
align?: "left" | "right" | "center";
|
||||||
|
sx?: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
function DataTableBodyCell({noBorder, align, children}: Props): JSX.Element
|
function DataTableBodyCell({noBorder, align, sx, children}: Props): JSX.Element
|
||||||
{
|
{
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
component="td"
|
component="div"
|
||||||
textAlign={align}
|
textAlign={align}
|
||||||
py={1.5}
|
py={1.5}
|
||||||
px={1.5}
|
px={1.5}
|
||||||
@ -54,7 +55,7 @@ function DataTableBodyCell({noBorder, align, children}: Props): JSX.Element
|
|||||||
},
|
},
|
||||||
"&:last-child": {
|
"&:last-child": {
|
||||||
paddingRight: "1rem"
|
paddingRight: "1rem"
|
||||||
}
|
}, ...sx
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<Box
|
<Box
|
||||||
@ -72,6 +73,7 @@ function DataTableBodyCell({noBorder, align, children}: Props): JSX.Element
|
|||||||
DataTableBodyCell.defaultProps = {
|
DataTableBodyCell.defaultProps = {
|
||||||
noBorder: false,
|
noBorder: false,
|
||||||
align: "left",
|
align: "left",
|
||||||
|
sx: {}
|
||||||
};
|
};
|
||||||
|
|
||||||
export default DataTableBodyCell;
|
export default DataTableBodyCell;
|
||||||
|
@ -44,18 +44,14 @@ function DataTableHeadCell({width, children, sorted, align, tooltip, ...rest}: P
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
component="th"
|
component="div"
|
||||||
width={width}
|
width={width}
|
||||||
py={1.5}
|
py={1.5}
|
||||||
px={1.5}
|
px={1.5}
|
||||||
sx={({palette: {light}, borders: {borderWidth}}: Theme) => ({
|
sx={({palette: {light}, borders: {borderWidth}}: Theme) => ({
|
||||||
borderBottom: `${borderWidth[1]} solid ${colors.grayLines.main}`,
|
borderBottom: `${borderWidth[1]} solid ${colors.grayLines.main}`,
|
||||||
"&:nth-of-type(1)": {
|
position: "sticky", top: 0, background: "white",
|
||||||
paddingLeft: "1rem"
|
zIndex: 1 // so if body rows scroll behind it, they don't show through
|
||||||
},
|
|
||||||
"&:last-child": {
|
|
||||||
paddingRight: "1rem"
|
|
||||||
},
|
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<Box
|
<Box
|
||||||
|
@ -121,7 +121,7 @@ function ColumnStats({tableMetaData, fieldMetaData, fieldTableName, filter}: Pro
|
|||||||
}
|
}
|
||||||
|
|
||||||
const valueCounts = [] as QRecord[];
|
const valueCounts = [] as QRecord[];
|
||||||
for(let i = 0; i < result.values.valueCounts.length; i++)
|
for(let i = 0; i < result.values.valueCounts?.length; i++)
|
||||||
{
|
{
|
||||||
let valueRecord = new QRecord(result.values.valueCounts[i]);
|
let valueRecord = new QRecord(result.values.valueCounts[i]);
|
||||||
|
|
||||||
|
@ -787,3 +787,20 @@ input[type="search"]::-webkit-search-results-decoration
|
|||||||
{
|
{
|
||||||
margin: 2rem 1rem;
|
margin: 2rem 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* default styles for a block widget overlay */
|
||||||
|
.blockWidgetOverlay
|
||||||
|
{
|
||||||
|
font-weight: 400;
|
||||||
|
position: relative;
|
||||||
|
top: 15px;
|
||||||
|
height: 0;
|
||||||
|
display: flex;
|
||||||
|
font-size: 14px;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.blockWidgetOverlay a
|
||||||
|
{
|
||||||
|
color: #0062FF !important;
|
||||||
|
}
|
||||||
|
@ -56,8 +56,8 @@ public class DashboardTableWidgetExportTest extends QBaseSeleniumTest
|
|||||||
"label": "Sample Table Widget",
|
"label": "Sample Table Widget",
|
||||||
"footerHTML": "<span class='material-icons-round notranslate MuiIcon-root MuiIcon-fontSizeInherit' aria-hidden='true'><span class='dashboard-schedule-icon'>schedule</span></span>Updated at 2023-10-17 09:11:38 AM CDT",
|
"footerHTML": "<span class='material-icons-round notranslate MuiIcon-root MuiIcon-fontSizeInherit' aria-hidden='true'><span class='dashboard-schedule-icon'>schedule</span></span>Updated at 2023-10-17 09:11:38 AM CDT",
|
||||||
"columns": [
|
"columns": [
|
||||||
{ "type": "html", "header": "Id", "accessor": "id", "width": "1%" },
|
{ "type": "html", "header": "Id", "accessor": "id", "width": "30px" },
|
||||||
{ "type": "html", "header": "Name", "accessor": "name", "width": "99%" }
|
{ "type": "html", "header": "Name", "accessor": "name", "width": "1fr" }
|
||||||
],
|
],
|
||||||
"rows": [
|
"rows": [
|
||||||
{ "id": "1", "name": "<a href='/setup/person/1'>Homer S.</a>" },
|
{ "id": "1", "name": "<a href='/setup/person/1'>Homer S.</a>" },
|
||||||
@ -83,7 +83,7 @@ public class DashboardTableWidgetExportTest extends QBaseSeleniumTest
|
|||||||
// assert that the table widget rendered its header and some contents //
|
// assert that the table widget rendered its header and some contents //
|
||||||
////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////
|
||||||
qSeleniumLib.waitForSelectorContaining("#SampleTableWidget h6", "Sample Table Widget");
|
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");
|
qSeleniumLib.waitForSelectorContaining("#SampleTableWidget div", "Updated at 2023-10-17 09:11:38 AM CDT");
|
||||||
|
|
||||||
/////////////////////////////
|
/////////////////////////////
|
||||||
|
Reference in New Issue
Block a user