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( runSomething(bulkLoadClicked)}>library_addBulk Load); + } + if (tableMetaData.capabilities.has(Capability.TABLE_UPDATE) && tableMetaData.editPermission) + { + menuItems.push( runSomething(bulkEditClicked)}>editBulk Edit); + } + if (tableMetaData.capabilities.has(Capability.TABLE_DELETE) && tableMetaData.deletePermission) + { + menuItems.push( runSomething(bulkDeleteClicked)}>deleteBulk Delete); + } + + const runRecordScriptProcess = metaData?.processes.get("runRecordScript"); + if (runRecordScriptProcess) + { + const process = runRecordScriptProcess; + menuItems.push( runSomething(() => processClicked(process))}>{process.iconName ?? "arrow_forward"}{process.label}); + } + + menuItems.push( navigate(`${metaData.getTablePathByName(tableMetaData.name)}/dev`)}>codeDeveloper Mode); + + if (tableProcesses && tableProcesses.length) + { + pushDividerIfNeeded(menuItems); + } + + tableProcesses.sort((a, b) => a.label.localeCompare(b.label)); + tableProcesses.map((process) => + { + menuItems.push( runSomething(() => processClicked(process))}>{process.iconName ?? "arrow_forward"}{process.label}); + }); + + if (menuItems.length === 0) + { + menuItems.push(blockNo actions available); + } + + return ( + <> + + + {menuItems} + + + ) +}