From f44ba8d6d39e5b3ae4feeb1be951aaaadf421c43 Mon Sep 17 00:00:00 2001 From: Tim Chamberlain Date: Tue, 21 May 2024 18:26:35 -0500 Subject: [PATCH] CE-938: improvements to the report setup widget --- src/qqq/components/forms/EntityForm.tsx | 70 ++++----- .../widgets/misc/ReportSetupWidget.tsx | 134 ++++++++++++++---- 2 files changed, 142 insertions(+), 62 deletions(-) diff --git a/src/qqq/components/forms/EntityForm.tsx b/src/qqq/components/forms/EntityForm.tsx index bd01e2a..c885367 100644 --- a/src/qqq/components/forms/EntityForm.tsx +++ b/src/qqq/components/forms/EntityForm.tsx @@ -88,7 +88,7 @@ EntityForm.defaultProps = { //////////////////////////////////////////////////////////////////////////// let formikSetFieldValueFunction = (field: string, value: any, shouldValidate?: boolean): void => { -} +}; function EntityForm(props: Props): JSX.Element { @@ -123,7 +123,7 @@ function EntityForm(props: Props): JSX.Element const [notAllowedError, setNotAllowedError] = useState(null as string); const [formValuesJSON, setFormValuesJSON] = useState(""); - const [formValues, setFormValues] = useState({} as {[name: string]: any}); + const [formValues, setFormValues] = useState({} as { [name: string]: any }); const {pageHeader, setPageHeader} = useContext(QContext); @@ -291,7 +291,7 @@ function EntityForm(props: Props): JSX.Element *******************************************************************************/ useEffect(() => { - const newRenderedWidgetSections: {[name: string]: JSX.Element} = {}; + const newRenderedWidgetSections: { [name: string]: JSX.Element } = {}; for (let widgetName in renderedWidgetSections) { const widgetMetaData = metaData.widgets.get(widgetName); @@ -351,12 +351,11 @@ function EntityForm(props: Props): JSX.Element } - /******************************************************************************* ** if we have a widget that wants to set form-field values, they can take this ** function in as a callback, and then call it with their values. *******************************************************************************/ - function setFormFieldValuesFromWidget(values: {[name: string]: any}) + function setFormFieldValuesFromWidget(values: { [name: string]: any }) { for (let key in values) { @@ -370,7 +369,7 @@ function EntityForm(props: Props): JSX.Element *******************************************************************************/ function getWidgetSection(widgetMetaData: QWidgetMetaData, widgetData: any): JSX.Element { - if(widgetMetaData.type == "childRecordList") + if (widgetMetaData.type == "childRecordList") { widgetData.viewAllLink = null; widgetMetaData.showExportButton = false; @@ -388,18 +387,23 @@ function EntityForm(props: Props): JSX.Element />; } - if(widgetMetaData.type == "reportSetup") + if (widgetMetaData.type == "reportSetup") { + if (widgetMetaData?.defaultValues?.has("tableName")) + { + formValues["tableName"] = widgetMetaData?.defaultValues.get("tableName"); + } + return + />; } - if(widgetMetaData.type == "pivotTableSetup") + if (widgetMetaData.type == "pivotTableSetup") { return + />; } - if(widgetMetaData.type == "dynamicForm") + if (widgetMetaData.type == "dynamicForm") { return + />; } - return (Unsupported widget type: {widgetMetaData.type}) + return (Unsupported widget type: {widgetMetaData.type}); } @@ -449,12 +453,12 @@ function EntityForm(props: Props): JSX.Element function setupFieldRules(tableMetaData: QTableMetaData) { const mdbMetaData = tableMetaData?.supplementalTableMetaData?.get("materialDashboard"); - if(!mdbMetaData) + if (!mdbMetaData) { return; } - if(mdbMetaData.fieldRules) + if (mdbMetaData.fieldRules) { const newFieldRules: FieldRule[] = []; for (let i = 0; i < mdbMetaData.fieldRules.length; i++) @@ -489,14 +493,14 @@ function EntityForm(props: Props): JSX.Element const tableSections = TableUtils.getSectionsForRecordSidebar(tableMetaData, [...tableMetaData.fields.keys()], (section: QTableSection) => { const widget = metaData.widgets.get(section.widgetName); - if(widget) + if (widget) { - if(widget.type == "childRecordList" && widget.defaultValues?.has("manageAssociationName")) + if (widget.type == "childRecordList" && widget.defaultValues?.has("manageAssociationName")) { return (true); } - if(widget.type == "reportSetup" || widget.type == "pivotTableSetup" || widget.type == "dynamicForm") + if (widget.type == "reportSetup" || widget.type == "pivotTableSetup" || widget.type == "dynamicForm") { return (true); } @@ -680,7 +684,7 @@ function EntityForm(props: Props): JSX.Element } const hasFields = section.fieldNames && section.fieldNames.length > 0; - if(hasFields) + if (hasFields) { for (let j = 0; j < section.fieldNames.length; j++) { @@ -1000,19 +1004,19 @@ function EntityForm(props: Props): JSX.Element /******************************************************************************* ** *******************************************************************************/ - function makeQueryStringWithIdAndObject(tableMetaData: QTableMetaData, object: {[key: string]: any}) + function makeQueryStringWithIdAndObject(tableMetaData: QTableMetaData, object: { [key: string]: any }) { const queryParamsArray: string[] = []; - if(props.id) + if (props.id) { - queryParamsArray.push(`${tableMetaData.primaryKeyField}=${encodeURIComponent(props.id)}`) + queryParamsArray.push(`${tableMetaData.primaryKeyField}=${encodeURIComponent(props.id)}`); } - if(object) + if (object) { for (let key in object) { - queryParamsArray.push(`${key}=${encodeURIComponent(object[key])}`) + queryParamsArray.push(`${key}=${encodeURIComponent(object[key])}`); } } @@ -1023,7 +1027,7 @@ function EntityForm(props: Props): JSX.Element /******************************************************************************* ** *******************************************************************************/ - async function reloadWidget(widgetName: string, additionalQueryParamsForWidget: {[key: string]: any }) + async function reloadWidget(widgetName: string, additionalQueryParamsForWidget: { [key: string]: any }) { const widgetData = await qController.widget(widgetName, makeQueryStringWithIdAndObject(tableMetaData, additionalQueryParamsForWidget)); const widgetMetaData = metaData.widgets.get(widgetName); @@ -1045,11 +1049,11 @@ function EntityForm(props: Props): JSX.Element /******************************************************************************* ** process a form-field having a changed value (e.g., apply field rules). *******************************************************************************/ - function handleChangedFieldValue(fieldName: string, oldValue: any, newValue: any, valueChangesToMake: {[fieldName: string]: any}) + function handleChangedFieldValue(fieldName: string, oldValue: any, newValue: any, valueChangesToMake: { [fieldName: string]: any }) { for (let fieldRule of fieldRules) { - if(fieldRule.trigger == FieldRuleTrigger.ON_CHANGE && fieldRule.sourceField == fieldName) + if (fieldRule.trigger == FieldRuleTrigger.ON_CHANGE && fieldRule.sourceField == fieldName) { switch (fieldRule.action) { @@ -1058,7 +1062,7 @@ function EntityForm(props: Props): JSX.Element valueChangesToMake[fieldRule.targetField] = null; break; case FieldRuleAction.RELOAD_WIDGET: - const additionalQueryParamsForWidget: {[key: string]: any} = {}; + const additionalQueryParamsForWidget: { [key: string]: any } = {}; additionalQueryParamsForWidget[fieldRule.sourceField] = newValue; reloadWidget(fieldRule.targetWidget, additionalQueryParamsForWidget); } @@ -1148,21 +1152,21 @@ function EntityForm(props: Props): JSX.Element ///////////////////////////////////////////////// // if we have values from formik, look at them // ///////////////////////////////////////////////// - if(values) + if (values) { //////////////////////////////////////////////////////////////////////// // use stringified values as cheap/easy way to see if any are changed // //////////////////////////////////////////////////////////////////////// const newFormValuesJSON = JSON.stringify(values); - if(formValuesJSON != newFormValuesJSON) + if (formValuesJSON != newFormValuesJSON) { - const valueChangesToMake: {[fieldName: string]: any} = {}; + const valueChangesToMake: { [fieldName: string]: any } = {}; //////////////////////////////////////////////////////////////////// // if the form is dirty (e.g., we're not doing the initial load), // // then process rules for any changed fields // //////////////////////////////////////////////////////////////////// - if(dirty) + if (dirty) { for (let fieldName in values) { @@ -1194,7 +1198,7 @@ function EntityForm(props: Props): JSX.Element setFieldValue(fieldName, valueChangesToMake[fieldName], false); } - setFormValues(formValues) + setFormValues(formValues); setFormValuesJSON(JSON.stringify(values)); } } diff --git a/src/qqq/components/widgets/misc/ReportSetupWidget.tsx b/src/qqq/components/widgets/misc/ReportSetupWidget.tsx index 05ebfa2..2baa811 100644 --- a/src/qqq/components/widgets/misc/ReportSetupWidget.tsx +++ b/src/qqq/components/widgets/misc/ReportSetupWidget.tsx @@ -22,6 +22,9 @@ import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData"; import {QWidgetMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QWidgetMetaData"; +import {QCriteriaOperator} from "@kingsrook/qqq-frontend-core/lib/model/query/QCriteriaOperator"; +import {QFilterCriteria} from "@kingsrook/qqq-frontend-core/lib/model/query/QFilterCriteria"; +import {QFilterOrderBy} from "@kingsrook/qqq-frontend-core/lib/model/query/QFilterOrderBy"; import {QQueryFilter} from "@kingsrook/qqq-frontend-core/lib/model/query/QQueryFilter"; import {Alert, Collapse} from "@mui/material"; import Box from "@mui/material/Box"; @@ -83,6 +86,7 @@ const qController = Client.getInstance(); export default function ReportSetupWidget({isEditable, widgetMetaData, recordValues, onSaveCallback}: ReportSetupWidgetProps): JSX.Element { const [modalOpen, setModalOpen] = useState(false); + const [hideColumns, setHideColumns] = useState(widgetMetaData?.defaultValues?.has("hideColumns") && widgetMetaData?.defaultValues?.get("hideColumns")); const [tableMetaData, setTableMetaData] = useState(null as QTableMetaData); const [alertContent, setAlertContent] = useState(null as string); @@ -101,15 +105,36 @@ export default function ReportSetupWidget({isEditable, widgetMetaData, recordVal ///////////////////////////// // load values from record // ///////////////////////////// - let queryFilter = recordValues["queryFilterJson"] && JSON.parse(recordValues["queryFilterJson"]) as QQueryFilter; + let columns: QQueryColumns = null; let usingDefaultEmptyFilter = false; + let queryFilter = recordValues["queryFilterJson"] && JSON.parse(recordValues["queryFilterJson"]) as QQueryFilter; if (!queryFilter) { queryFilter = new QQueryFilter(); - usingDefaultEmptyFilter = true; + + ////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // if there is no queryFilter provided, see if there are default fields from which a query should be seeded // + ////////////////////////////////////////////////////////////////////////////////////////////////////////////// + const defaultFilterFields = getDefaultFilterFieldNames(widgetMetaData); + if (defaultFilterFields?.length > 0) + { + defaultFilterFields.forEach((fieldName: string) => + { + if (recordValues[fieldName]) + { + queryFilter.addCriteria(new QFilterCriteria(fieldName, QCriteriaOperator.EQUALS, [recordValues[fieldName]])); + } + }); + + queryFilter.addOrderBy(new QFilterOrderBy("id", false)); + queryFilter = Object.assign({}, queryFilter); + } + else + { + usingDefaultEmptyFilter = true; + } } - let columns: QQueryColumns = null; if (recordValues["columnsJson"]) { columns = QQueryColumns.buildFromJSON(recordValues["columnsJson"]); @@ -120,11 +145,20 @@ export default function ReportSetupWidget({isEditable, widgetMetaData, recordVal ////////////////////////////////////////////////////////////////////// useEffect(() => { - if (recordValues["tableName"] && (tableMetaData == null || tableMetaData.name != recordValues["tableName"])) + //////////////////////////////////////////////////////////////////////////////////////// + // if a default table name specified, use it, otherwise use it from the record values // + //////////////////////////////////////////////////////////////////////////////////////// + let tableName = widgetMetaData?.defaultValues?.get("tableName"); + if (!tableName && recordValues["tableName"] && (tableMetaData == null || tableMetaData.name != recordValues["tableName"])) + { + tableName = recordValues["tableName"]; + } + + if (tableName) { (async () => { - const tableMetaData = await qController.loadTableMetaData(recordValues["tableName"]); + const tableMetaData = await qController.loadTableMetaData(tableName); setTableMetaData(tableMetaData); const queryFilterForFrontend = Object.assign({}, queryFilter); @@ -132,7 +166,21 @@ export default function ReportSetupWidget({isEditable, widgetMetaData, recordVal setFrontendQueryFilter(queryFilterForFrontend); })(); } - }, [recordValues]); + }, [JSON.stringify(recordValues)]); + + + /******************************************************************************* + ** + *******************************************************************************/ + function getDefaultFilterFieldNames(widgetMetaData: QWidgetMetaData) + { + if (widgetMetaData?.defaultValues?.has("filterDefaultFieldNames")) + { + return (widgetMetaData.defaultValues.get("filterDefaultFieldNames").split(",")); + } + + return ([]); + } /******************************************************************************* @@ -140,8 +188,27 @@ export default function ReportSetupWidget({isEditable, widgetMetaData, recordVal *******************************************************************************/ function openEditor() { + let missingRequiredFields = [] as string[]; + getDefaultFilterFieldNames(widgetMetaData)?.forEach((fieldName: string) => + { + if (!recordValues[fieldName]) + { + missingRequiredFields.push(tableMetaData.fields.get(fieldName).label); + } + }); + + //////////////////////////////////////////////////////////////////// + // display an alert and return if any required fields are missing // + //////////////////////////////////////////////////////////////////// + if (missingRequiredFields.length > 0) + { + setAlertContent("The following fields must first be selected to add Additional Order Filters: '" + missingRequiredFields.join(", ") + "'"); + return; + } + if (recordValues["tableName"]) { + setAlertContent(null); setModalOpen(true); } } @@ -272,7 +339,14 @@ export default function ReportSetupWidget({isEditable, widgetMetaData, recordVal const labelAdditionalElementsRight: JSX.Element[] = []; if (isEditable) { - labelAdditionalElementsRight.push(); + if (!hideColumns) + { + labelAdditionalElementsRight.push(); + } + else + { + labelAdditionalElementsRight.push(); + } } @@ -311,29 +385,31 @@ export default function ReportSetupWidget({isEditable, widgetMetaData, recordVal } - -
Columns
- - { - mayShowColumnsPreview() && - columns.columns.map((column, i) => {renderColumn(column)}) - } - { - !mayShowColumnsPreview() && - - { - isEditable && - - - - } - { - !isEditable && Your report has no columns. - } - - } + {!hideColumns && ( + +
Columns
+ + { + mayShowColumnsPreview() && + columns.columns.map((column, i) => {renderColumn(column)}) + } + { + !mayShowColumnsPreview() && + + { + isEditable && + + + + } + { + !isEditable && Your report has no columns. + } + + } +
-
+ )} { modalOpen && closeEditor(event, reason)}>