Fix dropdown flickering

This commit is contained in:
2023-06-21 11:53:37 -05:00
parent dac2588ae2
commit 5016d76b15

View File

@ -30,8 +30,7 @@ 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, {useEffect, useState} from "react";
import {Link, useNavigate, NavigateFunction} from "react-router-dom"; import {Link, NavigateFunction, useNavigate} from "react-router-dom";
import {bool} from "yup";
import colors from "qqq/components/legacy/colors"; import colors from "qqq/components/legacy/colors";
import DropdownMenu, {DropdownOption} from "qqq/components/widgets/components/DropdownMenu"; import DropdownMenu, {DropdownOption} from "qqq/components/widgets/components/DropdownMenu";
@ -94,7 +93,9 @@ export class LabelComponent
} }
/*******************************************************************************
**
*******************************************************************************/
export class HeaderLink extends LabelComponent export class HeaderLink extends LabelComponent
{ {
label: string; label: string;
@ -118,7 +119,9 @@ export class HeaderLink extends LabelComponent
} }
/*******************************************************************************
**
*******************************************************************************/
export class AddNewRecordButton extends LabelComponent export class AddNewRecordButton extends LabelComponent
{ {
table: QTableMetaData; table: QTableMetaData;
@ -152,6 +155,9 @@ export class AddNewRecordButton extends LabelComponent
} }
/*******************************************************************************
**
*******************************************************************************/
export class ExportDataButton extends LabelComponent export class ExportDataButton extends LabelComponent
{ {
callbackToExport: any; callbackToExport: any;
@ -177,26 +183,30 @@ export class ExportDataButton extends LabelComponent
} }
/*******************************************************************************
**
*******************************************************************************/
export class Dropdown extends LabelComponent export class Dropdown extends LabelComponent
{ {
label: string; label: string;
options: DropdownOption[]; 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(); super();
this.label = label; this.label = label;
this.options = options; this.options = options;
this.dropdownName = dropdownName;
this.onChangeCallback = onChangeCallback; this.onChangeCallback = onChangeCallback;
} }
render = (args: LabelComponentRenderArgs): JSX.Element => render = (args: LabelComponentRenderArgs): JSX.Element =>
{ {
let defaultValue = null; let defaultValue = null;
const dropdownName = args.widgetProps.widgetData.dropdownNameList[args.componentIndex]; const localStorageKey = `${WIDGET_DROPDOWN_SELECTION_LOCAL_STORAGE_KEY_ROOT}.${args.widgetProps.widgetMetaData.name}.${this.dropdownName}`;
const localStorageKey = `${WIDGET_DROPDOWN_SELECTION_LOCAL_STORAGE_KEY_ROOT}.${args.widgetProps.widgetMetaData.name}.${dropdownName}`; if (args.widgetProps.storeDropdownSelections)
if(args.widgetProps.storeDropdownSelections)
{ {
/////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////
// see if an existing value is stored in local storage, and if so set it in dropdown // // 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 ( return (
<Box my={2} sx={{float: "right"}}> <Box my={2} sx={{float: "right"}}>
<DropdownMenu <DropdownMenu
name={dropdownName} name={this.dropdownName}
defaultValue={defaultValue} defaultValue={defaultValue}
sx={{width: 200, marginLeft: "15px"}} sx={{width: 200, marginLeft: "15px"}}
label={`Select ${this.label}`} label={`Select ${this.label}`}
@ -221,6 +231,9 @@ export class Dropdown extends LabelComponent
} }
/*******************************************************************************
**
*******************************************************************************/
export class ReloadControl extends LabelComponent export class ReloadControl extends LabelComponent
{ {
callback: () => void; callback: () => void;
@ -235,7 +248,7 @@ export class ReloadControl extends LabelComponent
{ {
return ( return (
<Typography variant="body2" py={2} px={0} display="inline" position="relative" top="-0.375rem"> <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> </Typography>
); );
} }
@ -245,59 +258,101 @@ export class ReloadControl extends LabelComponent
export const WIDGET_DROPDOWN_SELECTION_LOCAL_STORAGE_KEY_ROOT = "qqq.widgets.dropdownData"; export const WIDGET_DROPDOWN_SELECTION_LOCAL_STORAGE_KEY_ROOT = "qqq.widgets.dropdownData";
/*******************************************************************************
**
*******************************************************************************/
function Widget(props: React.PropsWithChildren<Props>): JSX.Element function Widget(props: React.PropsWithChildren<Props>): JSX.Element
{ {
const navigate = useNavigate(); const navigate = useNavigate();
const [dropdownData, setDropdownData] = useState([]); const [dropdownData, setDropdownData] = useState([]);
const [fullScreenWidgetClassName, setFullScreenWidgetClassName] = useState(""); const [fullScreenWidgetClassName, setFullScreenWidgetClassName] = useState("");
const [reloading, setReloading] = useState(false); 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) 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});
} }
useEffect(() =>
///////////////////////////////////////////////////////////////////
// make dropdowns from the widgetData appear as label-components //
///////////////////////////////////////////////////////////////////
const effectiveLabelAdditionalComponentsRight: LabelComponent[] = [];
if(props.labelAdditionalComponentsRight)
{ {
props.labelAdditionalComponentsRight.map((component) => effectiveLabelAdditionalComponentsRight.push(component)); ////////////////////////////////////////////////////////////////////////////////
} // for initial render, put left-components from props into the state variable //
if(props.widgetData && props.widgetData.dropdownDataList) // plus others we can infer from other props //
{ ////////////////////////////////////////////////////////////////////////////////
props.widgetData.dropdownDataList?.map((dropdownData: any, index: number) => 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 = () => const doReload = () =>
{ {
setReloading(true); setReloading(true);
reloadWidget(dropdownData); reloadWidget(dropdownData);
} };
useEffect(() => useEffect(() =>
{ {
setReloading(false); setReloading(false);
}, [props.widgetData]); }, [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) function handleDataChange(dropdownLabel: string, changedData: any)
{ {
if(dropdownData) if (dropdownData)
{ {
/////////////////////////////////////////// ///////////////////////////////////////////
// find the index base on selected label // // 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 // // if should store in local storage, do so now //
// or remove if dropdown was cleared out // // or remove if dropdown was cleared out //
///////////////////////////////////////////////// /////////////////////////////////////////////////
if(props.storeDropdownSelections) if (props.storeDropdownSelections)
{ {
if (changedData?.id) if (changedData?.id)
{ {
@ -371,7 +426,7 @@ function Widget(props: React.PropsWithChildren<Props>): JSX.Element
const toggleFullScreenWidget = () => const toggleFullScreenWidget = () =>
{ {
if(fullScreenWidgetClassName) if (fullScreenWidgetClassName)
{ {
setFullScreenWidgetClassName(""); setFullScreenWidgetClassName("");
} }
@ -385,17 +440,17 @@ function Widget(props: React.PropsWithChildren<Props>): JSX.Element
const isSet = (v: any): boolean => 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 // // 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; let needLabelBox = false;
if(hasPermission) if (hasPermission)
{ {
needLabelBox ||= (effectiveLabelAdditionalComponentsLeft && effectiveLabelAdditionalComponentsLeft.length > 0); needLabelBox ||= (labelComponentsLeft && labelComponentsLeft.length > 0);
needLabelBox ||= (effectiveLabelAdditionalComponentsRight && effectiveLabelAdditionalComponentsRight.length > 0); needLabelBox ||= (labelComponentsRight && labelComponentsRight.length > 0);
needLabelBox ||= isSet(props.widgetMetaData?.icon); needLabelBox ||= isSet(props.widgetMetaData?.icon);
needLabelBox ||= isSet(props.widgetData?.label); needLabelBox ||= isSet(props.widgetData?.label);
needLabelBox ||= isSet(props.widgetMetaData?.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"}}> <Box sx={{width: "100%", height: "100%", minHeight: props.widgetMetaData?.minHeight ? props.widgetMetaData?.minHeight : "initial"}}>
{ {
needLabelBox && needLabelBox &&
<Box pr={2} display="flex" justifyContent="space-between" alignItems="flex-start" sx={{width: "100%"}} height={"3.5rem"}> <Box pr={2} display="flex" justifyContent="space-between" alignItems="flex-start" sx={{width: "100%"}} height={"3.5rem"}>
<Box pt={2} pb={1}> <Box pt={2} pb={1}>
{ {
hasPermission ? hasPermission ?
props.widgetMetaData?.icon && ( props.widgetMetaData?.icon && (
<Box <Box
ml={3} ml={3}
mt={-4} mt={-4}
sx={{ sx={{
display: "flex", display: "flex",
justifyContent: "center", justifyContent: "center",
alignItems: "center", alignItems: "center",
width: "64px", width: "64px",
height: "64px", height: "64px",
borderRadius: "8px", borderRadius: "8px",
background: colors.info.main, background: colors.info.main,
color: "#ffffff", color: "#ffffff",
float: "left" float: "left"
}} }}
> >
<Icon fontSize="medium" color="inherit"> <Icon fontSize="medium" color="inherit">
{props.widgetMetaData.icon} {props.widgetMetaData.icon}
</Icon> </Icon>
</Box> </Box>
) :
) : ( (
<Box <Box
ml={3} ml={3}
mt={-4} mt={-4}
sx={{ sx={{
display: "flex", display: "flex",
justifyContent: "center", justifyContent: "center",
alignItems: "center", alignItems: "center",
width: "64px", width: "64px",
height: "64px", height: "64px",
borderRadius: "8px", borderRadius: "8px",
background: colors.info.main, background: colors.info.main,
color: "#ffffff", color: "#ffffff",
float: "left" float: "left"
}} }}
> >
<Icon fontSize="medium" color="inherit">lock</Icon> <Icon fontSize="medium" color="inherit">lock</Icon>
</Box> </Box>
) )
} }
{ {
////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////
// first look for a label in the widget data, which would override that in the metadata // // first look for a label in the widget data, which would override that in the metadata //
////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////
hasPermission && props.widgetData?.label? ( hasPermission && props.widgetData?.label ? (
<Typography sx={{position: "relative", top: -4}} variant="h6" fontWeight="medium" pl={2} display="inline-block"> <Typography sx={{position: "relative", top: -4}} variant="h6" fontWeight="medium" pl={2} display="inline-block">
{props.widgetData.label} {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> </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 && (
hasPermission && ( labelComponentsLeft.map((component, i) =>
effectiveLabelAdditionalComponentsLeft.map((component, i) => {
{ return (<span key={i}>{renderComponent(component, i)}</span>);
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>
<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 ? ( 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> <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>
) )
} }