mirror of
https://github.com/Kingsrook/qqq-frontend-material-dashboard.git
synced 2025-07-17 12:50:43 +00:00
Add omitExposedJoins
prop throughout RecordQuery and all subcomponents. Initially for the FilterAndColumnsSetupWidget to allow some joins to not be exposed.
This commit is contained in:
@ -20,6 +20,7 @@
|
||||
*/
|
||||
|
||||
|
||||
import {QExposedJoin} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QExposedJoin";
|
||||
import {QFieldMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldMetaData";
|
||||
import {QInstance} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QInstance";
|
||||
import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData";
|
||||
@ -27,25 +28,26 @@ import {Box} from "@mui/material";
|
||||
import Autocomplete, {AutocompleteRenderOptionState} from "@mui/material/Autocomplete";
|
||||
import Icon from "@mui/material/Icon";
|
||||
import TextField from "@mui/material/TextField";
|
||||
import React, {ReactNode, useState} from "react";
|
||||
import React, {ReactNode, useMemo, useState} from "react";
|
||||
|
||||
interface FieldAutoCompleteProps
|
||||
{
|
||||
id: string;
|
||||
metaData: QInstance;
|
||||
tableMetaData: QTableMetaData;
|
||||
handleFieldChange: (event: any, newValue: any, reason: string) => void;
|
||||
defaultValue?: { field: QFieldMetaData, table: QTableMetaData, fieldName: string };
|
||||
autoFocus?: boolean;
|
||||
forceOpen?: boolean;
|
||||
hiddenFieldNames?: string[];
|
||||
availableFieldNames?: string[];
|
||||
variant?: "standard" | "filled" | "outlined";
|
||||
label?: string;
|
||||
textFieldSX?: any;
|
||||
autocompleteSlotProps?: any;
|
||||
hasError?: boolean;
|
||||
noOptionsText?: string;
|
||||
id: string,
|
||||
metaData: QInstance,
|
||||
tableMetaData: QTableMetaData,
|
||||
handleFieldChange: (event: any, newValue: any, reason: string) => void,
|
||||
defaultValue?: { field: QFieldMetaData, table: QTableMetaData, fieldName: string },
|
||||
autoFocus?: boolean,
|
||||
forceOpen?: boolean,
|
||||
hiddenFieldNames?: string[],
|
||||
availableFieldNames?: string[],
|
||||
variant?: "standard" | "filled" | "outlined",
|
||||
label?: string,
|
||||
textFieldSX?: any,
|
||||
autocompleteSlotProps?: any,
|
||||
hasError?: boolean,
|
||||
noOptionsText?: string,
|
||||
omitExposedJoins?: string[]
|
||||
}
|
||||
|
||||
FieldAutoComplete.defaultProps =
|
||||
@ -88,7 +90,7 @@ function makeFieldOptionsForTable(tableMetaData: QTableMetaData, fieldOptions: a
|
||||
/*******************************************************************************
|
||||
** Component for rendering a list of field names from a table as an auto-complete.
|
||||
*******************************************************************************/
|
||||
export default function FieldAutoComplete({id, metaData, tableMetaData, handleFieldChange, defaultValue, autoFocus, forceOpen, hiddenFieldNames, availableFieldNames, variant, label, textFieldSX, autocompleteSlotProps, hasError, noOptionsText}: FieldAutoCompleteProps): JSX.Element
|
||||
export default function FieldAutoComplete({id, metaData, tableMetaData, handleFieldChange, defaultValue, autoFocus, forceOpen, hiddenFieldNames, availableFieldNames, variant, label, textFieldSX, autocompleteSlotProps, hasError, noOptionsText, omitExposedJoins}: FieldAutoCompleteProps): JSX.Element
|
||||
{
|
||||
const [selectedFieldName, setSelectedFieldName] = useState(defaultValue ? defaultValue.fieldName : null);
|
||||
|
||||
@ -96,11 +98,25 @@ export default function FieldAutoComplete({id, metaData, tableMetaData, handleFi
|
||||
makeFieldOptionsForTable(tableMetaData, fieldOptions, false, hiddenFieldNames, availableFieldNames, selectedFieldName);
|
||||
let fieldsGroupBy = null;
|
||||
|
||||
if (tableMetaData.exposedJoins && tableMetaData.exposedJoins.length > 0)
|
||||
const availableExposedJoins = useMemo(() =>
|
||||
{
|
||||
for (let i = 0; i < tableMetaData.exposedJoins.length; i++)
|
||||
const rs: QExposedJoin[] = []
|
||||
for(let exposedJoin of tableMetaData.exposedJoins ?? [])
|
||||
{
|
||||
const exposedJoin = tableMetaData.exposedJoins[i];
|
||||
if(omitExposedJoins?.indexOf(exposedJoin.joinTable.name) > -1)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
rs.push(exposedJoin);
|
||||
}
|
||||
return (rs);
|
||||
}, [tableMetaData, omitExposedJoins]);
|
||||
|
||||
if (availableExposedJoins && availableExposedJoins.length > 0)
|
||||
{
|
||||
for (let i = 0; i < availableExposedJoins.length; i++)
|
||||
{
|
||||
const exposedJoin = availableExposedJoins[i];
|
||||
if (metaData.tables.has(exposedJoin.joinTable.name))
|
||||
{
|
||||
fieldsGroupBy = (option: any) => `${option.table.label} fields`;
|
||||
@ -185,7 +201,7 @@ export default function FieldAutoComplete({id, metaData, tableMetaData, handleFi
|
||||
{originalEndAdornment}
|
||||
</Box>;
|
||||
|
||||
return (<TextField {...params} autoFocus={autoFocus} label={label} variant={variant} sx={textFieldSX} autoComplete="off" type="search" InputProps={inputProps} />)
|
||||
return (<TextField {...params} autoFocus={autoFocus} label={label} variant={variant} sx={textFieldSX} autoComplete="off" type="search" InputProps={inputProps} />);
|
||||
}}
|
||||
// @ts-ignore
|
||||
defaultValue={defaultValue}
|
||||
|
@ -83,6 +83,8 @@ interface BasicAndAdvancedQueryControlsProps
|
||||
|
||||
mode: string;
|
||||
setMode: (mode: string) => void;
|
||||
|
||||
omitExposedJoins?: string[];
|
||||
}
|
||||
|
||||
let debounceTimeout: string | number | NodeJS.Timeout;
|
||||
@ -627,6 +629,7 @@ const BasicAndAdvancedQueryControls = forwardRef((props: BasicAndAdvancedQueryCo
|
||||
handleSelectedField={handleSetSort}
|
||||
fieldEndAdornment={<Box whiteSpace="nowrap"><Icon>arrow_upward</Icon><Icon>arrow_downward</Icon></Box>}
|
||||
handleAdornmentClick={handleSetSortArrowClick}
|
||||
omitExposedJoins={props.omitExposedJoins}
|
||||
/>);
|
||||
|
||||
const filterBuilderMouseEvents =
|
||||
@ -721,6 +724,7 @@ const BasicAndAdvancedQueryControls = forwardRef((props: BasicAndAdvancedQueryCo
|
||||
buttonChildren={"Add Filter"}
|
||||
isModeSelectOne={true}
|
||||
handleSelectedField={handleFieldListMenuSelection}
|
||||
omitExposedJoins={props.omitExposedJoins}
|
||||
/>
|
||||
}
|
||||
</>
|
||||
|
@ -43,6 +43,7 @@ declare module "@mui/x-data-grid"
|
||||
metaData: QInstance;
|
||||
queryFilter: QQueryFilter;
|
||||
updateFilter: (newFilter: QQueryFilter) => void;
|
||||
omitExposedJoins?: string[]
|
||||
}
|
||||
}
|
||||
|
||||
@ -181,6 +182,7 @@ export const CustomFilterPanel = forwardRef<any, GridFilterPanelProps>(
|
||||
updateBooleanOperator={(newValue) => updateBooleanOperator(newValue)}
|
||||
allowVariables={props.allowVariables}
|
||||
queryScreenUsage={props.queryScreenUsage}
|
||||
omitExposedJoins={props.omitExposedJoins}
|
||||
/>
|
||||
{/*JSON.stringify(criteria)*/}
|
||||
</Box>
|
||||
|
@ -19,6 +19,7 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {QExposedJoin} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QExposedJoin";
|
||||
import {QFieldMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldMetaData";
|
||||
import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData";
|
||||
import Box from "@mui/material/Box";
|
||||
@ -31,28 +32,26 @@ import ListItem, {ListItemProps} from "@mui/material/ListItem/ListItem";
|
||||
import Menu from "@mui/material/Menu";
|
||||
import Switch from "@mui/material/Switch";
|
||||
import TextField from "@mui/material/TextField";
|
||||
import React, {useState} from "react";
|
||||
import React, {useMemo, useState} from "react";
|
||||
|
||||
interface FieldListMenuProps
|
||||
{
|
||||
idPrefix: string;
|
||||
heading?: string;
|
||||
placeholder?: string;
|
||||
tableMetaData: QTableMetaData;
|
||||
showTableHeaderEvenIfNoExposedJoins: boolean;
|
||||
fieldNamesToHide?: string[];
|
||||
buttonProps: any;
|
||||
buttonChildren: JSX.Element | string;
|
||||
|
||||
isModeSelectOne?: boolean;
|
||||
handleSelectedField?: (field: QFieldMetaData, table: QTableMetaData) => void;
|
||||
|
||||
isModeToggle?: boolean;
|
||||
toggleStates?: {[fieldName: string]: boolean};
|
||||
handleToggleField?: (field: QFieldMetaData, table: QTableMetaData, newValue: boolean) => void;
|
||||
|
||||
fieldEndAdornment?: JSX.Element
|
||||
handleAdornmentClick?: (field: QFieldMetaData, table: QTableMetaData, event: React.MouseEvent<any>) => void;
|
||||
idPrefix: string,
|
||||
heading?: string,
|
||||
placeholder?: string,
|
||||
tableMetaData: QTableMetaData,
|
||||
showTableHeaderEvenIfNoExposedJoins: boolean,
|
||||
fieldNamesToHide?: string[],
|
||||
buttonProps: any,
|
||||
buttonChildren: JSX.Element | string,
|
||||
isModeSelectOne?: boolean,
|
||||
handleSelectedField?: (field: QFieldMetaData, table: QTableMetaData) => void,
|
||||
isModeToggle?: boolean,
|
||||
toggleStates?: { [fieldName: string]: boolean },
|
||||
handleToggleField?: (field: QFieldMetaData, table: QTableMetaData, newValue: boolean) => void,
|
||||
fieldEndAdornment?: JSX.Element,
|
||||
handleAdornmentClick?: (field: QFieldMetaData, table: QTableMetaData, event: React.MouseEvent<any>) => void,
|
||||
omitExposedJoins?: string[]
|
||||
}
|
||||
|
||||
FieldListMenu.defaultProps = {
|
||||
@ -71,38 +70,52 @@ interface TableWithFields
|
||||
** Component to render a list of fields from a table (and its join tables)
|
||||
** which can be interacted with...
|
||||
*******************************************************************************/
|
||||
export default function FieldListMenu({idPrefix, heading, placeholder, tableMetaData, showTableHeaderEvenIfNoExposedJoins, buttonProps, buttonChildren, isModeSelectOne, fieldNamesToHide, handleSelectedField, isModeToggle, toggleStates, handleToggleField, fieldEndAdornment, handleAdornmentClick}: FieldListMenuProps): JSX.Element
|
||||
export default function FieldListMenu({idPrefix, heading, placeholder, tableMetaData, showTableHeaderEvenIfNoExposedJoins, buttonProps, buttonChildren, isModeSelectOne, fieldNamesToHide, handleSelectedField, isModeToggle, toggleStates, handleToggleField, fieldEndAdornment, handleAdornmentClick, omitExposedJoins}: FieldListMenuProps): JSX.Element
|
||||
{
|
||||
const [menuAnchorElement, setMenuAnchorElement] = useState(null);
|
||||
const [searchText, setSearchText] = useState("");
|
||||
const [focusedIndex, setFocusedIndex] = useState(null as number);
|
||||
|
||||
const [fieldsByTable, setFieldsByTable] = useState([] as TableWithFields[]);
|
||||
const [collapsedTables, setCollapsedTables] = useState({} as {[tableName: string]: boolean});
|
||||
const [collapsedTables, setCollapsedTables] = useState({} as { [tableName: string]: boolean });
|
||||
|
||||
const [lastMouseOverXY, setLastMouseOverXY] = useState({x: 0, y: 0});
|
||||
const [timeOfLastArrow, setTimeOfLastArrow] = useState(0)
|
||||
const [timeOfLastArrow, setTimeOfLastArrow] = useState(0);
|
||||
|
||||
const availableExposedJoins = useMemo(() =>
|
||||
{
|
||||
const rs: QExposedJoin[] = []
|
||||
for(let exposedJoin of tableMetaData.exposedJoins ?? [])
|
||||
{
|
||||
if(omitExposedJoins?.indexOf(exposedJoin.joinTable.name) > -1)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
rs.push(exposedJoin);
|
||||
}
|
||||
return (rs);
|
||||
}, [tableMetaData, omitExposedJoins]);
|
||||
|
||||
//////////////////
|
||||
// check usages //
|
||||
//////////////////
|
||||
if(isModeSelectOne)
|
||||
if (isModeSelectOne)
|
||||
{
|
||||
if(!handleSelectedField)
|
||||
if (!handleSelectedField)
|
||||
{
|
||||
throw("In FieldListMenu, if isModeSelectOne=true, then a callback for handleSelectedField must be provided.");
|
||||
throw ("In FieldListMenu, if isModeSelectOne=true, then a callback for handleSelectedField must be provided.");
|
||||
}
|
||||
}
|
||||
|
||||
if(isModeToggle)
|
||||
if (isModeToggle)
|
||||
{
|
||||
if(!toggleStates)
|
||||
if (!toggleStates)
|
||||
{
|
||||
throw("In FieldListMenu, if isModeToggle=true, then a model for toggleStates must be provided.");
|
||||
throw ("In FieldListMenu, if isModeToggle=true, then a model for toggleStates must be provided.");
|
||||
}
|
||||
if(!handleToggleField)
|
||||
if (!handleToggleField)
|
||||
{
|
||||
throw("In FieldListMenu, if isModeToggle=true, then a callback for handleToggleField must be provided.");
|
||||
throw ("In FieldListMenu, if isModeToggle=true, then a callback for handleToggleField must be provided.");
|
||||
}
|
||||
}
|
||||
|
||||
@ -113,16 +126,16 @@ export default function FieldListMenu({idPrefix, heading, placeholder, tableMeta
|
||||
{
|
||||
collapsedTables[tableMetaData.name] = false;
|
||||
|
||||
if (tableMetaData.exposedJoins?.length > 0)
|
||||
if (availableExposedJoins?.length > 0)
|
||||
{
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// if we have exposed joins, put the table meta data with its fields, and then all of the join tables & fields too //
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
fieldsByTable.push({table: tableMetaData, fields: getTableFieldsAsAlphabeticalArray(tableMetaData)});
|
||||
|
||||
for (let i = 0; i < tableMetaData.exposedJoins?.length; i++)
|
||||
for (let i = 0; i < availableExposedJoins?.length; i++)
|
||||
{
|
||||
const joinTable = tableMetaData.exposedJoins[i].joinTable;
|
||||
const joinTable = availableExposedJoins[i].joinTable;
|
||||
fieldsByTable.push({table: joinTable, fields: getTableFieldsAsAlphabeticalArray(joinTable)});
|
||||
|
||||
collapsedTables[joinTable.name] = false;
|
||||
@ -150,16 +163,16 @@ export default function FieldListMenu({idPrefix, heading, placeholder, tableMeta
|
||||
table.fields.forEach(field =>
|
||||
{
|
||||
let fullFieldName = field.name;
|
||||
if(table.name != tableMetaData.name)
|
||||
if (table.name != tableMetaData.name)
|
||||
{
|
||||
fullFieldName = `${table.name}.${field.name}`;
|
||||
}
|
||||
|
||||
if(fieldNamesToHide && fieldNamesToHide.indexOf(fullFieldName) > -1)
|
||||
if (fieldNamesToHide && fieldNamesToHide.indexOf(fullFieldName) > -1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
fields.push(field)
|
||||
fields.push(field);
|
||||
});
|
||||
fields.sort((a, b) => a.label.localeCompare(b.label));
|
||||
return (fields);
|
||||
@ -181,7 +194,7 @@ export default function FieldListMenu({idPrefix, heading, placeholder, tableMeta
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
function getShownFieldAndTableByIndex(targetIndex: number): {field: QFieldMetaData, table: QTableMetaData}
|
||||
function getShownFieldAndTableByIndex(targetIndex: number): { field: QFieldMetaData, table: QTableMetaData }
|
||||
{
|
||||
let index = -1;
|
||||
for (let i = 0; i < fieldsByTableToShow.length; i++)
|
||||
@ -191,9 +204,9 @@ export default function FieldListMenu({idPrefix, heading, placeholder, tableMeta
|
||||
{
|
||||
index++;
|
||||
|
||||
if(index == targetIndex)
|
||||
if (index == targetIndex)
|
||||
{
|
||||
return {field: tableWithField.fields[j], table: tableWithField.table}
|
||||
return {field: tableWithField.fields[j], table: tableWithField.table};
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -210,7 +223,7 @@ export default function FieldListMenu({idPrefix, heading, placeholder, tableMeta
|
||||
// console.log(`Event key: ${event.key}`);
|
||||
setTimeout(() => document.getElementById(`field-list-dropdown-${idPrefix}-textField`).focus());
|
||||
|
||||
if(isModeSelectOne && event.key == "Enter" && focusedIndex != null)
|
||||
if (isModeSelectOne && event.key == "Enter" && focusedIndex != null)
|
||||
{
|
||||
setTimeout(() =>
|
||||
{
|
||||
@ -249,13 +262,13 @@ export default function FieldListMenu({idPrefix, heading, placeholder, tableMeta
|
||||
/////////////////
|
||||
// a down move //
|
||||
/////////////////
|
||||
if(startIndex == null)
|
||||
if (startIndex == null)
|
||||
{
|
||||
startIndex = -1;
|
||||
}
|
||||
|
||||
let goalIndex = startIndex + offset;
|
||||
if(goalIndex > maxFieldIndex - 1)
|
||||
if (goalIndex > maxFieldIndex - 1)
|
||||
{
|
||||
goalIndex = maxFieldIndex - 1;
|
||||
}
|
||||
@ -268,7 +281,7 @@ export default function FieldListMenu({idPrefix, heading, placeholder, tableMeta
|
||||
// an up move //
|
||||
////////////////
|
||||
let goalIndex = startIndex + offset;
|
||||
if(goalIndex < 0)
|
||||
if (goalIndex < 0)
|
||||
{
|
||||
goalIndex = 0;
|
||||
}
|
||||
@ -335,7 +348,7 @@ export default function FieldListMenu({idPrefix, heading, placeholder, tableMeta
|
||||
// the last x/y isn't really useful, because the mouse generally isn't left exactly where it was when the mouse-over happened (edge of the element) //
|
||||
// but the keyboard last-arrow time that we capture, that's what's actually being useful in here //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
if(event.clientX == lastMouseOverXY.x && event.clientY == lastMouseOverXY.y)
|
||||
if (event.clientX == lastMouseOverXY.x && event.clientY == lastMouseOverXY.y)
|
||||
{
|
||||
// console.log("mouse didn't move, so, doesn't count");
|
||||
return;
|
||||
@ -343,7 +356,7 @@ export default function FieldListMenu({idPrefix, heading, placeholder, tableMeta
|
||||
|
||||
const now = new Date().getTime();
|
||||
// console.log(`Compare now [${now}] to last arrow [${timeOfLastArrow}] (diff: [${now - timeOfLastArrow}])`);
|
||||
if(now < timeOfLastArrow + 300)
|
||||
if (now < timeOfLastArrow + 300)
|
||||
{
|
||||
// console.log("An arrow event happened less than 300 mills ago, so doesn't count.");
|
||||
return;
|
||||
@ -480,7 +493,7 @@ export default function FieldListMenu({idPrefix, heading, placeholder, tableMeta
|
||||
for (let i = 0; i < fieldsList.length; i++)
|
||||
{
|
||||
const field = fieldsList[i];
|
||||
if(doesFieldMatchSearchText(field))
|
||||
if (doesFieldMatchSearchText(field))
|
||||
{
|
||||
handleToggleField(field, table, event.target.checked);
|
||||
}
|
||||
@ -491,18 +504,18 @@ export default function FieldListMenu({idPrefix, heading, placeholder, tableMeta
|
||||
/////////////////////////////////////////////////////////
|
||||
// compute the table-level toggle state & count values //
|
||||
/////////////////////////////////////////////////////////
|
||||
const tableToggleStates: {[tableName: string]: boolean} = {};
|
||||
const tableToggleCounts: {[tableName: string]: number} = {};
|
||||
const tableToggleStates: { [tableName: string]: boolean } = {};
|
||||
const tableToggleCounts: { [tableName: string]: number } = {};
|
||||
|
||||
if(isModeToggle)
|
||||
if (isModeToggle)
|
||||
{
|
||||
const {allOn, count} = getTableToggleState(tableMetaData, true);
|
||||
tableToggleStates[tableMetaData.name] = allOn;
|
||||
tableToggleCounts[tableMetaData.name] = count;
|
||||
|
||||
for (let i = 0; i < tableMetaData.exposedJoins?.length; i++)
|
||||
for (let i = 0; i < availableExposedJoins?.length; i++)
|
||||
{
|
||||
const join = tableMetaData.exposedJoins[i];
|
||||
const join = availableExposedJoins[i];
|
||||
const {allOn, count} = getTableToggleState(join.joinTable, false);
|
||||
tableToggleStates[join.joinTable.name] = allOn;
|
||||
tableToggleCounts[join.joinTable.name] = count;
|
||||
@ -513,7 +526,7 @@ export default function FieldListMenu({idPrefix, heading, placeholder, tableMeta
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
function getTableToggleState(table: QTableMetaData, isMainTable: boolean): {allOn: boolean, count: number}
|
||||
function getTableToggleState(table: QTableMetaData, isMainTable: boolean): { allOn: boolean, count: number }
|
||||
{
|
||||
const fieldsList = [...table.fields.values()];
|
||||
let allOn = true;
|
||||
@ -522,7 +535,7 @@ export default function FieldListMenu({idPrefix, heading, placeholder, tableMeta
|
||||
{
|
||||
const field = fieldsList[i];
|
||||
const name = isMainTable ? field.name : `${table.name}.${field.name}`;
|
||||
if(!toggleStates[name])
|
||||
if (!toggleStates[name])
|
||||
{
|
||||
allOn = false;
|
||||
}
|
||||
@ -541,7 +554,7 @@ export default function FieldListMenu({idPrefix, heading, placeholder, tableMeta
|
||||
*******************************************************************************/
|
||||
function toggleCollapsedTable(tableName: string)
|
||||
{
|
||||
collapsedTables[tableName] = !collapsedTables[tableName]
|
||||
collapsedTables[tableName] = !collapsedTables[tableName];
|
||||
setCollapsedTables(Object.assign({}, collapsedTables));
|
||||
}
|
||||
|
||||
@ -559,7 +572,7 @@ export default function FieldListMenu({idPrefix, heading, placeholder, tableMeta
|
||||
|
||||
let index = -1;
|
||||
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) //
|
||||
@ -607,12 +620,12 @@ export default function FieldListMenu({idPrefix, heading, placeholder, tableMeta
|
||||
{
|
||||
let headerContents = null;
|
||||
const headerTable = tableWithFields.table || tableMetaData;
|
||||
if(tableWithFields.table || showTableHeaderEvenIfNoExposedJoins)
|
||||
if (tableWithFields.table || showTableHeaderEvenIfNoExposedJoins)
|
||||
{
|
||||
headerContents = (<b>{headerTable.label} Fields</b>);
|
||||
}
|
||||
|
||||
if(isModeToggle)
|
||||
if (isModeToggle)
|
||||
{
|
||||
headerContents = (<FormControlLabel
|
||||
sx={{display: "flex", alignItems: "flex-start", "& .MuiFormControlLabel-label": {lineHeight: "1.4", fontWeight: "500 !important"}}}
|
||||
@ -622,10 +635,10 @@ export default function FieldListMenu({idPrefix, heading, placeholder, tableMeta
|
||||
checked={tableToggleStates[headerTable.name]}
|
||||
onChange={(event) => handleTableToggle(event, headerTable)}
|
||||
/>}
|
||||
label={<span style={{marginTop: "0.25rem", display: "inline-block"}}><b>{headerTable.label} Fields</b> <span style={{fontWeight: 400}}>({tableToggleCounts[headerTable.name]})</span></span>} />)
|
||||
label={<span style={{marginTop: "0.25rem", display: "inline-block"}}><b>{headerTable.label} Fields</b> <span style={{fontWeight: 400}}>({tableToggleCounts[headerTable.name]})</span></span>} />);
|
||||
}
|
||||
|
||||
if(isModeToggle)
|
||||
if (isModeToggle)
|
||||
{
|
||||
headerContents = (
|
||||
<>
|
||||
@ -638,11 +651,11 @@ export default function FieldListMenu({idPrefix, heading, placeholder, tableMeta
|
||||
</IconButton>
|
||||
{headerContents}
|
||||
</>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
let marginLeft = "unset";
|
||||
if(isModeToggle)
|
||||
if (isModeToggle)
|
||||
{
|
||||
marginLeft = "-1rem";
|
||||
}
|
||||
@ -652,14 +665,14 @@ export default function FieldListMenu({idPrefix, heading, placeholder, tableMeta
|
||||
return (
|
||||
<React.Fragment key={tableWithFields.table?.name ?? "theTable"}>
|
||||
<>
|
||||
{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>}
|
||||
{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) =>
|
||||
{
|
||||
index++;
|
||||
const key = `${tableWithFields.table?.name}-${field.name}`
|
||||
const key = `${tableWithFields.table?.name}-${field.name}`;
|
||||
|
||||
if(collapsedTables[headerTable.name])
|
||||
if (collapsedTables[headerTable.name])
|
||||
{
|
||||
return (<React.Fragment key={key} />);
|
||||
}
|
||||
@ -677,13 +690,13 @@ export default function FieldListMenu({idPrefix, heading, placeholder, tableMeta
|
||||
{
|
||||
closeMenu();
|
||||
handleSelectedField(field, tableWithFields.table ?? tableMetaData);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
let label: JSX.Element | string = field.label;
|
||||
const fullFieldName = tableWithFields.table && tableWithFields.table.name != tableMetaData.name ? `${tableWithFields.table.name}.${field.name}` : field.name;
|
||||
|
||||
if(fieldEndAdornment)
|
||||
if (fieldEndAdornment)
|
||||
{
|
||||
label = <Box width="100%" display="inline-flex" justifyContent="space-between">
|
||||
{label}
|
||||
|
@ -33,6 +33,7 @@ import MenuItem from "@mui/material/MenuItem";
|
||||
import Select, {SelectChangeEvent} from "@mui/material/Select/Select";
|
||||
import TextField from "@mui/material/TextField";
|
||||
import Tooltip from "@mui/material/Tooltip";
|
||||
import {omit} from "lodash";
|
||||
import FieldAutoComplete from "qqq/components/misc/FieldAutoComplete";
|
||||
import FilterCriteriaRowValues from "qqq/components/query/FilterCriteriaRowValues";
|
||||
import {QueryScreenUsage} from "qqq/pages/records/query/RecordQuery";
|
||||
@ -190,17 +191,18 @@ export const getOperatorOptions = (tableMetaData: QTableMetaData, fieldName: str
|
||||
|
||||
interface FilterCriteriaRowProps
|
||||
{
|
||||
id: number;
|
||||
index: number;
|
||||
tableMetaData: QTableMetaData;
|
||||
metaData: QInstance;
|
||||
criteria: QFilterCriteria;
|
||||
booleanOperator: "AND" | "OR" | null;
|
||||
updateCriteria: (newCriteria: QFilterCriteria, needDebounce: boolean) => void;
|
||||
removeCriteria: () => void;
|
||||
updateBooleanOperator: (newValue: string) => void;
|
||||
queryScreenUsage?: QueryScreenUsage;
|
||||
allowVariables?: boolean;
|
||||
id: number,
|
||||
index: number,
|
||||
tableMetaData: QTableMetaData,
|
||||
metaData: QInstance,
|
||||
criteria: QFilterCriteria,
|
||||
booleanOperator: "AND" | "OR" | null,
|
||||
updateCriteria: (newCriteria: QFilterCriteria, needDebounce: boolean) => void,
|
||||
removeCriteria: () => void,
|
||||
updateBooleanOperator: (newValue: string) => void,
|
||||
queryScreenUsage?: QueryScreenUsage,
|
||||
allowVariables?: boolean,
|
||||
omitExposedJoins?: string[]
|
||||
}
|
||||
|
||||
FilterCriteriaRow.defaultProps =
|
||||
@ -269,7 +271,7 @@ export function validateCriteria(criteria: QFilterCriteria, operatorSelectedValu
|
||||
return {criteriaIsValid, criteriaStatusTooltip};
|
||||
}
|
||||
|
||||
export function FilterCriteriaRow({id, index, tableMetaData, metaData, criteria, booleanOperator, updateCriteria, removeCriteria, updateBooleanOperator, queryScreenUsage, allowVariables}: FilterCriteriaRowProps): JSX.Element
|
||||
export function FilterCriteriaRow({id, index, tableMetaData, metaData, criteria, booleanOperator, updateCriteria, removeCriteria, updateBooleanOperator, queryScreenUsage, allowVariables, omitExposedJoins}: FilterCriteriaRowProps): JSX.Element
|
||||
{
|
||||
// console.log(`FilterCriteriaRow: criteria: ${JSON.stringify(criteria)}`);
|
||||
const [operatorSelectedValue, setOperatorSelectedValue] = useState(null as OperatorOption);
|
||||
@ -488,7 +490,7 @@ export function FilterCriteriaRow({id, index, tableMetaData, metaData, criteria,
|
||||
</Box>
|
||||
<Box display="inline-block" width={250} className="fieldColumn">
|
||||
<FieldAutoComplete id={`field-${id}`} metaData={metaData} tableMetaData={tableMetaData} defaultValue={defaultFieldValue} handleFieldChange={handleFieldChange}
|
||||
autocompleteSlotProps={{popper: {className: "filterCriteriaRowColumnPopper", style: {padding: 0, width: "250px"}}}}
|
||||
omitExposedJoins={omitExposedJoins} autocompleteSlotProps={{popper: {className: "filterCriteriaRowColumnPopper", style: {padding: 0, width: "250px"}}}}
|
||||
/>
|
||||
</Box>
|
||||
<Box display="inline-block" width={200} className="operatorColumn">
|
||||
|
@ -44,7 +44,7 @@ import RecordQuery from "qqq/pages/records/query/RecordQuery";
|
||||
import Client from "qqq/utils/qqq/Client";
|
||||
import FilterUtils from "qqq/utils/qqq/FilterUtils";
|
||||
import TableUtils from "qqq/utils/qqq/TableUtils";
|
||||
import React, {useContext, useEffect, useRef, useState} from "react";
|
||||
import React, {useContext, useEffect, useMemo, useRef, useState} from "react";
|
||||
|
||||
interface FilterAndColumnsSetupWidgetProps
|
||||
{
|
||||
@ -106,6 +106,8 @@ export default function FilterAndColumnsSetupWidget({isEditable: isEditableProp,
|
||||
const [warning, setWarning] = useState(null as string);
|
||||
const [widgetFailureAlertContent, setWidgetFailureAlertContent] = useState(null as string);
|
||||
|
||||
const omitExposedJoins: string[] = widgetData?.omitExposedJoins ?? [];
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// we'll actually keep 2 copies of the query filter around here - //
|
||||
// the one in the record (as json) is one that the backend likes (e.g., possible values as ids) //
|
||||
@ -441,7 +443,7 @@ export default function FilterAndColumnsSetupWidget({isEditable: isEditableProp,
|
||||
</Collapse>
|
||||
<Box pt="0.5rem">
|
||||
<Box display="flex" justifyContent="space-between" alignItems="center">
|
||||
<h5>{label ?? "Query Filter"}</h5>
|
||||
<h5>{label ?? widgetData.label ?? widgetMetaData.label ?? "Query Filter"}</h5>
|
||||
{!hideSortBy && <Box fontSize="0.75rem" fontWeight="700">{mayShowQuery() && getCurrentSortIndicator(frontendQueryFilter, tableMetaData, null)}</Box>}
|
||||
</Box>
|
||||
{
|
||||
@ -454,7 +456,7 @@ export default function FilterAndColumnsSetupWidget({isEditable: isEditableProp,
|
||||
{
|
||||
isEditable &&
|
||||
<Tooltip title={selectTableFirstTooltipTitle}>
|
||||
<span><Button disabled={!recordValues["tableName"]} sx={{mb: "0.125rem", ...unborderedButtonSX}} onClick={openEditor}>+ Add Filters</Button></span>
|
||||
<span><Button disabled={tableMetaData == null} sx={{mb: "0.125rem", ...unborderedButtonSX}} onClick={openEditor}>+ Add Filters</Button></span>
|
||||
</Tooltip>
|
||||
}
|
||||
{
|
||||
@ -501,6 +503,7 @@ export default function FilterAndColumnsSetupWidget({isEditable: isEditableProp,
|
||||
initialQueryFilter={frontendQueryFilter}
|
||||
initialColumns={columns}
|
||||
apiVersion={apiVersion}
|
||||
omitExposedJoins={omitExposedJoins}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
@ -510,7 +513,7 @@ export default function FilterAndColumnsSetupWidget({isEditable: isEditableProp,
|
||||
<div>
|
||||
<Box sx={{position: "absolute", overflowY: "auto", maxHeight: "100%", width: "100%"}}>
|
||||
<Card sx={{m: "2rem", p: "2rem"}}>
|
||||
<h3>Edit Filters and Columns</h3>
|
||||
<h3>Edit Filters {hideColumns ? "" : " and Columns"}</h3>
|
||||
{
|
||||
showHelp("modalSubheader") &&
|
||||
<Box color={colors.gray.main} pb={"0.5rem"}>
|
||||
@ -527,6 +530,7 @@ export default function FilterAndColumnsSetupWidget({isEditable: isEditableProp,
|
||||
initialQueryFilter={usingDefaultEmptyFilter ? null : frontendQueryFilter}
|
||||
initialColumns={columns}
|
||||
apiVersion={apiVersion}
|
||||
omitExposedJoins={omitExposedJoins}
|
||||
/>
|
||||
}
|
||||
|
||||
|
@ -89,15 +89,16 @@ export type QueryScreenUsage = "queryScreen" | "reportSetup"
|
||||
|
||||
interface Props
|
||||
{
|
||||
table?: QTableMetaData;
|
||||
apiVersion?: ApiVersion;
|
||||
launchProcess?: QProcessMetaData;
|
||||
usage?: QueryScreenUsage;
|
||||
isModal?: boolean;
|
||||
isPreview?: boolean;
|
||||
initialQueryFilter?: QQueryFilter;
|
||||
initialColumns?: QQueryColumns;
|
||||
allowVariables?: boolean;
|
||||
table?: QTableMetaData,
|
||||
apiVersion?: ApiVersion,
|
||||
launchProcess?: QProcessMetaData,
|
||||
usage?: QueryScreenUsage,
|
||||
isModal?: boolean,
|
||||
isPreview?: boolean,
|
||||
initialQueryFilter?: QQueryFilter,
|
||||
initialColumns?: QQueryColumns,
|
||||
allowVariables?: boolean,
|
||||
omitExposedJoins?: string[]
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////
|
||||
@ -130,7 +131,7 @@ const getLoadingScreen = (isModal: boolean) =>
|
||||
**
|
||||
** Yuge component. The best. Lots of very smart people are saying so.
|
||||
*******************************************************************************/
|
||||
const RecordQuery = forwardRef(({table, apiVersion, usage, isModal, isPreview, allowVariables, initialQueryFilter, initialColumns}: Props, ref) =>
|
||||
const RecordQuery = forwardRef(({table, apiVersion, usage, isModal, isPreview, allowVariables, initialQueryFilter, initialColumns, omitExposedJoins}: Props, ref) =>
|
||||
{
|
||||
const tableName = table.name;
|
||||
const [searchParams] = useSearchParams();
|
||||
@ -2834,6 +2835,7 @@ const RecordQuery = forwardRef(({table, apiVersion, usage, isModal, isPreview, a
|
||||
idPrefix="columns"
|
||||
tableMetaData={tableMetaData}
|
||||
showTableHeaderEvenIfNoExposedJoins={true}
|
||||
omitExposedJoins={omitExposedJoins}
|
||||
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></>}
|
||||
@ -2975,6 +2977,7 @@ const RecordQuery = forwardRef(({table, apiVersion, usage, isModal, isPreview, a
|
||||
setMode={doSetMode}
|
||||
savedViewsComponent={savedViewsComponent}
|
||||
columnMenuComponent={buildColumnMenu()}
|
||||
omitExposedJoins={omitExposedJoins}
|
||||
/>
|
||||
}
|
||||
|
||||
@ -3000,7 +3003,8 @@ const RecordQuery = forwardRef(({table, apiVersion, usage, isModal, isPreview, a
|
||||
metaData: metaData,
|
||||
queryFilter: queryFilter,
|
||||
updateFilter: doSetQueryFilter,
|
||||
allowVariables: allowVariables
|
||||
allowVariables: allowVariables,
|
||||
omitExposedJoins: omitExposedJoins,
|
||||
}
|
||||
}}
|
||||
localeText={{
|
||||
|
Reference in New Issue
Block a user