Updates for dropdowns for all widgets, process specifically

This commit is contained in:
2022-12-09 10:36:59 -06:00
parent 7ea20fd82f
commit a55f87946a
9 changed files with 198 additions and 118 deletions

View File

@ -184,7 +184,14 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, omit
} }
{ {
widgetMetaData.type === "process" && widgetData[i]?.processMetaData && ( 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 <RecordGridWidget
title={widgetMetaData.label} title={widgetMetaData.label}
data={widgetData[i]} data={widgetData[i]}
reloadWidgetCallback={reloadWidget}
/> />
) )
} }
{ {
widgetMetaData.type === "fieldValueList" && ( widgetMetaData.type === "fieldValueList" && (

View File

@ -154,7 +154,7 @@ function EntityForm(props: Props): JSX.Element
///////////////////////////////////////////////// /////////////////////////////////////////////////
// define the sections, e.g., for the left-bar // // define the sections, e.g., for the left-bar //
///////////////////////////////////////////////// /////////////////////////////////////////////////
const tableSections = QTableUtils.getSectionsForRecordSidebar(tableMetaData); const tableSections = QTableUtils.getSectionsForRecordSidebar(tableMetaData, [...tableMetaData.fields.keys()]);
setTableSections(tableSections); setTableSections(tableSections);
const fieldArray = [] as QFieldMetaData[]; const fieldArray = [] as QFieldMetaData[];

View File

@ -101,7 +101,7 @@ interface QActionsMenuButtonProps
export function QActionsMenuButton({isOpen, onClickHandler}: QActionsMenuButtonProps): JSX.Element export function QActionsMenuButton({isOpen, onClickHandler}: QActionsMenuButtonProps): JSX.Element
{ {
return ( return (
<MDBox width={standardWidth}> <MDBox width={standardWidth} ml={1}>
<MDButton <MDButton
variant={isOpen ? "contained" : "outlined"} variant={isOpen ? "contained" : "outlined"}
color="dark" color="dark"

View File

@ -339,7 +339,8 @@ function Overview(): JSX.Element
<MDBox mt={2}> <MDBox mt={2}>
<Grid container spacing={3}> <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}> <Grid item xs={12} md={6} lg={4} key={data.title}>
<MDBox mt={3}> <MDBox mt={3}>
<LocationCard <LocationCard

View File

@ -27,6 +27,7 @@ import Grid from "@mui/material/Grid";
import React, {useEffect, useState} from "react"; import React, {useEffect, useState} from "react";
import DashboardWidgets from "qqq/components/DashboardWidgets"; import DashboardWidgets from "qqq/components/DashboardWidgets";
import DropdownMenu from "qqq/pages/dashboards/Widgets/Components/DropdownMenu"; 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"; import QClient from "qqq/utils/QClient";
@ -91,104 +92,22 @@ function ParentWidget({widgetIndex, label, data, reloadWidgetCallback, entityPri
} }
}, [qInstance, data]); }, [qInstance, data]);
function handleDataChange(dropdownLabel: string, changedData: any) const parentReloadWidgetCallback = (data: string) =>
{ {
if(dropdownData) setChildUrlParams(data);
{ reloadWidgetCallback(widgetIndex, data);
///////////////////////////////////////////
// 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);
}
} }
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 ( return (
<Card className="parentWidgetCard" sx={{alignItems: "stretch", flexGrow: 1, display: "flex", marginTop: "0px", paddingTop: "0px"}}> <Widget
label={label}
<Grid container> widgetData={data}
<Grid item xs={4}> reloadWidgetCallback={parentReloadWidgetCallback}
<Box pt={3} px={3}> >
{ <Box px={3}>
label && ( <DashboardWidgets widgetMetaDataList={widgets} entityPrimaryKey={entityPrimaryKey} tableName={tableName} childUrlParams={childUrlParams} areChildren={true}/>
<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}/>
)
}
</Box> </Box>
</Card> </Widget>
); );
} }

View File

