This is an automated email from the ASF dual-hosted git repository.
ppawar pushed a commit to branch atlas-2.5
in repository https://gitbox.apache.org/repos/asf/atlas.git
The following commit(s) were added to refs/heads/atlas-2.5 by this push:
new 7904c9f64 ATLAS-5036: [React UI] Pagination Issue: 'Go to Page' input
resets and Incorrect page number Display with font & alignment inconsistencies
(#435)
7904c9f64 is described below
commit 7904c9f64e5c89e9890ecec8bd6bdac1757ed04a
Author: Prasad Pawar <[email protected]>
AuthorDate: Tue Sep 9 14:36:48 2025 +0530
ATLAS-5036: [React UI] Pagination Issue: 'Go to Page' input resets and
Incorrect page number Display with font & alignment inconsistencies (#435)
---
dashboard/src/components/Table/TableLayout.tsx | 82 ++-
dashboard/src/components/Table/TablePagination.tsx | 582 +++++++++++++--------
dashboard/src/components/commonComponents.tsx | 24 +
dashboard/src/models/tableLayoutType.ts | 4 +
dashboard/src/styles/table.scss | 25 +-
dashboard/src/utils/Enum.ts | 18 +
dashboard/src/utils/Messages.ts | 10 +-
dashboard/src/utils/Utils.ts | 2 +-
.../views/Administrator/Audits/AdminAuditTable.tsx | 11 +-
.../DetailPage/EntityDetailTabs/AuditsTab.tsx | 16 +-
.../EntityDetailTabs/ClassificationsTab.tsx | 3 +
.../DetailPage/EntityDetailTabs/ProfileTab.tsx | 12 +-
.../EntityDetailTabs/ReplicationAuditTab.tsx | 3 +-
.../src/views/SearchResult/RelationShipSearch.tsx | 6 +-
dashboard/src/views/SearchResult/SearchResult.tsx | 57 +-
.../src/views/SideBar/SideBarTree/SideBarTree.tsx | 6 +-
16 files changed, 534 insertions(+), 327 deletions(-)
diff --git a/dashboard/src/components/Table/TableLayout.tsx
b/dashboard/src/components/Table/TableLayout.tsx
index 12dfa2cff..f58d150b0 100644
--- a/dashboard/src/components/Table/TableLayout.tsx
+++ b/dashboard/src/components/Table/TableLayout.tsx
@@ -43,7 +43,6 @@ import {
} from "@tanstack/react-table";
import { FC, useEffect, useMemo, useState } from "react";
import TableFilter from "./TableFilters";
-import TablePagination from "./TablePagination";
import ArrowUpwardOutlinedIcon from "@mui/icons-material/ArrowUpwardOutlined";
import ArrowDownwardOutlinedIcon from
"@mui/icons-material/ArrowDownwardOutlined";
import SwapVertOutlinedIcon from "@mui/icons-material/SwapVertOutlined";
@@ -75,6 +74,7 @@ import AddOutlinedIcon from "@mui/icons-material/AddOutlined";
import TableRowsLoader from "./TableLoader";
import AddTag from "@views/Classification/AddTag";
import FilterQuery from "@components/FilterQuery";
+import TablePagination from "./TablePagination";
interface IndeterminateCheckboxProps extends Omit<CheckboxProps, "ref"> {
indeterminate?: boolean;
@@ -335,6 +335,7 @@ const TableLayout: FC<TableProps> = ({
isFetching,
defaultColumnVisibility,
pageCount,
+ totalCount,
onClickRow,
emptyText,
defaultColumnParams,
@@ -355,18 +356,23 @@ const TableLayout: FC<TableProps> = ({
showPagination,
setUpdateTable,
isfilterQuery,
- isClientSidePagination
+ isClientSidePagination,
+ isEmptyData,
+ setIsEmptyData,
+ showGoToPage
}) => {
let defaultHideColumns = { ...defaultColumnVisibility };
const location = useLocation();
const memoizedData = useMemo(() => data, [data]);
const memoizedColumns = useMemo(() => columns, [columns]);
const [searchParams] = useSearchParams();
-
const [pagination, setPagination] = useState<PaginationState>({
pageIndex: 0,
pageSize: 25
});
+
+ const [goToPageVal, setGoToPageVal] = useState<any>("");
+
const [rowSelection, setRowSelection] = useState({});
const [sorting, setSorting] = useState<SortingState>(
!isEmpty(defaultSortCol) ? defaultSortCol : []
@@ -387,16 +393,11 @@ const TableLayout: FC<TableProps> = ({
const {
getHeaderGroups,
getRowModel,
- firstPage,
- getCanPreviousPage,
- previousPage,
- nextPage,
- getCanNextPage,
- lastPage,
setPageIndex,
getPageCount,
+ nextPage,
+ previousPage,
setPageSize,
- getRowCount,
resetSorting,
resetRowSelection,
getIsAllRowsSelected,
@@ -417,7 +418,9 @@ const TableLayout: FC<TableProps> = ({
onSortingChange: setSorting,
onColumnOrderChange: setColumnOrder,
getSortedRowModel: getSortedRowModel(),
- getPaginationRowModel: getPaginationRowModel(),
+ getPaginationRowModel: isClientSidePagination
+ ? getPaginationRowModel()
+ : undefined,
onPaginationChange: setPagination,
state: {
columnVisibility: columnVisibilityParams
@@ -435,14 +438,17 @@ const TableLayout: FC<TableProps> = ({
});
useEffect(() => {
- if (!isEmpty(fetchData)) {
- fetchData({ pagination, sorting });
+ if (typeof fetchData === "function") {
+ fetchData({
+ pagination,
+ sorting
+ });
}
}, [
fetchData,
pagination.pageIndex,
pagination.pageSize,
- !clientSideSorting && sorting
+ clientSideSorting ? null : sorting
]);
function handleDragEnd(event: DragEndEvent) {
@@ -462,30 +468,14 @@ const TableLayout: FC<TableProps> = ({
useSensor(KeyboardSensor, {})
);
+ const [, setSearchParams] = useSearchParams();
+
useEffect(() => {
resetSorting(true);
resetRowSelection(true);
setRowSelection({});
setSorting(!isEmpty(defaultSortCol) ? defaultSortCol : []);
- // setColumnVisibility(defaultHideColumns);
- }, [typeParam]);
-
- // if (fetchData != undefined) {
-
- // }
-
- let currentPageOffset = searchParams.get("pageOffset") || 0;
- let isFirstPage = currentPageOffset == 0;
-
- // const filteredParams = Array.from(searchParams.entries())
- // .filter(([key]) => ["type", "tag", "term"].includes(key))
- // .map(([key, value]) => ({
- // keyName:
- // key === "tag"
- // ? "Classification"
- // : key.charAt(0).toUpperCase() + key.slice(1),
- // value
- // }));
+ }, [typeParam, defaultSortCol, setSearchParams]);
const handleCloseTagModal = () => {
setTagModal(false);
@@ -674,31 +664,27 @@ const TableLayout: FC<TableProps> = ({
</MuiTable>
</DndContext>
</TableContainer>
- {/* {noDataFound && (
- <Stack my={2} textAlign="center">
- {emptyText}
- </Stack>
- )} */}
{showPagination && !isFetching && (
<TablePagination
- firstPage={firstPage}
- getCanPreviousPage={getCanPreviousPage}
- previousPage={previousPage}
- nextPage={nextPage}
- getCanNextPage={getCanNextPage}
- lastPage={lastPage}
+ isServerSide={!isClientSidePagination}
getPageCount={getPageCount}
setPageIndex={setPageIndex}
setPageSize={setPageSize}
+ nextPage={nextPage}
+ previousPage={previousPage}
getRowModel={getRowModel}
- getRowCount={getRowCount}
pagination={pagination}
- memoizedData={memoizedData}
- isFirstPage={isFirstPage}
setRowSelection={setRowSelection}
- isClientSidePagination={isClientSidePagination}
+ memoizedData={memoizedData}
+ isFirstPage={pagination.pageIndex === 0}
setPagination={setPagination}
+ goToPageVal={goToPageVal}
+ setGoToPageVal={setGoToPageVal}
+ isEmptyData={isEmptyData}
+ setIsEmptyData={setIsEmptyData}
+ showGoToPage={showGoToPage}
+ totalCount={totalCount}
/>
)}
</Paper>
diff --git a/dashboard/src/components/Table/TablePagination.tsx
b/dashboard/src/components/Table/TablePagination.tsx
index 989708a28..2820b47f5 100644
--- a/dashboard/src/components/Table/TablePagination.tsx
+++ b/dashboard/src/components/Table/TablePagination.tsx
@@ -15,7 +15,7 @@
* limitations under the License.
*/
-import * as React from "react";
+import { useState, useRef, useEffect } from "react";
import {
Autocomplete,
FormControl,
@@ -26,16 +26,21 @@ import {
Paper,
Stack,
TextField,
- Typography,
- createFilterOptions,
- styled
+ Typography
} from "@mui/material";
-
import { useTheme } from "@emotion/react";
import { KeyboardArrowLeft, KeyboardArrowRight } from "@mui/icons-material";
import { LightTooltip } from "../muiComponents";
import { useLocation, useNavigate } from "react-router-dom";
import { isEmpty } from "../../utils/Utils";
+import Messages from "@utils/Messages";
+import { toast } from "react-toastify";
+import { GetNumberSuffix } from "@components/commonComponents";
+import { pageSizeOptions } from "@utils/Enum";
+import { createFilterOptions } from "@mui/material/Autocomplete";
+import { styled } from "@mui/material/styles";
+
+const filter = createFilterOptions<any>();
export const StyledPagination = styled(Pagination)`
display: flex;
@@ -44,46 +49,31 @@ export const StyledPagination = styled(Pagination)`
`;
interface PaginationProps {
- firstPage?: any;
- getCanPreviousPage?: any;
- previousPage?: any;
- nextPage?: any;
- getCanNextPage?: any;
- lastPage?: any;
- getPageCount?: any;
- setPageIndex?: any;
- setPageSize?: any;
- getRowModel?: any;
- getRowCount?: any;
- pagination?: any;
- setRowSelection?: any;
- memoizedData?: any;
- isFirstPage?: any;
- isClientSidePagination?: any;
- setPagination?: any;
-}
-interface FilmOptionType {
- inputValue?: string;
- label: string;
+ isServerSide?: boolean;
+ getPageCount?: () => number;
+ previousPage?: () => void;
+ nextPage?: () => void;
+ setPageIndex?: (index: number) => void;
+ setPageSize?: (size: number) => void;
+ getRowModel?: () => any;
+ pagination: { pageIndex: number; pageSize: number };
+ setRowSelection?: (selection: any) => void;
+ memoizedData: any[];
+ isFirstPage?: boolean;
+ setPagination?: (pagination: any) => void;
+ goToPageVal?: string;
+ setGoToPageVal?: React.Dispatch<React.SetStateAction<string>>;
+ isEmptyData?: boolean;
+ setIsEmptyData?: any;
+ showGoToPage?: boolean;
+ totalCount?: number;
}
-const optionsVal = [
- { label: "25" },
- { label: "50" },
- { label: "100" },
- { label: "150" },
- { label: "200" },
- { label: "250" },
- { label: "300" },
- { label: "350" },
- { label: "400" },
- { label: "450" },
- { label: "500" }
-];
-const filter = createFilterOptions<FilmOptionType>();
-
-const options: readonly FilmOptionType[] = optionsVal;
const TablePagination: React.FC<PaginationProps> = ({
+ isServerSide = false,
+ getPageCount,
+ previousPage,
+ nextPage,
setPageIndex,
setPageSize,
getRowModel,
@@ -91,45 +81,268 @@ const TablePagination: React.FC<PaginationProps> = ({
setRowSelection,
memoizedData,
isFirstPage,
- setPagination
+ setPagination,
+ goToPageVal,
+ setGoToPageVal,
+ isEmptyData,
+ setIsEmptyData,
+ showGoToPage = false,
+ totalCount
}) => {
const theme: any = useTheme();
const location = useLocation();
const navigate = useNavigate();
- const searchParams: any = new URLSearchParams(location.search);
- const [inputVal, setInputVal] = React.useState<any>("");
- const [value, setValue] = React.useState<any>(
- searchParams.get("pageLimit") != null
- ? searchParams.get("pageLimit")
- : options[0].label
+ const searchParams = new URLSearchParams(location.search);
+ const { pageIndex, pageSize } = pagination;
+
+ const [value, setValue] = useState<any>({
+ label:
+ searchParams.get("pageLimit") ??
+ pageSize.toString() ??
+ pageSizeOptions[0].label
+ });
+ const [limit, setLimit] = useState<number>(
+ searchParams.get("pageLimit")
+ ? Number(searchParams.get("pageLimit"))
+ : pageSize
);
- const { pageSize, pageIndex } = pagination;
- const handleChange = (newValue: any) => {
- setPageSize(newValue.label);
- setRowSelection({});
- searchParams.delete("pageOffset");
- searchParams.set("pageLimit", newValue.label);
- navigate({ search: searchParams.toString() });
- };
- const [_searchState, setSearchState] = React.useState(
- Object.fromEntries(searchParams)
+ const [pageFrom, setPageFrom] = useState<number>(
+ isServerSide && searchParams.get("pageOffset")
+ ? Number(searchParams.get("pageOffset")) + 1
+ : isServerSide
+ ? 1
+ : pageIndex * pageSize + 1
);
+ const [pageTo, setPageTo] = useState<number>(
+ isServerSide && searchParams.get("pageOffset")
+ ? Number(searchParams.get("pageOffset")) + Number(limit)
+ : isServerSide
+ ? Number(limit)
+ : Math.min((pageIndex + 1) * pageSize, getRowModel?.().rows.length || 0)
+ );
+ const [offset, setOffset] = useState<number>(
+ isServerSide && searchParams.get("pageOffset")
+ ? Number(searchParams.get("pageOffset"))
+ : pageIndex * pageSize
+ );
+ const [activePage, setActivePage] = useState<number>(
+ isServerSide ? Math.floor(offset / Number(limit)) + 1 : pageIndex + 1
+ );
+
+ const [pendingGoToPageVal, setPendingGoToPageVal] = useState<string>("");
+ const [goToPageTrigger, setGoToPageTrigger] = useState<string>("");
+ const toastId = useRef<any>(null);
+
+ useEffect(() => {
+ if (isServerSide) {
+ setValue({ label: searchParams.get("pageLimit") ?? limit.toString() });
+ setLimit(Number(searchParams.get("pageLimit") ?? limit));
+ setOffset(Number(searchParams.get("pageOffset") ?? pageIndex *
pageSize));
+ setPageFrom(
+ Number(searchParams.get("pageOffset") ?? pageIndex * pageSize) + 1
+ );
+ setPageTo(
+ Number(searchParams.get("pageOffset") ?? pageIndex * pageSize) +
+ Number(limit)
+ );
+ }
+ }, [isServerSide, location.search, limit, pageIndex, pageSize]);
+
+ // Do not force offset to 0 based solely on isFirstPage; rely on URL
(pageOffset)
+
+ useEffect(() => {
+ if (isServerSide) {
+ const sp = new URLSearchParams(location.search);
+ const effLimit = Number(sp.get("pageLimit") ?? limit);
+ const effOffset = Number(sp.get("pageOffset") ?? 0);
+ setActivePage(Math.floor(effOffset / effLimit) + 1);
+ } else {
+ setActivePage(pageIndex + 1);
+ }
+ }, [isServerSide, location.search, limit, pageIndex]);
+
+ const handlePageSizeChange = (newValue: any) => {
+ const newPageSize: any = Number(newValue.label);
+ setValue({ label: newValue.label });
+ if (isServerSide) {
+ setLimit(newPageSize);
+ setPageFrom(1);
+ setPageTo(newPageSize);
+ setActivePage(1);
+ setOffset(0);
+ searchParams.delete("pageOffset");
+ searchParams.set("pageLimit", newPageSize);
+ navigate({ search: searchParams.toString() });
+ setRowSelection?.({});
+ setPagination?.((prev: any) => ({ ...prev, pageIndex: 0, pageSize:
newPageSize }));
+ // Clear Go-to state on page size change
+ setGoToPageVal?.("");
+ setPendingGoToPageVal("");
+ setGoToPageTrigger("");
+ } else {
+ setPageSize?.(newPageSize);
+ setPageIndex?.(0);
+ setLimit(newPageSize);
+ setPageFrom(1);
+ setPageTo(Math.min(newPageSize, getRowModel?.().rows.length || 0));
+ // Clear Go-to state on page size change
+ setGoToPageVal?.("");
+ setPendingGoToPageVal("");
+ setGoToPageTrigger("");
+ }
+ };
+
+ const handleGoToPage = () => {
+ const goToPage = parseInt(goToPageVal || pendingGoToPageVal);
+ if (isNaN(goToPage) || goToPage < 1) return;
+
+ if (isServerSide) {
+ // Guard based on approximate total count if available: pages are
1..ceil(totalCount/limit)
+ if (typeof totalCount === "number" && totalCount >= 0) {
+ const maxPage = Math.max(1, Math.ceil(totalCount / Number(limit ||
1)));
+ if (goToPage > maxPage) {
+ toast.dismiss(toastId.current);
+ toastId.current = toast.info(
+ <>
+ {Messages.search.noRecordForPage}
+ <b>
+ <GetNumberSuffix number={goToPage} sup={true} />
+ </b>{" "}
+ page
+ </>
+ );
+ setGoToPageVal?.("");
+ setPendingGoToPageVal("");
+ return;
+ }
+ }
+ const newOffset = (goToPage - 1) * limit;
+ if (newOffset === offset) {
+ toast.dismiss(toastId.current);
+ toastId.current = toast.info(`${Messages.search.onSamePage}`);
+ setGoToPageVal?.("");
+ setPendingGoToPageVal("");
+ return;
+ }
+ setOffset(newOffset);
+ setPageFrom(newOffset + 1);
+ setPageTo(newOffset + limit);
+ setActivePage(goToPage);
+ setPageIndex?.(goToPage - 1);
+ setRowSelection?.({});
+ searchParams.set("pageOffset", newOffset.toString());
+ navigate({ search: searchParams.toString() });
+ setGoToPageVal?.("");
+ setPendingGoToPageVal("");
+ } else {
+ if (goToPage > (getPageCount?.() || Infinity)) {
+ toast.dismiss(toastId.current);
+ toastId.current = toast.info(
+ <>
+ {Messages.search.noRecordForPage} page
"<strong>{goToPage}</strong>"
+ </>
+ );
+ setGoToPageVal?.("");
+ setPendingGoToPageVal("");
+ setGoToPageTrigger("");
+ return;
+ }
+ if (goToPage - 1 === pageIndex) {
+ toast.dismiss(toastId.current);
+ toastId.current = toast.info(`${Messages.search.onSamePage}`);
+ setGoToPageVal?.("");
+ setPendingGoToPageVal("");
+ setGoToPageTrigger("");
+ return;
+ }
+ setPageIndex?.(goToPage - 1);
+ setPageFrom((goToPage - 1) * pageSize + 1);
+ setPageTo(
+ Math.min(goToPage * pageSize, getRowModel?.().rows.length || 0)
+ );
+ setGoToPageVal?.("");
+ setPendingGoToPageVal("");
+ setGoToPageTrigger("");
+ }
+ };
+
+ useEffect(() => {
+ if ((goToPageVal || goToPageTrigger || isEmptyData) && setPageIndex) {
+ handleGoToPage();
+ if (typeof setIsEmptyData === "function") {
+ setIsEmptyData(false);
+ }
+ setGoToPageTrigger("");
+ }
+ }, [goToPageVal, goToPageTrigger, isEmptyData]);
+
+ const handlePreviousPage = () => {
+ if (isServerSide) {
+ const prevOffset = offset - limit;
+ const safePrevOffset = prevOffset < 0 ? 0 : prevOffset;
+ setOffset(safePrevOffset);
+ setPageFrom(safePrevOffset + 1);
+ setPageTo(safePrevOffset + limit);
+ setPagination?.((prev: any) => ({
+ ...prev,
+ pageIndex: prev.pageIndex - 1
+ }));
+ setRowSelection?.({});
+ // Always reflect new offset in URL (including 0) so fetch effect
triggers correctly
+ searchParams.set("pageOffset", safePrevOffset.toString());
+ navigate({ search: searchParams.toString() });
+ } else {
+ previousPage?.();
+ setPageFrom(pageIndex * pageSize + 1);
+ setPageTo(
+ Math.min(pageIndex * pageSize, getRowModel?.().rows.length || 0)
+ );
+ }
+ setGoToPageVal?.("");
+ };
+
+ const handleNextPage = () => {
+ if (isServerSide) {
+ const nextOffset = offset + limit;
+ setOffset(nextOffset);
+ setPageFrom(nextOffset + 1);
+ setPageTo(nextOffset + limit);
+ setPagination?.((prev: any) => ({
+ ...prev,
+ pageIndex: prev.pageIndex + 1
+ }));
+ setRowSelection?.({});
+ searchParams.set("pageOffset", nextOffset.toString());
+ navigate({ search: searchParams.toString() });
+ } else {
+ nextPage?.();
+ setPageFrom((pageIndex + 1) * pageSize + 1);
+ setPageTo(
+ Math.min((pageIndex + 2) * pageSize, getRowModel?.().rows.length || 0)
+ );
+ }
+ setGoToPageVal?.("");
+ };
- React.useEffect(() => {
- setSearchState(Object.fromEntries(searchParams));
- }, [location.search]);
+ // In server-side mode, rely on computed offset to decide first page
+ const isPreviousDisabled = isServerSide ? offset === 0 : pageIndex === 0;
+ // In server-side mode, disable Next when fewer than limit rows returned
(last page)
+ const isNextDisabled = isServerSide
+ ? (typeof totalCount === "number" && totalCount >= 0
+ ? offset + limit >= totalCount
+ : memoizedData.length < limit)
+ : pageIndex + 1 >= (getPageCount?.() || Infinity);
- let limit =
- parseInt(
- searchParams.get("pageLimit") !== undefined &&
- searchParams.get("pageLimit") !== null
- ? searchParams.get("pageLimit")
- : pageSize || "0",
- 10
- ) || 0;
- let pageFrom = isFirstPage ? 1 : pageSize * pageIndex + 1;
- let pageTo = isFirstPage ? Number(limit) : pageSize * (Number(pageIndex) +
1);
+ const totalRows = getRowModel?.().rows.length || 0;
+ const displayFrom = isServerSide
+ ? pageFrom
+ : totalRows === 0
+ ? 0
+ : pageIndex * pageSize + 1;
+ const displayTo = isServerSide
+ ? pageTo
+ : Math.min((pageIndex + 1) * pageSize, totalRows);
return (
<Stack
@@ -143,10 +356,11 @@ const TablePagination: React.FC<PaginationProps> = ({
>
<div>
<span className="text-grey">
- Showing <u>{getRowModel()?.rows?.length.toLocaleString()}
records</u>{" "}
- From {pageFrom} - {pageTo}
+ Showing <u>{totalRows.toLocaleString()} records</u> From
{displayFrom}{" "}
+ - {displayTo}
</span>
</div>
+
<div className="table-pagination-filters">
<Stack
className="table-pagination-filters-box"
@@ -157,7 +371,7 @@ const TablePagination: React.FC<PaginationProps> = ({
<Typography
className="text-grey"
whiteSpace="nowrap"
- fontWeight="600"
+ fontWeight="400"
lineHeight="32px"
>
Page Limit :
@@ -167,163 +381,122 @@ const TablePagination: React.FC<PaginationProps> = ({
value={value}
className="pagination-page-limit"
disableClearable
- onChange={(_event: any, newValue) => {
+ onChange={(_, newValue: any) => {
if (typeof newValue === "string") {
- setValue({
- label: newValue
- });
- handleChange({ label: newValue });
- } else if (newValue && newValue.inputValue) {
- setValue({
- label: newValue.inputValue
- });
- handleChange({ label: newValue.inputValue });
+ handlePageSizeChange({ label: newValue });
+ } else if (newValue?.inputValue) {
+ handlePageSizeChange({ label: newValue.inputValue });
} else if (newValue) {
- setValue(newValue);
- handleChange(newValue);
- } else {
- const fallbackValue = { label: inputVal || "" };
- setValue(fallbackValue);
- handleChange(fallbackValue);
+ handlePageSizeChange(newValue);
}
- setPagination((prev: { pageIndex: number }) => ({
- ...prev,
- pageIndex: 0
- }));
}}
filterOptions={(options, params) => {
const filtered = filter(options, params);
-
const { inputValue } = params;
-
const isExisting = options.some(
(option) => inputValue === option.label
);
- if (inputValue !== "" && !isExisting) {
- filtered.push({
- inputValue,
- label: `${inputValue}`
- });
+ if (inputValue !== "" && setPageSize && !isExisting) {
+ filtered.push({ inputValue, label: `${inputValue}` });
}
-
return filtered;
}}
- defaultValue={
- searchParams.get("pageLimit") != null &&
- searchParams.get("pageLimit")
- }
selectOnFocus
clearOnBlur
handleHomeEndKeys
id="Page Limit:"
- options={options}
+ options={pageSizeOptions}
size="small"
- getOptionLabel={(option) => {
- if (typeof option === "string") {
- return option;
- }
- if (option.inputValue) {
- return option.inputValue;
- }
- return option.label;
- }}
+ getOptionLabel={(option) =>
+ typeof option === "string"
+ ? option
+ : option.inputValue || option.label
+ }
renderOption={(props, option) => (
<MenuItem {...props} value={option.label}>
{option.label}
</MenuItem>
)}
sx={{
- width: "78px"
+ width: "78px",
+ "& .MuiOutlinedInput-root.MuiInputBase-sizeSmall
.MuiAutocomplete-input":
+ {
+ padding: "0 4px !important",
+ height: "15px !important"
+ }
}}
freeSolo
renderInput={(params) => <TextField type="number" {...params} />}
/>
</FormControl>
</Stack>
- {isFirstPage &&
- (!memoizedData.length || memoizedData.length < pagination.pageSize) ? (
- ""
- ) : (
+
+ {(isServerSide
+ ? !isFirstPage || memoizedData.length >= limit
+ : memoizedData.length >= pageSize) && (
<>
- <Stack className="table-pagination-filters-box">
- <Paper
- component="form"
- elevation={0}
- className="table-pagination-gotopage-paper"
- >
- <InputBase
- placeholder="Go to page:"
- type="number"
- inputProps={{ "aria-label": "Go to page:" }}
- size="small"
- onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
- const page = e.target.value
- ? Number(e.target.value) - 1
- : 0;
- setInputVal(page);
- }}
- className="table-pagination-gotopage-input"
- defaultValue={inputVal}
- />
- <LightTooltip title="Goto Page">
- <IconButton
- type="button"
+ {showGoToPage && (
+ <Stack className="table-pagination-filters-box">
+ <Paper
+ component="form"
+ elevation={0}
+ className="table-pagination-gotopage-paper"
+ onSubmit={(e) => e.preventDefault()}
+ >
+ <InputBase
+ placeholder="Go to page:"
+ type="number"
+ inputProps={{
+ "aria-label": "Go to page:",
+ style: { width: "100%" }
+ }}
size="small"
- className={`${
- !isEmpty(inputVal)
- ? "cursor-pointer"
- : "cursor-not-allowed"
- } table-pagination-gotopage-button`}
- aria-label="search"
- onClick={() => {
- if (!isEmpty(inputVal)) {
- if (inputVal >= 1) {
- setPageIndex(inputVal);
- searchParams.set(
- "pageOffset",
- pagination.pageSize * inputVal
- );
- navigate({ search: searchParams.toString() });
- } else {
- setPageIndex(0);
- searchParams.delete("pageOffset");
- navigate({ search: searchParams.toString() });
- }
- setInputVal("");
- } else {
- return;
+ onChange={(e) => {
+ setPendingGoToPageVal(e.target.value);
+ }}
+ onKeyUp={(e) => {
+ const goToPage = parseInt(e.currentTarget.value);
+ const anyEvent: any = e as any;
+ if ((anyEvent.key === "Enter" || anyEvent.keyCode ===
13) && !isNaN(goToPage) && goToPage >= 1) {
+ setGoToPageVal?.(e.currentTarget.value);
+ setGoToPageTrigger(e.currentTarget.value);
}
- setRowSelection({});
}}
- >
- Go
- </IconButton>
- </LightTooltip>
- </Paper>
- </Stack>
+ className="table-pagination-gotopage-input"
+ value={pendingGoToPageVal}
+ />
+ <LightTooltip title="Goto Page">
+ <IconButton
+ type="button"
+ size="small"
+ className={`${
+ !isEmpty(pendingGoToPageVal)
+ ? "cursor-pointer"
+ : "cursor-not-allowed"
+ } table-pagination-gotopage-button`}
+ aria-label="search"
+ onClick={() => {
+ if (!isEmpty(pendingGoToPageVal)) {
+ setGoToPageTrigger(pendingGoToPageVal);
+ handleGoToPage();
+ }
+ }}
+ disabled={isEmpty(pendingGoToPageVal)}
+ >
+ Go
+ </IconButton>
+ </LightTooltip>
+ </Paper>
+ </Stack>
+ )}
<Stack flexDirection="row" alignItems="center">
<LightTooltip title="Previous">
<IconButton
size="small"
- className="pagination-previous-btn"
- onClick={() => {
- setPagination((prev: { pageIndex: number }) => ({
- ...prev,
- pageIndex: prev.pageIndex - 1
- }));
- setRowSelection({});
- if (isFirstPage) {
- searchParams.delete("pageOffset");
- } else {
- searchParams.set(
- "pageOffset",
- `${pagination.pageSize * (pagination.pageIndex - 1)}`
- );
- navigate({ search: searchParams.toString() });
- }
- }}
- disabled={isFirstPage}
+ className="pagination-page-change-btn"
+ onClick={handlePreviousPage}
+ disabled={isPreviousDisabled}
aria-label="previous page"
>
{theme.direction === "rtl" ? (
@@ -333,30 +506,19 @@ const TablePagination: React.FC<PaginationProps> = ({
)}
</IconButton>
</LightTooltip>
- <LightTooltip title={`Page ${Math.round(pageTo / limit)}`}>
+
+ <LightTooltip title={`Page ${activePage}`}>
<IconButton size="small" className="table-pagination-page">
- {Math.round(pageIndex + 1)}
+ {activePage}
</IconButton>
</LightTooltip>
<LightTooltip title="Next">
<IconButton
size="small"
- className="pagination-next-btn"
- onClick={() => {
- setPagination((prev: { pageIndex: number }) => ({
- ...prev,
- pageIndex: prev.pageIndex + 1
- }));
- setRowSelection({});
-
- searchParams.set(
- "pageOffset",
- `${pagination.pageSize * (pagination.pageIndex + 1)}`
- );
- navigate({ search: searchParams.toString() });
- }}
- disabled={memoizedData.length < limit}
+ className="pagination-page-change-btn"
+ onClick={handleNextPage}
+ disabled={isNextDisabled}
aria-label="next page"
>
{theme.direction === "rtl" ? (
diff --git a/dashboard/src/components/commonComponents.tsx
b/dashboard/src/components/commonComponents.tsx
index 6db34530b..604b3c712 100644
--- a/dashboard/src/components/commonComponents.tsx
+++ b/dashboard/src/components/commonComponents.tsx
@@ -454,3 +454,27 @@ export const getValues = (
</span>
);
};
+
+export const GetNumberSuffix = (options: { number: any; sup?: boolean }) => {
+ if (options && options.number) {
+ let n = options.number;
+ let s = ["th", "st", "nd", "rd"];
+ let v = n % 100;
+ let suffix = s[(v - 20) % 10] || s[v] || s[0];
+ if (options.sup) {
+ return (
+ <>
+ <>{n}</>
+ <sup>{suffix}</sup>
+ </>
+ );
+ } else {
+ return (
+ <>
+ {n}
+ {suffix}
+ </>
+ );
+ }
+ }
+};
diff --git a/dashboard/src/models/tableLayoutType.ts
b/dashboard/src/models/tableLayoutType.ts
index 96e291585..0baeb064f 100644
--- a/dashboard/src/models/tableLayoutType.ts
+++ b/dashboard/src/models/tableLayoutType.ts
@@ -26,6 +26,7 @@ export interface TableProps {
skeletonHeight?: number;
headerComponent?: JSX.Element;
pageCount?: number;
+ totalCount?: number;
defaultColumnVisibility?: any;
page?: (page: number) => void;
onClickRow?: (cell: Cell<any, unknown>, row: Row<any>) => void;
@@ -52,4 +53,7 @@ export interface TableProps {
setUpdateTable?: any;
isfilterQuery?: any;
isClientSidePagination?: boolean;
+ isEmptyData?: boolean;
+ setIsEmptyData?: React.Dispatch<React.SetStateAction<boolean>>;
+ showGoToPage?: boolean;
}
diff --git a/dashboard/src/styles/table.scss b/dashboard/src/styles/table.scss
index 4f60f05b0..4cc8aceb1 100644
--- a/dashboard/src/styles/table.scss
+++ b/dashboard/src/styles/table.scss
@@ -91,18 +91,22 @@
align-items: center;
width: 150px;
border: 1px solid rgba(0, 0, 0, 0.23);
- height: 32px;
+ height: 27px;
}
-.pagination-previous-btn {
- height: 32px;
- width: 32px;
+.pagination-page-change-btn {
+ height: 24px;
+ width: 24px;
}
.table-pagination-gotopage-input {
margin-left: 5px;
flex: 1px;
}
+.table-pagination-gotopage-input input {
+ padding: 0 4px !important;
+}
+
.table-pagination-gotopage-button {
padding: 6px !important;
border-radius: 0 !important;
@@ -111,20 +115,11 @@
}
.table-pagination-page {
border-radius: 4px !important;
- height: 24px;
- width: 24px;
+ height: 20px;
color: #37bb9b !important;
border: 1px solid #37bb9b !important;
margin: 0 0.5rem !important;
-}
-
-.pagination-page-limit .MuiInputBase-fullWidth {
- padding-top: 3.5px !important;
- padding-bottom: 3.5px !important;
-}
-
-.pagination-page-limit input {
- height: 20px;
+ font-size: 0.875rem !important;
}
/* table-flters */
diff --git a/dashboard/src/utils/Enum.ts b/dashboard/src/utils/Enum.ts
index ffb6fcf90..f95ea8add 100644
--- a/dashboard/src/utils/Enum.ts
+++ b/dashboard/src/utils/Enum.ts
@@ -473,3 +473,21 @@ export const defaultType = [
"array<long>",
"array<date>"
];
+
+export const optionsVal = [
+ "25",
+ "50",
+ "100",
+ "150",
+ "200",
+ "250",
+ "300",
+ "350",
+ "400",
+ "450",
+ "500"
+];
+
+export const pageSizeOptions = optionsVal.map((x: string) => ({
+ label: x
+}));
diff --git a/dashboard/src/utils/Messages.ts b/dashboard/src/utils/Messages.ts
index bdb0d900d..8c1a424b8 100644
--- a/dashboard/src/utils/Messages.ts
+++ b/dashboard/src/utils/Messages.ts
@@ -15,6 +15,12 @@
* limitations under the License.
*/
-export const Messages = {
- defaultErrorMessage: "Something went wrong"
+const Messages = {
+ defaultErrorMessage: "Something went wrong",
+ search: {
+ noRecordForPage: "No record found at ",
+ onSamePage: "You are on the same page!"
+ }
};
+
+export default Messages;
diff --git a/dashboard/src/utils/Utils.ts b/dashboard/src/utils/Utils.ts
index dec30ec04..0e8b4fb95 100644
--- a/dashboard/src/utils/Utils.ts
+++ b/dashboard/src/utils/Utils.ts
@@ -22,7 +22,7 @@ import moment from "moment-timezone";
import sanitizeHtml from "sanitize-html";
import { cloneDeep, toArrayifObject, uniq } from "./Helper";
import { attributeFilter } from "./CommonViewFunction";
-import { Messages } from "./Messages";
+import Messages from "./Messages";
interface childrenInterface {
gType: string;
diff --git a/dashboard/src/views/Administrator/Audits/AdminAuditTable.tsx
b/dashboard/src/views/Administrator/Audits/AdminAuditTable.tsx
index 0a09cc3c7..04031e03f 100644
--- a/dashboard/src/views/Administrator/Audits/AdminAuditTable.tsx
+++ b/dashboard/src/views/Administrator/Audits/AdminAuditTable.tsx
@@ -52,14 +52,15 @@ const AdminAuditTable = () => {
const fetchAuditResult = useCallback(
async ({ pagination }: { pagination?: any }) => {
const { pageSize, pageIndex } = pagination || {};
- if (pageIndex > 1) {
- searchParams.set("pageOffset", `${pageSize * pageIndex}`);
- }
+ // Derive strictly from table state to avoid URL race conditions
+ const limit = pageSize || 25;
+ const offset = (pageIndex || 0) * limit;
+
let params: any = {
auditFilters: !isEmpty(queryApiObj) ? queryApiObj : null,
- limit: pageSize,
+ limit: limit,
sortOrder: "DESCENDING",
- offset: pageIndex * pageSize,
+ offset: offset,
sortBy: "startTime"
};
diff --git a/dashboard/src/views/DetailPage/EntityDetailTabs/AuditsTab.tsx
b/dashboard/src/views/DetailPage/EntityDetailTabs/AuditsTab.tsx
index 79d8b0d40..4b4a1efa9 100644
--- a/dashboard/src/views/DetailPage/EntityDetailTabs/AuditsTab.tsx
+++ b/dashboard/src/views/DetailPage/EntityDetailTabs/AuditsTab.tsx
@@ -49,16 +49,16 @@ const AuditsTab = ({
sorting: [{ id: string; desc: boolean }];
}) => {
const { pageSize, pageIndex } = pagination || {};
- if (pageIndex > 1) {
- searchParams.set("pageOffset", `${pageSize * pageIndex}`);
- }
+ const limitParam = searchParams.get("pageLimit");
+ const offsetParam = searchParams.get("pageOffset");
+ const limit = !isEmpty(limitParam) ? Number(limitParam) : pageSize;
+ const offset = !isEmpty(offsetParam)
+ ? Number(offsetParam)
+ : pageIndex * pageSize;
let params: any = {
sortOrder: sorting[0]?.desc == false ? "asc" : "desc",
- offset:
- searchParams.get("pageOffset") != null
- ? Number(searchParams.get("pageOffset"))
- : pageIndex * pageSize,
- count: pageSize,
+ offset: offset,
+ limit: limit,
sortBy: sorting[0]?.id || "timestamp"
};
diff --git
a/dashboard/src/views/DetailPage/EntityDetailTabs/ClassificationsTab.tsx
b/dashboard/src/views/DetailPage/EntityDetailTabs/ClassificationsTab.tsx
index adb162102..bba595dec 100644
--- a/dashboard/src/views/DetailPage/EntityDetailTabs/ClassificationsTab.tsx
+++ b/dashboard/src/views/DetailPage/EntityDetailTabs/ClassificationsTab.tsx
@@ -365,6 +365,9 @@ const ClassificationsTab: React.FC<EntityDetailTabProps> =
({
showPagination={true}
showRowSelection={false}
tableFilters={false}
+ showGoToPage={true}
+ setUpdateTable={setUpdateTable}
+ isClientSidePagination={true}
/>
</Stack>
</Grid>
diff --git a/dashboard/src/views/DetailPage/EntityDetailTabs/ProfileTab.tsx
b/dashboard/src/views/DetailPage/EntityDetailTabs/ProfileTab.tsx
index 81b8d001d..baea3e3c9 100644
--- a/dashboard/src/views/DetailPage/EntityDetailTabs/ProfileTab.tsx
+++ b/dashboard/src/views/DetailPage/EntityDetailTabs/ProfileTab.tsx
@@ -88,17 +88,15 @@ const ProfileTab: React.FC<EntityDetailTabProps> = ({
entity }) => {
return;
}
const { pageSize, pageIndex } = pagination || {};
+ const offsetParam = searchParams.get("pageOffset");
+ const limitParam = searchParams.get("pageLimit");
if (pageIndex > 1) {
- searchParams.set("pageOffset", `${pageSize * pageIndex}`);
+ searchParams.set("pageOffset", `${pageSize + pageIndex}`);
}
let params: any = {
order: sorting[0]?.desc == false ? "asc" : "desc",
- offset:
- searchParams.get("pageOffset") !== undefined &&
- searchParams.get("pageOffset") !== null
- ? Number(searchParams.get("pageOffset"))
- : pageIndex * pageSize,
- limit: pageSize,
+ offset: !isEmpty(offsetParam) ? offsetParam : pageIndex + pageSize,
+ limit: !isEmpty(limitParam) ? limitParam : pageSize,
sort_by: sorting[0]?.id || "timestamp",
guid: guid,
relation:
diff --git
a/dashboard/src/views/DetailPage/EntityDetailTabs/ReplicationAuditTab.tsx
b/dashboard/src/views/DetailPage/EntityDetailTabs/ReplicationAuditTab.tsx
index 63ebb363d..6373f77e0 100644
--- a/dashboard/src/views/DetailPage/EntityDetailTabs/ReplicationAuditTab.tsx
+++ b/dashboard/src/views/DetailPage/EntityDetailTabs/ReplicationAuditTab.tsx
@@ -32,6 +32,7 @@ import RauditsTableResults from "./RauditsTableResults";
const ReplicationAuditTable = (props: any) => {
const { entity = {}, referredEntities = {}, loading } = props;
const [searchParams] = useSearchParams();
+ const limitParam = searchParams.get("pageLimit");
const { guid } = useParams();
const toastId: any = useRef(null);
const [loader, setLoader] = useState<boolean>(false);
@@ -52,7 +53,7 @@ const ReplicationAuditTable = (props: any) => {
let params: any = {
serverName: name,
- limit: pageSize
+ limit: !isEmpty(limitParam) ? limitParam : pageSize
};
setLoader(true);
diff --git a/dashboard/src/views/SearchResult/RelationShipSearch.tsx
b/dashboard/src/views/SearchResult/RelationShipSearch.tsx
index 6774f16b4..3b5e8e9ba 100644
--- a/dashboard/src/views/SearchResult/RelationShipSearch.tsx
+++ b/dashboard/src/views/SearchResult/RelationShipSearch.tsx
@@ -79,7 +79,7 @@ const RelationShipSearch: React.FC = () => {
async ({ pagination }: { pagination?: any }) => {
const { pageSize, pageIndex } = pagination || {};
if (pageIndex > 1) {
- searchParams.set("pageOffset", `${pageSize * pageIndex}`);
+ searchParams.set("pageOffset", `${pageSize + pageIndex}`);
}
let params: Params = {
attributes: !isEmpty(attributesParams)
@@ -90,8 +90,8 @@ const RelationShipSearch: React.FC = () => {
: [],
limit: !isEmpty(limitParams) ? Number(limitParams) : pageSize,
offset: !isEmpty(offsetParams)
- ? Number(searchParams.get("pageOffset"))
- : pageIndex * pageSize,
+ ? Number(offsetParams)
+ : pageIndex + pageSize,
relationshipFilters: !isEmpty(relationshipFiltersParams)
? relationshipFiltersParams
: null,
diff --git a/dashboard/src/views/SearchResult/SearchResult.tsx
b/dashboard/src/views/SearchResult/SearchResult.tsx
index 90f0e55f4..aa1cd0921 100644
--- a/dashboard/src/views/SearchResult/SearchResult.tsx
+++ b/dashboard/src/views/SearchResult/SearchResult.tsx
@@ -91,6 +91,8 @@ const SearchResult = ({ classificationParams,
glossaryTypeParams }: any) => {
const [updateTable, setUpdateTable] = useState(moment.now());
const { entityData } = useSelector((state: EntityState) => state.entity);
const [pageCount, setPageCount] = useState<number>(0);
+ const [totalCount, setTotalCount] = useState<number>(0);
+ const [isEmptyData, setIsEmptyData] = useState(false);
const [checkedEntities, setCheckedEntities] = useState<any>(
!isEmpty(searchParams.get("includeDE"))
? searchParams.get("includeDE")
@@ -139,9 +141,7 @@ const SearchResult = ({ classificationParams,
glossaryTypeParams }: any) => {
async ({ pagination }: { pagination?: any }) => {
setLoader(true);
const { pageSize, pageIndex } = pagination || {};
- if (pageIndex > 1) {
- searchParams.set("pageOffset", `${pageSize * pageIndex}`);
- }
+
let params: Params | any = {
excludeDeletedEntities: !isEmpty(searchParams.get("includeDE"))
? !searchParams.get("includeDE")
@@ -210,7 +210,7 @@ const SearchResult = ({ classificationParams,
glossaryTypeParams }: any) => {
let searchTypeParams =
searchParams.get("searchType") == "dsl" ? dslParams : params;
try {
- let searchResp = await getBasicSearchResult(
+ const searchResp = await getBasicSearchResult(
{
data:
(searchParams.get("searchType") == "basic" ||
@@ -220,12 +220,29 @@ const SearchResult = ({ classificationParams,
glossaryTypeParams }: any) => {
},
searchParams.get("searchType") || "basic"
);
- let totalCount = searchResp.data.approximateCount;
- setSearchData(searchResp.data);
- setPageCount(Math.ceil(totalCount / pagination.pageSize));
- setLoader(false);
+ const { data = {} } = searchResp || {};
+ const { approximateCount, entities } = data || {};
+ let totalCount = approximateCount;
+ let dataLength;
+ if (entities) {
+ dataLength = entities?.length;
+ } else {
+ dataLength = searchResp?.data?.length;
+ }
+ if (!dataLength) {
+ setIsEmptyData(true);
+ setLoader(false);
+ } else {
+ setSearchData(searchResp.data);
+ setTotalCount(totalCount || 0);
+ setPageCount(Math.ceil(totalCount / pagination.pageSize));
+ setLoader(false);
+ }
} catch (error: any) {
- console.error("Error fetching data:",
error.response.data.errorMessage);
+ console.error(
+ "Error fetching data:",
+ error?.response?.data?.errorMessage
+ );
toast.dismiss(toastId.current);
serverError(error, toastId);
setLoader(false);
@@ -451,7 +468,6 @@ const SearchResult = ({ classificationParams,
glossaryTypeParams }: any) => {
obj.name === superTypesEntityData.name
);
if (referredEntities == -1) {
- // let referredEntities = searchData.referredEntities[obj?.guid];
superTypesObj = {
accessorFn: (row: any) =>
row.attributes[superTypesEntityData.name],
@@ -765,17 +781,7 @@ const SearchResult = ({ classificationParams,
glossaryTypeParams }: any) => {
const getDefaultSort = useMemo(() => [{ id: "name", asc: true }], []);
return (
- <Stack
- // paddingTop={
- // isEmpty(classificationParams || glossaryTypeParams) ? 0 : "40px"
- // }
- // marginTop={
- // isEmpty(classificationParams || glossaryTypeParams) ? 0 : "20px"
- // }
- // padding="16px"
- position="relative"
- gap={"1rem"}
- >
+ <Stack position="relative" gap={"1rem"}>
{!isEmpty(classificationParams || glossaryTypeParams) && (
<Stack
direction="row"
@@ -825,9 +831,6 @@ const SearchResult = ({ classificationParams,
glossaryTypeParams }: any) => {
</Stack>
)}
- {/* {loader ? (
- <CircularProgress />
- ) : ( */}
<TableLayout
fetchData={fetchSearchResult}
data={searchData.entities || []}
@@ -863,11 +866,13 @@ const SearchResult = ({ classificationParams,
glossaryTypeParams }: any) => {
allTableFilters={true}
setUpdateTable={setUpdateTable}
isfilterQuery={true}
+ isEmptyData={isEmptyData}
+ setIsEmptyData={setIsEmptyData}
+ showGoToPage={true}
+ totalCount={totalCount}
/>
- {/* )} */}
</Stack>
);
- // );
};
export default SearchResult;
diff --git a/dashboard/src/views/SideBar/SideBarTree/SideBarTree.tsx
b/dashboard/src/views/SideBar/SideBarTree/SideBarTree.tsx
index 04813f375..dd49cfeb6 100644
--- a/dashboard/src/views/SideBar/SideBarTree/SideBarTree.tsx
+++ b/dashboard/src/views/SideBar/SideBarTree/SideBarTree.tsx
@@ -604,7 +604,11 @@ const BarTreeView: FC<{
searchParams.delete("entityFilters");
searchParams.delete("tagFilters");
searchParams.delete("relationshipFilters");
- searchParams.delete("pageOffset");
+ // Always reset pagination defaults on tree navigation
+ if (treeName !== "CustomFilters") {
+ searchParams.set("pageLimit", "25");
+ searchParams.set("pageOffset", "0");
+ }
};
const setGlossarySearchParams = (