mirror of
https://github.com/Unleash/unleash.git
synced 2025-06-14 01:16:17 +02:00
feat: project features - new columns visibility handling (#5605)
In `ExperimentalProjectTable.tsx`, changes have been made to the columns configuration in order to handle column visibiilty. This includes adding id property to every column. Logic responsible for dynamically adjusts column visibility based on screen size was moved to new hook, `useDefaultColumnVisibility`
This commit is contained in:
parent
c0c1dba9b0
commit
17b747ea8f
@ -45,7 +45,6 @@ const PaginatedProjectOverview = () => {
|
|||||||
<StyledContentContainer>
|
<StyledContentContainer>
|
||||||
<StyledProjectToggles>
|
<StyledProjectToggles>
|
||||||
<ExperimentalProjectFeatureToggles
|
<ExperimentalProjectFeatureToggles
|
||||||
style={{ width: '100%', margin: 0 }}
|
|
||||||
environments={environments}
|
environments={environments}
|
||||||
storageKey='project-features'
|
storageKey='project-features'
|
||||||
/>
|
/>
|
||||||
|
@ -96,14 +96,17 @@ export const ExperimentalColumnsMenu: VFC<IColumnsMenuProps> = ({
|
|||||||
<MenuList>
|
<MenuList>
|
||||||
{columns.map((column) =>
|
{columns.map((column) =>
|
||||||
column.id === 'divider' ? (
|
column.id === 'divider' ? (
|
||||||
<StyledDivider />
|
<StyledDivider key='divider' />
|
||||||
) : (
|
) : (
|
||||||
<StyledMenuItem
|
<StyledMenuItem
|
||||||
key={column.id}
|
key={column.id}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
onToggle?.(column.id);
|
onToggle?.(column.id);
|
||||||
}}
|
}}
|
||||||
disabled={column.isStatic === true}
|
disabled={
|
||||||
|
column.isStatic === true &&
|
||||||
|
column.isVisible === true
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<ListItemIcon>
|
<ListItemIcon>
|
||||||
<StyledCheckbox
|
<StyledCheckbox
|
||||||
|
@ -91,25 +91,26 @@ import { type FeatureSearchResponseSchema } from 'openapi';
|
|||||||
import { FeatureNameCell } from 'component/common/Table/cells/FeatureNameCell/FeatureNameCell';
|
import { FeatureNameCell } from 'component/common/Table/cells/FeatureNameCell/FeatureNameCell';
|
||||||
import { FeatureToggleCell } from './FeatureToggleCell/FeatureToggleCell';
|
import { FeatureToggleCell } from './FeatureToggleCell/FeatureToggleCell';
|
||||||
import { ProjectOverviewFilters } from './ProjectOverviewFilters';
|
import { ProjectOverviewFilters } from './ProjectOverviewFilters';
|
||||||
|
import { useDefaultColumnVisibility } from './hooks/useDefaultColumnVisibility';
|
||||||
|
|
||||||
interface IExperimentalProjectFeatureTogglesProps {
|
interface IExperimentalProjectFeatureTogglesProps {
|
||||||
environments: IProject['environments'];
|
environments: IProject['environments'];
|
||||||
style?: CSSProperties;
|
|
||||||
refreshInterval?: number;
|
refreshInterval?: number;
|
||||||
storageKey?: string;
|
storageKey?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const staticColumns = ['Select', 'Actions', 'name', 'favorite'];
|
const formatEnvironmentColumnId = (environment: string) =>
|
||||||
|
`environment:${environment}`;
|
||||||
|
|
||||||
const columnHelper = createColumnHelper<FeatureSearchResponseSchema>();
|
const columnHelper = createColumnHelper<FeatureSearchResponseSchema>();
|
||||||
|
|
||||||
export const ExperimentalProjectFeatureToggles = ({
|
export const ExperimentalProjectFeatureToggles = ({
|
||||||
environments,
|
environments,
|
||||||
style,
|
|
||||||
refreshInterval = 15 * 1000,
|
refreshInterval = 15 * 1000,
|
||||||
storageKey = 'project-feature-toggles',
|
storageKey = 'project-feature-toggles',
|
||||||
}: IExperimentalProjectFeatureTogglesProps) => {
|
}: IExperimentalProjectFeatureTogglesProps) => {
|
||||||
const projectId = useRequiredPathParam('projectId');
|
const projectId = useRequiredPathParam('projectId');
|
||||||
|
|
||||||
const stateConfig = {
|
const stateConfig = {
|
||||||
offset: withDefault(NumberParam, 0),
|
offset: withDefault(NumberParam, 0),
|
||||||
limit: withDefault(NumberParam, DEFAULT_PAGE_LIMIT),
|
limit: withDefault(NumberParam, DEFAULT_PAGE_LIMIT),
|
||||||
@ -158,7 +159,7 @@ export const ExperimentalProjectFeatureToggles = ({
|
|||||||
const columns = useMemo(
|
const columns = useMemo(
|
||||||
() => [
|
() => [
|
||||||
columnHelper.display({
|
columnHelper.display({
|
||||||
id: 'Select',
|
id: 'select',
|
||||||
header: ({ table }) => (
|
header: ({ table }) => (
|
||||||
<MemoizedRowSelectCell
|
<MemoizedRowSelectCell
|
||||||
noPadding
|
noPadding
|
||||||
@ -177,6 +178,7 @@ export const ExperimentalProjectFeatureToggles = ({
|
|||||||
),
|
),
|
||||||
}),
|
}),
|
||||||
columnHelper.accessor('favorite', {
|
columnHelper.accessor('favorite', {
|
||||||
|
id: 'favorite',
|
||||||
header: () => (
|
header: () => (
|
||||||
<FavoriteIconHeader
|
<FavoriteIconHeader
|
||||||
isActive={tableState.favoritesFirst}
|
isActive={tableState.favoritesFirst}
|
||||||
@ -196,10 +198,10 @@ export const ExperimentalProjectFeatureToggles = ({
|
|||||||
enableSorting: false,
|
enableSorting: false,
|
||||||
meta: {
|
meta: {
|
||||||
align: 'center',
|
align: 'center',
|
||||||
// hideInMenu: true,
|
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
columnHelper.accessor('lastSeenAt', {
|
columnHelper.accessor('lastSeenAt', {
|
||||||
|
id: 'lastSeenAt',
|
||||||
header: 'Last seen',
|
header: 'Last seen',
|
||||||
cell: ({ row: { original } }) => (
|
cell: ({ row: { original } }) => (
|
||||||
<MemoizedFeatureEnvironmentSeenCell
|
<MemoizedFeatureEnvironmentSeenCell
|
||||||
@ -213,6 +215,7 @@ export const ExperimentalProjectFeatureToggles = ({
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
columnHelper.accessor('type', {
|
columnHelper.accessor('type', {
|
||||||
|
id: 'type',
|
||||||
header: 'Type',
|
header: 'Type',
|
||||||
cell: FeatureTypeCell,
|
cell: FeatureTypeCell,
|
||||||
meta: {
|
meta: {
|
||||||
@ -220,6 +223,7 @@ export const ExperimentalProjectFeatureToggles = ({
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
columnHelper.accessor('name', {
|
columnHelper.accessor('name', {
|
||||||
|
id: 'name',
|
||||||
header: 'Name',
|
header: 'Name',
|
||||||
cell: FeatureNameCell,
|
cell: FeatureNameCell,
|
||||||
meta: {
|
meta: {
|
||||||
@ -227,6 +231,7 @@ export const ExperimentalProjectFeatureToggles = ({
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
columnHelper.accessor('createdAt', {
|
columnHelper.accessor('createdAt', {
|
||||||
|
id: 'createdAt',
|
||||||
header: 'Created',
|
header: 'Created',
|
||||||
cell: DateCell,
|
cell: DateCell,
|
||||||
}),
|
}),
|
||||||
@ -252,8 +257,8 @@ export const ExperimentalProjectFeatureToggles = ({
|
|||||||
) || false,
|
) || false,
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
id: `environment:${name}`,
|
id: formatEnvironmentColumnId(name),
|
||||||
header: loading ? '' : name,
|
header: name,
|
||||||
meta: {
|
meta: {
|
||||||
align: 'center',
|
align: 'center',
|
||||||
},
|
},
|
||||||
@ -287,7 +292,7 @@ export const ExperimentalProjectFeatureToggles = ({
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
[projectId, environments, loading, tableState.favoritesFirst, refetch],
|
[projectId, environments, tableState.favoritesFirst, refetch],
|
||||||
);
|
);
|
||||||
|
|
||||||
const placeholderData = useMemo(
|
const placeholderData = useMemo(
|
||||||
@ -319,15 +324,41 @@ export const ExperimentalProjectFeatureToggles = ({
|
|||||||
}
|
}
|
||||||
return features;
|
return features;
|
||||||
}, [loading, features]);
|
}, [loading, features]);
|
||||||
|
const allColumnIds = useMemo(
|
||||||
|
() => columns.map((column) => column.id).filter(Boolean) as string[],
|
||||||
|
[columns],
|
||||||
|
);
|
||||||
|
|
||||||
|
const defaultColumnVisibility = useDefaultColumnVisibility(allColumnIds);
|
||||||
|
|
||||||
const table = useReactTable(
|
const table = useReactTable(
|
||||||
withTableState(tableState, setTableState, {
|
withTableState(tableState, setTableState, {
|
||||||
columns,
|
columns,
|
||||||
data,
|
data,
|
||||||
enableRowSelection: true,
|
enableRowSelection: true,
|
||||||
|
state: {
|
||||||
|
columnVisibility: defaultColumnVisibility,
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const { columnVisibility } = table.getState();
|
||||||
|
const onToggleColumnVisibility = useCallback(
|
||||||
|
(columnId) => {
|
||||||
|
const isVisible = columnVisibility[columnId];
|
||||||
|
const newColumnVisibility: Record<string, boolean> = {
|
||||||
|
...columnVisibility,
|
||||||
|
[columnId]: !isVisible,
|
||||||
|
};
|
||||||
|
setTableState({
|
||||||
|
columns: Object.keys(newColumnVisibility).filter(
|
||||||
|
(columnId) => newColumnVisibility[columnId],
|
||||||
|
),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[columnVisibility, setTableState],
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<PageContent
|
<PageContent
|
||||||
@ -351,23 +382,41 @@ export const ExperimentalProjectFeatureToggles = ({
|
|||||||
{
|
{
|
||||||
header: 'Last seen',
|
header: 'Last seen',
|
||||||
id: 'lastSeenAt',
|
id: 'lastSeenAt',
|
||||||
|
isVisible: columnVisibility.lastSeenAt,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: 'Type',
|
header: 'Type',
|
||||||
id: 'type',
|
id: 'type',
|
||||||
|
isVisible: columnVisibility.type,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: 'Name',
|
header: 'Name',
|
||||||
id: 'name',
|
id: 'name',
|
||||||
|
isVisible: columnVisibility.name,
|
||||||
|
isStatic: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: 'Created',
|
header: 'Created',
|
||||||
id: 'createdAt',
|
id: 'createdAt',
|
||||||
|
isVisible: columnVisibility.createdAt,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'divider',
|
id: 'divider',
|
||||||
},
|
},
|
||||||
|
...environments.map(({ environment }) => ({
|
||||||
|
header: environment,
|
||||||
|
id: formatEnvironmentColumnId(
|
||||||
|
environment,
|
||||||
|
),
|
||||||
|
isVisible:
|
||||||
|
columnVisibility[
|
||||||
|
formatEnvironmentColumnId(
|
||||||
|
environment,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
})),
|
||||||
]}
|
]}
|
||||||
|
onToggle={onToggleColumnVisibility}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
@ -0,0 +1,62 @@
|
|||||||
|
import { useCallback } from 'react';
|
||||||
|
import { useMediaQuery, useTheme } from '@mui/material';
|
||||||
|
import { type VisibilityState } from '@tanstack/react-table';
|
||||||
|
|
||||||
|
const staticColumns = ['select', 'actions', 'name', 'favorite'];
|
||||||
|
|
||||||
|
const formatAsColumnVisibility = (
|
||||||
|
allColumns: string[],
|
||||||
|
visibleColumns: string[],
|
||||||
|
): VisibilityState =>
|
||||||
|
allColumns.reduce(
|
||||||
|
(acc, columnId) => ({
|
||||||
|
...acc,
|
||||||
|
[columnId]: visibleColumns.includes(columnId),
|
||||||
|
}),
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
|
||||||
|
export const useDefaultColumnVisibility = (allColumnIds: string[]) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
const isTinyScreen = useMediaQuery(theme.breakpoints.down('sm'));
|
||||||
|
const isSmallScreen = useMediaQuery(theme.breakpoints.down('md'));
|
||||||
|
const isMediumScreen = useMediaQuery(theme.breakpoints.down('lg'));
|
||||||
|
|
||||||
|
const showEnvironments = useCallback(
|
||||||
|
(environmentsToShow: number = 0) =>
|
||||||
|
allColumnIds
|
||||||
|
.filter((id) => id.startsWith('environment:') !== false)
|
||||||
|
.slice(0, environmentsToShow),
|
||||||
|
[allColumnIds],
|
||||||
|
);
|
||||||
|
|
||||||
|
if (isTinyScreen) {
|
||||||
|
return formatAsColumnVisibility(allColumnIds, [
|
||||||
|
...staticColumns,
|
||||||
|
'createdAt',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
if (isSmallScreen) {
|
||||||
|
return formatAsColumnVisibility(allColumnIds, [
|
||||||
|
...staticColumns,
|
||||||
|
'createdAt',
|
||||||
|
...showEnvironments(1),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
if (isMediumScreen) {
|
||||||
|
return formatAsColumnVisibility(allColumnIds, [
|
||||||
|
...staticColumns,
|
||||||
|
'createdAt',
|
||||||
|
'type',
|
||||||
|
...showEnvironments(1),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return formatAsColumnVisibility(allColumnIds, [
|
||||||
|
...staticColumns,
|
||||||
|
'lastSeenAt',
|
||||||
|
'createdAt',
|
||||||
|
'type',
|
||||||
|
...showEnvironments(3),
|
||||||
|
]);
|
||||||
|
};
|
@ -7,7 +7,7 @@ import {
|
|||||||
getCoreRowModel,
|
getCoreRowModel,
|
||||||
} from '@tanstack/react-table';
|
} from '@tanstack/react-table';
|
||||||
|
|
||||||
type TableStateColumnsType = (string | null)[] | null;
|
type TableStateColumns = (string | null)[] | null | undefined;
|
||||||
|
|
||||||
const createOnSortingChange =
|
const createOnSortingChange =
|
||||||
(
|
(
|
||||||
@ -79,10 +79,10 @@ const createOnPaginationChange =
|
|||||||
const createOnColumnVisibilityChange =
|
const createOnColumnVisibilityChange =
|
||||||
(
|
(
|
||||||
tableState: {
|
tableState: {
|
||||||
columns?: TableStateColumnsType;
|
columns?: TableStateColumns;
|
||||||
},
|
},
|
||||||
setTableState: (newState: {
|
setTableState: (newState: {
|
||||||
columns?: TableStateColumnsType;
|
columns?: TableStateColumns;
|
||||||
}) => void,
|
}) => void,
|
||||||
): OnChangeFn<VisibilityState> =>
|
): OnChangeFn<VisibilityState> =>
|
||||||
(newVisibility) => {
|
(newVisibility) => {
|
||||||
@ -132,7 +132,7 @@ const createPaginationState = (tableState: {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const createColumnVisibilityState = (tableState: {
|
const createColumnVisibilityState = (tableState: {
|
||||||
columns?: TableStateColumnsType;
|
columns?: TableStateColumns;
|
||||||
}) =>
|
}) =>
|
||||||
tableState.columns
|
tableState.columns
|
||||||
? {
|
? {
|
||||||
@ -152,35 +152,50 @@ export const withTableState = <T extends Object>(
|
|||||||
sortOrder: string;
|
sortOrder: string;
|
||||||
limit: number;
|
limit: number;
|
||||||
offset: number;
|
offset: number;
|
||||||
columns?: TableStateColumnsType;
|
columns?: TableStateColumns;
|
||||||
},
|
},
|
||||||
setTableState: (newState: {
|
setTableState: (newState: {
|
||||||
sortBy?: string;
|
sortBy?: string;
|
||||||
sortOrder?: string;
|
sortOrder?: string;
|
||||||
limit?: number;
|
limit?: number;
|
||||||
offset?: number;
|
offset?: number;
|
||||||
columns?: TableStateColumnsType;
|
columns?: TableStateColumns;
|
||||||
}) => void,
|
}) => void,
|
||||||
options: Omit<TableOptions<T>, 'getCoreRowModel'>,
|
options: Omit<TableOptions<T>, 'getCoreRowModel'>,
|
||||||
) => ({
|
) => {
|
||||||
getCoreRowModel: getCoreRowModel(),
|
const hideAllColumns = Object.fromEntries(
|
||||||
enableSorting: true,
|
Object.keys(options.state?.columnVisibility || {}).map((column) => [
|
||||||
enableMultiSort: false,
|
column,
|
||||||
manualPagination: true,
|
false,
|
||||||
manualSorting: true,
|
]),
|
||||||
enableSortingRemoval: false,
|
);
|
||||||
enableHiding: true,
|
const columnVisibility = tableState.columns
|
||||||
onPaginationChange: createOnPaginationChange(tableState, setTableState),
|
? {
|
||||||
onSortingChange: createOnSortingChange(tableState, setTableState),
|
...hideAllColumns,
|
||||||
onColumnVisibilityChange: createOnColumnVisibilityChange(
|
...createColumnVisibilityState(tableState).columnVisibility,
|
||||||
tableState,
|
}
|
||||||
setTableState,
|
: options.state?.columnVisibility;
|
||||||
),
|
|
||||||
...options,
|
return {
|
||||||
state: {
|
getCoreRowModel: getCoreRowModel(),
|
||||||
...createSortingState(tableState),
|
enableSorting: true,
|
||||||
...createPaginationState(tableState),
|
enableMultiSort: false,
|
||||||
...createColumnVisibilityState(tableState),
|
manualPagination: true,
|
||||||
...(options.state || {}),
|
manualSorting: true,
|
||||||
},
|
enableSortingRemoval: false,
|
||||||
});
|
enableHiding: true,
|
||||||
|
onPaginationChange: createOnPaginationChange(tableState, setTableState),
|
||||||
|
onSortingChange: createOnSortingChange(tableState, setTableState),
|
||||||
|
onColumnVisibilityChange: createOnColumnVisibilityChange(
|
||||||
|
tableState,
|
||||||
|
setTableState,
|
||||||
|
),
|
||||||
|
...options,
|
||||||
|
state: {
|
||||||
|
...createSortingState(tableState),
|
||||||
|
...createPaginationState(tableState),
|
||||||
|
...(options.state || {}),
|
||||||
|
columnVisibility,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user