@ -30,12 +30,11 @@ interface Props
{ {
title: string; title: string;
data: any; data: any;
reloadWidgetCallback?: (widgetIndex: number, params: string) => void;
} }
RecordGridWidget.defaultProps = {}; RecordGridWidget.defaultProps = {};
function RecordGridWidget({title, data, reloadWidgetCallback}: Props): JSX.Element function RecordGridWidget({title, data}: Props): JSX.Element
{ {
const [rows, setRows] = useState([]); const [rows, setRows] = useState([]);
const [columns, setColumns] = useState([]); const [columns, setColumns] = useState([]);
@ -87,7 +86,6 @@ function RecordGridWidget({title, data, reloadWidgetCallback}: Props): JSX.Eleme
label={title} label={title}
labelAdditionalComponentsLeft={labelAdditionalComponentsLeft} labelAdditionalComponentsLeft={labelAdditionalComponentsLeft}
labelAdditionalComponentsRight={labelAdditionalComponentsRight} labelAdditionalComponentsRight={labelAdditionalComponentsRight}
reloadWidgetCallback={reloadWidgetCallback}
> >
<DataGridPro <DataGridPro
autoHeight autoHeight

View File

@ -123,7 +123,7 @@ function TableCard({title, linkText, linkURL, noRowsFoundHTML, data, dropdownOpt
<Grid container> <Grid container>
<Grid item xs={6}> <Grid item xs={6}>
<MDBox pt={3} px={3}> <MDBox pt={3} px={3}>
<MDTypography variant={isChild ? "h5" : "h6"} fontWeight="medium"> <MDTypography variant={isChild ? "h6" : "h5"} fontWeight="medium">
{title} {title}
</MDTypography> </MDTypography>
</MDBox> </MDBox>

View File

@ -25,27 +25,42 @@ import Button from "@mui/material/Button";
import Card from "@mui/material/Card"; import Card from "@mui/material/Card";
import Modal from "@mui/material/Modal"; import Modal from "@mui/material/Modal";
import Typography from "@mui/material/Typography"; 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 {Link, useNavigate} from "react-router-dom";
import DashboardWidgets from "qqq/components/DashboardWidgets";
import EntityForm from "qqq/components/EntityForm"; 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 interface Props
{ {
label: string; label: string;
labelAdditionalComponentsLeft: LabelComponent[]; labelAdditionalComponentsLeft: LabelComponent[];
labelAdditionalComponentsRight: LabelComponent[]; labelAdditionalComponentsRight: LabelComponent[];
widgetData?: WidgetData;
children: JSX.Element; children: JSX.Element;
reloadWidgetCallback?: (widgetIndex: number, params: string) => void; reloadWidgetCallback?: (params: string) => void;
} }
Widget.defaultProps = { Widget.defaultProps = {
label: null, label: null,
widgetData: {},
labelAdditionalComponentsLeft: [], labelAdditionalComponentsLeft: [],
labelAdditionalComponentsRight: [], labelAdditionalComponentsRight: [],
}; };
export class LabelComponent export class LabelComponent
{ {
@ -71,7 +86,7 @@ export class HeaderLink extends LabelComponent
export class AddNewRecordButton extends LabelComponent export class AddNewRecordButton extends LabelComponent
{ {
table: QTableMetaData; table: QTableMetaData;
label:string; label: string;
defaultValues: any; defaultValues: any;
disabledFields: 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 function Widget(props: React.PropsWithChildren<Props>): JSX.Element
{ {
const navigate = useNavigate(); const navigate = useNavigate();
const [dropdownData, setDropdownData] = useState([]);
const [counter, setCounter] = useState(0);
function openEditForm(table: QTableMetaData, id: any = null, defaultValues: any, disabledFields: any) 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 ( return (
<> <>
<Card sx={{width: "100%"}}> <Card sx={{width: "100%"}}>
@ -137,14 +263,24 @@ function Widget(props: React.PropsWithChildren<Props>): JSX.Element
</Box> </Box>
<Box pr={1}> <Box pr={1}>
{ {
props.labelAdditionalComponentsRight.map((component, i) => effectiveLabelAdditionalComponentsRight.map((component, i) =>
{ {
return (<span key={i}>{renderComponent(component)}</span>); return (<span key={i}>{renderComponent(component)}</span>);
}) })
} }
</Box> </Box>
</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> </Card>
</> </>
); );

View File

@ -74,13 +74,14 @@ interface Props
isWidget?: boolean; isWidget?: boolean;
recordIds?: string | QQueryFilter; recordIds?: string | QQueryFilter;
closeModalHandler?: (event: object, reason: string) => void; closeModalHandler?: (event: object, reason: string) => void;
forceReInit?: number;
} }
const INITIAL_RETRY_MILLIS = 1_500; const INITIAL_RETRY_MILLIS = 1_500;
const RETRY_MAX_MILLIS = 12_000; const RETRY_MAX_MILLIS = 12_000;
const BACKOFF_AMOUNT = 1.5; 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 processNameParam = useParams().processName;
const processName = process === null ? processNameParam : process.name; const processName = process === null ? processNameParam : process.name;
@ -98,6 +99,7 @@ function ProcessRun({process, defaultProcessValues, isModal, isWidget, recordIds
const [newStep, setNewStep] = useState(null); const [newStep, setNewStep] = useState(null);
const [steps, setSteps] = useState([] as QFrontendStepMetaData[]); const [steps, setSteps] = useState([] as QFrontendStepMetaData[]);
const [needInitialLoad, setNeedInitialLoad] = useState(true); const [needInitialLoad, setNeedInitialLoad] = useState(true);
const [lastForcedReInit, setLastForcedReInit] = useState(null as number);
const [processMetaData, setProcessMetaData] = useState(null); const [processMetaData, setProcessMetaData] = useState(null);
const [tableMetaData, setTableMetaData] = useState(null); const [tableMetaData, setTableMetaData] = useState(null);
const [tableSections, setTableSections] = useState(null as QTableSection[]); const [tableSections, setTableSections] = useState(null as QTableSection[]);
@ -322,13 +324,23 @@ function ProcessRun({process, defaultProcessValues, isModal, isWidget, recordIds
return ( return (
<> <>
<MDTypography variant={isWidget ? "h6" : "h5"} component="div" fontWeight="bold"> {
{(isModal) ? `${process.label}: ` : ""} ///////////////////////////////////////////////////////////////////////////////////////////////////////////////
{step?.label} // hide label on widgets - the Widget component itself provides the label //
</MDTypography> // for modals, show the process label, but not for full-screen processes (for them, it is in the breadcrumb) //
{step.components && ( ///////////////////////////////////////////////////////////////////////////////////////////////////////////////
step.components.map((component: QFrontendComponent, index: number) => ( !isWidget &&
// eslint-disable-next-line react/no-array-index-key <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}> <div key={index}>
{ {
component.type === QComponentType.HELP_TEXT && ( 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 // // 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); setNeedInitialLoad(false);
setLastForcedReInit(forceReInit);
(async () => (async () =>
{ {
const urlSearchParams = new URLSearchParams(location.search); const urlSearchParams = new URLSearchParams(location.search);
@ -1108,6 +1124,8 @@ function ProcessRun({process, defaultProcessValues, isModal, isWidget, recordIds
} }
if(isWidget) if(isWidget)
{ {
mainCardStyles.background = "none";
mainCardStyles.boxShadow = "none";
mainCardStyles.minHeight = ""; mainCardStyles.minHeight = "";
mainCardStyles.alignItems = "stretch"; mainCardStyles.alignItems = "stretch";
mainCardStyles.flexGrow = 1; mainCardStyles.flexGrow = 1;
@ -1269,7 +1287,8 @@ ProcessRun.defaultProps = {
isModal: false, isModal: false,
isWidget: false, isWidget: false,
recordIds: null, recordIds: null,
closeModalHandler: null closeModalHandler: null,
forceReInit: 0
}; };
export default ProcessRun; export default ProcessRun;