mirror of
https://github.com/Kingsrook/qqq-frontend-material-dashboard.git
synced 2025-07-17 21:00:45 +00:00
Merged feature/CE-798-quick-filters into integration/sprint-35
This commit is contained in:
@ -251,7 +251,7 @@ function NavBar({absolute, light, isMini}: Props): JSX.Element
|
|||||||
{
|
{
|
||||||
pageHeader &&
|
pageHeader &&
|
||||||
<Box display="flex" justifyContent="space-between">
|
<Box display="flex" justifyContent="space-between">
|
||||||
<MDTypography pt={1} textTransform="capitalize" variant="h3" color={light ? "white" : "dark"} noWrap>
|
<MDTypography pb="0.5rem" textTransform="capitalize" variant="h3" color={light ? "white" : "dark"} noWrap>
|
||||||
{pageHeader}
|
{pageHeader}
|
||||||
</MDTypography>
|
</MDTypography>
|
||||||
</Box>
|
</Box>
|
||||||
|
@ -405,7 +405,7 @@ function SavedViews({qController, metaData, tableMetaData, currentSavedView, tab
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
hasStorePermission &&
|
hasStorePermission && currentSavedView != null &&
|
||||||
<Tooltip {...menuTooltipAttribs} title="Change the name for this saved view.">
|
<Tooltip {...menuTooltipAttribs} title="Change the name for this saved view.">
|
||||||
<MenuItem disabled={currentSavedView === null} onClick={() => handleDropdownOptionClick(RENAME_OPTION)}>
|
<MenuItem disabled={currentSavedView === null} onClick={() => handleDropdownOptionClick(RENAME_OPTION)}>
|
||||||
<ListItemIcon><Icon>edit</Icon></ListItemIcon>
|
<ListItemIcon><Icon>edit</Icon></ListItemIcon>
|
||||||
@ -414,16 +414,16 @@ function SavedViews({qController, metaData, tableMetaData, currentSavedView, tab
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
hasStorePermission &&
|
hasStorePermission && currentSavedView != null &&
|
||||||
<Tooltip {...menuTooltipAttribs} title="Make a copy this saved view, with a different name, separate from the original.">
|
<Tooltip {...menuTooltipAttribs} title="Save a new copy this view, with a different name, separate from the original.">
|
||||||
<MenuItem disabled={currentSavedView === null} onClick={() => handleDropdownOptionClick(DUPLICATE_OPTION)}>
|
<MenuItem disabled={currentSavedView === null} onClick={() => handleDropdownOptionClick(DUPLICATE_OPTION)}>
|
||||||
<ListItemIcon><Icon>content_copy</Icon></ListItemIcon>
|
<ListItemIcon><Icon>content_copy</Icon></ListItemIcon>
|
||||||
Duplicate...
|
Save As...
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
hasDeletePermission &&
|
hasStorePermission && currentSavedView != null &&
|
||||||
<Tooltip {...menuTooltipAttribs} title="Delete this saved view.">
|
<Tooltip {...menuTooltipAttribs} title="Delete this saved view.">
|
||||||
<MenuItem disabled={currentSavedView === null} onClick={() => handleDropdownOptionClick(DELETE_OPTION)}>
|
<MenuItem disabled={currentSavedView === null} onClick={() => handleDropdownOptionClick(DELETE_OPTION)}>
|
||||||
<ListItemIcon><Icon>delete</Icon></ListItemIcon>
|
<ListItemIcon><Icon>delete</Icon></ListItemIcon>
|
||||||
@ -462,15 +462,6 @@ function SavedViews({qController, metaData, tableMetaData, currentSavedView, tab
|
|||||||
let buttonBorder = colors.grayLines.main;
|
let buttonBorder = colors.grayLines.main;
|
||||||
let buttonColor = colors.gray.main;
|
let buttonColor = colors.gray.main;
|
||||||
|
|
||||||
if(loadingSavedView)
|
|
||||||
{
|
|
||||||
buttonText = "Loading...";
|
|
||||||
}
|
|
||||||
else if(currentSavedView)
|
|
||||||
{
|
|
||||||
buttonText = currentSavedView.values.get("label")
|
|
||||||
}
|
|
||||||
|
|
||||||
if(currentSavedView)
|
if(currentSavedView)
|
||||||
{
|
{
|
||||||
if (viewIsModified)
|
if (viewIsModified)
|
||||||
|
@ -99,6 +99,7 @@ const BasicAndAdvancedQueryControls = forwardRef((props: BasicAndAdvancedQueryCo
|
|||||||
const [addQuickFilterMenu, setAddQuickFilterMenu] = useState(null)
|
const [addQuickFilterMenu, setAddQuickFilterMenu] = useState(null)
|
||||||
const [addQuickFilterOpenCounter, setAddQuickFilterOpenCounter] = useState(0);
|
const [addQuickFilterOpenCounter, setAddQuickFilterOpenCounter] = useState(0);
|
||||||
const [showClearFiltersWarning, setShowClearFiltersWarning] = useState(false);
|
const [showClearFiltersWarning, setShowClearFiltersWarning] = useState(false);
|
||||||
|
const [mouseOverElement, setMouseOverElement] = useState(null as string);
|
||||||
const [, forceUpdate] = useReducer((x) => x + 1, 0);
|
const [, forceUpdate] = useReducer((x) => x + 1, 0);
|
||||||
|
|
||||||
const {accentColor} = useContext(QContext);
|
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.
|
** for a given field, set its default operator for quick-filter dropdowns.
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@ -391,11 +410,11 @@ const BasicAndAdvancedQueryControls = forwardRef((props: BasicAndAdvancedQueryCo
|
|||||||
counter++;
|
counter++;
|
||||||
|
|
||||||
return (
|
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} </span> : <span/>}
|
{counter > 1 ? <span style={{marginLeft: "0.25rem", marginRight: "0.25rem"}}>{queryFilter.booleanOperator} </span> : <span/>}
|
||||||
{FilterUtils.criteriaToHumanString(tableMetaData, criteria, true)}
|
{FilterUtils.criteriaToHumanString(tableMetaData, criteria, true)}
|
||||||
<XIcon position="forAdvancedQueryPreview" onClick={() => removeCriteriaByIndex(i)} />
|
{mouseOverElement == `queryPreview-${i}` && <span className={`advancedQueryPreviewX-${counter - 1}`}><XIcon position="forAdvancedQueryPreview" onClick={() => removeCriteriaByIndex(i)} /></span>}
|
||||||
</React.Fragment>
|
</span>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -433,7 +452,7 @@ const BasicAndAdvancedQueryControls = forwardRef((props: BasicAndAdvancedQueryCo
|
|||||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
if (queryFilter && queryFilter.criteria)
|
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
|
** make sure that all fields in the current query are on-screen as quick-filters
|
||||||
** (that is, if the query can be basic)
|
** (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)
|
if(!tableMetaData || !queryFilter)
|
||||||
{
|
{
|
||||||
@ -465,7 +484,8 @@ const BasicAndAdvancedQueryControls = forwardRef((props: BasicAndAdvancedQueryCo
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(mode == "basic")
|
const modeToUse = newMode ?? mode;
|
||||||
|
if(modeToUse == "basic")
|
||||||
{
|
{
|
||||||
for (let i = 0; i < queryFilter?.criteria?.length; i++)
|
for (let i = 0; i < queryFilter?.criteria?.length; i++)
|
||||||
{
|
{
|
||||||
@ -601,6 +621,12 @@ const BasicAndAdvancedQueryControls = forwardRef((props: BasicAndAdvancedQueryCo
|
|||||||
handleAdornmentClick={handleSetSortArrowClick}
|
handleAdornmentClick={handleSetSortArrowClick}
|
||||||
/>);
|
/>);
|
||||||
|
|
||||||
|
const filterBuilderMouseEvents =
|
||||||
|
{
|
||||||
|
onMouseOver: () => handleMouseOverElement("filterBuilderButton"),
|
||||||
|
onMouseOut: () => handleMouseOutElement()
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box pb={mode == "advanced" ? "0.25rem" : "0"}>
|
<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">
|
<Tooltip enterDelay={500} title="Build an advanced Filter" placement="top">
|
||||||
<>
|
<>
|
||||||
<Button
|
<Button
|
||||||
|
className="filterBuilderButton"
|
||||||
onClick={(e) => openFilterBuilder(e)}
|
onClick={(e) => openFilterBuilder(e)}
|
||||||
|
{... filterBuilderMouseEvents}
|
||||||
startIcon={<Icon>build</Icon>}
|
startIcon={<Icon>build</Icon>}
|
||||||
sx={{borderRadius: "0.75rem", padding: "0.5rem", pl: "1rem", fontSize: "0.875rem", fontWeight: 500, border: `1px solid ${accentColor}`, textTransform: "none"}}
|
sx={{borderRadius: "0.75rem", padding: "0.5rem", pl: "1rem", fontSize: "0.875rem", fontWeight: 500, border: `1px solid ${accentColor}`, textTransform: "none"}}
|
||||||
>
|
>
|
||||||
Filter Builder
|
Filter Builder
|
||||||
{
|
{
|
||||||
countValidCriteria(queryFilter) > 0 &&
|
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) }
|
{countValidCriteria(queryFilter) }
|
||||||
</Box>
|
</Box>
|
||||||
}
|
}
|
||||||
</Button>
|
</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>
|
</Tooltip>
|
||||||
@ -738,14 +766,15 @@ const BasicAndAdvancedQueryControls = forwardRef((props: BasicAndAdvancedQueryCo
|
|||||||
<Box whiteSpace="nowrap" display="flex" flexShrink={1} flexGrow={1} alignItems="center">
|
<Box whiteSpace="nowrap" display="flex" flexShrink={1} flexGrow={1} alignItems="center">
|
||||||
{
|
{
|
||||||
<Box
|
<Box
|
||||||
|
className="advancedQueryString"
|
||||||
display="inline-block"
|
display="inline-block"
|
||||||
borderTop={`1px solid ${borderGray}`}
|
borderTop={`1px solid ${borderGray}`}
|
||||||
borderRadius="0 0 0.75rem 0.75rem"
|
borderRadius="0 0 0.75rem 0.75rem"
|
||||||
width="100%"
|
width="100%"
|
||||||
sx={{fontSize: "1rem", background: "#FFFFFF"}}
|
sx={{fontSize: "1rem", background: "#FFFFFF"}}
|
||||||
minHeight={"2.5rem"}
|
minHeight={"2.375rem"}
|
||||||
p={"0.5rem"}
|
p={"0.5rem"}
|
||||||
pb={0} // comes from the elements inside
|
pb={"0.125rem"}
|
||||||
boxShadow={"inset 0px 0px 4px 2px #EFEFED"}
|
boxShadow={"inset 0px 0px 4px 2px #EFEFED"}
|
||||||
>
|
>
|
||||||
{queryToAdvancedString()}
|
{queryToAdvancedString()}
|
||||||
|
@ -561,6 +561,14 @@ export default function FieldListMenu({idPrefix, heading, placeholder, tableMeta
|
|||||||
const textFieldId = `field-list-dropdown-${idPrefix}-textField`;
|
const textFieldId = `field-list-dropdown-${idPrefix}-textField`;
|
||||||
let listItemPadding = isModeToggle ? "0.125rem": "0.5rem";
|
let listItemPadding = isModeToggle ? "0.125rem": "0.5rem";
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// for z-indexes, we set each table header to i+1, then the fields in that table to i (so they go behind it) //
|
||||||
|
// then we increment i by 2 for the next table (so the next header goes above the previous header) //
|
||||||
|
// this fixes a thing where, if one table's name wrapped to 2 lines, then when the next table below it would //
|
||||||
|
// come up, if it was only 1 line, then the second line from the previous one would bleed through. //
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
let zIndex = 1;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Button onClick={openMenu} {...buttonProps}>
|
<Button onClick={openMenu} {...buttonProps}>
|
||||||
@ -639,10 +647,12 @@ export default function FieldListMenu({idPrefix, heading, placeholder, tableMeta
|
|||||||
marginLeft = "-1rem";
|
marginLeft = "-1rem";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
zIndex += 2;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment key={tableWithFields.table?.name ?? "theTable"}>
|
<React.Fragment key={tableWithFields.table?.name ?? "theTable"}>
|
||||||
<>
|
<>
|
||||||
{headerContents && <ListItem sx={{position: "sticky", top: "0", backgroundColor: "#FFFFFF", zIndex: 1, padding: listItemPadding, ml: marginLeft, display: "flex", alignItems: "flex-start"}}>{headerContents}</ListItem>}
|
{headerContents && <ListItem sx={{position: "sticky", top: -1, zIndex: zIndex+1, padding: listItemPadding, ml: marginLeft, display: "flex", alignItems: "flex-start", backgroundImage: "linear-gradient(to bottom, rgba(255,255,255,1), rgba(255,255,255,1) 90%, rgba(255,255,255,0))"}}>{headerContents}</ListItem>}
|
||||||
{
|
{
|
||||||
tableWithFields.fields.map((field) =>
|
tableWithFields.fields.map((field) =>
|
||||||
{
|
{
|
||||||
@ -703,7 +713,7 @@ export default function FieldListMenu({idPrefix, heading, placeholder, tableMeta
|
|||||||
return <ListItem
|
return <ListItem
|
||||||
key={key}
|
key={key}
|
||||||
id={`field-list-dropdown-${idPrefix}-${index}`}
|
id={`field-list-dropdown-${idPrefix}-${index}`}
|
||||||
sx={{color: "#757575", p: 1, borderRadius: ".5rem", padding: listItemPadding, pl: paddingLeft, scrollMarginTop: "3rem", ...style}}
|
sx={{color: "#757575", p: 1, borderRadius: ".5rem", padding: listItemPadding, pl: paddingLeft, scrollMarginTop: "3rem", zIndex: zIndex, background: "#FFFFFF", ...style}}
|
||||||
onMouseOver={(event) => handleMouseOver(event, field, tableWithFields.table)}
|
onMouseOver={(event) => handleMouseOver(event, field, tableWithFields.table)}
|
||||||
{...onClick}
|
{...onClick}
|
||||||
>{contents}</ListItem>;
|
>{contents}</ListItem>;
|
||||||
|
@ -53,7 +53,7 @@ export enum ValueMode
|
|||||||
PVS_MULTI = "PVS_MULTI",
|
PVS_MULTI = "PVS_MULTI",
|
||||||
}
|
}
|
||||||
|
|
||||||
const getValueModeRequiredCount = (valueMode: ValueMode): number =>
|
export const getValueModeRequiredCount = (valueMode: ValueMode): number =>
|
||||||
{
|
{
|
||||||
switch (valueMode)
|
switch (valueMode)
|
||||||
{
|
{
|
||||||
|
@ -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 {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData";
|
||||||
import {QCriteriaOperator} from "@kingsrook/qqq-frontend-core/lib/model/query/QCriteriaOperator";
|
import {QCriteriaOperator} from "@kingsrook/qqq-frontend-core/lib/model/query/QCriteriaOperator";
|
||||||
import {QFilterCriteria} from "@kingsrook/qqq-frontend-core/lib/model/query/QFilterCriteria";
|
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 Autocomplete from "@mui/material/Autocomplete";
|
||||||
import Box from "@mui/material/Box";
|
import Box from "@mui/material/Box";
|
||||||
import Button from "@mui/material/Button";
|
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 Menu from "@mui/material/Menu";
|
||||||
import TextField from "@mui/material/TextField";
|
import TextField from "@mui/material/TextField";
|
||||||
import React, {SyntheticEvent, useContext, useState} from "react";
|
import React, {SyntheticEvent, useContext, useState} from "react";
|
||||||
import QContext from "QContext";
|
import QContext from "QContext";
|
||||||
import {QFilterCriteriaWithId} from "qqq/components/query/CustomFilterPanel";
|
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 FilterCriteriaRowValues from "qqq/components/query/FilterCriteriaRowValues";
|
||||||
import XIcon from "qqq/components/query/XIcon";
|
import XIcon from "qqq/components/query/XIcon";
|
||||||
import FilterUtils from "qqq/utils/qqq/FilterUtils";
|
import FilterUtils from "qqq/utils/qqq/FilterUtils";
|
||||||
@ -71,8 +69,7 @@ export const quickFilterButtonStyles = {
|
|||||||
border: "1px solid #757575",
|
border: "1px solid #757575",
|
||||||
minWidth: "3.5rem",
|
minWidth: "3.5rem",
|
||||||
minHeight: "auto",
|
minHeight: "auto",
|
||||||
padding: "0.375rem 0.625rem",
|
padding: "0.375rem 0.625rem", whiteSpace: "nowrap",
|
||||||
whiteSpace: "nowrap",
|
|
||||||
marginBottom: "0.5rem"
|
marginBottom: "0.5rem"
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -149,6 +146,7 @@ export default function QuickFilter({tableMetaData, fullFieldName, fieldMetaData
|
|||||||
|
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
const [anchorEl, setAnchorEl] = useState(null);
|
const [anchorEl, setAnchorEl] = useState(null);
|
||||||
|
const [isMouseOver, setIsMouseOver] = useState(false);
|
||||||
|
|
||||||
const [criteria, setCriteria] = useState(criteriaParamIsCriteria(criteriaParam) ? criteriaParam as QFilterCriteriaWithId : null);
|
const [criteria, setCriteria] = useState(criteriaParamIsCriteria(criteriaParam) ? criteriaParam as QFilterCriteriaWithId : null);
|
||||||
const [id, setId] = useState(criteriaParamIsCriteria(criteriaParam) ? (criteriaParam as QFilterCriteriaWithId).id : ++seedId);
|
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 [operatorSelectedValue, setOperatorSelectedValue] = useState(getOperatorSelectedValue(operatorOptions, criteria, defaultOperator));
|
||||||
const [operatorInputValue, setOperatorInputValue] = useState(operatorSelectedValue?.label);
|
const [operatorInputValue, setOperatorInputValue] = useState(operatorSelectedValue?.label);
|
||||||
|
|
||||||
const [startIconName, setStartIconName] = useState("filter_alt");
|
|
||||||
|
|
||||||
const {criteriaIsValid, criteriaStatusTooltip} = validateCriteria(criteria, operatorSelectedValue);
|
const {criteriaIsValid, criteriaStatusTooltip} = validateCriteria(criteria, operatorSelectedValue);
|
||||||
|
|
||||||
const {accentColor} = useContext(QContext);
|
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) //
|
// 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;
|
const newCriteria = criteriaParam as QFilterCriteriaWithId;
|
||||||
setCriteria(newCriteria);
|
setCriteria(newCriteria);
|
||||||
const operatorOption = operatorOptions.filter(o => o.value == newCriteria.operator)[0];
|
const operatorOption = operatorOptions.filter(o => o.value == newCriteria.operator)[0];
|
||||||
console.log(`B: setOperatorSelectedValue [${JSON.stringify(operatorOption)}]`);
|
|
||||||
setOperatorSelectedValue(operatorOption);
|
setOperatorSelectedValue(operatorOption);
|
||||||
setOperatorInputValue(operatorOption.label);
|
setOperatorInputValue(operatorOption.label);
|
||||||
}
|
}
|
||||||
@ -202,7 +215,6 @@ export default function QuickFilter({tableMetaData, fullFieldName, fieldMetaData
|
|||||||
const operatorOption = operatorOptions.filter(o => o.value == defaultOperator)[0];
|
const operatorOption = operatorOptions.filter(o => o.value == defaultOperator)[0];
|
||||||
const criteria = new QFilterCriteriaWithId(fullFieldName, operatorOption?.value, getDefaultCriteriaValue());
|
const criteria = new QFilterCriteriaWithId(fullFieldName, operatorOption?.value, getDefaultCriteriaValue());
|
||||||
criteria.id = id;
|
criteria.id = id;
|
||||||
console.log(`C: setOperatorSelectedValue [${JSON.stringify(operatorOption)}]`);
|
|
||||||
setOperatorSelectedValue(operatorOption);
|
setOperatorSelectedValue(operatorOption);
|
||||||
setOperatorInputValue(operatorOption?.label);
|
setOperatorInputValue(operatorOption?.label);
|
||||||
setCriteria(criteria);
|
setCriteria(criteria);
|
||||||
@ -216,6 +228,12 @@ export default function QuickFilter({tableMetaData, fullFieldName, fieldMetaData
|
|||||||
{
|
{
|
||||||
setIsOpen(!isOpen);
|
setIsOpen(!isOpen);
|
||||||
setAnchorEl(event.currentTarget);
|
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)
|
if (newValue)
|
||||||
{
|
{
|
||||||
console.log(`D: setOperatorSelectedValue [${JSON.stringify(newValue)}]`);
|
|
||||||
setOperatorSelectedValue(newValue);
|
setOperatorSelectedValue(newValue);
|
||||||
setOperatorInputValue(newValue.label);
|
setOperatorInputValue(newValue.label);
|
||||||
|
|
||||||
@ -244,10 +261,27 @@ export default function QuickFilter({tableMetaData, fullFieldName, fieldMetaData
|
|||||||
{
|
{
|
||||||
criteria.values = newValue.implicitValues;
|
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
|
else
|
||||||
{
|
{
|
||||||
console.log("E: setOperatorSelectedValue [null]");
|
|
||||||
setOperatorSelectedValue(null);
|
setOperatorSelectedValue(null);
|
||||||
setOperatorInputValue("");
|
setOperatorInputValue("");
|
||||||
}
|
}
|
||||||
@ -307,30 +341,9 @@ export default function QuickFilter({tableMetaData, fullFieldName, fieldMetaData
|
|||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
const newCriteria = makeNewCriteria();
|
const newCriteria = makeNewCriteria();
|
||||||
updateCriteria(newCriteria, false, true);
|
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.
|
** 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).
|
** 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);
|
const maybeNewOperatorSelectedValue = getOperatorSelectedValue(operatorOptions, criteria, defaultOperator);
|
||||||
if(JSON.stringify(maybeNewOperatorSelectedValue) !== JSON.stringify(operatorSelectedValue))
|
if(JSON.stringify(maybeNewOperatorSelectedValue) !== JSON.stringify(operatorSelectedValue))
|
||||||
{
|
{
|
||||||
console.log(`A: setOperatorSelectedValue [${JSON.stringify(maybeNewOperatorSelectedValue)}]`);
|
|
||||||
setOperatorSelectedValue(maybeNewOperatorSelectedValue)
|
setOperatorSelectedValue(maybeNewOperatorSelectedValue)
|
||||||
setOperatorInputValue(maybeNewOperatorSelectedValue?.label)
|
setOperatorInputValue(maybeNewOperatorSelectedValue?.label)
|
||||||
}
|
}
|
||||||
@ -377,11 +389,6 @@ export default function QuickFilter({tableMetaData, fullFieldName, fieldMetaData
|
|||||||
/////////////////////////
|
/////////////////////////
|
||||||
const tooComplex = criteriaParam == "tooComplex";
|
const tooComplex = criteriaParam == "tooComplex";
|
||||||
const tooltipEnterDelay = 500;
|
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 buttonAdditionalStyles: any = {};
|
||||||
let buttonContent = <span>{tableForField?.name != tableMetaData.name ? `${tableForField.label}: ` : ""}{fieldMetaData.label}</span>
|
let buttonContent = <span>{tableForField?.name != tableMetaData.name ? `${tableForField.label}: ` : ""}{fieldMetaData.label}</span>
|
||||||
@ -402,16 +409,19 @@ export default function QuickFilter({tableMetaData, fullFieldName, fieldMetaData
|
|||||||
valuesString = "";
|
valuesString = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
buttonContent = (
|
buttonContent = (<><span style={{fontWeight: 700}}>{buttonContent}:</span> <span style={{fontWeight: 400}}>{operatorSelectedValue.label} {valuesString}</span></>);
|
||||||
<Tooltip title={`${operatorSelectedValue.label} ${valuesString}`} enterDelay={tooltipEnterDelay}>
|
|
||||||
{buttonContent}
|
|
||||||
</Tooltip>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const mouseEvents =
|
||||||
|
{
|
||||||
|
onMouseOver: () => handleMouseOverElement(),
|
||||||
|
onMouseOut: () => handleMouseOutElement()
|
||||||
|
};
|
||||||
|
|
||||||
let button = fieldMetaData && <Button
|
let button = fieldMetaData && <Button
|
||||||
id={`quickFilter.${fullFieldName}`}
|
id={`quickFilter.${fullFieldName}`}
|
||||||
className={buttonClassName}
|
className={buttonClassName}
|
||||||
|
{...mouseEvents}
|
||||||
sx={{...quickFilterButtonStyles, ...buttonAdditionalStyles, mr: "0.5rem"}}
|
sx={{...quickFilterButtonStyles, ...buttonAdditionalStyles, mr: "0.5rem"}}
|
||||||
onClick={tooComplex ? noop : handleOpenMenu}
|
onClick={tooComplex ? noop : handleOpenMenu}
|
||||||
disabled={tooComplex}
|
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, //
|
// 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 //
|
// 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"}}>
|
isOpen && <Menu open={Boolean(anchorEl)} anchorEl={anchorEl} onClose={closeMenu} sx={{overflow: "visible"}}>
|
||||||
|
@ -59,7 +59,7 @@ export default function XIcon({onClick, position, shade}: XIconProps): JSX.Eleme
|
|||||||
else if(position == "forAdvancedQueryPreview")
|
else if(position == "forAdvancedQueryPreview")
|
||||||
{
|
{
|
||||||
rest = {
|
rest = {
|
||||||
top: "-0.375rem",
|
top: "-0.5rem",
|
||||||
left: "-0.75rem",
|
left: "-0.75rem",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -69,6 +69,7 @@ export class LoadingState
|
|||||||
|
|
||||||
public setLoading()
|
public setLoading()
|
||||||
{
|
{
|
||||||
|
clearTimeout(this.slowTimeout);
|
||||||
this.state = "loading";
|
this.state = "loading";
|
||||||
this.slowTimeout = setTimeout(() =>
|
this.slowTimeout = setTimeout(() =>
|
||||||
{
|
{
|
||||||
|
@ -23,7 +23,9 @@
|
|||||||
import {QFieldMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldMetaData";
|
import {QFieldMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldMetaData";
|
||||||
import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData";
|
import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData";
|
||||||
import {GridPinnedColumns} from "@mui/x-data-grid-pro";
|
import {GridPinnedColumns} from "@mui/x-data-grid-pro";
|
||||||
|
import quickSightChart from "qqq/components/widgets/misc/QuickSightChart";
|
||||||
import DataGridUtils from "qqq/utils/DataGridUtils";
|
import DataGridUtils from "qqq/utils/DataGridUtils";
|
||||||
|
import TableUtils from "qqq/utils/qqq/TableUtils";
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** member object
|
** member object
|
||||||
@ -101,6 +103,45 @@ export default class QQueryColumns
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public addColumnForNewField = (table: QTableMetaData, fieldName: string, defaultVisibilityIfInMainTable: boolean): void =>
|
||||||
|
{
|
||||||
|
const [field, tableForField] = TableUtils.getFieldAndTable(table, fieldName)
|
||||||
|
|
||||||
|
let column: Column;
|
||||||
|
if(tableForField.name == table.name)
|
||||||
|
{
|
||||||
|
column = {name: field.name, isVisible: defaultVisibilityIfInMainTable, width: DataGridUtils.getColumnWidthForField(field, table)};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
column = {name: `${tableForField.name}.${field.name}`, isVisible: false, width: DataGridUtils.getColumnWidthForField(field, null)};
|
||||||
|
}
|
||||||
|
|
||||||
|
this.columns.push(column);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public deleteColumnForOldField = (table: QTableMetaData, fieldName: string): void =>
|
||||||
|
{
|
||||||
|
for (let i = 0; i < this.columns.length; i++)
|
||||||
|
{
|
||||||
|
if(this.columns[i].name == fieldName)
|
||||||
|
{
|
||||||
|
this.columns.splice(i, 1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Couldn't find column to be deleted, for name [${fieldName}]`);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
|
@ -44,7 +44,7 @@ import LinearProgress from "@mui/material/LinearProgress";
|
|||||||
import MenuItem from "@mui/material/MenuItem";
|
import MenuItem from "@mui/material/MenuItem";
|
||||||
import Modal from "@mui/material/Modal";
|
import Modal from "@mui/material/Modal";
|
||||||
import Tooltip from "@mui/material/Tooltip";
|
import Tooltip from "@mui/material/Tooltip";
|
||||||
import {ColumnHeaderFilterIconButtonProps, DataGridPro, GridCallbackDetails, GridColDef, GridColumnHeaderParams, GridColumnHeaderSortIconProps, GridColumnMenuContainer, GridColumnMenuProps, GridColumnOrderChangeParams, GridColumnPinningMenuItems, GridColumnResizeParams, GridColumnsMenuItem, GridColumnVisibilityModel, GridDensity, GridEventListener, GridFilterMenuItem, GridPinnedColumns, gridPreferencePanelStateSelector, GridPreferencePanelsValue, GridRowId, GridRowParams, GridRowsProp, GridSelectionModel, GridSortItem, GridSortModel, GridState, GridToolbarColumnsButton, GridToolbarContainer, GridToolbarDensitySelector, GridToolbarExportContainer, HideGridColMenuItem, MuiEvent, SortGridMenuItems, useGridApiContext, useGridApiEventHandler, useGridApiRef, useGridSelector} from "@mui/x-data-grid-pro";
|
import {ColumnHeaderFilterIconButtonProps, DataGridPro, GridCallbackDetails, GridColDef, GridColumnHeaderParams, GridColumnMenuContainer, GridColumnMenuProps, GridColumnOrderChangeParams, GridColumnPinningMenuItems, GridColumnResizeParams, GridColumnsMenuItem, GridColumnVisibilityModel, GridDensity, GridEventListener, GridFilterMenuItem, GridPinnedColumns, gridPreferencePanelStateSelector, GridPreferencePanelsValue, GridRowId, GridRowParams, GridRowsProp, GridSelectionModel, GridSortItem, GridSortModel, GridState, GridToolbarColumnsButton, GridToolbarContainer, GridToolbarDensitySelector, GridToolbarExportContainer, HideGridColMenuItem, MuiEvent, SortGridMenuItems, useGridApiContext, useGridApiEventHandler, useGridApiRef, useGridSelector} from "@mui/x-data-grid-pro";
|
||||||
import {GridRowModel} from "@mui/x-data-grid/models/gridRows";
|
import {GridRowModel} from "@mui/x-data-grid/models/gridRows";
|
||||||
import FormData from "form-data";
|
import FormData from "form-data";
|
||||||
import React, {forwardRef, useContext, useEffect, useReducer, useRef, useState} from "react";
|
import React, {forwardRef, useContext, useEffect, useReducer, useRef, useState} from "react";
|
||||||
@ -515,6 +515,11 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
|||||||
{
|
{
|
||||||
let label: string = tableMetaData?.label ?? "";
|
let label: string = tableMetaData?.label ?? "";
|
||||||
|
|
||||||
|
if(currentSavedView?.values?.get("label"))
|
||||||
|
{
|
||||||
|
label += " / " + currentSavedView?.values?.get("label");
|
||||||
|
}
|
||||||
|
|
||||||
if (visibleJoinTables.size > 0)
|
if (visibleJoinTables.size > 0)
|
||||||
{
|
{
|
||||||
let joinLabels = [];
|
let joinLabels = [];
|
||||||
@ -675,7 +680,7 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
doSetCurrentSavedView(null);
|
doClearCurrentSavedView();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1539,39 +1544,41 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
const doSetCurrentSavedView = (savedViewRecord: QRecord) =>
|
const doSetCurrentSavedView = (savedViewRecord: QRecord) =>
|
||||||
{
|
{
|
||||||
|
if(savedViewRecord == null)
|
||||||
|
{
|
||||||
|
console.log("doSetCurrentView called with a null view record - calling doClearCurrentSavedView instead.");
|
||||||
|
doClearCurrentSavedView();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
setCurrentSavedView(savedViewRecord);
|
setCurrentSavedView(savedViewRecord);
|
||||||
|
|
||||||
if(savedViewRecord)
|
const viewJson = savedViewRecord.values.get("viewJson")
|
||||||
{
|
const newView = RecordQueryView.buildFromJSON(viewJson);
|
||||||
(async () =>
|
|
||||||
{
|
|
||||||
const viewJson = savedViewRecord.values.get("viewJson")
|
|
||||||
const newView = RecordQueryView.buildFromJSON(viewJson);
|
|
||||||
|
|
||||||
activateView(newView);
|
activateView(newView);
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////
|
||||||
// todo - we used to be able to set "warnings" here (i think, like, for if a field got deleted from a table... //
|
// todo can/should/does this move into the view's "identity"? //
|
||||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////
|
||||||
// setWarningAlert(models.warning);
|
localStorage.setItem(currentSavedViewLocalStorageKey, `${savedViewRecord.values.get("id")}`);
|
||||||
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////
|
|
||||||
// todo can/should/does this move into the view's "identity"? //
|
/*******************************************************************************
|
||||||
////////////////////////////////////////////////////////////////
|
** wrapper around un-setting current saved view and removing its id from local-stroage
|
||||||
localStorage.setItem(currentSavedViewLocalStorageKey, `${savedViewRecord.values.get("id")}`);
|
*******************************************************************************/
|
||||||
})()
|
const doClearCurrentSavedView = () =>
|
||||||
}
|
{
|
||||||
else
|
setCurrentSavedView(null);
|
||||||
{
|
localStorage.removeItem(currentSavedViewLocalStorageKey);
|
||||||
localStorage.removeItem(currentSavedViewLocalStorageKey);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
const buildTableDefaultView = (): RecordQueryView =>
|
const buildTableDefaultView = (tableMetaData: QTableMetaData): RecordQueryView =>
|
||||||
{
|
{
|
||||||
const newDefaultView = new RecordQueryView();
|
const newDefaultView = new RecordQueryView();
|
||||||
newDefaultView.queryFilter = new QQueryFilter([], [new QFilterOrderBy(tableMetaData.primaryKeyField, false)]);
|
newDefaultView.queryFilter = new QQueryFilter([], [new QFilterOrderBy(tableMetaData.primaryKeyField, false)]);
|
||||||
@ -1616,7 +1623,7 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
|||||||
///////////////////////////////////////////////
|
///////////////////////////////////////////////
|
||||||
// activate a new default view for the table //
|
// activate a new default view for the table //
|
||||||
///////////////////////////////////////////////
|
///////////////////////////////////////////////
|
||||||
activateView(buildTableDefaultView())
|
activateView(buildTableDefaultView(tableMetaData))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1634,6 +1641,7 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
|||||||
{
|
{
|
||||||
const jobError = processResult as QJobError;
|
const jobError = processResult as QJobError;
|
||||||
console.error("Could not retrieve saved filter: " + jobError.userFacingError);
|
console.error("Could not retrieve saved filter: " + jobError.userFacingError);
|
||||||
|
setAlertContent("There was an error loading the selected view.");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -1646,29 +1654,138 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
|||||||
// what they're like in the backend); similarly, set anything that's unset. //
|
// what they're like in the backend); similarly, set anything that's unset. //
|
||||||
//////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
const viewJson = qRecord.values.get("viewJson")
|
const viewJson = qRecord.values.get("viewJson")
|
||||||
const view = RecordQueryView.buildFromJSON(viewJson);
|
const newView = RecordQueryView.buildFromJSON(viewJson);
|
||||||
view.viewIdentity = "savedView:" + id;
|
|
||||||
|
setWarningAlert(null);
|
||||||
|
reconcileCurrentTableMetaDataWithView(newView, "loadingSavedView");
|
||||||
|
|
||||||
|
newView.viewIdentity = "savedView:" + id;
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////
|
||||||
// e.g., translate possible values from ids to objects w/ labels //
|
// e.g., translate possible values from ids to objects w/ labels //
|
||||||
///////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////
|
||||||
await FilterUtils.cleanupValuesInFilerFromQueryString(qController, tableMetaData, view.queryFilter);
|
await FilterUtils.cleanupValuesInFilerFromQueryString(qController, tableMetaData, newView.queryFilter);
|
||||||
|
|
||||||
///////////////////////////
|
///////////////////////////
|
||||||
// set columns if absent //
|
// set columns if absent //
|
||||||
///////////////////////////
|
///////////////////////////
|
||||||
if(!view.queryColumns || !view.queryColumns.columns || view.queryColumns.columns?.length == 0)
|
if(!newView.queryColumns || !newView.queryColumns.columns || newView.queryColumns.columns?.length == 0)
|
||||||
{
|
{
|
||||||
view.queryColumns = QQueryColumns.buildDefaultForTable(tableMetaData);
|
newView.queryColumns = QQueryColumns.buildDefaultForTable(tableMetaData);
|
||||||
}
|
}
|
||||||
|
|
||||||
qRecord.values.set("viewJson", JSON.stringify(view))
|
qRecord.values.set("viewJson", JSON.stringify(newView))
|
||||||
}
|
}
|
||||||
|
|
||||||
return (qRecord);
|
return (qRecord);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** after a page-load, or before activating a saved view, make sure that no
|
||||||
|
** fields are missing from its column list, and that no deleted-fields are still
|
||||||
|
** being used.
|
||||||
|
*******************************************************************************/
|
||||||
|
const reconcileCurrentTableMetaDataWithView = (view: RecordQueryView, useCase: "initialPageLoad" | "loadingSavedView") =>
|
||||||
|
{
|
||||||
|
let changedView = false;
|
||||||
|
const removedFieldNames = new Set<string>();
|
||||||
|
|
||||||
|
if (view.queryColumns?.columns?.length > 0)
|
||||||
|
{
|
||||||
|
const fieldNamesInView: { [name: string]: boolean } = {};
|
||||||
|
view.queryColumns?.columns?.forEach(column => fieldNamesInView[column.name] = true);
|
||||||
|
for (let i = 0; i < tableDefaultView?.queryColumns?.columns.length; i++)
|
||||||
|
{
|
||||||
|
const currentColumn = tableDefaultView?.queryColumns?.columns[i];
|
||||||
|
if (!fieldNamesInView[currentColumn.name])
|
||||||
|
{
|
||||||
|
console.log(`Adding a new column to this view ${currentColumn.name}`);
|
||||||
|
view.queryColumns.addColumnForNewField(tableMetaData, currentColumn.name, useCase == "initialPageLoad");
|
||||||
|
changedView = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
delete fieldNamesInView[currentColumn.name];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////
|
||||||
|
// delete, from the view, any fields no longer in the table //
|
||||||
|
//////////////////////////////////////////////////////////////
|
||||||
|
for (let fieldName in fieldNamesInView)
|
||||||
|
{
|
||||||
|
console.log(`Deleting an old column from this view ${fieldName}`);
|
||||||
|
view.queryColumns.deleteColumnForOldField(tableMetaData, fieldName);
|
||||||
|
changedView = true;
|
||||||
|
removedFieldNames.add(fieldName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////////////////////////////
|
||||||
|
// look for deleted fields as criteria //
|
||||||
|
/////////////////////////////////////////
|
||||||
|
for (let i = 0; i < view?.queryFilter?.criteria?.length; i++)
|
||||||
|
{
|
||||||
|
const fieldName = view.queryFilter.criteria[i].fieldName;
|
||||||
|
const [field, fieldTable] = TableUtils.getFieldAndTable(tableMetaData, fieldName);
|
||||||
|
if (field == null)
|
||||||
|
{
|
||||||
|
console.log(`Deleting an old criteria field from this view ${fieldName}`);
|
||||||
|
view.queryFilter.criteria.splice(i, 1);
|
||||||
|
changedView = true;
|
||||||
|
removedFieldNames.add(fieldName);
|
||||||
|
i--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/////////////////////////////////////////
|
||||||
|
// look for deleted fields as orderBys //
|
||||||
|
/////////////////////////////////////////
|
||||||
|
for (let i = 0; i < view?.queryFilter?.orderBys?.length; i++)
|
||||||
|
{
|
||||||
|
const fieldName = view.queryFilter.orderBys[i].fieldName;
|
||||||
|
const [field, fieldTable] = TableUtils.getFieldAndTable(tableMetaData, fieldName);
|
||||||
|
if (field == null)
|
||||||
|
{
|
||||||
|
console.log(`Deleting an old orderBy field from this view ${fieldName}`);
|
||||||
|
view.queryFilter.orderBys.splice(i, 1);
|
||||||
|
changedView = true;
|
||||||
|
removedFieldNames.add(fieldName);
|
||||||
|
i--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////////////////////////
|
||||||
|
// look for deleted fields as quick-filters //
|
||||||
|
//////////////////////////////////////////////
|
||||||
|
for (let i = 0; i < view?.quickFilterFieldNames?.length; i++)
|
||||||
|
{
|
||||||
|
const fieldName = view.quickFilterFieldNames[i];
|
||||||
|
const [field, fieldTable] = TableUtils.getFieldAndTable(tableMetaData, fieldName);
|
||||||
|
if (field == null)
|
||||||
|
{
|
||||||
|
console.log(`Deleting an old quikc-filter field from this view ${fieldName}`);
|
||||||
|
view.quickFilterFieldNames.splice(i, 1);
|
||||||
|
changedView = true;
|
||||||
|
removedFieldNames.add(fieldName);
|
||||||
|
i--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (changedView && useCase == "initialPageLoad")
|
||||||
|
{
|
||||||
|
activateView(view);
|
||||||
|
}
|
||||||
|
|
||||||
|
const removedFieldCount = removedFieldNames.size;
|
||||||
|
if(removedFieldCount > 0)
|
||||||
|
{
|
||||||
|
const plural = removedFieldCount > 1;
|
||||||
|
setWarningAlert(`${removedFieldCount} field${plural ? "s" : ""} that ${plural ? "were" : "was"} part of this view ${plural ? "are" : "is"} no longer in this table, and ${plural ? "were" : "was"} removed from this view (${[...removedFieldNames.values()].join(", ")}).`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** event handler for selecting 'filter' action from columns menu in advanced mode.
|
** event handler for selecting 'filter' action from columns menu in advanced mode.
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@ -1731,12 +1848,13 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
|||||||
{
|
{
|
||||||
await navigator.clipboard.writeText(data);
|
await navigator.clipboard.writeText(data);
|
||||||
setSuccessAlert(`Copied ${counter} ${qFieldMetaData.label} value${counter == 1 ? "" : "s"}.`);
|
setSuccessAlert(`Copied ${counter} ${qFieldMetaData.label} value${counter == 1 ? "" : "s"}.`);
|
||||||
|
setTimeout(() => setSuccessAlert(null), 3000);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
setSuccessAlert(`There are no ${qFieldMetaData.label} values to copy.`);
|
setWarningAlert(`There are no ${qFieldMetaData.label} values to copy.`);
|
||||||
|
setTimeout(() => setWarningAlert(null), 3000);
|
||||||
}
|
}
|
||||||
setTimeout(() => setSuccessAlert(null), 3000);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1811,7 +1929,6 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
|||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
|
||||||
<HideGridColMenuItem onClick={hideMenu} column={currentColumn!} />
|
<HideGridColMenuItem onClick={hideMenu} column={currentColumn!} />
|
||||||
<GridColumnsMenuItem onClick={hideMenu} column={currentColumn!} />
|
|
||||||
|
|
||||||
<Divider />
|
<Divider />
|
||||||
<GridColumnPinningMenuItems onClick={hideMenu} column={currentColumn!} />
|
<GridColumnPinningMenuItems onClick={hideMenu} column={currentColumn!} />
|
||||||
@ -2157,6 +2274,13 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
|||||||
setTableProcesses(ProcessUtils.getProcessesForTable(metaData, tableName)); // these are the ones to show in the dropdown
|
setTableProcesses(ProcessUtils.getProcessesForTable(metaData, tableName)); // these are the ones to show in the dropdown
|
||||||
setAllTableProcesses(ProcessUtils.getProcessesForTable(metaData, tableName, true)); // these include hidden ones (e.g., to find the bulks)
|
setAllTableProcesses(ProcessUtils.getProcessesForTable(metaData, tableName, true)); // these include hidden ones (e.g., to find the bulks)
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// now that we know the table - build a default view - initially, only used by SavedViews component, for showing if there's anything to be saved. //
|
||||||
|
// but also used when user selects new-view from the view menu //
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
const newDefaultView = buildTableDefaultView(tableMetaData);
|
||||||
|
setTableDefaultView(newDefaultView);
|
||||||
|
|
||||||
setPageState("loadedMetaData");
|
setPageState("loadedMetaData");
|
||||||
})();
|
})();
|
||||||
}
|
}
|
||||||
@ -2171,13 +2295,6 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
|||||||
|
|
||||||
(async () =>
|
(async () =>
|
||||||
{
|
{
|
||||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
// now that we know the table - build a default view - initially, only used by SavedViews component, for showing if there's anything to be saved. //
|
|
||||||
// but also used when user selects new-view from the view menu //
|
|
||||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
const newDefaultView = buildTableDefaultView();
|
|
||||||
setTableDefaultView(newDefaultView);
|
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// once we've loaded meta data, let's check the location to see if we should open a process //
|
// once we've loaded meta data, let's check the location to see if we should open a process //
|
||||||
//////////////////////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
@ -2255,7 +2372,7 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
|||||||
/////////////////////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// make sure that we clear out any currently saved view - we're no longer in such a state. //
|
// make sure that we clear out any currently saved view - we're no longer in such a state. //
|
||||||
/////////////////////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
doSetCurrentSavedView(null);
|
doClearCurrentSavedView();
|
||||||
}
|
}
|
||||||
catch(e)
|
catch(e)
|
||||||
{
|
{
|
||||||
@ -2331,68 +2448,32 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
|||||||
console.log("page state is loadedView - going to preparingGrid...");
|
console.log("page state is loadedView - going to preparingGrid...");
|
||||||
setPageState("preparingGrid");
|
setPageState("preparingGrid");
|
||||||
|
|
||||||
(async () =>
|
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// check if any new columns have been added to the table since last time this view was activated... //
|
||||||
|
// or if anything in the view is no longer in the table //
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
reconcileCurrentTableMetaDataWithView(view, "initialPageLoad");
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// this ref may not be defined on the initial render, so, make this call in a timeout //
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
setTimeout(() =>
|
||||||
{
|
{
|
||||||
const visibleJoinTables = getVisibleJoinTables();
|
// @ts-ignore
|
||||||
|
basicAndAdvancedQueryControlsRef?.current?.ensureAllFilterCriteriaAreActiveQuickFilters(view.queryFilter, "defaultFilterLoaded")
|
||||||
|
});
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
console.log("finished preparing grid, going to page state ready");
|
||||||
// todo - we used to be able to set "warnings" here (i think, like, for if a field got deleted from a table... //
|
setPageState("ready");
|
||||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
// setWarningAlert(models.warning);
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////
|
||||||
// this ref may not be defined on the initial render, so, make this call in a timeout //
|
// if we need a variant, show that prompt //
|
||||||
////////////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////
|
||||||
setTimeout(() =>
|
if (tableMetaData?.usesVariants && !tableVariant)
|
||||||
{
|
{
|
||||||
// @ts-ignore
|
promptForTableVariantSelection();
|
||||||
basicAndAdvancedQueryControlsRef?.current?.ensureAllFilterCriteriaAreActiveQuickFilters(view.queryFilter, "defaultFilterLoaded")
|
}
|
||||||
});
|
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
// make sure that any if any sort columns are from a join table, that the join table is visible //
|
|
||||||
// todo - figure out what this is, see if still needed, etc...
|
|
||||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
/*
|
|
||||||
let resetColumnSortModel = false;
|
|
||||||
for (let i = 0; i < columnSortModel.length; i++)
|
|
||||||
{
|
|
||||||
const gridSortItem = columnSortModel[i];
|
|
||||||
if (gridSortItem.field.indexOf(".") > -1)
|
|
||||||
{
|
|
||||||
const tableName = gridSortItem.field.split(".")[0];
|
|
||||||
if (!visibleJoinTables?.has(tableName))
|
|
||||||
{
|
|
||||||
columnSortModel.splice(i, 1);
|
|
||||||
setColumnSortModel(columnSortModel);
|
|
||||||
// todo - need to setQueryFilter?
|
|
||||||
resetColumnSortModel = true;
|
|
||||||
i--;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (resetColumnSortModel && latestQueryId > 0)
|
|
||||||
{
|
|
||||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
// let the next render (since columnSortModel is watched below) build the filter, using the new columnSort //
|
|
||||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
console.log("finished preparing grid, going to page state ready");
|
|
||||||
setPageState("ready");
|
|
||||||
|
|
||||||
////////////////////////////////////////////
|
|
||||||
// if we need a variant, show that prompt //
|
|
||||||
////////////////////////////////////////////
|
|
||||||
if (tableMetaData?.usesVariants && !tableVariant)
|
|
||||||
{
|
|
||||||
promptForTableVariantSelection();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
return (getLoadingScreen());
|
return (getLoadingScreen());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2584,13 +2665,13 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
|||||||
<BaseLayout>
|
<BaseLayout>
|
||||||
<Box display="flex" justifyContent="space-between">
|
<Box display="flex" justifyContent="space-between">
|
||||||
<Box>
|
<Box>
|
||||||
<Typography textTransform="capitalize" variant="h3" noWrap>
|
<Typography textTransform="capitalize" variant="h3">
|
||||||
{pageLoadingState.isLoading() && ""}
|
{pageLoadingState.isLoading() && ""}
|
||||||
{pageLoadingState.isLoadingSlow() && "Loading..."}
|
{pageLoadingState.isLoadingSlow() && "Loading..."}
|
||||||
{pageLoadingState.isNotLoading() && getPageHeader(tableMetaData, visibleJoinTables, tableVariant)}
|
{pageLoadingState.isNotLoading() && getPageHeader(tableMetaData, visibleJoinTables, tableVariant)}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
<Box>
|
<Box whiteSpace="nowrap">
|
||||||
<GotoRecordButton metaData={metaData} tableMetaData={tableMetaData} />
|
<GotoRecordButton metaData={metaData} tableMetaData={tableMetaData} />
|
||||||
<Box display="inline-block" width="150px">
|
<Box display="inline-block" width="150px">
|
||||||
{
|
{
|
||||||
@ -2622,37 +2703,31 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
|||||||
</iframe>
|
</iframe>
|
||||||
*/}
|
*/}
|
||||||
<Box mb={3}>
|
<Box mb={3}>
|
||||||
{alertContent ? (
|
{
|
||||||
<Box mb={3}>
|
alertContent ? (
|
||||||
<Alert
|
<Collapse in={Boolean(alertContent)}>
|
||||||
severity="error"
|
<Alert severity="error" sx={{mt: 1.5, mb: 0.5}} onClose={() => setAlertContent(null)}>{alertContent}</Alert>
|
||||||
onClose={() =>
|
</Collapse>
|
||||||
{
|
) : null
|
||||||
setAlertContent(null);
|
}
|
||||||
}}
|
|
||||||
>
|
|
||||||
{alertContent}
|
|
||||||
</Alert>
|
|
||||||
</Box>
|
|
||||||
) : (
|
|
||||||
""
|
|
||||||
)}
|
|
||||||
{
|
{
|
||||||
(tableLabel && showSuccessfullyDeletedAlert) ? (
|
(tableLabel && showSuccessfullyDeletedAlert) ? (
|
||||||
<Alert color="success" sx={{mb: 3}} onClose={() => setShowSuccessfullyDeletedAlert(false)}>{`${tableLabel} successfully deleted`}</Alert>
|
<Collapse in={Boolean(showSuccessfullyDeletedAlert)}>
|
||||||
|
<Alert color="success" sx={{mt: 1.5, mb: 0.5}} onClose={() => setShowSuccessfullyDeletedAlert(false)}>{`${tableLabel} successfully deleted`}</Alert>
|
||||||
|
</Collapse>
|
||||||
) : null
|
) : null
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
(successAlert) ? (
|
(successAlert) ? (
|
||||||
<Collapse in={Boolean(successAlert)}>
|
<Collapse in={Boolean(successAlert)}>
|
||||||
<Alert color="success" sx={{mb: 3}} onClose={() => setSuccessAlert(null)}>{successAlert}</Alert>
|
<Alert color="success" sx={{mt: 1.5, mb: 0.5}} onClose={() => setSuccessAlert(null)}>{successAlert}</Alert>
|
||||||
</Collapse>
|
</Collapse>
|
||||||
) : null
|
) : null
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
(warningAlert) ? (
|
(warningAlert) ? (
|
||||||
<Collapse in={Boolean(warningAlert)}>
|
<Collapse in={Boolean(warningAlert)}>
|
||||||
<Alert color="warning" icon={<Icon>warning</Icon>} sx={{mb: 3}} onClose={() => setWarningAlert(null)}>{warningAlert}</Alert>
|
<Alert color="warning" icon={<Icon>warning</Icon>} sx={{mt: 1.5, mb: 0.5}} onClose={() => setWarningAlert(null)}>{warningAlert}</Alert>
|
||||||
</Collapse>
|
</Collapse>
|
||||||
) : null
|
) : null
|
||||||
}
|
}
|
||||||
@ -2700,11 +2775,8 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
localeText={{
|
localeText={{
|
||||||
toolbarFilters: "Filter", // label on the filters button. we prefer singular (1 filter has many "conditions" in it).
|
columnMenuSortAsc: "Sort ascending",
|
||||||
toolbarFiltersLabel: "", // setting these 3 to "" turns off the "Show Filters" and "Hide Filters" tooltip (which can get in the way of the actual filters panel)
|
columnMenuSortDesc: "Sort descending",
|
||||||
toolbarFiltersTooltipShow: "",
|
|
||||||
toolbarFiltersTooltipHide: "",
|
|
||||||
toolbarFiltersTooltipActive: count => count !== 1 ? `${count} conditions` : `${count} condition`
|
|
||||||
}}
|
}}
|
||||||
pinnedColumns={pinnedColumns}
|
pinnedColumns={pinnedColumns}
|
||||||
onPinnedColumnsChange={handlePinnedColumnsChange}
|
onPinnedColumnsChange={handlePinnedColumnsChange}
|
||||||
|
@ -63,6 +63,11 @@ public class QSeleniumLib
|
|||||||
|
|
||||||
private boolean autoHighlight = false;
|
private boolean autoHighlight = false;
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// useful to use on a WebElement, in a call like: .findElement(QSeleniumLib.PARENT) //
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
public static final By PARENT = By.xpath("./..");
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
@ -245,6 +250,18 @@ public class QSeleniumLib
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public void moveMouseCursorToElement(WebElement element)
|
||||||
|
{
|
||||||
|
Actions actions = new Actions(driver);
|
||||||
|
actions.moveToElement(element);
|
||||||
|
actions.perform();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
|
@ -57,6 +57,29 @@ public class QueryScreenLib
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public void clickAdvancedFilterClearIcon()
|
||||||
|
{
|
||||||
|
qSeleniumLib.moveMouseCursorToElement(qSeleniumLib.waitForSelector(".filterBuilderButton"));
|
||||||
|
qSeleniumLib.waitForSelector(".filterBuilderXIcon BUTTON").click();
|
||||||
|
qSeleniumLib.waitForSelectorContaining("BUTTON", "Yes").click();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public void clickQuickFilterClearIcon(String fieldName)
|
||||||
|
{
|
||||||
|
qSeleniumLib.moveMouseCursorToElement(qSeleniumLib.waitForSelector("#quickFilter\\." + fieldName));
|
||||||
|
qSeleniumLib.waitForSelector("#quickFilter\\." + fieldName + "+span button").click();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@ -97,6 +120,16 @@ public class QueryScreenLib
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public void assertQuickFilterButtonDoesNotIndicateActiveFilter(String fieldName)
|
||||||
|
{
|
||||||
|
qSeleniumLib.waitForSelectorToNotExist("#quickFilter\\." + fieldName + ".filterActive");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@ -132,7 +165,26 @@ public class QueryScreenLib
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public void addQueryFilterInput(QSeleniumLib qSeleniumLib, int index, String fieldLabel, String operator, String value, String booleanOperator)
|
public void assertSavedViewNameOnScreen(String savedViewName)
|
||||||
|
{
|
||||||
|
qSeleniumLib.waitForSelectorContaining("H3", savedViewName);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public WebElement waitForDataGridCellContaining(String containingText)
|
||||||
|
{
|
||||||
|
return qSeleniumLib.waitForSelectorContaining("DIV.MuiDataGrid-cell", containingText);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public void addAdvancedQueryFilterInput(QSeleniumLib qSeleniumLib, int index, String fieldLabel, String operator, String value, String booleanOperator)
|
||||||
{
|
{
|
||||||
if(index > 0)
|
if(index > 0)
|
||||||
{
|
{
|
||||||
|
@ -26,6 +26,7 @@ import java.io.File;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import com.kingsrook.qqq.frontend.materialdashboard.selenium.lib.QBaseSeleniumTest;
|
import com.kingsrook.qqq.frontend.materialdashboard.selenium.lib.QBaseSeleniumTest;
|
||||||
|
import com.kingsrook.qqq.frontend.materialdashboard.selenium.lib.QSeleniumLib;
|
||||||
import com.kingsrook.qqq.frontend.materialdashboard.selenium.lib.javalin.QSeleniumJavalin;
|
import com.kingsrook.qqq.frontend.materialdashboard.selenium.lib.javalin.QSeleniumJavalin;
|
||||||
import org.apache.commons.io.FileUtils;
|
import org.apache.commons.io.FileUtils;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
@ -89,7 +90,7 @@ public class DashboardTableWidgetExportTest extends QBaseSeleniumTest
|
|||||||
// click the export button //
|
// click the export button //
|
||||||
/////////////////////////////
|
/////////////////////////////
|
||||||
qSeleniumLib.waitForSelector("#SampleTableWidget h6")
|
qSeleniumLib.waitForSelector("#SampleTableWidget h6")
|
||||||
.findElement(By.xpath("./.."))
|
.findElement(QSeleniumLib.PARENT)
|
||||||
.findElement(By.cssSelector("button"))
|
.findElement(By.cssSelector("button"))
|
||||||
.click();
|
.click();
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.kingsrook.qqq.frontend.materialdashboard.selenium.tests;
|
package com.kingsrook.qqq.frontend.materialdashboard.selenium.tests.query;
|
||||||
|
|
||||||
|
|
||||||
import java.net.URLEncoder;
|
import java.net.URLEncoder;
|
||||||
@ -98,6 +98,16 @@ public class QueryScreenFilterInUrlAdvancedModeTest extends QBaseSeleniumTest
|
|||||||
qSeleniumLib.waitForSelector("input[value=\"1701\"]");
|
qSeleniumLib.waitForSelector("input[value=\"1701\"]");
|
||||||
qSeleniumLib.waitForSelector("input[value=\"74656\"]");
|
qSeleniumLib.waitForSelector("input[value=\"74656\"]");
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// click the x to remove a condition from the filter (in the on-screen preview) //
|
||||||
|
// reload the page first, so filter-panel won't be up (clicking backdrop doesn't seem to be closing it like it should...) //
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
qSeleniumLib.gotoAndWaitForBreadcrumbHeaderToContain("/peopleApp/greetingsApp/person?filter=" + URLEncoder.encode(filterJSON, StandardCharsets.UTF_8), "Person");
|
||||||
|
qSeleniumLib.highlightElement(qSeleniumLib.waitForSelectorContaining(".advancedQueryString DIV DIV", "1701"));
|
||||||
|
qSeleniumLib.moveMouseCursorToElement(qSeleniumLib.waitForSelectorContaining(".advancedQueryString DIV DIV", "1701"));
|
||||||
|
qSeleniumLib.waitForSelector(".advancedQueryPreviewX-0 button").click();
|
||||||
|
queryScreenLib.assertNoFilterButtonBadge(1);
|
||||||
|
|
||||||
//////////////////////////////////////
|
//////////////////////////////////////
|
||||||
// an IN for a possible-value field //
|
// an IN for a possible-value field //
|
||||||
//////////////////////////////////////
|
//////////////////////////////////////
|
||||||
@ -162,8 +172,7 @@ public class QueryScreenFilterInUrlAdvancedModeTest extends QBaseSeleniumTest
|
|||||||
////////////////
|
////////////////
|
||||||
// remove one //
|
// remove one //
|
||||||
////////////////
|
////////////////
|
||||||
qSeleniumLib.tryMultiple(3, () -> qSeleniumLib.waitForSelector(".filterBuilderXIcon BUTTON").click());
|
queryScreenLib.clickAdvancedFilterClearIcon();
|
||||||
qSeleniumLib.waitForSelectorContaining("BUTTON", "Yes").click();
|
|
||||||
queryScreenLib.assertNoFilterButtonBadge(1);
|
queryScreenLib.assertNoFilterButtonBadge(1);
|
||||||
|
|
||||||
// qSeleniumLib.waitForever();
|
// qSeleniumLib.waitForever();
|
@ -19,7 +19,7 @@
|
|||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.kingsrook.qqq.frontend.materialdashboard.selenium.tests;
|
package com.kingsrook.qqq.frontend.materialdashboard.selenium.tests.query;
|
||||||
|
|
||||||
|
|
||||||
import java.net.URLEncoder;
|
import java.net.URLEncoder;
|
||||||
@ -147,11 +147,15 @@ public class QueryScreenFilterInUrlBasicModeTest extends QBaseSeleniumTest
|
|||||||
qSeleniumLib.waitForSelector("input[value=\"starts with\"]");
|
qSeleniumLib.waitForSelector("input[value=\"starts with\"]");
|
||||||
qSeleniumLib.waitForSelector("input[value=\"Dar\"]");
|
qSeleniumLib.waitForSelector("input[value=\"Dar\"]");
|
||||||
|
|
||||||
////////////////
|
////////////////////////////////
|
||||||
// remove one //
|
// remove one, then the other //
|
||||||
////////////////
|
////////////////////////////////
|
||||||
// todo! qSeleniumLib.waitForSelectorContaining(".MuiIcon-root", "close").click();
|
qSeleniumLib.clickBackdrop();
|
||||||
// todo! assertQuickFilterButtonBadge(1);
|
queryScreenLib.clickQuickFilterClearIcon("createDate");
|
||||||
|
queryScreenLib.assertQuickFilterButtonIndicatesActiveFilter("firstName");
|
||||||
|
queryScreenLib.assertQuickFilterButtonDoesNotIndicateActiveFilter("createDate");
|
||||||
|
queryScreenLib.clickQuickFilterClearIcon("firstName");
|
||||||
|
queryScreenLib.assertQuickFilterButtonDoesNotIndicateActiveFilter("firstName");
|
||||||
|
|
||||||
// qSeleniumLib.waitForever();
|
// qSeleniumLib.waitForever();
|
||||||
}
|
}
|
@ -19,7 +19,7 @@
|
|||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.kingsrook.qqq.frontend.materialdashboard.selenium.tests;
|
package com.kingsrook.qqq.frontend.materialdashboard.selenium.tests.query;
|
||||||
|
|
||||||
|
|
||||||
import com.kingsrook.qqq.frontend.materialdashboard.selenium.lib.QBaseSeleniumTest;
|
import com.kingsrook.qqq.frontend.materialdashboard.selenium.lib.QBaseSeleniumTest;
|
||||||
@ -70,7 +70,7 @@ public class QueryScreenTest extends QBaseSeleniumTest
|
|||||||
// open the filter window, enter a value, wait for query to re-run //
|
// open the filter window, enter a value, wait for query to re-run //
|
||||||
/////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////
|
||||||
qSeleniumJavalin.beginCapture();
|
qSeleniumJavalin.beginCapture();
|
||||||
queryScreenLib.addQueryFilterInput(qSeleniumLib, 0, "Id", "equals", "1", null);
|
queryScreenLib.addAdvancedQueryFilterInput(qSeleniumLib, 0, "Id", "equals", "1", null);
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////
|
||||||
// assert that query & count both have the expected filter value //
|
// assert that query & count both have the expected filter value //
|
||||||
@ -86,14 +86,13 @@ public class QueryScreenTest extends QBaseSeleniumTest
|
|||||||
///////////////////////////////////////
|
///////////////////////////////////////
|
||||||
qSeleniumLib.waitForSeconds(1); // todo grr.
|
qSeleniumLib.waitForSeconds(1); // todo grr.
|
||||||
qSeleniumLib.waitForSelector(QQQMaterialDashboardSelectors.BREADCRUMB_HEADER).click();
|
qSeleniumLib.waitForSelector(QQQMaterialDashboardSelectors.BREADCRUMB_HEADER).click();
|
||||||
qSeleniumLib.waitForSelectorContaining(".filterBuilderCountBadge", "1");
|
queryScreenLib.assertFilterButtonBadge(1);
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////
|
||||||
// click the 'x' clear icon, then yes, then expect another query //
|
// click the 'x' clear icon, then yes, then expect another query //
|
||||||
///////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////
|
||||||
qSeleniumJavalin.beginCapture();
|
qSeleniumJavalin.beginCapture();
|
||||||
qSeleniumLib.tryMultiple(3, () -> qSeleniumLib.waitForSelector(".filterBuilderXIcon BUTTON").click());
|
queryScreenLib.clickAdvancedFilterClearIcon();
|
||||||
qSeleniumLib.waitForSelectorContaining("BUTTON", "Yes").click();
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////
|
||||||
// assert that query & count both no longer have the filter value //
|
// assert that query & count both no longer have the filter value //
|
||||||
@ -121,8 +120,8 @@ public class QueryScreenTest extends QBaseSeleniumTest
|
|||||||
queryScreenLib.clickFilterButton();
|
queryScreenLib.clickFilterButton();
|
||||||
|
|
||||||
qSeleniumJavalin.beginCapture();
|
qSeleniumJavalin.beginCapture();
|
||||||
queryScreenLib.addQueryFilterInput(qSeleniumLib, 0, "First Name", "contains", "Dar", "Or");
|
queryScreenLib.addAdvancedQueryFilterInput(qSeleniumLib, 0, "First Name", "contains", "Dar", "Or");
|
||||||
queryScreenLib.addQueryFilterInput(qSeleniumLib, 1, "First Name", "contains", "Jam", "Or");
|
queryScreenLib.addAdvancedQueryFilterInput(qSeleniumLib, 1, "First Name", "contains", "Jam", "Or");
|
||||||
|
|
||||||
String expectedFilterContents0 = """
|
String expectedFilterContents0 = """
|
||||||
{"fieldName":"firstName","operator":"CONTAINS","values":["Dar"]}""";
|
{"fieldName":"firstName","operator":"CONTAINS","values":["Dar"]}""";
|
@ -19,7 +19,7 @@
|
|||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.kingsrook.qqq.frontend.materialdashboard.selenium.tests;
|
package com.kingsrook.qqq.frontend.materialdashboard.selenium.tests.query;
|
||||||
|
|
||||||
|
|
||||||
import java.net.URLEncoder;
|
import java.net.URLEncoder;
|
||||||
@ -90,13 +90,13 @@ public class SavedViewsTest extends QBaseSeleniumTest
|
|||||||
/////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////
|
||||||
qSeleniumLib.waitForSelectorContaining("LI", "Some People").click();
|
qSeleniumLib.waitForSelectorContaining("LI", "Some People").click();
|
||||||
qSeleniumLib.waitForCondition("Current URL should have view id", () -> driver.getCurrentUrl().endsWith("/person/savedView/2"));
|
qSeleniumLib.waitForCondition("Current URL should have view id", () -> driver.getCurrentUrl().endsWith("/person/savedView/2"));
|
||||||
qSeleniumLib.waitForSelectorContaining("BUTTON", "Some People");
|
queryScreenLib.assertSavedViewNameOnScreen("Some People");
|
||||||
|
|
||||||
//////////////////////////////
|
//////////////////////////////
|
||||||
// click into a view screen //
|
// click into a view screen //
|
||||||
//////////////////////////////
|
//////////////////////////////
|
||||||
qSeleniumLib.waitForSeconds(1); // wait for the filters menu to fully disappear? if this doesn't work, try a different word to look for...
|
qSeleniumLib.waitForSeconds(1); // wait for the filters menu to fully disappear? if this doesn't work, try a different word to look for...
|
||||||
qSeleniumLib.waitForSelectorContaining("DIV.MuiDataGrid-cell", "jdoe@kingsrook.com").click();
|
queryScreenLib.waitForDataGridCellContaining("jdoe@kingsrook.com").click();
|
||||||
qSeleniumLib.waitForSelectorContaining("H5", "Viewing Person: John Doe");
|
qSeleniumLib.waitForSelectorContaining("H5", "Viewing Person: John Doe");
|
||||||
|
|
||||||
///////////////////////////////////////////////////
|
///////////////////////////////////////////////////
|
||||||
@ -105,7 +105,7 @@ public class SavedViewsTest extends QBaseSeleniumTest
|
|||||||
///////////////////////////////////////////////////
|
///////////////////////////////////////////////////
|
||||||
qSeleniumLib.waitForSelectorContaining("A", "Person").click();
|
qSeleniumLib.waitForSelectorContaining("A", "Person").click();
|
||||||
qSeleniumLib.waitForCondition("Current URL should have View id", () -> driver.getCurrentUrl().endsWith("/person/savedView/2"));
|
qSeleniumLib.waitForCondition("Current URL should have View id", () -> driver.getCurrentUrl().endsWith("/person/savedView/2"));
|
||||||
qSeleniumLib.waitForSelectorContaining("BUTTON", "Some People");
|
queryScreenLib.assertSavedViewNameOnScreen("Some People");
|
||||||
queryScreenLib.assertQuickFilterButtonIndicatesActiveFilter("firstName");
|
queryScreenLib.assertQuickFilterButtonIndicatesActiveFilter("firstName");
|
||||||
|
|
||||||
//////////////////////
|
//////////////////////
|
||||||
@ -123,7 +123,7 @@ public class SavedViewsTest extends QBaseSeleniumTest
|
|||||||
//////////////////////////////
|
//////////////////////////////
|
||||||
// click into a view screen //
|
// click into a view screen //
|
||||||
//////////////////////////////
|
//////////////////////////////
|
||||||
qSeleniumLib.waitForSelectorContaining("DIV.MuiDataGrid-cell", "jdoe@kingsrook.com").click();
|
queryScreenLib.waitForDataGridCellContaining("jdoe@kingsrook.com").click();
|
||||||
qSeleniumLib.waitForSelectorContaining("H5", "Viewing Person: John Doe");
|
qSeleniumLib.waitForSelectorContaining("H5", "Viewing Person: John Doe");
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
@ -133,7 +133,7 @@ public class SavedViewsTest extends QBaseSeleniumTest
|
|||||||
qSeleniumJavalin.beginCapture();
|
qSeleniumJavalin.beginCapture();
|
||||||
qSeleniumLib.waitForSelectorContaining("A", "Person").click();
|
qSeleniumLib.waitForSelectorContaining("A", "Person").click();
|
||||||
qSeleniumLib.waitForCondition("Current URL should have filter id", () -> driver.getCurrentUrl().endsWith("/person/savedView/2"));
|
qSeleniumLib.waitForCondition("Current URL should have filter id", () -> driver.getCurrentUrl().endsWith("/person/savedView/2"));
|
||||||
qSeleniumLib.waitForSelectorContaining("BUTTON", "Some People");
|
queryScreenLib.assertSavedViewNameOnScreen("Some People");
|
||||||
qSeleniumLib.waitForSelectorContaining("DIV", "Unsaved Changes");
|
qSeleniumLib.waitForSelectorContaining("DIV", "Unsaved Changes");
|
||||||
CapturedContext capturedContext = qSeleniumJavalin.waitForCapturedPath("/data/person/query");
|
CapturedContext capturedContext = qSeleniumJavalin.waitForCapturedPath("/data/person/query");
|
||||||
assertTrue(capturedContext.getBody().contains("Kelkhoff"));
|
assertTrue(capturedContext.getBody().contains("Kelkhoff"));
|
||||||
@ -143,16 +143,7 @@ public class SavedViewsTest extends QBaseSeleniumTest
|
|||||||
// navigate to the table with a View in the URL //
|
// navigate to the table with a View in the URL //
|
||||||
//////////////////////////////////////////////////
|
//////////////////////////////////////////////////
|
||||||
String filter = """
|
String filter = """
|
||||||
{
|
{"criteria":[{"fieldName":"id", "operator":"LESS_THAN", "values":[10]}]}
|
||||||
"criteria":
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"fieldName": "id",
|
|
||||||
"operator": "LESS_THAN",
|
|
||||||
"values": [10]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
""".replace('\n', ' ').replaceAll(" ", "");
|
""".replace('\n', ' ').replaceAll(" ", "");
|
||||||
qSeleniumLib.gotoAndWaitForBreadcrumbHeaderToContain("/peopleApp/greetingsApp/person?filter=" + URLEncoder.encode(filter, StandardCharsets.UTF_8), "Person");
|
qSeleniumLib.gotoAndWaitForBreadcrumbHeaderToContain("/peopleApp/greetingsApp/person?filter=" + URLEncoder.encode(filter, StandardCharsets.UTF_8), "Person");
|
||||||
qSeleniumLib.waitForSelectorContaining("BUTTON", "Save View As");
|
qSeleniumLib.waitForSelectorContaining("BUTTON", "Save View As");
|
||||||
@ -160,7 +151,7 @@ public class SavedViewsTest extends QBaseSeleniumTest
|
|||||||
//////////////////////////////
|
//////////////////////////////
|
||||||
// click into a view screen //
|
// click into a view screen //
|
||||||
//////////////////////////////
|
//////////////////////////////
|
||||||
qSeleniumLib.waitForSelectorContaining("DIV.MuiDataGrid-cell", "jdoe@kingsrook.com").click();
|
queryScreenLib.waitForDataGridCellContaining("jdoe@kingsrook.com").click();
|
||||||
qSeleniumLib.waitForSelectorContaining("H5", "Viewing Person: John Doe");
|
qSeleniumLib.waitForSelectorContaining("H5", "Viewing Person: John Doe");
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////////
|
Reference in New Issue
Block a user