mirror of
https://github.com/Unleash/unleash.git
synced 2025-10-04 11:17:02 +02:00
https://linear.app/unleash/issue/2-563/fix-issue-with-useconditionallyhiddencolumns-and-react-table It seems like we should add `autoResetHiddenColumns: false` to `useTable` whenever we use `useConditionallyHiddenColumns`. Basically the thought is that, if we're controlling column visibility in our own way, we should not want other things to change that state unpredictably, otherwise this may make React go _brrrrrr_. And it can be very hard to pinpoint what exactly may be causing React to go _brrrrrr_.  First detected this issue apparently randomly while developing the new SA table. Around 10-20 page refreshes would eventually trigger it. Was not easy to find, but hopefully this fixes it permanently. At least I haven't been able to reproduce it since. Maybe someone has a better idea of where the issue could be or if this is a pretty good guess. Doesn't seem like this change hurts us anyways. I love React, `useEffect` and these very to-the-point error messages. Very fun and productive. Reference: https://react-table-v7.tanstack.com/docs/api/useTable
277 lines
9.6 KiB
TypeScript
277 lines
9.6 KiB
TypeScript
import { Delete, Edit } from '@mui/icons-material';
|
|
import { styled, useMediaQuery, useTheme } from '@mui/material';
|
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
|
import { PageContent } from 'component/common/PageContent/PageContent';
|
|
import { PageHeader } from 'component/common/PageHeader/PageHeader';
|
|
import PermissionIconButton from 'component/common/PermissionIconButton/PermissionIconButton';
|
|
import { Search } from 'component/common/Search/Search';
|
|
import { SidebarModal } from 'component/common/SidebarModal/SidebarModal';
|
|
import { TablePlaceholder, VirtualizedTable } from 'component/common/Table';
|
|
import { DateCell } from 'component/common/Table/cells/DateCell/DateCell';
|
|
import { HighlightCell } from 'component/common/Table/cells/HighlightCell/HighlightCell';
|
|
import { TextCell } from 'component/common/Table/cells/TextCell/TextCell';
|
|
import { TimeAgoCell } from 'component/common/Table/cells/TimeAgoCell/TimeAgoCell';
|
|
import { SearchHighlightProvider } from 'component/common/Table/SearchHighlightContext/SearchHighlightContext';
|
|
import { UserAvatar } from 'component/common/UserAvatar/UserAvatar';
|
|
import { UPDATE_PROJECT } from 'component/providers/AccessProvider/permissions';
|
|
import { useSearch } from 'hooks/useSearch';
|
|
import { IGroup, IGroupUser } from 'interfaces/group';
|
|
import { VFC, useState } from 'react';
|
|
import { SortingRule, useFlexLayout, useSortBy, useTable } from 'react-table';
|
|
import { sortTypes } from 'utils/sortTypes';
|
|
import { useConditionallyHiddenColumns } from 'hooks/useConditionallyHiddenColumns';
|
|
|
|
const StyledPageContent = styled(PageContent)(({ theme }) => ({
|
|
height: '100vh',
|
|
overflow: 'auto',
|
|
padding: theme.spacing(7.5, 6),
|
|
[theme.breakpoints.down('md')]: {
|
|
padding: theme.spacing(4, 2),
|
|
},
|
|
'& .header': {
|
|
padding: theme.spacing(0, 0, 2, 0),
|
|
},
|
|
'& .body': {
|
|
padding: theme.spacing(3, 0, 0, 0),
|
|
},
|
|
borderRadius: `${theme.spacing(1.5, 0, 0, 1.5)} !important`,
|
|
}));
|
|
|
|
const StyledTitle = styled('div')(({ theme }) => ({
|
|
display: 'flex',
|
|
flexDirection: 'column',
|
|
'& > span': {
|
|
color: theme.palette.text.secondary,
|
|
fontSize: theme.fontSizes.bodySize,
|
|
},
|
|
}));
|
|
|
|
const defaultSort: SortingRule<string> = { id: 'joinedAt' };
|
|
|
|
const columns = [
|
|
{
|
|
Header: 'Avatar',
|
|
accessor: 'imageUrl',
|
|
Cell: ({ row: { original: user } }: any) => (
|
|
<TextCell>
|
|
<UserAvatar user={user} />
|
|
</TextCell>
|
|
),
|
|
maxWidth: 85,
|
|
disableSortBy: true,
|
|
},
|
|
{
|
|
id: 'name',
|
|
Header: 'Name',
|
|
accessor: (row: IGroupUser) => row.name || '',
|
|
Cell: HighlightCell,
|
|
minWidth: 100,
|
|
searchable: true,
|
|
},
|
|
{
|
|
id: 'username',
|
|
Header: 'Username',
|
|
accessor: (row: IGroupUser) => row.username || row.email,
|
|
Cell: HighlightCell,
|
|
minWidth: 100,
|
|
searchable: true,
|
|
},
|
|
{
|
|
id: 'joined',
|
|
Header: 'Joined',
|
|
accessor: 'joinedAt',
|
|
Cell: DateCell,
|
|
sortType: 'date',
|
|
maxWidth: 150,
|
|
},
|
|
{
|
|
id: 'lastLogin',
|
|
Header: 'Last login',
|
|
accessor: (row: IGroupUser) => row.seenAt || '',
|
|
Cell: ({ row: { original: user } }: any) => (
|
|
<TimeAgoCell
|
|
value={user.seenAt}
|
|
emptyText="Never"
|
|
title={date => `Last login: ${date}`}
|
|
/>
|
|
),
|
|
sortType: 'date',
|
|
maxWidth: 150,
|
|
},
|
|
];
|
|
|
|
const hiddenColumnsSmall = ['imageUrl', 'name', 'joined', 'lastLogin'];
|
|
|
|
interface IProjectGroupViewProps {
|
|
open: boolean;
|
|
setOpen: React.Dispatch<React.SetStateAction<boolean>>;
|
|
group: IGroup;
|
|
projectId: string;
|
|
subtitle: string;
|
|
onEdit: () => void;
|
|
onRemove: () => void;
|
|
}
|
|
|
|
export const ProjectGroupView: VFC<IProjectGroupViewProps> = ({
|
|
open,
|
|
setOpen,
|
|
group,
|
|
projectId,
|
|
subtitle,
|
|
onEdit,
|
|
onRemove,
|
|
}) => {
|
|
const theme = useTheme();
|
|
const isSmallScreen = useMediaQuery(theme.breakpoints.down('md'));
|
|
|
|
const [initialState] = useState(() => ({
|
|
sortBy: [
|
|
{
|
|
id: defaultSort.id,
|
|
desc: defaultSort.desc,
|
|
},
|
|
],
|
|
}));
|
|
const [searchValue, setSearchValue] = useState('');
|
|
|
|
const { data, getSearchText, getSearchContext } = useSearch(
|
|
columns,
|
|
searchValue,
|
|
group?.users ?? []
|
|
);
|
|
|
|
const { headerGroups, rows, prepareRow, setHiddenColumns } = useTable(
|
|
{
|
|
columns: columns as any[],
|
|
data,
|
|
initialState,
|
|
sortTypes,
|
|
autoResetHiddenColumns: false,
|
|
autoResetSortBy: false,
|
|
disableSortRemove: true,
|
|
disableMultiSort: true,
|
|
},
|
|
useSortBy,
|
|
useFlexLayout
|
|
);
|
|
|
|
useConditionallyHiddenColumns(
|
|
[
|
|
{
|
|
condition: isSmallScreen,
|
|
columns: hiddenColumnsSmall,
|
|
},
|
|
],
|
|
setHiddenColumns,
|
|
columns
|
|
);
|
|
|
|
return (
|
|
<SidebarModal
|
|
open={open}
|
|
onClose={() => {
|
|
setOpen(false);
|
|
}}
|
|
label={group?.name || 'Group'}
|
|
>
|
|
<StyledPageContent
|
|
header={
|
|
<PageHeader
|
|
secondary
|
|
titleElement={
|
|
<StyledTitle>
|
|
{group?.name} (
|
|
{rows.length < data.length
|
|
? `${rows.length} of ${data.length}`
|
|
: data.length}
|
|
)<span>{subtitle}</span>
|
|
</StyledTitle>
|
|
}
|
|
actions={
|
|
<>
|
|
<ConditionallyRender
|
|
condition={!isSmallScreen}
|
|
show={
|
|
<>
|
|
<Search
|
|
initialValue={searchValue}
|
|
onChange={setSearchValue}
|
|
hasFilters
|
|
getSearchContext={
|
|
getSearchContext
|
|
}
|
|
/>
|
|
<PageHeader.Divider />
|
|
</>
|
|
}
|
|
/>
|
|
<PermissionIconButton
|
|
permission={UPDATE_PROJECT}
|
|
projectId={projectId}
|
|
tooltipProps={{
|
|
title: 'Edit group access',
|
|
}}
|
|
onClick={onEdit}
|
|
>
|
|
<Edit />
|
|
</PermissionIconButton>
|
|
<PermissionIconButton
|
|
permission={UPDATE_PROJECT}
|
|
projectId={projectId}
|
|
tooltipProps={{
|
|
title: 'Remove group access',
|
|
}}
|
|
onClick={onRemove}
|
|
>
|
|
<Delete />
|
|
</PermissionIconButton>
|
|
</>
|
|
}
|
|
>
|
|
<ConditionallyRender
|
|
condition={isSmallScreen}
|
|
show={
|
|
<Search
|
|
initialValue={searchValue}
|
|
onChange={setSearchValue}
|
|
hasFilters
|
|
getSearchContext={getSearchContext}
|
|
/>
|
|
}
|
|
/>
|
|
</PageHeader>
|
|
}
|
|
>
|
|
<SearchHighlightProvider value={getSearchText(searchValue)}>
|
|
<VirtualizedTable
|
|
rows={rows}
|
|
headerGroups={headerGroups}
|
|
prepareRow={prepareRow}
|
|
/>
|
|
</SearchHighlightProvider>
|
|
<ConditionallyRender
|
|
condition={rows.length === 0}
|
|
show={
|
|
<ConditionallyRender
|
|
condition={searchValue?.length > 0}
|
|
show={
|
|
<TablePlaceholder>
|
|
No users found matching “
|
|
{searchValue}
|
|
” in this group.
|
|
</TablePlaceholder>
|
|
}
|
|
elseShow={
|
|
<TablePlaceholder>
|
|
This group is empty. Get started by adding a
|
|
user to the group.
|
|
</TablePlaceholder>
|
|
}
|
|
/>
|
|
}
|
|
/>
|
|
</StyledPageContent>
|
|
</SidebarModal>
|
|
);
|
|
};
|