mirror of
https://github.com/Kingsrook/qqq-frontend-material-dashboard.git
synced 2025-07-17 21:00:45 +00:00
Updates for dropdowns for all widgets, process specifically
This commit is contained in:
@ -184,7 +184,14 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, omit
|
||||
}
|
||||
{
|
||||
widgetMetaData.type === "process" && widgetData[i]?.processMetaData && (
|
||||
<ProcessRun process={widgetData[i]?.processMetaData} defaultProcessValues={widgetData[i]?.defaultValues} isWidget={true} />
|
||||
<Widget
|
||||
label={widgetData[i]?.processMetaData?.label}
|
||||
widgetData={widgetData[i]}
|
||||
reloadWidgetCallback={(data) => reloadWidget(i, data)}>
|
||||
<div>
|
||||
<ProcessRun process={widgetData[i]?.processMetaData} defaultProcessValues={widgetData[i]?.defaultValues} isWidget={true} forceReInit={widgetCounter} />
|
||||
</div>
|
||||
</Widget>
|
||||
)
|
||||
}
|
||||
{
|
||||
@ -303,9 +310,9 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, omit
|
||||
<RecordGridWidget
|
||||
title={widgetMetaData.label}
|
||||
data={widgetData[i]}
|
||||
reloadWidgetCallback={reloadWidget}
|
||||
/>
|
||||
)
|
||||
|
||||
}
|
||||
{
|
||||
widgetMetaData.type === "fieldValueList" && (
|
||||
|
@ -154,7 +154,7 @@ function EntityForm(props: Props): JSX.Element
|
||||
/////////////////////////////////////////////////
|
||||
// define the sections, e.g., for the left-bar //
|
||||
/////////////////////////////////////////////////
|
||||
const tableSections = QTableUtils.getSectionsForRecordSidebar(tableMetaData);
|
||||
const tableSections = QTableUtils.getSectionsForRecordSidebar(tableMetaData, [...tableMetaData.fields.keys()]);
|
||||
setTableSections(tableSections);
|
||||
|
||||
const fieldArray = [] as QFieldMetaData[];
|
||||
|
@ -101,7 +101,7 @@ interface QActionsMenuButtonProps
|
||||
export function QActionsMenuButton({isOpen, onClickHandler}: QActionsMenuButtonProps): JSX.Element
|
||||
{
|
||||
return (
|
||||
<MDBox width={standardWidth}>
|
||||
<MDBox width={standardWidth} ml={1}>
|
||||
<MDButton
|
||||
variant={isOpen ? "contained" : "outlined"}
|
||||
color="dark"
|
||||
|
@ -339,7 +339,8 @@ function Overview(): JSX.Element
|
||||
<MDBox mt={2}>
|
||||
<Grid container spacing={3}>
|
||||
{
|
||||
warehouseData && warehouseData.map((data) => (
|
||||
// @ts-ignore
|
||||
warehouseData && warehouseData.locationDataList?.map((data) => (
|
||||
<Grid item xs={12} md={6} lg={4} key={data.title}>
|
||||
<MDBox mt={3}>
|
||||
<LocationCard
|
||||
|
@ -27,6 +27,7 @@ import Grid from "@mui/material/Grid";
|
||||
import React, {useEffect, useState} from "react";
|
||||
import DashboardWidgets from "qqq/components/DashboardWidgets";
|
||||
import DropdownMenu from "qqq/pages/dashboards/Widgets/Components/DropdownMenu";
|
||||
import Widget, {Dropdown, LabelComponent} from "qqq/pages/dashboards/Widgets/Widget";
|
||||
import QClient from "qqq/utils/QClient";
|
||||
|
||||
|
||||
@ -91,104 +92,22 @@ function ParentWidget({widgetIndex, label, data, reloadWidgetCallback, entityPri
|
||||
}
|
||||
}, [qInstance, data]);
|
||||
|
||||
function handleDataChange(dropdownLabel: string, changedData: any)
|
||||
const parentReloadWidgetCallback = (data: string) =>
|
||||
{
|
||||
if(dropdownData)
|
||||
{
|
||||
///////////////////////////////////////////
|
||||
// find the index base on selected label //
|
||||
///////////////////////////////////////////
|
||||
const tableName = dropdownLabel.replace("Select ", "");
|
||||
let index = -1;
|
||||
for (let i = 0; i < data.dropdownLabelList.length; i++)
|
||||
{
|
||||
if (tableName === data.dropdownLabelList[i])
|
||||
{
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (index < 0)
|
||||
{
|
||||
throw(`Could not find table name for label ${tableName}`);
|
||||
}
|
||||
|
||||
dropdownData[index] = (changedData) ? changedData.id : null;
|
||||
setDropdownData(dropdownData);
|
||||
setCounter(counter + 1);
|
||||
}
|
||||
setChildUrlParams(data);
|
||||
reloadWidgetCallback(widgetIndex, data);
|
||||
}
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(dropdownData)
|
||||
{
|
||||
let params = "";
|
||||
for (let i = 0; i < dropdownData.length; i++)
|
||||
{
|
||||
if (i > 0)
|
||||
{
|
||||
params += "&";
|
||||
}
|
||||
params += `${data.dropdownNameList[i]}=`;
|
||||
if(dropdownData[i])
|
||||
{
|
||||
params += `${dropdownData[i]}`;
|
||||
|
||||
}
|
||||
}
|
||||
reloadWidgetCallback(widgetIndex, params);
|
||||
setChildUrlParams(params)
|
||||
}
|
||||
}, [counter]);
|
||||
|
||||
|
||||
return (
|
||||
<Card className="parentWidgetCard" sx={{alignItems: "stretch", flexGrow: 1, display: "flex", marginTop: "0px", paddingTop: "0px"}}>
|
||||
|
||||
<Grid container>
|
||||
<Grid item xs={4}>
|
||||
<Box pt={3} px={3}>
|
||||
{
|
||||
label && (
|
||||
<Typography variant="h5" textTransform="capitalize">
|
||||
{label}
|
||||
</Typography>
|
||||
)
|
||||
}
|
||||
</Box>
|
||||
</Grid>
|
||||
<Grid item xs={8}>
|
||||
<Box mb={3} p={3}>
|
||||
{
|
||||
data?.dropdownDataList?.map((dropdownData: any, index: number) =>
|
||||
<DropdownMenu
|
||||
key={`dropdown-${data.dropdownLabelList[index]}-${index}`}
|
||||
label={`Select ${data.dropdownLabelList[index]}`}
|
||||
sx={{width: 250, marginLeft: "15px", float: "right"}}
|
||||
dropdownOptions={dropdownData}
|
||||
onChangeCallback={handleDataChange}
|
||||
/>
|
||||
)
|
||||
}
|
||||
</Box>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Box pr={3} pl={3}>
|
||||
{
|
||||
data?.dropdownNeedsSelectedText ? (
|
||||
<Box pb={3} sx={{width: "100%", textAlign: "right"}}>
|
||||
<Typography variant="body2">
|
||||
{data.dropdownNeedsSelectedText}
|
||||
</Typography>
|
||||
</Box>
|
||||
) :(
|
||||
<DashboardWidgets widgetMetaDataList={widgets} entityPrimaryKey={entityPrimaryKey} tableName={tableName} childUrlParams={childUrlParams} areChildren={true}/>
|
||||
)
|
||||
}
|
||||
<Widget
|
||||
label={label}
|
||||
widgetData={data}
|
||||
reloadWidgetCallback={parentReloadWidgetCallback}
|
||||
>
|
||||
<Box px={3}>
|
||||
<DashboardWidgets widgetMetaDataList={widgets} entityPrimaryKey={entityPrimaryKey} tableName={tableName} childUrlParams={childUrlParams} areChildren={true}/>
|
||||
</Box>
|
||||
</Card>
|
||||
</Widget>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -30,12 +30,11 @@ interface Props
|
||||
{
|
||||
title: string;
|
||||
data: any;
|
||||
reloadWidgetCallback?: (widgetIndex: number, params: string) => void;
|
||||
}
|
||||
|
||||
RecordGridWidget.defaultProps = {};
|
||||
|
||||
function RecordGridWidget({title, data, reloadWidgetCallback}: Props): JSX.Element
|
||||
function RecordGridWidget({title, data}: Props): JSX.Element
|
||||
{
|
||||
const [rows, setRows] = useState([]);
|
||||
const [columns, setColumns] = useState([]);
|
||||
@ -87,7 +86,6 @@ function RecordGridWidget({title, data, reloadWidgetCallback}: Props): JSX.Eleme
|
||||
label={title}
|
||||
labelAdditionalComponentsLeft={labelAdditionalComponentsLeft}
|
||||
labelAdditionalComponentsRight={labelAdditionalComponentsRight}
|
||||
reloadWidgetCallback={reloadWidgetCallback}
|
||||
>
|
||||
<DataGridPro
|
||||
autoHeight
|
||||
|
@ -123,7 +123,7 @@ function TableCard({title, linkText, linkURL, noRowsFoundHTML, data, dropdownOpt
|
||||
<Grid container>
|
||||
<Grid item xs={6}>
|
||||
<MDBox pt={3} px={3}>
|
||||
<MDTypography variant={isChild ? "h5" : "h6"} fontWeight="medium">
|
||||
<MDTypography variant={isChild ? "h6" : "h5"} fontWeight="medium">
|
||||
{title}
|
||||
</MDTypography>
|
||||
</MDBox>
|
||||
|
@ -25,27 +25,42 @@ import Button from "@mui/material/Button";
|
||||
import Card from "@mui/material/Card";
|
||||
import Modal from "@mui/material/Modal";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import React, {useState} from "react";
|
||||
import React, {useEffect, useState} from "react";
|
||||
import {Link, useNavigate} from "react-router-dom";
|
||||
import DashboardWidgets from "qqq/components/DashboardWidgets";
|
||||
import EntityForm from "qqq/components/EntityForm";
|
||||
import DropdownMenu, {DropdownOption} from "qqq/pages/dashboards/Widgets/Components/DropdownMenu";
|
||||
|
||||
export interface WidgetData
|
||||
{
|
||||
dropdownLabelList: string[];
|
||||
dropdownNameList: string[];
|
||||
dropdownDataList: {
|
||||
id: string,
|
||||
label: string
|
||||
}[][];
|
||||
dropdownNeedsSelectedText?: string;
|
||||
}
|
||||
|
||||
|
||||
interface Props
|
||||
{
|
||||
label: string;
|
||||
labelAdditionalComponentsLeft: LabelComponent[];
|
||||
labelAdditionalComponentsRight: LabelComponent[];
|
||||
widgetData?: WidgetData;
|
||||
children: JSX.Element;
|
||||
reloadWidgetCallback?: (widgetIndex: number, params: string) => void;
|
||||
reloadWidgetCallback?: (params: string) => void;
|
||||
}
|
||||
|
||||
Widget.defaultProps = {
|
||||
label: null,
|
||||
widgetData: {},
|
||||
labelAdditionalComponentsLeft: [],
|
||||
labelAdditionalComponentsRight: [],
|
||||
};
|
||||
|
||||
|
||||
|
||||
export class LabelComponent
|
||||
{
|
||||
|
||||
@ -71,7 +86,7 @@ export class HeaderLink extends LabelComponent
|
||||
export class AddNewRecordButton extends LabelComponent
|
||||
{
|
||||
table: QTableMetaData;
|
||||
label:string;
|
||||
label: string;
|
||||
defaultValues: any;
|
||||
disabledFields: any;
|
||||
|
||||
@ -86,10 +101,28 @@ export class AddNewRecordButton extends LabelComponent
|
||||
}
|
||||
|
||||
|
||||
export class Dropdown extends LabelComponent
|
||||
{
|
||||
label: string;
|
||||
options: DropdownOption[];
|
||||
onChangeCallback: any
|
||||
|
||||
constructor(label: string, options: DropdownOption[], onChangeCallback: any)
|
||||
{
|
||||
super();
|
||||
this.label = label;
|
||||
this.options = options;
|
||||
this.onChangeCallback = onChangeCallback;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
function Widget(props: React.PropsWithChildren<Props>): JSX.Element
|
||||
{
|
||||
const navigate = useNavigate();
|
||||
const [dropdownData, setDropdownData] = useState([]);
|
||||
const [counter, setCounter] = useState(0);
|
||||
|
||||
function openEditForm(table: QTableMetaData, id: any = null, defaultValues: any, disabledFields: any)
|
||||
{
|
||||
@ -118,8 +151,101 @@ function Widget(props: React.PropsWithChildren<Props>): JSX.Element
|
||||
);
|
||||
}
|
||||
|
||||
if (component instanceof Dropdown)
|
||||
{
|
||||
const dropdown = component as Dropdown
|
||||
return (
|
||||
<Box my={3} mr={2} sx={{float: "right"}}>
|
||||
<DropdownMenu
|
||||
sx={{width: 200, marginLeft: "15px"}}
|
||||
label={`Select ${dropdown.label}`}
|
||||
dropdownOptions={dropdown.options}
|
||||
onChangeCallback={dropdown.onChangeCallback}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
return (<div>Unsupported component type.</div>)
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
// make dropdowns from the widgetData appear as label-components //
|
||||
///////////////////////////////////////////////////////////////////
|
||||
const effectiveLabelAdditionalComponentsRight: LabelComponent[] = [];
|
||||
if(props.labelAdditionalComponentsRight)
|
||||
{
|
||||
props.labelAdditionalComponentsRight.map((component) => effectiveLabelAdditionalComponentsRight.push(component));
|
||||
}
|
||||
if(props.widgetData && props.widgetData.dropdownDataList)
|
||||
{
|
||||
props.widgetData.dropdownDataList?.map((dropdownData: any, index: number) =>
|
||||
{
|
||||
effectiveLabelAdditionalComponentsRight.push(new Dropdown(props.widgetData.dropdownLabelList[index], dropdownData, handleDataChange))
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function handleDataChange(dropdownLabel: string, changedData: any)
|
||||
{
|
||||
if(dropdownData)
|
||||
{
|
||||
///////////////////////////////////////////
|
||||
// find the index base on selected label //
|
||||
///////////////////////////////////////////
|
||||
const tableName = dropdownLabel.replace("Select ", "");
|
||||
let index = -1;
|
||||
for (let i = 0; i < props.widgetData.dropdownLabelList.length; i++)
|
||||
{
|
||||
if (tableName === props.widgetData.dropdownLabelList[i])
|
||||
{
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (index < 0)
|
||||
{
|
||||
throw(`Could not find table name for label ${tableName}`);
|
||||
}
|
||||
|
||||
dropdownData[index] = (changedData) ? changedData.id : null;
|
||||
setDropdownData(dropdownData);
|
||||
setCounter(counter + 1);
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(dropdownData)
|
||||
{
|
||||
let params = "";
|
||||
for (let i = 0; i < dropdownData.length; i++)
|
||||
{
|
||||
if (i > 0)
|
||||
{
|
||||
params += "&";
|
||||
}
|
||||
params += `${props.widgetData.dropdownNameList[i]}=`;
|
||||
if(dropdownData[i])
|
||||
{
|
||||
params += `${dropdownData[i]}`;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if(props.reloadWidgetCallback)
|
||||
{
|
||||
props.reloadWidgetCallback(params);
|
||||
}
|
||||
else
|
||||
{
|
||||
console.log(`No reload widget callback in ${props.label}`)
|
||||
}
|
||||
}
|
||||
}, [counter]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Card sx={{width: "100%"}}>
|
||||
@ -137,14 +263,24 @@ function Widget(props: React.PropsWithChildren<Props>): JSX.Element
|
||||
</Box>
|
||||
<Box pr={1}>
|
||||
{
|
||||
props.labelAdditionalComponentsRight.map((component, i) =>
|
||||
effectiveLabelAdditionalComponentsRight.map((component, i) =>
|
||||
{
|
||||
return (<span key={i}>{renderComponent(component)}</span>);
|
||||
})
|
||||
}
|
||||
</Box>
|
||||
</Box>
|
||||
{props.children}
|
||||
{
|
||||
props.widgetData?.dropdownNeedsSelectedText ? (
|
||||
<Box pb={3} pr={3} sx={{width: "100%", textAlign: "right"}}>
|
||||
<Typography variant="body2">
|
||||
{props.widgetData?.dropdownNeedsSelectedText}
|
||||
</Typography>
|
||||
</Box>
|
||||
) : (
|
||||
props.children
|
||||
)
|
||||
}
|
||||
</Card>
|
||||
</>
|
||||
);
|
||||
|
@ -74,13 +74,14 @@ interface Props
|
||||
isWidget?: boolean;
|
||||
recordIds?: string | QQueryFilter;
|
||||
closeModalHandler?: (event: object, reason: string) => void;
|
||||
forceReInit?: number;
|
||||
}
|
||||
|
||||
const INITIAL_RETRY_MILLIS = 1_500;
|
||||
const RETRY_MAX_MILLIS = 12_000;
|
||||
const BACKOFF_AMOUNT = 1.5;
|
||||
|
||||
function ProcessRun({process, defaultProcessValues, isModal, isWidget, recordIds, closeModalHandler}: Props): JSX.Element
|
||||
function ProcessRun({process, defaultProcessValues, isModal, isWidget, recordIds, closeModalHandler, forceReInit}: Props): JSX.Element
|
||||
{
|
||||
const processNameParam = useParams().processName;
|
||||
const processName = process === null ? processNameParam : process.name;
|
||||
@ -98,6 +99,7 @@ function ProcessRun({process, defaultProcessValues, isModal, isWidget, recordIds
|
||||
const [newStep, setNewStep] = useState(null);
|
||||
const [steps, setSteps] = useState([] as QFrontendStepMetaData[]);
|
||||
const [needInitialLoad, setNeedInitialLoad] = useState(true);
|
||||
const [lastForcedReInit, setLastForcedReInit] = useState(null as number);
|
||||
const [processMetaData, setProcessMetaData] = useState(null);
|
||||
const [tableMetaData, setTableMetaData] = useState(null);
|
||||
const [tableSections, setTableSections] = useState(null as QTableSection[]);
|
||||
@ -322,13 +324,23 @@ function ProcessRun({process, defaultProcessValues, isModal, isWidget, recordIds
|
||||
|
||||
return (
|
||||
<>
|
||||
<MDTypography variant={isWidget ? "h6" : "h5"} component="div" fontWeight="bold">
|
||||
{(isModal) ? `${process.label}: ` : ""}
|
||||
{step?.label}
|
||||
</MDTypography>
|
||||
{step.components && (
|
||||
step.components.map((component: QFrontendComponent, index: number) => (
|
||||
// eslint-disable-next-line react/no-array-index-key
|
||||
{
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// hide label on widgets - the Widget component itself provides the label //
|
||||
// for modals, show the process label, but not for full-screen processes (for them, it is in the breadcrumb) //
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
!isWidget &&
|
||||
<MDTypography variant={isWidget ? "h6" : "h5"} component="div" fontWeight="bold">
|
||||
{(isModal) ? `${process.label}: ` : ""}
|
||||
{step?.label}
|
||||
</MDTypography>
|
||||
}
|
||||
|
||||
{
|
||||
//////////////////////////////////////////////////
|
||||
// render all of the components for this screen //
|
||||
//////////////////////////////////////////////////
|
||||
step.components && (step.components.map((component: QFrontendComponent, index: number) => (
|
||||
<div key={index}>
|
||||
{
|
||||
component.type === QComponentType.HELP_TEXT && (
|
||||
@ -925,10 +937,14 @@ function ProcessRun({process, defaultProcessValues, isModal, isWidget, recordIds
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////
|
||||
// do the initial load of data for the process - that is, meta data, plus the init step //
|
||||
// also - allow the component that contains this component to force a re-init, by //
|
||||
// changing the value in the forceReInit property //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////
|
||||
if (needInitialLoad)
|
||||
if (needInitialLoad || forceReInit != lastForcedReInit)
|
||||
{
|
||||
setNeedInitialLoad(false);
|
||||
setLastForcedReInit(forceReInit);
|
||||
|
||||
(async () =>
|
||||
{
|
||||
const urlSearchParams = new URLSearchParams(location.search);
|
||||
@ -1108,6 +1124,8 @@ function ProcessRun({process, defaultProcessValues, isModal, isWidget, recordIds
|
||||
}
|
||||
if(isWidget)
|
||||
{
|
||||
mainCardStyles.background = "none";
|
||||
mainCardStyles.boxShadow = "none";
|
||||
mainCardStyles.minHeight = "";
|
||||
mainCardStyles.alignItems = "stretch";
|
||||
mainCardStyles.flexGrow = 1;
|
||||
@ -1269,7 +1287,8 @@ ProcessRun.defaultProps = {
|
||||
isModal: false,
|
||||
isWidget: false,
|
||||
recordIds: null,
|
||||
closeModalHandler: null
|
||||
closeModalHandler: null,
|
||||
forceReInit: 0
|
||||
};
|
||||
|
||||
export default ProcessRun;
|
||||
|
Reference in New Issue
Block a user