CE-798 Post-demo style updates; add concept of reconciling current table definition w/ view (e.g., add/delete columns from tables); test updates

This commit is contained in:
2024-02-08 20:09:30 -06:00
parent fc238127a7
commit 3f3b188a9d
16 changed files with 449 additions and 232 deletions

View File

@ -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 (
<React.Fragment key={i}>
<span key={i} style={{marginBottom: "0.125rem"}} onMouseOver={() => handleMouseOverElement(`queryPreview-${i}`)} onMouseOut={() => handleMouseOutElement()}>
{counter > 1 ? <span style={{marginLeft: "0.25rem", marginRight: "0.25rem"}}>{queryFilter.booleanOperator}&nbsp;</span> : <span/>}
{FilterUtils.criteriaToHumanString(tableMetaData, criteria, true)}
<XIcon position="forAdvancedQueryPreview" onClick={() => removeCriteriaByIndex(i)} />
</React.Fragment>
{mouseOverElement == `queryPreview-${i}` && <span className={`advancedQueryPreviewX-${counter - 1}`}><XIcon position="forAdvancedQueryPreview" onClick={() => removeCriteriaByIndex(i)} /></span>}
</span>
);
}
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 (
<Box pb={mode == "advanced" ? "0.25rem" : "0"}>
@ -703,20 +729,22 @@ const BasicAndAdvancedQueryControls = forwardRef((props: BasicAndAdvancedQueryCo
<Tooltip enterDelay={500} title="Build an advanced Filter" placement="top">
<>
<Button
className="filterBuilderButton"
onClick={(e) => openFilterBuilder(e)}
{... filterBuilderMouseEvents}
startIcon={<Icon>build</Icon>}
sx={{borderRadius: "0.75rem", padding: "0.5rem", pl: "1rem", fontSize: "0.875rem", fontWeight: 500, border: `1px solid ${accentColor}`, textTransform: "none"}}
>
Filter Builder
{
countValidCriteria(queryFilter) > 0 &&
<Box sx={{backgroundColor: accentColor, marginLeft: "0.25rem", minWidth: "1rem", fontSize: "0.75rem"}} borderRadius="50%" color="#FFFFFF" position="relative" top="-2px" className="filterBuilderCountBadge">
<Box {... filterBuilderMouseEvents} sx={{backgroundColor: accentColor, marginLeft: "0.25rem", minWidth: "1rem", fontSize: "0.75rem"}} borderRadius="50%" color="#FFFFFF" position="relative" top="-2px" className="filterBuilderCountBadge">
{countValidCriteria(queryFilter) }
</Box>
}
</Button>
{
hasValidFilters && <span className="filterBuilderXIcon"><XIcon shade="accent" position="default" onClick={() => setShowClearFiltersWarning(true)} /></span>
hasValidFilters && mouseOverElement == "filterBuilderButton" && <span {... filterBuilderMouseEvents} className="filterBuilderXIcon"><XIcon shade="accent" position="default" onClick={() => setShowClearFiltersWarning(true)} /></span>
}
</>
</Tooltip>
@ -738,14 +766,15 @@ const BasicAndAdvancedQueryControls = forwardRef((props: BasicAndAdvancedQueryCo
<Box whiteSpace="nowrap" display="flex" flexShrink={1} flexGrow={1} alignItems="center">
{
<Box
className="advancedQueryString"
display="inline-block"
borderTop={`1px solid ${borderGray}`}
borderRadius="0 0 0.75rem 0.75rem"
width="100%"
sx={{fontSize: "1rem", background: "#FFFFFF"}}
minHeight={"2.5rem"}
minHeight={"2.375rem"}
p={"0.5rem"}
pb={0} // comes from the elements inside
pb={"0.125rem"}
boxShadow={"inset 0px 0px 4px 2px #EFEFED"}
>
{queryToAdvancedString()}

View File

@ -53,7 +53,7 @@ export enum ValueMode
PVS_MULTI = "PVS_MULTI",
}
const getValueModeRequiredCount = (valueMode: ValueMode): number =>
export const getValueModeRequiredCount = (valueMode: ValueMode): number =>
{
switch (valueMode)
{

View File

@ -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 = <Badge badgeContent={criteriaIsValid && !tooComplex ? 1 : 0} color="warning" variant="dot" onMouseOver={startIconMouseOver} onMouseOut={startIconMouseOut} onClick={resetCriteria}><Icon>{startIconName}</Icon></Badge>
if(criteriaIsValid)
{
startIcon = <Tooltip title={"Remove this condition from your filter"} enterDelay={tooltipEnterDelay}>{startIcon}</Tooltip>
}
let buttonAdditionalStyles: any = {};
let buttonContent = <span>{tableForField?.name != tableMetaData.name ? `${tableForField.label}: ` : ""}{fieldMetaData.label}</span>
@ -402,16 +409,19 @@ export default function QuickFilter({tableMetaData, fullFieldName, fieldMetaData
valuesString = "";
}
buttonContent = (
<Tooltip title={`${operatorSelectedValue.label} ${valuesString}`} enterDelay={tooltipEnterDelay}>
{buttonContent}
</Tooltip>
);
buttonContent = (<><span style={{fontWeight: 700}}>{buttonContent}:</span>&nbsp;<span style={{fontWeight: 400}}>{operatorSelectedValue.label}&nbsp;{valuesString}</span></>);
}
const mouseEvents =
{
onMouseOver: () => handleMouseOverElement(),
onMouseOut: () => handleMouseOutElement()
};
let button = fieldMetaData && <Button
id={`quickFilter.${fullFieldName}`}
className={buttonClassName}
{...mouseEvents}
sx={{...quickFilterButtonStyles, ...buttonAdditionalStyles, mr: "0.5rem"}}
onClick={tooComplex ? noop : handleOpenMenu}
disabled={tooComplex}
@ -461,7 +471,7 @@ export default function QuickFilter({tableMetaData, fullFieldName, fieldMetaData
// only show the 'x' if it's to clear out a valid criteria on the field, //
// or if we were given a callback to remove the quick-filter field from the screen //
/////////////////////////////////////////////////////////////////////////////////////
(criteriaIsValid || handleRemoveQuickFilterField) && <XIcon shade={criteriaIsValid ? "accent" : "default"} position="forQuickFilter" onClick={xClicked} />
(criteriaIsValid || handleRemoveQuickFilterField) && isMouseOver && <span {...mouseEvents}><XIcon shade={criteriaIsValid ? "accent" : "default"} position="forQuickFilter" onClick={xClicked} /></span>
}
{
isOpen && <Menu open={Boolean(anchorEl)} anchorEl={anchorEl} onClose={closeMenu} sx={{overflow: "visible"}}>

View File

@ -59,7 +59,7 @@ export default function XIcon({onClick, position, shade}: XIconProps): JSX.Eleme
else if(position == "forAdvancedQueryPreview")
{
rest = {
top: "-0.375rem",
top: "-0.5rem",
left: "-0.75rem",
}
}