mirror of
https://github.com/Kingsrook/qqq-frontend-material-dashboard.git
synced 2025-07-17 12:50:43 +00:00
CE-1068 - Add dynamic form widget; add widgets on processes
This commit is contained in:
@ -6,7 +6,7 @@
|
|||||||
"@auth0/auth0-react": "1.10.2",
|
"@auth0/auth0-react": "1.10.2",
|
||||||
"@emotion/react": "11.7.1",
|
"@emotion/react": "11.7.1",
|
||||||
"@emotion/styled": "11.6.0",
|
"@emotion/styled": "11.6.0",
|
||||||
"@kingsrook/qqq-frontend-core": "1.0.96",
|
"@kingsrook/qqq-frontend-core": "1.0.99",
|
||||||
"@mui/icons-material": "5.4.1",
|
"@mui/icons-material": "5.4.1",
|
||||||
"@mui/material": "5.11.1",
|
"@mui/material": "5.11.1",
|
||||||
"@mui/styles": "5.11.1",
|
"@mui/styles": "5.11.1",
|
||||||
|
@ -175,7 +175,7 @@ class DynamicFormUtils
|
|||||||
initialDisplayValue: initialDisplayValue,
|
initialDisplayValue: initialDisplayValue,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
else
|
else if(processName)
|
||||||
{
|
{
|
||||||
dynamicFormFields[field.name].possibleValueProps =
|
dynamicFormFields[field.name].possibleValueProps =
|
||||||
{
|
{
|
||||||
@ -184,6 +184,14 @@ class DynamicFormUtils
|
|||||||
initialDisplayValue: initialDisplayValue,
|
initialDisplayValue: initialDisplayValue,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
dynamicFormFields[field.name].possibleValueProps =
|
||||||
|
{
|
||||||
|
isPossibleValue: true,
|
||||||
|
initialDisplayValue: initialDisplayValue,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -43,6 +43,7 @@ import DynamicFormUtils from "qqq/components/forms/DynamicFormUtils";
|
|||||||
import MDTypography from "qqq/components/legacy/MDTypography";
|
import MDTypography from "qqq/components/legacy/MDTypography";
|
||||||
import HelpContent from "qqq/components/misc/HelpContent";
|
import HelpContent from "qqq/components/misc/HelpContent";
|
||||||
import QRecordSidebar from "qqq/components/misc/RecordSidebar";
|
import QRecordSidebar from "qqq/components/misc/RecordSidebar";
|
||||||
|
import DynamicFormWidget from "qqq/components/widgets/misc/DynamicFormWidget";
|
||||||
import PivotTableSetupWidget from "qqq/components/widgets/misc/PivotTableSetupWidget";
|
import PivotTableSetupWidget from "qqq/components/widgets/misc/PivotTableSetupWidget";
|
||||||
import RecordGridWidget, {ChildRecordListData} from "qqq/components/widgets/misc/RecordGridWidget";
|
import RecordGridWidget, {ChildRecordListData} from "qqq/components/widgets/misc/RecordGridWidget";
|
||||||
import ReportSetupWidget from "qqq/components/widgets/misc/ReportSetupWidget";
|
import ReportSetupWidget from "qqq/components/widgets/misc/ReportSetupWidget";
|
||||||
@ -409,6 +410,19 @@ function EntityForm(props: Props): JSX.Element
|
|||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(widgetMetaData.type == "dynamicForm")
|
||||||
|
{
|
||||||
|
return <DynamicFormWidget
|
||||||
|
key={formValues["savedReportId"]} // todo - pull this from the metaData (could do so above too...)
|
||||||
|
isEditable={true}
|
||||||
|
widgetMetaData={widgetMetaData}
|
||||||
|
widgetData={widgetData}
|
||||||
|
recordValues={formValues}
|
||||||
|
record={record}
|
||||||
|
onSaveCallback={setFormFieldValuesFromWidget}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
|
||||||
return (<Box>Unsupported widget type: {widgetMetaData.type}</Box>)
|
return (<Box>Unsupported widget type: {widgetMetaData.type}</Box>)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -482,7 +496,7 @@ function EntityForm(props: Props): JSX.Element
|
|||||||
return (true);
|
return (true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(widget.type == "reportSetup" || widget.type == "pivotTableSetup")
|
if(widget.type == "reportSetup" || widget.type == "pivotTableSetup" || widget.type == "dynamicForm")
|
||||||
{
|
{
|
||||||
return (true);
|
return (true);
|
||||||
}
|
}
|
||||||
@ -706,7 +720,8 @@ function EntityForm(props: Props): JSX.Element
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
const widgetMetaData = metaData.widgets.get(section.widgetName);
|
const widgetMetaData = metaData.widgets.get(section.widgetName);
|
||||||
const widgetData = await qController.widget(widgetMetaData.name, props.id ? `${tableMetaData.primaryKeyField}=${props.id}` : "");
|
const widgetData = await qController.widget(widgetMetaData.name, makeQueryStringWithIdAndObject(tableMetaData, defaultValues));
|
||||||
|
|
||||||
newRenderedWidgetSections[section.widgetName] = getWidgetSection(widgetMetaData, widgetData);
|
newRenderedWidgetSections[section.widgetName] = getWidgetSection(widgetMetaData, widgetData);
|
||||||
newChildListWidgetData[section.widgetName] = widgetData;
|
newChildListWidgetData[section.widgetName] = widgetData;
|
||||||
}
|
}
|
||||||
@ -966,6 +981,51 @@ function EntityForm(props: Props): JSX.Element
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
function makeQueryStringWithIdAndObject(tableMetaData: QTableMetaData, object: {[key: string]: any})
|
||||||
|
{
|
||||||
|
const queryParamsArray: string[] = [];
|
||||||
|
if(props.id)
|
||||||
|
{
|
||||||
|
queryParamsArray.push(`${tableMetaData.primaryKeyField}=${encodeURIComponent(props.id)}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if(object)
|
||||||
|
{
|
||||||
|
for (let key in object)
|
||||||
|
{
|
||||||
|
queryParamsArray.push(`${key}=${encodeURIComponent(object[key])}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (queryParamsArray.join("&"));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
async function reloadWidget(widgetName: string, additionalQueryParamsForWidget: {[key: string]: any })
|
||||||
|
{
|
||||||
|
const widgetData = await qController.widget(widgetName, makeQueryStringWithIdAndObject(tableMetaData, additionalQueryParamsForWidget));
|
||||||
|
const widgetMetaData = metaData.widgets.get(widgetName);
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// todo - rename this - it holds all widget dta, not just child-lists. also, the type is wrong... //
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
const newChildListWidgetData: { [name: string]: ChildRecordListData } = Object.assign({}, childListWidgetData);
|
||||||
|
newChildListWidgetData[widgetName] = widgetData;
|
||||||
|
setChildListWidgetData(newChildListWidgetData);
|
||||||
|
|
||||||
|
const newRenderedWidgetSections = Object.assign({}, renderedWidgetSections);
|
||||||
|
newRenderedWidgetSections[widgetName] = getWidgetSection(widgetMetaData, widgetData);
|
||||||
|
setRenderedWidgetSections(newRenderedWidgetSections);
|
||||||
|
forceUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** process a form-field having a changed value (e.g., apply field rules).
|
** process a form-field having a changed value (e.g., apply field rules).
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@ -981,6 +1041,10 @@ function EntityForm(props: Props): JSX.Element
|
|||||||
console.log(`Clearing value from [${fieldRule.targetField}] due to change in [${fieldName}]`);
|
console.log(`Clearing value from [${fieldRule.targetField}] due to change in [${fieldName}]`);
|
||||||
valueChangesToMake[fieldRule.targetField] = null;
|
valueChangesToMake[fieldRule.targetField] = null;
|
||||||
break;
|
break;
|
||||||
|
case FieldRuleAction.RELOAD_WIDGET:
|
||||||
|
const additionalQueryParamsForWidget: {[key: string]: any} = {};
|
||||||
|
additionalQueryParamsForWidget[fieldRule.sourceField] = newValue;
|
||||||
|
reloadWidget(fieldRule.targetWidget, additionalQueryParamsForWidget);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -38,6 +38,7 @@ import StackedBarChart from "qqq/components/widgets/charts/StackedBarChart";
|
|||||||
import CompositeWidget from "qqq/components/widgets/CompositeWidget";
|
import CompositeWidget from "qqq/components/widgets/CompositeWidget";
|
||||||
import DataBagViewer from "qqq/components/widgets/misc/DataBagViewer";
|
import DataBagViewer from "qqq/components/widgets/misc/DataBagViewer";
|
||||||
import DividerWidget from "qqq/components/widgets/misc/Divider";
|
import DividerWidget from "qqq/components/widgets/misc/Divider";
|
||||||
|
import DynamicFormWidget from "qqq/components/widgets/misc/DynamicFormWidget";
|
||||||
import FieldValueListWidget from "qqq/components/widgets/misc/FieldValueListWidget";
|
import FieldValueListWidget from "qqq/components/widgets/misc/FieldValueListWidget";
|
||||||
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";
|
||||||
@ -261,7 +262,7 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, reco
|
|||||||
{
|
{
|
||||||
const rs: {[name: string]: any} = {};
|
const rs: {[name: string]: any} = {};
|
||||||
|
|
||||||
if(record.values)
|
if(record && record.values)
|
||||||
{
|
{
|
||||||
record.values.forEach((value, key) => rs[key] = value);
|
record.values.forEach((value, key) => rs[key] = value);
|
||||||
}
|
}
|
||||||
@ -596,6 +597,12 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, reco
|
|||||||
{}} />
|
{}} />
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
widgetMetaData.type === "dynamicForm" && (
|
||||||
|
widgetData && widgetData[i] &&
|
||||||
|
<DynamicFormWidget isEditable={false} widgetMetaData={widgetMetaData} widgetData={widgetData[i]} record={record} recordValues={convertQRecordValuesFromMapToObject(record)} />
|
||||||
|
)
|
||||||
|
}
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -21,8 +21,7 @@
|
|||||||
|
|
||||||
import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData";
|
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 {InputLabel} from "@mui/material";
|
import {Box, InputLabel} from "@mui/material";
|
||||||
import Box from "@mui/material/Box";
|
|
||||||
import Button from "@mui/material/Button";
|
import Button from "@mui/material/Button";
|
||||||
import Card from "@mui/material/Card";
|
import Card from "@mui/material/Card";
|
||||||
import Icon from "@mui/material/Icon";
|
import Icon from "@mui/material/Icon";
|
||||||
|
264
src/qqq/components/widgets/misc/DynamicFormWidget.tsx
Normal file
264
src/qqq/components/widgets/misc/DynamicFormWidget.tsx
Normal file
@ -0,0 +1,264 @@
|
|||||||
|
/*
|
||||||
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
|
* Copyright (C) 2021-2024. 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 {QFieldMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldMetaData";
|
||||||
|
import {QWidgetMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QWidgetMetaData";
|
||||||
|
import {QRecord} from "@kingsrook/qqq-frontend-core/lib/model/QRecord";
|
||||||
|
import Box from "@mui/material/Box";
|
||||||
|
import {FormikContextType, useFormikContext} from "formik";
|
||||||
|
import QDynamicForm from "qqq/components/forms/DynamicForm";
|
||||||
|
import DynamicFormUtils from "qqq/components/forms/DynamicFormUtils";
|
||||||
|
import Widget from "qqq/components/widgets/Widget";
|
||||||
|
import {renderSectionOfFields} from "qqq/pages/records/view/RecordView";
|
||||||
|
import Client from "qqq/utils/qqq/Client";
|
||||||
|
import React, {useEffect, useState} from "react";
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** component props
|
||||||
|
*******************************************************************************/
|
||||||
|
interface DynamicFormWidgetProps
|
||||||
|
{
|
||||||
|
isEditable: boolean;
|
||||||
|
widgetMetaData: QWidgetMetaData;
|
||||||
|
widgetData: any;
|
||||||
|
record: QRecord;
|
||||||
|
recordValues: { [name: string]: any };
|
||||||
|
onSaveCallback?: (values: { [name: string]: any }) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** default values for props
|
||||||
|
*******************************************************************************/
|
||||||
|
DynamicFormWidget.defaultProps = {
|
||||||
|
onSaveCallback: null
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Component to display a dynamic form - e.g., on a record edit or view screen,
|
||||||
|
** or even within a process.
|
||||||
|
*******************************************************************************/
|
||||||
|
export default function DynamicFormWidget({isEditable, widgetMetaData, widgetData, record, recordValues, onSaveCallback}: DynamicFormWidgetProps): JSX.Element
|
||||||
|
{
|
||||||
|
const [fields, setFields] = useState([] as QFieldMetaData[]);
|
||||||
|
|
||||||
|
const [effectiveIsEditable, setEffectiveIsEditable] = useState(isEditable);
|
||||||
|
if(widgetMetaData.defaultValues.has("isEditable"))
|
||||||
|
{
|
||||||
|
const defaultIsEditableValue = widgetMetaData.defaultValues.get("isEditable")
|
||||||
|
if(defaultIsEditableValue != effectiveIsEditable)
|
||||||
|
{
|
||||||
|
setEffectiveIsEditable(defaultIsEditableValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const [dynamicFormFields, setDynamicFormFields] = useState(null as any);
|
||||||
|
const [formValidations, setFormValidations] = useState(null as any);
|
||||||
|
|
||||||
|
const [lastKnowFormValues, setLastKnowFormValues] = useState({} as {[name: string]: any});
|
||||||
|
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// on initial load, and any time widgetData changes (e.g., if widget gets re-rendered), //
|
||||||
|
// figure out what our form fields are //
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
useEffect(() =>
|
||||||
|
{
|
||||||
|
setDynamicFormFields({})
|
||||||
|
setFormValidations({})
|
||||||
|
|
||||||
|
if(widgetData && widgetData.fieldList)
|
||||||
|
{
|
||||||
|
const newFields: QFieldMetaData[] = [];
|
||||||
|
for (let i = 0; i < widgetData.fieldList.length; i++)
|
||||||
|
{
|
||||||
|
newFields.push(new QFieldMetaData(widgetData.fieldList[i]));
|
||||||
|
}
|
||||||
|
setFields(newFields);
|
||||||
|
|
||||||
|
if(newFields.length > 0)
|
||||||
|
{
|
||||||
|
const {dynamicFormFields: newDynamicFormFields, formValidations: newFormValidations} = DynamicFormUtils.getFormData(newFields);
|
||||||
|
const defaultDisplayValues = new Map<string,string>(); // todo - seems not right?
|
||||||
|
DynamicFormUtils.addPossibleValueProps(newDynamicFormFields, newFields, recordValues.tableName, null, record ? record.displayValues : defaultDisplayValues);
|
||||||
|
setDynamicFormFields(newDynamicFormFields)
|
||||||
|
setFormValidations(newFormValidations)
|
||||||
|
}
|
||||||
|
|
||||||
|
setLastKnowFormValues({});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
setFields([])
|
||||||
|
}
|
||||||
|
}, [widgetData]);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
function checkForFormValueChanges(formikProps: FormikContextType<any>)
|
||||||
|
{
|
||||||
|
if(!fields || !fields.length)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let anyChanged = false;
|
||||||
|
for (let i = 0; i < fields.length; i++)
|
||||||
|
{
|
||||||
|
const name = fields[i].name;
|
||||||
|
if(formikProps.values[name] != lastKnowFormValues[name])
|
||||||
|
{
|
||||||
|
anyChanged = true;
|
||||||
|
lastKnowFormValues[name] = formikProps.values[name];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(anyChanged)
|
||||||
|
{
|
||||||
|
const mergedDynamicFormValuesIntoFieldName = widgetData.mergedDynamicFormValuesIntoFieldName;
|
||||||
|
if(mergedDynamicFormValuesIntoFieldName && onSaveCallback)
|
||||||
|
{
|
||||||
|
const onSaveCallbackParam: {[name: string]: any} = {};
|
||||||
|
onSaveCallbackParam[mergedDynamicFormValuesIntoFieldName] = JSON.stringify(lastKnowFormValues);
|
||||||
|
onSaveCallback(onSaveCallbackParam);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
function getInitialValue(fieldName: string)
|
||||||
|
{
|
||||||
|
for (let i = 0; i < fields?.length; i++)
|
||||||
|
{
|
||||||
|
if(fields[i].name == fieldName && fields[i].defaultValue)
|
||||||
|
{
|
||||||
|
return (fields[i].defaultValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (null);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
function renderEditForm()
|
||||||
|
{
|
||||||
|
const formikProps = useFormikContext();
|
||||||
|
if(!fields || !fields.length)
|
||||||
|
{
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Box fontSize="1rem">{widgetData && widgetData.noFieldsMessage}</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const formData: any = {};
|
||||||
|
formData.values = formikProps.values;
|
||||||
|
formData.touched = formikProps.touched;
|
||||||
|
formData.errors = formikProps.errors;
|
||||||
|
formData.formFields = {};
|
||||||
|
|
||||||
|
// todo - merge the formValidations object with formik's - maybe in the useEffect where we build it
|
||||||
|
// setValidations(Yup.object().shape(formValidations));
|
||||||
|
// formikProps.validationSchema.
|
||||||
|
|
||||||
|
for (let key of Object.keys(dynamicFormFields))
|
||||||
|
{
|
||||||
|
const dynamicFormField = dynamicFormFields[key];
|
||||||
|
formData.formFields[dynamicFormField.name] = dynamicFormField;
|
||||||
|
|
||||||
|
const initialValue = getInitialValue(dynamicFormField.name);
|
||||||
|
if(initialValue != null)
|
||||||
|
{
|
||||||
|
console.log(`@dk trying to set an initial value [${dynamicFormField.name}] to [${initialValue}]`);
|
||||||
|
// @ts-ignore some any
|
||||||
|
formikProps.initialValues[dynamicFormField.name] = initialValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(formData.values)
|
||||||
|
{
|
||||||
|
checkForFormValueChanges(formikProps);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<QDynamicForm formData={formData} record={record} />
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
function renderViewForm()
|
||||||
|
{
|
||||||
|
const fieldNames: string[] = [];
|
||||||
|
const fieldMap: {[name: string]: QFieldMetaData} = {};
|
||||||
|
const fakeRecord = new QRecord({});
|
||||||
|
|
||||||
|
const mergedDynamicFormValuesIntoFieldName = widgetData.mergedDynamicFormValuesIntoFieldName;
|
||||||
|
|
||||||
|
for (let i = 0; i < fields?.length; i++)
|
||||||
|
{
|
||||||
|
const fieldName = fields[i].name;
|
||||||
|
fieldNames.push(fieldName);
|
||||||
|
fieldMap[fieldName] = fields[i];
|
||||||
|
|
||||||
|
if(mergedDynamicFormValuesIntoFieldName && recordValues[mergedDynamicFormValuesIntoFieldName])
|
||||||
|
{
|
||||||
|
fakeRecord.values.set(fieldName, recordValues[mergedDynamicFormValuesIntoFieldName][fieldName]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const section = renderSectionOfFields(`dynamicFormWidget:${widgetMetaData.name}`, fieldNames, null, false, fakeRecord, fieldMap);
|
||||||
|
|
||||||
|
return (<Box>
|
||||||
|
{section}
|
||||||
|
</Box>);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
////////////
|
||||||
|
// render //
|
||||||
|
////////////
|
||||||
|
return (<Widget widgetMetaData={widgetMetaData}>
|
||||||
|
{
|
||||||
|
<React.Fragment>
|
||||||
|
{effectiveIsEditable ? renderEditForm() : renderViewForm()}
|
||||||
|
</React.Fragment>
|
||||||
|
}
|
||||||
|
</Widget>);
|
||||||
|
}
|
||||||
|
|
@ -185,7 +185,7 @@ function RecordGridWidget({widgetMetaData, data, addNewRecordCallback, disableRo
|
|||||||
if(data && data.viewAllLink)
|
if(data && data.viewAllLink)
|
||||||
{
|
{
|
||||||
labelAdditionalElementsLeft.push(
|
labelAdditionalElementsLeft.push(
|
||||||
<Typography variant="body2" p={2} display="inline" fontSize=".875rem" pt="0" position="relative">
|
<Typography key={"viewAllLink"} variant="body2" p={2} display="inline" fontSize=".875rem" pt="0" position="relative">
|
||||||
<Link to={data.viewAllLink}>View All</Link>
|
<Link to={data.viewAllLink}>View All</Link>
|
||||||
</Typography>
|
</Typography>
|
||||||
)
|
)
|
||||||
@ -225,8 +225,8 @@ function RecordGridWidget({widgetMetaData, data, addNewRecordCallback, disableRo
|
|||||||
if(widgetMetaData?.showExportButton)
|
if(widgetMetaData?.showExportButton)
|
||||||
{
|
{
|
||||||
labelAdditionalElementsLeft.push(
|
labelAdditionalElementsLeft.push(
|
||||||
<Typography key={1} variant="body2" px={0} display="inline" position="relative">
|
<Typography key={"exportButton"} variant="body2" px={0} display="inline" position="relative">
|
||||||
<Tooltip title={tooltipTitle}><Button sx={{px: 1, py: 0, minWidth: "initial"}} onClick={onExportClick} disabled={isExportDisabled}><Icon sx={{color: "#757575", fontSize: 1.25}}>save_alt</Icon></Button></Tooltip>
|
<Tooltip title={tooltipTitle}><span><Button sx={{px: 1, py: 0, minWidth: "initial"}} onClick={onExportClick} disabled={isExportDisabled}><Icon sx={{color: "#757575", fontSize: 1.25}}>save_alt</Icon></Button></span></Tooltip>
|
||||||
</Typography>
|
</Typography>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -305,48 +305,50 @@ function RecordGridWidget({widgetMetaData, data, addNewRecordCallback, disableRo
|
|||||||
labelBoxAdditionalSx={{position: "relative", top: "-0.375rem"}}
|
labelBoxAdditionalSx={{position: "relative", top: "-0.375rem"}}
|
||||||
>
|
>
|
||||||
<Box mx={-2} mb={-3}>
|
<Box mx={-2} mb={-3}>
|
||||||
<DataGridPro
|
<Box className="recordGridWidget">
|
||||||
autoHeight
|
<DataGridPro
|
||||||
sx={{
|
autoHeight
|
||||||
borderBottom: "none",
|
sx={{
|
||||||
borderLeft: "none",
|
borderBottom: "none",
|
||||||
borderRight: "none"
|
borderLeft: "none",
|
||||||
}}
|
borderRight: "none"
|
||||||
rows={rows}
|
}}
|
||||||
disableSelectionOnClick
|
rows={rows}
|
||||||
columns={columns}
|
disableSelectionOnClick
|
||||||
rowBuffer={10}
|
columns={columns}
|
||||||
getRowClassName={(params) => (params.indexRelativeToCurrentPage % 2 === 0 ? "even" : "odd")}
|
rowBuffer={10}
|
||||||
onRowClick={handleRowClick}
|
getRowClassName={(params) => (params.indexRelativeToCurrentPage % 2 === 0 ? "even" : "odd")}
|
||||||
getRowId={(row) => row.__rowIndex}
|
onRowClick={handleRowClick}
|
||||||
// getRowHeight={() => "auto"} // maybe nice? wraps values in cells...
|
getRowId={(row) => row.__rowIndex}
|
||||||
components={{
|
// getRowHeight={() => "auto"} // maybe nice? wraps values in cells...
|
||||||
Toolbar: CustomToolbar
|
components={{
|
||||||
}}
|
Toolbar: CustomToolbar
|
||||||
// pinnedColumns={pinnedColumns}
|
}}
|
||||||
// onPinnedColumnsChange={handlePinnedColumnsChange}
|
// pinnedColumns={pinnedColumns}
|
||||||
// pagination
|
// onPinnedColumnsChange={handlePinnedColumnsChange}
|
||||||
// paginationMode="server"
|
// pagination
|
||||||
// rowsPerPageOptions={[20]}
|
// paginationMode="server"
|
||||||
// sortingMode="server"
|
// rowsPerPageOptions={[20]}
|
||||||
// filterMode="server"
|
// sortingMode="server"
|
||||||
// page={pageNumber}
|
// filterMode="server"
|
||||||
// checkboxSelection
|
// page={pageNumber}
|
||||||
rowCount={data && data.totalRows}
|
// checkboxSelection
|
||||||
// onPageSizeChange={handleRowsPerPageChange}
|
rowCount={data && data.totalRows}
|
||||||
// onStateChange={handleStateChange}
|
// onPageSizeChange={handleRowsPerPageChange}
|
||||||
// density={density}
|
// onStateChange={handleStateChange}
|
||||||
// loading={loading}
|
// density={density}
|
||||||
// filterModel={filterModel}
|
// loading={loading}
|
||||||
// onFilterModelChange={handleFilterChange}
|
// filterModel={filterModel}
|
||||||
// columnVisibilityModel={columnVisibilityModel}
|
// onFilterModelChange={handleFilterChange}
|
||||||
// onColumnVisibilityModelChange={handleColumnVisibilityChange}
|
// columnVisibilityModel={columnVisibilityModel}
|
||||||
// onColumnOrderChange={handleColumnOrderChange}
|
// onColumnVisibilityModelChange={handleColumnVisibilityChange}
|
||||||
// onSelectionModelChange={selectionChanged}
|
// onColumnOrderChange={handleColumnOrderChange}
|
||||||
// onSortModelChange={handleSortChange}
|
// onSelectionModelChange={selectionChanged}
|
||||||
// sortingOrder={[ "asc", "desc" ]}
|
// onSortModelChange={handleSortChange}
|
||||||
// sortModel={columnSortModel}
|
// sortingOrder={[ "asc", "desc" ]}
|
||||||
/>
|
// sortModel={columnSortModel}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
</Widget>
|
</Widget>
|
||||||
);
|
);
|
||||||
|
@ -58,6 +58,7 @@ import QRecordSidebar from "qqq/components/misc/RecordSidebar";
|
|||||||
import {GoogleDriveFolderPickerWrapper} from "qqq/components/processes/GoogleDriveFolderPickerWrapper";
|
import {GoogleDriveFolderPickerWrapper} from "qqq/components/processes/GoogleDriveFolderPickerWrapper";
|
||||||
import ProcessSummaryResults from "qqq/components/processes/ProcessSummaryResults";
|
import ProcessSummaryResults from "qqq/components/processes/ProcessSummaryResults";
|
||||||
import ValidationReview from "qqq/components/processes/ValidationReview";
|
import ValidationReview from "qqq/components/processes/ValidationReview";
|
||||||
|
import DashboardWidgets from "qqq/components/widgets/DashboardWidgets";
|
||||||
import BaseLayout from "qqq/layouts/BaseLayout";
|
import BaseLayout from "qqq/layouts/BaseLayout";
|
||||||
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";
|
||||||
import Client from "qqq/utils/qqq/Client";
|
import Client from "qqq/utils/qqq/Client";
|
||||||
@ -124,6 +125,8 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is
|
|||||||
const [showErrorDetail, setShowErrorDetail] = useState(false);
|
const [showErrorDetail, setShowErrorDetail] = useState(false);
|
||||||
const [showFullHelpText, setShowFullHelpText] = useState(false);
|
const [showFullHelpText, setShowFullHelpText] = useState(false);
|
||||||
|
|
||||||
|
const [renderedWidgets, setRenderedWidgets] = useState({} as {[step: string]: {[widgetName: string]: any}});
|
||||||
|
|
||||||
const {pageHeader, recordAnalytics, setPageHeader} = useContext(QContext);
|
const {pageHeader, recordAnalytics, setPageHeader} = useContext(QContext);
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
@ -273,6 +276,42 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
function renderWidget(widgetName: string)
|
||||||
|
{
|
||||||
|
if(!renderedWidgets[activeStep.name])
|
||||||
|
{
|
||||||
|
renderedWidgets[activeStep.name] = {};
|
||||||
|
setRenderedWidgets(renderedWidgets);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(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[] = [];
|
||||||
|
for (let name in processValues)
|
||||||
|
{
|
||||||
|
queryStringParts.push(`${name}=${encodeURIComponent(processValues[name])}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const renderedWidget = (<Box m={-2}>
|
||||||
|
<DashboardWidgets widgetMetaDataList={[widgetMetaData]} omitWrappingGridContainer={true} childUrlParams={queryStringParts.join("&")} />
|
||||||
|
</Box>)
|
||||||
|
renderedWidgets[activeStep.name][widgetName] = renderedWidget;
|
||||||
|
return renderedWidget;
|
||||||
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////
|
////////////////////////////////////////////////////
|
||||||
// generate the main form body content for a step //
|
// generate the main form body content for a step //
|
||||||
////////////////////////////////////////////////////
|
////////////////////////////////////////////////////
|
||||||
@ -653,6 +692,12 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is
|
|||||||
</Box>
|
</Box>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
component.type === QComponentType.WIDGET && (
|
||||||
|
component.values?.widgetName &&
|
||||||
|
renderWidget(component.values?.widgetName)
|
||||||
|
)
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}))
|
}))
|
||||||
|
@ -21,6 +21,7 @@
|
|||||||
|
|
||||||
import {QException} from "@kingsrook/qqq-frontend-core/lib/exceptions/QException";
|
import {QException} from "@kingsrook/qqq-frontend-core/lib/exceptions/QException";
|
||||||
import {Capability} from "@kingsrook/qqq-frontend-core/lib/model/metaData/Capability";
|
import {Capability} from "@kingsrook/qqq-frontend-core/lib/model/metaData/Capability";
|
||||||
|
import {QFieldMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldMetaData";
|
||||||
import {QInstance} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QInstance";
|
import {QInstance} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QInstance";
|
||||||
import {QProcessMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QProcessMetaData";
|
import {QProcessMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QProcessMetaData";
|
||||||
import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData";
|
import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData";
|
||||||
@ -82,6 +83,47 @@ RecordView.defaultProps =
|
|||||||
|
|
||||||
const TABLE_VARIANT_LOCAL_STORAGE_KEY_ROOT = "qqq.tableVariant";
|
const TABLE_VARIANT_LOCAL_STORAGE_KEY_ROOT = "qqq.tableVariant";
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
export function renderSectionOfFields(key: string, fieldNames: string[], tableMetaData: QTableMetaData, helpHelpActive: boolean, record: QRecord, fieldMap?: {[name: string]: QFieldMetaData} )
|
||||||
|
{
|
||||||
|
return <Box key={key} display="flex" flexDirection="column" py={1} pr={2}>
|
||||||
|
{
|
||||||
|
fieldNames.map((fieldName: string) =>
|
||||||
|
{
|
||||||
|
let [field, tableForField] = tableMetaData ? TableUtils.getFieldAndTable(tableMetaData, fieldName) : fieldMap ? [fieldMap[fieldName], null] : [null, null];
|
||||||
|
|
||||||
|
if (field != null)
|
||||||
|
{
|
||||||
|
let label = field.label;
|
||||||
|
|
||||||
|
const helpRoles = ["VIEW_SCREEN", "READ_SCREENS", "ALL_SCREENS"];
|
||||||
|
const showHelp = helpHelpActive || hasHelpContent(field.helpContents, helpRoles);
|
||||||
|
const formattedHelpContent = <HelpContent helpContents={field.helpContents} roles={helpRoles} heading={label} helpContentKey={`table:${tableMetaData?.name};field:${fieldName}`} />;
|
||||||
|
|
||||||
|
const labelElement = <Typography variant="button" textTransform="none" fontWeight="bold" pr={1} color="rgb(52, 71, 103)" sx={{cursor: "default"}}>{label}:</Typography>;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box key={fieldName} flexDirection="row" pr={2}>
|
||||||
|
<>
|
||||||
|
{
|
||||||
|
showHelp && formattedHelpContent ? <Tooltip title={formattedHelpContent}>{labelElement}</Tooltip> : labelElement
|
||||||
|
}
|
||||||
|
<div style={{display: "inline-block", width: 0}}> </div>
|
||||||
|
<Typography variant="button" textTransform="none" fontWeight="regular" color="rgb(123, 128, 154)">
|
||||||
|
{ValueUtils.getDisplayValue(field, record, "view", fieldName)}
|
||||||
|
</Typography>
|
||||||
|
</>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</Box>;
|
||||||
|
}
|
||||||
|
|
||||||
function RecordView({table, launchProcess}: Props): JSX.Element
|
function RecordView({table, launchProcess}: Props): JSX.Element
|
||||||
{
|
{
|
||||||
const {id} = useParams();
|
const {id} = useParams();
|
||||||
@ -519,40 +561,7 @@ function RecordView({table, launchProcess}: Props): JSX.Element
|
|||||||
// for a section with field names, render the field values. //
|
// for a section with field names, render the field values. //
|
||||||
// for the T1 section, the "wrapper" will come out below - but for other sections, produce a wrapper too. //
|
// for the T1 section, the "wrapper" will come out below - but for other sections, produce a wrapper too. //
|
||||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
const fields = (
|
const fields = renderSectionOfFields(section.name, section.fieldNames, tableMetaData, helpHelpActive, record);
|
||||||
<Box key={section.name} display="flex" flexDirection="column" py={1} pr={2}>
|
|
||||||
{
|
|
||||||
section.fieldNames.map((fieldName: string) =>
|
|
||||||
{
|
|
||||||
let [field, tableForField] = TableUtils.getFieldAndTable(tableMetaData, fieldName);
|
|
||||||
if (field != null)
|
|
||||||
{
|
|
||||||
let label = field.label;
|
|
||||||
|
|
||||||
const helpRoles = ["VIEW_SCREEN", "READ_SCREENS", "ALL_SCREENS"];
|
|
||||||
const showHelp = helpHelpActive || hasHelpContent(field.helpContents, helpRoles);
|
|
||||||
const formattedHelpContent = <HelpContent helpContents={field.helpContents} roles={helpRoles} heading={label} helpContentKey={`table:${tableName};field:${fieldName}`} />;
|
|
||||||
|
|
||||||
const labelElement = <Typography variant="button" textTransform="none" fontWeight="bold" pr={1} color="rgb(52, 71, 103)" sx={{cursor: "default"}}>{label}:</Typography>;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box key={fieldName} flexDirection="row" pr={2}>
|
|
||||||
<>
|
|
||||||
{
|
|
||||||
showHelp && formattedHelpContent ? <Tooltip title={formattedHelpContent}>{labelElement}</Tooltip> : labelElement
|
|
||||||
}
|
|
||||||
<div style={{display: "inline-block", width: 0}}> </div>
|
|
||||||
<Typography variant="button" textTransform="none" fontWeight="regular" color="rgb(123, 128, 154)">
|
|
||||||
{ValueUtils.getDisplayValue(field, record, "view", fieldName)}
|
|
||||||
</Typography>
|
|
||||||
</>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
|
|
||||||
if (section.tier === "T1")
|
if (section.tier === "T1")
|
||||||
{
|
{
|
||||||
@ -979,7 +988,7 @@ function RecordView({table, launchProcess}: Props): JSX.Element
|
|||||||
|
|
||||||
{
|
{
|
||||||
showEditChildForm &&
|
showEditChildForm &&
|
||||||
<Modal open={showEditChildForm as boolean} onClose={(event, reason) => closeEditChildForm(event, reason)}>
|
<Modal open={showEditChildForm !== null} onClose={(event, reason) => closeEditChildForm(event, reason)}>
|
||||||
<div className="modalEditForm">
|
<div className="modalEditForm">
|
||||||
<EntityForm
|
<EntityForm
|
||||||
isModal={true}
|
isModal={true}
|
||||||
|
@ -688,6 +688,16 @@ input[type="search"]::-webkit-search-results-decoration
|
|||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.recordView .widget
|
||||||
|
{
|
||||||
|
padding: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.recordView .widget .recordGridWidget
|
||||||
|
{
|
||||||
|
margin: -8px;
|
||||||
|
}
|
||||||
|
|
||||||
.MuiPickersDay-root.Mui-selected, .MuiPickersDay-root.MuiPickersDay-dayWithMargin:hover
|
.MuiPickersDay-root.Mui-selected, .MuiPickersDay-root.MuiPickersDay-dayWithMargin:hover
|
||||||
{
|
{
|
||||||
color: white;
|
color: white;
|
||||||
|
Reference in New Issue
Block a user