From 0182db5f027605fecfffb50aa428bcaea7a0a07e Mon Sep 17 00:00:00 2001 From: Tim Chamberlain Date: Thu, 6 Oct 2022 19:17:31 -0500 Subject: [PATCH] SPRINT-12: added pagination to top of entity list table, fix for record not found view enttity, store rowsperpage in local storage per table --- src/qqq/pages/entity-list/index.tsx | 38 ++-- .../components/ViewContents/index.tsx | 193 ++++++++++-------- src/qqq/styles/qqq-override-styles.css | 7 + 3 files changed, 128 insertions(+), 110 deletions(-) diff --git a/src/qqq/pages/entity-list/index.tsx b/src/qqq/pages/entity-list/index.tsx index 1fae608..eee170a 100644 --- a/src/qqq/pages/entity-list/index.tsx +++ b/src/qqq/pages/entity-list/index.tsx @@ -34,28 +34,7 @@ import Icon from "@mui/material/Icon"; import LinearProgress from "@mui/material/LinearProgress"; import Menu from "@mui/material/Menu"; import MenuItem from "@mui/material/MenuItem"; -import { - DataGridPro, getGridDateOperators, getGridNumericOperators, getGridStringOperators, - GridCallbackDetails, - GridColDef, - GridColumnOrderChangeParams, - GridColumnVisibilityModel, - GridExportMenuItemProps, - GridFilterItem, - GridFilterModel, - GridRowId, - GridRowParams, - GridRowsProp, - GridSelectionModel, - GridSortItem, - GridSortModel, - GridToolbarColumnsButton, - GridToolbarContainer, - GridToolbarDensitySelector, - GridToolbarExportContainer, - GridToolbarFilterButton, - MuiEvent -} from "@mui/x-data-grid-pro"; +import {DataGridPro, getGridDateOperators, getGridNumericOperators, getGridStringOperators, GridCallbackDetails, GridColDef, GridColumnOrderChangeParams, GridColumnVisibilityModel, GridExportMenuItemProps, GridFilterItem, GridFilterModel, GridRowId, GridRowParams, GridRowsProp, GridSelectionModel, GridSortItem, GridSortModel, GridToolbarColumnsButton, GridToolbarContainer, GridToolbarDensitySelector, GridToolbarExportContainer, GridToolbarFilterButton, MuiEvent} from "@mui/x-data-grid-pro"; import {GridFilterOperator} from "@mui/x-data-grid/models/gridFilterOperator"; import React, {useCallback, useContext, useEffect, useReducer, useRef, useState} from "react"; import {Link, useNavigate, useParams, useSearchParams} from "react-router-dom"; @@ -74,6 +53,7 @@ import QValueUtils from "qqq/utils/QValueUtils"; const COLUMN_VISIBILITY_LOCAL_STORAGE_KEY_ROOT = "qqq.columnVisibility"; const COLUMN_SORT_LOCAL_STORAGE_KEY_ROOT = "qqq.columnSort"; const FILTER_LOCAL_STORAGE_KEY_ROOT = "qqq.filter"; +const ROWS_PER_PAGE_LOCAL_STORAGE_KEY_ROOT = "qqq.rowsPerPage"; interface Props { @@ -140,10 +120,12 @@ function EntityList({table}: Props): JSX.Element // look for defaults in the local storage // //////////////////////////////////////////// const sortLocalStorageKey = `${COLUMN_SORT_LOCAL_STORAGE_KEY_ROOT}.${tableName}`; + const rowsPerPageLocalStorageKey = `${ROWS_PER_PAGE_LOCAL_STORAGE_KEY_ROOT}.${tableName}`; const columnVisibilityLocalStorageKey = `${COLUMN_VISIBILITY_LOCAL_STORAGE_KEY_ROOT}.${tableName}`; const filterLocalStorageKey = `${FILTER_LOCAL_STORAGE_KEY_ROOT}.${tableName}`; let defaultSort = [] as GridSortItem[]; let defaultVisibility = {}; + let defaultRowsPerPage = 10; if (localStorage.getItem(sortLocalStorageKey)) { @@ -153,10 +135,15 @@ function EntityList({table}: Props): JSX.Element { defaultVisibility = JSON.parse(localStorage.getItem(columnVisibilityLocalStorageKey)); } + if (localStorage.getItem(rowsPerPageLocalStorageKey)) + { + defaultRowsPerPage = JSON.parse(localStorage.getItem(rowsPerPageLocalStorageKey)); + } const [filterModel, setFilterModel] = useState({items: []} as GridFilterModel); const [columnSortModel, setColumnSortModel] = useState(defaultSort); const [columnVisibilityModel, setColumnVisibilityModel] = useState(defaultVisibility); + const [rowsPerPage, setRowsPerPage] = useState(defaultRowsPerPage); /////////////////////////////////////////////////////////////////////////////////////////////// // for some reason, if we set the filterModel to what is in local storage, an onChange event // @@ -174,7 +161,6 @@ function EntityList({table}: Props): JSX.Element const [tableProcesses, setTableProcesses] = useState([] as QProcessMetaData[]); const [pageNumber, setPageNumber] = useState(0); const [totalRecords, setTotalRecords] = useState(0); - const [rowsPerPage, setRowsPerPage] = useState(10); const [selectedIds, setSelectedIds] = useState([] as string[]); const [selectFullFilterState, setSelectFullFilterState] = useState("n/a" as "n/a" | "checked" | "filter"); const [columns, setColumns] = useState([] as GridColDef[]); @@ -356,7 +342,7 @@ function EntityList({table}: Props): JSX.Element const getCustomGridBooleanOperators = (): GridFilterOperator[] => { return [booleanTrueOperator, booleanFalseOperator, booleanEmptyOperator, booleanNotEmptyOperator]; - } + }; /////////////////////////// // display query results // @@ -557,6 +543,7 @@ function EntityList({table}: Props): JSX.Element const handleRowsPerPageChange = (size: number) => { setRowsPerPage(size); + localStorage.setItem(rowsPerPageLocalStorageKey, JSON.stringify(size)); }; const navigate = useNavigate(); @@ -901,6 +888,9 @@ function EntityList({table}: Props): JSX.Element ) } +
+ +
); } diff --git a/src/qqq/pages/entity-view/components/ViewContents/index.tsx b/src/qqq/pages/entity-view/components/ViewContents/index.tsx index fcf5fbc..af6a99d 100644 --- a/src/qqq/pages/entity-view/components/ViewContents/index.tsx +++ b/src/qqq/pages/entity-view/components/ViewContents/index.tsx @@ -19,6 +19,7 @@ * along with this program. If not, see . */ +import {QException} from "@kingsrook/qqq-frontend-core/lib/exceptions/QException"; import {QProcessMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QProcessMetaData"; import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData"; import {QTableSection} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableSection"; @@ -80,8 +81,9 @@ function ViewContents({id, table}: Props): JSX.Element const [tableProcesses, setTableProcesses] = useState([] as QProcessMetaData[]); const [actionsMenu, setActionsMenu] = useState(null); const [tableWidgets, setTableWidgets] = useState([] as QWidgetMetaData[]); + const [notFoundMessage, setNotFoundMessage] = useState(null); const [searchParams] = useSearchParams(); - const {pageHeader, setPageHeader} = useContext(QContext); + const {setPageHeader} = useContext(QContext); const [, forceUpdate] = useReducer((x) => x + 1, 0); const openActionsMenu = (event: any) => setActionsMenu(event.currentTarget); @@ -126,8 +128,24 @@ function ViewContents({id, table}: Props): JSX.Element ///////////////////// // load the record // ///////////////////// - const record = await qController.get(tableName, id); - setRecord(record); + let record: QRecord; + try + { + record = await qController.get(tableName, id); + setRecord(record); + } + catch (e) + { + if (e instanceof QException) + { + if ((e as QException).status === "404") + { + setNotFoundMessage(`${tableMetaData.label} ${id} could not be found.`); + return; + } + } + } + setPageHeader(record.recordLabel); /////////////////////////// @@ -254,96 +272,99 @@ function ViewContents({id, table}: Props): JSX.Element ); return ( + notFoundMessage + ? + {notFoundMessage} + : + + { + (searchParams.get("createSuccess") || searchParams.get("updateSuccess")) ? ( + + {tableMetaData?.label} + {" "} + successfully + {" "} + {searchParams.get("createSuccess") ? "created" : "updated"} - - { - (searchParams.get("createSuccess") || searchParams.get("updateSuccess")) ? ( - - {tableMetaData?.label} - {" "} - successfully - {" "} - {searchParams.get("createSuccess") ? "created" : "updated"} + + ) : ("") + } - - ) : ("") - } - - - - - - - - - - - - - - - {tableMetaData?.iconName} - - - - - - {tableMetaData && record ? `Viewing ${tableMetaData?.label}: ${record?.recordLabel}` : ""} - - - {renderActionsMenu} - - - {t1SectionElement ? ({t1SectionElement}) : null} - - + + + - {tableMetaData && tableMetaData.widgets && record && ( - - )} - {nonT1TableSections.length > 0 ? nonT1TableSections.map(({ - iconName, label, name, fieldNames, tier, - }: any) => ( - - - - {label} - - {sectionFieldElements.get(name)} - - - )) : null} - - - - + + + + + + + + + + {tableMetaData?.iconName} + + + + + + {tableMetaData && record ? `Viewing ${tableMetaData?.label}: ${record?.recordLabel}` : ""} + + + {renderActionsMenu} + + + {t1SectionElement ? ({t1SectionElement}) : null} + + - + {tableMetaData && tableMetaData.widgets && record && ( + + )} + {nonT1TableSections.length > 0 ? nonT1TableSections.map(({ + iconName, label, name, fieldNames, tier, + }: any) => ( + + + + {label} + + {sectionFieldElements.get(name)} + + + )) : null} + + + + + + + - - {/* Delete confirmation Dialog */} - - Confirm Deletion - - - Are you sure you want to delete this record? - - - - - - - - + {/* Delete confirmation Dialog */} + + Confirm Deletion + + + Are you sure you want to delete this record? + + + + + + + + ); } diff --git a/src/qqq/styles/qqq-override-styles.css b/src/qqq/styles/qqq-override-styles.css index e2458d6..ee3be50 100644 --- a/src/qqq/styles/qqq-override-styles.css +++ b/src/qqq/styles/qqq-override-styles.css @@ -111,6 +111,13 @@ font-size: 14px; } +.MuiDataGrid-toolbarContainer .pagination +{ + display: flex; + flex-grow: 1; + justify-content: flex-end; +} + /* Help make the radio, text, and icon wrap in a good way */ .doFullValidationRadios label {