CE-798 Redesign of query screen controls - moving columns & sort & export controls out of grid; css from Paul

This commit is contained in:
2024-02-06 19:53:16 -06:00
parent bda07066b4
commit f7c3bef3e8
12 changed files with 2105 additions and 739 deletions

View File

@ -33,7 +33,7 @@ import {QCriteriaOperator} from "@kingsrook/qqq-frontend-core/lib/model/query/QC
import {QFilterCriteria} from "@kingsrook/qqq-frontend-core/lib/model/query/QFilterCriteria";
import {QFilterOrderBy} from "@kingsrook/qqq-frontend-core/lib/model/query/QFilterOrderBy";
import {QQueryFilter} from "@kingsrook/qqq-frontend-core/lib/model/query/QQueryFilter";
import {Alert, Collapse, Typography} from "@mui/material";
import {Alert, Collapse, Menu, Typography} from "@mui/material";
import Box from "@mui/material/Box";
import Button from "@mui/material/Button";
import Card from "@mui/material/Card";
@ -44,21 +44,22 @@ import LinearProgress from "@mui/material/LinearProgress";
import MenuItem from "@mui/material/MenuItem";
import Modal from "@mui/material/Modal";
import Tooltip from "@mui/material/Tooltip";
import {ColumnHeaderFilterIconButtonProps, DataGridPro, GridCallbackDetails, GridColDef, 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, 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 {GridRowModel} from "@mui/x-data-grid/models/gridRows";
import FormData from "form-data";
import React, {forwardRef, useContext, useEffect, useReducer, useRef, useState} from "react";
import {useLocation, useNavigate, useSearchParams} from "react-router-dom";
import QContext from "QContext";
import colors from "qqq/assets/theme/base/colors";
import {QCancelButton, QCreateNewButton} from "qqq/components/buttons/DefaultButtons";
import MenuButton from "qqq/components/buttons/MenuButton";
import {GotoRecordButton} from "qqq/components/misc/GotoRecordDialog";
import SavedViews from "qqq/components/misc/SavedViews";
import BasicAndAdvancedQueryControls from "qqq/components/query/BasicAndAdvancedQueryControls";
import {CustomColumnsPanel} from "qqq/components/query/CustomColumnsPanel";
import {CustomFilterPanel} from "qqq/components/query/CustomFilterPanel";
import CustomPaginationComponent from "qqq/components/query/CustomPaginationComponent";
import ExportMenuItem from "qqq/components/query/ExportMenuItem";
import FieldListMenu from "qqq/components/query/FieldListMenu";
import {validateCriteria} from "qqq/components/query/FilterCriteriaRow";
import QueryScreenActionMenu from "qqq/components/query/QueryScreenActionMenu";
import SelectionSubsetDialog from "qqq/components/query/SelectionSubsetDialog";
@ -74,6 +75,7 @@ import DataGridUtils from "qqq/utils/DataGridUtils";
import Client from "qqq/utils/qqq/Client";
import FilterUtils from "qqq/utils/qqq/FilterUtils";
import ProcessUtils from "qqq/utils/qqq/ProcessUtils";
import {SavedViewUtils} from "qqq/utils/qqq/SavedViewUtils";
import TableUtils from "qqq/utils/qqq/TableUtils";
import ValueUtils from "qqq/utils/qqq/ValueUtils";
@ -246,14 +248,6 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
const [pageState, setPageState] = useState("initial" as PageState)
///////////////////////////////////////////////////
// state used by the custom column-chooser panel //
///////////////////////////////////////////////////
const initialColumnChooserOpenGroups = {} as { [name: string]: boolean };
initialColumnChooserOpenGroups[tableName] = true;
const [columnChooserOpenGroups, setColumnChooserOpenGroups] = useState(initialColumnChooserOpenGroups);
const [columnChooserFilterText, setColumnChooserFilterText] = useState("");
/////////////////////////////////
// meta-data and derived state //
/////////////////////////////////
@ -285,6 +279,8 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
const [currentSavedView, setCurrentSavedView] = useState(null as QRecord);
const [viewIdInLocation, setViewIdInLocation] = useState(null as number);
const [loadingSavedView, setLoadingSavedView] = useState(false);
const [exportMenuAnchorElement, setExportMenuAnchorElement] = useState(null);
const [tableDefaultView, setTableDefaultView] = useState(new RecordQueryView());
/////////////////////////////////////////////////////
// state related to avoiding accidental row clicks //
@ -342,7 +338,12 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
/////////////////////////////
// page context references //
/////////////////////////////
const {setPageHeader, dotMenuOpen, keyboardHelpOpen} = useContext(QContext);
const {accentColor, accentColorLight, setPageHeader, dotMenuOpen, keyboardHelpOpen} = useContext(QContext);
//////////////////////////////////////////////////////////////////
// we use our own header - so clear out the context page header //
//////////////////////////////////////////////////////////////////
setPageHeader(null);
//////////////////////
// ole' faithful... //
@ -416,6 +417,97 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
return (false);
}
/*******************************************************************************
**
*******************************************************************************/
const prepQueryFilterForBackend = (sourceFilter: QQueryFilter) =>
{
const filterForBackend = new QQueryFilter([], sourceFilter.orderBys, sourceFilter.booleanOperator);
for (let i = 0; i < sourceFilter?.criteria?.length; i++)
{
const criteria = sourceFilter.criteria[i];
const {criteriaIsValid} = validateCriteria(criteria, null);
if (criteriaIsValid)
{
if (criteria.operator == QCriteriaOperator.IS_BLANK || criteria.operator == QCriteriaOperator.IS_NOT_BLANK)
{
///////////////////////////////////////////////////////////////////////////////////////////
// do this to avoid submitting an empty-string argument for blank/not-blank operators... //
///////////////////////////////////////////////////////////////////////////////////////////
filterForBackend.criteria.push(new QFilterCriteria(criteria.fieldName, criteria.operator, []));
}
else
{
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
// else push a clone of the criteria - since it may get manipulated below (convertFilterPossibleValuesToIds) //
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
const [field] = FilterUtils.getField(tableMetaData, criteria.fieldName)
filterForBackend.criteria.push(new QFilterCriteria(criteria.fieldName, criteria.operator, FilterUtils.cleanseCriteriaValueForQQQ(criteria.values, field)));
}
}
}
filterForBackend.skip = pageNumber * rowsPerPage;
filterForBackend.limit = rowsPerPage;
return filterForBackend;
}
/*******************************************************************************
**
*******************************************************************************/
function openExportMenu(event: any)
{
setExportMenuAnchorElement(event.currentTarget);
}
/*******************************************************************************
**
*******************************************************************************/
function closeExportMenu()
{
setExportMenuAnchorElement(null);
}
///////////////////////////////////////////
// build the export menu, for the header //
///////////////////////////////////////////
let exportMenu = <></>
try
{
const exportMenuItemRestProps =
{
tableMetaData: tableMetaData,
totalRecords: totalRecords,
columnsModel: columnsModel,
columnVisibilityModel: columnVisibilityModel,
queryFilter: prepQueryFilterForBackend(queryFilter)
}
exportMenu = (<>
<IconButton sx={{p: 0, fontSize: "0.75rem", mb: 1, color: colors.secondary.main, fontVariationSettings: "'wght' 100"}} onClick={openExportMenu}><Icon fontSize="small">save_alt</Icon></IconButton>
<Menu
anchorEl={exportMenuAnchorElement}
anchorOrigin={{vertical: "bottom", horizontal: "center"}}
transformOrigin={{vertical: "top", horizontal: "center"}}
open={exportMenuAnchorElement != null}
onClose={closeExportMenu}
sx={{top: "0.5rem"}}
keepMounted>
<ExportMenuItem format="csv" {...exportMenuItemRestProps} />
<ExportMenuItem format="xlsx" {...exportMenuItemRestProps} />
<ExportMenuItem format="json" {...exportMenuItemRestProps} />
</Menu>
</>);
}
catch(e)
{
console.log("Error preparing export menu for page header: " + e);
}
/*******************************************************************************
**
*******************************************************************************/
@ -459,9 +551,9 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
return(
<div>
{label}
{label} {exportMenu}
<CustomWidthTooltip title={tooltipHTML}>
<IconButton sx={{p: 0, fontSize: "0.5rem", mb: 1, color: "#9f9f9f", fontVariationSettings: "'wght' 100"}}><Icon fontSize="small">emergency</Icon></IconButton>
<IconButton sx={{ml: "0.5rem", p: 0, fontSize: "0.5rem", mb: 1, color: "#9f9f9f", fontVariationSettings: "'wght' 100"}}><Icon fontSize="small">emergency</Icon></IconButton>
</CustomWidthTooltip>
{tableVariant && getTableVariantHeader(tableVariant)}
</div>);
@ -470,7 +562,7 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
{
return (
<div>
{label}
{label} {exportMenu}
{tableVariant && getTableVariantHeader(tableVariant)}
</div>);
}
@ -569,7 +661,7 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
// in case page-state has already advanced to "ready" (e.g., and we're dealing with a user //
// hitting back & forth between filters), then do a load of the new saved-view right here //
/////////////////////////////////////////////////////////////////////////////////////////////
if(pageState == "ready")
if (pageState == "ready")
{
handleSavedViewChange(currentSavedViewId);
}
@ -593,6 +685,19 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
}
}, [location]);
/*******************************************************************************
** set the current view in state & local-storage - but do NOT update any
** child-state data.
*******************************************************************************/
const doSetView = (view: RecordQueryView): void =>
{
setView(view);
setViewAsJson(JSON.stringify(view));
localStorage.setItem(viewLocalStorageKey, JSON.stringify(view));
}
/*******************************************************************************
**
*******************************************************************************/
@ -607,6 +712,40 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
forceUpdate();
};
/*******************************************************************************
** function called by columns menu to turn a column on or off
*******************************************************************************/
const handleChangeOneColumnVisibility = (field: QFieldMetaData, table: QTableMetaData, newValue: boolean) =>
{
///////////////////////////////////////
// set the field's value in the view //
///////////////////////////////////////
let fieldName = field.name;
if(table && table.name != tableMetaData.name)
{
fieldName = `${table.name}.${field.name}`;
}
view.queryColumns.setIsVisible(fieldName, newValue)
/////////////////////
// update the grid //
/////////////////////
setColumnVisibilityModel(queryColumns.toColumnVisibilityModel());
/////////////////////////////////////////////////
// update the view (e.g., write local storage) //
/////////////////////////////////////////////////
doSetView(view)
///////////////////
// ole' faithful //
///////////////////
forceUpdate();
}
/*******************************************************************************
**
*******************************************************************************/
@ -656,43 +795,32 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
/*******************************************************************************
**
** return array of table names that need ... added to query
*******************************************************************************/
const prepQueryFilterForBackend = (sourceFilter: QQueryFilter) =>
const ensureOrderBysFromJoinTablesAreVisibleTables = (queryFilter: QQueryFilter, visibleJoinTablesParam?: Set<string>): string[] =>
{
const filterForBackend = new QQueryFilter([], sourceFilter.orderBys, sourceFilter.booleanOperator);
for (let i = 0; i < sourceFilter?.criteria?.length; i++)
const rs: string[] = [];
const vjtToUse = visibleJoinTablesParam ?? visibleJoinTables;
for (let i = 0; i < queryFilter?.orderBys?.length; i++)
{
const criteria = sourceFilter.criteria[i];
const {criteriaIsValid} = validateCriteria(criteria, null);
if (criteriaIsValid)
const fieldName = queryFilter.orderBys[i].fieldName;
if(fieldName.indexOf(".") > -1)
{
if (criteria.operator == QCriteriaOperator.IS_BLANK || criteria.operator == QCriteriaOperator.IS_NOT_BLANK)
const joinTableName = fieldName.replaceAll(/\..*/g, "");
if(!vjtToUse.has(joinTableName))
{
///////////////////////////////////////////////////////////////////////////////////////////
// do this to avoid submitting an empty-string argument for blank/not-blank operators... //
///////////////////////////////////////////////////////////////////////////////////////////
filterForBackend.criteria.push(new QFilterCriteria(criteria.fieldName, criteria.operator, []));
}
else
{
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
// else push a clone of the criteria - since it may get manipulated below (convertFilterPossibleValuesToIds) //
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
const [field] = FilterUtils.getField(tableMetaData, criteria.fieldName)
filterForBackend.criteria.push(new QFilterCriteria(criteria.fieldName, criteria.operator, FilterUtils.cleanseCriteriaValueForQQQ(criteria.values, field)));
const [field, fieldTable] = TableUtils.getFieldAndTable(tableMetaData, fieldName);
handleChangeOneColumnVisibility(field, fieldTable, true);
rs.push(fieldTable.name);
}
}
}
filterForBackend.skip = pageNumber * rowsPerPage;
filterForBackend.limit = rowsPerPage;
// FilterUtils.convertFilterPossibleValuesToIds(filterForBackend);
// todo - expressions?
// todo - utc
return filterForBackend;
return (rs);
}
/*******************************************************************************
** This is the method that actually executes a query to update the data in the table.
*******************************************************************************/
@ -729,6 +857,10 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
if (tableMetaData?.exposedJoins)
{
const visibleJoinTables = getVisibleJoinTables();
const tablesToAdd = ensureOrderBysFromJoinTablesAreVisibleTables(queryFilter, visibleJoinTables);
tablesToAdd?.forEach(t => visibleJoinTables.add(t));
queryJoins = TableUtils.getQueryJoins(tableMetaData, visibleJoinTables);
}
@ -1062,15 +1194,13 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
doSetQueryFilter(queryFilter);
};
/*******************************************************************************
** set the current view in state & local-storage - but do NOT update any
** child-state data.
**
*******************************************************************************/
const doSetView = (view: RecordQueryView): void =>
const handleColumnHeaderClick = (params: GridColumnHeaderParams, event: MuiEvent, details: GridCallbackDetails): void =>
{
setView(view);
setViewAsJson(JSON.stringify(view));
localStorage.setItem(viewLocalStorageKey, JSON.stringify(view));
event.defaultMuiPrevented = true;
}
@ -1112,6 +1242,11 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
{
console.log(`Setting a new query filter: ${JSON.stringify(queryFilter)}`);
///////////////////////////////////////////////////
// when we have a new filter, go back to page 0. //
///////////////////////////////////////////////////
setPageNumber(0);
///////////////////////////////////////////////////
// in case there's no orderBys, set default here //
///////////////////////////////////////////////////
@ -1121,6 +1256,15 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
view.queryFilter = queryFilter;
}
////////////////////////////////////////////////////////////////////////////////////////////////
// in case the order-by is from a join table, and that table doesn't have any visible fields, //
// then activate the order-by field itself //
////////////////////////////////////////////////////////////////////////////////////////////////
ensureOrderBysFromJoinTablesAreVisibleTables(queryFilter);
//////////////////////////////
// set the filter state var //
//////////////////////////////
setQueryFilter(queryFilter);
///////////////////////////////////////////////////////
@ -1423,6 +1567,22 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
}
}
/*******************************************************************************
**
*******************************************************************************/
const buildTableDefaultView = (): RecordQueryView =>
{
const newDefaultView = new RecordQueryView();
newDefaultView.queryFilter = new QQueryFilter([], [new QFilterOrderBy(tableMetaData.primaryKeyField, false)]);
newDefaultView.queryColumns = QQueryColumns.buildDefaultForTable(tableMetaData);
newDefaultView.viewIdentity = "empty";
newDefaultView.rowsPerPage = defaultRowsPerPage;
newDefaultView.quickFilterFieldNames = [];
newDefaultView.mode = defaultMode;
return newDefaultView;
}
/*******************************************************************************
** event handler for SavedViews component, to handle user selecting a view
** (or clearing / selecting new)
@ -1453,18 +1613,10 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
setCurrentSavedView(null);
localStorage.removeItem(currentSavedViewLocalStorageKey);
/////////////////////////////////////////////////////
// go back to a default query filter for the table //
/////////////////////////////////////////////////////
doSetQueryFilter(new QQueryFilter());
const queryColumns = QQueryColumns.buildDefaultForTable(tableMetaData);
doSetQueryColumns(queryColumns)
/////////////////////////////////////////////////////
// also reset the (user-added) quick-filter fields //
/////////////////////////////////////////////////////
doSetQuickFilterFieldNames([]);
///////////////////////////////////////////////
// activate a new default view for the table //
///////////////////////////////////////////////
activateView(buildTableDefaultView())
}
}
@ -1848,36 +2000,14 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
}
};
//////////////////////////////////////////////////////////////////
// props that get passed into all of the ExportMenuItem's below //
//////////////////////////////////////////////////////////////////
const exportMenuItemRestProps =
{
tableMetaData: tableMetaData,
totalRecords: totalRecords,
columnsModel: columnsModel,
columnVisibilityModel: columnVisibilityModel,
queryFilter: prepQueryFilterForBackend(queryFilter)
}
return (
<GridToolbarContainer>
<div>
<Button id="refresh-button" onClick={() => updateTable("refresh button")} startIcon={<Icon>refresh</Icon>} sx={{pr: "1.25rem"}}>
Refresh
</Button>
<Button id="refresh-button" onClick={() => updateTable("refresh button")} startIcon={<Icon>refresh</Icon>} sx={{pl: "1rem", pr: "0.5rem", minWidth: "unset"}}></Button>
</div>
{/* @ts-ignore */}
<GridToolbarColumnsButton nonce={undefined} />
<div style={{position: "relative"}}>
{/* @ts-ignore */}
<GridToolbarDensitySelector nonce={undefined} />
{/* @ts-ignore */}
<GridToolbarExportContainer nonce={undefined}>
<ExportMenuItem format="csv" {...exportMenuItemRestProps} />
<ExportMenuItem format="xlsx" {...exportMenuItemRestProps} />
<ExportMenuItem format="json" {...exportMenuItemRestProps} />
</GridToolbarExportContainer>
</div>
<div style={{zIndex: 10}}>
@ -1990,15 +2120,6 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
);
}
/*******************************************************************************
** maybe something to do with how page header is in a context, but, it didn't
** work to check pageLoadingState.isLoadingSlow inside an element that we put
** in the page header, so, this works instead.
*******************************************************************************/
const setPageHeaderToLoadingSlow = (): void =>
{
setPageHeader("Loading...")
}
/////////////////////////////////////////////////////////////////////////////////
// use this to make changes to the queryFilter more likely to re-run the query //
@ -2024,12 +2145,6 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
setPageState("loadingMetaData");
pageLoadingState.setLoading();
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// reset the page header to blank, and tell the pageLoadingState object that if it becomes slow, to show 'Loading' //
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
setPageHeader("");
pageLoadingState.setUponSlowCallback(setPageHeaderToLoadingSlow);
(async () =>
{
const metaData = await qController.loadMetaData();
@ -2056,6 +2171,13 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
(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 //
//////////////////////////////////////////////////////////////////////////////////////////////
@ -2212,7 +2334,6 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
(async () =>
{
const visibleJoinTables = getVisibleJoinTables();
setPageHeader(getPageHeader(tableMetaData, visibleJoinTables, tableVariant));
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// todo - we used to be able to set "warnings" here (i think, like, for if a field got deleted from a table... //
@ -2362,11 +2483,135 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
return (getLoadingScreen());
}
let savedViewsComponent = null;
if(metaData && metaData.processes.has("querySavedView"))
{
savedViewsComponent = (<SavedViews qController={qController} metaData={metaData} tableMetaData={tableMetaData} view={view} viewAsJson={viewAsJson} currentSavedView={currentSavedView} tableDefaultView={tableDefaultView} viewOnChangeCallback={handleSavedViewChange} loadingSavedView={loadingSavedView} />);
}
/*******************************************************************************
**
*******************************************************************************/
const buildColumnMenu = () =>
{
//////////////////////////////////////////
// default (no saved view, and "clean") //
//////////////////////////////////////////
let buttonBackground = "none";
let buttonBorder = colors.grayLines.main;
let buttonColor = colors.gray.main;
////////////////////////////////////////////////////////////////////////////////////////////////////////////
// diff the current view with either the current saved one, if there's one active, else the table default //
////////////////////////////////////////////////////////////////////////////////////////////////////////////
const baseView = currentSavedView ? JSON.parse(currentSavedView.values.get("viewJson")) as RecordQueryView : tableDefaultView;
const viewDiffs: string[] = [];
SavedViewUtils.diffColumns(tableMetaData, baseView, view, viewDiffs)
if(viewDiffs.length == 0 && currentSavedView)
{
/////////////////////////////////////////////////////////////////
// if 's a saved view, and it's "clean", show it in main style //
/////////////////////////////////////////////////////////////////
buttonBackground = accentColor;
buttonBorder = accentColor;
buttonColor = "#FFFFFF";
}
else if(viewDiffs.length > 0)
{
///////////////////////////////////////////////////
// else if there are diffs, show alt/light style //
///////////////////////////////////////////////////
buttonBackground = accentColorLight;
buttonBorder = accentColorLight;
buttonColor = accentColor;
}
const columnMenuButtonStyles = {
borderRadius: "0.75rem",
border: `1px solid ${buttonBorder}`,
color: buttonColor,
textTransform: "none",
fontWeight: 500,
fontSize: "0.875rem",
p: "0.5rem",
backgroundColor: buttonBackground,
"&:focus:not(:hover)": {
color: buttonColor,
backgroundColor: buttonBackground,
},
"&:hover": {
color: buttonColor,
backgroundColor: buttonBackground,
}
}
return (<Box order="2">
<FieldListMenu
idPrefix="columns"
tableMetaData={tableMetaData}
showTableHeaderEvenIfNoExposedJoins={true}
placeholder="Search Fields"
buttonProps={{sx: columnMenuButtonStyles}}
buttonChildren={<><Icon sx={{mr: "0.5rem"}}>view_week_outline</Icon> Columns ({view.queryColumns.getVisibleColumnCount()}) <Icon sx={{ml: "0.5rem"}}>keyboard_arrow_down</Icon></>}
isModeToggle={true}
toggleStates={view.queryColumns.getVisibilityToggleStates()}
handleToggleField={handleChangeOneColumnVisibility}
/>
</Box>);
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////
// these numbers help set the height of the grid (so page won't scroll) based on spcae above & below it //
//////////////////////////////////////////////////////////////////////////////////////////////////////////
let spaceBelowGrid = 40;
let spaceAboveGrid = 205;
if(tableMetaData?.usesVariants)
{
spaceAboveGrid += 30;
}
if(mode == "advanced")
{
spaceAboveGrid += 60;
}
////////////////////////
// main screen render //
////////////////////////
return (
<BaseLayout>
<Box display="flex" justifyContent="space-between">
<Box>
<Typography textTransform="capitalize" variant="h3" noWrap>
{pageLoadingState.isLoading() && ""}
{pageLoadingState.isLoadingSlow() && "Loading..."}
{pageLoadingState.isNotLoading() && getPageHeader(tableMetaData, visibleJoinTables, tableVariant)}
</Typography>
</Box>
<Box>
<GotoRecordButton metaData={metaData} tableMetaData={tableMetaData} />
<Box display="inline-block" width="150px">
{
tableMetaData &&
<QueryScreenActionMenu
metaData={metaData}
tableMetaData={tableMetaData}
tableProcesses={tableProcesses}
bulkLoadClicked={bulkLoadClicked}
bulkEditClicked={bulkEditClicked}
bulkDeleteClicked={bulkDeleteClicked}
processClicked={processClicked}
/>
}
</Box>
{
table.capabilities.has(Capability.TABLE_INSERT) && table.insertPermission &&
<QCreateNewButton tablePath={metaData?.getTablePathByName(tableName)} />
}
</Box>
</Box>
<div className="recordQuery">
{/*
// see code in ExportMenuItem that would use this
@ -2411,34 +2656,6 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
</Collapse>
) : null
}
<Box display="flex" justifyContent="flex-end" alignItems="flex-start" mb={2}>
<Box display="flex" marginRight="auto">
{
metaData && metaData.processes.has("querySavedView") &&
<SavedViews qController={qController} metaData={metaData} tableMetaData={tableMetaData} view={view} viewAsJson={viewAsJson} currentSavedView={currentSavedView} viewOnChangeCallback={handleSavedViewChange} loadingSavedView={loadingSavedView} />
}
</Box>
<GotoRecordButton metaData={metaData} tableMetaData={tableMetaData} />
<Box display="flex" width="150px">
{
tableMetaData &&
<QueryScreenActionMenu
metaData={metaData}
tableMetaData={tableMetaData}
tableProcesses={tableProcesses}
bulkLoadClicked={bulkLoadClicked}
bulkEditClicked={bulkEditClicked}
bulkDeleteClicked={bulkDeleteClicked}
processClicked={processClicked}
/>
}
</Box>
{
table.capabilities.has(Capability.TABLE_INSERT) && table.insertPermission &&
<QCreateNewButton tablePath={metaData?.getTablePathByName(tableName)} />
}
</Box>
{
metaData && tableMetaData &&
@ -2454,6 +2671,8 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
gridApiRef={gridApiRef}
mode={mode}
setMode={doSetMode}
savedViewsComponent={savedViewsComponent}
columnMenuComponent={buildColumnMenu()}
/>
}
@ -2466,20 +2685,12 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
Pagination: CustomPagination,
LoadingOverlay: CustomLoadingOverlay,
ColumnMenu: CustomColumnMenu,
ColumnsPanel: CustomColumnsPanel,
FilterPanel: CustomFilterPanel,
// @ts-ignore - this turns these off, whether TS likes it or not...
ColumnsPanel: "", ColumnSortedDescendingIcon: "", ColumnSortedAscendingIcon: "", ColumnUnsortedIcon: "",
ColumnHeaderFilterIconButton: CustomColumnHeaderFilterIconButton,
}}
componentsProps={{
columnsPanel:
{
tableMetaData: tableMetaData,
metaData: metaData,
initialOpenedGroups: columnChooserOpenGroups,
openGroupsChanger: setColumnChooserOpenGroups,
initialFilterText: columnChooserFilterText,
filterTextChanger: setColumnChooserFilterText
},
filterPanel:
{
tableMetaData: tableMetaData,
@ -2522,12 +2733,12 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
onSelectionModelChange={handleSelectionChanged}
onSortModelChange={handleSortChange}
sortingOrder={["asc", "desc"]}
sortModel={columnSortModel}
onColumnHeaderClick={handleColumnHeaderClick}
getRowClassName={(params) => (params.indexRelativeToCurrentPage % 2 === 0 ? "even" : "odd")}
getRowId={(row) => row.__rowIndex}
selectionModel={rowSelectionModel}
hideFooterSelectedRowCount={true}
sx={{border: 0, height: tableMetaData?.usesVariants ? "calc(100vh - 300px)" : "calc(100vh - 270px)"}}
sx={{border: 0, height: `calc(100vh - ${spaceAboveGrid + spaceBelowGrid}px)`}}
/>
</Box>
</Card>
@ -2548,7 +2759,6 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
{
setTableVariantPromptOpen(false);
setTableVariant(value);
setPageHeader(getPageHeader(tableMetaData, visibleJoinTables, value));
}} />
}