mirror of
https://github.com/Kingsrook/qqq-frontend-material-dashboard.git
synced 2025-07-17 21:00:45 +00:00
CE-878 - Add tooltips from helpContent to widget labels & table-widget column headers
This commit is contained in:
@ -6,7 +6,7 @@
|
|||||||
"@auth0/auth0-react": "1.10.2",
|
"@auth0/auth0-react": "1.10.2",
|
||||||
"@emotion/react": "11.7.1",
|
"@emotion/react": "11.7.1",
|
||||||
"@emotion/styled": "11.6.0",
|
"@emotion/styled": "11.6.0",
|
||||||
"@kingsrook/qqq-frontend-core": "1.0.86",
|
"@kingsrook/qqq-frontend-core": "1.0.87",
|
||||||
"@mui/icons-material": "5.4.1",
|
"@mui/icons-material": "5.4.1",
|
||||||
"@mui/material": "5.11.1",
|
"@mui/material": "5.11.1",
|
||||||
"@mui/styles": "5.11.1",
|
"@mui/styles": "5.11.1",
|
||||||
|
@ -28,7 +28,7 @@ import QContext from "QContext";
|
|||||||
|
|
||||||
interface Props
|
interface Props
|
||||||
{
|
{
|
||||||
helpContents: QHelpContent[];
|
helpContents: null | QHelpContent | QHelpContent[];
|
||||||
roles: string[];
|
roles: string[];
|
||||||
heading?: string;
|
heading?: string;
|
||||||
helpContentKey?: string;
|
helpContentKey?: string;
|
||||||
@ -93,9 +93,27 @@ const getMatchingHelpContent = (helpContents: QHelpContent[], roles: string[]):
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** test if a list of help contents would find any matches from a list of roles.
|
** test if a list of help contents would find any matches from a list of roles.
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
export const hasHelpContent = (helpContents: QHelpContent[], roles: string[]) =>
|
export const hasHelpContent = (helpContents: null | QHelpContent | QHelpContent[], roles: string[]) =>
|
||||||
{
|
{
|
||||||
return getMatchingHelpContent(helpContents, roles) != null;
|
return getMatchingHelpContent(nullOrSingletonOrArrayToArray(helpContents), roles) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
const nullOrSingletonOrArrayToArray = (helpContents: null | QHelpContent | QHelpContent[]): QHelpContent[] =>
|
||||||
|
{
|
||||||
|
let array: QHelpContent[] = [];
|
||||||
|
if(Array.isArray(helpContents))
|
||||||
|
{
|
||||||
|
array = helpContents;
|
||||||
|
}
|
||||||
|
else if(helpContents != null)
|
||||||
|
{
|
||||||
|
array.push(helpContents);
|
||||||
|
}
|
||||||
|
return (array);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -106,7 +124,8 @@ export const hasHelpContent = (helpContents: QHelpContent[], roles: string[]) =>
|
|||||||
function HelpContent({helpContents, roles, heading, helpContentKey}: Props): JSX.Element
|
function HelpContent({helpContents, roles, heading, helpContentKey}: Props): JSX.Element
|
||||||
{
|
{
|
||||||
const {helpHelpActive} = useContext(QContext);
|
const {helpHelpActive} = useContext(QContext);
|
||||||
let selectedHelpContent = getMatchingHelpContent(helpContents, roles);
|
const helpContentsArray = nullOrSingletonOrArrayToArray(helpContents);
|
||||||
|
let selectedHelpContent = getMatchingHelpContent(helpContentsArray, roles);
|
||||||
|
|
||||||
let content = null;
|
let content = null;
|
||||||
if (helpHelpActive)
|
if (helpHelpActive)
|
||||||
|
@ -28,9 +28,11 @@ import Icon from "@mui/material/Icon";
|
|||||||
import Tooltip from "@mui/material/Tooltip/Tooltip";
|
import Tooltip from "@mui/material/Tooltip/Tooltip";
|
||||||
import Typography from "@mui/material/Typography";
|
import Typography from "@mui/material/Typography";
|
||||||
import parse from "html-react-parser";
|
import parse from "html-react-parser";
|
||||||
import React, {useEffect, useState} from "react";
|
import React, {useContext, useEffect, useState} from "react";
|
||||||
import {NavigateFunction, useNavigate} from "react-router-dom";
|
import {NavigateFunction, useNavigate} from "react-router-dom";
|
||||||
|
import QContext from "QContext";
|
||||||
import colors from "qqq/assets/theme/base/colors";
|
import colors from "qqq/assets/theme/base/colors";
|
||||||
|
import HelpContent, {hasHelpContent} from "qqq/components/misc/HelpContent";
|
||||||
import WidgetDropdownMenu, {DropdownOption} from "qqq/components/widgets/components/WidgetDropdownMenu";
|
import WidgetDropdownMenu, {DropdownOption} from "qqq/components/widgets/components/WidgetDropdownMenu";
|
||||||
import {WidgetUtils} from "qqq/components/widgets/WidgetUtils";
|
import {WidgetUtils} from "qqq/components/widgets/WidgetUtils";
|
||||||
import HtmlUtils from "qqq/utils/HtmlUtils";
|
import HtmlUtils from "qqq/utils/HtmlUtils";
|
||||||
@ -359,6 +361,8 @@ function Widget(props: React.PropsWithChildren<Props>): JSX.Element
|
|||||||
const [lastSeenLabel, setLastSeenLabel] = useState("");
|
const [lastSeenLabel, setLastSeenLabel] = useState("");
|
||||||
const [usingLabelAsTitle, setUsingLabelAsTitle] = useState(false);
|
const [usingLabelAsTitle, setUsingLabelAsTitle] = useState(false);
|
||||||
|
|
||||||
|
const {helpHelpActive} = useContext(QContext);
|
||||||
|
|
||||||
function renderComponent(component: LabelComponent, componentIndex: number)
|
function renderComponent(component: LabelComponent, componentIndex: number)
|
||||||
{
|
{
|
||||||
if(component && component.render)
|
if(component && component.render)
|
||||||
@ -608,9 +612,18 @@ function Widget(props: React.PropsWithChildren<Props>): JSX.Element
|
|||||||
setUsingLabelAsTitle(props.widgetData.isLabelPageTitle);
|
setUsingLabelAsTitle(props.widgetData.isLabelPageTitle);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (props.widgetMetaData.tooltip)
|
const helpRoles = ["ALL_SCREENS"]
|
||||||
|
const slotName = "label";
|
||||||
|
const showHelp = helpHelpActive || hasHelpContent(props.widgetMetaData?.helpContent?.get(slotName), helpRoles);
|
||||||
|
|
||||||
|
if(showHelp)
|
||||||
{
|
{
|
||||||
labelElement = <Tooltip title={props.widgetMetaData.tooltip} arrow={false} followCursor={true} placement="bottom-start">{labelElement}</Tooltip>;
|
const formattedHelpContent = <HelpContent helpContents={props.widgetMetaData?.helpContent?.get(slotName)} roles={helpRoles} helpContentKey={`widget:${props.widgetMetaData?.name};slot:${slotName}`} />;
|
||||||
|
labelElement = <Tooltip title={formattedHelpContent} arrow={true} placement="bottom-start">{labelElement}</Tooltip>;
|
||||||
|
}
|
||||||
|
else if (props.widgetMetaData?.tooltip)
|
||||||
|
{
|
||||||
|
labelElement = <Tooltip title={props.widgetMetaData.tooltip} arrow={true} placement="bottom-start">{labelElement}</Tooltip>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const isTable = props.widgetMetaData.type == "table";
|
const isTable = props.widgetMetaData.type == "table";
|
||||||
|
@ -30,8 +30,8 @@ import TableContainer from "@mui/material/TableContainer";
|
|||||||
import TableRow from "@mui/material/TableRow";
|
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 {useEffect, useMemo, useState} from "react";
|
import React, {useEffect, useMemo, useState} from "react";
|
||||||
import {useAsyncDebounce, useGlobalFilter, usePagination, useSortBy, useTable, useExpanded} from "react-table";
|
import {useAsyncDebounce, useExpanded, useGlobalFilter, usePagination, useSortBy, useTable} from "react-table";
|
||||||
import colors from "qqq/assets/theme/base/colors";
|
import colors from "qqq/assets/theme/base/colors";
|
||||||
import MDInput from "qqq/components/legacy/MDInput";
|
import MDInput from "qqq/components/legacy/MDInput";
|
||||||
import MDPagination from "qqq/components/legacy/MDPagination";
|
import MDPagination from "qqq/components/legacy/MDPagination";
|
||||||
@ -173,6 +173,17 @@ function DataTable({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(table.columnHeaderTooltips)
|
||||||
|
{
|
||||||
|
for (let column of columnsToMemo)
|
||||||
|
{
|
||||||
|
if(table.columnHeaderTooltips[column.accessor])
|
||||||
|
{
|
||||||
|
column.tooltip = table.columnHeaderTooltips[column.accessor];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const columns = useMemo<any>(() => columnsToMemo, [table]);
|
const columns = useMemo<any>(() => columnsToMemo, [table]);
|
||||||
const data = useMemo<any>(() => table.rows, [table]);
|
const data = useMemo<any>(() => table.rows, [table]);
|
||||||
const gridTemplateColumns = widths.join(" ");
|
const gridTemplateColumns = widths.join(" ");
|
||||||
@ -324,6 +335,7 @@ function DataTable({
|
|||||||
{...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}
|
||||||
>
|
>
|
||||||
{column.render("header")}
|
{column.render("header")}
|
||||||
</DataTableHeadCell>
|
</DataTableHeadCell>
|
||||||
|
@ -43,6 +43,7 @@ import Client from "qqq/utils/qqq/Client";
|
|||||||
export interface TableDataInput
|
export interface TableDataInput
|
||||||
{
|
{
|
||||||
columns: { [key: string]: any }[];
|
columns: { [key: string]: any }[];
|
||||||
|
columnHeaderTooltips?: { [columnName: string]: string | JSX.Element }
|
||||||
rows: { [key: string]: any }[];
|
rows: { [key: string]: any }[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,7 +23,9 @@
|
|||||||
import {QWidgetMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QWidgetMetaData";
|
import {QWidgetMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QWidgetMetaData";
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import {htmlToText} from "html-to-text";
|
import {htmlToText} from "html-to-text";
|
||||||
import React, {useEffect, useState} from "react";
|
import React, {useContext, useEffect, useState} from "react";
|
||||||
|
import QContext from "QContext";
|
||||||
|
import HelpContent, {hasHelpContent} from "qqq/components/misc/HelpContent";
|
||||||
import TableCard from "qqq/components/widgets/tables/TableCard";
|
import TableCard from "qqq/components/widgets/tables/TableCard";
|
||||||
import Widget, {WidgetData} from "qqq/components/widgets/Widget";
|
import Widget, {WidgetData} from "qqq/components/widgets/Widget";
|
||||||
import {WidgetUtils} from "qqq/components/widgets/WidgetUtils";
|
import {WidgetUtils} from "qqq/components/widgets/WidgetUtils";
|
||||||
@ -46,6 +48,7 @@ function TableWidget(props: Props): JSX.Element
|
|||||||
const [isExportDisabled, setIsExportDisabled] = useState(false); // hmm, would like true here, but it broke...
|
const [isExportDisabled, setIsExportDisabled] = useState(false); // hmm, would like true here, but it broke...
|
||||||
const [csv, setCsv] = useState(null as string);
|
const [csv, setCsv] = useState(null as string);
|
||||||
const [fileName, setFileName] = useState(null as string);
|
const [fileName, setFileName] = useState(null as string);
|
||||||
|
const {helpHelpActive} = useContext(QContext);
|
||||||
|
|
||||||
const rows = props.widgetData?.rows;
|
const rows = props.widgetData?.rows;
|
||||||
const columns = props.widgetData?.columns;
|
const columns = props.widgetData?.columns;
|
||||||
@ -133,6 +136,23 @@ function TableWidget(props: Props): JSX.Element
|
|||||||
labelAdditionalElementsLeft.push(WidgetUtils.generateExportButton(onExportClick));
|
labelAdditionalElementsLeft.push(WidgetUtils.generateExportButton(onExportClick));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////
|
||||||
|
// look for column-header tooltips from helpContent //
|
||||||
|
//////////////////////////////////////////////////////
|
||||||
|
const columnHeaderTooltips: {[columnName: string]: JSX.Element} = {}
|
||||||
|
for (let column of props.widgetData?.columns ?? [])
|
||||||
|
{
|
||||||
|
const helpRoles = ["ALL_SCREENS"]
|
||||||
|
const slotName = `columnHeader=${column.accessor}`;
|
||||||
|
const showHelp = helpHelpActive || hasHelpContent(props.widgetMetaData?.helpContent?.get(slotName), helpRoles);
|
||||||
|
|
||||||
|
if(showHelp)
|
||||||
|
{
|
||||||
|
const formattedHelpContent = <HelpContent helpContents={props.widgetMetaData?.helpContent?.get(slotName)} roles={helpRoles} helpContentKey={`widget:${props.widgetMetaData?.name};slot:${slotName}`} />;
|
||||||
|
columnHeaderTooltips[column.accessor] = formattedHelpContent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Widget
|
<Widget
|
||||||
widgetMetaData={props.widgetMetaData}
|
widgetMetaData={props.widgetMetaData}
|
||||||
@ -148,7 +168,7 @@ function TableWidget(props: Props): JSX.Element
|
|||||||
hidePaginationDropdown={props.widgetData?.hidePaginationDropdown}
|
hidePaginationDropdown={props.widgetData?.hidePaginationDropdown}
|
||||||
fixedStickyLastRow={props.widgetData?.fixedStickyLastRow}
|
fixedStickyLastRow={props.widgetData?.fixedStickyLastRow}
|
||||||
fixedHeight={props.widgetData?.fixedHeight}
|
fixedHeight={props.widgetData?.fixedHeight}
|
||||||
data={{columns: props.widgetData?.columns, rows: props.widgetData?.rows}}
|
data={{columns: props.widgetData?.columns, rows: props.widgetData?.rows, columnHeaderTooltips: columnHeaderTooltips}}
|
||||||
widgetMetaData={props.widgetMetaData}
|
widgetMetaData={props.widgetMetaData}
|
||||||
/>
|
/>
|
||||||
</Widget>
|
</Widget>
|
||||||
|
@ -22,6 +22,7 @@
|
|||||||
import Box from "@mui/material/Box";
|
import Box from "@mui/material/Box";
|
||||||
import Icon from "@mui/material/Icon";
|
import Icon from "@mui/material/Icon";
|
||||||
import {Theme} from "@mui/material/styles";
|
import {Theme} from "@mui/material/styles";
|
||||||
|
import Tooltip from "@mui/material/Tooltip";
|
||||||
import {ReactNode} from "react";
|
import {ReactNode} from "react";
|
||||||
import colors from "qqq/assets/theme/base/colors";
|
import colors from "qqq/assets/theme/base/colors";
|
||||||
import {useMaterialUIController} from "qqq/context";
|
import {useMaterialUIController} from "qqq/context";
|
||||||
@ -33,9 +34,10 @@ interface Props
|
|||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
sorted?: false | "none" | "asce" | "desc";
|
sorted?: false | "none" | "asce" | "desc";
|
||||||
align?: "left" | "right" | "center";
|
align?: "left" | "right" | "center";
|
||||||
|
tooltip?: string | JSX.Element;
|
||||||
}
|
}
|
||||||
|
|
||||||
function DataTableHeadCell({width, children, sorted, align, ...rest}: Props): JSX.Element
|
function DataTableHeadCell({width, children, sorted, align, tooltip, ...rest}: Props): JSX.Element
|
||||||
{
|
{
|
||||||
const [controller] = useMaterialUIController();
|
const [controller] = useMaterialUIController();
|
||||||
const {darkMode} = controller;
|
const {darkMode} = controller;
|
||||||
@ -73,39 +75,43 @@ function DataTableHeadCell({width, children, sorted, align, ...rest}: Props): JS
|
|||||||
userSelect: sorted && "none",
|
userSelect: sorted && "none",
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
{children}
|
<>
|
||||||
{sorted && (
|
{
|
||||||
<Box
|
tooltip ? <Tooltip title={tooltip}><span style={{cursor: "default"}}>{children}</span></Tooltip> : children
|
||||||
position="absolute"
|
}
|
||||||
top={0}
|
{sorted && (
|
||||||
right={align !== "right" ? "16px" : 0}
|
|
||||||
left={align === "right" ? "-5px" : "unset"}
|
|
||||||
sx={({typography: {size}}: any) => ({
|
|
||||||
fontSize: size.lg,
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
position="absolute"
|
||||||
position: "absolute",
|
top={0}
|
||||||
top: -6,
|
right={align !== "right" ? "16px" : 0}
|
||||||
color: sorted === "asce" ? "text" : "secondary",
|
left={align === "right" ? "-5px" : "unset"}
|
||||||
opacity: sorted === "asce" ? 1 : 0.5
|
sx={({typography: {size}}: any) => ({
|
||||||
}}
|
fontSize: size.lg,
|
||||||
|
})}
|
||||||
>
|
>
|
||||||
<Icon>arrow_drop_up</Icon>
|
<Box
|
||||||
|
sx={{
|
||||||
|
position: "absolute",
|
||||||
|
top: -6,
|
||||||
|
color: sorted === "asce" ? "text" : "secondary",
|
||||||
|
opacity: sorted === "asce" ? 1 : 0.5
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Icon>arrow_drop_up</Icon>
|
||||||
|
</Box>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
position: "absolute",
|
||||||
|
top: 0,
|
||||||
|
color: sorted === "desc" ? "text" : "secondary",
|
||||||
|
opacity: sorted === "desc" ? 1 : 0.5
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Icon>arrow_drop_down</Icon>
|
||||||
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
<Box
|
)}
|
||||||
sx={{
|
</>
|
||||||
position: "absolute",
|
|
||||||
top: 0,
|
|
||||||
color: sorted === "desc" ? "text" : "secondary",
|
|
||||||
opacity: sorted === "desc" ? 1 : 0.5
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Icon>arrow_drop_down</Icon>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
Reference in New Issue
Block a user