Skip to content

Commit

Permalink
feat: features list pagination (#5496)
Browse files Browse the repository at this point in the history
New paginated table - tested on /features-new behind a flag
  • Loading branch information
Tymek authored Dec 1, 2023
1 parent be17b7f commit 755c22f
Show file tree
Hide file tree
Showing 28 changed files with 543 additions and 564 deletions.
1 change: 0 additions & 1 deletion frontend/orval.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ module.exports = {
target: 'apis',
schemas: 'models',
client: 'swr',
prettier: true,
clean: true,
// mock: true,
override: {
Expand Down
4 changes: 4 additions & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"@mui/icons-material": "5.11.9",
"@mui/lab": "5.0.0-alpha.120",
"@mui/material": "5.11.10",
"@tanstack/react-table": "^8.10.7",
"@testing-library/dom": "8.20.1",
"@testing-library/jest-dom": "5.17.0",
"@testing-library/react": "12.1.5",
Expand All @@ -47,6 +48,7 @@
"@types/deep-diff": "1.0.5",
"@types/jest": "29.5.10",
"@types/lodash.clonedeep": "4.5.9",
"@types/lodash.mapvalues": "^4.6.9",
"@types/lodash.omit": "4.5.9",
"@types/node": "18.17.19",
"@types/react": "17.0.71",
Expand Down Expand Up @@ -79,6 +81,7 @@
"immer": "9.0.21",
"jsdom": "22.1.0",
"lodash.clonedeep": "4.5.0",
"lodash.mapvalues": "^4.6.0",
"lodash.omit": "4.5.0",
"mermaid": "^9.3.0",
"millify": "^6.0.0",
Expand All @@ -105,6 +108,7 @@
"swr": "2.2.4",
"tss-react": "4.9.3",
"typescript": "4.8.4",
"use-query-params": "^2.2.1",
"vanilla-jsoneditor": "^0.19.0",
"vite": "4.5.0",
"vite-plugin-env-compatible": "1.1.1",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ const StyledChip = styled(
)(({ theme, isActive = false }) => ({
borderRadius: `${theme.shape.borderRadius}px`,
padding: 0,
margin: theme.spacing(0, 0, 1, 0),
fontSize: theme.typography.body2.fontSize,
...(isActive
? {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export const FavoriteIconHeader: VFC<IFavoriteIconHeaderProps> = ({
<IconButton
sx={{
mx: -0.75,
my: -1,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
Expand Down
114 changes: 114 additions & 0 deletions frontend/src/component/common/Table/PaginatedTable/PaginatedTable.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import { TableBody, TableRow, TableHead } from '@mui/material';
import { Table } from 'component/common/Table/Table/Table';
import {
Header,
type Table as TableType,
flexRender,
} from '@tanstack/react-table';
import { TableCell } from '../TableCell/TableCell';
import { CellSortable } from '../SortableTableHeader/CellSortable/CellSortable';
import { StickyPaginationBar } from 'component/common/Table/StickyPaginationBar/StickyPaginationBar';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';

const HeaderCell = <T extends object>(header: Header<T, unknown>) => {
const column = header.column;
const isDesc = column.getIsSorted() === 'desc';
const align = column.columnDef.meta?.align || undefined;

return (
<CellSortable
isSortable={column.getCanSort()}
isSorted={column.getIsSorted() !== false}
isDescending={isDesc}
align={align}
onClick={() => column.toggleSorting()}
styles={{ borderRadius: '0px' }}
>
{header.isPlaceholder
? null
: flexRender(column.columnDef.header, header.getContext())}
</CellSortable>
);
};

/**
* Use with react-table v8
*/
export const PaginatedTable = <T extends object>({
totalItems,
tableInstance,
}: {
tableInstance: TableType<T>;
totalItems?: number;
}) => {
const { pagination } = tableInstance.getState();

return (
<>
<Table>
<TableHead>
{tableInstance.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id}>
{headerGroup.headers.map((header) => (
<HeaderCell {...header} key={header.id} />
))}
</TableRow>
))}
</TableHead>
<TableBody
role='rowgroup'
sx={{
'& tr': {
'&:hover': {
'.show-row-hover': {
opacity: 1,
},
},
},
}}
>
{tableInstance.getRowModel().rows.map((row) => (
<TableRow key={row.id}>
{row.getVisibleCells().map((cell) => (
<TableCell key={cell.id}>
{flexRender(
cell.column.columnDef.cell,
cell.getContext(),
)}
</TableCell>
))}
</TableRow>
))}
</TableBody>
</Table>
<ConditionallyRender
condition={tableInstance.getRowModel().rows.length > 0}
show={
<StickyPaginationBar
totalItems={totalItems}
pageIndex={pagination.pageIndex}
pageSize={pagination.pageSize}
fetchNextPage={() =>
tableInstance.setPagination({
pageIndex: pagination.pageIndex + 1,
pageSize: pagination.pageSize,
})
}
fetchPrevPage={() =>
tableInstance.setPagination({
pageIndex: pagination.pageIndex - 1,
pageSize: pagination.pageSize,
})
}
setPageLimit={(pageSize) =>
tableInstance.setPagination({
pageIndex: 0,
pageSize,
})
}
/>
}
/>
</>
);
};
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react';
import { Box, Typography, Button, styled } from '@mui/material';
import { ConditionallyRender } from '../ConditionallyRender/ConditionallyRender';
import { ConditionallyRender } from '../../ConditionallyRender/ConditionallyRender';
import { ReactComponent as ArrowRight } from 'assets/icons/arrowRight.svg';
import { ReactComponent as ArrowLeft } from 'assets/icons/arrowLeft.svg';

Expand Down Expand Up @@ -44,51 +44,42 @@ const StyledSelect = styled('select')(({ theme }) => ({
}));

interface PaginationBarProps {
total: number;
currentOffset: number;
totalItems?: number;
pageIndex: number;
pageSize: number;
fetchPrevPage: () => void;
fetchNextPage: () => void;
hasPreviousPage: boolean;
hasNextPage: boolean;
pageLimit: number;
setPageLimit: (limit: number) => void;
}

export const PaginationBar: React.FC<PaginationBarProps> = ({
total,
currentOffset,
totalItems,
pageSize,
pageIndex = 0,
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;
};
const itemRange =
totalItems !== undefined && pageSize && totalItems > 1
? `${pageIndex * pageSize + 1}-${Math.min(
totalItems,
(pageIndex + 1) * pageSize,
)}`
: totalItems;
const pageCount =
totalItems !== undefined ? Math.ceil(totalItems / pageSize) : 1;
const hasPreviousPage = pageIndex > 0;
const hasNextPage = totalItems !== undefined && pageIndex < pageCount - 1;

return (
<StyledBoxContainer>
<StyledTypography>
Showing {calculatePageOffset(currentOffset, total)} out of{' '}
{total}
{totalItems !== undefined
? `Showing ${itemRange} item${
totalItems !== 1 ? 's' : ''
} out of ${totalItems}`
: ' '}
</StyledTypography>
<StyledCenterBox>
<ConditionallyRender
Expand All @@ -104,8 +95,7 @@ export const PaginationBar: React.FC<PaginationBarProps> = ({
}
/>
<StyledTypographyPageText>
Page {calculateCurrentPage(currentOffset)} of{' '}
{calculateTotalPages(total, pageLimit)}
Page {pageIndex + 1} of {pageCount}
</StyledTypographyPageText>
<ConditionallyRender
condition={hasNextPage}
Expand All @@ -123,16 +113,16 @@ export const PaginationBar: React.FC<PaginationBarProps> = ({
<StyledCenterBox>
<StyledTypography>Show rows</StyledTypography>

{/* 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.
{/* 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.
*/}
<StyledSelect
value={pageLimit}
value={pageSize}
onChange={(event: React.ChangeEvent<HTMLSelectElement>) =>
setPageLimit(Number(event.target.value))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ interface ICellSortableProps {
isFlex?: boolean;
isFlexGrow?: boolean;
onClick?: MouseEventHandler<HTMLButtonElement>;
styles: React.CSSProperties;
styles?: React.CSSProperties;
}

export const CellSortable: FC<ICellSortableProps> = ({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,17 @@
import { Box, styled } from '@mui/material';
import { PaginationBar } from 'component/common/PaginationBar/PaginationBar';
import { PaginationBar } from '../PaginationBar/PaginationBar';
import { ComponentProps, FC } from 'react';

const StyledStickyBar = styled('div')(({ theme }) => ({
position: 'sticky',
bottom: 0,
backgroundColor: theme.palette.background.paper,
padding: theme.spacing(2),
marginLeft: theme.spacing(2),
padding: theme.spacing(1.5, 2),
zIndex: theme.zIndex.fab,
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 }) => ({
Expand All @@ -25,12 +23,10 @@ const StyledStickyBarContentContainer = styled(Box)(({ theme }) => ({

export const StickyPaginationBar: FC<ComponentProps<typeof PaginationBar>> = ({
...props
}) => {
return (
<StyledStickyBar>
<StyledStickyBarContentContainer>
<PaginationBar {...props} />
</StyledStickyBarContentContainer>
</StyledStickyBar>
);
};
}) => (
<StyledStickyBar>
<StyledStickyBarContentContainer>
<PaginationBar {...props} />
</StyledStickyBarContentContainer>
</StyledStickyBar>
);
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,15 @@ const StyledIconButton = styled(IconButton)(({ theme }) => ({

const StyledIconButtonInactive = styled(StyledIconButton)({
opacity: 0,
'&:hover': {
opacity: 1,
},
'&:focus': {
opacity: 1,
},
'&:active': {
opacity: 1,
},
});

interface IFavoriteIconCellProps {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import React, { VFC } from 'react';
import { IFeatureToggleListItem } from 'interfaces/featureToggle';
import { FeatureEnvironmentSeen } from 'component/feature/FeatureView/FeatureEnvironmentSeen/FeatureEnvironmentSeen';
import { FeatureSchema } from 'openapi';

interface IFeatureSeenCellProps {
feature: IFeatureToggleListItem;
feature: FeatureSchema;
}

export const FeatureEnvironmentSeenCell: VFC<IFeatureSeenCellProps> = ({
Expand All @@ -16,7 +16,7 @@ export const FeatureEnvironmentSeenCell: VFC<IFeatureSeenCellProps> = ({

return (
<FeatureEnvironmentSeen
featureLastSeen={feature.lastSeenAt}
featureLastSeen={feature.lastSeenAt || undefined}
environments={environments}
{...rest}
/>
Expand Down
1 change: 1 addition & 0 deletions frontend/src/component/common/Table/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ export { Table } from './Table/Table';
export { TableCell } from './TableCell/TableCell';
export { TablePlaceholder } from './TablePlaceholder/TablePlaceholder';
export { VirtualizedTable } from './VirtualizedTable/VirtualizedTable';
export { PaginatedTable } from './PaginatedTable/PaginatedTable';
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { Box } from '@mui/material';
import { FilterItem } from 'component/common/FilterItem/FilterItem';
import useProjects from 'hooks/api/getters/useProjects/useProjects';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { useTableState } from 'hooks/useTableState';

export type FeatureTogglesListFilters = {
projectId?: string;
Expand All @@ -25,7 +24,7 @@ export const FeatureToggleFilters: VFC<IFeatureToggleFiltersProps> = ({
}));

return (
<Box sx={(theme) => ({ marginBottom: theme.spacing(2) })}>
<Box sx={(theme) => ({ padding: theme.spacing(2, 3) })}>
<ConditionallyRender
condition={projectsOptions.length > 1}
show={() => (
Expand Down
Loading

0 comments on commit 755c22f

Please sign in to comment.