mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01: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, | ||||
|     environmentId, | ||||
|     tooltipProps, | ||||
|     disabled, | ||||
|     ...rest | ||||
| }: IButtonProps | ILinkProps) => { | ||||
|     const { hasAccess } = useContext(AccessContext); | ||||
| @ -57,7 +58,11 @@ const PermissionIconButton = ({ | ||||
|             arrow | ||||
|         > | ||||
|             <div> | ||||
|                 <IconButton {...rest} disabled={!access} size="large"> | ||||
|                 <IconButton | ||||
|                     {...rest} | ||||
|                     disabled={!access || disabled} | ||||
|                     size="large" | ||||
|                 > | ||||
|                     {children} | ||||
|                 </IconButton> | ||||
|             </div> | ||||
|  | ||||
| @ -111,7 +111,8 @@ export const CellSortable: FC<ICellSortableProps> = ({ | ||||
|                         <button | ||||
|                             className={classnames( | ||||
|                                 isSorted && styles.sortedButton, | ||||
|                                 styles.sortButton | ||||
|                                 styles.sortButton, | ||||
|                                 alignClass | ||||
|                             )} | ||||
|                             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 { | ||||
|     Table, | ||||
| @ -12,6 +12,7 @@ import { | ||||
| import { PageContent } from 'component/common/PageContent/PageContent'; | ||||
| import { PageHeader } from 'component/common/PageHeader/PageHeader'; | ||||
| 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 useUnleashContext from 'hooks/api/getters/useUnleashContext/useUnleashContext'; | ||||
| 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 { Adjust } from '@mui/icons-material'; | ||||
| import { IconCell } from 'component/common/Table/cells/IconCell/IconCell'; | ||||
| import AccessContext from 'contexts/AccessContext'; | ||||
| 
 | ||||
| const ContextList: VFC = () => { | ||||
|     const [showDelDialogue, setShowDelDialogue] = useState(false); | ||||
| @ -31,6 +33,7 @@ const ContextList: VFC = () => { | ||||
|     const { context, refetchUnleashContext, loading } = useUnleashContext(); | ||||
|     const { removeContext } = useContextsApi(); | ||||
|     const { setToastData, setToastApiError } = useToast(); | ||||
|     const { hasAccess } = useContext(AccessContext); | ||||
| 
 | ||||
|     const data = useMemo(() => { | ||||
|         if (loading) { | ||||
| @ -66,8 +69,8 @@ const ContextList: VFC = () => { | ||||
|                 }: any) => ( | ||||
|                     <LinkCell | ||||
|                         title={name} | ||||
|                         to={`/context/edit/${name}`} | ||||
|                         subtitle={description} | ||||
|                         data-loading | ||||
|                     /> | ||||
|                 ), | ||||
|                 sortType: 'alphanumeric', | ||||
|  | ||||
| @ -6,10 +6,11 @@ export const useStyles = makeStyles()(theme => ({ | ||||
|     }, | ||||
|     divider: { | ||||
|         height: '1px', | ||||
|         width: '106.65%', | ||||
|         marginLeft: '-2rem', | ||||
|         backgroundColor: '#efefef', | ||||
|         marginTop: '2rem', | ||||
|         position: 'relative', | ||||
|         left: 0, | ||||
|         right: 0, | ||||
|         backgroundColor: theme.palette.divider, | ||||
|         margin: theme.spacing(4, -4, 3), | ||||
|     }, | ||||
|     inputLabel: { backgroundColor: '#fff' }, | ||||
|     roleName: { | ||||
|  | ||||
| @ -1,12 +1,10 @@ | ||||
| /* 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 { ProjectAccessAddUser } from './ProjectAccessAddUser/ProjectAccessAddUser'; | ||||
| import { PageContent } from 'component/common/PageContent/PageContent'; | ||||
| import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; | ||||
| import { useStyles } from './ProjectAccess.styles'; | ||||
| import usePagination from 'hooks/usePagination'; | ||||
| import PaginateUI from 'component/common/PaginateUI/PaginateUI'; | ||||
| import useToast from 'hooks/useToast'; | ||||
| import { Dialogue as ConfirmDialogue } from 'component/common/Dialogue/Dialogue'; | ||||
| import useProjectAccess, { | ||||
| @ -14,8 +12,8 @@ import useProjectAccess, { | ||||
| } from 'hooks/api/getters/useProjectAccess/useProjectAccess'; | ||||
| import useProjectApi from 'hooks/api/actions/useProjectApi/useProjectApi'; | ||||
| import { PageHeader } from 'component/common/PageHeader/PageHeader'; | ||||
| import { ProjectAccessList } from './ProjectAccessList/ProjectAccessList'; | ||||
| import { useRequiredPathParam } from 'hooks/useRequiredPathParam'; | ||||
| import { ProjectAccessTable } from './ProjectAccessTable/ProjectAccessTable'; | ||||
| 
 | ||||
| export const ProjectAccess = () => { | ||||
|     const projectId = useRequiredPathParam('projectId'); | ||||
| @ -23,28 +21,11 @@ export const ProjectAccess = () => { | ||||
|     const { access, refetchProjectAccess } = useProjectAccess(projectId); | ||||
|     const { setToastData } = useToast(); | ||||
|     const { isOss } = useUiConfig(); | ||||
|     const { page, pages, nextPage, prevPage, setPageIndex, pageIndex } = | ||||
|         usePagination(access.users, 10); | ||||
|     const { removeUserFromRole, changeUserRole } = useProjectApi(); | ||||
|     const [showDelDialogue, setShowDelDialogue] = useState(false); | ||||
|     const [user, setUser] = useState<IProjectAccessUser | undefined>(); | ||||
| 
 | ||||
|     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 handleRoleChange = | ||||
|     const handleRoleChange = useCallback( | ||||
|         (userId: number) => async (evt: SelectChangeEvent) => { | ||||
|             const roleId = Number(evt.target.value); | ||||
|             try { | ||||
| @ -61,7 +42,24 @@ export const ProjectAccess = () => { | ||||
|                     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) => { | ||||
|         setUser(user); | ||||
| @ -96,21 +94,13 @@ export const ProjectAccess = () => { | ||||
|             <ProjectAccessAddUser roles={access?.roles} /> | ||||
| 
 | ||||
|             <div className={styles.divider}></div> | ||||
|             <ProjectAccessList | ||||
| 
 | ||||
|             <ProjectAccessTable | ||||
|                 access={access} | ||||
|                 handleRoleChange={handleRoleChange} | ||||
|                 handleRemoveAccess={handleRemoveAccess} | ||||
|                 page={page} | ||||
|                 access={access} | ||||
|             > | ||||
|                 <PaginateUI | ||||
|                     pages={pages} | ||||
|                     pageIndex={pageIndex} | ||||
|                     setPageIndex={setPageIndex} | ||||
|                     nextPage={nextPage} | ||||
|                     prevPage={prevPage} | ||||
|                     style={{ bottom: '-21px' }} | ||||
|                 /> | ||||
|             </ProjectAccessList> | ||||
|                 projectId={projectId} | ||||
|             /> | ||||
| 
 | ||||
|             <ConfirmDialogue | ||||
|                 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 { useStyles } from '../ProjectAccess.styles'; | ||||
| import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; | ||||
| 
 | ||||
| interface IProjectRoleSelect { | ||||
|     roles: IProjectRole[]; | ||||
|     labelId: string; | ||||
|     labelId?: string; | ||||
|     id: string; | ||||
|     placeholder?: string; | ||||
|     onChange: (evt: SelectChangeEvent) => void; | ||||
| @ -31,12 +32,18 @@ export const ProjectRoleSelect: React.FC<IProjectRoleSelect> = ({ | ||||
|     const { classes: styles } = useStyles(); | ||||
|     return ( | ||||
|         <FormControl variant="outlined" size="small"> | ||||
|             <InputLabel | ||||
|                 style={{ backgroundColor: '#fff' }} | ||||
|                 id="add-user-select-role-label" | ||||
|             > | ||||
|                 Role | ||||
|             </InputLabel> | ||||
|             <ConditionallyRender | ||||
|                 condition={Boolean(labelId)} | ||||
|                 show={() => ( | ||||
|                     <InputLabel | ||||
|                         style={{ backgroundColor: '#fff' }} | ||||
|                         id={labelId} | ||||
|                     > | ||||
|                         Role | ||||
|                     </InputLabel> | ||||
|                 )} | ||||
|             /> | ||||
| 
 | ||||
|             <Select | ||||
|                 labelId={labelId} | ||||
|                 id={id} | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user