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.
- }
- />
-
- );
-};