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 ]
|
||||
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)-.*/
|
||||
|
||||
|
5
pom.xml
5
pom.xml
@ -29,7 +29,7 @@
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<properties>
|
||||
<revision>0.20.0</revision>
|
||||
<revision>0.21.0</revision>
|
||||
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||
@ -66,7 +66,7 @@
|
||||
<dependency>
|
||||
<groupId>com.kingsrook.qqq</groupId>
|
||||
<artifactId>qqq-backend-core</artifactId>
|
||||
<version>0.20.0-20240308.165846-65</version>
|
||||
<version>0.21.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
@ -159,6 +159,7 @@
|
||||
<versionDigitToIncrement>1</versionDigitToIncrement> <!-- In general, we update the minor -->
|
||||
<versionProperty>revision</versionProperty>
|
||||
<skipUpdateVersion>true</skipUpdateVersion>
|
||||
<skipTestProject>true</skipTestProject> <!-- we allow CI to do the tests -->
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
|
@ -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 (
|
||||
<AppBar
|
||||
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} />
|
||||
</Box>
|
||||
{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"}}}>
|
||||
{renderHistory()}
|
||||
</Box>
|
||||
|
@ -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 (<Box sx={boxStyle} className="compositeWidget">
|
||||
{
|
||||
data.blocks.map((block: BlockData, index) => (
|
||||
<React.Fragment key={index}>
|
||||
<WidgetBlock widgetMetaData={widgetMetaData} block={block} />
|
||||
</React.Fragment>
|
||||
))
|
||||
}
|
||||
</Box>);
|
||||
let overlayStyle: any = {};
|
||||
|
||||
if (data?.overlayStyleOverrides)
|
||||
{
|
||||
overlayStyle = {...overlayStyle, ...data.overlayStyleOverrides};
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{
|
||||
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)
|
||||
{
|
||||
// @ts-ignore
|
||||
renderedWidget = (<Grid id={widgetMetaData.name} item xxl={widgetMetaData.gridColumns ? widgetMetaData.gridColumns : 12} xs={12} sx={{display: "flex", alignItems: "stretch", scrollMarginTop: "100px"}}>
|
||||
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 = (<Grid id={widgetMetaData.name} item {...gridProps} sx={{display: "flex", alignItems: "stretch", scrollMarginTop: "100px"}}>
|
||||
{renderedWidget}
|
||||
</Grid>);
|
||||
}
|
||||
|
@ -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 = <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;
|
||||
|
||||
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
|
||||
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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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... */}
|
||||
<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>
|
||||
) : 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 <Box sx={boxStyle}><Box sx={innerBoxStyle}>
|
||||
<Table {...getTableProps()}>
|
||||
<Table {...getTableProps()} component="div" sx={{display: "grid", gridTemplateRows: "auto", gridTemplateColumns: gridTemplateColumns}}>
|
||||
{
|
||||
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", alignItems: "flex-end", gridTemplateColumns: gridTemplateColumns}}>
|
||||
{headerGroup.headers.map((column: any) => (
|
||||
column.type !== "hidden" && (
|
||||
<DataTableHeadCell
|
||||
key={i++}
|
||||
{...column.getHeaderProps(isSorted && column.getSortByToggleProps())}
|
||||
align={column.align ? column.align : "left"}
|
||||
sorted={setSortedValue(column)}
|
||||
tooltip={column.tooltip}
|
||||
>
|
||||
{column.render("header")}
|
||||
</DataTableHeadCell>
|
||||
)
|
||||
))}
|
||||
</TableRow>
|
||||
))}
|
||||
</Box>
|
||||
headerGroups.map((headerGroup: any, i: number) => (
|
||||
headerGroup.headers.map((column: any) => (
|
||||
column.type !== "hidden" && (
|
||||
<DataTableHeadCell
|
||||
sx={{position: "sticky", top: 0, background: "white", zIndex: 10, alignItems: "flex-end"}}
|
||||
key={i++}
|
||||
{...column.getHeaderProps(isSorted && column.getSortByToggleProps())}
|
||||
align={column.align ? column.align : "left"}
|
||||
sorted={setSortedValue(column)}
|
||||
tooltip={column.tooltip}
|
||||
>
|
||||
{column.render("header")}
|
||||
</DataTableHeadCell>
|
||||
)
|
||||
))
|
||||
))
|
||||
)
|
||||
}
|
||||
<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);
|
||||
|
||||
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 (
|
||||
<TableRow sx={{verticalAlign: "top", display: "grid", gridTemplateColumns: gridTemplateColumns, background: background}} key={key} {...row.getRowProps()}>
|
||||
{row.cells.map((cell: any) => (
|
||||
cell.column.type !== "hidden" && (
|
||||
<DataTableBodyCell
|
||||
key={key}
|
||||
noBorder={noEndBorder || overrideNoEndBorder || row.isExpanded}
|
||||
depth={row.depth}
|
||||
align={cell.column.align ? cell.column.align : "left"}
|
||||
{...cell.getCellProps()}
|
||||
>
|
||||
{
|
||||
cell.column.type === "default" && (
|
||||
cell.value && "number" === typeof cell.value ? (
|
||||
<DefaultCell isFooter={isFooter}>{cell.value.toLocaleString()}</DefaultCell>
|
||||
) : (<DefaultCell isFooter={isFooter}>{cell.render("Cell")}</DefaultCell>)
|
||||
)
|
||||
}
|
||||
{
|
||||
cell.column.type === "htmlAndTooltip" && (
|
||||
<DefaultCell isFooter={isFooter}>
|
||||
<NoMaxWidthTooltip title={parse(row.values["tooltip"])}>
|
||||
<Box>
|
||||
{parse(cell.value)}
|
||||
</Box>
|
||||
</NoMaxWidthTooltip>
|
||||
</DefaultCell>
|
||||
)
|
||||
}
|
||||
{
|
||||
cell.column.type === "html" && (
|
||||
<DefaultCell isFooter={isFooter}>{parse(cell.value ?? "")}</DefaultCell>
|
||||
)
|
||||
}
|
||||
{
|
||||
cell.column.type === "composite" && (
|
||||
<DefaultCell isFooter={isFooter}>
|
||||
<CompositeWidget widgetMetaData={widgetMetaData} data={cell.value} />
|
||||
</DefaultCell>
|
||||
)
|
||||
}
|
||||
{
|
||||
cell.column.type === "block" && (
|
||||
<DefaultCell isFooter={isFooter}>
|
||||
<WidgetBlock widgetMetaData={widgetMetaData} block={cell.value} />
|
||||
</DefaultCell>
|
||||
)
|
||||
}
|
||||
{
|
||||
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"]} />
|
||||
)
|
||||
}
|
||||
{
|
||||
cell.column.type === "image" && !row.values["imageTotal"] && (
|
||||
<ImageCell imageUrl={row.values["imageUrl"]} label={row.values["imageLabel"]} />
|
||||
)
|
||||
}
|
||||
{
|
||||
(cell.column.id === "__expander") && cell.render("cell")
|
||||
}
|
||||
</DataTableBodyCell>
|
||||
)
|
||||
))}
|
||||
</TableRow>
|
||||
);
|
||||
})}
|
||||
|
||||
</TableBody>
|
||||
return (
|
||||
row.cells.map((cell: any) => (
|
||||
cell.column.type !== "hidden" && (
|
||||
<DataTableBodyCell
|
||||
key={key}
|
||||
sx={{verticalAlign: "top", background: background}}
|
||||
noBorder={noEndBorder || overrideNoEndBorder || row.isExpanded}
|
||||
depth={row.depth}
|
||||
align={cell.column.align ? cell.column.align : "left"}
|
||||
{...cell.getCellProps()}
|
||||
>
|
||||
{
|
||||
cell.column.type === "default" && (
|
||||
cell.value && "number" === typeof cell.value ? (
|
||||
<DefaultCell isFooter={isFooter}>{cell.value.toLocaleString()}</DefaultCell>
|
||||
) : (<DefaultCell isFooter={isFooter}>{cell.render("Cell")}</DefaultCell>)
|
||||
)
|
||||
}
|
||||
{
|
||||
cell.column.type === "htmlAndTooltip" && (
|
||||
<DefaultCell isFooter={isFooter}>
|
||||
<NoMaxWidthTooltip title={parse(row.values["tooltip"])}>
|
||||
<Box>
|
||||
{parse(cell.value)}
|
||||
</Box>
|
||||
</NoMaxWidthTooltip>
|
||||
</DefaultCell>
|
||||
)
|
||||
}
|
||||
{
|
||||
cell.column.type === "html" && (
|
||||
<DefaultCell isFooter={isFooter}>{parse(cell.value ?? "")}</DefaultCell>
|
||||
)
|
||||
}
|
||||
{
|
||||
cell.column.type === "composite" && (
|
||||
<DefaultCell isFooter={isFooter}>
|
||||
<CompositeWidget widgetMetaData={widgetMetaData} data={cell.value} />
|
||||
</DefaultCell>
|
||||
)
|
||||
}
|
||||
{
|
||||
cell.column.type === "block" && (
|
||||
<DefaultCell isFooter={isFooter}>
|
||||
<WidgetBlock widgetMetaData={widgetMetaData} block={cell.value} />
|
||||
</DefaultCell>
|
||||
)
|
||||
}
|
||||
{
|
||||
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"]} />
|
||||
)
|
||||
}
|
||||
{
|
||||
cell.column.type === "image" && !row.values["imageTotal"] && (
|
||||
<ImageCell imageUrl={row.values["imageUrl"]} label={row.values["imageLabel"]} />
|
||||
)
|
||||
}
|
||||
{
|
||||
(cell.column.id === "__expander") && cell.render("cell")
|
||||
}
|
||||
</DataTableBodyCell>
|
||||
)
|
||||
))
|
||||
);
|
||||
})}
|
||||
</Table>
|
||||
</Box></Box>;
|
||||
}
|
||||
|
||||
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) ? (
|
||||
<Box display="flex" justifyContent="space-between" alignItems="center" p={3}>
|
||||
{entriesPerPage && (hidePaginationDropdown === undefined || !hidePaginationDropdown) && (
|
||||
|
@ -93,41 +93,25 @@ function TableCard({noRowsFoundHTML, data, rowsPerPage, hidePaginationDropdown,
|
||||
/>
|
||||
: noRowsFoundHTML ?
|
||||
<Box p={3} pt={0} pb={3} sx={{textAlign: "center"}}>
|
||||
<MDTypography
|
||||
variant="subtitle2"
|
||||
color="secondary"
|
||||
fontWeight="regular"
|
||||
>
|
||||
{
|
||||
noRowsFoundHTML ? (
|
||||
parse(noRowsFoundHTML)
|
||||
) : "No rows found"
|
||||
}
|
||||
<MDTypography variant="subtitle2" color="secondary" fontWeight="regular">
|
||||
{noRowsFoundHTML ? (parse(noRowsFoundHTML)) : "No rows found"}
|
||||
</MDTypography>
|
||||
</Box>
|
||||
:
|
||||
<TableContainer sx={{boxShadow: "none"}}>
|
||||
<Table>
|
||||
<Box component="thead">
|
||||
<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%" />
|
||||
</DataTableHeadCell>
|
||||
)}
|
||||
</TableRow>
|
||||
</Box>
|
||||
<TableBody>
|
||||
{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 component="div" sx={{display: "grid", gridTemplateRows: "auto", gridTemplateColumns: "1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr"}}>
|
||||
{Array(8).fill(0).map((_, i) =>
|
||||
<DataTableHeadCell key={`head-${i}`} sorted={false} width="auto" align="center">
|
||||
<Skeleton width="100%" />
|
||||
</DataTableHeadCell>
|
||||
)}
|
||||
{Array(5).fill(0).map((_, i) =>
|
||||
Array(8).fill(0).map((_, j) =>
|
||||
<DataTableBodyCell key={`cell-${i}-${j}`} align="center">
|
||||
<DefaultCell isFooter={false}><Skeleton /></DefaultCell>
|
||||
</DataTableBodyCell>
|
||||
)
|
||||
)}
|
||||
</Table>
|
||||
</TableContainer>
|
||||
}
|
||||
|
@ -19,7 +19,7 @@
|
||||
* 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 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 (
|
||||
<Box
|
||||
component="td"
|
||||
component="div"
|
||||
textAlign={align}
|
||||
py={1.5}
|
||||
px={1.5}
|
||||
@ -54,7 +55,7 @@ function DataTableBodyCell({noBorder, align, children}: Props): JSX.Element
|
||||
},
|
||||
"&:last-child": {
|
||||
paddingRight: "1rem"
|
||||
}
|
||||
}, ...sx
|
||||
})}
|
||||
>
|
||||
<Box
|
||||
@ -72,6 +73,7 @@ function DataTableBodyCell({noBorder, align, children}: Props): JSX.Element
|
||||
DataTableBodyCell.defaultProps = {
|
||||
noBorder: false,
|
||||
align: "left",
|
||||
sx: {}
|
||||
};
|
||||
|
||||
export default DataTableBodyCell;
|
||||
|
@ -44,18 +44,14 @@ function DataTableHeadCell({width, children, sorted, align, tooltip, ...rest}: P
|
||||
|
||||
return (
|
||||
<Box
|
||||
component="th"
|
||||
component="div"
|
||||
width={width}
|
||||
py={1.5}
|
||||
px={1.5}
|
||||
sx={({palette: {light}, borders: {borderWidth}}: Theme) => ({
|
||||
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
|
||||
})}
|
||||
>
|
||||
<Box
|
||||
|
@ -121,7 +121,7 @@ function ColumnStats({tableMetaData, fieldMetaData, fieldTableName, filter}: Pro
|
||||
}
|
||||
|
||||
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]);
|
||||
|
||||
|
@ -787,3 +787,20 @@ input[type="search"]::-webkit-search-results-decoration
|
||||
{
|
||||
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",
|
||||
"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": [
|
||||
{ "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": "<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 //
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
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");
|
||||
|
||||
/////////////////////////////
|
||||
|
Reference in New Issue
Block a user