mirror of
https://github.com/Kingsrook/qqq-frontend-material-dashboard.git
synced 2025-07-18 13:20:43 +00:00
SPRINT-20: checkpoint commit of saved filter frontends
This commit is contained in:
@ -28,13 +28,17 @@ import MDButton from "qqq/components/legacy/MDButton";
|
||||
|
||||
// eslint-disable import/prefer-default-export
|
||||
|
||||
const standardWidth = "150px";
|
||||
export const standardWidth = "150px";
|
||||
|
||||
export function QCreateNewButton(): JSX.Element
|
||||
interface QCreateNewButtonProps
|
||||
{
|
||||
tablePath: string;
|
||||
}
|
||||
export function QCreateNewButton({tablePath}: QCreateNewButtonProps): JSX.Element
|
||||
{
|
||||
return (
|
||||
<Box ml={3} mr={2} width={standardWidth}>
|
||||
<Link to="create">
|
||||
<Link to={`${tablePath}/create`}>
|
||||
<MDButton variant="gradient" color="info" fullWidth startIcon={<Icon>add</Icon>}>
|
||||
Create New
|
||||
</MDButton>
|
||||
@ -116,6 +120,23 @@ export function QActionsMenuButton({isOpen, onClickHandler}: QActionsMenuButtonP
|
||||
);
|
||||
}
|
||||
|
||||
export function QSavedFiltersMenuButton({isOpen, onClickHandler}: QActionsMenuButtonProps): JSX.Element
|
||||
{
|
||||
return (
|
||||
<Box width={standardWidth} ml={1}>
|
||||
<MDButton
|
||||
variant={isOpen ? "contained" : "outlined"}
|
||||
color="dark"
|
||||
onClick={onClickHandler}
|
||||
fullWidth
|
||||
>
|
||||
saved filters
|
||||
<Icon>keyboard_arrow_down</Icon>
|
||||
</MDButton>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
interface QCancelButtonProps
|
||||
{
|
||||
onClickHandler: any;
|
||||
|
@ -67,6 +67,11 @@ function QBreadcrumbs({icon, title, route, light}: Props): JSX.Element
|
||||
let accumulatedPath = "";
|
||||
for (let i = 0; i < routes.length; i++)
|
||||
{
|
||||
if(routes[i] === "savedFilter")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
accumulatedPath = `${accumulatedPath}/${routes[i]}`;
|
||||
fullRoutes.push(accumulatedPath);
|
||||
pageTitle = `${routeToLabel(routes[i])} | ${pageTitle}`;
|
||||
|
526
src/qqq/components/misc/SavedFilters.tsx
Normal file
526
src/qqq/components/misc/SavedFilters.tsx
Normal file
@ -0,0 +1,526 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2022. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {QController} from "@kingsrook/qqq-frontend-core/lib/controllers/QController";
|
||||
import {QInstance} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QInstance";
|
||||
import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData";
|
||||
import {QJobComplete} from "@kingsrook/qqq-frontend-core/lib/model/processes/QJobComplete";
|
||||
import {QJobError} from "@kingsrook/qqq-frontend-core/lib/model/processes/QJobError";
|
||||
import {QRecord} from "@kingsrook/qqq-frontend-core/lib/model/QRecord";
|
||||
import {KeyboardArrowDown} from "@mui/icons-material";
|
||||
import {Alert, ClickAwayListener, Grow, MenuList, Paper, Popper} from "@mui/material";
|
||||
import Box from "@mui/material/Box";
|
||||
import Button from "@mui/material/Button";
|
||||
import ButtonGroup from "@mui/material/ButtonGroup";
|
||||
import Dialog from "@mui/material/Dialog";
|
||||
import DialogActions from "@mui/material/DialogActions";
|
||||
import DialogContent from "@mui/material/DialogContent";
|
||||
import DialogTitle from "@mui/material/DialogTitle";
|
||||
import Menu from "@mui/material/Menu";
|
||||
import MenuItem from "@mui/material/MenuItem";
|
||||
import TextField from "@mui/material/TextField";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import {GridFilterModel, GridSortItem} from "@mui/x-data-grid-pro";
|
||||
import FormData from "form-data";
|
||||
import React, {useEffect, useRef, useState} from "react";
|
||||
import {useLocation, useNavigate} from "react-router-dom";
|
||||
import {QCancelButton, QDeleteButton, QSaveButton, QSavedFiltersMenuButton} from "qqq/components/buttons/DefaultButtons";
|
||||
import FilterUtils from "qqq/utils/qqq/FilterUtils";
|
||||
|
||||
interface Props
|
||||
{
|
||||
qController: QController;
|
||||
metaData: QInstance;
|
||||
tableMetaData: QTableMetaData;
|
||||
currentSavedFilter: QRecord;
|
||||
filterModel?: GridFilterModel;
|
||||
columnSortModel?: GridSortItem[];
|
||||
filterOnChangeCallback?: (selectedSavedFilterId: number) => void;
|
||||
}
|
||||
|
||||
function SavedFilters({qController, metaData, tableMetaData, currentSavedFilter, filterModel, columnSortModel, filterOnChangeCallback}: Props): JSX.Element
|
||||
{
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [savedFilters, setSavedFilters] = useState([] as QRecord[]);
|
||||
const [savedFiltersMenu, setSavedFiltersMenu] = useState(null);
|
||||
const [savedFiltersHaveLoaded, setSavedFiltersHaveLoaded] = useState(false);
|
||||
const [filterIsModified, setFilterIsModified] = useState(false);
|
||||
|
||||
const [saveFilterPopupOpen, setSaveFilterPopupOpen] = useState(false);
|
||||
const [isSaveFilterAs, setIsSaveFilterAs] = useState(false);
|
||||
const [isRenameFilter, setIsRenameFilter] = useState(false);
|
||||
const [isDeleteFilter, setIsDeleteFilter] = useState(false);
|
||||
const [savedFilterNameInputValue, setSavedFilterNameInputValue] = useState(null as string);
|
||||
const [popupAlertContent, setPopupAlertContent] = useState("");
|
||||
|
||||
const anchorRef = useRef<HTMLDivElement>(null);
|
||||
const location = useLocation();
|
||||
const [saveOptionsOpen, setSaveOptionsOpen] = useState(false);
|
||||
|
||||
const SAVE_OPTION = "Save Filter...";
|
||||
const SAVE_AS_OPTION = "Create A New Filter From This Filter...";
|
||||
const RENAME_OPTION = "Rename This Filter...";
|
||||
const CLEAR_OPTION = "Clear This Filter";
|
||||
const DELETE_OPTION = "Delete This Filter...";
|
||||
const dropdownOptions = [SAVE_AS_OPTION, RENAME_OPTION, CLEAR_OPTION, DELETE_OPTION];
|
||||
|
||||
const openSavedFiltersMenu = (event: any) => setSavedFiltersMenu(event.currentTarget);
|
||||
const closeSavedFiltersMenu = () => setSavedFiltersMenu(null);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// load filters on first run, then monitor location or metadata changes //
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
useEffect(() =>
|
||||
{
|
||||
loadSavedFilters()
|
||||
.then(() =>
|
||||
{
|
||||
if (currentSavedFilter != null)
|
||||
{
|
||||
let qFilter = FilterUtils.buildQFilterFromGridFilter(filterModel, columnSortModel);
|
||||
setFilterIsModified(JSON.stringify(qFilter) !== currentSavedFilter.values.get("filterJson"));
|
||||
}
|
||||
|
||||
setSavedFiltersHaveLoaded(true);
|
||||
});
|
||||
}, [location , tableMetaData, currentSavedFilter, filterModel, columnSortModel])
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** make request to load all saved filters from backend
|
||||
*******************************************************************************/
|
||||
async function loadSavedFilters()
|
||||
{
|
||||
if (! tableMetaData)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append("tableName", tableMetaData.name);
|
||||
|
||||
let savedFilters = await makeSavedFilterRequest("querySavedFilter", formData);
|
||||
setSavedFilters(savedFilters);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** fired when a saved record is clicked from the dropdown
|
||||
*******************************************************************************/
|
||||
const handleSavedFilterRecordOnClick = async (record: QRecord) =>
|
||||
{
|
||||
setSaveFilterPopupOpen(false);
|
||||
closeSavedFiltersMenu();
|
||||
filterOnChangeCallback(record.values.get("id"));
|
||||
navigate(`${metaData.getTablePathByName(tableMetaData.name)}/savedFilter/${record.values.get("id")}`);
|
||||
};
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** fired when a save option is selected from the save... button/dropdown combo
|
||||
*******************************************************************************/
|
||||
const handleDropdownOptionClick = (optionName: string) =>
|
||||
{
|
||||
setSaveOptionsOpen(false);
|
||||
setPopupAlertContent(null);
|
||||
closeSavedFiltersMenu();
|
||||
setSavedFilterNameInputValue(null);
|
||||
setSaveFilterPopupOpen(true);
|
||||
setIsSaveFilterAs(false);
|
||||
setIsRenameFilter(false);
|
||||
setIsDeleteFilter(false)
|
||||
|
||||
switch(optionName)
|
||||
{
|
||||
case SAVE_OPTION:
|
||||
break;
|
||||
case SAVE_AS_OPTION:
|
||||
setIsSaveFilterAs(true);
|
||||
break;
|
||||
case CLEAR_OPTION:
|
||||
setSaveFilterPopupOpen(false)
|
||||
filterOnChangeCallback(null);
|
||||
navigate(metaData.getTablePathByName(tableMetaData.name));
|
||||
break;
|
||||
case RENAME_OPTION:
|
||||
setIsRenameFilter(true);
|
||||
break;
|
||||
case DELETE_OPTION:
|
||||
setIsDeleteFilter(true)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** fired when save or delete button saved on confirmation dialogs
|
||||
*******************************************************************************/
|
||||
async function handleFilterDialogButtonOnClick()
|
||||
{
|
||||
try
|
||||
{
|
||||
const formData = new FormData();
|
||||
if (isDeleteFilter)
|
||||
{
|
||||
formData.append("id", currentSavedFilter.values.get("id"));
|
||||
await makeSavedFilterRequest("deleteSavedFilter", formData);
|
||||
await(async() =>
|
||||
{
|
||||
handleDropdownOptionClick(CLEAR_OPTION);
|
||||
})();
|
||||
}
|
||||
else
|
||||
{
|
||||
formData.append("tableName", tableMetaData.name);
|
||||
formData.append("filterJson", JSON.stringify(FilterUtils.buildQFilterFromGridFilter(filterModel, columnSortModel)));
|
||||
|
||||
if (isSaveFilterAs || isRenameFilter || currentSavedFilter == null)
|
||||
{
|
||||
formData.append("label", savedFilterNameInputValue);
|
||||
if(currentSavedFilter != null && isRenameFilter)
|
||||
{
|
||||
formData.append("id", currentSavedFilter.values.get("id"));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
formData.append("id", currentSavedFilter.values.get("id"));
|
||||
formData.append("label", currentSavedFilter?.values.get("label"));
|
||||
}
|
||||
const recordList = await makeSavedFilterRequest("storeSavedFilter", formData);
|
||||
await(async() =>
|
||||
{
|
||||
if (recordList && recordList.length > 0)
|
||||
{
|
||||
setSavedFiltersHaveLoaded(false);
|
||||
loadSavedFilters();
|
||||
handleSavedFilterRecordOnClick(recordList[0]);
|
||||
}
|
||||
})();
|
||||
}
|
||||
}
|
||||
catch (e: any)
|
||||
{
|
||||
setPopupAlertContent(JSON.stringify(e.message));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** hides/shows the save options
|
||||
*******************************************************************************/
|
||||
const handleToggleSaveOptions = () =>
|
||||
{
|
||||
setSaveOptionsOpen((prevOpen) => !prevOpen);
|
||||
};
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** closes save options menu (on clickaway)
|
||||
*******************************************************************************/
|
||||
const handleSaveOptionsMenuClose = (event: Event) =>
|
||||
{
|
||||
if (anchorRef.current && anchorRef.current.contains(event.target as HTMLElement))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
setSaveOptionsOpen(false);
|
||||
};
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** stores the current dialog input text to state
|
||||
*******************************************************************************/
|
||||
const handleSaveFilterInputChange = (event: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) =>
|
||||
{
|
||||
setSavedFilterNameInputValue(event.target.value);
|
||||
};
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** closes current dialog
|
||||
*******************************************************************************/
|
||||
const handleSaveFilterPopupClose = () =>
|
||||
{
|
||||
setSaveFilterPopupOpen(false);
|
||||
};
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** make a request to the backend for various savedFilter processes
|
||||
*******************************************************************************/
|
||||
async function makeSavedFilterRequest(processName: string, formData: FormData): Promise<QRecord[]>
|
||||
{
|
||||
/////////////////////////
|
||||
// fetch saved filters //
|
||||
/////////////////////////
|
||||
let savedFilters = [] as QRecord[]
|
||||
try
|
||||
{
|
||||
//////////////////////////////////////////////////////////////////
|
||||
// we don't want this job to go async, so, pass a large timeout //
|
||||
//////////////////////////////////////////////////////////////////
|
||||
formData.append("_qStepTimeoutMillis", 60 * 1000);
|
||||
|
||||
const formDataHeaders = {
|
||||
"content-type": "multipart/form-data; boundary=--------------------------320289315924586491558366",
|
||||
};
|
||||
|
||||
const processResult = await qController.processInit(processName, formData, formDataHeaders);
|
||||
if (processResult instanceof QJobError)
|
||||
{
|
||||
const jobError = processResult as QJobError;
|
||||
throw(jobError.error);
|
||||
}
|
||||
else
|
||||
{
|
||||
const result = processResult as QJobComplete;
|
||||
if(result.values.savedFilterList)
|
||||
{
|
||||
for (let i = 0; i < result.values.savedFilterList.length; i++)
|
||||
{
|
||||
const qRecord = new QRecord(result.values.savedFilterList[i]);
|
||||
savedFilters.push(qRecord);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
throw(e);
|
||||
}
|
||||
|
||||
return (savedFilters);
|
||||
}
|
||||
|
||||
const renderSavedFiltersMenu = tableMetaData && (
|
||||
<Menu
|
||||
sx={{width: "500px"}}
|
||||
anchorEl={savedFiltersMenu}
|
||||
anchorOrigin={{
|
||||
vertical: "bottom",
|
||||
horizontal: "left",
|
||||
}}
|
||||
transformOrigin={{
|
||||
vertical: "top",
|
||||
horizontal: "left",
|
||||
}}
|
||||
open={Boolean(savedFiltersMenu)}
|
||||
onClose={closeSavedFiltersMenu}
|
||||
keepMounted
|
||||
>
|
||||
{
|
||||
savedFilters && savedFilters.length > 0 ? (
|
||||
savedFilters.map((record: QRecord, index: number) =>
|
||||
<MenuItem key={`savedFiler-${index}`} onClick={() => handleSavedFilterRecordOnClick(record)}>
|
||||
{record.values.get("label")}
|
||||
</MenuItem>
|
||||
)
|
||||
): (
|
||||
<MenuItem >
|
||||
<i>No filters have been saved for this table.</i>
|
||||
</MenuItem>
|
||||
)
|
||||
}
|
||||
</Menu>
|
||||
);
|
||||
|
||||
|
||||
const hasStorePermission = metaData?.processes.has("storeSavedFilter");
|
||||
const hasDeletePermission = metaData?.processes.has("deleteSavedFilter");
|
||||
const hasQueryPermission = metaData?.processes.has("querySavedFilter");
|
||||
|
||||
return (
|
||||
hasQueryPermission && tableMetaData ? (
|
||||
<Box display="flex" flexGrow={1}>
|
||||
<QSavedFiltersMenuButton isOpen={savedFiltersMenu} onClickHandler={openSavedFiltersMenu} />
|
||||
{renderSavedFiltersMenu}
|
||||
<Box display="flex" justifyContent="flex-end" flexDirection="column">
|
||||
<Box pl={2} pr={2} sx={{display: "flex", alignItems: "center"}}>
|
||||
|
||||
{
|
||||
savedFiltersHaveLoaded && (
|
||||
currentSavedFilter ? (
|
||||
<Typography mr={2} variant="h6">Current Filter:
|
||||
<span style={{fontWeight: "initial"}}>
|
||||
{currentSavedFilter.values.get("label")}
|
||||
{
|
||||
filterIsModified && (
|
||||
<i> *</i>
|
||||
|
||||
)
|
||||
}
|
||||
</span>
|
||||
</Typography>
|
||||
) : (
|
||||
<Typography mr={2} variant="h6">
|
||||
<span style={{fontWeight: "initial"}}> </span>
|
||||
</Typography>
|
||||
)
|
||||
)
|
||||
}
|
||||
</Box>
|
||||
<Box pl={2} m={0} sx={{display: "flex", alignItems: "center"}}>
|
||||
<ButtonGroup variant="text" ref={anchorRef}>
|
||||
{
|
||||
hasStorePermission && (
|
||||
<Button sx={{minHeight: 1, margin: 0, padding: 0, minWidth: "initial !important", border: "0 !important"}} onClick={() => handleDropdownOptionClick(SAVE_OPTION)}>{SAVE_OPTION}</Button>
|
||||
)
|
||||
}
|
||||
{
|
||||
currentSavedFilter && (
|
||||
<Button sx={{minHeight: 1, margin: 0, padding: 0, minWidth: "20px !important", border: 0}} onClick={handleToggleSaveOptions} >
|
||||
<KeyboardArrowDown />
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
</ButtonGroup>
|
||||
<Popper
|
||||
sx={{
|
||||
zIndex: 10,
|
||||
marginLeft: "175px !important"
|
||||
}}
|
||||
open={saveOptionsOpen}
|
||||
anchorEl={anchorRef.current}
|
||||
transition
|
||||
disablePortal
|
||||
role={undefined}
|
||||
nonce={undefined}
|
||||
onResizeCapture={undefined}
|
||||
onResize={null}>
|
||||
{({TransitionProps, placement}) => (
|
||||
<Grow
|
||||
{...TransitionProps}
|
||||
style={{
|
||||
transformOrigin: "inherit",
|
||||
}}
|
||||
>
|
||||
<Paper>
|
||||
<ClickAwayListener onClickAway={handleSaveOptionsMenuClose}>
|
||||
<MenuList id="split-button-menu" autoFocusItem>
|
||||
{dropdownOptions.map((option, index) => (
|
||||
(option === CLEAR_OPTION || ((option !== DELETE_OPTION || hasDeletePermission) && (option !== SAVE_AS_OPTION || hasStorePermission))) && (
|
||||
<MenuItem
|
||||
key={option}
|
||||
onClick={() => handleDropdownOptionClick(option)}
|
||||
>
|
||||
{option}
|
||||
</MenuItem>
|
||||
)
|
||||
))}
|
||||
</MenuList>
|
||||
</ClickAwayListener>
|
||||
</Paper>
|
||||
</Grow>
|
||||
)}
|
||||
</Popper>
|
||||
</Box>
|
||||
</Box>
|
||||
{
|
||||
<Dialog
|
||||
open={saveFilterPopupOpen}
|
||||
onClose={handleSaveFilterPopupClose}
|
||||
aria-labelledby="alert-dialog-title"
|
||||
aria-describedby="alert-dialog-description"
|
||||
>
|
||||
{
|
||||
currentSavedFilter ? (
|
||||
isDeleteFilter ? (
|
||||
<DialogTitle id="alert-dialog-title">Delete Filter</DialogTitle>
|
||||
) : (
|
||||
isSaveFilterAs ? (
|
||||
<DialogTitle id="alert-dialog-title">Save Filter As</DialogTitle>
|
||||
):(
|
||||
isRenameFilter ? (
|
||||
<DialogTitle id="alert-dialog-title">Rename Filter</DialogTitle>
|
||||
):(
|
||||
<DialogTitle id="alert-dialog-title">Update Existing Filter</DialogTitle>
|
||||
)
|
||||
)
|
||||
)
|
||||
):(
|
||||
<DialogTitle id="alert-dialog-title">Save New Filter</DialogTitle>
|
||||
)
|
||||
}
|
||||
<DialogContent sx={{width: "500px"}}>
|
||||
{
|
||||
(! currentSavedFilter || isSaveFilterAs || isRenameFilter) && ! isDeleteFilter ? (
|
||||
<Box>
|
||||
{
|
||||
isSaveFilterAs ? (
|
||||
<Box mb={3}>Enter a name for this new saved filter.</Box>
|
||||
):(
|
||||
<Box mb={3}>Enter a new name for this saved filter.</Box>
|
||||
)
|
||||
}
|
||||
<TextField
|
||||
autoFocus
|
||||
name="custom-delimiter-value"
|
||||
placeholder="Filter Name"
|
||||
label="Filter Name"
|
||||
inputProps={{width: "100%", maxLength: 100}}
|
||||
sx={{width: "100%"}}
|
||||
onChange={handleSaveFilterInputChange}
|
||||
/>
|
||||
</Box>
|
||||
):(
|
||||
isDeleteFilter ? (
|
||||
<Box>Are you sure you want to delete the filter {`'${currentSavedFilter?.values.get("label")}'`}?</Box>
|
||||
|
||||
):(
|
||||
<Box>Are you sure you want to update the filter {`'${currentSavedFilter?.values.get("label")}'`} with the current filter criteria?</Box>
|
||||
)
|
||||
)
|
||||
}
|
||||
{popupAlertContent ? (
|
||||
<Box m={1}>
|
||||
<Alert severity="error">{popupAlertContent}</Alert>
|
||||
</Box>
|
||||
) : ("")}
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<QCancelButton onClickHandler={handleSaveFilterPopupClose} disabled={false} />
|
||||
{
|
||||
isDeleteFilter ?
|
||||
<QDeleteButton onClickHandler={handleFilterDialogButtonOnClick} />
|
||||
:
|
||||
<QSaveButton label="Save" onClickHandler={handleFilterDialogButtonOnClick} disabled={(isSaveFilterAs || currentSavedFilter == null) && savedFilterNameInputValue == null}/>
|
||||
}
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
}
|
||||
</Box>
|
||||
) : null
|
||||
);
|
||||
}
|
||||
|
||||
export default SavedFilters;
|
Reference in New Issue
Block a user