diff --git a/frontend/src/assets/icons/arrowLeft.svg b/frontend/src/assets/icons/arrowLeft.svg
new file mode 100644
index 0000000000..f57b57ff6e
--- /dev/null
+++ b/frontend/src/assets/icons/arrowLeft.svg
@@ -0,0 +1,10 @@
+
\ No newline at end of file
diff --git a/frontend/src/assets/icons/arrowRight.svg b/frontend/src/assets/icons/arrowRight.svg
new file mode 100644
index 0000000000..1944b05d3d
--- /dev/null
+++ b/frontend/src/assets/icons/arrowRight.svg
@@ -0,0 +1,10 @@
+
\ No newline at end of file
diff --git a/frontend/src/component/common/BatchSelectionActionsBar/BatchSelectionActionsBar.tsx b/frontend/src/component/common/BatchSelectionActionsBar/BatchSelectionActionsBar.tsx
index 324b68671d..7fc34a9dca 100644
--- a/frontend/src/component/common/BatchSelectionActionsBar/BatchSelectionActionsBar.tsx
+++ b/frontend/src/component/common/BatchSelectionActionsBar/BatchSelectionActionsBar.tsx
@@ -8,8 +8,7 @@ interface IBatchSelectionActionsBarProps {
const StyledStickyContainer = styled('div')(({ theme }) => ({
position: 'sticky',
- marginTop: 'auto',
- bottom: 0,
+ bottom: 50,
zIndex: theme.zIndex.mobileStepper,
pointerEvents: 'none',
}));
diff --git a/frontend/src/component/common/PaginationBar/PaginationBar.tsx b/frontend/src/component/common/PaginationBar/PaginationBar.tsx
new file mode 100644
index 0000000000..6954894f92
--- /dev/null
+++ b/frontend/src/component/common/PaginationBar/PaginationBar.tsx
@@ -0,0 +1,148 @@
+import React from 'react';
+import { Box, Typography, Button, styled } from '@mui/material';
+import { ConditionallyRender } from '../ConditionallyRender/ConditionallyRender';
+import { ReactComponent as ArrowRight } from 'assets/icons/arrowRight.svg';
+import { ReactComponent as ArrowLeft } from 'assets/icons/arrowLeft.svg';
+
+const StyledPaginationButton = styled(Button)(({ theme }) => ({
+ padding: `0 ${theme.spacing(0.8)}`,
+ minWidth: 'auto',
+}));
+
+const StyledTypography = styled(Typography)(({ theme }) => ({
+ color: theme.palette.text.secondary,
+ fontSize: theme.fontSizes.smallerBody,
+}));
+
+const StyledTypographyPageText = styled(Typography)(({ theme }) => ({
+ marginLeft: theme.spacing(2),
+ marginRight: theme.spacing(2),
+ color: theme.palette.text.primary,
+ fontSize: theme.fontSizes.smallerBody,
+}));
+
+const StyledBoxContainer = styled(Box)({
+ display: 'flex',
+ justifyContent: 'space-between',
+ alignItems: 'center',
+ width: '100%',
+});
+
+const StyledCenterBox = styled(Box)({
+ display: 'flex',
+ alignItems: 'center',
+});
+
+const StyledSelect = styled('select')(({ theme }) => ({
+ backgroundColor: theme.palette.background.paper,
+ border: `1px solid ${theme.palette.divider}`,
+ borderRadius: theme.shape.borderRadius,
+ padding: theme.spacing(0.5),
+ fontSize: theme.fontSizes.smallBody,
+ marginLeft: theme.spacing(1),
+ color: theme.palette.text.primary,
+}));
+
+interface PaginationBarProps {
+ total: number;
+ currentOffset: number;
+ fetchPrevPage: () => void;
+ fetchNextPage: () => void;
+ hasPreviousPage: boolean;
+ hasNextPage: boolean;
+ pageLimit: number;
+ setPageLimit: (limit: number) => void;
+}
+
+export const PaginationBar: React.FC = ({
+ total,
+ currentOffset,
+ fetchPrevPage,
+ fetchNextPage,
+ hasPreviousPage,
+ hasNextPage,
+ pageLimit,
+ setPageLimit,
+}) => {
+ const calculatePageOffset = (
+ currentOffset: number,
+ total: number,
+ ): string => {
+ if (total === 0) return '0-0';
+
+ const start = currentOffset + 1;
+ const end = Math.min(total, currentOffset + pageLimit);
+
+ return `${start}-${end}`;
+ };
+
+ const calculateTotalPages = (total: number, offset: number): number => {
+ return Math.ceil(total / pageLimit);
+ };
+
+ const calculateCurrentPage = (offset: number): number => {
+ return Math.floor(offset / pageLimit) + 1;
+ };
+
+ return (
+
+
+ Showing {calculatePageOffset(currentOffset, total)} out of{' '}
+ {total}
+
+
+
+
+
+ }
+ />
+
+ Page {calculateCurrentPage(currentOffset)} of{' '}
+ {calculateTotalPages(total, pageLimit)}
+
+
+
+
+ }
+ />
+
+
+ Show rows
+
+ {/* We are using the native select element instead of the Material-UI Select
+ component due to an issue with Material-UI's Select. When the Material-UI
+ Select dropdown is opened, it temporarily removes the scrollbar,
+ causing the page to jump. This can be disorienting for users.
+ The native select does not have this issue,
+ as it does not affect the scrollbar when opened.
+ Therefore, we use the native select to provide a better user experience.
+ */}
+ ) =>
+ setPageLimit(Number(event.target.value))
+ }
+ >
+
+
+
+
+
+
+
+ );
+};
diff --git a/frontend/src/component/project/Project/ProjectFeatureToggles/PaginatedProjectFeatureToggles.tsx b/frontend/src/component/project/Project/ProjectFeatureToggles/PaginatedProjectFeatureToggles.tsx
index ac554290a1..5f5390053a 100644
--- a/frontend/src/component/project/Project/ProjectFeatureToggles/PaginatedProjectFeatureToggles.tsx
+++ b/frontend/src/component/project/Project/ProjectFeatureToggles/PaginatedProjectFeatureToggles.tsx
@@ -75,6 +75,7 @@ interface IPaginatedProjectFeatureTogglesProps {
total?: number;
searchValue: string;
setSearchValue: React.Dispatch>;
+ paginationBar: JSX.Element;
}
const staticColumns = ['Select', 'Actions', 'name', 'favorite'];
@@ -91,6 +92,7 @@ export const PaginatedProjectFeatureToggles = ({
total,
searchValue,
setSearchValue,
+ paginationBar,
}: IPaginatedProjectFeatureTogglesProps) => {
const { classes: styles } = useStyles();
const theme = useTheme();
@@ -491,6 +493,7 @@ export const PaginatedProjectFeatureToggles = ({
{featureToggleModals}
+
+ {paginationBar}
diff --git a/frontend/src/component/project/Project/ProjectOverview.tsx b/frontend/src/component/project/Project/ProjectOverview.tsx
index 44f2c2ed8f..00e2de7386 100644
--- a/frontend/src/component/project/Project/ProjectOverview.tsx
+++ b/frontend/src/component/project/Project/ProjectOverview.tsx
@@ -16,6 +16,8 @@ import { useFeatureSearch } from 'hooks/api/getters/useFeatureSearch/useFeatureS
import { PaginatedProjectFeatureToggles } from './ProjectFeatureToggles/PaginatedProjectFeatureToggles';
import { useSearchParams } from 'react-router-dom';
+import { PaginationBar } from 'component/common/PaginationBar/PaginationBar';
+
const refreshInterval = 15 * 1000;
const StyledContainer = styled('div')(({ theme }) => ({
@@ -37,14 +39,13 @@ const StyledContentContainer = styled(Box)(() => ({
minWidth: 0,
}));
-const PAGE_LIMIT = 25;
-
const PaginatedProjectOverview = () => {
const projectId = useRequiredPathParam('projectId');
const [searchParams, setSearchParams] = useSearchParams();
const { project, loading: projectLoading } = useProject(projectId, {
refreshInterval,
});
+ const [pageLimit, setPageLimit] = useState(10);
const [currentOffset, setCurrentOffset] = useState(0);
const [searchValue, setSearchValue] = useState(
@@ -56,7 +57,7 @@ const PaginatedProjectOverview = () => {
total,
refetch,
loading,
- } = useFeatureSearch(currentOffset, PAGE_LIMIT, projectId, searchValue, {
+ } = useFeatureSearch(currentOffset, pageLimit, projectId, searchValue, {
refreshInterval,
});
@@ -64,15 +65,15 @@ const PaginatedProjectOverview = () => {
project;
const fetchNextPage = () => {
if (!loading) {
- setCurrentOffset(Math.min(total, currentOffset + PAGE_LIMIT));
+ setCurrentOffset(Math.min(total, currentOffset + pageLimit));
}
};
const fetchPrevPage = () => {
- setCurrentOffset(Math.max(0, currentOffset - PAGE_LIMIT));
+ setCurrentOffset(Math.max(0, currentOffset - pageLimit));
};
const hasPreviousPage = currentOffset > 0;
- const hasNextPage = currentOffset + PAGE_LIMIT < total;
+ const hasNextPage = currentOffset + pageLimit < total;
return (
@@ -100,14 +101,20 @@ const PaginatedProjectOverview = () => {
total={total}
searchValue={searchValue}
setSearchValue={setSearchValue}
- />
- Prev}
- />
- Next}
+ paginationBar={
+
+
+
+ }
/>
@@ -115,6 +122,37 @@ const PaginatedProjectOverview = () => {
);
};
+const StyledStickyBar = styled('div')(({ theme }) => ({
+ position: 'sticky',
+ bottom: 0,
+ backgroundColor: theme.palette.background.paper,
+ padding: theme.spacing(2),
+ marginLeft: theme.spacing(2),
+ zIndex: theme.zIndex.mobileStepper,
+ borderBottomLeftRadius: theme.shape.borderRadiusMedium,
+ borderBottomRightRadius: theme.shape.borderRadiusMedium,
+ borderTop: `1px solid ${theme.palette.divider}`,
+ boxShadow: `0px -2px 8px 0px rgba(32, 32, 33, 0.06)`,
+ height: '52px',
+}));
+
+const StyledStickyBarContentContainer = styled(Box)(({ theme }) => ({
+ display: 'flex',
+ justifyContent: 'space-between',
+ width: '100%',
+ minWidth: 0,
+}));
+
+const StickyPaginationBar: React.FC = ({ children }) => {
+ return (
+
+
+ {children}
+
+
+ );
+};
+
/**
* @deprecated remove when flag `featureSearchFrontend` is removed
*/
diff --git a/src/server-dev.ts b/src/server-dev.ts
index 44ffd4c1e3..198800edd6 100644
--- a/src/server-dev.ts
+++ b/src/server-dev.ts
@@ -48,7 +48,7 @@ process.nextTick(async () => {
playgroundImprovements: true,
featureSwitchRefactor: true,
featureSearchAPI: true,
- featureSearchFrontend: false,
+ featureSearchFrontend: true,
},
},
authentication: {