diff --git a/src/qqq/components/misc/FieldAutoComplete.tsx b/src/qqq/components/misc/FieldAutoComplete.tsx index 2cd297b..3b78dc2 100644 --- a/src/qqq/components/misc/FieldAutoComplete.tsx +++ b/src/qqq/components/misc/FieldAutoComplete.tsx @@ -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} ; - return () + return (); }} // @ts-ignore defaultValue={defaultValue} diff --git a/src/qqq/components/query/BasicAndAdvancedQueryControls.tsx b/src/qqq/components/query/BasicAndAdvancedQueryControls.tsx index 06ef0d6..fbfa9bb 100644 --- a/src/qqq/components/query/BasicAndAdvancedQueryControls.tsx +++ b/src/qqq/components/query/BasicAndAdvancedQueryControls.tsx @@ -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={arrow_upwardarrow_downward} 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} /> } diff --git a/src/qqq/components/query/CustomFilterPanel.tsx b/src/qqq/components/query/CustomFilterPanel.tsx index ded1bd0..6ba9e8c 100644 --- a/src/qqq/components/query/CustomFilterPanel.tsx +++ b/src/qqq/components/query/CustomFilterPanel.tsx @@ -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( updateBooleanOperator={(newValue) => updateBooleanOperator(newValue)} allowVariables={props.allowVariables} queryScreenUsage={props.queryScreenUsage} + omitExposedJoins={props.omitExposedJoins} /> {/*JSON.stringify(criteria)*/} diff --git a/src/qqq/components/query/FieldListMenu.tsx b/src/qqq/components/query/FieldListMenu.tsx index 2436d57..63001b8 100644 --- a/src/qqq/components/query/FieldListMenu.tsx +++ b/src/qqq/components/query/FieldListMenu.tsx @@ -19,6 +19,7 @@ * along with this program. If not, see . */ +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) => 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) => 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 = ({headerTable.label} Fields); } - if(isModeToggle) + if (isModeToggle) { headerContents = ( handleTableToggle(event, headerTable)} />} - label={{headerTable.label} Fields ({tableToggleCounts[headerTable.name]})} />) + label={{headerTable.label} Fields ({tableToggleCounts[headerTable.name]})} />); } - if(isModeToggle) + if (isModeToggle) { headerContents = ( <> @@ -638,11 +651,11 @@ export default function FieldListMenu({idPrefix, heading, placeholder, tableMeta {headerContents} - ) + ); } let marginLeft = "unset"; - if(isModeToggle) + if (isModeToggle) { marginLeft = "-1rem"; } @@ -652,14 +665,14 @@ export default function FieldListMenu({idPrefix, heading, placeholder, tableMeta return ( <> - {headerContents && {headerContents}} + {headerContents && {headerContents}} { 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 (); } @@ -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 = {label} diff --git a/src/qqq/components/query/FilterCriteriaRow.tsx b/src/qqq/components/query/FilterCriteriaRow.tsx index d35d046..dc45032 100644 --- a/src/qqq/components/query/FilterCriteriaRow.tsx +++ b/src/qqq/components/query/FilterCriteriaRow.tsx @@ -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, diff --git a/src/qqq/components/widgets/misc/FilterAndColumnsSetupWidget.tsx b/src/qqq/components/widgets/misc/FilterAndColumnsSetupWidget.tsx index 4a22fe9..49e3faa 100644 --- a/src/qqq/components/widgets/misc/FilterAndColumnsSetupWidget.tsx +++ b/src/qqq/components/widgets/misc/FilterAndColumnsSetupWidget.tsx @@ -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, -
{label ?? "Query Filter"}
+
{label ?? widgetData.label ?? widgetMetaData.label ?? "Query Filter"}
{!hideSortBy && {mayShowQuery() && getCurrentSortIndicator(frontendQueryFilter, tableMetaData, null)}}
{ @@ -454,7 +456,7 @@ export default function FilterAndColumnsSetupWidget({isEditable: isEditableProp, { isEditable && - + } { @@ -501,6 +503,7 @@ export default function FilterAndColumnsSetupWidget({isEditable: isEditableProp, initialQueryFilter={frontendQueryFilter} initialColumns={columns} apiVersion={apiVersion} + omitExposedJoins={omitExposedJoins} />
)} @@ -510,7 +513,7 @@ export default function FilterAndColumnsSetupWidget({isEditable: isEditableProp,
-

Edit Filters and Columns

+

Edit Filters {hideColumns ? "" : " and Columns"}

{ showHelp("modalSubheader") && @@ -527,6 +530,7 @@ export default function FilterAndColumnsSetupWidget({isEditable: isEditableProp, initialQueryFilter={usingDefaultEmptyFilter ? null : frontendQueryFilter} initialColumns={columns} apiVersion={apiVersion} + omitExposedJoins={omitExposedJoins} /> } diff --git a/src/qqq/pages/records/query/RecordQuery.tsx b/src/qqq/pages/records/query/RecordQuery.tsx index fc4e606..0c199f7 100644 --- a/src/qqq/pages/records/query/RecordQuery.tsx +++ b/src/qqq/pages/records/query/RecordQuery.tsx @@ -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={<>view_week_outline Columns ({view.queryColumns.getVisibleColumnCount()}) keyboard_arrow_down} @@ -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={{