diff --git a/src/qqq/components/widgets/misc/ReportSetupWidget.tsx b/src/qqq/components/widgets/misc/ReportSetupWidget.tsx
index 9cdf995..4195139 100644
--- a/src/qqq/components/widgets/misc/ReportSetupWidget.tsx
+++ b/src/qqq/components/widgets/misc/ReportSetupWidget.tsx
@@ -86,11 +86,17 @@ export default function ReportSetupWidget({isEditable, widgetMetaData, recordVal
const [alertContent, setAlertContent] = useState(null as string);
+ //////////////////////////////////////////////////////////////////////////////////////////////////
+ // we'll actually keep 2 copies of the query filter around here - //
+ // the one in the record (as json) is one that the backend likes (e.g., possible values as ids) //
+ // this "frontend" one is one that the frontend can use (possible values as objects w/ labels). //
+ //////////////////////////////////////////////////////////////////////////////////////////////////
+ const [frontendQueryFilter, setFrontendQueryFilter] = useState(null as QQueryFilter);
+
const {helpHelpActive} = useContext(QContext);
const recordQueryRef = useRef();
-
/////////////////////////////
// load values from record //
/////////////////////////////
@@ -100,10 +106,10 @@ export default function ReportSetupWidget({isEditable, widgetMetaData, recordVal
queryFilter = new QQueryFilter();
}
- let columns = recordValues["columnsJson"] && JSON.parse(recordValues["columnsJson"]) as QQueryColumns;
- if(!columns)
+ let columns: QQueryColumns = null;
+ if(recordValues["columnsJson"])
{
- columns = new QQueryColumns();
+ columns = QQueryColumns.buildFromJSON(recordValues["columnsJson"]);
}
//////////////////////////////////////////////////////////////////////
@@ -117,6 +123,10 @@ export default function ReportSetupWidget({isEditable, widgetMetaData, recordVal
{
const tableMetaData = await qController.loadTableMetaData(recordValues["tableName"])
setTableMetaData(tableMetaData);
+
+ const queryFilterForFrontend = Object.assign({}, queryFilter);
+ await FilterUtils.cleanupValuesInFilerFromQueryString(qController, tableMetaData, queryFilterForFrontend)
+ setFrontendQueryFilter(queryFilterForFrontend)
})();
}
}, [recordValues]);
@@ -150,7 +160,14 @@ export default function ReportSetupWidget({isEditable, widgetMetaData, recordVal
view.queryColumns.sortColumnsFixingPinPositions();
- onSaveCallback({queryFilterJson: JSON.stringify(view.queryFilter), columnsJson: JSON.stringify(view.queryColumns)});
+ //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+ // keep the query filter that came from the recordQuery screen as the front-end version (w/ possible value objects) //
+ // but prep a copy of it for the backend, to stringify as json in the record being edited //
+ //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+ setFrontendQueryFilter(view.queryFilter);
+ const filter = FilterUtils.prepQueryFilterForBackend(tableMetaData, view.queryFilter);
+
+ onSaveCallback({queryFilterJson: JSON.stringify(filter), columnsJson: JSON.stringify(view.queryColumns)});
closeEditor();
}
@@ -197,7 +214,7 @@ export default function ReportSetupWidget({isEditable, widgetMetaData, recordVal
{
if(tableMetaData)
{
- if(queryFilter?.criteria?.length > 0 || queryFilter?.subFilters?.length > 0)
+ if(frontendQueryFilter?.criteria?.length > 0 || frontendQueryFilter?.subFilters?.length > 0)
{
return (true);
}
@@ -271,7 +288,7 @@ export default function ReportSetupWidget({isEditable, widgetMetaData, recordVal
Query Filter
{
mayShowQueryPreview() &&
- 0} removeCriteriaByIndexCallback={null} />
+ 0} removeCriteriaByIndexCallback={null} />
}
{
!mayShowQueryPreview() &&
@@ -329,7 +346,10 @@ export default function ReportSetupWidget({isEditable, widgetMetaData, recordVal
ref={recordQueryRef}
table={tableMetaData}
usage="reportSetup"
- isModal={true} />
+ isModal={true}
+ initialQueryFilter={frontendQueryFilter}
+ initialColumns={columns}
+ />
}
diff --git a/src/qqq/pages/records/query/RecordQuery.tsx b/src/qqq/pages/records/query/RecordQuery.tsx
index 1c5e016..3b6d111 100644
--- a/src/qqq/pages/records/query/RecordQuery.tsx
+++ b/src/qqq/pages/records/query/RecordQuery.tsx
@@ -92,6 +92,8 @@ interface Props
launchProcess?: QProcessMetaData;
usage?: QueryScreenUsage;
isModal?: boolean;
+ initialQueryFilter?: QQueryFilter;
+ initialColumns?: QQueryColumns;
}
///////////////////////////////////////////////////////
@@ -123,7 +125,7 @@ const getLoadingScreen = (isModal: boolean) =>
**
** Yuge component. The best. Lots of very smart people are saying so.
*******************************************************************************/
-const RecordQuery = forwardRef(({table, usage, isModal}: Props, ref) =>
+const RecordQuery = forwardRef(({table, usage, isModal, initialQueryFilter, initialColumns}: Props, ref) =>
{
const tableName = table.name;
const [searchParams] = useSearchParams();
@@ -193,7 +195,9 @@ const RecordQuery = forwardRef(({table, usage, isModal}: Props, ref) =>
/////////////////////////////////////
const densityLocalStorageKey = `${DENSITY_LOCAL_STORAGE_KEY_ROOT}`;
- // only load things out of local storage on the first render
+ ///////////////////////////////////////////////////////////////
+ // only load things out of local storage on the first render //
+ ///////////////////////////////////////////////////////////////
if (firstRender)
{
console.log("This is firstRender, so reading defaults from local storage...");
@@ -224,6 +228,25 @@ const RecordQuery = forwardRef(({table, usage, isModal}: Props, ref) =>
defaultView.mode = defaultMode;
}
+ if(firstRender)
+ {
+ /////////////////////////////////////////////////////////////////////////
+ // allow a caller to send in an initial filter & set of columns. //
+ // only to be used on "first render" //
+ // JSON.parse(JSON.stringify()) to do deep clone and keep object clean //
+ // unclear why not needed on initialColumns... //
+ /////////////////////////////////////////////////////////////////////////
+ if (initialQueryFilter)
+ {
+ defaultView.queryFilter = JSON.parse(JSON.stringify(initialQueryFilter));
+ }
+
+ if (initialColumns)
+ {
+ defaultView.queryColumns = initialColumns;
+ }
+ }
+
/////////////////////////////////////////////////////////////////////////////////////////
// in case the view is missing any of these attributes, give them a reasonable default //
/////////////////////////////////////////////////////////////////////////////////////////
@@ -431,51 +454,6 @@ const RecordQuery = forwardRef(({table, usage, isModal}: Props, ref) =>
};
- /*******************************************************************************
- **
- *******************************************************************************/
- const prepQueryFilterForBackend = (sourceFilter: QQueryFilter) =>
- {
- const filterForBackend = new QQueryFilter([], sourceFilter.orderBys, sourceFilter.subFilters, sourceFilter.booleanOperator);
- for (let i = 0; i < sourceFilter?.criteria?.length; i++)
- {
- const criteria = sourceFilter.criteria[i];
- const {criteriaIsValid} = validateCriteria(criteria, null);
- if (criteriaIsValid)
- {
- if (criteria.operator == QCriteriaOperator.IS_BLANK || criteria.operator == QCriteriaOperator.IS_NOT_BLANK)
- {
- ///////////////////////////////////////////////////////////////////////////////////////////
- // do this to avoid submitting an empty-string argument for blank/not-blank operators... //
- ///////////////////////////////////////////////////////////////////////////////////////////
- filterForBackend.criteria.push(new QFilterCriteria(criteria.fieldName, criteria.operator, []));
- }
- else
- {
- ///////////////////////////////////////////////////////////////////////////////////////////////////////////////
- // else push a clone of the criteria - since it may get manipulated below (convertFilterPossibleValuesToIds) //
- ///////////////////////////////////////////////////////////////////////////////////////////////////////////////
- const [field] = FilterUtils.getField(tableMetaData, criteria.fieldName);
- filterForBackend.criteria.push(new QFilterCriteria(criteria.fieldName, criteria.operator, FilterUtils.cleanseCriteriaValueForQQQ(criteria.values, field)));
- }
- }
- }
-
- /////////////////////////////////////////
- // recursively prep subfilters as well //
- /////////////////////////////////////////
- let subFilters = [] as QQueryFilter[];
- for (let j = 0; j < sourceFilter?.subFilters?.length; j++)
- {
- subFilters.push(prepQueryFilterForBackend(sourceFilter.subFilters[j]));
- }
-
- filterForBackend.subFilters = subFilters;
- filterForBackend.skip = pageNumber * rowsPerPage;
- filterForBackend.limit = rowsPerPage;
- return filterForBackend;
- };
-
/*******************************************************************************
**
@@ -507,7 +485,7 @@ const RecordQuery = forwardRef(({table, usage, isModal}: Props, ref) =>
totalRecords: totalRecords,
columnsModel: columnsModel,
columnVisibilityModel: columnVisibilityModel,
- queryFilter: prepQueryFilterForBackend(queryFilter)
+ queryFilter: FilterUtils.prepQueryFilterForBackend(tableMetaData, queryFilter)
};
exportMenu = (<>
@@ -877,7 +855,7 @@ const RecordQuery = forwardRef(({table, usage, isModal}: Props, ref) =>
/*******************************************************************************
** This is the method that actually executes a query to update the data in the table.
*******************************************************************************/
- const updateTable = (reason?: string) =>
+ const updateTable = (reason?: string, clearOutCount = true) =>
{
if (pageState != "ready")
{
@@ -901,7 +879,7 @@ const RecordQuery = forwardRef(({table, usage, isModal}: Props, ref) =>
// copy the orderBys & operator into it - but we'll build its criteria one-by-one, //
// as clones, as we'll need to tweak them a bit //
/////////////////////////////////////////////////////////////////////////////////////
- const filterForBackend = prepQueryFilterForBackend(queryFilter);
+ const filterForBackend = FilterUtils.prepQueryFilterForBackend(tableMetaData, queryFilter, pageNumber, rowsPerPage);
//////////////////////////////////////////
// figure out joins to use in the query //
@@ -927,6 +905,12 @@ const RecordQuery = forwardRef(({table, usage, isModal}: Props, ref) =>
console.log(`Issuing query: ${thisQueryId}`);
if (tableMetaData.capabilities.has(Capability.TABLE_COUNT))
{
+ if(clearOutCount)
+ {
+ setTotalRecords(null);
+ setDistinctRecords(null);
+ }
+
let includeDistinct = isJoinMany(tableMetaData, getVisibleJoinTables());
qController.count(tableName, filterForBackend, queryJoins, includeDistinct, tableVariant).then(([count, distinctCount]) =>
{
@@ -1428,7 +1412,7 @@ const RecordQuery = forwardRef(({table, usage, isModal}: Props, ref) =>
{
if (selectFullFilterState === "filter")
{
- const filterForBackend = prepQueryFilterForBackend(queryFilter);
+ const filterForBackend = FilterUtils.prepQueryFilterForBackend(tableMetaData, queryFilter);
filterForBackend.skip = 0;
filterForBackend.limit = null;
return `?recordsParam=filterJSON&filterJSON=${encodeURIComponent(JSON.stringify(filterForBackend))}`;
@@ -1436,7 +1420,7 @@ const RecordQuery = forwardRef(({table, usage, isModal}: Props, ref) =>
if (selectFullFilterState === "filterSubset")
{
- const filterForBackend = prepQueryFilterForBackend(queryFilter);
+ const filterForBackend = FilterUtils.prepQueryFilterForBackend(tableMetaData, queryFilter);
filterForBackend.skip = 0;
filterForBackend.limit = selectionSubsetSize;
return `?recordsParam=filterJSON&filterJSON=${encodeURIComponent(JSON.stringify(filterForBackend))}`;
@@ -1459,14 +1443,14 @@ const RecordQuery = forwardRef(({table, usage, isModal}: Props, ref) =>
{
if (selectFullFilterState === "filter")
{
- const filterForBackend = prepQueryFilterForBackend(queryFilter);
+ const filterForBackend = FilterUtils.prepQueryFilterForBackend(tableMetaData, queryFilter);
filterForBackend.skip = 0;
filterForBackend.limit = null;
setRecordIdsForProcess(filterForBackend);
}
else if (selectFullFilterState === "filterSubset")
{
- const filterForBackend = prepQueryFilterForBackend(queryFilter);
+ const filterForBackend = FilterUtils.prepQueryFilterForBackend(tableMetaData, queryFilter);
filterForBackend.skip = 0;
filterForBackend.limit = selectionSubsetSize;
setRecordIdsForProcess(filterForBackend);
@@ -1924,7 +1908,7 @@ const RecordQuery = forwardRef(({table, usage, isModal}: Props, ref) =>
*******************************************************************************/
const openColumnStatistics = async (column: GridColDef) =>
{
- setFilterForColumnStats(prepQueryFilterForBackend(queryFilter));
+ setFilterForColumnStats(FilterUtils.prepQueryFilterForBackend(tableMetaData, queryFilter));
setColumnStatsFieldName(column.field);
const [field, fieldTable] = TableUtils.getFieldAndTable(tableMetaData, column.field);
@@ -2285,7 +2269,7 @@ const RecordQuery = forwardRef(({table, usage, isModal}: Props, ref) =>
// to avoid both this useEffect and the one below from both doing an "initial query", //
// only run this one if at least 1 query has already been ran //
////////////////////////////////////////////////////////////////////////////////////////
- updateTable("useEffect(pageNumber,rowsPerPage)");
+ updateTable("useEffect(pageNumber,rowsPerPage)", false);
}
}, [pageNumber, rowsPerPage]);
@@ -2320,7 +2304,16 @@ const RecordQuery = forwardRef(({table, usage, isModal}: Props, ref) =>
if (pageState == "ready")
{
- const filterForBackend = prepQueryFilterForBackend(queryFilter);
+ const filterForBackend = FilterUtils.prepQueryFilterForBackend(tableMetaData, queryFilter);
+
+ ///////////////////////////////////////////////////////////////////////
+ // remove the skip & limit (e.g., pagination) from this hash - //
+ // as we have a specific useEffect watching these, specifically //
+ // so we can pass the dont-clear-count flag into updateTable, //
+ // to try to keep the count from flashing back & forth to "Counting" //
+ ///////////////////////////////////////////////////////////////////////
+ filterForBackend.skip = null;
+ filterForBackend.limit = null;
const newFilterHash = JSON.stringify(filterForBackend);
if (filterHash != newFilterHash)
@@ -2960,6 +2953,8 @@ RecordQuery.defaultProps = {
usage: "queryScreen",
launchProcess: null,
isModal: false,
+ initialQueryFilter: null,
+ initialColumns: null,
};
diff --git a/src/qqq/utils/qqq/FilterUtils.tsx b/src/qqq/utils/qqq/FilterUtils.tsx
index 153fbcb..eaa8940 100644
--- a/src/qqq/utils/qqq/FilterUtils.tsx
+++ b/src/qqq/utils/qqq/FilterUtils.tsx
@@ -32,6 +32,7 @@ import {QQueryFilter} from "@kingsrook/qqq-frontend-core/lib/model/query/QQueryF
import {ThisOrLastPeriodExpression} from "@kingsrook/qqq-frontend-core/lib/model/query/ThisOrLastPeriodExpression";
import Box from "@mui/material/Box";
import {GridSortModel} from "@mui/x-data-grid-pro";
+import {validateCriteria} from "qqq/components/query/FilterCriteriaRow";
import TableUtils from "qqq/utils/qqq/TableUtils";
import ValueUtils from "qqq/utils/qqq/ValueUtils";
@@ -612,6 +613,58 @@ class FilterUtils
}
}
+
+ /*******************************************************************************
+ ** make a new query filter, based on the input one, but w/ values good for the
+ ** backend. such as, possible values as just ids, not objects w/ a label;
+ ** date-times formatted properly and in UTC
+ *******************************************************************************/
+ public static prepQueryFilterForBackend(tableMetaData: QTableMetaData, sourceFilter: QQueryFilter, pageNumber?: number, rowsPerPage?: number): QQueryFilter
+ {
+ const filterForBackend = new QQueryFilter([], sourceFilter.orderBys, sourceFilter.subFilters, sourceFilter.booleanOperator);
+ for (let i = 0; i < sourceFilter?.criteria?.length; i++)
+ {
+ const criteria = sourceFilter.criteria[i];
+ const {criteriaIsValid} = validateCriteria(criteria, null);
+ if (criteriaIsValid)
+ {
+ if (criteria.operator == QCriteriaOperator.IS_BLANK || criteria.operator == QCriteriaOperator.IS_NOT_BLANK)
+ {
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // do this to avoid submitting an empty-string argument for blank/not-blank operators... //
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ filterForBackend.criteria.push(new QFilterCriteria(criteria.fieldName, criteria.operator, []));
+ }
+ else
+ {
+ ///////////////////////////////////////////////////////////////////////////////////////////////////////////////
+ // else push a clone of the criteria - since it may get manipulated below (convertFilterPossibleValuesToIds) //
+ ///////////////////////////////////////////////////////////////////////////////////////////////////////////////
+ const [field] = FilterUtils.getField(tableMetaData, criteria.fieldName);
+ filterForBackend.criteria.push(new QFilterCriteria(criteria.fieldName, criteria.operator, FilterUtils.cleanseCriteriaValueForQQQ(criteria.values, field)));
+ }
+ }
+ }
+
+ /////////////////////////////////////////
+ // recursively prep subfilters as well //
+ /////////////////////////////////////////
+ let subFilters = [] as QQueryFilter[];
+ for (let j = 0; j < sourceFilter?.subFilters?.length; j++)
+ {
+ subFilters.push(FilterUtils.prepQueryFilterForBackend(tableMetaData, sourceFilter.subFilters[j]));
+ }
+
+ filterForBackend.subFilters = subFilters;
+
+ if(pageNumber !== undefined && rowsPerPage !== undefined)
+ {
+ filterForBackend.skip = pageNumber * rowsPerPage;
+ filterForBackend.limit = rowsPerPage;
+ }
+
+ return filterForBackend;
+ };
}
export default FilterUtils;