From b61980e71b65af26b2c15961200a5365ad4d1bc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nuno=20G=C3=B3is?= Date: Thu, 26 May 2022 15:27:20 +0100 Subject: [PATCH] feat: Admin project roles table (#1030) * feat: new admin project roles table * small fixes * replace Box in defaultColumn Cell with the new TextCell * refactor: slight adjustments * misc improvements * add HighlightCell * fix: description width * Update src/component/admin/projectRoles/ProjectRoles/ProjectRoleList/ProjectRoleList.tsx Co-authored-by: Tymoteusz Czech <2625371+Tymek@users.noreply.github.com> * address PR comments, small tooltip fixes * fix: prettier fmt Co-authored-by: Tymoteusz Czech <2625371+Tymek@users.noreply.github.com> --- .../component/addons/AddonList/AddonList.tsx | 2 - .../ConfiguredAddons/ConfiguredAddons.tsx | 10 +- .../src/component/admin/menu/AdminMenu.tsx | 6 +- .../ProjectRoleList/ProjectRoleList.tsx | 262 ++++++++++++++---- .../ProjectRoles/ProjectRoles.tsx | 49 +--- .../cells/HighlightCell/HighlightCell.tsx | 29 ++ .../EnvironmentActionCell.tsx | 18 +- .../EnvironmentNameCell.tsx | 6 +- 8 files changed, 265 insertions(+), 117 deletions(-) create mode 100644 frontend/src/component/common/Table/cells/HighlightCell/HighlightCell.tsx diff --git a/frontend/src/component/addons/AddonList/AddonList.tsx b/frontend/src/component/addons/AddonList/AddonList.tsx index a8702d19e7..15c5a0131a 100644 --- a/frontend/src/component/addons/AddonList/AddonList.tsx +++ b/frontend/src/component/addons/AddonList/AddonList.tsx @@ -79,8 +79,6 @@ export const AddonList = () => { condition={addons.length > 0} show={} /> - -
); diff --git a/frontend/src/component/addons/AddonList/ConfiguredAddons/ConfiguredAddons.tsx b/frontend/src/component/addons/AddonList/ConfiguredAddons/ConfiguredAddons.tsx index f00508b52c..f7ddb64cee 100644 --- a/frontend/src/component/addons/AddonList/ConfiguredAddons/ConfiguredAddons.tsx +++ b/frontend/src/component/addons/AddonList/ConfiguredAddons/ConfiguredAddons.tsx @@ -1,19 +1,12 @@ import { useMemo } from 'react'; -import { Box, Table, TableBody, TableCell, TableRow } from '@mui/material'; -import { Delete, Edit, Visibility, VisibilityOff } from '@mui/icons-material'; +import { Table, TableBody, TableCell, TableRow } from 'component/common/Table'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; -import { - DELETE_ADDON, - UPDATE_ADDON, -} from 'component/providers/AccessProvider/permissions'; -import { useNavigate } from 'react-router-dom'; import { PageContent } from 'component/common/PageContent/PageContent'; import useAddons from 'hooks/api/getters/useAddons/useAddons'; import useToast from 'hooks/useToast'; import useAddonsApi from 'hooks/api/actions/useAddonsApi/useAddonsApi'; import { useState, useCallback } from 'react'; import { IAddon } from 'interfaces/addons'; -import PermissionIconButton from 'component/common/PermissionIconButton/PermissionIconButton'; import { Dialogue } from 'component/common/Dialogue/Dialogue'; import { formatUnknownError } from 'utils/formatUnknownError'; import { LinkCell } from 'component/common/Table/cells/LinkCell/LinkCell'; @@ -169,6 +162,7 @@ export const ConfiguredAddons = () => { } + sx={theme => ({ marginBottom: theme.spacing(2) })} > diff --git a/frontend/src/component/admin/menu/AdminMenu.tsx b/frontend/src/component/admin/menu/AdminMenu.tsx index bb341cf307..9b38b7a306 100644 --- a/frontend/src/component/admin/menu/AdminMenu.tsx +++ b/frontend/src/component/admin/menu/AdminMenu.tsx @@ -59,7 +59,7 @@ function AdminMenu() { to="/admin/roles" style={createNavLinkStyle} > - Project Roles + Project roles } /> @@ -69,7 +69,7 @@ function AdminMenu() { value="/admin/api" label={ - API Access + API access } /> @@ -77,7 +77,7 @@ function AdminMenu() { value="/admin/auth" label={ - Single Sign-On + Single sign-on } /> diff --git a/frontend/src/component/admin/projectRoles/ProjectRoles/ProjectRoleList/ProjectRoleList.tsx b/frontend/src/component/admin/projectRoles/ProjectRoles/ProjectRoleList/ProjectRoleList.tsx index 7960909abe..29e80877cf 100644 --- a/frontend/src/component/admin/projectRoles/ProjectRoles/ProjectRoleList/ProjectRoleList.tsx +++ b/frontend/src/component/admin/projectRoles/ProjectRoles/ProjectRoleList/ProjectRoleList.tsx @@ -1,37 +1,47 @@ -import { useContext, useState } from 'react'; +import { useEffect, useMemo, useState } from 'react'; import { Table, + SortableTableHeader, TableBody, TableCell, - TableHead, TableRow, -} from '@mui/material'; -import AccessContext from 'contexts/AccessContext'; -import usePagination from 'hooks/usePagination'; + TablePlaceholder, + TableSearch, +} from 'component/common/Table'; +import { useTable, useGlobalFilter, useSortBy } from 'react-table'; import { ADMIN } from 'component/providers/AccessProvider/permissions'; -import PaginateUI from 'component/common/PaginateUI/PaginateUI'; -import ProjectRoleListItem from './ProjectRoleListItem/ProjectRoleListItem'; import useProjectRoles from 'hooks/api/getters/useProjectRoles/useProjectRoles'; import IRole, { IProjectRole } from 'interfaces/role'; import useProjectRolesApi from 'hooks/api/actions/useProjectRolesApi/useProjectRolesApi'; import useToast from 'hooks/useToast'; import ProjectRoleDeleteConfirm from '../ProjectRoleDeleteConfirm/ProjectRoleDeleteConfirm'; import { formatUnknownError } from 'utils/formatUnknownError'; -import { useStyles } from './ProjectRoleListItem/ProjectRoleListItem.styles'; +import { Box, Button, useMediaQuery } from '@mui/material'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; +import PermissionIconButton from 'component/common/PermissionIconButton/PermissionIconButton'; +import { Delete, Edit, SupervisedUserCircle } from '@mui/icons-material'; +import { useNavigate } from 'react-router-dom'; +import { PageContent } from 'component/common/PageContent/PageContent'; +import { SearchHighlightProvider } from 'component/common/Table/SearchHighlightContext/SearchHighlightContext'; +import { PageHeader } from 'component/common/PageHeader/PageHeader'; +import { sortTypes } from 'utils/sortTypes'; +import { HighlightCell } from 'component/common/Table/cells/HighlightCell/HighlightCell'; +import theme from 'themes/theme'; +import { IconCell } from 'component/common/Table/cells/IconCell/IconCell'; const ROOTROLE = 'root'; +const BUILTIN_ROLE_TYPE = 'project'; const ProjectRoleList = () => { - const { hasAccess } = useContext(AccessContext); - const { roles } = useProjectRoles(); - const { classes: styles } = useStyles(); + const navigate = useNavigate(); + const { roles, refetch, loading } = useProjectRoles(); + + const isExtraSmallScreen = useMediaQuery(theme.breakpoints.down('sm')); const paginationFilter = (role: IRole) => role?.type !== ROOTROLE; + const data = roles.filter(paginationFilter); - const { page, pages, nextPage, prevPage, setPageIndex, pageIndex } = - usePagination(roles, 10, paginationFilter); const { deleteRole } = useProjectRolesApi(); - const { refetch } = useProjectRoles(); const [currentRole, setCurrentRole] = useState(null); const [delDialog, setDelDialog] = useState(false); const [confirmName, setConfirmName] = useState(''); @@ -54,60 +64,198 @@ const ProjectRoleList = () => { setConfirmName(''); }; - const renderRoles = () => { - return page.map((role: IProjectRole) => { - return ( - - ); - }); - }; + const columns = useMemo( + () => [ + { + id: 'Icon', + Cell: () => ( + } + /> + ), + }, + { + Header: 'Project role', + accessor: 'name', + }, + { + Header: 'Description', + accessor: 'description', + width: '90%', + }, + { + Header: 'Actions', + id: 'Actions', + align: 'center', + Cell: ({ + row: { + original: { id, type, name, description }, + }, + }: any) => ( + + { + navigate(`/admin/roles/${id}/edit`); + }} + permission={ADMIN} + tooltipProps={{ + title: + type === BUILTIN_ROLE_TYPE + ? 'You cannot edit role' + : 'Edit role', + }} + > + + + { + setCurrentRole({ + id, + name, + description, + } as IProjectRole); + setDelDialog(true); + }} + permission={ADMIN} + tooltipProps={{ + title: + type === BUILTIN_ROLE_TYPE + ? 'You cannot remove role' + : 'Remove role', + }} + > + + + + ), + width: 100, + disableSortBy: true, + }, + ], + [navigate] + ); - if (!roles) return null; + const initialState = useMemo( + () => ({ + sortBy: [{ id: 'name', desc: false }], + }), + [] + ); + + const { + getTableProps, + getTableBodyProps, + headerGroups, + rows, + prepareRow, + state: { globalFilter }, + setGlobalFilter, + setHiddenColumns, + } = useTable( + { + columns: columns as any[], // TODO: fix after `react-table` v8 update + data, + initialState, + sortTypes, + autoResetGlobalFilter: false, + autoResetSortBy: false, + disableSortRemove: true, + defaultColumn: { + Cell: HighlightCell, + }, + }, + useGlobalFilter, + useSortBy + ); + + useEffect(() => { + setHiddenColumns([]); + if (isExtraSmallScreen) { + setHiddenColumns(['Icon']); + } + }, [setHiddenColumns, isExtraSmallScreen]); return ( -
-
- - - - Project Role - - Description - - - {hasAccess(ADMIN) ? 'Action' : ''} - - - - {renderRoles()} - + + + + + } /> -
-
+ } + > + + + + + {rows.map(row => { + prepareRow(row); + return ( + + {row.cells.map(cell => ( + + {cell.render('Cell')} + + ))} + + ); + })} + +
+
+ 0} + show={ + + No project roles found matching “ + {globalFilter} + ” + + } + elseShow={ + + No project roles available. Get started by + adding one. + + } + /> + } + /> + - +
); }; diff --git a/frontend/src/component/admin/projectRoles/ProjectRoles/ProjectRoles.tsx b/frontend/src/component/admin/projectRoles/ProjectRoles/ProjectRoles.tsx index 3d14e7e7e4..8b538acac8 100644 --- a/frontend/src/component/admin/projectRoles/ProjectRoles/ProjectRoles.tsx +++ b/frontend/src/component/admin/projectRoles/ProjectRoles/ProjectRoles.tsx @@ -1,61 +1,22 @@ -import { Button } from '@mui/material'; import { useContext } from 'react'; -import { useNavigate } from 'react-router-dom'; import AccessContext from 'contexts/AccessContext'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; -import { PageHeader } from 'component/common/PageHeader/PageHeader'; -import { PageContent } from 'component/common/PageContent/PageContent'; import { ADMIN } from 'component/providers/AccessProvider/permissions'; import AdminMenu from 'component/admin/menu/AdminMenu'; -import { useStyles } from './ProjectRoles.styles'; import ProjectRoleList from './ProjectRoleList/ProjectRoleList'; import { AdminAlert } from 'component/common/AdminAlert/AdminAlert'; const ProjectRoles = () => { const { hasAccess } = useContext(AccessContext); - const { classes: styles } = useStyles(); - const navigate = useNavigate(); return (
- - navigate( - '/admin/create-project-role' - ) - } - > - New Project role - - } - elseShow={ - - PS! Only admins can add/remove roles. - - } - /> - } - /> - } - > - } - elseShow={} - /> - + } + elseShow={} + />
); }; diff --git a/frontend/src/component/common/Table/cells/HighlightCell/HighlightCell.tsx b/frontend/src/component/common/Table/cells/HighlightCell/HighlightCell.tsx new file mode 100644 index 0000000000..0292fa1034 --- /dev/null +++ b/frontend/src/component/common/Table/cells/HighlightCell/HighlightCell.tsx @@ -0,0 +1,29 @@ +import { VFC } from 'react'; +import { Box } from '@mui/material'; +import { Highlighter } from 'component/common/Highlighter/Highlighter'; +import { useSearchHighlightContext } from 'component/common/Table/SearchHighlightContext/SearchHighlightContext'; + +interface IHighlightCellProps { + value?: string | null; + children?: string | null; +} + +export const HighlightCell: VFC = ({ + value, + children, +}) => { + const { searchQuery } = useSearchHighlightContext(); + + const text = children ?? value; + if (!text) { + return ; + } + + return ( + + + {text} + + + ); +}; diff --git a/frontend/src/component/environments/EnvironmentActionCell/EnvironmentActionCell.tsx b/frontend/src/component/environments/EnvironmentActionCell/EnvironmentActionCell.tsx index c8464b303f..9ba499b8ad 100644 --- a/frontend/src/component/environments/EnvironmentActionCell/EnvironmentActionCell.tsx +++ b/frontend/src/component/environments/EnvironmentActionCell/EnvironmentActionCell.tsx @@ -138,7 +138,14 @@ export const EnvironmentActionCell = ({ + + { + const { searchQuery } = useSearchHighlightContext(); + return ( - {environment.name} + {environment.name} Disabled}