mirror of
https://github.com/Unleash/unleash.git
synced 2025-04-29 01:15:48 +02:00
feat: implement better roles sub-tabs (#4009)
https://linear.app/unleash/issue/2-1145/improve-roles-sub-tabs Improves UI/UX of the roles sub-tabs. Some of the logic is a bit specific due to the feature flag, will be nice to clean this up once we remove it. Before:  After: 
This commit is contained in:
parent
02600880d1
commit
3a27f2a4bd
@ -1,49 +1,86 @@
|
||||
import { useContext } from 'react';
|
||||
import { useContext, useState } from 'react';
|
||||
import AccessContext from 'contexts/AccessContext';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import { ADMIN } from 'component/providers/AccessProvider/permissions';
|
||||
import { RolesTable } from './RolesTable/RolesTable';
|
||||
import { AdminAlert } from 'component/common/AdminAlert/AdminAlert';
|
||||
import { PageContent } from 'component/common/PageContent/PageContent';
|
||||
import { Tab, Tabs, styled } from '@mui/material';
|
||||
import { Tab, Tabs, styled, useMediaQuery } from '@mui/material';
|
||||
import { Route, Routes, useLocation } from 'react-router-dom';
|
||||
import { CenteredNavLink } from '../menu/CenteredNavLink';
|
||||
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||
import { PROJECT_ROLE_TYPE } from '@server/util/constants';
|
||||
import { PROJECT_ROLE_TYPE, ROOT_ROLE_TYPE } from '@server/util/constants';
|
||||
import { useRoles } from 'hooks/api/getters/useRoles/useRoles';
|
||||
import { Search } from 'component/common/Search/Search';
|
||||
import theme from 'themes/theme';
|
||||
import { PageHeader } from 'component/common/PageHeader/PageHeader';
|
||||
import { Add } from '@mui/icons-material';
|
||||
import { UPDATE_ROLE } from '@server/types/permissions';
|
||||
import ResponsiveButton from 'component/common/ResponsiveButton/ResponsiveButton';
|
||||
import IRole from 'interfaces/role';
|
||||
|
||||
const StyledPageContent = styled(PageContent)(({ theme }) => ({
|
||||
'.page-header': {
|
||||
padding: 0,
|
||||
'& .page-header': {
|
||||
padding: theme.spacing(0, 4),
|
||||
[theme.breakpoints.down('md')]: {
|
||||
padding: theme.spacing(1),
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
const tabs = [
|
||||
{
|
||||
label: 'Root',
|
||||
path: '/admin/roles',
|
||||
},
|
||||
{
|
||||
label: 'Project',
|
||||
path: '/admin/roles/project-roles',
|
||||
},
|
||||
];
|
||||
const StyledHeader = styled('div')(({ theme }) => ({
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
}));
|
||||
|
||||
const StyledTabsContainer = styled('div')({
|
||||
flex: 1,
|
||||
});
|
||||
|
||||
const StyledActions = styled('div')({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
});
|
||||
|
||||
export const Roles = () => {
|
||||
const { uiConfig } = useUiConfig();
|
||||
const { hasAccess } = useContext(AccessContext);
|
||||
const { pathname } = useLocation();
|
||||
|
||||
if (!uiConfig.flags.customRootRoles) {
|
||||
return (
|
||||
<div>
|
||||
<ConditionallyRender
|
||||
condition={hasAccess(ADMIN)}
|
||||
show={<RolesTable type={PROJECT_ROLE_TYPE} />}
|
||||
elseShow={<AdminAlert />}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
const { roles, projectRoles, loading } = useRoles();
|
||||
|
||||
const [searchValue, setSearchValue] = useState('');
|
||||
const [modalOpen, setModalOpen] = useState(false);
|
||||
const [selectedRole, setSelectedRole] = useState<IRole>();
|
||||
|
||||
const tabs = uiConfig.flags.customRootRoles
|
||||
? [
|
||||
{
|
||||
label: 'Root roles',
|
||||
path: '/admin/roles',
|
||||
total: roles.length,
|
||||
},
|
||||
{
|
||||
label: 'Project roles',
|
||||
path: '/admin/roles/project-roles',
|
||||
total: projectRoles.length,
|
||||
},
|
||||
]
|
||||
: [
|
||||
{
|
||||
label: 'Project roles',
|
||||
path: '/admin/roles',
|
||||
total: projectRoles.length,
|
||||
},
|
||||
];
|
||||
|
||||
const isSmallScreen = useMediaQuery(theme.breakpoints.down('md'));
|
||||
|
||||
const type =
|
||||
!uiConfig.flags.customRootRoles || pathname.includes('project-roles')
|
||||
? PROJECT_ROLE_TYPE
|
||||
: ROOT_ROLE_TYPE;
|
||||
|
||||
return (
|
||||
<div>
|
||||
@ -53,36 +90,111 @@ export const Roles = () => {
|
||||
<StyledPageContent
|
||||
headerClass="page-header"
|
||||
bodyClass="page-body"
|
||||
isLoading={loading}
|
||||
header={
|
||||
<Tabs
|
||||
value={pathname}
|
||||
indicatorColor="primary"
|
||||
textColor="primary"
|
||||
variant="scrollable"
|
||||
allowScrollButtonsMobile
|
||||
>
|
||||
{tabs.map(({ label, path }) => (
|
||||
<Tab
|
||||
key={label}
|
||||
value={path}
|
||||
label={
|
||||
<CenteredNavLink to={path}>
|
||||
<span>{label}</span>
|
||||
</CenteredNavLink>
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</Tabs>
|
||||
<>
|
||||
<StyledHeader>
|
||||
<StyledTabsContainer>
|
||||
<Tabs
|
||||
value={pathname}
|
||||
indicatorColor="primary"
|
||||
textColor="primary"
|
||||
variant="scrollable"
|
||||
allowScrollButtonsMobile
|
||||
>
|
||||
{tabs.map(
|
||||
({ label, path, total }) => (
|
||||
<Tab
|
||||
key={label}
|
||||
value={path}
|
||||
label={
|
||||
<CenteredNavLink
|
||||
to={path}
|
||||
>
|
||||
<span>
|
||||
{label} (
|
||||
{total})
|
||||
</span>
|
||||
</CenteredNavLink>
|
||||
}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
</Tabs>
|
||||
</StyledTabsContainer>
|
||||
<StyledActions>
|
||||
<ConditionallyRender
|
||||
condition={!isSmallScreen}
|
||||
show={
|
||||
<>
|
||||
<Search
|
||||
initialValue={
|
||||
searchValue
|
||||
}
|
||||
onChange={
|
||||
setSearchValue
|
||||
}
|
||||
/>
|
||||
<PageHeader.Divider />
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<ResponsiveButton
|
||||
onClick={() => {
|
||||
setSelectedRole(undefined);
|
||||
setModalOpen(true);
|
||||
}}
|
||||
maxWidth={`${theme.breakpoints.values['sm']}px`}
|
||||
Icon={Add}
|
||||
permission={UPDATE_ROLE}
|
||||
>
|
||||
New {type} role
|
||||
</ResponsiveButton>
|
||||
</StyledActions>
|
||||
</StyledHeader>
|
||||
<ConditionallyRender
|
||||
condition={isSmallScreen}
|
||||
show={
|
||||
<Search
|
||||
initialValue={searchValue}
|
||||
onChange={setSearchValue}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<Routes>
|
||||
<Route
|
||||
path="project-roles"
|
||||
element={
|
||||
<RolesTable type={PROJECT_ROLE_TYPE} />
|
||||
<RolesTable
|
||||
type={PROJECT_ROLE_TYPE}
|
||||
searchValue={searchValue}
|
||||
modalOpen={modalOpen}
|
||||
setModalOpen={setModalOpen}
|
||||
selectedRole={selectedRole}
|
||||
setSelectedRole={setSelectedRole}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="*"
|
||||
element={
|
||||
<RolesTable
|
||||
type={
|
||||
uiConfig.flags.customRootRoles
|
||||
? ROOT_ROLE_TYPE
|
||||
: PROJECT_ROLE_TYPE
|
||||
}
|
||||
searchValue={searchValue}
|
||||
modalOpen={modalOpen}
|
||||
setModalOpen={setModalOpen}
|
||||
selectedRole={selectedRole}
|
||||
setSelectedRole={setSelectedRole}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Route path="*" element={<RolesTable />} />
|
||||
</Routes>
|
||||
</StyledPageContent>
|
||||
}
|
||||
|
@ -5,14 +5,12 @@ import IRole, { PredefinedRoleType } from 'interfaces/role';
|
||||
import useToast from 'hooks/useToast';
|
||||
import { formatUnknownError } from 'utils/formatUnknownError';
|
||||
import { PageContent } from 'component/common/PageContent/PageContent';
|
||||
import { PageHeader } from 'component/common/PageHeader/PageHeader';
|
||||
import { Button, useMediaQuery } from '@mui/material';
|
||||
import { useMediaQuery } from '@mui/material';
|
||||
import { SearchHighlightProvider } from 'component/common/Table/SearchHighlightContext/SearchHighlightContext';
|
||||
import { useFlexLayout, useSortBy, useTable } from 'react-table';
|
||||
import { sortTypes } from 'utils/sortTypes';
|
||||
import { TextCell } from 'component/common/Table/cells/TextCell/TextCell';
|
||||
import theme from 'themes/theme';
|
||||
import { Search } from 'component/common/Search/Search';
|
||||
import { useConditionallyHiddenColumns } from 'hooks/useConditionallyHiddenColumns';
|
||||
import { useSearch } from 'hooks/useSearch';
|
||||
import { IconCell } from 'component/common/Table/cells/IconCell/IconCell';
|
||||
@ -28,18 +26,27 @@ import { ROOT_ROLE_TYPE } from '@server/util/constants';
|
||||
|
||||
interface IRolesTableProps {
|
||||
type?: PredefinedRoleType;
|
||||
searchValue?: string;
|
||||
modalOpen: boolean;
|
||||
setModalOpen: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
selectedRole?: IRole;
|
||||
setSelectedRole: React.Dispatch<React.SetStateAction<IRole | undefined>>;
|
||||
}
|
||||
|
||||
export const RolesTable = ({ type = ROOT_ROLE_TYPE }: IRolesTableProps) => {
|
||||
export const RolesTable = ({
|
||||
type = ROOT_ROLE_TYPE,
|
||||
searchValue = '',
|
||||
modalOpen,
|
||||
setModalOpen,
|
||||
selectedRole,
|
||||
setSelectedRole,
|
||||
}: IRolesTableProps) => {
|
||||
const { setToastData, setToastApiError } = useToast();
|
||||
|
||||
const { roles, projectRoles, refetch, loading } = useRoles();
|
||||
const { removeRole } = useRolesApi();
|
||||
|
||||
const [searchValue, setSearchValue] = useState('');
|
||||
const [modalOpen, setModalOpen] = useState(false);
|
||||
const [deleteOpen, setDeleteOpen] = useState(false);
|
||||
const [selectedRole, setSelectedRole] = useState<IRole>();
|
||||
|
||||
const onDeleteConfirm = async (role: IRole) => {
|
||||
try {
|
||||
@ -154,53 +161,8 @@ export const RolesTable = ({ type = ROOT_ROLE_TYPE }: IRolesTableProps) => {
|
||||
columns
|
||||
);
|
||||
|
||||
const titledCaseType = type[0].toUpperCase() + type.slice(1);
|
||||
|
||||
return (
|
||||
<PageContent
|
||||
isLoading={loading}
|
||||
header={
|
||||
<PageHeader
|
||||
title={`${titledCaseType} roles (${rows.length})`}
|
||||
actions={
|
||||
<>
|
||||
<ConditionallyRender
|
||||
condition={!isSmallScreen}
|
||||
show={
|
||||
<>
|
||||
<Search
|
||||
initialValue={searchValue}
|
||||
onChange={setSearchValue}
|
||||
/>
|
||||
<PageHeader.Divider />
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
onClick={() => {
|
||||
setSelectedRole(undefined);
|
||||
setModalOpen(true);
|
||||
}}
|
||||
>
|
||||
New {type} role
|
||||
</Button>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<ConditionallyRender
|
||||
condition={isSmallScreen}
|
||||
show={
|
||||
<Search
|
||||
initialValue={searchValue}
|
||||
onChange={setSearchValue}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</PageHeader>
|
||||
}
|
||||
>
|
||||
<PageContent isLoading={loading}>
|
||||
<SearchHighlightProvider value={getSearchText(searchValue)}>
|
||||
<VirtualizedTable
|
||||
rows={rows}
|
||||
|
Loading…
Reference in New Issue
Block a user