mirror of
https://github.com/Kingsrook/qqq-frontend-material-dashboard.git
synced 2025-07-18 05:10:45 +00:00
Add export to table widgets; add reload to most widgets; refactor widget label components (render in class!)
This commit is contained in:
@ -25,10 +25,11 @@ import Box from "@mui/material/Box";
|
||||
import Button from "@mui/material/Button";
|
||||
import Card from "@mui/material/Card";
|
||||
import Icon from "@mui/material/Icon";
|
||||
import LinearProgress from "@mui/material/LinearProgress";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import parse from "html-react-parser";
|
||||
import React, {useState} from "react";
|
||||
import {Link, useNavigate} from "react-router-dom";
|
||||
import React, {useEffect, useState} from "react";
|
||||
import {Link, useNavigate, NavigateFunction} from "react-router-dom";
|
||||
import colors from "qqq/components/legacy/colors";
|
||||
import DropdownMenu, {DropdownOption} from "qqq/components/widgets/components/DropdownMenu";
|
||||
|
||||
@ -43,6 +44,7 @@ export interface WidgetData
|
||||
}[][];
|
||||
dropdownNeedsSelectedText?: string;
|
||||
hasPermission?: boolean;
|
||||
[other: string]: any;
|
||||
}
|
||||
|
||||
|
||||
@ -54,6 +56,7 @@ interface Props
|
||||
widgetData?: WidgetData;
|
||||
children: JSX.Element;
|
||||
reloadWidgetCallback?: (params: string) => void;
|
||||
showReloadControl: boolean;
|
||||
isChild?: boolean;
|
||||
footerHTML?: string;
|
||||
storeDropdownSelections?: boolean;
|
||||
@ -61,6 +64,7 @@ interface Props
|
||||
|
||||
Widget.defaultProps = {
|
||||
isChild: false,
|
||||
showReloadControl: true,
|
||||
widgetMetaData: {},
|
||||
widgetData: {},
|
||||
labelAdditionalComponentsLeft: [],
|
||||
@ -68,9 +72,22 @@ Widget.defaultProps = {
|
||||
};
|
||||
|
||||
|
||||
interface LabelComponentRenderArgs
|
||||
{
|
||||
navigate: NavigateFunction;
|
||||
widgetProps: Props;
|
||||
dropdownData: any[];
|
||||
componentIndex: number;
|
||||
reloadFunction: () => void;
|
||||
}
|
||||
|
||||
|
||||
export class LabelComponent
|
||||
{
|
||||
|
||||
render = (args: LabelComponentRenderArgs): JSX.Element =>
|
||||
{
|
||||
return (<div>Unsupported component type</div>)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -86,6 +103,15 @@ export class HeaderLink extends LabelComponent
|
||||
this.label = label;
|
||||
this.to = to;
|
||||
}
|
||||
|
||||
render = (args: LabelComponentRenderArgs): JSX.Element =>
|
||||
{
|
||||
return (
|
||||
<Typography variant="body2" p={2} display="inline" fontSize=".875rem" pt="0" position="relative" top="-0.25rem">
|
||||
{this.to ? <Link to={this.to}>{this.label}</Link> : null}
|
||||
</Typography>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -97,6 +123,7 @@ export class AddNewRecordButton extends LabelComponent
|
||||
defaultValues: any;
|
||||
disabledFields: any;
|
||||
|
||||
|
||||
constructor(table: QTableMetaData, defaultValues: any, label: string = "Add new", disabledFields: any = defaultValues)
|
||||
{
|
||||
super();
|
||||
@ -105,6 +132,45 @@ export class AddNewRecordButton extends LabelComponent
|
||||
this.defaultValues = defaultValues;
|
||||
this.disabledFields = disabledFields;
|
||||
}
|
||||
|
||||
openEditForm = (navigate: any, table: QTableMetaData, id: any = null, defaultValues: any, disabledFields: any) =>
|
||||
{
|
||||
navigate(`#/createChild=${table.name}/defaultValues=${JSON.stringify(defaultValues)}/disabledFields=${JSON.stringify(disabledFields)}`)
|
||||
}
|
||||
|
||||
render = (args: LabelComponentRenderArgs): JSX.Element =>
|
||||
{
|
||||
return (
|
||||
<Typography variant="body2" p={2} pr={0} display="inline" position="relative" top="0.25rem">
|
||||
<Button sx={{mt: 0.75}} onClick={() => this.openEditForm(args.navigate, this.table, null, this.defaultValues, this.disabledFields)}>{this.label}</Button>
|
||||
</Typography>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class ExportDataButton extends LabelComponent
|
||||
{
|
||||
callbackToExport: any;
|
||||
label: string;
|
||||
isDisabled: boolean;
|
||||
|
||||
constructor(callbackToExport: any, isDisabled = false, label: string = "Export")
|
||||
{
|
||||
super();
|
||||
this.callbackToExport = callbackToExport;
|
||||
this.isDisabled = isDisabled;
|
||||
this.label = label;
|
||||
}
|
||||
|
||||
render = (args: LabelComponentRenderArgs): JSX.Element =>
|
||||
{
|
||||
return (
|
||||
<Typography variant="body2" py={2} px={1} display="inline" position="relative" top="-0.25rem">
|
||||
<Button sx={{px: 1}} onClick={() => this.callbackToExport()} disabled={this.isDisabled}><Icon>save_alt</Icon> {this.label}</Button>
|
||||
</Typography>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -121,6 +187,55 @@ export class Dropdown extends LabelComponent
|
||||
this.options = options;
|
||||
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)
|
||||
{
|
||||
///////////////////////////////////////////////////////////////////////////////////////
|
||||
// see if an existing value is stored in local storage, and if so set it in dropdown //
|
||||
///////////////////////////////////////////////////////////////////////////////////////
|
||||
defaultValue = JSON.parse(localStorage.getItem(localStorageKey));
|
||||
args.dropdownData[args.componentIndex] = defaultValue?.id;
|
||||
}
|
||||
|
||||
return (
|
||||
<Box my={2} sx={{float: "right"}}>
|
||||
<DropdownMenu
|
||||
name={dropdownName}
|
||||
defaultValue={defaultValue}
|
||||
sx={{width: 200, marginLeft: "15px"}}
|
||||
label={`Select ${this.label}`}
|
||||
dropdownOptions={this.options}
|
||||
onChangeCallback={this.onChangeCallback}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class ReloadControl extends LabelComponent
|
||||
{
|
||||
callback: () => void;
|
||||
|
||||
constructor(callback: () => void)
|
||||
{
|
||||
super();
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
render = (args: LabelComponentRenderArgs): JSX.Element =>
|
||||
{
|
||||
return (
|
||||
<Typography variant="body2" py={2} px={1} display="inline" position="relative" top="-0.25rem">
|
||||
<Button sx={{px: 1}} onClick={() => this.callback()}><Icon>refresh</Icon> Refresh</Button>
|
||||
</Typography>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -132,64 +247,11 @@ function Widget(props: React.PropsWithChildren<Props>): JSX.Element
|
||||
const navigate = useNavigate();
|
||||
const [dropdownData, setDropdownData] = useState([]);
|
||||
const [fullScreenWidgetClassName, setFullScreenWidgetClassName] = useState("");
|
||||
const [reloading, setReloading] = useState(false);
|
||||
|
||||
function openEditForm(table: QTableMetaData, id: any = null, defaultValues: any, disabledFields: any)
|
||||
function renderComponent(component: LabelComponent, componentIndex: number)
|
||||
{
|
||||
navigate(`#/createChild=${table.name}/defaultValues=${JSON.stringify(defaultValues)}/disabledFields=${JSON.stringify(disabledFields)}`)
|
||||
}
|
||||
|
||||
function renderComponent(component: LabelComponent, index: number)
|
||||
{
|
||||
if(component instanceof HeaderLink)
|
||||
{
|
||||
const link = component as HeaderLink
|
||||
return (
|
||||
<Typography variant="body2" p={2} display="inline" fontSize=".875rem" pt="0" position="relative" top="-0.25rem">
|
||||
{link.to ? <Link to={link.to}>{link.label}</Link> : null}
|
||||
</Typography>
|
||||
);
|
||||
}
|
||||
|
||||
if (component instanceof AddNewRecordButton)
|
||||
{
|
||||
const addNewRecordButton = component as AddNewRecordButton
|
||||
return (
|
||||
<Typography variant="body2" p={2} pr={0} display="inline" position="relative" top="0.25rem">
|
||||
<Button sx={{mt: 0.75}} onClick={() => openEditForm(addNewRecordButton.table, null, addNewRecordButton.defaultValues, addNewRecordButton.disabledFields)}>{addNewRecordButton.label}</Button>
|
||||
</Typography>
|
||||
);
|
||||
}
|
||||
|
||||
if (component instanceof Dropdown)
|
||||
{
|
||||
let defaultValue = null;
|
||||
const dropdownName = props.widgetData.dropdownNameList[index];
|
||||
const localStorageKey = `${WIDGET_DROPDOWN_SELECTION_LOCAL_STORAGE_KEY_ROOT}.${props.widgetMetaData.name}.${dropdownName}`;
|
||||
if(props.storeDropdownSelections)
|
||||
{
|
||||
///////////////////////////////////////////////////////////////////////////////////////
|
||||
// see if an existing value is stored in local storage, and if so set it in dropdown //
|
||||
///////////////////////////////////////////////////////////////////////////////////////
|
||||
defaultValue = JSON.parse(localStorage.getItem(localStorageKey));
|
||||
dropdownData[index] = defaultValue?.id;
|
||||
}
|
||||
|
||||
const dropdown = component as Dropdown
|
||||
return (
|
||||
<Box my={2} sx={{float: "right"}}>
|
||||
<DropdownMenu
|
||||
name={dropdownName}
|
||||
defaultValue={defaultValue}
|
||||
sx={{width: 200, marginLeft: "15px"}}
|
||||
label={`Select ${dropdown.label}`}
|
||||
dropdownOptions={dropdown.options}
|
||||
onChangeCallback={dropdown.onChangeCallback}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
return (<div>Unsupported component type.</div>)
|
||||
return component.render({navigate: navigate, widgetProps: props, dropdownData: dropdownData, componentIndex: componentIndex, reloadFunction: doReload})
|
||||
}
|
||||
|
||||
|
||||
@ -209,6 +271,27 @@ function Widget(props: React.PropsWithChildren<Props>): JSX.Element
|
||||
});
|
||||
}
|
||||
|
||||
const doReload = () =>
|
||||
{
|
||||
setReloading(true);
|
||||
reloadWidget(dropdownData);
|
||||
}
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
setReloading(false);
|
||||
}, [props.widgetData]);
|
||||
|
||||
const effectiveLabelAdditionalComponentsLeft: LabelComponent[] = [];
|
||||
if(props.labelAdditionalComponentsLeft)
|
||||
{
|
||||
props.labelAdditionalComponentsLeft.map((component) => effectiveLabelAdditionalComponentsLeft.push(component));
|
||||
}
|
||||
|
||||
if(props.reloadWidgetCallback && props.widgetData && props.showReloadControl)
|
||||
{
|
||||
effectiveLabelAdditionalComponentsLeft.push(new ReloadControl(doReload))
|
||||
}
|
||||
|
||||
function handleDataChange(dropdownLabel: string, changedData: any)
|
||||
{
|
||||
@ -299,7 +382,7 @@ function Widget(props: React.PropsWithChildren<Props>): JSX.Element
|
||||
const hasPermission = props.widgetData?.hasPermission === undefined || props.widgetData?.hasPermission === true;
|
||||
const widgetContent =
|
||||
<Box sx={{width: "100%", height: "100%", minHeight: props.widgetMetaData?.minHeight ? props.widgetMetaData?.minHeight : "initial"}}>
|
||||
<Box pr={2} display="flex" justifyContent="space-between" alignItems="flex-start" sx={{width: "100%"}}>
|
||||
<Box pr={2} display="flex" justifyContent="space-between" alignItems="flex-start" sx={{width: "100%"}} height={"3.5rem"}>
|
||||
<Box pt={2} pb={1}>
|
||||
{
|
||||
hasPermission ?
|
||||
@ -367,7 +450,7 @@ function Widget(props: React.PropsWithChildren<Props>): JSX.Element
|
||||
*/}
|
||||
{
|
||||
hasPermission && (
|
||||
props.labelAdditionalComponentsLeft.map((component, i) =>
|
||||
effectiveLabelAdditionalComponentsLeft.map((component, i) =>
|
||||
{
|
||||
return (<span key={i}>{renderComponent(component, i)}</span>);
|
||||
})
|
||||
@ -385,6 +468,9 @@ function Widget(props: React.PropsWithChildren<Props>): JSX.Element
|
||||
}
|
||||
</Box>
|
||||
</Box>
|
||||
{
|
||||
props.widgetMetaData?.isCard && (reloading ? <LinearProgress color="info" sx={{overflow: "hidden", borderRadius: "0"}} /> : <Box height="0.375rem"/>)
|
||||
}
|
||||
{
|
||||
hasPermission && props.widgetData?.dropdownNeedsSelectedText ? (
|
||||
<Box pb={3} pr={3} sx={{width: "100%", textAlign: "right"}}>
|
||||
@ -407,7 +493,11 @@ function Widget(props: React.PropsWithChildren<Props>): JSX.Element
|
||||
}
|
||||
</Box>;
|
||||
|
||||
return props.widgetMetaData?.isCard ? <Card sx={{marginTop: props.widgetMetaData?.icon ? 2 : 0, width: "100%"}} className={fullScreenWidgetClassName}>{widgetContent}</Card> : widgetContent;
|
||||
return props.widgetMetaData?.isCard
|
||||
? <Card sx={{marginTop: props.widgetMetaData?.icon ? 2 : 0, width: "100%"}} className={fullScreenWidgetClassName}>
|
||||
{widgetContent}
|
||||
</Card>
|
||||
: widgetContent;
|
||||
}
|
||||
|
||||
export default Widget;
|
||||
|
Reference in New Issue
Block a user