Compare commits

..

10 Commits

Author SHA1 Message Date
39a7aadd3f CE-989 add option to (not) includeTableCountsOn(app)HomeScreen 2024-03-07 20:30:14 -06:00
167af989d5 Add tooltips from metaData/helpContent to widget blocks. 2024-03-05 14:36:54 -06:00
ad7ea994a8 CE-889 - try to fix NPE's on localeCompares 2024-03-04 15:09:22 -06:00
e925310173 Merge pull request #44 from Kingsrook/feature/CE-878-make-the-operations-dashboard
CE-878: updated to allow sublabel to be displayed under label
2024-03-04 14:47:52 -06:00
8ebc2415fe Merged feature/CE-878-make-the-operations-dashboard into main 2024-02-29 09:39:08 -06:00
88a4c17bbc CE-798 follow-up - in cleanupValuesInFilerFromQueryString, don't try to translate [null] to a list of possible values (which fetches all of them)... 2024-02-27 13:57:04 -06:00
2900cd8593 CE-798 follow-up - Prevent tab in date/date-time filter value input boxes from closing a quick-filter menu (via an onKeyDown handler) 2024-02-27 13:35:37 -06:00
8ab0f5f549 CE-798 follow-up - increase width of date-time boxes (was too small to see all the fields!) 2024-02-27 11:56:54 -06:00
8cffbbcac4 Update to cimg/openjdk:17.0.9 2024-02-22 20:30:51 -06:00
37eb280d79 Revert to previous check for disabling export-menu items - that is - totalRecords === 0, instead of !totalRecords. makes tables w/o count allowed to do exports again. 2024-02-22 12:16:27 -06:00
16 changed files with 205 additions and 64 deletions

View File

@ -7,7 +7,7 @@ orbs:
executors: executors:
java17: java17:
docker: docker:
- image: 'cimg/openjdk:17.0' - image: 'cimg/openjdk:17.0.9'
commands: commands:
install_java17: install_java17:

View File

