diff --git a/src/qqq/components/query/CustomPaginationComponent.tsx b/src/qqq/components/query/CustomPaginationComponent.tsx
new file mode 100644
index 0000000..c6eccfc
--- /dev/null
+++ b/src/qqq/components/query/CustomPaginationComponent.tsx
@@ -0,0 +1,122 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2024. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+import {Capability} from "@kingsrook/qqq-frontend-core/lib/model/metaData/Capability";
+import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData";
+import {TablePagination} from "@mui/material";
+import Box from "@mui/material/Box";
+import Icon from "@mui/material/Icon";
+import IconButton from "@mui/material/IconButton";
+import {GridRowsProp} from "@mui/x-data-grid-pro";
+import React from "react";
+import CustomWidthTooltip from "qqq/components/tooltips/CustomWidthTooltip";
+import ValueUtils from "qqq/utils/qqq/ValueUtils";
+
+interface CustomPaginationProps
+{
+ tableMetaData: QTableMetaData;
+ rows: GridRowsProp[];
+ totalRecords: number;
+ distinctRecords: number;
+ pageNumber: number;
+ rowsPerPage: number;
+ loading: boolean;
+ isJoinMany: boolean;
+ handlePageChange: (value: number) => void;
+ handleRowsPerPageChange: (value: number) => void;
+}
+
+/*******************************************************************************
+ ** DataGrid custom component - for pagination!
+ *******************************************************************************/
+export default function CustomPaginationComponent({tableMetaData, rows, totalRecords, distinctRecords, pageNumber, rowsPerPage, loading, isJoinMany, handlePageChange, handleRowsPerPageChange}: CustomPaginationProps): JSX.Element
+{
+ // @ts-ignore
+ const defaultLabelDisplayedRows = ({from, to, count}) =>
+ {
+ const tooltipHTML = <>
+ The number of rows shown on this screen may be greater than the number of {tableMetaData?.label} records
+ that match your query, because you have included fields from other tables which may have
+ more than one record associated with each {tableMetaData?.label}.
+ >
+ let distinctPart = isJoinMany ? (
+ ({ValueUtils.safeToLocaleString(distinctRecords)} distinct
+ info_outlined
+
+ )
+ ) : <>>;
+
+ if (tableMetaData && !tableMetaData.capabilities.has(Capability.TABLE_COUNT))
+ {
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+ // to avoid a non-countable table showing (this is what data-grid did) 91-100 even if there were only 95 records, //
+ // we'll do this... not quite good enough, but better than the original //
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+ if (rows.length > 0 && rows.length < to - from)
+ {
+ to = from + rows.length;
+ }
+ return (`Showing ${from.toLocaleString()} to ${to.toLocaleString()}`);
+ }
+
+ /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+ // treat -1 as the sentinel that it's set as below -- remember, we did that so that 'to' would have a value in here when there's no count. //
+ /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+ if (count !== null && count !== undefined && count !== -1)
+ {
+ if (count === 0)
+ {
+ return (loading ? "Counting..." : "No rows");
+ }
+
+ return
+ Showing {from.toLocaleString()} to {to.toLocaleString()} of
+ {
+ count == -1 ?
+ <>more than {to.toLocaleString()}>
+ : <> {count.toLocaleString()}{distinctPart}>
+ }
+ ;
+ }
+ else
+ {
+ return ("Counting...");
+ }
+ };
+
+
+ return (
+ handlePageChange(value)}
+ onRowsPerPageChange={(event) => handleRowsPerPageChange(Number(event.target.value))}
+ labelDisplayedRows={defaultLabelDisplayedRows}
+ />
+ );
+
+}
diff --git a/src/qqq/components/query/QueryScreenActionMenu.tsx b/src/qqq/components/query/QueryScreenActionMenu.tsx
new file mode 100644
index 0000000..73147a4
--- /dev/null
+++ b/src/qqq/components/query/QueryScreenActionMenu.tsx
@@ -0,0 +1,134 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2024. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+
+import {Capability} from "@kingsrook/qqq-frontend-core/lib/model/metaData/Capability";
+import {QInstance} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QInstance";
+import {QProcessMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QProcessMetaData";
+import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData";
+import Divider from "@mui/material/Divider";
+import Icon from "@mui/material/Icon";
+import ListItemIcon from "@mui/material/ListItemIcon";
+import Menu from "@mui/material/Menu";
+import MenuItem from "@mui/material/MenuItem";
+import React, {useState} from "react";
+import {useNavigate} from "react-router-dom";
+import {QActionsMenuButton} from "qqq/components/buttons/DefaultButtons";
+
+interface QueryScreenActionMenuProps
+{
+ metaData: QInstance;
+ tableMetaData: QTableMetaData;
+ tableProcesses: QProcessMetaData[];
+ bulkLoadClicked: () => void;
+ bulkEditClicked: () => void;
+ bulkDeleteClicked: () => void;
+ processClicked: (process: QProcessMetaData) => void;
+}
+
+QueryScreenActionMenu.defaultProps = {
+};
+
+export default function QueryScreenActionMenu({metaData, tableMetaData, tableProcesses, bulkLoadClicked, bulkEditClicked, bulkDeleteClicked, processClicked}: QueryScreenActionMenuProps): JSX.Element
+{
+ const [anchorElement, setAnchorElement] = useState(null)
+
+ const navigate = useNavigate();
+
+ const openActionsMenu = (event: any) =>
+ {
+ setAnchorElement(event.currentTarget);
+ }
+
+ const closeActionsMenu = () =>
+ {
+ setAnchorElement(null);
+ }
+
+ const pushDividerIfNeeded = (menuItems: JSX.Element[]) =>
+ {
+ if (menuItems.length > 0)
+ {
+ menuItems.push();
+ }
+ };
+
+ const runSomething = (handler: () => void) =>
+ {
+ closeActionsMenu();
+ handler();
+ }
+
+ const menuItems: JSX.Element[] = [];
+ if (tableMetaData.capabilities.has(Capability.TABLE_INSERT) && tableMetaData.insertPermission)
+ {
+ menuItems.push();
+ }
+ if (tableMetaData.capabilities.has(Capability.TABLE_UPDATE) && tableMetaData.editPermission)
+ {
+ menuItems.push();
+ }
+ if (tableMetaData.capabilities.has(Capability.TABLE_DELETE) && tableMetaData.deletePermission)
+ {
+ menuItems.push();
+ }
+
+ const runRecordScriptProcess = metaData?.processes.get("runRecordScript");
+ if (runRecordScriptProcess)
+ {
+ const process = runRecordScriptProcess;
+ menuItems.push();
+ }
+
+ menuItems.push();
+
+ if (tableProcesses && tableProcesses.length)
+ {
+ pushDividerIfNeeded(menuItems);
+ }
+
+ tableProcesses.sort((a, b) => a.label.localeCompare(b.label));
+ tableProcesses.map((process) =>
+ {
+ menuItems.push();
+ });
+
+ if (menuItems.length === 0)
+ {
+ menuItems.push();
+ }
+
+ return (
+ <>
+
+
+ >
+ )
+}