CE-1946: checkpoint initial commit

This commit is contained in:
Tim Chamberlain
2024-11-19 15:07:10 -06:00
parent 2514c463a6
commit 726906061d
6 changed files with 4429 additions and 4676 deletions

8578
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -18,18 +18,20 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData";
import {QWidgetMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QWidgetMetaData"; import {QWidgetMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QWidgetMetaData";
import {QRecord} from "@kingsrook/qqq-frontend-core/lib/model/QRecord"; import {QRecord} from "@kingsrook/qqq-frontend-core/lib/model/QRecord";
import {Alert, Skeleton} from "@mui/material"; import {Alert, Skeleton} from "@mui/material";
import Box from "@mui/material/Box"; import Box from "@mui/material/Box";
import Grid from "@mui/material/Grid"; import Grid from "@mui/material/Grid";
import Modal from "@mui/material/Modal";
import Tab from "@mui/material/Tab"; import Tab from "@mui/material/Tab";
import Tabs from "@mui/material/Tabs"; import Tabs from "@mui/material/Tabs";
import parse from "html-react-parser"; import parse from "html-react-parser";
import QContext from "QContext"; import QContext from "QContext";
import EntityForm from "qqq/components/forms/EntityForm";
import MDTypography from "qqq/components/legacy/MDTypography"; import MDTypography from "qqq/components/legacy/MDTypography";
import TabPanel from "qqq/components/misc/TabPanel"; import TabPanel from "qqq/components/misc/TabPanel";
import {BlockData} from "qqq/components/widgets/blocks/BlockModels";
import BarChart from "qqq/components/widgets/charts/barchart/BarChart"; import BarChart from "qqq/components/widgets/charts/barchart/BarChart";
import HorizontalBarChart from "qqq/components/widgets/charts/barchart/HorizontalBarChart"; import HorizontalBarChart from "qqq/components/widgets/charts/barchart/HorizontalBarChart";
import DefaultLineChart from "qqq/components/widgets/charts/linechart/DefaultLineChart"; import DefaultLineChart from "qqq/components/widgets/charts/linechart/DefaultLineChart";
@ -44,7 +46,7 @@ import FieldValueListWidget from "qqq/components/widgets/misc/FieldValueListWidg
import FilterAndColumnsSetupWidget from "qqq/components/widgets/misc/FilterAndColumnsSetupWidget"; import FilterAndColumnsSetupWidget from "qqq/components/widgets/misc/FilterAndColumnsSetupWidget";
import PivotTableSetupWidget from "qqq/components/widgets/misc/PivotTableSetupWidget"; import PivotTableSetupWidget from "qqq/components/widgets/misc/PivotTableSetupWidget";
import QuickSightChart from "qqq/components/widgets/misc/QuickSightChart"; import QuickSightChart from "qqq/components/widgets/misc/QuickSightChart";
import RecordGridWidget from "qqq/components/widgets/misc/RecordGridWidget"; import RecordGridWidget, {ChildRecordListData} from "qqq/components/widgets/misc/RecordGridWidget";
import ScriptViewer from "qqq/components/widgets/misc/ScriptViewer"; import ScriptViewer from "qqq/components/widgets/misc/ScriptViewer";
import StepperCard from "qqq/components/widgets/misc/StepperCard"; import StepperCard from "qqq/components/widgets/misc/StepperCard";
import USMapWidget from "qqq/components/widgets/misc/USMapWidget"; import USMapWidget from "qqq/components/widgets/misc/USMapWidget";
@ -72,9 +74,9 @@ interface Props
childUrlParams?: string; childUrlParams?: string;
parentWidgetMetaData?: QWidgetMetaData; parentWidgetMetaData?: QWidgetMetaData;
wrapWidgetsInTabPanels: boolean; wrapWidgetsInTabPanels: boolean;
actionCallback?: (blockData: BlockData) => boolean; actionCallback?: (data: any, eventValues?: { [name: string]: any }) => boolean;
initialWidgetDataList: any[]; initialWidgetDataList: any[];
values?: {[key: string]: any}; values?: { [key: string]: any };
} }
DashboardWidgets.defaultProps = { DashboardWidgets.defaultProps = {
@ -101,6 +103,12 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, reco
const [haveLoadedParams, setHaveLoadedParams] = useState(false); const [haveLoadedParams, setHaveLoadedParams] = useState(false);
const {accentColor} = useContext(QContext); const {accentColor} = useContext(QContext);
/////////////////////////
// modal form controls //
/////////////////////////
const [showEditChildForm, setShowEditChildForm] = useState(null as any);
const [modalTable, setModalTable] = useState(null as QTableMetaData);
let initialSelectedTab = 0; let initialSelectedTab = 0;
let selectedTabKey: string = null; let selectedTabKey: string = null;
if (parentWidgetMetaData && wrapWidgetsInTabPanels) if (parentWidgetMetaData && wrapWidgetsInTabPanels)
@ -121,11 +129,11 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, reco
useEffect(() => useEffect(() =>
{ {
if(initialWidgetDataList && initialWidgetDataList.length > 0) if (initialWidgetDataList && initialWidgetDataList.length > 0)
{ {
// todo actually, should this check each element of the array, down in the loop? yeah, when we need to, do it that way. // todo actually, should this check each element of the array, down in the loop? yeah, when we need to, do it that way.
console.log("We already have initial widget data, so not fetching from backend."); console.log("We already have initial widget data, so not fetching from backend.");
return return;
} }
setWidgetData([]); setWidgetData([]);
@ -166,7 +174,7 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, reco
const reloadWidget = async (index: number, data: string) => const reloadWidget = async (index: number, data: string) =>
{ {
(async () => await (async () =>
{ {
const urlParams = getQueryParams(widgetMetaDataList[index], data); const urlParams = getQueryParams(widgetMetaDataList[index], data);
setCurrentUrlParams(urlParams); setCurrentUrlParams(urlParams);
@ -285,6 +293,132 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, reco
return (rs); return (rs);
} }
/*******************************************************************************
**
*******************************************************************************/
const closeEditChildForm = (event: object, reason: string) =>
{
if (reason === "backdropClick" || reason === "escapeKeyDown")
{
return;
}
setShowEditChildForm(null);
};
/*******************************************************************************
**
*******************************************************************************/
function deleteChildRecord(name: string, widgetIndex: number, rowIndex: number)
{
updateChildRecordList(name, "delete", rowIndex);
actionCallback(widgetData[widgetIndex]);
};
/*******************************************************************************
**
*******************************************************************************/
function openEditChildRecord(name: string, widgetData: any, rowIndex: number)
{
let defaultValues = widgetData.queryOutput.records[rowIndex].values;
let disabledFields = widgetData.disabledFieldsForNewChildRecords;
if (!disabledFields)
{
disabledFields = widgetData.defaultValuesForNewChildRecords;
}
doOpenEditChildForm(name, widgetData.childTableMetaData, rowIndex, defaultValues, disabledFields);
}
/*******************************************************************************
**
*******************************************************************************/
function doOpenEditChildForm(widgetName: string, table: QTableMetaData, rowIndex: number, defaultValues: any, disabledFields: any)
{
const showEditChildForm: any = {};
showEditChildForm.widgetName = widgetName;
showEditChildForm.table = table;
showEditChildForm.rowIndex = rowIndex;
showEditChildForm.defaultValues = defaultValues;
showEditChildForm.disabledFields = disabledFields;
setShowEditChildForm(showEditChildForm);
}
/*******************************************************************************
**
*******************************************************************************/
function submitEditChildForm(values: any)
{
updateChildRecordList(showEditChildForm.widgetName, showEditChildForm.rowIndex == null ? "insert" : "edit", showEditChildForm.rowIndex, values);
let widgetIndex = determineChildRecordListIndex(showEditChildForm.widgetName);
actionCallback(widgetData[widgetIndex]);
}
/*******************************************************************************
**
*******************************************************************************/
function determineChildRecordListIndex(widgetName: string): number
{
let widgetIndex = -1;
for (var i = 0; i < widgetMetaDataList.length; i++)
{
const widgetMetaData = widgetMetaDataList[i];
if (widgetMetaData.name == widgetName)
{
widgetIndex = i;
break;
}
}
return (widgetIndex);
}
/*******************************************************************************
**
*******************************************************************************/
function updateChildRecordList(widgetName: string, action: "insert" | "edit" | "delete", rowIndex?: number, values?: any)
{
////////////////////////////////////////////////
// find the correct child record widget index //
////////////////////////////////////////////////
let widgetIndex = determineChildRecordListIndex(widgetName);
if (!widgetData[widgetIndex].queryOutput.records)
{
widgetData[widgetIndex].queryOutput.records = [];
}
const newChildListWidgetData: ChildRecordListData = widgetData[widgetIndex];
if (!newChildListWidgetData.queryOutput.records)
{
newChildListWidgetData.queryOutput.records = [];
}
switch (action)
{
case "insert":
newChildListWidgetData.queryOutput.records.push({values: values});
break;
case "edit":
newChildListWidgetData.queryOutput.records[rowIndex] = {values: values};
break;
case "delete":
newChildListWidgetData.queryOutput.records.splice(rowIndex, 1);
break;
}
newChildListWidgetData.totalRows = newChildListWidgetData.queryOutput.records.length;
widgetData[widgetIndex] = newChildListWidgetData;
setWidgetData(widgetData);
setShowEditChildForm(null);
}
const renderWidget = (widgetMetaData: QWidgetMetaData, i: number): JSX.Element => const renderWidget = (widgetMetaData: QWidgetMetaData, i: number): JSX.Element =>
{ {
const labelAdditionalComponentsRight: LabelComponent[] = []; const labelAdditionalComponentsRight: LabelComponent[] = [];
@ -324,7 +458,7 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, reco
) )
} }
{ {
widgetMetaData.type === "alert" && widgetData[i]?.html && ( widgetMetaData.type === "alert" && widgetData[i]?.html && !widgetData[i]?.hideWidget && (
<Widget <Widget
omitPadding={true} omitPadding={true}
widgetMetaData={widgetMetaData} widgetMetaData={widgetMetaData}
@ -334,7 +468,16 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, reco
labelAdditionalComponentsRight={labelAdditionalComponentsRight} labelAdditionalComponentsRight={labelAdditionalComponentsRight}
labelAdditionalComponentsLeft={labelAdditionalComponentsLeft} labelAdditionalComponentsLeft={labelAdditionalComponentsLeft}
> >
<Alert severity={widgetData[i]?.alertType?.toLowerCase()}>{parse(widgetData[i]?.html)}</Alert> <Alert severity={widgetData[i]?.alertType?.toLowerCase()}>
{parse(widgetData[i]?.html)}
{widgetData[i]?.bulletList && (
<div style={{fontSize: "14px"}}>
{widgetData[i].bulletList.map((bullet: string, index: number) =>
<li key={`widget-${i}-${index}`}>{parse(bullet)}</li>
)}
</div>
)}
</Alert>
</Widget> </Widget>
) )
} }
@ -516,9 +659,7 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, reco
} }
{ {
widgetMetaData.type === "divider" && ( widgetMetaData.type === "divider" && (
<Box> <DividerWidget />
<DividerWidget />
</Box>
) )
} }
{ {
@ -552,6 +693,11 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, reco
widgetMetaData.type === "childRecordList" && ( widgetMetaData.type === "childRecordList" && (
widgetData && widgetData[i] && widgetData && widgetData[i] &&
<RecordGridWidget <RecordGridWidget
disableRowClick={true}
allowRecordEdit={true}
deleteRecordCallback={(rowIndex) => deleteChildRecord(widgetMetaData.name, i, rowIndex)}
editRecordCallback={(rowIndex) => openEditChildRecord(widgetMetaData.name, widgetData[i], rowIndex)}
allowRecordDelete={true}
widgetMetaData={widgetMetaData} widgetMetaData={widgetMetaData}
data={widgetData[i]} data={widgetData[i]}
/> />
@ -653,23 +799,23 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, reco
if (!omitWrappingGridContainer) if (!omitWrappingGridContainer)
{ {
const gridProps: {[key: string]: any} = {}; const gridProps: { [key: string]: any } = {};
for(let size of ["xs", "sm", "md", "lg", "xl", "xxl"]) for (let size of ["xs", "sm", "md", "lg", "xl", "xxl"])
{ {
const key = `gridCols:sizeClass:${size}` const key = `gridCols:sizeClass:${size}`;
if(widgetMetaData?.defaultValues?.has(key)) if (widgetMetaData?.defaultValues?.has(key))
{ {
gridProps[size] = widgetMetaData?.defaultValues.get(key); gridProps[size] = widgetMetaData?.defaultValues.get(key);
} }
} }
if(!gridProps["xxl"]) if (!gridProps["xxl"])
{ {
gridProps["xxl"] = widgetMetaData.gridColumns ? widgetMetaData.gridColumns : 12; gridProps["xxl"] = widgetMetaData.gridColumns ? widgetMetaData.gridColumns : 12;
} }
if(!gridProps["xs"]) if (!gridProps["xs"])
{ {
gridProps["xs"] = 12; gridProps["xs"] = 12;
} }
@ -725,6 +871,22 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, reco
</Grid> </Grid>
) )
} }
{
showEditChildForm &&
<Modal open={showEditChildForm as boolean} onClose={(event, reason) => closeEditChildForm(event, reason)}>
<div className="modalEditForm">
<EntityForm
isModal={true}
closeModalHandler={closeEditChildForm}
table={showEditChildForm.table}
defaultValues={showEditChildForm.defaultValues}
disabledFields={showEditChildForm.disabledFields}
onSubmitCallback={submitEditChildForm}
overrideHeading={`${showEditChildForm.rowIndex != null ? "Editing" : "Creating New"} ${showEditChildForm.table.label}`}
/>
</div>
</Modal>
}
</> </>
) : null ) : null
); );

View File

@ -19,13 +19,16 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import Box from "@mui/material/Box";
import Divider from "@mui/material/Divider"; import Divider from "@mui/material/Divider";
function DividerWidget(): JSX.Element function DividerWidget(): JSX.Element
{ {
return ( return (
<Divider sx={{padding: "1px", background: "red"}}/> <Box pl={3} pt={1} width="100%">
<Divider sx={{width: "100%", height: "1px", background: "grey"}} />
</Box>
); );
} }

View File

@ -39,15 +39,15 @@ import {Link, useNavigate} from "react-router-dom";
export interface ChildRecordListData extends WidgetData export interface ChildRecordListData extends WidgetData
{ {
title: string; title?: string;
queryOutput: { records: { values: any }[] }; queryOutput?: { records: { values: any }[] };
childTableMetaData: QTableMetaData; childTableMetaData?: QTableMetaData;
tablePath: string; tablePath?: string;
viewAllLink: string; viewAllLink?: string;
totalRows: number; totalRows?: number;
canAddChildRecord: boolean; canAddChildRecord?: boolean;
defaultValuesForNewChildRecords: { [fieldName: string]: any }; defaultValuesForNewChildRecords?: { [fieldName: string]: any };
disabledFieldsForNewChildRecords: { [fieldName: string]: any }; disabledFieldsForNewChildRecords?: { [fieldName: string]: any };
} }
interface Props interface Props
@ -176,7 +176,7 @@ function RecordGridWidget({widgetMetaData, data, addNewRecordCallback, disableRo
setCsv(csv); setCsv(csv);
setFileName(fileName); setFileName(fileName);
} }
}, [data]); }, [JSON.stringify(data?.queryOutput)]);
/////////////////// ///////////////////
// view all link // // view all link //
@ -304,7 +304,7 @@ function RecordGridWidget({widgetMetaData, data, addNewRecordCallback, disableRo
labelAdditionalComponentsRight={labelAdditionalComponentsRight} labelAdditionalComponentsRight={labelAdditionalComponentsRight}
labelBoxAdditionalSx={{position: "relative", top: "-0.375rem"}} labelBoxAdditionalSx={{position: "relative", top: "-0.375rem"}}
> >
<Box mx={-3} mb={-3}> <Box>
<Box> <Box>
<DataGridPro <DataGridPro
autoHeight autoHeight

View File

@ -0,0 +1,96 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData";
import Modal from "@mui/material/Modal";
import EntityForm from "qqq/components/forms/EntityForm";
import Client from "qqq/utils/qqq/Client";
import React, {useEffect, useReducer, useState} from "react";
////////////////////////////////
// structure of expected data //
////////////////////////////////
export interface ModalEditFormData
{
tableName: string;
defaultValues?: { [key: string]: string };
disabledFields?: { [key: string]: boolean } | string[];
overrideHeading?: string;
onSubmitCallback?: (values: any) => void;
initialShowModalValue?: boolean;
}
const qController = Client.getInstance();
function ModalEditForm({tableName, defaultValues, disabledFields, overrideHeading, onSubmitCallback, initialShowModalValue}: ModalEditFormData,): JSX.Element
{
const [showModal, setShowModal] = useState(initialShowModalValue);
const [table, setTable] = useState(null as QTableMetaData);
const [, forceUpdate] = useReducer((x) => x + 1, 0);
useEffect(() =>
{
if (!tableName)
{
return;
}
(async () =>
{
const tableMetaData = await qController.loadTableMetaData(tableName);
setTable(tableMetaData);
forceUpdate();
})();
}, [tableName]);
/*******************************************************************************
**
*******************************************************************************/
const closeEditChildForm = (event: object, reason: string) =>
{
if (reason === "backdropClick" || reason === "escapeKeyDown")
{
return;
}
setShowModal(null);
};
return (
table && showModal &&
<Modal open={showModal as boolean} onClose={(event, reason) => closeEditChildForm(event, reason)}>
<div className="modalEditForm">
<EntityForm
isModal={true}
closeModalHandler={closeEditChildForm}
table={table}
defaultValues={defaultValues}
disabledFields={disabledFields}
onSubmitCallback={onSubmitCallback}
overrideHeading={overrideHeading}
/>
</div>
</Modal>
);
}
export default ModalEditForm;

View File

@ -66,6 +66,7 @@ import ValidationReview from "qqq/components/processes/ValidationReview";
import {BlockData} from "qqq/components/widgets/blocks/BlockModels"; import {BlockData} from "qqq/components/widgets/blocks/BlockModels";
import CompositeWidget, {CompositeData} from "qqq/components/widgets/CompositeWidget"; import CompositeWidget, {CompositeData} from "qqq/components/widgets/CompositeWidget";
import DashboardWidgets from "qqq/components/widgets/DashboardWidgets"; import DashboardWidgets from "qqq/components/widgets/DashboardWidgets";
import {ChildRecordListData} from "qqq/components/widgets/misc/RecordGridWidget";
import BaseLayout from "qqq/layouts/BaseLayout"; import BaseLayout from "qqq/layouts/BaseLayout";
import ProcessWidgetBlockUtils from "qqq/pages/processes/ProcessWidgetBlockUtils"; import ProcessWidgetBlockUtils from "qqq/pages/processes/ProcessWidgetBlockUtils";
import {TABLE_VARIANT_LOCAL_STORAGE_KEY_ROOT} from "qqq/pages/records/query/RecordQuery"; import {TABLE_VARIANT_LOCAL_STORAGE_KEY_ROOT} from "qqq/pages/records/query/RecordQuery";
@ -150,7 +151,7 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is
const [previouslySeenUpdatedFieldMetaDataMap, setPreviouslySeenUpdatedFieldMetaDataMap] = useState(new Map<string, QFieldMetaData>); const [previouslySeenUpdatedFieldMetaDataMap, setPreviouslySeenUpdatedFieldMetaDataMap] = useState(new Map<string, QFieldMetaData>);
const [renderedWidgets, setRenderedWidgets] = useState({} as { [step: string]: { [widgetName: string]: any } }); const [renderedWidgets, setRenderedWidgets] = useState({} as { [step: string]: { [widgetName: string]: any } });
const [controlCallbacks, setControlCallbacks] = useState({} as {[name: string]: () => void}) const [controlCallbacks, setControlCallbacks] = useState({} as { [name: string]: () => void });
const {pageHeader, recordAnalytics, setPageHeader, helpHelpActive} = useContext(QContext); const {pageHeader, recordAnalytics, setPageHeader, helpHelpActive} = useContext(QContext);
@ -185,7 +186,7 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is
{ {
noMoreSteps = true; noMoreSteps = true;
} }
if(processValues["noMoreSteps"]) if (processValues["noMoreSteps"])
{ {
////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////
// this, to allow a non-linear process to request this behavior // // this, to allow a non-linear process to request this behavior //
@ -210,7 +211,8 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is
const [recordConfig, setRecordConfig] = useState({} as any); const [recordConfig, setRecordConfig] = useState({} as any);
const [pageNumber, setPageNumber] = useState(0); const [pageNumber, setPageNumber] = useState(0);
const [rowsPerPage, setRowsPerPage] = useState(10); const [rowsPerPage, setRowsPerPage] = useState(10);
const [records, setRecords] = useState([] as QRecord[]); const [records, setRecords] = useState([] as any);
const [childRecordData, setChildRecordData] = useState(null as ChildRecordListData);
////////////////////////////// //////////////////////////////
// state for bulk edit form // // state for bulk edit form //
@ -329,23 +331,24 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is
*******************************************************************************/ *******************************************************************************/
function renderWidget(widgetName: string) function renderWidget(widgetName: string)
{ {
const widgetMetaData = qInstance.widgets.get(widgetName);
if (!widgetMetaData)
{
return (<Alert color="error">Unrecognized widget name: {widgetName}</Alert>);
}
if (!renderedWidgets[activeStep.name]) if (!renderedWidgets[activeStep.name])
{ {
renderedWidgets[activeStep.name] = {}; renderedWidgets[activeStep.name] = {};
setRenderedWidgets(renderedWidgets); setRenderedWidgets(renderedWidgets);
} }
if (renderedWidgets[activeStep.name][widgetName]) let isChildRecordWidget = widgetMetaData.type == "childRecordList";
if (!isChildRecordWidget && renderedWidgets[activeStep.name][widgetName])
{ {
return renderedWidgets[activeStep.name][widgetName]; return renderedWidgets[activeStep.name][widgetName];
} }
const widgetMetaData = qInstance.widgets.get(widgetName);
if (!widgetMetaData)
{
return (<Alert color="error">Unrecognized widget name: {widgetName}</Alert>);
}
const queryStringParts: string[] = []; const queryStringParts: string[] = [];
for (let name in processValues) for (let name in processValues)
{ {
@ -353,14 +356,25 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is
} }
let initialWidgetDataList = null; let initialWidgetDataList = null;
if(processValues[widgetName]) if (processValues[widgetName])
{ {
processValues[widgetName].hasPermission = true processValues[widgetName].hasPermission = true;
initialWidgetDataList = [processValues[widgetName]] initialWidgetDataList = [processValues[widgetName]];
}
let actionCallback = blockWidgetActionCallback;
if (isChildRecordWidget)
{
actionCallback = childRecordListWidgetActionCallBack;
if (childRecordData)
{
initialWidgetDataList = [childRecordData];
}
} }
const renderedWidget = (<Box m={-2}> const renderedWidget = (<Box m={-2}>
<DashboardWidgets widgetMetaDataList={[widgetMetaData]} omitWrappingGridContainer={true} childUrlParams={queryStringParts.join("&")} initialWidgetDataList={initialWidgetDataList} values={processValues} actionCallback={blockWidgetActionCallback} /> <DashboardWidgets widgetMetaDataList={[widgetMetaData]} omitWrappingGridContainer={true} childUrlParams={queryStringParts.join("&")} initialWidgetDataList={initialWidgetDataList} values={processValues} actionCallback={actionCallback} />
</Box>); </Box>);
renderedWidgets[activeStep.name][widgetName] = renderedWidget; renderedWidgets[activeStep.name][widgetName] = renderedWidget;
return renderedWidget; return renderedWidget;
@ -374,46 +388,57 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is
{ {
const split = controlCode.split(":", 2); const split = controlCode.split(":", 2);
let controlCallbackName: string; let controlCallbackName: string;
let controlCallbackValue: any let controlCallbackValue: any;
if(split.length == 2) if (split.length == 2)
{ {
if(split[0] == "showModal") if (split[0] == "showModal")
{ {
processValues[split[1]] = true processValues[split[1]] = true;
controlCallbackName = split[1] controlCallbackName = split[1];
controlCallbackValue = true controlCallbackValue = true;
} }
else if(split[0] == "hideModal") else if (split[0] == "hideModal")
{ {
processValues[split[1]] = false processValues[split[1]] = false;
controlCallbackName = split[1] controlCallbackName = split[1];
controlCallbackValue = false controlCallbackValue = false;
} }
else if(split[0] == "toggleModal") else if (split[0] == "toggleModal")
{ {
const currentValue = processValues[split[1]] const currentValue = processValues[split[1]];
processValues[split[1]] = !!!currentValue; processValues[split[1]] = !!!currentValue;
controlCallbackName = split[1] controlCallbackName = split[1];
controlCallbackValue = processValues[split[1]] controlCallbackValue = processValues[split[1]];
} }
else else
{ {
console.log(`Unexpected part[0] (before colon) in controlCode: [${controlCode}]`) console.log(`Unexpected part[0] (before colon) in controlCode: [${controlCode}]`);
} }
} }
else else
{ {
console.log(`Expected controlCode to have 2 colon-delimited parts, but was: [${controlCode}]`) console.log(`Expected controlCode to have 2 colon-delimited parts, but was: [${controlCode}]`);
} }
if(controlCallbackName && controlCallbacks[controlCallbackName]) if (controlCallbackName && controlCallbacks[controlCallbackName])
{ {
// @ts-ignore ... args are hard // @ts-ignore ... args are hard
controlCallbacks[controlCallbackName](controlCallbackValue) controlCallbacks[controlCallbackName](controlCallbackValue);
} }
} }
/***************************************************************************
** callback used by child list widget
***************************************************************************/
function childRecordListWidgetActionCallBack(data: any): boolean
{
console.log(`in childRecordListWidgetActionCallBack: ${JSON.stringify(data)}`);
setChildRecordData(data as ChildRecordListData);
return (true);
}
/*************************************************************************** /***************************************************************************
** callback used by widget blocks, e.g., for input-text-enter-on-submit, ** callback used by widget blocks, e.g., for input-text-enter-on-submit,
** and action buttons. ** and action buttons.
@ -422,11 +447,11 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is
{ {
console.log(`in blockWidgetActionCallback, called by block: ${JSON.stringify(blockData)}`); console.log(`in blockWidgetActionCallback, called by block: ${JSON.stringify(blockData)}`);
if(eventValues?.registerControlCallbackName && eventValues?.registerControlCallbackFunction) if (eventValues?.registerControlCallbackName && eventValues?.registerControlCallbackFunction)
{ {
controlCallbacks[eventValues.registerControlCallbackName] = eventValues.registerControlCallbackFunction; controlCallbacks[eventValues.registerControlCallbackName] = eventValues.registerControlCallbackFunction;
setControlCallbacks(controlCallbacks) setControlCallbacks(controlCallbacks);
return (true) return (true);
} }
//////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////
@ -449,29 +474,29 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is
// } // }
let doSubmit = false; let doSubmit = false;
if(blockData?.blockTypeName == "BUTTON" && eventValues?.actionCode) if (blockData?.blockTypeName == "BUTTON" && eventValues?.actionCode)
{ {
doSubmit = true doSubmit = true;
} }
else if(blockData?.blockTypeName == "BUTTON" && eventValues?.controlCode) else if (blockData?.blockTypeName == "BUTTON" && eventValues?.controlCode)
{ {
handleControlCode(eventValues.controlCode); handleControlCode(eventValues.controlCode);
doSubmit = false doSubmit = false;
} }
else if(blockData?.blockTypeName == "INPUT_FIELD") else if (blockData?.blockTypeName == "INPUT_FIELD")
{ {
/////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////////
// if action callback was fired from an input field, assume that means we're good to submit. // // if action callback was fired from an input field, assume that means we're good to submit. //
/////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////////
doSubmit = true doSubmit = true;
} }
////////////////// //////////////////
// ok - submit! // // ok - submit! //
////////////////// //////////////////
if(doSubmit) if (doSubmit)
{ {
handleSubmit(eventValues); handleFormSubmit(eventValues);
return (true); return (true);
} }
} }
@ -658,7 +683,7 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is
let helpRoles = ["PROCESS_SCREEN", "ALL_SCREENS"]; let helpRoles = ["PROCESS_SCREEN", "ALL_SCREENS"];
const showHelp = helpHelpActive || hasHelpContent(step.helpContents, helpRoles); const showHelp = helpHelpActive || hasHelpContent(step.helpContents, helpRoles);
const formattedHelpContent = <HelpContent helpContents={step.helpContents} roles={helpRoles} helpContentKey={`process:${processName};step:${step?.name}`} />; const formattedHelpContent = <HelpContent helpContents={step.helpContents} roles={helpRoles} helpContentKey={`process:${processName};step:${step?.name}`} />;
const isFormatScanner = step?.format?.toLowerCase() == "scanner" const isFormatScanner = step?.format?.toLowerCase() == "scanner";
return ( return (
<> <>
@ -965,7 +990,7 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is
// if neither of those, then programmer error // // if neither of those, then programmer error //
//////////////////////////////////////////////// ////////////////////////////////////////////////
!(component.values?.widgetName || component.values?.isAdHocWidget) && !(component.values?.widgetName || component.values?.isAdHocWidget) &&
<Alert severity="error">Error: Component is marked as WIDGET type, but does not specify a <u>widgetName</u>, nor the <u>isAdHocWidget</u> flag.</Alert> <Alert severity="error">Error: Component is marked as WIDGET type, but does not specify a <u>widgetName</u>, nor the <u>isAdHocWidget</u> flag.</Alert>
} }
</> </>
) )
@ -1114,7 +1139,7 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is
// if this is the last step or not - and by default that radio will be true, to make this // // if this is the last step or not - and by default that radio will be true, to make this //
// NOT the last step - so set this value. // // NOT the last step - so set this value. //
////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////
if(!processValues["validationSummary"] && processValues["supportsFullValidation"]) if (!processValues["validationSummary"] && processValues["supportsFullValidation"])
{ {
setOverrideOnLastStep(false); setOverrideOnLastStep(false);
} }
@ -1133,7 +1158,7 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is
{ {
const dynamicField = DynamicFormUtils.getDynamicField(fieldMetaData); const dynamicField = DynamicFormUtils.getDynamicField(fieldMetaData);
const validation = DynamicFormUtils.getValidationForField(fieldMetaData); const validation = DynamicFormUtils.getValidationForField(fieldMetaData);
addField(fieldMetaData.name, dynamicField, processValues[fieldMetaData.name], validation) addField(fieldMetaData.name, dynamicField, processValues[fieldMetaData.name], validation);
}); });
} }
@ -1295,6 +1320,11 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is
const {records} = response; const {records} = response;
setRecords(records); setRecords(records);
if (!childRecordData || childRecordData.length == 0)
{
setChildRecordData(convertRecordsToChildRecordData(records));
}
///////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////
// re-construct the recordConfig object, so the setState call triggers a new rendering // // re-construct the recordConfig object, so the setState call triggers a new rendering //
///////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////
@ -1317,6 +1347,30 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is
}, [needRecords]); }, [needRecords]);
/***************************************************************************
**
***************************************************************************/
function convertRecordsToChildRecordData(records: QRecord[])
{
const frontendRecords = [] as any[];
records.forEach((record: QRecord) =>
{
const object = {
"tableName": record.tableName,
"recordLabel": record.recordLabel,
"errors": record.errors,
"warnings": record.warnings,
"values": Object.fromEntries(record.values),
"displayValues": Object.fromEntries(record.displayValues),
};
frontendRecords.push(object);
});
const newChildListData = {} as ChildRecordListData;
newChildListData.queryOutput = {records: frontendRecords};
return (newChildListData);
}
/*************************************************************************** /***************************************************************************
** **
***************************************************************************/ ***************************************************************************/
@ -1690,11 +1744,35 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is
setNewStep(activeStepIndex - 1); setNewStep(activeStepIndex - 1);
}; };
////////////////////////////////////////////
// handle user submitting changed records //
////////////////////////////////////////////
const doSubmit = async (formData: FormData) =>
{
const formDataHeaders = {
"content-type": "multipart/form-data; boundary=--------------------------320289315924586491558366",
};
setTimeout(async () =>
{
recordAnalytics({category: "processEvents", action: "processStep", label: activeStep.label});
const processResponse = await Client.getInstance().processStep(
processName,
processUUID,
activeStep.name,
formData,
formDataHeaders
);
setLastProcessResponse(processResponse);
});
};
////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////
// handle user submitting the form - which in qqq means moving forward from any screen. // // handle user submitting the form - which in qqq means moving forward from any screen. //
// caller can pass in a map of values to be added to the form data too // // caller can pass in a map of values to be added to the form data too //
////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////
const handleSubmit = async (values: any) => const handleFormSubmit = async (values: any) =>
{ {
setFormError(null); setFormError(null);
@ -1733,28 +1811,20 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is
formData.append("bulkEditEnabledFields", bulkEditEnabledFields.join(",")); formData.append("bulkEditEnabledFields", bulkEditEnabledFields.join(","));
} }
const formDataHeaders = { /////////////////////////////////////////////////////////////
"content-type": "multipart/form-data; boundary=--------------------------320289315924586491558366", // convert to regular objects so that they can be jsonized //
}; /////////////////////////////////////////////////////////////
if (childRecordData)
{
formData.append("frontendRecords", JSON.stringify(childRecordData.queryOutput.records));
}
setProcessValues({}); setProcessValues({});
setRecords([]); setRecords([]);
setOverrideOnLastStep(null); setOverrideOnLastStep(null);
setLastProcessResponse(new QJobRunning({message: "Working..."})); setLastProcessResponse(new QJobRunning({message: "Working..."}));
setTimeout(async () => doSubmit(formData);
{
recordAnalytics({category: "processEvents", action: "processStep", label: activeStep.label});
const processResponse = await Client.getInstance().processStep(
processName,
processUUID,
activeStep.name,
formData,
formDataHeaders,
);
setLastProcessResponse(processResponse);
});
}; };
@ -1785,7 +1855,7 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is
const formStyles: any = {}; const formStyles: any = {};
if(isWidget) if (isWidget)
{ {
formStyles.display = "flex"; formStyles.display = "flex";
formStyles.flexGrow = 1; formStyles.flexGrow = 1;
@ -1799,7 +1869,7 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is
{ {
const mainCardStyles: any = {}; const mainCardStyles: any = {};
if(!isWidget && !isModal) if (!isWidget && !isModal)
{ {
//////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////
// remove margin around card for non-widget, non-modal, small // // remove margin around card for non-widget, non-modal, small //
@ -1829,7 +1899,7 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is
mainCardStyles.display = "flex"; mainCardStyles.display = "flex";
} }
return mainCardStyles return mainCardStyles;
} }
let nextButtonLabel = "Next"; let nextButtonLabel = "Next";
@ -1854,7 +1924,7 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is
initialValues={initialValues} initialValues={initialValues}
validationSchema={validationScheme} validationSchema={validationScheme}
validation={validationFunction} validation={validationFunction}
onSubmit={handleSubmit} onSubmit={handleFormSubmit}
> >
{({ {({
values, errors, touched, isSubmitting, setFieldValue, setTouched values, errors, touched, isSubmitting, setFieldValue, setTouched