{
selectFullFilterState === "checked" && (
The
{` ${selectedIds.length.toLocaleString()} `}
- records on this page are selected.
-
+ {joinIsMany ? " distinct " : ""}
+ record{selectedIds.length == 1 ? "" : "s"} on this page {selectedIds.length == 1 ? "is" : "are"} selected.
)
}
{
selectFullFilterState === "filter" && (
- All
- {` ${totalRecords ? totalRecords.toLocaleString() : ""} `}
- records matching this query are selected.
-
+ {
+ (joinIsMany
+ ? (
+ distinctRecords == 1
+ ? (<>The only 1 distinct record matching this query is selected.>)
+ : (<>All {(distinctRecords ? distinctRecords.toLocaleString() : "")} distinct records matching this query are selected.>)
+ )
+ : (<>All {totalRecords ? totalRecords.toLocaleString() : ""} records matching this query are selected.>)
+ )
+ }
+
+ )
+ }
+ {
+ selectFullFilterState === "filterSubset" && (
+
+ )
+ }
+ {
+ (selectFullFilterState === "n/a" && selectedIds.length > 0) && (
+
+ {safeToLocaleString(selectedIds.length)} {joinIsMany ? "distinct" : ""} {selectedIds.length == 1 ? "record is" : "records are"} selected.
)
}
@@ -1170,28 +1643,28 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
const pushDividerIfNeeded = (menuItems: JSX.Element[]) =>
{
- if(menuItems.length > 0)
+ if (menuItems.length > 0)
{
menuItems.push(
);
}
- }
+ };
const menuItems: JSX.Element[] = [];
- if(table.capabilities.has(Capability.TABLE_INSERT) && table.insertPermission)
+ if (table.capabilities.has(Capability.TABLE_INSERT) && table.insertPermission)
{
- menuItems.push(
)
+ menuItems.push(
);
}
- if(table.capabilities.has(Capability.TABLE_UPDATE) && table.editPermission)
+ if (table.capabilities.has(Capability.TABLE_UPDATE) && table.editPermission)
{
- menuItems.push(
)
+ menuItems.push(
);
}
- if(table.capabilities.has(Capability.TABLE_DELETE) && table.deletePermission)
+ if (table.capabilities.has(Capability.TABLE_DELETE) && table.deletePermission)
{
- menuItems.push(
)
+ menuItems.push(
);
}
const runRecordScriptProcess = metaData?.processes.get("runRecordScript");
- if(runRecordScriptProcess)
+ if (runRecordScriptProcess)
{
const process = runRecordScriptProcess;
menuItems.push(
);
@@ -1199,7 +1672,7 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
menuItems.push(
);
- if(tableProcesses && tableProcesses.length)
+ if (tableProcesses && tableProcesses.length)
{
pushDividerIfNeeded(menuItems);
}
@@ -1210,9 +1683,9 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
menuItems.push(
);
});
- if(menuItems.length === 0)
+ if (menuItems.length === 0)
{
- menuItems.push(
)
+ menuItems.push(
);
}
const renderActionsMenu = (
@@ -1240,7 +1713,7 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
///////////////////////////////////////////////////////////////////////////////////////////
useEffect(() =>
{
- if(latestQueryId > 0)
+ if (latestQueryId > 0)
{
////////////////////////////////////////////////////////////////////////////////////////
// to avoid both this useEffect and the one below from both doing an "initial query", //
@@ -1248,7 +1721,7 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
////////////////////////////////////////////////////////////////////////////////////////
updateTable();
}
- }, [ pageNumber, rowsPerPage, columnSortModel, currentSavedFilter ]);
+ }, [pageNumber, rowsPerPage, columnSortModel, currentSavedFilter]);
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
// for state changes that DO change the filter, call to update the table - and DO clear out the totalRecords //
@@ -1256,16 +1729,17 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
useEffect(() =>
{
setTotalRecords(null);
+ setDistinctRecords(null);
updateTable();
- }, [ tableState, filterModel]);
+ }, [columnsModel, tableState, filterModel]);
useEffect(() =>
{
document.documentElement.scrollTop = 0;
document.scrollingElement.scrollTop = 0;
- }, [ pageNumber, rowsPerPage ]);
+ }, [pageNumber, rowsPerPage]);
- if(tableMetaData && !tableMetaData.readPermission)
+ if (tableMetaData && !tableMetaData.readPermission)
{
return (
@@ -1327,7 +1801,7 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
}
-
+
@@ -1343,7 +1817,7 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
(params.indexRelativeToCurrentPage % 2 === 0 ? "even" : "odd")}
+ getRowId={(row) => row.__rowIndex}
+ selectionModel={rowSelectionModel}
+ hideFooterSelectedRowCount={true}
/>
@@ -1410,4 +1887,49 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
);
}
+//////////////////////////////////////////////////////////////////////////////////
+// mini-component that is the dialog for the user to enter the selection-subset //
+//////////////////////////////////////////////////////////////////////////////////
+function SelectionSubsetDialog(props: {isOpen: boolean; initialValue: number; closeHandler: (value?: number) => void})
+{
+ const [value, setValue] = useState(props.initialValue)
+
+ const handleChange = (newValue: string) =>
+ {
+ setValue(parseInt(newValue))
+ }
+
+ const keyPressed = (e: React.KeyboardEvent) =>
+ {
+ if(e.key == "Enter" && value)
+ {
+ props.closeHandler(value);
+ }
+ }
+
+ return (
+
+ )
+}
+
+
+
export default RecordQuery;
diff --git a/src/qqq/styles/qqq-override-styles.css b/src/qqq/styles/qqq-override-styles.css
index 3f8024f..160e1aa 100644
--- a/src/qqq/styles/qqq-override-styles.css
+++ b/src/qqq/styles/qqq-override-styles.css
@@ -124,7 +124,6 @@
.MuiDataGrid-toolbarContainer .selectionTool
{
- margin-left: 40px;
font-size: 14px;
}
diff --git a/src/qqq/utils/DataGridUtils.tsx b/src/qqq/utils/DataGridUtils.tsx
index 99c5d72..54d3243 100644
--- a/src/qqq/utils/DataGridUtils.tsx
+++ b/src/qqq/utils/DataGridUtils.tsx
@@ -22,10 +22,12 @@
import {AdornmentType} from "@kingsrook/qqq-frontend-core/lib/model/metaData/AdornmentType";
import {QFieldMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldMetaData";
import {QFieldType} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldType";
+import {QInstance} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QInstance";
import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData";
import {QRecord} from "@kingsrook/qqq-frontend-core/lib/model/QRecord";
import {getGridDateOperators, GridColDef, GridRowsProp} from "@mui/x-data-grid-pro";
import {GridFilterOperator} from "@mui/x-data-grid/models/gridFilterOperator";
+import React from "react";
import {Link} from "react-router-dom";
import {buildQGridPvsOperators, QGridBooleanOperators, QGridNumericOperators, QGridStringOperators} from "qqq/pages/records/query/GridFilterOperators";
import ValueUtils from "qqq/utils/qqq/ValueUtils";
@@ -36,24 +38,39 @@ export default class DataGridUtils
/*******************************************************************************
**
*******************************************************************************/
- public static makeRows = (results: QRecord[], tableMetaData: QTableMetaData): {rows: GridRowsProp[], columnsToRender: any} =>
+ public static makeRows = (results: QRecord[], tableMetaData: QTableMetaData): GridRowsProp[] =>
{
const fields = [ ...tableMetaData.fields.values() ];
const rows = [] as any[];
- const columnsToRender = {} as any;
+ let rowIndex = 0;
results.forEach((record: QRecord) =>
{
const row: any = {};
+ row.__rowIndex = rowIndex++;
+
fields.forEach((field) =>
{
- const value = ValueUtils.getDisplayValue(field, record, "query");
- if (typeof value !== "string")
- {
- columnsToRender[field.name] = true;
- }
- row[field.name] = value;
+ row[field.name] = ValueUtils.getDisplayValue(field, record, "query");
});
+ if(tableMetaData.exposedJoins)
+ {
+ for (let i = 0; i < tableMetaData.exposedJoins.length; i++)
+ {
+ const join = tableMetaData.exposedJoins[i];
+
+ if(join?.joinTable?.fields?.values())
+ {
+ const fields = [...join.joinTable.fields.values()];
+ fields.forEach((field) =>
+ {
+ let fieldName = join.joinTable.name + "." + field.name;
+ row[fieldName] = ValueUtils.getDisplayValue(field, record, "query", fieldName);
+ });
+ }
+ }
+ }
+
if(!row["id"])
{
row["id"] = record.values.get(tableMetaData.primaryKeyField) ?? row[tableMetaData.primaryKeyField];
@@ -69,70 +86,106 @@ export default class DataGridUtils
rows.push(row);
});
- /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- // do this secondary check for columnsToRender - in case we didn't have any rows above, and our check for string isn't enough. //
- // ... shouldn't this be just based on the field definition anyway... ? plus adornments? //
- /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- fields.forEach((field) =>
- {
- if(field.possibleValueSourceName)
- {
- columnsToRender[field.name] = true;
- }
- });
-
- return ({rows, columnsToRender});
+ return (rows);
}
/*******************************************************************************
**
*******************************************************************************/
- public static setupGridColumns = (tableMetaData: QTableMetaData, columnsToRender: any, linkBase: string = ""): GridColDef[] =>
+ public static setupGridColumns = (tableMetaData: QTableMetaData, linkBase: string = "", metaData?: QInstance, columnSort: "bySection" | "alphabetical" = "alphabetical"): GridColDef[] =>
{
const columns = [] as GridColDef[];
- const sortedKeys: string[] = [];
+ this.addColumnsForTable(tableMetaData, linkBase, columns, columnSort, null, null);
- for (let i = 0; i < tableMetaData.sections.length; i++)
+ if(tableMetaData.exposedJoins)
{
- const section = tableMetaData.sections[i];
- if(!section.fieldNames)
+ for (let i = 0; i < tableMetaData.exposedJoins.length; i++)
{
- continue;
- }
+ const join = tableMetaData.exposedJoins[i];
- for (let j = 0; j < section.fieldNames.length; j++)
- {
- sortedKeys.push(section.fieldNames[j]);
+ let joinLinkBase = null;
+ if(metaData)
+ {
+ joinLinkBase = metaData.getTablePath(join.joinTable);
+ joinLinkBase += joinLinkBase.endsWith("/") ? "" : "/";
+ }
+
+ if(join?.joinTable?.fields?.values())
+ {
+ this.addColumnsForTable(join.joinTable, joinLinkBase, columns, columnSort, join.joinTable.name + ".", join.label + ": ");
+ }
}
}
- sortedKeys.forEach((key) =>
- {
- const field = tableMetaData.fields.get(key);
- const column = this.makeColumnFromField(field, tableMetaData, columnsToRender);
-
- if (key === tableMetaData.primaryKeyField && linkBase)
- {
- columns.splice(0, 0, column);
- column.renderCell = (cellValues: any) => (
- {cellValues.value}
- );
- }
- else
- {
- columns.push(column);
- }
- });
-
return (columns);
};
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ private static addColumnsForTable(tableMetaData: QTableMetaData, linkBase: string, columns: GridColDef[], columnSort: "bySection" | "alphabetical" = "alphabetical", namePrefix?: string, labelPrefix?: string)
+ {
+ const sortedKeys: string[] = [];
+
+ ////////////////////////////////////////////////////////////////////////
+ // this sorted by sections - e.g., manual sorting by the meta-data... //
+ ////////////////////////////////////////////////////////////////////////
+ if(columnSort === "bySection")
+ {
+ for (let i = 0; i < tableMetaData.sections.length; i++)
+ {
+ const section = tableMetaData.sections[i];
+ if (!section.fieldNames)
+ {
+ continue;
+ }
+
+ for (let j = 0; j < section.fieldNames.length; j++)
+ {
+ sortedKeys.push(section.fieldNames[j]);
+ }
+ }
+ }
+ else // columnSort = "alphabetical"
+ {
+ ///////////////////////////
+ // sort by labels... mmm //
+ ///////////////////////////
+ sortedKeys.push(...tableMetaData.fields.keys())
+ sortedKeys.sort((a: string, b: string): number =>
+ {
+ return (tableMetaData.fields.get(a).label.localeCompare(tableMetaData.fields.get(b).label))
+ })
+ }
+
+ sortedKeys.forEach((key) =>
+ {
+ const field = tableMetaData.fields.get(key);
+ const column = this.makeColumnFromField(field, tableMetaData, namePrefix, labelPrefix);
+
+ if(key === tableMetaData.primaryKeyField && linkBase && namePrefix == null)
+ {
+ columns.splice(0, 0, column);
+ }
+ else
+ {
+ columns.push(column);
+ }
+
+ if (key === tableMetaData.primaryKeyField && linkBase)
+ {
+ column.renderCell = (cellValues: any) => (
+ e.stopPropagation()}>{cellValues.value}
+ );
+ }
+ });
+ }
/*******************************************************************************
**
*******************************************************************************/
- public static makeColumnFromField = (field: QFieldMetaData, tableMetaData: QTableMetaData, columnsToRender: any): GridColDef =>
+ public static makeColumnFromField = (field: QFieldMetaData, tableMetaData: QTableMetaData, namePrefix?: string, labelPrefix?: string): GridColDef =>
{
let columnType = "string";
let columnWidth = 200;
@@ -198,24 +251,21 @@ export default class DataGridUtils
}
}
+ let headerName = labelPrefix ? labelPrefix + field.label : field.label;
+ let fieldName = namePrefix ? namePrefix + field.name : field.name;
+
const column = {
- field: field.name,
+ field: fieldName,
type: columnType,
- headerName: field.label,
+ headerName: headerName,
width: columnWidth,
renderCell: null as any,
filterOperators: filterOperators,
};
- /////////////////////////////////////////////////////////////////////////////////////////
- // looks like, maybe we can just always render all columns, and remove this parameter? //
- /////////////////////////////////////////////////////////////////////////////////////////
- if (columnsToRender == null || columnsToRender[field.name])
- {
- column.renderCell = (cellValues: any) => (
- (cellValues.value)
- );
- }
+ column.renderCell = (cellValues: any) => (
+ (cellValues.value)
+ );
return (column);
}
diff --git a/src/qqq/utils/qqq/FilterUtils.ts b/src/qqq/utils/qqq/FilterUtils.ts
index 8885d39..1c61364 100644
--- a/src/qqq/utils/qqq/FilterUtils.ts
+++ b/src/qqq/utils/qqq/FilterUtils.ts
@@ -514,7 +514,7 @@ class FilterUtils
/*******************************************************************************
** build a qqq filter from a grid and column sort model
*******************************************************************************/
- public static buildQFilterFromGridFilter(tableMetaData: QTableMetaData, filterModel: GridFilterModel, columnSortModel: GridSortItem[]): QQueryFilter
+ public static buildQFilterFromGridFilter(tableMetaData: QTableMetaData, filterModel: GridFilterModel, columnSortModel: GridSortItem[], limit?: number): QQueryFilter
{
console.log("Building q filter with model:");
console.log(filterModel);
@@ -528,6 +528,12 @@ class FilterUtils
});
}
+ if (limit)
+ {
+ console.log("Setting limit to: " + limit);
+ qFilter.limit = limit;
+ }
+
if (filterModel)
{
let foundFilter = false;
diff --git a/src/qqq/utils/qqq/ValueUtils.tsx b/src/qqq/utils/qqq/ValueUtils.tsx
index e401814..d7e27fb 100644
--- a/src/qqq/utils/qqq/ValueUtils.tsx
+++ b/src/qqq/utils/qqq/ValueUtils.tsx
@@ -70,10 +70,12 @@ class ValueUtils
** When you have a field, and a record - call this method to get a string or
** element back to display the field's value.
*******************************************************************************/
- public static getDisplayValue(field: QFieldMetaData, record: QRecord, usage: "view" | "query" = "view"): string | JSX.Element | JSX.Element[]
+ public static getDisplayValue(field: QFieldMetaData, record: QRecord, usage: "view" | "query" = "view", overrideFieldName?: string): string | JSX.Element | JSX.Element[]
{
- const displayValue = record.displayValues ? record.displayValues.get(field.name) : undefined;
- const rawValue = record.values ? record.values.get(field.name) : undefined;
+ const fieldName = overrideFieldName ?? field.name;
+
+ const displayValue = record.displayValues ? record.displayValues.get(fieldName) : undefined;
+ const rawValue = record.values ? record.values.get(fieldName) : undefined;
return ValueUtils.getValueForDisplay(field, rawValue, displayValue, usage);
}
diff --git a/src/test/java/com/kingsrook/qqq/materialdashboard/tests/QueryScreenTest.java b/src/test/java/com/kingsrook/qqq/materialdashboard/tests/QueryScreenTest.java
index f1157e3..2d1d9b4 100755
--- a/src/test/java/com/kingsrook/qqq/materialdashboard/tests/QueryScreenTest.java
+++ b/src/test/java/com/kingsrook/qqq/materialdashboard/tests/QueryScreenTest.java
@@ -128,7 +128,7 @@ public class QueryScreenTest extends QBaseSeleniumTest
String expectedFilterContents1 = """
{"fieldName":"firstName","operator":"CONTAINS","values":["Jam"]}""";
String expectedFilterContents2 = """
- "booleanOperator":"OR"}""";
+ "booleanOperator":"OR\"""";
qSeleniumJavalin.waitForCapturedPathWithBodyContaining("/data/person/query", expectedFilterContents0);
qSeleniumJavalin.waitForCapturedPathWithBodyContaining("/data/person/query", expectedFilterContents1);