diff --git a/src/qqq/components/forms/DynamicSelect.tsx b/src/qqq/components/forms/DynamicSelect.tsx index ccaeb92..005dfc7 100644 --- a/src/qqq/components/forms/DynamicSelect.tsx +++ b/src/qqq/components/forms/DynamicSelect.tsx @@ -321,9 +321,14 @@ function DynamicSelect({tableName, processName, fieldName, overrideId, fieldLabe { setOpen(false); }} - isOptionEqualToValue={(option, value) => option.id === value.id} + isOptionEqualToValue={(option, value) => value !== null && value !== undefined && option.id === value.id} getOptionLabel={(option) => { + if(option === null || option === undefined) + { + return (""); + } + // @ts-ignore if(option && option.length) { diff --git a/src/qqq/components/misc/SavedViews.tsx b/src/qqq/components/misc/SavedViews.tsx index 0fd6886..f604181 100644 --- a/src/qqq/components/misc/SavedViews.tsx +++ b/src/qqq/components/misc/SavedViews.tsx @@ -47,7 +47,6 @@ import FormData from "form-data"; import React, {useEffect, useRef, useState} from "react"; import {useLocation, useNavigate} from "react-router-dom"; import {QCancelButton, QDeleteButton, QSaveButton, QSavedViewsMenuButton} from "qqq/components/buttons/DefaultButtons"; -import CustomWidthTooltip from "qqq/components/tooltips/CustomWidthTooltip"; import QQueryColumns from "qqq/models/query/QQueryColumns"; import RecordQueryView from "qqq/models/query/RecordQueryView"; import FilterUtils from "qqq/utils/qqq/FilterUtils"; @@ -390,8 +389,8 @@ function SavedViews({qController, metaData, tableMetaData, currentSavedView, vie } }; - diffVisibilityFunction(savedView.queryColumns, activeView.queryColumns, "Turned on visibility for "); - diffVisibilityFunction(activeView.queryColumns, savedView.queryColumns, "Turned off visibility for "); + diffVisibilityFunction(savedView.queryColumns, activeView.queryColumns, "Turned on "); + diffVisibilityFunction(activeView.queryColumns, savedView.queryColumns, "Turned off "); diffPinsFunction(savedView.queryColumns, activeView.queryColumns, "Changed pinned state for "); if(savedView.queryColumns.columns.map(c => c.name).join(",") != activeView.queryColumns.columns.map(c => c.name).join(",")) diff --git a/src/qqq/components/query/BasicAndAdvancedQueryControls.tsx b/src/qqq/components/query/BasicAndAdvancedQueryControls.tsx index 16d2697..18617dd 100644 --- a/src/qqq/components/query/BasicAndAdvancedQueryControls.tsx +++ b/src/qqq/components/query/BasicAndAdvancedQueryControls.tsx @@ -347,7 +347,8 @@ const BasicAndAdvancedQueryControls = forwardRef((props: BasicAndAdvancedQueryCo {queryFilter.criteria.map((criteria, i) => { - if(criteria && criteria.fieldName && criteria.operator) + const {criteriaIsValid} = validateCriteria(criteria, null); + if(criteriaIsValid) { const [field, fieldTable] = TableUtils.getFieldAndTable(tableMetaData, criteria.fieldName); counter++; diff --git a/src/qqq/components/query/FilterCriteriaRow.tsx b/src/qqq/components/query/FilterCriteriaRow.tsx index 745ca78..efd41bc 100644 --- a/src/qqq/components/query/FilterCriteriaRow.tsx +++ b/src/qqq/components/query/FilterCriteriaRow.tsx @@ -53,6 +53,27 @@ export enum ValueMode PVS_MULTI = "PVS_MULTI", } +const getValueModeRequiredCount = (valueMode: ValueMode): number => +{ + switch (valueMode) + { + case ValueMode.NONE: + return (0); + case ValueMode.SINGLE: + case ValueMode.SINGLE_DATE: + case ValueMode.SINGLE_DATE_TIME: + case ValueMode.PVS_SINGLE: + return (1); + case ValueMode.DOUBLE: + case ValueMode.DOUBLE_DATE: + case ValueMode.DOUBLE_DATE_TIME: + return (2); + case ValueMode.MULTI: + case ValueMode.PVS_MULTI: + return (null); + } +} + export interface OperatorOption { label: string; @@ -367,6 +388,24 @@ export function FilterCriteriaRow({id, index, tableMetaData, metaData, criteria, { criteria.values = newValue.implicitValues; } + + ////////////////////////////////////////////////////////////////////////////////////////////////// + // we've seen cases where switching operators can sometimes put a null in as the first value... // + // that just causes a bad time (e.g., null pointers in Autocomplete), so, get rid of that. // + ////////////////////////////////////////////////////////////////////////////////////////////////// + if(criteria.values && criteria.values.length == 1 && criteria.values[0] == null) + { + criteria.values = []; + } + + if(newValue.valueMode) + { + const requiredValueCount = getValueModeRequiredCount(newValue.valueMode); + if(requiredValueCount != null && criteria.values.length > requiredValueCount) + { + criteria.values.splice(requiredValueCount); + } + } } else { diff --git a/src/qqq/models/query/RecordQueryView.ts b/src/qqq/models/query/RecordQueryView.ts index 9784b29..a705603 100644 --- a/src/qqq/models/query/RecordQueryView.ts +++ b/src/qqq/models/query/RecordQueryView.ts @@ -21,6 +21,7 @@ import {QQueryFilter} from "@kingsrook/qqq-frontend-core/lib/model/query/QQueryFilter"; import QQueryColumns, {PreLoadQueryColumns} from "qqq/models/query/QQueryColumns"; +import FilterUtils from "qqq/utils/qqq/FilterUtils"; /******************************************************************************* @@ -62,6 +63,23 @@ export default class RecordQueryView view.queryFilter = json.queryFilter as QQueryFilter; + ////////////////////////////////////////////////////////////////////////////////////////// + // it's important that some criteria values exist as expression objects - so - do that. // + ////////////////////////////////////////////////////////////////////////////////////////// + for (let i = 0; i < view.queryFilter?.criteria?.length; i++) + { + const criteria = view.queryFilter.criteria[i] + for (let j = 0; j < criteria?.values?.length; j++) + { + const value = criteria.values[j]; + const expression = FilterUtils.gridCriteriaValueToExpression(value); + if(expression) + { + criteria.values[j] = expression; + } + } + } + if(json.queryColumns) { view.queryColumns = QQueryColumns.buildFromJSON(json.queryColumns); diff --git a/src/qqq/pages/records/query/RecordQuery.tsx b/src/qqq/pages/records/query/RecordQuery.tsx index 59fc00f..fbb5ca8 100644 --- a/src/qqq/pages/records/query/RecordQuery.tsx +++ b/src/qqq/pages/records/query/RecordQuery.tsx @@ -78,7 +78,6 @@ import TableUtils from "qqq/utils/qqq/TableUtils"; import ValueUtils from "qqq/utils/qqq/ValueUtils"; const CURRENT_SAVED_VIEW_ID_LOCAL_STORAGE_KEY_ROOT = "qqq.currentSavedViewId"; -const SEEN_JOIN_TABLES_LOCAL_STORAGE_KEY_ROOT = "qqq.seenJoinTables"; const DENSITY_LOCAL_STORAGE_KEY_ROOT = "qqq.density"; const VIEW_LOCAL_STORAGE_KEY_ROOT = "qqq.recordQueryView"; @@ -159,7 +158,6 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element // look for defaults in the local storage // //////////////////////////////////////////// const currentSavedViewLocalStorageKey = `${CURRENT_SAVED_VIEW_ID_LOCAL_STORAGE_KEY_ROOT}.${tableName}`; - const seenJoinTablesLocalStorageKey = `${SEEN_JOIN_TABLES_LOCAL_STORAGE_KEY_ROOT}.${tableName}`; const tableVariantLocalStorageKey = `${TABLE_VARIANT_LOCAL_STORAGE_KEY_ROOT}.${tableName}`; const viewLocalStorageKey = `${VIEW_LOCAL_STORAGE_KEY_ROOT}.${tableName}`; @@ -167,10 +165,8 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element // define some default values (e.g., to be used if nothing in local storage or no active view) // ///////////////////////////////////////////////////////////////////////////////////////////////// let defaultSort = [] as GridSortItem[]; - let didDefaultVisibilityComeFromLocalStorage = false; let defaultRowsPerPage = 10; let defaultDensity = "standard" as GridDensity; - let seenJoinTables: {[tableName: string]: boolean} = {}; let defaultTableVariant: QTableVariant = null; let defaultMode = "basic"; let defaultQueryColumns: QQueryColumns = new PreLoadQueryColumns(); @@ -188,10 +184,6 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element { defaultDensity = JSON.parse(localStorage.getItem(densityLocalStorageKey)); } - if (localStorage.getItem(seenJoinTablesLocalStorageKey)) - { - seenJoinTables = JSON.parse(localStorage.getItem(seenJoinTablesLocalStorageKey)); - } if (localStorage.getItem(tableVariantLocalStorageKey)) { defaultTableVariant = JSON.parse(localStorage.getItem(tableVariantLocalStorageKey)); @@ -719,6 +711,13 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element { if(pageState != "ready") { + console.log(`In updateTable, but pageSate[${pageState}] is not ready, so returning with noop`); + return; + } + + if(tableMetaData?.usesVariants && (!tableVariant || tableVariantPromptOpen)) + { + console.log("In updateTable, but a variant is needed, so returning with noop"); return; } diff --git a/src/qqq/utils/qqq/FilterUtils.tsx b/src/qqq/utils/qqq/FilterUtils.tsx index c99149b..9f55aab 100644 --- a/src/qqq/utils/qqq/FilterUtils.tsx +++ b/src/qqq/utils/qqq/FilterUtils.tsx @@ -189,7 +189,7 @@ class FilterUtils /******************************************************************************* ** *******************************************************************************/ - private static gridCriteriaValueToExpression(value: any) + public static gridCriteriaValueToExpression(value: any) { if (value && value.length) { @@ -311,6 +311,15 @@ class FilterUtils public static getValuesString(fieldMetaData: QFieldMetaData, criteria: QFilterCriteria, maxValuesToShow: number = 3): string { let valuesString = ""; + + if(criteria.operator == QCriteriaOperator.IS_BLANK || criteria.operator == QCriteriaOperator.IS_NOT_BLANK) + { + /////////////////////////////////////////////// + // we don't want values for these operators. // + /////////////////////////////////////////////// + return valuesString; + } + if (criteria.values && criteria.values.length) { let labels = [] as string[]; @@ -323,17 +332,41 @@ class FilterUtils for (let i = 0; i < maxLoops; i++) { - if(fieldMetaData.type == QFieldType.BOOLEAN) + const value = criteria.values[i]; + if (value.type == "NowWithOffset") { - labels.push(criteria.values[i] == true ? "yes" : "no") + const expression = new NowWithOffsetExpression(value); + labels.push(expression.toString()); } - else if (criteria.values[i] && criteria.values[i].label) + else if (value.type == "Now") { - labels.push(criteria.values[i].label); + const expression = new NowExpression(value); + labels.push(expression.toString()); + } + else if (value.type == "ThisOrLastPeriod") + { + const expression = new ThisOrLastPeriodExpression(value); + labels.push(expression.toString()); + } + else if(fieldMetaData.type == QFieldType.BOOLEAN) + { + labels.push(value == true ? "yes" : "no") + } + else if(fieldMetaData.type == QFieldType.DATE_TIME) + { + labels.push(ValueUtils.formatDateTime(value)); + } + else if(fieldMetaData.type == QFieldType.DATE) + { + labels.push(ValueUtils.formatDate(value)); + } + else if (value && value.label) + { + labels.push(value.label); } else { - labels.push(criteria.values[i]); + labels.push(value); } } @@ -395,13 +428,16 @@ class FilterUtils /******************************************************************************* ** *******************************************************************************/ - public static operatorToHumanString(criteria: QFilterCriteria): string + public static operatorToHumanString(criteria: QFilterCriteria, field: QFieldMetaData): string { if(criteria == null || criteria.operator == null) { return (null); } + const isDate = field.type == QFieldType.DATE; + const isDateTime = field.type == QFieldType.DATE_TIME; + try { switch(criteria.operator) @@ -428,17 +464,41 @@ class FilterUtils case QCriteriaOperator.NOT_CONTAINS: return ("does not contain"); case QCriteriaOperator.LESS_THAN: + if(isDate || isDateTime) + { + return ("is before") + } return ("less than"); case QCriteriaOperator.LESS_THAN_OR_EQUALS: + if(isDate) + { + return ("is on or before") + } + if(isDateTime) + { + return ("is at or before") + } return ("less than or equals"); case QCriteriaOperator.GREATER_THAN: + if(isDate || isDateTime) + { + return ("is after") + } return ("greater than or equals"); case QCriteriaOperator.GREATER_THAN_OR_EQUALS: + if(isDate) + { + return ("is on or after") + } + if(isDateTime) + { + return ("is at or after") + } return ("greater than or equals"); case QCriteriaOperator.IS_BLANK: - return ("is blank"); + return ("is empty"); case QCriteriaOperator.IS_NOT_BLANK: - return ("is not blank"); + return ("is not empty"); case QCriteriaOperator.BETWEEN: return ("is between"); case QCriteriaOperator.NOT_BETWEEN: @@ -470,12 +530,12 @@ class FilterUtils if(styled) { return (<> - {fieldLabel} {FilterUtils.operatorToHumanString(criteria)} {valuesString}  + {fieldLabel} {FilterUtils.operatorToHumanString(criteria, field)} {valuesString}  ); } else { - return (`${fieldLabel} ${FilterUtils.operatorToHumanString(criteria)} ${valuesString}`); + return (`${fieldLabel} ${FilterUtils.operatorToHumanString(criteria, field)} ${valuesString}`); } }