diff --git a/frontend/src/component/admin/users/AccessOverview/AccessOverview.tsx b/frontend/src/component/admin/users/AccessOverview/AccessOverview.tsx index f012ffab32..3c217348e5 100644 --- a/frontend/src/component/admin/users/AccessOverview/AccessOverview.tsx +++ b/frontend/src/component/admin/users/AccessOverview/AccessOverview.tsx @@ -2,7 +2,6 @@ import { PageContent } from 'component/common/PageContent/PageContent'; import { PageHeader } from 'component/common/PageHeader/PageHeader'; import { useRequiredPathParam } from 'hooks/useRequiredPathParam'; import useUserInfo from 'hooks/api/getters/useUserInfo/useUserInfo'; -import { AccessOverviewTable } from './AccessOverviewTable'; import { styled, useMediaQuery } from '@mui/material'; import { useEffect, useState } from 'react'; import { useEnvironments } from 'hooks/api/getters/useEnvironments/useEnvironments'; @@ -12,6 +11,7 @@ import { StringParam, useQueryParams } from 'use-query-params'; import useProjects from 'hooks/api/getters/useProjects/useProjects'; import { AccessOverviewSelect } from './AccessOverviewSelect'; import { useUserAccessOverview } from 'hooks/api/getters/useUserAccessOverview/useUserAccessOverview'; +import { AccessOverviewAccordion } from './AccessOverviewAccordion/AccessOverviewAccordion'; const StyledActionsContainer = styled('div')(({ theme }) => ({ display: 'flex', @@ -24,8 +24,10 @@ const StyledActionsContainer = styled('div')(({ theme }) => ({ }, })); -const StyledTitle = styled('h2')(({ theme }) => ({ - margin: theme.spacing(2, 0), +const StyledAccessOverviewContainer = styled('div')(({ theme }) => ({ + display: 'flex', + flexDirection: 'column', + gap: theme.spacing(2), })); export const AccessOverview = () => { @@ -104,19 +106,24 @@ export const AccessOverview = () => { } > - - Root permissions for role {rootRole?.name} - - - - Project permissions for project {project} with project roles [ - {projectRoles?.map((role: any) => role.name).join(', ')}] - - - - Environment permissions for environment {environment} - - + + + Root permissions for role {rootRole?.name} + + + Project permissions + {project + ? ` for project ${project}${projectRoles?.length ? ` with project role${projectRoles.length !== 1 ? 's' : ''} ${projectRoles?.map((role: any) => role.name).join(', ')}` : ''}` + : ''} + + {environment && ( + + Environment permissions for {environment} + + )} + ); }; diff --git a/frontend/src/component/admin/users/AccessOverview/AccessOverviewAccordion/AccessOverviewAccordion.tsx b/frontend/src/component/admin/users/AccessOverview/AccessOverviewAccordion/AccessOverviewAccordion.tsx new file mode 100644 index 0000000000..72d51e9e6a --- /dev/null +++ b/frontend/src/component/admin/users/AccessOverview/AccessOverviewAccordion/AccessOverviewAccordion.tsx @@ -0,0 +1,78 @@ +import ExpandMore from '@mui/icons-material/ExpandMore'; +import { + Accordion, + AccordionDetails, + AccordionSummary, + styled, +} from '@mui/material'; +import type { IAccessOverviewPermission } from 'interfaces/permissions'; +import { AccessOverviewList } from './AccessOverviewList'; + +const StyledAccordion = styled(Accordion)(({ theme }) => ({ + border: `1px solid ${theme.palette.divider}`, + borderRadius: theme.shape.borderRadiusLarge, + overflow: 'hidden', + boxShadow: 'none', + margin: 0, + '&:before': { + display: 'none', + }, +})); + +const StyledAccordionSummary = styled(AccordionSummary)(({ theme }) => ({ + backgroundColor: theme.palette.background.elevation1, + '& .MuiAccordionSummary-content': { + justifyContent: 'space-between', + alignItems: 'center', + minHeight: '30px', + }, +})); + +const StyledTitleContainer = styled('div')(({ theme }) => ({ + display: 'flex', + alignItems: 'start', + flexDirection: 'column', + gap: theme.spacing(0.5), +})); + +const StyledTitle = styled('span')(({ theme }) => ({ + fontWeight: theme.fontWeight.bold, +})); + +const StyledSecondaryLabel = styled('span')(({ theme }) => ({ + color: theme.palette.text.secondary, + fontSize: theme.fontSizes.smallBody, + marginRight: theme.spacing(1), +})); + +const StyledAccordionDetails = styled(AccordionDetails)(({ theme }) => ({ + padding: 0, +})); + +interface IAccessAccordionProps { + permissions: IAccessOverviewPermission[]; + children: React.ReactNode; +} + +export const AccessOverviewAccordion = ({ + permissions, + children, +}: IAccessAccordionProps) => ( + + }> + + {children} + + + { + permissions.filter(({ hasPermission }) => hasPermission) + .length + } + /{permissions.length} permissions + + + + + + +); diff --git a/frontend/src/component/admin/users/AccessOverview/AccessOverviewAccordion/AccessOverviewList.tsx b/frontend/src/component/admin/users/AccessOverview/AccessOverviewAccordion/AccessOverviewList.tsx new file mode 100644 index 0000000000..26a63cb4c5 --- /dev/null +++ b/frontend/src/component/admin/users/AccessOverview/AccessOverviewAccordion/AccessOverviewList.tsx @@ -0,0 +1,73 @@ +import Check from '@mui/icons-material/Check'; +import Close from '@mui/icons-material/Close'; +import { Box, styled } from '@mui/material'; +import type { IAccessOverviewPermission } from 'interfaces/permissions'; + +const StyledList = styled('ul')(({ theme }) => ({ + listStyle: 'none', + padding: 0, + margin: 0, + fontSize: theme.fontSizes.smallBody, + '& li': { + display: 'flex', + justifyContent: 'space-between', + padding: theme.spacing(2), + '&:not(:last-child)': { + borderBottom: `1px solid ${theme.palette.divider}`, + }, + }, +})); + +const StyledPermissionStatus = styled('div', { + shouldForwardProp: (prop) => prop !== 'hasPermission', +})<{ hasPermission: boolean }>(({ theme, hasPermission }) => ({ + display: 'flex', + gap: theme.spacing(1), + alignItems: 'center', + width: theme.spacing(17.5), + color: hasPermission + ? theme.palette.text.primary + : theme.palette.text.secondary, + '& > svg': { + color: hasPermission + ? theme.palette.success.main + : theme.palette.error.main, + }, +})); + +export const AccessOverviewList = ({ + permissions, +}: { + permissions: IAccessOverviewPermission[]; +}) => { + return ( + + + {permissions.map((permission) => ( +
  • +
    {permission.displayName}
    + +
  • + ))} +
    +
    + ); +}; + +const PermissionStatus = ({ hasPermission }: { hasPermission: boolean }) => ( + + {hasPermission ? ( + <> + + Has permission + + ) : ( + <> + + No permission + + )} + +); diff --git a/frontend/src/component/admin/users/AccessOverview/AccessOverviewTable.tsx b/frontend/src/component/admin/users/AccessOverview/AccessOverviewTable.tsx deleted file mode 100644 index 4bbc8ca95b..0000000000 --- a/frontend/src/component/admin/users/AccessOverview/AccessOverviewTable.tsx +++ /dev/null @@ -1,81 +0,0 @@ -import { useMemo, useRef } from 'react'; -import { TablePlaceholder, VirtualizedTable } from 'component/common/Table'; -import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; -import { useFlexLayout, useSortBy, useTable } from 'react-table'; -import { sortTypes } from 'utils/sortTypes'; -import { IconCell } from 'component/common/Table/cells/IconCell/IconCell'; -import Check from '@mui/icons-material/Check'; -import Close from '@mui/icons-material/Close'; -import { Box } from '@mui/material'; -import type { IAccessOverviewPermission } from 'interfaces/permissions'; - -export const AccessOverviewTable = ({ - permissions, -}: { - permissions: IAccessOverviewPermission[]; -}) => { - const columns = useMemo( - () => [ - { - Header: 'Permission', - accessor: 'name', - minWidth: 100, - }, - { - Header: 'Description', - accessor: 'displayName', - minWidth: 180, - }, - { - Header: 'Has permission', - accessor: 'hasPermission', - Cell: ({ value }: { value: boolean }) => ( - - ) : ( - - ) - } - /> - ), - }, - ], - [permissions], - ); - - const initialState = { - sortBy: [{ id: 'name', desc: true }], - }; - - const { headerGroups, rows, prepareRow } = useTable( - { - columns: columns as any, - data: permissions ?? [], - initialState, - sortTypes, - }, - useSortBy, - useFlexLayout, - ); - - const parentRef = useRef(null); - - return ( - - - No permissions found. - } - /> - - ); -};