From 3f3b188a9dc5e43f96f07cd5c659d9a072bf0dc9 Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Thu, 8 Feb 2024 20:09:30 -0600 Subject: [PATCH] CE-798 Post-demo style updates; add concept of reconciling current table definition w/ view (e.g., add/delete columns from tables); test updates --- src/qqq/components/horseshoe/NavBar.tsx | 2 +- src/qqq/components/misc/SavedViews.tsx | 19 +- .../query/BasicAndAdvancedQueryControls.tsx | 49 ++- .../components/query/FilterCriteriaRow.tsx | 2 +- src/qqq/components/query/QuickFilter.tsx | 100 +++--- src/qqq/components/query/XIcon.tsx | 2 +- src/qqq/models/LoadingState.ts | 1 + src/qqq/models/query/QQueryColumns.ts | 41 +++ src/qqq/pages/records/query/RecordQuery.tsx | 322 +++++++++++------- .../selenium/lib/QSeleniumLib.java | 17 + .../selenium/lib/QueryScreenLib.java | 54 ++- .../tests/DashboardTableWidgetExportTest.java | 3 +- ...ueryScreenFilterInUrlAdvancedModeTest.java | 15 +- .../QueryScreenFilterInUrlBasicModeTest.java | 16 +- .../tests/{ => query}/QueryScreenTest.java | 13 +- .../tests/{ => query}/SavedViewsTest.java | 25 +- 16 files changed, 449 insertions(+), 232 deletions(-) rename src/test/java/com/kingsrook/qqq/frontend/materialdashboard/selenium/tests/{ => query}/QueryScreenFilterInUrlAdvancedModeTest.java (88%) rename src/test/java/com/kingsrook/qqq/frontend/materialdashboard/selenium/tests/{ => query}/QueryScreenFilterInUrlBasicModeTest.java (94%) rename src/test/java/com/kingsrook/qqq/frontend/materialdashboard/selenium/tests/{ => query}/QueryScreenTest.java (92%) rename src/test/java/com/kingsrook/qqq/frontend/materialdashboard/selenium/tests/{ => query}/SavedViewsTest.java (91%) diff --git a/src/qqq/components/horseshoe/NavBar.tsx b/src/qqq/components/horseshoe/NavBar.tsx index f2aefb4..fd7cfac 100644 --- a/src/qqq/components/horseshoe/NavBar.tsx +++ b/src/qqq/components/horseshoe/NavBar.tsx @@ -251,7 +251,7 @@ function NavBar({absolute, light, isMini}: Props): JSX.Element { pageHeader && - + {pageHeader} diff --git a/src/qqq/components/misc/SavedViews.tsx b/src/qqq/components/misc/SavedViews.tsx index 81237ea..59dcc07 100644 --- a/src/qqq/components/misc/SavedViews.tsx +++ b/src/qqq/components/misc/SavedViews.tsx @@ -405,7 +405,7 @@ function SavedViews({qController, metaData, tableMetaData, currentSavedView, tab } { - hasStorePermission && + hasStorePermission && currentSavedView != null && handleDropdownOptionClick(RENAME_OPTION)}> edit @@ -414,16 +414,16 @@ function SavedViews({qController, metaData, tableMetaData, currentSavedView, tab } { - hasStorePermission && - + hasStorePermission && currentSavedView != null && + handleDropdownOptionClick(DUPLICATE_OPTION)}> content_copy - Duplicate... + Save As... } { - hasDeletePermission && + hasStorePermission && currentSavedView != null && handleDropdownOptionClick(DELETE_OPTION)}> delete @@ -462,15 +462,6 @@ function SavedViews({qController, metaData, tableMetaData, currentSavedView, tab let buttonBorder = colors.grayLines.main; let buttonColor = colors.gray.main; - if(loadingSavedView) - { - buttonText = "Loading..."; - } - else if(currentSavedView) - { - buttonText = currentSavedView.values.get("label") - } - if(currentSavedView) { if (viewIsModified) diff --git a/src/qqq/components/query/BasicAndAdvancedQueryControls.tsx b/src/qqq/components/query/BasicAndAdvancedQueryControls.tsx index 907476a..e947bbf 100644 --- a/src/qqq/components/query/BasicAndAdvancedQueryControls.tsx +++ b/src/qqq/components/query/BasicAndAdvancedQueryControls.tsx @@ -99,6 +99,7 @@ const BasicAndAdvancedQueryControls = forwardRef((props: BasicAndAdvancedQueryCo const [addQuickFilterMenu, setAddQuickFilterMenu] = useState(null) const [addQuickFilterOpenCounter, setAddQuickFilterOpenCounter] = useState(0); const [showClearFiltersWarning, setShowClearFiltersWarning] = useState(false); + const [mouseOverElement, setMouseOverElement] = useState(null as string); const [, forceUpdate] = useReducer((x) => x + 1, 0); const {accentColor} = useContext(QContext); @@ -125,6 +126,24 @@ const BasicAndAdvancedQueryControls = forwardRef((props: BasicAndAdvancedQueryCo }); + /******************************************************************************* + ** + *******************************************************************************/ + function handleMouseOverElement(name: string) + { + setMouseOverElement(name); + } + + + /******************************************************************************* + ** + *******************************************************************************/ + function handleMouseOutElement() + { + setMouseOverElement(null); + } + + /******************************************************************************* ** for a given field, set its default operator for quick-filter dropdowns. *******************************************************************************/ @@ -391,11 +410,11 @@ const BasicAndAdvancedQueryControls = forwardRef((props: BasicAndAdvancedQueryCo counter++; return ( - + handleMouseOverElement(`queryPreview-${i}`)} onMouseOut={() => handleMouseOutElement()}> {counter > 1 ? {queryFilter.booleanOperator}  : } {FilterUtils.criteriaToHumanString(tableMetaData, criteria, true)} - removeCriteriaByIndex(i)} /> - + {mouseOverElement == `queryPreview-${i}` && removeCriteriaByIndex(i)} />} + ); } else @@ -433,7 +452,7 @@ const BasicAndAdvancedQueryControls = forwardRef((props: BasicAndAdvancedQueryCo //////////////////////////////////////////////////////////////////////////////////////////////// if (queryFilter && queryFilter.criteria) { - ensureAllFilterCriteriaAreActiveQuickFilters(tableMetaData, queryFilter, "modeToggleClicked"); + ensureAllFilterCriteriaAreActiveQuickFilters(tableMetaData, queryFilter, "modeToggleClicked", "basic"); } } @@ -449,7 +468,7 @@ const BasicAndAdvancedQueryControls = forwardRef((props: BasicAndAdvancedQueryCo ** make sure that all fields in the current query are on-screen as quick-filters ** (that is, if the query can be basic) *******************************************************************************/ - const ensureAllFilterCriteriaAreActiveQuickFilters = (tableMetaData: QTableMetaData, queryFilter: QQueryFilter, reason: "modeToggleClicked" | "defaultFilterLoaded" | "savedFilterSelected" | string) => + const ensureAllFilterCriteriaAreActiveQuickFilters = (tableMetaData: QTableMetaData, queryFilter: QQueryFilter, reason: "modeToggleClicked" | "defaultFilterLoaded" | "savedFilterSelected" | string, newMode?: string) => { if(!tableMetaData || !queryFilter) { @@ -465,7 +484,8 @@ const BasicAndAdvancedQueryControls = forwardRef((props: BasicAndAdvancedQueryCo return; } - if(mode == "basic") + const modeToUse = newMode ?? mode; + if(modeToUse == "basic") { for (let i = 0; i < queryFilter?.criteria?.length; i++) { @@ -601,6 +621,12 @@ const BasicAndAdvancedQueryControls = forwardRef((props: BasicAndAdvancedQueryCo handleAdornmentClick={handleSetSortArrowClick} />); + const filterBuilderMouseEvents = + { + onMouseOver: () => handleMouseOverElement("filterBuilderButton"), + onMouseOut: () => handleMouseOutElement() + }; + return ( @@ -703,20 +729,22 @@ const BasicAndAdvancedQueryControls = forwardRef((props: BasicAndAdvancedQueryCo <> { - hasValidFilters && setShowClearFiltersWarning(true)} /> + hasValidFilters && mouseOverElement == "filterBuilderButton" && setShowClearFiltersWarning(true)} /> } @@ -738,14 +766,15 @@ const BasicAndAdvancedQueryControls = forwardRef((props: BasicAndAdvancedQueryCo { {queryToAdvancedString()} diff --git a/src/qqq/components/query/FilterCriteriaRow.tsx b/src/qqq/components/query/FilterCriteriaRow.tsx index 2e88ea2..e170d00 100644 --- a/src/qqq/components/query/FilterCriteriaRow.tsx +++ b/src/qqq/components/query/FilterCriteriaRow.tsx @@ -53,7 +53,7 @@ export enum ValueMode PVS_MULTI = "PVS_MULTI", } -const getValueModeRequiredCount = (valueMode: ValueMode): number => +export const getValueModeRequiredCount = (valueMode: ValueMode): number => { switch (valueMode) { diff --git a/src/qqq/components/query/QuickFilter.tsx b/src/qqq/components/query/QuickFilter.tsx index ae88728..f881f56 100644 --- a/src/qqq/components/query/QuickFilter.tsx +++ b/src/qqq/components/query/QuickFilter.tsx @@ -24,18 +24,16 @@ import {QFieldType} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QField import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData"; import {QCriteriaOperator} from "@kingsrook/qqq-frontend-core/lib/model/query/QCriteriaOperator"; import {QFilterCriteria} from "@kingsrook/qqq-frontend-core/lib/model/query/QFilterCriteria"; -import {Badge, Tooltip} from "@mui/material"; +import {Tooltip} from "@mui/material"; import Autocomplete from "@mui/material/Autocomplete"; import Box from "@mui/material/Box"; import Button from "@mui/material/Button"; -import Icon from "@mui/material/Icon"; -import IconButton from "@mui/material/IconButton"; import Menu from "@mui/material/Menu"; import TextField from "@mui/material/TextField"; import React, {SyntheticEvent, useContext, useState} from "react"; import QContext from "QContext"; import {QFilterCriteriaWithId} from "qqq/components/query/CustomFilterPanel"; -import {getDefaultCriteriaValue, getOperatorOptions, OperatorOption, validateCriteria} from "qqq/components/query/FilterCriteriaRow"; +import {getDefaultCriteriaValue, getOperatorOptions, getValueModeRequiredCount, OperatorOption, validateCriteria} from "qqq/components/query/FilterCriteriaRow"; import FilterCriteriaRowValues from "qqq/components/query/FilterCriteriaRowValues"; import XIcon from "qqq/components/query/XIcon"; import FilterUtils from "qqq/utils/qqq/FilterUtils"; @@ -71,8 +69,7 @@ export const quickFilterButtonStyles = { border: "1px solid #757575", minWidth: "3.5rem", minHeight: "auto", - padding: "0.375rem 0.625rem", - whiteSpace: "nowrap", + padding: "0.375rem 0.625rem", whiteSpace: "nowrap", marginBottom: "0.5rem" } @@ -149,6 +146,7 @@ export default function QuickFilter({tableMetaData, fullFieldName, fieldMetaData const [isOpen, setIsOpen] = useState(false); const [anchorEl, setAnchorEl] = useState(null); + const [isMouseOver, setIsMouseOver] = useState(false); const [criteria, setCriteria] = useState(criteriaParamIsCriteria(criteriaParam) ? criteriaParam as QFilterCriteriaWithId : null); const [id, setId] = useState(criteriaParamIsCriteria(criteriaParam) ? (criteriaParam as QFilterCriteriaWithId).id : ++seedId); @@ -156,13 +154,29 @@ export default function QuickFilter({tableMetaData, fullFieldName, fieldMetaData const [operatorSelectedValue, setOperatorSelectedValue] = useState(getOperatorSelectedValue(operatorOptions, criteria, defaultOperator)); const [operatorInputValue, setOperatorInputValue] = useState(operatorSelectedValue?.label); - const [startIconName, setStartIconName] = useState("filter_alt"); - const {criteriaIsValid, criteriaStatusTooltip} = validateCriteria(criteria, operatorSelectedValue); const {accentColor} = useContext(QContext); + /******************************************************************************* + ** + *******************************************************************************/ + function handleMouseOverElement() + { + setIsMouseOver(true); + } + + + /******************************************************************************* + ** + *******************************************************************************/ + function handleMouseOutElement() + { + setIsMouseOver(false); + } + + ////////////////////////////////////////////////////////////////////////////////////////////////////////////// // handle a change to the criteria from outside this component (e.g., the prop isn't the same as the state) // ////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -171,7 +185,6 @@ export default function QuickFilter({tableMetaData, fullFieldName, fieldMetaData const newCriteria = criteriaParam as QFilterCriteriaWithId; setCriteria(newCriteria); const operatorOption = operatorOptions.filter(o => o.value == newCriteria.operator)[0]; - console.log(`B: setOperatorSelectedValue [${JSON.stringify(operatorOption)}]`); setOperatorSelectedValue(operatorOption); setOperatorInputValue(operatorOption.label); } @@ -202,7 +215,6 @@ export default function QuickFilter({tableMetaData, fullFieldName, fieldMetaData const operatorOption = operatorOptions.filter(o => o.value == defaultOperator)[0]; const criteria = new QFilterCriteriaWithId(fullFieldName, operatorOption?.value, getDefaultCriteriaValue()); criteria.id = id; - console.log(`C: setOperatorSelectedValue [${JSON.stringify(operatorOption)}]`); setOperatorSelectedValue(operatorOption); setOperatorInputValue(operatorOption?.label); setCriteria(criteria); @@ -216,6 +228,12 @@ export default function QuickFilter({tableMetaData, fullFieldName, fieldMetaData { setIsOpen(!isOpen); setAnchorEl(event.currentTarget); + + setTimeout(() => + { + const element = document.getElementById("value-" + criteria.id); + element?.focus(); + }) }; /******************************************************************************* @@ -236,7 +254,6 @@ export default function QuickFilter({tableMetaData, fullFieldName, fieldMetaData if (newValue) { - console.log(`D: setOperatorSelectedValue [${JSON.stringify(newValue)}]`); setOperatorSelectedValue(newValue); setOperatorInputValue(newValue.label); @@ -244,10 +261,27 @@ export default function QuickFilter({tableMetaData, fullFieldName, fieldMetaData { 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 { - console.log("E: setOperatorSelectedValue [null]"); setOperatorSelectedValue(null); setOperatorInputValue(""); } @@ -307,30 +341,9 @@ export default function QuickFilter({tableMetaData, fullFieldName, fieldMetaData e.stopPropagation(); const newCriteria = makeNewCriteria(); updateCriteria(newCriteria, false, true); - setStartIconName("filter_alt"); } } - /******************************************************************************* - ** event handler for mouse-over on the filter icon - that changes to an 'x' - ** if there's a valid criteria in the quick-filter - *******************************************************************************/ - const startIconMouseOver = () => - { - if(criteriaIsValid) - { - setStartIconName("clear"); - } - } - - /******************************************************************************* - ** event handler for mouse-out on the filter icon - always resets it. - *******************************************************************************/ - const startIconMouseOut = () => - { - setStartIconName("filter_alt"); - } - /******************************************************************************* ** event handler for clicking the (x) icon that turns off this quick filter field. ** hands off control to the function that was passed in (e.g., from RecordQueryOrig). @@ -359,7 +372,6 @@ export default function QuickFilter({tableMetaData, fullFieldName, fieldMetaData const maybeNewOperatorSelectedValue = getOperatorSelectedValue(operatorOptions, criteria, defaultOperator); if(JSON.stringify(maybeNewOperatorSelectedValue) !== JSON.stringify(operatorSelectedValue)) { - console.log(`A: setOperatorSelectedValue [${JSON.stringify(maybeNewOperatorSelectedValue)}]`); setOperatorSelectedValue(maybeNewOperatorSelectedValue) setOperatorInputValue(maybeNewOperatorSelectedValue?.label) } @@ -377,11 +389,6 @@ export default function QuickFilter({tableMetaData, fullFieldName, fieldMetaData ///////////////////////// const tooComplex = criteriaParam == "tooComplex"; const tooltipEnterDelay = 500; - let startIcon = {startIconName} - if(criteriaIsValid) - { - startIcon = {startIcon} - } let buttonAdditionalStyles: any = {}; let buttonContent = {tableForField?.name != tableMetaData.name ? `${tableForField.label}: ` : ""}{fieldMetaData.label} @@ -402,16 +409,19 @@ export default function QuickFilter({tableMetaData, fullFieldName, fieldMetaData valuesString = ""; } - buttonContent = ( - - {buttonContent} - - ); + buttonContent = (<>{buttonContent}: {operatorSelectedValue.label} {valuesString}); } + const mouseEvents = + { + onMouseOver: () => handleMouseOverElement(), + onMouseOut: () => handleMouseOutElement() + }; + let button = fieldMetaData &&