@ -36,7 +36,7 @@ import Icon from "@mui/material/Icon";
import Typography from "@mui/material/Typography"; import Typography from "@mui/material/Typography";
import {makeStyles} from "@mui/styles"; import {makeStyles} from "@mui/styles";
import {Command} from "cmdk"; import {Command} from "cmdk";
import React, {useContext, useEffect, useRef, useState} from "react"; import React, {useContext, useEffect, useRef} from "react";
import {useNavigate} from "react-router-dom"; import {useNavigate} from "react-router-dom";
import QContext from "QContext"; import QContext from "QContext";
import HistoryUtils, {QHistoryEntry} from "qqq/utils/HistoryUtils"; import HistoryUtils, {QHistoryEntry} from "qqq/utils/HistoryUtils";
@ -174,7 +174,9 @@ const CommandMenu = ({metaData}: Props) =>
}) })
tableNames = tableNames.sort((a: string, b:string) => tableNames = tableNames.sort((a: string, b:string) =>
{ {
return (metaData.tables.get(a).label.localeCompare(metaData.tables.get(b).label)); const labelA = metaData.tables.get(a).label ?? "";
const labelB = metaData.tables.get(b).label ?? "";
return (labelA.localeCompare(labelB));
}) })
const path = location.pathname; const path = location.pathname;
@ -222,7 +224,9 @@ const CommandMenu = ({metaData}: Props) =>
}) })
tableNames = tableNames.sort((a: string, b:string) => tableNames = tableNames.sort((a: string, b:string) =>
{ {
return (metaData.tables.get(a).label.localeCompare(metaData.tables.get(b).label)); const labelA = metaData.tables.get(a).label ?? "";
const labelB = metaData.tables.get(b).label ?? "";
return (labelA.localeCompare(labelB));
}) })
return( return(
<Command.Group heading="Tables"> <Command.Group heading="Tables">
@ -252,7 +256,9 @@ const CommandMenu = ({metaData}: Props) =>
appNames = appNames.sort((a: string, b:string) => appNames = appNames.sort((a: string, b:string) =>
{ {
return (getFullAppLabel(metaData.appTree, a, 1, "").localeCompare(getFullAppLabel(metaData.appTree, b, 1, ""))); const labelA = getFullAppLabel(metaData.appTree, a, 1, "") ?? "";
const labelB = getFullAppLabel(metaData.appTree, b, 1, "") ?? "";
return (labelA.localeCompare(labelB));
}) })
return( return(
@ -286,7 +292,9 @@ const CommandMenu = ({metaData}: Props) =>
appNames = appNames.sort((a: string, b:string) => appNames = appNames.sort((a: string, b:string) =>
{ {
return (metaData.apps.get(a).label.localeCompare(metaData.apps.get(b).label)); const labelA = metaData.apps.get(a).label ?? "";
const labelB = metaData.apps.get(b).label ?? "";
return (labelA.localeCompare(labelB));
}) })
const entryMap = new Map<string, boolean>(); const entryMap = new Map<string, boolean>();

View File

@ -22,7 +22,9 @@
package com.kingsrook.qqq.frontend.materialdashboard.model.metadata; package com.kingsrook.qqq.frontend.materialdashboard.model.metadata;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QSupplementalAppMetaData; import com.kingsrook.qqq.backend.core.model.metadata.layout.QSupplementalAppMetaData;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
/******************************************************************************* /*******************************************************************************
@ -30,7 +32,39 @@ import com.kingsrook.qqq.backend.core.model.metadata.layout.QSupplementalAppMeta
*******************************************************************************/ *******************************************************************************/
public class MaterialDashboardAppMetaData extends QSupplementalAppMetaData public class MaterialDashboardAppMetaData extends QSupplementalAppMetaData
{ {
public static final String TYPE_NAME = "materialDashboard";
private Boolean showAppLabelOnHomeScreen = true; private Boolean showAppLabelOnHomeScreen = true;
private Boolean includeTableCountsOnHomeScreen = true;
/*******************************************************************************
**
*******************************************************************************/
public static MaterialDashboardAppMetaData of(QAppMetaData app)
{
return ((MaterialDashboardAppMetaData) CollectionUtils.nonNullMap(app.getSupplementalMetaData()).get(TYPE_NAME));
}
/*******************************************************************************
** either get the supplemental meta dat attached to an app - or create a new one
** and attach it to the app, and return that.
*******************************************************************************/
public static MaterialDashboardAppMetaData ofOrWithNew(QAppMetaData app)
{
MaterialDashboardAppMetaData materialDashboardAppMetaData = of(app);
if(materialDashboardAppMetaData == null)
{
materialDashboardAppMetaData = new MaterialDashboardAppMetaData();
app.withSupplementalMetaData(materialDashboardAppMetaData);
}
return (materialDashboardAppMetaData);
}
@ -51,7 +85,7 @@ public class MaterialDashboardAppMetaData extends QSupplementalAppMetaData
@Override @Override
public String getType() public String getType()
{ {
return ("materialDashboard"); return TYPE_NAME;
} }
@ -85,4 +119,35 @@ public class MaterialDashboardAppMetaData extends QSupplementalAppMetaData
return (this); return (this);
} }
/*******************************************************************************
** Getter for includeTableCountsOnHomeScreen
*******************************************************************************/
public Boolean getIncludeTableCountsOnHomeScreen()
{
return (this.includeTableCountsOnHomeScreen);
}
/*******************************************************************************
** Setter for includeTableCountsOnHomeScreen
*******************************************************************************/
public void setIncludeTableCountsOnHomeScreen(Boolean includeTableCountsOnHomeScreen)
{
this.includeTableCountsOnHomeScreen = includeTableCountsOnHomeScreen;
}
/*******************************************************************************
** Fluent setter for includeTableCountsOnHomeScreen
*******************************************************************************/
public MaterialDashboardAppMetaData withIncludeTableCountsOnHomeScreen(Boolean includeTableCountsOnHomeScreen)
{
this.includeTableCountsOnHomeScreen = includeTableCountsOnHomeScreen;
return (this);
}
} }

View File

@ -45,7 +45,7 @@ export default function ExportMenuItem(props: QExportMenuItemProps)
return ( return (
<MenuItem <MenuItem
disabled={!totalRecords} disabled={totalRecords === 0}
onClick={() => onClick={() =>
{ {
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////

View File

@ -94,6 +94,24 @@ export const makeTextField = (field: QFieldMetaData, criteria: QFilterCriteriaWi
document.getElementById(`${idPrefix}${criteria.id}`).focus(); document.getElementById(`${idPrefix}${criteria.id}`).focus();
}; };
/*******************************************************************************
** Event handler for key-down events - specifically added here, to stop pressing
** 'tab' in a date or date-time from closing the quick-filter...
*******************************************************************************/
const handleKeyDown = (e: any) =>
{
if (field.type == QFieldType.DATE || field.type == QFieldType.DATE_TIME)
{
if(e.code == "Tab")
{
console.log("Tab on date or date-time - don't close me, just move to the next sub-field!...");
e.stopPropagation();
}
}
};
const inputProps: any = {}; const inputProps: any = {};
inputProps.endAdornment = ( inputProps.endAdornment = (
<InputAdornment position="end"> <InputAdornment position="end">
@ -110,6 +128,7 @@ export const makeTextField = (field: QFieldMetaData, criteria: QFilterCriteriaWi
autoComplete="off" autoComplete="off"
type={type} type={type}
onChange={(event) => valueChangeHandler(event, valueIndex)} onChange={(event) => valueChangeHandler(event, valueIndex)}
onKeyDown={handleKeyDown}
value={value} value={value}
InputLabelProps={inputLabelProps} InputLabelProps={inputLabelProps}
InputProps={inputProps} InputProps={inputProps}

View File

@ -504,7 +504,7 @@ export default function QuickFilter({tableMetaData, fullFieldName, fieldMetaData
////////////////////////////// //////////////////////////////
// return the button & menu // // return the button & menu //
////////////////////////////// //////////////////////////////
const widthAndMaxWidth = 250 const widthAndMaxWidth = fieldMetaData?.type == QFieldType.DATE_TIME ? 275 : 250
return ( return (
<> <>
{button} {button}

View File

@ -21,8 +21,6 @@
import BlockElementWrapper from "qqq/components/widgets/blocks/BlockElementWrapper"; import BlockElementWrapper from "qqq/components/widgets/blocks/BlockElementWrapper";
import {StandardBlockComponentProps} from "qqq/components/widgets/blocks/BlockModels"; import {StandardBlockComponentProps} from "qqq/components/widgets/blocks/BlockModels";
import UpOrDownNumberBlock from "qqq/components/widgets/blocks/UpOrDownNumberBlock";
/******************************************************************************* /*******************************************************************************
@ -40,7 +38,7 @@ export default function BigNumberBlock({widgetMetaData, data}: StandardBlockComp
<div style={{width: data.styles.width ?? "auto"}}> <div style={{width: data.styles.width ?? "auto"}}>
<div style={{fontWeight: "700", fontSize: "0.875rem", color: "#3D3D3D", marginBottom: "-0.5rem"}}> <div style={{fontWeight: "700", fontSize: "0.875rem", color: "#3D3D3D", marginBottom: "-0.5rem"}}>
<BlockElementWrapper data={data} slot="heading"> <BlockElementWrapper metaData={widgetMetaData} data={data} slot="heading">
<span>{data.values.heading}</span> <span>{data.values.heading}</span>
</BlockElementWrapper> </BlockElementWrapper>
</div> </div>
@ -49,14 +47,14 @@ export default function BigNumberBlock({widgetMetaData, data}: StandardBlockComp
<div style={{display: "flex", alignItems: "baseline"}}> <div style={{display: "flex", alignItems: "baseline"}}>
<div style={{fontWeight: "700", fontSize: "2rem", marginRight: "0.25rem"}}> <div style={{fontWeight: "700", fontSize: "2rem", marginRight: "0.25rem"}}>
<BlockElementWrapper data={data} slot="number"> <BlockElementWrapper metaData={widgetMetaData} data={data} slot="number">
<span style={{color: data.styles.numberColor}}>{data.values.number}</span> <span style={{color: data.styles.numberColor}}>{data.values.number}</span>
</BlockElementWrapper> </BlockElementWrapper>
</div> </div>
{ {
data.values.context && data.values.context &&
<div style={{fontWeight: "500", fontSize: "0.875rem", color: "#7b809a"}}> <div style={{fontWeight: "500", fontSize: "0.875rem", color: "#7b809a"}}>
<BlockElementWrapper data={data} slot="context"> <BlockElementWrapper metaData={widgetMetaData} data={data} slot="context">
<span>{data.values.context}</span> <span>{data.values.context}</span>
</BlockElementWrapper> </BlockElementWrapper>
</div> </div>

View File

@ -20,14 +20,18 @@
*/ */
import {QWidgetMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QWidgetMetaData";
import {Tooltip} from "@mui/material"; import {Tooltip} from "@mui/material";
import React, {ReactElement} from "react"; import React, {ReactElement, useContext} from "react";
import {Link} from "react-router-dom"; import {Link} from "react-router-dom";
import QContext from "QContext";
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";
interface BlockElementWrapperProps interface BlockElementWrapperProps
{ {
data: BlockData; data: BlockData;
metaData: QWidgetMetaData;
slot: string slot: string
linkProps?: any; linkProps?: any;
children: ReactElement; children: ReactElement;
@ -36,8 +40,10 @@ interface BlockElementWrapperProps
/******************************************************************************* /*******************************************************************************
** For Blocks - wrap their "slot" elements with an optional tooltip and/or link ** For Blocks - wrap their "slot" elements with an optional tooltip and/or link
*******************************************************************************/ *******************************************************************************/
export default function BlockElementWrapper({data, slot, linkProps, children}: BlockElementWrapperProps): JSX.Element export default function BlockElementWrapper({data, metaData, slot, linkProps, children}: BlockElementWrapperProps): JSX.Element
{ {
const {helpHelpActive} = useContext(QContext);
let link: BlockLink; let link: BlockLink;
let tooltip: BlockTooltip; let tooltip: BlockTooltip;
@ -61,6 +67,26 @@ export default function BlockElementWrapper({data, slot, linkProps, children}: B
tooltip = data.tooltip; tooltip = data.tooltip;
} }
if(!tooltip)
{
const helpRoles = ["ALL_SCREENS"]
///////////////////////////////////////////////////////////////////////////////////////////////
// the full keys in the helpContent table will look like: //
// widget:MyCoolWidget;slot=myBlockId,label (if the block has a blockId in data) //
// widget:MyCoolWidget;slot=label (no blockId; note, label is slot name here) //
// in the widget metaData, the map of helpContent will just have the "slot" portion as a key //
///////////////////////////////////////////////////////////////////////////////////////////////
const key = data.blockId ? `${data.blockId},${slot}` : slot;
const showHelp = helpHelpActive || hasHelpContent(metaData?.helpContent?.get(key), helpRoles);
if(showHelp)
{
const formattedHelpContent = <HelpContent helpContents={metaData?.helpContent?.get(key)} roles={helpRoles} helpContentKey={`widget:${metaData?.name};slot:${key}`} />;
tooltip = {title: formattedHelpContent, placement: "bottom"}
}
}
let rs = children; let rs = children;
if(link) if(link)

View File

@ -24,6 +24,7 @@ import {QWidgetMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/Q
export interface BlockData export interface BlockData
{ {
blockId?: string;
blockTypeName: string; blockTypeName: string;
tooltip?: BlockTooltip; tooltip?: BlockTooltip;
@ -38,7 +39,7 @@ export interface BlockData
export interface BlockTooltip export interface BlockTooltip
{ {
title: string; title: string | JSX.Element;
placement: string; placement: string;
} }

View File

@ -28,19 +28,19 @@ import {StandardBlockComponentProps} from "qqq/components/widgets/blocks/BlockMo
** **
** ${number} ${icon} ** ${number} ${icon}
*******************************************************************************/ *******************************************************************************/
export default function NumberIconBadgeBlock({data}: StandardBlockComponentProps): JSX.Element export default function NumberIconBadgeBlock({widgetMetaData, data}: StandardBlockComponentProps): JSX.Element
{ {
return ( return (
<div style={{display: "inline-block", whiteSpace: "nowrap", color: data.styles.color}}> <div style={{display: "inline-block", whiteSpace: "nowrap", color: data.styles.color}}>
{ {
data.values.number && data.values.number &&
<BlockElementWrapper data={data} slot="number"> <BlockElementWrapper metaData={widgetMetaData} data={data} slot="number">
<span style={{color: data.styles.color, fontSize: "0.875rem"}}>{data.values.number}</span> <span style={{color: data.styles.color, fontSize: "0.875rem"}}>{data.values.number}</span>
</BlockElementWrapper> </BlockElementWrapper>
} }
{ {
data.values.iconName && data.values.iconName &&
<BlockElementWrapper data={data} slot="icon"> <BlockElementWrapper metaData={widgetMetaData} data={data} slot="icon">
<Icon style={{color: data.styles.color, fontSize: "1rem", position: "relative", top: "3px"}}>{data.values.iconName}</Icon> <Icon style={{color: data.styles.color, fontSize: "1rem", position: "relative", top: "3px"}}>{data.values.iconName}</Icon>
</BlockElementWrapper> </BlockElementWrapper>
} }

View File

@ -35,14 +35,14 @@ import {StandardBlockComponentProps} from "qqq/components/widgets/blocks/BlockMo
** ${heading} ** ${heading}
** ${bar} ${value} ** ${bar} ${value}
*******************************************************************************/ *******************************************************************************/
export default function ProgressBarBlock({data}: StandardBlockComponentProps): JSX.Element export default function ProgressBarBlock({widgetMetaData, data}: StandardBlockComponentProps): JSX.Element
{ {
return ( return (
<Typography component="div" variant="button" color="text" fontWeight="light" sx={{textTransform: "none"}}> <Typography component="div" variant="button" color="text" fontWeight="light" sx={{textTransform: "none"}}>
{ {
data.values.heading && data.values.heading &&
<div style={{marginBottom: "0.25rem", fontWeight: 500, color: "#3D3D3D"}}> <div style={{marginBottom: "0.25rem", fontWeight: 500, color: "#3D3D3D"}}>
<BlockElementWrapper data={data} slot="heading"> <BlockElementWrapper metaData={widgetMetaData} data={data} slot="heading">
<span>{data.values.heading}</span> <span>{data.values.heading}</span>
</BlockElementWrapper> </BlockElementWrapper>
</div> </div>
@ -50,7 +50,7 @@ export default function ProgressBarBlock({data}: StandardBlockComponentProps): J
<div style={{display: "flex", alignItems: "center", marginBottom: "0.75rem"}}> <div style={{display: "flex", alignItems: "center", marginBottom: "0.75rem"}}>
<BlockElementWrapper data={data} slot="bar" linkProps={{style: {width: "100%"}}}> <BlockElementWrapper metaData={widgetMetaData} data={data} slot="bar" linkProps={{style: {width: "100%"}}}>
<div style={{background: "#E0E0E0", width: "100%", borderRadius: "0.5rem", height: "1rem"}}> <div style={{background: "#E0E0E0", width: "100%", borderRadius: "0.5rem", height: "1rem"}}>
{ {
data.values.percent > 0 ? <div style={{background: data.styles.barColor ?? "#0062ff", minWidth: "1rem", width: `${data.values.percent}%`, borderRadius: "0.5rem", height: "1rem"}}></div> : <></> data.values.percent > 0 ? <div style={{background: data.styles.barColor ?? "#0062ff", minWidth: "1rem", width: `${data.values.percent}%`, borderRadius: "0.5rem", height: "1rem"}}></div> : <></>
@ -59,7 +59,7 @@ export default function ProgressBarBlock({data}: StandardBlockComponentProps): J
</BlockElementWrapper> </BlockElementWrapper>
<div style={{width: "60px", textAlign: "right", fontWeight: 600, color: "#3D3D3D"}}> <div style={{width: "60px", textAlign: "right", fontWeight: 600, color: "#3D3D3D"}}>
<BlockElementWrapper data={data} slot="value"> <BlockElementWrapper metaData={widgetMetaData} data={data} slot="value">
<span>{data.values.value ?? `${(data.values.percent as number).toFixed(1)}%`}</span> <span>{data.values.value ?? `${(data.values.percent as number).toFixed(1)}%`}</span>
</BlockElementWrapper> </BlockElementWrapper>
</div> </div>

View File

@ -29,7 +29,7 @@ import {StandardBlockComponentProps} from "qqq/components/widgets/blocks/BlockMo
** **
** ${label} ${value} ** ${label} ${value}
*******************************************************************************/ *******************************************************************************/
export default function TableSubRowDetailRowBlock({data}: StandardBlockComponentProps): JSX.Element export default function TableSubRowDetailRowBlock({widgetMetaData, data}: StandardBlockComponentProps): JSX.Element
{ {
return ( return (
<div style={{display: "flex", maxWidth: "calc(100% - 24px)", justifyContent: "space-between"}}> <div style={{display: "flex", maxWidth: "calc(100% - 24px)", justifyContent: "space-between"}}>
@ -37,7 +37,7 @@ export default function TableSubRowDetailRowBlock({data}: StandardBlockComponent
{ {
data.values.label && data.values.label &&
<div style={{overflow: "hidden", whiteSpace: "nowrap", textOverflow: "ellipsis"}}> <div style={{overflow: "hidden", whiteSpace: "nowrap", textOverflow: "ellipsis"}}>
<BlockElementWrapper data={data} slot="label"> <BlockElementWrapper metaData={widgetMetaData} data={data} slot="label">
<span style={{color: data.styles.labelColor}}>{data.values.label}</span> <span style={{color: data.styles.labelColor}}>{data.values.label}</span>
</BlockElementWrapper> </BlockElementWrapper>
</div> </div>
@ -45,7 +45,7 @@ export default function TableSubRowDetailRowBlock({data}: StandardBlockComponent
{ {
data.values.value && data.values.value &&
<BlockElementWrapper data={data} slot="value"> <BlockElementWrapper metaData={widgetMetaData} data={data} slot="value">
<span style={{color: data.styles.valueColor}}>{data.values.value}</span> <span style={{color: data.styles.valueColor}}>{data.values.value}</span>
</BlockElementWrapper> </BlockElementWrapper>
} }

View File

@ -27,10 +27,10 @@ import {StandardBlockComponentProps} from "qqq/components/widgets/blocks/BlockMo
** **
** ${text} ** ${text}
*******************************************************************************/ *******************************************************************************/
export default function TextBlock({data}: StandardBlockComponentProps): JSX.Element export default function TextBlock({widgetMetaData, data}: StandardBlockComponentProps): JSX.Element
{ {
return ( return (
<BlockElementWrapper data={data} slot=""> <BlockElementWrapper metaData={widgetMetaData} data={data} slot="">
<span>{data.values.text}</span> <span>{data.values.text}</span>
</BlockElementWrapper> </BlockElementWrapper>
); );

View File

@ -35,7 +35,7 @@ import {StandardBlockComponentProps} from "qqq/components/widgets/blocks/BlockMo
** ${icon} ${number} ** ${icon} ${number}
** ${context} ** ${context}
*******************************************************************************/ *******************************************************************************/
export default function UpOrDownNumberBlock({data}: StandardBlockComponentProps): JSX.Element export default function UpOrDownNumberBlock({widgetMetaData, data}: StandardBlockComponentProps): JSX.Element
{ {
if (!data.styles) if (!data.styles)
{ {
@ -61,7 +61,7 @@ export default function UpOrDownNumberBlock({data}: StandardBlockComponentProps)
<div style={{display: "flex", flexDirection: data.styles.isStacked ? "column" : "row", alignItems: data.styles.isStacked ? "flex-end" : "baseline"}}> <div style={{display: "flex", flexDirection: data.styles.isStacked ? "column" : "row", alignItems: data.styles.isStacked ? "flex-end" : "baseline"}}>
<div style={{display: "flex", alignItems: "baseline", fontWeight: 700, fontSize: ".875rem"}}> <div style={{display: "flex", alignItems: "baseline", fontWeight: 700, fontSize: ".875rem"}}>
<BlockElementWrapper data={data} slot="number"> <BlockElementWrapper metaData={widgetMetaData} data={data} slot="number">
<> <>
<Icon sx={{color: goodOrBadColor, alignSelf: "flex-end", fontSize: "2.25rem !important", lineHeight: "0.875rem", height: "1rem", width: "2rem",}}>{iconName}</Icon> <Icon sx={{color: goodOrBadColor, alignSelf: "flex-end", fontSize: "2.25rem !important", lineHeight: "0.875rem", height: "1rem", width: "2rem",}}>{iconName}</Icon>
<span style={{color: goodOrBadColor}}>{data.values.number}</span> <span style={{color: goodOrBadColor}}>{data.values.number}</span>
@ -70,7 +70,7 @@ export default function UpOrDownNumberBlock({data}: StandardBlockComponentProps)
</div> </div>
<div style={{fontWeight: 500, fontSize: "0.875rem", color: "#7b809a", marginLeft: "0.25rem"}}> <div style={{fontWeight: 500, fontSize: "0.875rem", color: "#7b809a", marginLeft: "0.25rem"}}>
<BlockElementWrapper data={data} slot="context"> <BlockElementWrapper metaData={widgetMetaData} data={data} slot="context">
<span>{data.values.context}</span> <span>{data.values.context}</span>
</BlockElementWrapper> </BlockElementWrapper>
</div> </div>

View File

@ -77,9 +77,11 @@ function AppHome({app}: Props): JSX.Element
const mdbMetaData = app?.supplementalAppMetaData?.get("materialDashboard"); const mdbMetaData = app?.supplementalAppMetaData?.get("materialDashboard");
let showAppLabelOnHomeScreen = true; let showAppLabelOnHomeScreen = true;
let includeTableCountsOnHomeScreen = true;
if(mdbMetaData) if(mdbMetaData)
{ {
showAppLabelOnHomeScreen = mdbMetaData.showAppLabelOnHomeScreen; showAppLabelOnHomeScreen = mdbMetaData.showAppLabelOnHomeScreen;
includeTableCountsOnHomeScreen = mdbMetaData.includeTableCountsOnHomeScreen;
} }
useEffect(() => useEffect(() =>
@ -129,48 +131,60 @@ function AppHome({app}: Props): JSX.Element
const tableCountTexts = new Map<string, string>(); const tableCountTexts = new Map<string, string>();
newTables.forEach((table) => newTables.forEach((table) =>
{ {
tableCounts.set(table.name, {isLoading: true, value: null}); if(includeTableCountsOnHomeScreen)
setTimeout(async () =>
{ {
const tableMetaData = await qController.loadTableMetaData(table.name); tableCounts.set(table.name, {isLoading: true, value: null});
let countResult = null; setTimeout(async () =>
if(tableMetaData.capabilities.has(Capability.TABLE_COUNT) && tableMetaData.readPermission)
{ {
try const tableMetaData = await qController.loadTableMetaData(table.name);
let countResult = null;
if (tableMetaData.capabilities.has(Capability.TABLE_COUNT) && tableMetaData.readPermission)
{ {
[countResult] = await qController.count(table.name); try
{
[countResult] = await qController.count(table.name);
if (countResult !== null && countResult !== undefined) if (countResult !== null && countResult !== undefined)
{ {
tableCountNumbers.set(table.name, countResult.toLocaleString()); tableCountNumbers.set(table.name, countResult.toLocaleString());
tableCountTexts.set(table.name, countResult === 1 ? "total record" : "total records"); tableCountTexts.set(table.name, countResult === 1 ? "total record" : "total records");
}
else
{
tableCountNumbers.set(table.name, "");
tableCountTexts.set(table.name, " ");
}
} }
else catch (e)
{ {
console.log("Caught: " + e);
tableCountNumbers.set(table.name, ""); tableCountNumbers.set(table.name, "");
tableCountTexts.set(table.name, " "); tableCountTexts.set(table.name, " ");
} }
} }
catch(e) else
{ {
console.log("Caught: " + e);
tableCountNumbers.set(table.name, ""); tableCountNumbers.set(table.name, "");
tableCountTexts.set(table.name, " "); tableCountTexts.set(table.name, " ");
} }
}
else
{
tableCountNumbers.set(table.name, "");
tableCountTexts.set(table.name, " ");
}
tableCounts.set(table.name, {isLoading: false, value: countResult}); tableCounts.set(table.name, {isLoading: false, value: countResult});
setTableCounts(tableCounts);
setTableCountNumbers(tableCountNumbers);
setTableCountTexts(tableCountTexts);
setUpdatedTableCounts(new Date());
}, 1);
}
else
{
tableCounts.set(table.name, {isLoading: false, value: null});
tableCountNumbers.set(table.name, " ");
tableCountTexts.set(table.name, " ");
setTableCounts(tableCounts); setTableCounts(tableCounts);
setTableCountNumbers(tableCountNumbers); setTableCountNumbers(tableCountNumbers);
setTableCountTexts(tableCountTexts); setTableCountTexts(tableCountTexts);
setUpdatedTableCounts(new Date()); }
}, 1);
}); });
setTableCounts(tableCounts); setTableCounts(tableCounts);
@ -299,7 +313,7 @@ function AppHome({app}: Props): JSX.Element
</Box> </Box>
{ {
section.processes ? ( section.processes ? (
<Box p={3} pl={5} pt={0} pb={1}> <Box p={3} pl={3} pt={0} pb={1}>
<MDTypography variant="h6">Actions</MDTypography> <MDTypography variant="h6">Actions</MDTypography>
</Box> </Box>
) : null ) : null
@ -340,7 +354,7 @@ function AppHome({app}: Props): JSX.Element
} }
{ {
section.reports ? ( section.reports ? (
<Box p={3} pl={5} pt={0} pb={1}> <Box p={3} pl={3} pt={0} pb={1}>
<MDTypography variant="h6">Reports</MDTypography> <MDTypography variant="h6">Reports</MDTypography>
</Box> </Box>
) : null ) : null
@ -383,7 +397,7 @@ function AppHome({app}: Props): JSX.Element
} }
{ {
section.tables ? ( section.tables ? (
<Box p={3} pl={5} pb={1} pt={0}> <Box p={3} pl={3} pb={1} pt={0}>
<MDTypography variant="h6">Data</MDTypography> <MDTypography variant="h6">Data</MDTypography>
</Box> </Box>
) : null ) : null
@ -395,6 +409,13 @@ function AppHome({app}: Props): JSX.Element
section.tables.map((tableName) => section.tables.map((tableName) =>
{ {
let table = app.childMap.get(tableName); let table = app.childMap.get(tableName);
let count = "";
let percentage = "";
if(includeTableCountsOnHomeScreen)
{
count = !tableCounts.has(table.name) || tableCounts.get(table.name).isLoading ? "..." : (tableCountNumbers.get(table.name));
percentage = !tableCounts.has(table.name) || tableCounts.get(table.name).isLoading ? "" : (tableCountTexts.get(table.name));
}
return ( return (
<Grid key={table.name} item xs={12} md={12} lg={tileSizeLg}> <Grid key={table.name} item xs={12} md={12} lg={tileSizeLg}>
{hasTablePermission(tableName) ? {hasTablePermission(tableName) ?
@ -402,8 +423,8 @@ function AppHome({app}: Props): JSX.Element
<Box className="big-icon" mb={3}> <Box className="big-icon" mb={3}>
<MiniStatisticsCard <MiniStatisticsCard
title={{fontWeight: "bold", text: table.label}} title={{fontWeight: "bold", text: table.label}}
count={!tableCounts.has(table.name) || tableCounts.get(table.name).isLoading ? "..." : (tableCountNumbers.get(table.name))} count={count}
percentage={{color: "info", text: (!tableCounts.has(table.name) || tableCounts.get(table.name).isLoading ? "" : (tableCountTexts.get(table.name)))}} percentage={{color: "info", text: percentage}}
icon={{color: "info", component: <Icon>{table.iconName || app.iconName}</Icon>}} icon={{color: "info", component: <Icon>{table.iconName || app.iconName}</Icon>}}
/> />
</Box> </Box>
@ -411,8 +432,8 @@ function AppHome({app}: Props): JSX.Element
<Box mb={3} title="You do not have permission to access this table"> <Box mb={3} title="You do not have permission to access this table">
<MiniStatisticsCard <MiniStatisticsCard
title={{fontWeight: "bold", text: table.label}} title={{fontWeight: "bold", text: table.label}}
count={!tableCounts.has(table.name) || tableCounts.get(table.name).isLoading ? "..." : (tableCountNumbers.get(table.name))} count={count}
percentage={{color: "info", text: (!tableCounts.has(table.name) || tableCounts.get(table.name).isLoading ? "" : (tableCountTexts.get(table.name)))}} percentage={{color: "info", text: percentage}}
icon={{color: "info", component: <Icon>{table.iconName || app.iconName}</Icon>}} icon={{color: "info", component: <Icon>{table.iconName || app.iconName}</Icon>}}
isDisabled={true} isDisabled={true}
/> />

View File

@ -114,8 +114,11 @@ class FilterUtils
// e.g., ...values=[1]... // // e.g., ...values=[1]... //
// but we need them to be possibleValue objects (w/ id & label) so the label // // but we need them to be possibleValue objects (w/ id & label) so the label //
// can be shown in the filter dropdown. So, make backend call to look them up. // // can be shown in the filter dropdown. So, make backend call to look them up. //
// also, there are cases where we can get a null or "" as the only value in the //
// values array - avoid sending that to the backend, as it comes back w/ all //
// possible values, and a general "bad time" //
////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////
if (values && values.length > 0) if (values && values.length > 0 && values[0] !== null && values[0] !== undefined && values[0] !== "")
{ {
values = await qController.possibleValues(fieldTable.name, null, field.name, "", values); values = await qController.possibleValues(fieldTable.name, null, field.name, "", values);
} }