mirror of
https://github.com/Unleash/unleash.git
synced 2025-05-03 01:18:43 +02:00
Feat: Contexts and Project access tables (#1028)
* feat: new contexts table * improve context list actions * refactor: disabled icon colors * fix: update snapshots * fix: icons * fix: context fields typo * feat: new project access table * fix: header cell styles
This commit is contained in:
parent
7093b49962
commit
9ac962da45
@ -37,6 +37,7 @@ const PermissionIconButton = ({
|
|||||||
children,
|
children,
|
||||||
environmentId,
|
environmentId,
|
||||||
tooltipProps,
|
tooltipProps,
|
||||||
|
disabled,
|
||||||
...rest
|
...rest
|
||||||
}: IButtonProps | ILinkProps) => {
|
}: IButtonProps | ILinkProps) => {
|
||||||
const { hasAccess } = useContext(AccessContext);
|
const { hasAccess } = useContext(AccessContext);
|
||||||
@ -57,7 +58,11 @@ const PermissionIconButton = ({
|
|||||||
arrow
|
arrow
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<IconButton {...rest} disabled={!access} size="large">
|
<IconButton
|
||||||
|
{...rest}
|
||||||
|
disabled={!access || disabled}
|
||||||
|
size="large"
|
||||||
|
>
|
||||||
{children}
|
{children}
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</div>
|
</div>
|
||||||
|
@ -111,7 +111,8 @@ export const CellSortable: FC<ICellSortableProps> = ({
|
|||||||
<button
|
<button
|
||||||
className={classnames(
|
className={classnames(
|
||||||
isSorted && styles.sortedButton,
|
isSorted && styles.sortedButton,
|
||||||
styles.sortButton
|
styles.sortButton,
|
||||||
|
alignClass
|
||||||
)}
|
)}
|
||||||
onClick={onSortClick}
|
onClick={onSortClick}
|
||||||
>
|
>
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { useMemo, useState, VFC } from 'react';
|
import { useContext, useMemo, useState, VFC } from 'react';
|
||||||
import { useGlobalFilter, useSortBy, useTable } from 'react-table';
|
import { useGlobalFilter, useSortBy, useTable } from 'react-table';
|
||||||
import {
|
import {
|
||||||
Table,
|
Table,
|
||||||
@ -12,6 +12,7 @@ import {
|
|||||||
import { PageContent } from 'component/common/PageContent/PageContent';
|
import { PageContent } from 'component/common/PageContent/PageContent';
|
||||||
import { PageHeader } from 'component/common/PageHeader/PageHeader';
|
import { PageHeader } from 'component/common/PageHeader/PageHeader';
|
||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
|
import { UPDATE_CONTEXT_FIELD } from 'component/providers/AccessProvider/permissions';
|
||||||
import { Dialogue as ConfirmDialogue } from 'component/common/Dialogue/Dialogue';
|
import { Dialogue as ConfirmDialogue } from 'component/common/Dialogue/Dialogue';
|
||||||
import useUnleashContext from 'hooks/api/getters/useUnleashContext/useUnleashContext';
|
import useUnleashContext from 'hooks/api/getters/useUnleashContext/useUnleashContext';
|
||||||
import useContextsApi from 'hooks/api/actions/useContextsApi/useContextsApi';
|
import useContextsApi from 'hooks/api/actions/useContextsApi/useContextsApi';
|
||||||
@ -24,6 +25,7 @@ import { LinkCell } from 'component/common/Table/cells/LinkCell/LinkCell';
|
|||||||
import { ContextActionsCell } from './ContextActionsCell/ContextActionsCell';
|
import { ContextActionsCell } from './ContextActionsCell/ContextActionsCell';
|
||||||
import { Adjust } from '@mui/icons-material';
|
import { Adjust } from '@mui/icons-material';
|
||||||
import { IconCell } from 'component/common/Table/cells/IconCell/IconCell';
|
import { IconCell } from 'component/common/Table/cells/IconCell/IconCell';
|
||||||
|
import AccessContext from 'contexts/AccessContext';
|
||||||
|
|
||||||
const ContextList: VFC = () => {
|
const ContextList: VFC = () => {
|
||||||
const [showDelDialogue, setShowDelDialogue] = useState(false);
|
const [showDelDialogue, setShowDelDialogue] = useState(false);
|
||||||
@ -31,6 +33,7 @@ const ContextList: VFC = () => {
|
|||||||
const { context, refetchUnleashContext, loading } = useUnleashContext();
|
const { context, refetchUnleashContext, loading } = useUnleashContext();
|
||||||
const { removeContext } = useContextsApi();
|
const { removeContext } = useContextsApi();
|
||||||
const { setToastData, setToastApiError } = useToast();
|
const { setToastData, setToastApiError } = useToast();
|
||||||
|
const { hasAccess } = useContext(AccessContext);
|
||||||
|
|
||||||
const data = useMemo(() => {
|
const data = useMemo(() => {
|
||||||
if (loading) {
|
if (loading) {
|
||||||
@ -66,8 +69,8 @@ const ContextList: VFC = () => {
|
|||||||
}: any) => (
|
}: any) => (
|
||||||
<LinkCell
|
<LinkCell
|
||||||
title={name}
|
title={name}
|
||||||
|
to={`/context/edit/${name}`}
|
||||||
subtitle={description}
|
subtitle={description}
|
||||||
data-loading
|
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
sortType: 'alphanumeric',
|
sortType: 'alphanumeric',
|
||||||
|
@ -6,10 +6,11 @@ export const useStyles = makeStyles()(theme => ({
|
|||||||
},
|
},
|
||||||
divider: {
|
divider: {
|
||||||
height: '1px',
|
height: '1px',
|
||||||
width: '106.65%',
|
position: 'relative',
|
||||||
marginLeft: '-2rem',
|
left: 0,
|
||||||
backgroundColor: '#efefef',
|
right: 0,
|
||||||
marginTop: '2rem',
|
backgroundColor: theme.palette.divider,
|
||||||
|
margin: theme.spacing(4, -4, 3),
|
||||||
},
|
},
|
||||||
inputLabel: { backgroundColor: '#fff' },
|
inputLabel: { backgroundColor: '#fff' },
|
||||||
roleName: {
|
roleName: {
|
||||||
|
@ -1,12 +1,10 @@
|
|||||||
/* eslint-disable react/jsx-no-target-blank */
|
/* eslint-disable react/jsx-no-target-blank */
|
||||||
import React, { useState } from 'react';
|
import React, { useCallback, useState } from 'react';
|
||||||
import { Alert, SelectChangeEvent } from '@mui/material';
|
import { Alert, SelectChangeEvent } from '@mui/material';
|
||||||
import { ProjectAccessAddUser } from './ProjectAccessAddUser/ProjectAccessAddUser';
|
import { ProjectAccessAddUser } from './ProjectAccessAddUser/ProjectAccessAddUser';
|
||||||
import { PageContent } from 'component/common/PageContent/PageContent';
|
import { PageContent } from 'component/common/PageContent/PageContent';
|
||||||
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||||
import { useStyles } from './ProjectAccess.styles';
|
import { useStyles } from './ProjectAccess.styles';
|
||||||
import usePagination from 'hooks/usePagination';
|
|
||||||
import PaginateUI from 'component/common/PaginateUI/PaginateUI';
|
|
||||||
import useToast from 'hooks/useToast';
|
import useToast from 'hooks/useToast';
|
||||||
import { Dialogue as ConfirmDialogue } from 'component/common/Dialogue/Dialogue';
|
import { Dialogue as ConfirmDialogue } from 'component/common/Dialogue/Dialogue';
|
||||||
import useProjectAccess, {
|
import useProjectAccess, {
|
||||||
@ -14,8 +12,8 @@ import useProjectAccess, {
|
|||||||
} from 'hooks/api/getters/useProjectAccess/useProjectAccess';
|
} from 'hooks/api/getters/useProjectAccess/useProjectAccess';
|
||||||
import useProjectApi from 'hooks/api/actions/useProjectApi/useProjectApi';
|
import useProjectApi from 'hooks/api/actions/useProjectApi/useProjectApi';
|
||||||
import { PageHeader } from 'component/common/PageHeader/PageHeader';
|
import { PageHeader } from 'component/common/PageHeader/PageHeader';
|
||||||
import { ProjectAccessList } from './ProjectAccessList/ProjectAccessList';
|
|
||||||
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
||||||
|
import { ProjectAccessTable } from './ProjectAccessTable/ProjectAccessTable';
|
||||||
|
|
||||||
export const ProjectAccess = () => {
|
export const ProjectAccess = () => {
|
||||||
const projectId = useRequiredPathParam('projectId');
|
const projectId = useRequiredPathParam('projectId');
|
||||||
@ -23,28 +21,11 @@ export const ProjectAccess = () => {
|
|||||||
const { access, refetchProjectAccess } = useProjectAccess(projectId);
|
const { access, refetchProjectAccess } = useProjectAccess(projectId);
|
||||||
const { setToastData } = useToast();
|
const { setToastData } = useToast();
|
||||||
const { isOss } = useUiConfig();
|
const { isOss } = useUiConfig();
|
||||||
const { page, pages, nextPage, prevPage, setPageIndex, pageIndex } =
|
|
||||||
usePagination(access.users, 10);
|
|
||||||
const { removeUserFromRole, changeUserRole } = useProjectApi();
|
const { removeUserFromRole, changeUserRole } = useProjectApi();
|
||||||
const [showDelDialogue, setShowDelDialogue] = useState(false);
|
const [showDelDialogue, setShowDelDialogue] = useState(false);
|
||||||
const [user, setUser] = useState<IProjectAccessUser | undefined>();
|
const [user, setUser] = useState<IProjectAccessUser | undefined>();
|
||||||
|
|
||||||
if (isOss()) {
|
const handleRoleChange = useCallback(
|
||||||
return (
|
|
||||||
<PageContent header={<PageHeader title="Project Access" />}>
|
|
||||||
<Alert severity="error">
|
|
||||||
Controlling access to projects requires a paid version of
|
|
||||||
Unleash. Check out{' '}
|
|
||||||
<a href="https://www.getunleash.io" target="_blank">
|
|
||||||
getunleash.io
|
|
||||||
</a>{' '}
|
|
||||||
to find out more.
|
|
||||||
</Alert>
|
|
||||||
</PageContent>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleRoleChange =
|
|
||||||
(userId: number) => async (evt: SelectChangeEvent) => {
|
(userId: number) => async (evt: SelectChangeEvent) => {
|
||||||
const roleId = Number(evt.target.value);
|
const roleId = Number(evt.target.value);
|
||||||
try {
|
try {
|
||||||
@ -61,7 +42,24 @@ export const ProjectAccess = () => {
|
|||||||
title: err.message || 'Server problems when adding users.',
|
title: err.message || 'Server problems when adding users.',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
},
|
||||||
|
[changeUserRole, projectId, refetchProjectAccess, setToastData]
|
||||||
|
);
|
||||||
|
|
||||||
|
if (isOss()) {
|
||||||
|
return (
|
||||||
|
<PageContent header={<PageHeader title="Project Access" />}>
|
||||||
|
<Alert severity="error">
|
||||||
|
Controlling access to projects requires a paid version of
|
||||||
|
Unleash. Check out{' '}
|
||||||
|
<a href="https://www.getunleash.io" target="_blank">
|
||||||
|
getunleash.io
|
||||||
|
</a>{' '}
|
||||||
|
to find out more.
|
||||||
|
</Alert>
|
||||||
|
</PageContent>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const handleRemoveAccess = (user: IProjectAccessUser) => {
|
const handleRemoveAccess = (user: IProjectAccessUser) => {
|
||||||
setUser(user);
|
setUser(user);
|
||||||
@ -96,21 +94,13 @@ export const ProjectAccess = () => {
|
|||||||
<ProjectAccessAddUser roles={access?.roles} />
|
<ProjectAccessAddUser roles={access?.roles} />
|
||||||
|
|
||||||
<div className={styles.divider}></div>
|
<div className={styles.divider}></div>
|
||||||
<ProjectAccessList
|
|
||||||
|
<ProjectAccessTable
|
||||||
|
access={access}
|
||||||
handleRoleChange={handleRoleChange}
|
handleRoleChange={handleRoleChange}
|
||||||
handleRemoveAccess={handleRemoveAccess}
|
handleRemoveAccess={handleRemoveAccess}
|
||||||
page={page}
|
projectId={projectId}
|
||||||
access={access}
|
/>
|
||||||
>
|
|
||||||
<PaginateUI
|
|
||||||
pages={pages}
|
|
||||||
pageIndex={pageIndex}
|
|
||||||
setPageIndex={setPageIndex}
|
|
||||||
nextPage={nextPage}
|
|
||||||
prevPage={prevPage}
|
|
||||||
style={{ bottom: '-21px' }}
|
|
||||||
/>
|
|
||||||
</ProjectAccessList>
|
|
||||||
|
|
||||||
<ConfirmDialogue
|
<ConfirmDialogue
|
||||||
open={showDelDialogue}
|
open={showDelDialogue}
|
||||||
|
@ -1,57 +0,0 @@
|
|||||||
import { List, SelectChangeEvent } from '@mui/material';
|
|
||||||
import {
|
|
||||||
IProjectAccessOutput,
|
|
||||||
IProjectAccessUser,
|
|
||||||
} from 'hooks/api/getters/useProjectAccess/useProjectAccess';
|
|
||||||
import { ProjectAccessListItem } from './ProjectAccessListItem/ProjectAccessListItem';
|
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
interface IProjectAccesListProps {
|
|
||||||
page: IProjectAccessUser[];
|
|
||||||
handleRoleChange: (userId: number) => (evt: SelectChangeEvent) => void;
|
|
||||||
handleRemoveAccess: (user: IProjectAccessUser) => void;
|
|
||||||
access: IProjectAccessOutput;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ProjectAccessList: React.FC<IProjectAccesListProps> = ({
|
|
||||||
page,
|
|
||||||
access,
|
|
||||||
handleRoleChange,
|
|
||||||
handleRemoveAccess,
|
|
||||||
children,
|
|
||||||
}) => {
|
|
||||||
const sortUsers = (users: IProjectAccessUser[]): IProjectAccessUser[] => {
|
|
||||||
/* This should be done on the API side in the future,
|
|
||||||
we should expect the list of users to come in the
|
|
||||||
same order each time and not jump around on the screen*/
|
|
||||||
|
|
||||||
return users.sort(
|
|
||||||
(userA: IProjectAccessUser, userB: IProjectAccessUser) => {
|
|
||||||
if (!userA.name) {
|
|
||||||
return -1;
|
|
||||||
} else if (!userB.name) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return userA.name.localeCompare(userB.name);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<List>
|
|
||||||
{sortUsers(page).map(user => {
|
|
||||||
return (
|
|
||||||
<ProjectAccessListItem
|
|
||||||
key={user.id}
|
|
||||||
user={user}
|
|
||||||
access={access}
|
|
||||||
handleRoleChange={handleRoleChange}
|
|
||||||
handleRemoveAccess={handleRemoveAccess}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
{children}
|
|
||||||
</List>
|
|
||||||
);
|
|
||||||
};
|
|
@ -1,11 +0,0 @@
|
|||||||
import { makeStyles } from 'tss-react/mui';
|
|
||||||
|
|
||||||
export const useStyles = makeStyles()(() => ({
|
|
||||||
iconButton: {
|
|
||||||
marginLeft: '0.5rem',
|
|
||||||
},
|
|
||||||
actionList: {
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
},
|
|
||||||
}));
|
|
@ -1,78 +0,0 @@
|
|||||||
import {
|
|
||||||
Avatar,
|
|
||||||
ListItem,
|
|
||||||
ListItemAvatar,
|
|
||||||
ListItemSecondaryAction,
|
|
||||||
ListItemText,
|
|
||||||
MenuItem,
|
|
||||||
SelectChangeEvent,
|
|
||||||
} from '@mui/material';
|
|
||||||
import { Delete } from '@mui/icons-material';
|
|
||||||
import {
|
|
||||||
IProjectAccessOutput,
|
|
||||||
IProjectAccessUser,
|
|
||||||
} from 'hooks/api/getters/useProjectAccess/useProjectAccess';
|
|
||||||
import PermissionIconButton from 'component/common/PermissionIconButton/PermissionIconButton';
|
|
||||||
import { UPDATE_PROJECT } from 'component/providers/AccessProvider/permissions';
|
|
||||||
import { ProjectRoleSelect } from 'component/project/ProjectAccess/ProjectRoleSelect/ProjectRoleSelect';
|
|
||||||
import { useStyles } from '../ProjectAccessListItem/ProjectAccessListItem.styles';
|
|
||||||
import React from 'react';
|
|
||||||
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
|
||||||
|
|
||||||
interface IProjectAccessListItemProps {
|
|
||||||
user: IProjectAccessUser;
|
|
||||||
handleRoleChange: (userId: number) => (evt: SelectChangeEvent) => void;
|
|
||||||
handleRemoveAccess: (user: IProjectAccessUser) => void;
|
|
||||||
access: IProjectAccessOutput;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ProjectAccessListItem = ({
|
|
||||||
user,
|
|
||||||
access,
|
|
||||||
handleRoleChange,
|
|
||||||
handleRemoveAccess,
|
|
||||||
}: IProjectAccessListItemProps) => {
|
|
||||||
const projectId = useRequiredPathParam('projectId');
|
|
||||||
const { classes: styles } = useStyles();
|
|
||||||
|
|
||||||
const labelId = `checkbox-list-secondary-label-${user.id}`;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ListItem key={user.id} button>
|
|
||||||
<ListItemAvatar>
|
|
||||||
<Avatar alt="Gravatar" src={user.imageUrl} />
|
|
||||||
</ListItemAvatar>
|
|
||||||
<ListItemText
|
|
||||||
id={labelId}
|
|
||||||
primary={user.name}
|
|
||||||
secondary={user.email || user.username}
|
|
||||||
/>
|
|
||||||
<ListItemSecondaryAction className={styles.actionList}>
|
|
||||||
<ProjectRoleSelect
|
|
||||||
labelId={`role-${user.id}-select-label`}
|
|
||||||
id={`role-${user.id}-select`}
|
|
||||||
key={user.id}
|
|
||||||
placeholder="Choose role"
|
|
||||||
onChange={handleRoleChange(user.id)}
|
|
||||||
roles={access.roles}
|
|
||||||
value={user.roleId || -1}
|
|
||||||
>
|
|
||||||
<MenuItem value="" disabled>
|
|
||||||
Choose role
|
|
||||||
</MenuItem>
|
|
||||||
</ProjectRoleSelect>
|
|
||||||
<PermissionIconButton
|
|
||||||
permission={UPDATE_PROJECT}
|
|
||||||
projectId={projectId}
|
|
||||||
className={styles.iconButton}
|
|
||||||
edge="end"
|
|
||||||
onClick={() => handleRemoveAccess(user)}
|
|
||||||
disabled={access.users.length === 1}
|
|
||||||
tooltipProps={{ title: 'Remove access' }}
|
|
||||||
>
|
|
||||||
<Delete />
|
|
||||||
</PermissionIconButton>
|
|
||||||
</ListItemSecondaryAction>
|
|
||||||
</ListItem>
|
|
||||||
);
|
|
||||||
};
|
|
@ -0,0 +1,164 @@
|
|||||||
|
import { useMemo, useState, VFC } from 'react';
|
||||||
|
import { useSortBy, useTable } from 'react-table';
|
||||||
|
import {
|
||||||
|
Table,
|
||||||
|
TableBody,
|
||||||
|
TableRow,
|
||||||
|
TableCell,
|
||||||
|
SortableTableHeader,
|
||||||
|
} from 'component/common/Table';
|
||||||
|
import { Avatar, Box, SelectChangeEvent } from '@mui/material';
|
||||||
|
import { Delete } from '@mui/icons-material';
|
||||||
|
import { sortTypes } from 'utils/sortTypes';
|
||||||
|
import {
|
||||||
|
IProjectAccessOutput,
|
||||||
|
IProjectAccessUser,
|
||||||
|
} from 'hooks/api/getters/useProjectAccess/useProjectAccess';
|
||||||
|
import { ProjectRoleCell } from './ProjectRoleCell/ProjectRoleCell';
|
||||||
|
import PermissionIconButton from 'component/common/PermissionIconButton/PermissionIconButton';
|
||||||
|
import { UPDATE_PROJECT } from 'component/providers/AccessProvider/permissions';
|
||||||
|
import { TextCell } from 'component/common/Table/cells/TextCell/TextCell';
|
||||||
|
|
||||||
|
interface IProjectAccessTableProps {
|
||||||
|
access: IProjectAccessOutput;
|
||||||
|
projectId: string;
|
||||||
|
handleRoleChange: (
|
||||||
|
userId: number
|
||||||
|
) => (event: SelectChangeEvent) => Promise<void>;
|
||||||
|
handleRemoveAccess: (user: IProjectAccessUser) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ProjectAccessTable: VFC<IProjectAccessTableProps> = ({
|
||||||
|
access,
|
||||||
|
projectId,
|
||||||
|
handleRoleChange,
|
||||||
|
handleRemoveAccess,
|
||||||
|
}) => {
|
||||||
|
const [initialState] = useState({});
|
||||||
|
const data = access.users;
|
||||||
|
|
||||||
|
const columns = useMemo(
|
||||||
|
() => [
|
||||||
|
{
|
||||||
|
Header: 'Avatar',
|
||||||
|
accessor: 'imageUrl',
|
||||||
|
disableSortBy: true,
|
||||||
|
width: 80,
|
||||||
|
Cell: ({ value }: { value: string }) => (
|
||||||
|
<Avatar
|
||||||
|
alt="Gravatar"
|
||||||
|
src={value}
|
||||||
|
sx={{ width: 32, height: 32, mx: 'auto' }}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
align: 'center',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Header: 'Name',
|
||||||
|
accessor: 'name',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Header: 'Username',
|
||||||
|
id: 'username',
|
||||||
|
accessor: 'email',
|
||||||
|
Cell: ({ row: { original: user } }: any) => (
|
||||||
|
<TextCell>{user.email || user.username}</TextCell>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Header: 'Role',
|
||||||
|
accessor: 'roleId',
|
||||||
|
Cell: ({
|
||||||
|
value,
|
||||||
|
row: { original: user },
|
||||||
|
}: {
|
||||||
|
value: number;
|
||||||
|
row: { original: IProjectAccessUser };
|
||||||
|
}) => (
|
||||||
|
<ProjectRoleCell
|
||||||
|
value={value}
|
||||||
|
user={user}
|
||||||
|
roles={access.roles}
|
||||||
|
onChange={handleRoleChange(user.id)}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Header: 'Actions',
|
||||||
|
id: 'actions',
|
||||||
|
disableSortBy: true,
|
||||||
|
align: 'center',
|
||||||
|
width: 80,
|
||||||
|
Cell: ({ row: { original: user } }: any) => (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<PermissionIconButton
|
||||||
|
permission={UPDATE_PROJECT}
|
||||||
|
projectId={projectId}
|
||||||
|
edge="end"
|
||||||
|
onClick={() => handleRemoveAccess(user)}
|
||||||
|
disabled={access.users.length === 1}
|
||||||
|
tooltipProps={{
|
||||||
|
title:
|
||||||
|
access.users.length === 1
|
||||||
|
? 'Cannot remove access. A project must have at least one owner'
|
||||||
|
: 'Remove access',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Delete />
|
||||||
|
</PermissionIconButton>
|
||||||
|
</Box>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
access.roles,
|
||||||
|
access.users.length,
|
||||||
|
handleRemoveAccess,
|
||||||
|
handleRoleChange,
|
||||||
|
projectId,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
const { getTableProps, getTableBodyProps, headerGroups, rows, prepareRow } =
|
||||||
|
useTable(
|
||||||
|
{
|
||||||
|
columns: columns as any[], // TODO: fix after `react-table` v8 update
|
||||||
|
data,
|
||||||
|
initialState,
|
||||||
|
sortTypes,
|
||||||
|
autoResetGlobalFilter: false,
|
||||||
|
autoResetSortBy: false,
|
||||||
|
disableSortRemove: true,
|
||||||
|
defaultColumn: {
|
||||||
|
Cell: TextCell,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
useSortBy
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Table {...getTableProps()}>
|
||||||
|
{/* @ts-expect-error -- react-table */}
|
||||||
|
<SortableTableHeader headerGroups={headerGroups} />
|
||||||
|
<TableBody {...getTableBodyProps()}>
|
||||||
|
{rows.map(row => {
|
||||||
|
prepareRow(row);
|
||||||
|
return (
|
||||||
|
<TableRow hover {...row.getRowProps()}>
|
||||||
|
{row.cells.map(cell => (
|
||||||
|
<TableCell {...cell.getCellProps()}>
|
||||||
|
{cell.render('Cell')}
|
||||||
|
</TableCell>
|
||||||
|
))}
|
||||||
|
</TableRow>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,7 @@
|
|||||||
|
import { makeStyles } from 'tss-react/mui';
|
||||||
|
|
||||||
|
export const useStyles = makeStyles()(theme => ({
|
||||||
|
cell: {
|
||||||
|
padding: theme.spacing(1, 1.5),
|
||||||
|
},
|
||||||
|
}));
|
@ -0,0 +1,38 @@
|
|||||||
|
import { VFC } from 'react';
|
||||||
|
import { Box, MenuItem, SelectChangeEvent } from '@mui/material';
|
||||||
|
import { IProjectAccessUser } from 'hooks/api/getters/useProjectAccess/useProjectAccess';
|
||||||
|
import { IProjectRole } from 'interfaces/role';
|
||||||
|
import { ProjectRoleSelect } from '../../ProjectRoleSelect/ProjectRoleSelect';
|
||||||
|
import { useStyles } from './ProjectRoleCell.styles';
|
||||||
|
|
||||||
|
interface IProjectRoleCellProps {
|
||||||
|
value: number;
|
||||||
|
user: IProjectAccessUser;
|
||||||
|
roles: IProjectRole[];
|
||||||
|
onChange: (event: SelectChangeEvent) => Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ProjectRoleCell: VFC<IProjectRoleCellProps> = ({
|
||||||
|
value,
|
||||||
|
user,
|
||||||
|
roles,
|
||||||
|
onChange,
|
||||||
|
}) => {
|
||||||
|
const { classes } = useStyles();
|
||||||
|
return (
|
||||||
|
<Box className={classes.cell}>
|
||||||
|
<ProjectRoleSelect
|
||||||
|
id={`role-${user.id}-select`}
|
||||||
|
key={user.id}
|
||||||
|
placeholder="Choose role"
|
||||||
|
onChange={onChange}
|
||||||
|
roles={roles}
|
||||||
|
value={value || -1}
|
||||||
|
>
|
||||||
|
<MenuItem value="" disabled>
|
||||||
|
Choose role
|
||||||
|
</MenuItem>
|
||||||
|
</ProjectRoleSelect>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
@ -9,10 +9,11 @@ import React from 'react';
|
|||||||
import { IProjectRole } from 'interfaces/role';
|
import { IProjectRole } from 'interfaces/role';
|
||||||
|
|
||||||
import { useStyles } from '../ProjectAccess.styles';
|
import { useStyles } from '../ProjectAccess.styles';
|
||||||
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
|
|
||||||
interface IProjectRoleSelect {
|
interface IProjectRoleSelect {
|
||||||
roles: IProjectRole[];
|
roles: IProjectRole[];
|
||||||
labelId: string;
|
labelId?: string;
|
||||||
id: string;
|
id: string;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
onChange: (evt: SelectChangeEvent) => void;
|
onChange: (evt: SelectChangeEvent) => void;
|
||||||
@ -31,12 +32,18 @@ export const ProjectRoleSelect: React.FC<IProjectRoleSelect> = ({
|
|||||||
const { classes: styles } = useStyles();
|
const { classes: styles } = useStyles();
|
||||||
return (
|
return (
|
||||||
<FormControl variant="outlined" size="small">
|
<FormControl variant="outlined" size="small">
|
||||||
<InputLabel
|
<ConditionallyRender
|
||||||
style={{ backgroundColor: '#fff' }}
|
condition={Boolean(labelId)}
|
||||||
id="add-user-select-role-label"
|
show={() => (
|
||||||
>
|
<InputLabel
|
||||||
Role
|
style={{ backgroundColor: '#fff' }}
|
||||||
</InputLabel>
|
id={labelId}
|
||||||
|
>
|
||||||
|
Role
|
||||||
|
</InputLabel>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
<Select
|
<Select
|
||||||
labelId={labelId}
|
labelId={labelId}
|
||||||
id={id}
|
id={id}
|
||||||
|
Loading…
Reference in New Issue
Block a user