mirror of
https://github.com/Kingsrook/qqq-frontend-material-dashboard.git
synced 2025-07-18 13:20:43 +00:00
Fix dropdown flickering
This commit is contained in:
@ -30,8 +30,7 @@ import Tooltip from "@mui/material/Tooltip/Tooltip";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import parse from "html-react-parser";
|
||||
import React, {useEffect, useState} from "react";
|
||||
import {Link, useNavigate, NavigateFunction} from "react-router-dom";
|
||||
import {bool} from "yup";
|
||||
import {Link, NavigateFunction, useNavigate} from "react-router-dom";
|
||||
import colors from "qqq/components/legacy/colors";
|
||||
import DropdownMenu, {DropdownOption} from "qqq/components/widgets/components/DropdownMenu";
|
||||
|
||||
@ -94,7 +93,9 @@ export class LabelComponent
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
export class HeaderLink extends LabelComponent
|
||||
{
|
||||
label: string;
|
||||
@ -118,7 +119,9 @@ export class HeaderLink extends LabelComponent
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
export class AddNewRecordButton extends LabelComponent
|
||||
{
|
||||
table: QTableMetaData;
|
||||
@ -152,6 +155,9 @@ export class AddNewRecordButton extends LabelComponent
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
export class ExportDataButton extends LabelComponent
|
||||
{
|
||||
callbackToExport: any;
|
||||
@ -177,26 +183,30 @@ export class ExportDataButton extends LabelComponent
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
export class Dropdown extends LabelComponent
|
||||
{
|
||||
label: string;
|
||||
options: DropdownOption[];
|
||||
onChangeCallback: any
|
||||
dropdownName: string;
|
||||
onChangeCallback: any;
|
||||
|
||||
constructor(label: string, options: DropdownOption[], onChangeCallback: any)
|
||||
constructor(label: string, options: DropdownOption[], dropdownName: string, onChangeCallback: any)
|
||||
{
|
||||
super();
|
||||
this.label = label;
|
||||
this.options = options;
|
||||
this.dropdownName = dropdownName;
|
||||
this.onChangeCallback = onChangeCallback;
|
||||
}
|
||||
|
||||
render = (args: LabelComponentRenderArgs): JSX.Element =>
|
||||
{
|
||||
let defaultValue = null;
|
||||
const dropdownName = args.widgetProps.widgetData.dropdownNameList[args.componentIndex];
|
||||
const localStorageKey = `${WIDGET_DROPDOWN_SELECTION_LOCAL_STORAGE_KEY_ROOT}.${args.widgetProps.widgetMetaData.name}.${dropdownName}`;
|
||||
if(args.widgetProps.storeDropdownSelections)
|
||||
const localStorageKey = `${WIDGET_DROPDOWN_SELECTION_LOCAL_STORAGE_KEY_ROOT}.${args.widgetProps.widgetMetaData.name}.${this.dropdownName}`;
|
||||
if (args.widgetProps.storeDropdownSelections)
|
||||
{
|
||||
///////////////////////////////////////////////////////////////////////////////////////
|
||||
// see if an existing value is stored in local storage, and if so set it in dropdown //
|
||||
@ -208,7 +218,7 @@ export class Dropdown extends LabelComponent
|
||||
return (
|
||||
<Box my={2} sx={{float: "right"}}>
|
||||
<DropdownMenu
|
||||
name={dropdownName}
|
||||
name={this.dropdownName}
|
||||
defaultValue={defaultValue}
|
||||
sx={{width: 200, marginLeft: "15px"}}
|
||||
label={`Select ${this.label}`}
|
||||
@ -221,6 +231,9 @@ export class Dropdown extends LabelComponent
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
export class ReloadControl extends LabelComponent
|
||||
{
|
||||
callback: () => void;
|
||||
@ -235,7 +248,7 @@ export class ReloadControl extends LabelComponent
|
||||
{
|
||||
return (
|
||||
<Typography variant="body2" py={2} px={0} display="inline" position="relative" top="-0.375rem">
|
||||
<Tooltip title="Refresh"><Button sx={{px: 1, py:0, minWidth: "initial"}} onClick={() => this.callback()}><Icon>refresh</Icon></Button></Tooltip>
|
||||
<Tooltip title="Refresh"><Button sx={{px: 1, py: 0, minWidth: "initial"}} onClick={() => this.callback()}><Icon>refresh</Icon></Button></Tooltip>
|
||||
</Typography>
|
||||
);
|
||||
}
|
||||
@ -245,59 +258,101 @@ export class ReloadControl extends LabelComponent
|
||||
export const WIDGET_DROPDOWN_SELECTION_LOCAL_STORAGE_KEY_ROOT = "qqq.widgets.dropdownData";
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
function Widget(props: React.PropsWithChildren<Props>): JSX.Element
|
||||
{
|
||||
const navigate = useNavigate();
|
||||
const [dropdownData, setDropdownData] = useState([]);
|
||||
const [fullScreenWidgetClassName, setFullScreenWidgetClassName] = useState("");
|
||||
const [reloading, setReloading] = useState(false);
|
||||
const [dropdownDataJSON, setDropdownDataJSON] = useState("");
|
||||
const [labelComponentsLeft, setLabelComponentsLeft] = useState([] as LabelComponent[]);
|
||||
const [labelComponentsRight, setLabelComponentsRight] = useState([] as LabelComponent[]);
|
||||
|
||||
function renderComponent(component: LabelComponent, componentIndex: number)
|
||||
{
|
||||
return component.render({navigate: navigate, widgetProps: props, dropdownData: dropdownData, componentIndex: componentIndex, reloadFunction: doReload})
|
||||
return component.render({navigate: navigate, widgetProps: props, dropdownData: dropdownData, componentIndex: componentIndex, reloadFunction: doReload});
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
// make dropdowns from the widgetData appear as label-components //
|
||||
///////////////////////////////////////////////////////////////////
|
||||
const effectiveLabelAdditionalComponentsRight: LabelComponent[] = [];
|
||||
if(props.labelAdditionalComponentsRight)
|
||||
useEffect(() =>
|
||||
{
|
||||
props.labelAdditionalComponentsRight.map((component) => effectiveLabelAdditionalComponentsRight.push(component));
|
||||
}
|
||||
if(props.widgetData && props.widgetData.dropdownDataList)
|
||||
{
|
||||
props.widgetData.dropdownDataList?.map((dropdownData: any, index: number) =>
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// for initial render, put left-components from props into the state variable //
|
||||
// plus others we can infer from other props //
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
const stateLabelComponentsLeft: LabelComponent[] = [];
|
||||
if (props.reloadWidgetCallback && props.widgetData && props.showReloadControl && props.widgetMetaData.showReloadButton)
|
||||
{
|
||||
effectiveLabelAdditionalComponentsRight.push(new Dropdown(props.widgetData.dropdownLabelList[index], dropdownData, handleDataChange))
|
||||
});
|
||||
stateLabelComponentsLeft.push(new ReloadControl(doReload));
|
||||
}
|
||||
if (props.labelAdditionalComponentsLeft)
|
||||
{
|
||||
props.labelAdditionalComponentsLeft.map((component) => stateLabelComponentsLeft.push(component));
|
||||
}
|
||||
setLabelComponentsLeft(stateLabelComponentsLeft);
|
||||
}, []);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
// for initial render, put right-components from props into the state variable //
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
const stateLabelComponentsRight = [] as LabelComponent[];
|
||||
// console.log(`${props.widgetMetaData.name} init'ing right-components`);
|
||||
if (props.labelAdditionalComponentsRight)
|
||||
{
|
||||
props.labelAdditionalComponentsRight.map((component) => stateLabelComponentsRight.push(component));
|
||||
}
|
||||
setLabelComponentsRight(stateLabelComponentsRight);
|
||||
}, []);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// if we have widgetData, and it has a dropdown list, capture that in a state variable, if it's changed //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
if (props.widgetData && props.widgetData.dropdownDataList)
|
||||
{
|
||||
const currentDropdownDataJSON = JSON.stringify(props.widgetData.dropdownDataList);
|
||||
if (currentDropdownDataJSON !== dropdownDataJSON)
|
||||
{
|
||||
// console.log(`${props.widgetMetaData.name} we have (new) dropdown data!!: ${currentDropdownDataJSON}`);
|
||||
setDropdownDataJSON(currentDropdownDataJSON);
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// if we've seen a change in the dropdown data, then update the right-components //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// console.log(`${props.widgetMetaData.name} in useEffect post dropdownData change`);
|
||||
if (props.widgetData && props.widgetData.dropdownDataList)
|
||||
{
|
||||
const updatedStateLabelComponentsRight = JSON.parse(JSON.stringify(labelComponentsRight)) as LabelComponent[];
|
||||
props.widgetData.dropdownDataList?.map((dropdownData: any, index: number) =>
|
||||
{
|
||||
// console.log(`${props.widgetMetaData.name} building a Dropdown, data is: ${dropdownData}`);
|
||||
updatedStateLabelComponentsRight.push(new Dropdown(props.widgetData.dropdownLabelList[index], dropdownData, props.widgetData.dropdownNameList[index], handleDataChange));
|
||||
});
|
||||
setLabelComponentsRight(updatedStateLabelComponentsRight);
|
||||
}
|
||||
}, [dropdownDataJSON]);
|
||||
|
||||
const doReload = () =>
|
||||
{
|
||||
setReloading(true);
|
||||
reloadWidget(dropdownData);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
setReloading(false);
|
||||
}, [props.widgetData]);
|
||||
|
||||
const effectiveLabelAdditionalComponentsLeft: LabelComponent[] = [];
|
||||
if(props.reloadWidgetCallback && props.widgetData && props.showReloadControl && props.widgetMetaData.showReloadButton)
|
||||
{
|
||||
effectiveLabelAdditionalComponentsLeft.push(new ReloadControl(doReload))
|
||||
}
|
||||
if(props.labelAdditionalComponentsLeft)
|
||||
{
|
||||
props.labelAdditionalComponentsLeft.map((component) => effectiveLabelAdditionalComponentsLeft.push(component));
|
||||
}
|
||||
|
||||
function handleDataChange(dropdownLabel: string, changedData: any)
|
||||
{
|
||||
if(dropdownData)
|
||||
if (dropdownData)
|
||||
{
|
||||
///////////////////////////////////////////
|
||||
// find the index base on selected label //
|
||||
@ -327,7 +382,7 @@ function Widget(props: React.PropsWithChildren<Props>): JSX.Element
|
||||
// if should store in local storage, do so now //
|
||||
// or remove if dropdown was cleared out //
|
||||
/////////////////////////////////////////////////
|
||||
if(props.storeDropdownSelections)
|
||||
if (props.storeDropdownSelections)
|
||||
{
|
||||
if (changedData?.id)
|
||||
{
|
||||
@ -371,7 +426,7 @@ function Widget(props: React.PropsWithChildren<Props>): JSX.Element
|
||||
|
||||
const toggleFullScreenWidget = () =>
|
||||
{
|
||||
if(fullScreenWidgetClassName)
|
||||
if (fullScreenWidgetClassName)
|
||||
{
|
||||
setFullScreenWidgetClassName("");
|
||||
}
|
||||
@ -385,17 +440,17 @@ function Widget(props: React.PropsWithChildren<Props>): JSX.Element
|
||||
|
||||
const isSet = (v: any): boolean =>
|
||||
{
|
||||
return(v !== null && v !== undefined);
|
||||
return (v !== null && v !== undefined);
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// to avoid taking up the space of the Box with the label and icon and label-components (since it has a height), only output that box if we need any of the components //
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
let needLabelBox = false;
|
||||
if(hasPermission)
|
||||
if (hasPermission)
|
||||
{
|
||||
needLabelBox ||= (effectiveLabelAdditionalComponentsLeft && effectiveLabelAdditionalComponentsLeft.length > 0);
|
||||
needLabelBox ||= (effectiveLabelAdditionalComponentsRight && effectiveLabelAdditionalComponentsRight.length > 0);
|
||||
needLabelBox ||= (labelComponentsLeft && labelComponentsLeft.length > 0);
|
||||
needLabelBox ||= (labelComponentsRight && labelComponentsRight.length > 0);
|
||||
needLabelBox ||= isSet(props.widgetMetaData?.icon);
|
||||
needLabelBox ||= isSet(props.widgetData?.label);
|
||||
needLabelBox ||= isSet(props.widgetMetaData?.label);
|
||||
@ -406,90 +461,90 @@ function Widget(props: React.PropsWithChildren<Props>): JSX.Element
|
||||
<Box sx={{width: "100%", height: "100%", minHeight: props.widgetMetaData?.minHeight ? props.widgetMetaData?.minHeight : "initial"}}>
|
||||
{
|
||||
needLabelBox &&
|
||||
<Box pr={2} display="flex" justifyContent="space-between" alignItems="flex-start" sx={{width: "100%"}} height={"3.5rem"}>
|
||||
<Box pt={2} pb={1}>
|
||||
{
|
||||
hasPermission ?
|
||||
props.widgetMetaData?.icon && (
|
||||
<Box
|
||||
ml={3}
|
||||
mt={-4}
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
width: "64px",
|
||||
height: "64px",
|
||||
borderRadius: "8px",
|
||||
background: colors.info.main,
|
||||
color: "#ffffff",
|
||||
float: "left"
|
||||
}}
|
||||
>
|
||||
<Icon fontSize="medium" color="inherit">
|
||||
{props.widgetMetaData.icon}
|
||||
</Icon>
|
||||
</Box>
|
||||
|
||||
) : (
|
||||
<Box
|
||||
ml={3}
|
||||
mt={-4}
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
width: "64px",
|
||||
height: "64px",
|
||||
borderRadius: "8px",
|
||||
background: colors.info.main,
|
||||
color: "#ffffff",
|
||||
float: "left"
|
||||
}}
|
||||
>
|
||||
<Icon fontSize="medium" color="inherit">lock</Icon>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
{
|
||||
//////////////////////////////////////////////////////////////////////////////////////////
|
||||
// first look for a label in the widget data, which would override that in the metadata //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////
|
||||
hasPermission && props.widgetData?.label? (
|
||||
<Typography sx={{position: "relative", top: -4}} variant="h6" fontWeight="medium" pl={2} display="inline-block">
|
||||
{props.widgetData.label}
|
||||
</Typography>
|
||||
) : (
|
||||
hasPermission && props.widgetMetaData?.label && (
|
||||
<Typography sx={{position: "relative", top: -4}} variant="h6" fontWeight="medium" pl={3} display="inline-block">
|
||||
{props.widgetMetaData.label}
|
||||
<Box pr={2} display="flex" justifyContent="space-between" alignItems="flex-start" sx={{width: "100%"}} height={"3.5rem"}>
|
||||
<Box pt={2} pb={1}>
|
||||
{
|
||||
hasPermission ?
|
||||
props.widgetMetaData?.icon && (
|
||||
<Box
|
||||
ml={3}
|
||||
mt={-4}
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
width: "64px",
|
||||
height: "64px",
|
||||
borderRadius: "8px",
|
||||
background: colors.info.main,
|
||||
color: "#ffffff",
|
||||
float: "left"
|
||||
}}
|
||||
>
|
||||
<Icon fontSize="medium" color="inherit">
|
||||
{props.widgetMetaData.icon}
|
||||
</Icon>
|
||||
</Box>
|
||||
) :
|
||||
(
|
||||
<Box
|
||||
ml={3}
|
||||
mt={-4}
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
width: "64px",
|
||||
height: "64px",
|
||||
borderRadius: "8px",
|
||||
background: colors.info.main,
|
||||
color: "#ffffff",
|
||||
float: "left"
|
||||
}}
|
||||
>
|
||||
<Icon fontSize="medium" color="inherit">lock</Icon>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
{
|
||||
//////////////////////////////////////////////////////////////////////////////////////////
|
||||
// first look for a label in the widget data, which would override that in the metadata //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////
|
||||
hasPermission && props.widgetData?.label ? (
|
||||
<Typography sx={{position: "relative", top: -4}} variant="h6" fontWeight="medium" pl={2} display="inline-block">
|
||||
{props.widgetData.label}
|
||||
</Typography>
|
||||
) : (
|
||||
hasPermission && props.widgetMetaData?.label && (
|
||||
<Typography sx={{position: "relative", top: -4}} variant="h6" fontWeight="medium" pl={3} display="inline-block">
|
||||
{props.widgetMetaData.label}
|
||||
</Typography>
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
{
|
||||
hasPermission && (
|
||||
effectiveLabelAdditionalComponentsLeft.map((component, i) =>
|
||||
{
|
||||
return (<span key={i}>{renderComponent(component, i)}</span>);
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
{
|
||||
hasPermission && (
|
||||
labelComponentsLeft.map((component, i) =>
|
||||
{
|
||||
return (<span key={i}>{renderComponent(component, i)}</span>);
|
||||
})
|
||||
)
|
||||
}
|
||||
</Box>
|
||||
<Box>
|
||||
{
|
||||
hasPermission && (
|
||||
labelComponentsRight.map((component, i) =>
|
||||
{
|
||||
return (<span key={i}>{renderComponent(component, i)}</span>);
|
||||
})
|
||||
)
|
||||
}
|
||||
</Box>
|
||||
</Box>
|
||||
<Box>
|
||||
{
|
||||
hasPermission && (
|
||||
effectiveLabelAdditionalComponentsRight.map((component, i) =>
|
||||
{
|
||||
return (<span key={i}>{renderComponent(component, i)}</span>);
|
||||
})
|
||||
)
|
||||
}
|
||||
</Box>
|
||||
</Box>
|
||||
}
|
||||
{
|
||||
props.widgetMetaData?.isCard && (reloading ? <LinearProgress color="info" sx={{overflow: "hidden", borderRadius: "0"}} /> : <Box height="0.375rem"/>)
|
||||
props.widgetMetaData?.isCard && (reloading ? <LinearProgress color="info" sx={{overflow: "hidden", borderRadius: "0"}} /> : <Box height="0.375rem" />)
|
||||
}
|
||||
{
|
||||
errorLoading ? (
|
||||
@ -514,7 +569,7 @@ function Widget(props: React.PropsWithChildren<Props>): JSX.Element
|
||||
)
|
||||
}
|
||||
{
|
||||
! errorLoading && props?.footerHTML && (
|
||||
!errorLoading && props?.footerHTML && (
|
||||
<Box mt={1} ml={3} mr={3} mb={2} sx={{fontWeight: 300, color: "#7b809a", display: "flex", alignContent: "flex-end", fontSize: "14px"}}>{parse(props.footerHTML)}</Box>
|
||||
)
|
||||
}
|
||||
|
Reference in New Issue
Block a user