mirror of
https://github.com/Unleash/unleash.git
synced 2025-04-29 01:15:48 +02:00
Merge branch 'main' into task/Add_strategy_information_to_playground_results
This commit is contained in:
commit
74ccde2096
@ -18,18 +18,19 @@ import { CopyApiTokenButton } from 'component/admin/apiToken/CopyApiTokenButton/
|
|||||||
import { RemoveApiTokenButton } from 'component/admin/apiToken/RemoveApiTokenButton/RemoveApiTokenButton';
|
import { RemoveApiTokenButton } from 'component/admin/apiToken/RemoveApiTokenButton/RemoveApiTokenButton';
|
||||||
import { DateCell } from 'component/common/Table/cells/DateCell/DateCell';
|
import { DateCell } from 'component/common/Table/cells/DateCell/DateCell';
|
||||||
import { sortTypes } from 'utils/sortTypes';
|
import { sortTypes } from 'utils/sortTypes';
|
||||||
import { useEffect, useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import theme from 'themes/theme';
|
import theme from 'themes/theme';
|
||||||
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||||
import { ProjectsList } from 'component/admin/apiToken/ProjectsList/ProjectsList';
|
import { ProjectsList } from 'component/admin/apiToken/ProjectsList/ProjectsList';
|
||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
import { HighlightCell } from 'component/common/Table/cells/HighlightCell/HighlightCell';
|
import { HighlightCell } from 'component/common/Table/cells/HighlightCell/HighlightCell';
|
||||||
import { Search } from 'component/common/Search/Search';
|
import { Search } from 'component/common/Search/Search';
|
||||||
|
import useHiddenColumns from 'hooks/useHiddenColumns';
|
||||||
|
|
||||||
export const ApiTokenTable = () => {
|
export const ApiTokenTable = () => {
|
||||||
const { tokens, loading } = useApiTokens();
|
const { tokens, loading } = useApiTokens();
|
||||||
const hiddenColumns = useHiddenColumns();
|
|
||||||
const initialState = useMemo(() => ({ sortBy: [{ id: 'createdAt' }] }), []);
|
const initialState = useMemo(() => ({ sortBy: [{ id: 'createdAt' }] }), []);
|
||||||
|
const { uiConfig } = useUiConfig();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
getTableProps,
|
getTableProps,
|
||||||
@ -52,9 +53,16 @@ export const ApiTokenTable = () => {
|
|||||||
useSortBy
|
useSortBy
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useHiddenColumns(
|
||||||
setHiddenColumns(hiddenColumns);
|
setHiddenColumns,
|
||||||
}, [setHiddenColumns, hiddenColumns]);
|
['Icon', 'createdAt'],
|
||||||
|
useMediaQuery(theme.breakpoints.down('md'))
|
||||||
|
);
|
||||||
|
useHiddenColumns(
|
||||||
|
setHiddenColumns,
|
||||||
|
['projects', 'environment'],
|
||||||
|
!uiConfig.flags.E
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageContent
|
<PageContent
|
||||||
@ -124,27 +132,6 @@ export const ApiTokenTable = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const useHiddenColumns = (): string[] => {
|
|
||||||
const { uiConfig } = useUiConfig();
|
|
||||||
const isMediumScreen = useMediaQuery(theme.breakpoints.down('md'));
|
|
||||||
|
|
||||||
return useMemo(() => {
|
|
||||||
const hidden: string[] = [];
|
|
||||||
|
|
||||||
if (!uiConfig.flags.E) {
|
|
||||||
hidden.push('projects');
|
|
||||||
hidden.push('environment');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isMediumScreen) {
|
|
||||||
hidden.push('Icon');
|
|
||||||
hidden.push('createdAt');
|
|
||||||
}
|
|
||||||
|
|
||||||
return hidden;
|
|
||||||
}, [uiConfig, isMediumScreen]);
|
|
||||||
};
|
|
||||||
|
|
||||||
const COLUMNS = [
|
const COLUMNS = [
|
||||||
{
|
{
|
||||||
id: 'Icon',
|
id: 'Icon',
|
||||||
|
@ -1,13 +1,12 @@
|
|||||||
import { useEffect, useMemo, useState, VFC } from 'react';
|
import { useEffect, useMemo, useState, VFC } from 'react';
|
||||||
import {
|
import {
|
||||||
Button,
|
|
||||||
IconButton,
|
IconButton,
|
||||||
styled,
|
styled,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
useMediaQuery,
|
useMediaQuery,
|
||||||
useTheme,
|
useTheme,
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import { useSearchParams } from 'react-router-dom';
|
import { useSearchParams, Link } from 'react-router-dom';
|
||||||
import { SortingRule, useFlexLayout, useSortBy, useTable } from 'react-table';
|
import { SortingRule, useFlexLayout, useSortBy, useTable } from 'react-table';
|
||||||
import { TablePlaceholder, VirtualizedTable } from 'component/common/Table';
|
import { TablePlaceholder, VirtualizedTable } from 'component/common/Table';
|
||||||
import { useGroup } from 'hooks/api/getters/useGroup/useGroup';
|
import { useGroup } from 'hooks/api/getters/useGroup/useGroup';
|
||||||
@ -26,17 +25,17 @@ import { HighlightCell } from 'component/common/Table/cells/HighlightCell/Highli
|
|||||||
import { TimeAgoCell } from 'component/common/Table/cells/TimeAgoCell/TimeAgoCell';
|
import { TimeAgoCell } from 'component/common/Table/cells/TimeAgoCell/TimeAgoCell';
|
||||||
import { GroupUserRoleCell } from 'component/admin/groups/GroupUserRoleCell/GroupUserRoleCell';
|
import { GroupUserRoleCell } from 'component/admin/groups/GroupUserRoleCell/GroupUserRoleCell';
|
||||||
import PermissionIconButton from 'component/common/PermissionIconButton/PermissionIconButton';
|
import PermissionIconButton from 'component/common/PermissionIconButton/PermissionIconButton';
|
||||||
import { Delete, Edit } from '@mui/icons-material';
|
import { Add, Delete, Edit } from '@mui/icons-material';
|
||||||
import { ADMIN } from 'component/providers/AccessProvider/permissions';
|
import { ADMIN } from 'component/providers/AccessProvider/permissions';
|
||||||
import { MainHeader } from 'component/common/MainHeader/MainHeader';
|
import { MainHeader } from 'component/common/MainHeader/MainHeader';
|
||||||
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
||||||
import { RemoveGroup } from 'component/admin/groups/RemoveGroup/RemoveGroup';
|
import { RemoveGroup } from 'component/admin/groups/RemoveGroup/RemoveGroup';
|
||||||
import { Link } from 'react-router-dom';
|
|
||||||
import { ActionCell } from 'component/common/Table/cells/ActionCell/ActionCell';
|
import { ActionCell } from 'component/common/Table/cells/ActionCell/ActionCell';
|
||||||
import { AddGroupUser } from './AddGroupUser/AddGroupUser';
|
import { AddGroupUser } from './AddGroupUser/AddGroupUser';
|
||||||
import { EditGroupUser } from './EditGroupUser/EditGroupUser';
|
import { EditGroupUser } from './EditGroupUser/EditGroupUser';
|
||||||
import { RemoveGroupUser } from './RemoveGroupUser/RemoveGroupUser';
|
import { RemoveGroupUser } from './RemoveGroupUser/RemoveGroupUser';
|
||||||
import { UserAvatar } from 'component/common/UserAvatar/UserAvatar';
|
import { UserAvatar } from 'component/common/UserAvatar/UserAvatar';
|
||||||
|
import ResponsiveButton from 'component/common/ResponsiveButton/ResponsiveButton';
|
||||||
import {
|
import {
|
||||||
UG_EDIT_BTN_ID,
|
UG_EDIT_BTN_ID,
|
||||||
UG_DELETE_BTN_ID,
|
UG_DELETE_BTN_ID,
|
||||||
@ -140,30 +139,36 @@ export const Group: VFC = () => {
|
|||||||
Cell: ({ row: { original: rowUser } }: any) => (
|
Cell: ({ row: { original: rowUser } }: any) => (
|
||||||
<ActionCell>
|
<ActionCell>
|
||||||
<Tooltip title="Edit user" arrow describeChild>
|
<Tooltip title="Edit user" arrow describeChild>
|
||||||
<IconButton
|
<span>
|
||||||
data-testid={`${UG_EDIT_USER_BTN_ID}-${rowUser.id}`}
|
<IconButton
|
||||||
onClick={() => {
|
data-testid={`${UG_EDIT_USER_BTN_ID}-${rowUser.id}`}
|
||||||
setSelectedUser(rowUser);
|
disabled={group?.users.length === 1}
|
||||||
setEditUserOpen(true);
|
onClick={() => {
|
||||||
}}
|
setSelectedUser(rowUser);
|
||||||
>
|
setEditUserOpen(true);
|
||||||
<Edit />
|
}}
|
||||||
</IconButton>
|
>
|
||||||
|
<Edit />
|
||||||
|
</IconButton>
|
||||||
|
</span>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
title="Remove user from group"
|
title="Remove user from group"
|
||||||
arrow
|
arrow
|
||||||
describeChild
|
describeChild
|
||||||
>
|
>
|
||||||
<IconButton
|
<span>
|
||||||
data-testid={`${UG_REMOVE_USER_BTN_ID}-${rowUser.id}`}
|
<IconButton
|
||||||
onClick={() => {
|
data-testid={`${UG_REMOVE_USER_BTN_ID}-${rowUser.id}`}
|
||||||
setSelectedUser(rowUser);
|
disabled={group?.users.length === 1}
|
||||||
setRemoveUserOpen(true);
|
onClick={() => {
|
||||||
}}
|
setSelectedUser(rowUser);
|
||||||
>
|
setRemoveUserOpen(true);
|
||||||
<Delete />
|
}}
|
||||||
</IconButton>
|
>
|
||||||
|
<Delete />
|
||||||
|
</IconButton>
|
||||||
|
</span>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</ActionCell>
|
</ActionCell>
|
||||||
),
|
),
|
||||||
@ -171,7 +176,7 @@ export const Group: VFC = () => {
|
|||||||
disableSortBy: true,
|
disableSortBy: true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
[setSelectedUser, setRemoveUserOpen]
|
[setSelectedUser, setRemoveUserOpen, group?.users.length]
|
||||||
);
|
);
|
||||||
|
|
||||||
const [searchParams, setSearchParams] = useSearchParams();
|
const [searchParams, setSearchParams] = useSearchParams();
|
||||||
@ -306,16 +311,17 @@ export const Group: VFC = () => {
|
|||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Button
|
<ResponsiveButton
|
||||||
data-testid={UG_ADD_USER_BTN_ID}
|
data-testid={UG_ADD_USER_BTN_ID}
|
||||||
variant="contained"
|
|
||||||
color="primary"
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setAddUserOpen(true);
|
setAddUserOpen(true);
|
||||||
}}
|
}}
|
||||||
|
maxWidth="700px"
|
||||||
|
Icon={Add}
|
||||||
|
permission={ADMIN}
|
||||||
>
|
>
|
||||||
Add user
|
Add user
|
||||||
</Button>
|
</ResponsiveButton>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
@ -376,13 +382,13 @@ export const Group: VFC = () => {
|
|||||||
<EditGroupUser
|
<EditGroupUser
|
||||||
open={editUserOpen}
|
open={editUserOpen}
|
||||||
setOpen={setEditUserOpen}
|
setOpen={setEditUserOpen}
|
||||||
user={selectedUser!}
|
user={selectedUser}
|
||||||
group={group!}
|
group={group!}
|
||||||
/>
|
/>
|
||||||
<RemoveGroupUser
|
<RemoveGroupUser
|
||||||
open={removeUserOpen}
|
open={removeUserOpen}
|
||||||
setOpen={setRemoveUserOpen}
|
setOpen={setRemoveUserOpen}
|
||||||
user={selectedUser!}
|
user={selectedUser}
|
||||||
group={group!}
|
group={group!}
|
||||||
/>
|
/>
|
||||||
</PageContent>
|
</PageContent>
|
||||||
|
@ -47,23 +47,22 @@ export const RemoveGroupUser: FC<IRemoveGroupUserProps> = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const userName = user?.name || user?.username || user?.email;
|
||||||
return (
|
return (
|
||||||
<Dialogue
|
<Dialogue
|
||||||
open={open && Boolean(user)}
|
open={open && Boolean(user)}
|
||||||
primaryButtonText="Remove"
|
primaryButtonText="Remove user"
|
||||||
secondaryButtonText="Cancel"
|
secondaryButtonText="Cancel"
|
||||||
onClick={onRemoveClick}
|
onClick={onRemoveClick}
|
||||||
onClose={() => {
|
onClose={() => {
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
}}
|
}}
|
||||||
title="Remove user from group"
|
title="Remove user from group?"
|
||||||
>
|
>
|
||||||
<Typography>
|
<Typography>
|
||||||
Are you sure you wish to remove{' '}
|
Do you really want to remove <strong>{userName}</strong> from{' '}
|
||||||
<strong>{user?.name || user?.username || user?.email}</strong>{' '}
|
<strong>{group.name}</strong>? <strong>{userName}</strong> will
|
||||||
from <strong>{group.name}</strong>? Removing the user from this
|
lose all access rights granted by this group.
|
||||||
group may also remove their access from projects this group is
|
|
||||||
assigned to.
|
|
||||||
</Typography>
|
</Typography>
|
||||||
</Dialogue>
|
</Dialogue>
|
||||||
);
|
);
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { useMemo, VFC } from 'react';
|
import { useMemo, VFC } from 'react';
|
||||||
import { IconButton, Tooltip } from '@mui/material';
|
import { IconButton, Tooltip, useMediaQuery } from '@mui/material';
|
||||||
import { TextCell } from 'component/common/Table/cells/TextCell/TextCell';
|
import { TextCell } from 'component/common/Table/cells/TextCell/TextCell';
|
||||||
import { IGroupUser } from 'interfaces/group';
|
import { IGroupUser } from 'interfaces/group';
|
||||||
import { HighlightCell } from 'component/common/Table/cells/HighlightCell/HighlightCell';
|
import { HighlightCell } from 'component/common/Table/cells/HighlightCell/HighlightCell';
|
||||||
@ -11,6 +11,8 @@ import { VirtualizedTable } from 'component/common/Table';
|
|||||||
import { useFlexLayout, useSortBy, useTable } from 'react-table';
|
import { useFlexLayout, useSortBy, useTable } from 'react-table';
|
||||||
import { sortTypes } from 'utils/sortTypes';
|
import { sortTypes } from 'utils/sortTypes';
|
||||||
import { UserAvatar } from 'component/common/UserAvatar/UserAvatar';
|
import { UserAvatar } from 'component/common/UserAvatar/UserAvatar';
|
||||||
|
import theme from 'themes/theme';
|
||||||
|
import useHiddenColumns from 'hooks/useHiddenColumns';
|
||||||
|
|
||||||
interface IGroupFormUsersTableProps {
|
interface IGroupFormUsersTableProps {
|
||||||
users: IGroupUser[];
|
users: IGroupUser[];
|
||||||
@ -106,7 +108,7 @@ export const GroupFormUsersTable: VFC<IGroupFormUsersTableProps> = ({
|
|||||||
[setUsers]
|
[setUsers]
|
||||||
);
|
);
|
||||||
|
|
||||||
const { headerGroups, rows, prepareRow } = useTable(
|
const { headerGroups, rows, prepareRow, setHiddenColumns } = useTable(
|
||||||
{
|
{
|
||||||
columns: columns as any[],
|
columns: columns as any[],
|
||||||
data: users as any[],
|
data: users as any[],
|
||||||
@ -119,6 +121,12 @@ export const GroupFormUsersTable: VFC<IGroupFormUsersTableProps> = ({
|
|||||||
useFlexLayout
|
useFlexLayout
|
||||||
);
|
);
|
||||||
|
|
||||||
|
useHiddenColumns(
|
||||||
|
setHiddenColumns,
|
||||||
|
['imageUrl', 'name'],
|
||||||
|
useMediaQuery(theme.breakpoints.down('md'))
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ConditionallyRender
|
<ConditionallyRender
|
||||||
condition={rows.length > 0}
|
condition={rows.length > 0}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { useEffect, useMemo, useState, VFC } from 'react';
|
import { useEffect, useMemo, useState, VFC } from 'react';
|
||||||
import { useGroups } from 'hooks/api/getters/useGroups/useGroups';
|
import { useGroups } from 'hooks/api/getters/useGroups/useGroups';
|
||||||
import { Link, useSearchParams } from 'react-router-dom';
|
import { Link, useNavigate, useSearchParams } from 'react-router-dom';
|
||||||
import { IGroup } from 'interfaces/group';
|
import { IGroup } from 'interfaces/group';
|
||||||
import { PageContent } from 'component/common/PageContent/PageContent';
|
import { PageContent } from 'component/common/PageContent/PageContent';
|
||||||
import { PageHeader } from 'component/common/PageHeader/PageHeader';
|
import { PageHeader } from 'component/common/PageHeader/PageHeader';
|
||||||
@ -12,6 +12,9 @@ import { SearchHighlightProvider } from 'component/common/Table/SearchHighlightC
|
|||||||
import { TablePlaceholder } from 'component/common/Table';
|
import { TablePlaceholder } from 'component/common/Table';
|
||||||
import { GroupCard } from './GroupCard/GroupCard';
|
import { GroupCard } from './GroupCard/GroupCard';
|
||||||
import { GroupEmpty } from './GroupEmpty/GroupEmpty';
|
import { GroupEmpty } from './GroupEmpty/GroupEmpty';
|
||||||
|
import ResponsiveButton from 'component/common/ResponsiveButton/ResponsiveButton';
|
||||||
|
import { ADMIN } from 'component/providers/AccessProvider/permissions';
|
||||||
|
import { Add } from '@mui/icons-material';
|
||||||
import { NAVIGATE_TO_CREATE_GROUP } from 'utils/testIds';
|
import { NAVIGATE_TO_CREATE_GROUP } from 'utils/testIds';
|
||||||
|
|
||||||
type PageQueryType = Partial<Record<'search', string>>;
|
type PageQueryType = Partial<Record<'search', string>>;
|
||||||
@ -33,6 +36,7 @@ const groupsSearch = (group: IGroup, searchValue: string) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const GroupsList: VFC = () => {
|
export const GroupsList: VFC = () => {
|
||||||
|
const navigate = useNavigate();
|
||||||
const { groups = [], loading } = useGroups();
|
const { groups = [], loading } = useGroups();
|
||||||
const [searchParams, setSearchParams] = useSearchParams();
|
const [searchParams, setSearchParams] = useSearchParams();
|
||||||
const [searchValue, setSearchValue] = useState(
|
const [searchValue, setSearchValue] = useState(
|
||||||
@ -81,15 +85,17 @@ export const GroupsList: VFC = () => {
|
|||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Button
|
<ResponsiveButton
|
||||||
to="/admin/groups/create-group"
|
onClick={() =>
|
||||||
component={Link}
|
navigate('/admin/groups/create-group')
|
||||||
variant="contained"
|
}
|
||||||
color="primary"
|
maxWidth="700px"
|
||||||
|
Icon={Add}
|
||||||
|
permission={ADMIN}
|
||||||
data-testid={NAVIGATE_TO_CREATE_GROUP}
|
data-testid={NAVIGATE_TO_CREATE_GROUP}
|
||||||
>
|
>
|
||||||
New group
|
New group
|
||||||
</Button>
|
</ResponsiveButton>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
@ -42,18 +42,18 @@ export const RemoveGroup: FC<IRemoveGroupProps> = ({
|
|||||||
return (
|
return (
|
||||||
<Dialogue
|
<Dialogue
|
||||||
open={open}
|
open={open}
|
||||||
primaryButtonText="Remove"
|
primaryButtonText="Delete group"
|
||||||
secondaryButtonText="Cancel"
|
secondaryButtonText="Cancel"
|
||||||
onClick={onRemoveClick}
|
onClick={onRemoveClick}
|
||||||
onClose={() => {
|
onClose={() => {
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
}}
|
}}
|
||||||
title="Delete group"
|
title="Delete group?"
|
||||||
>
|
>
|
||||||
<Typography>
|
<Typography>
|
||||||
Are you sure you wish to delete <strong>{group.name}</strong>?
|
Do you really want to delete <strong>{group.name}</strong>?
|
||||||
If this group is currently assigned to one or more projects then
|
Users who are granted access to projects only via this group
|
||||||
users belonging to this group may lose access to those projects.
|
will lose access to those projects.
|
||||||
</Typography>
|
</Typography>
|
||||||
</Dialogue>
|
</Dialogue>
|
||||||
);
|
);
|
||||||
|
@ -13,7 +13,7 @@ const StyledMainHeader = styled(Paper)(({ theme }) => ({
|
|||||||
|
|
||||||
const StyledTitleHeader = styled('div')(({ theme }) => ({
|
const StyledTitleHeader = styled('div')(({ theme }) => ({
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'flex-start',
|
||||||
justifyContent: 'space-between',
|
justifyContent: 'space-between',
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
@ -0,0 +1,50 @@
|
|||||||
|
import { Dialogue } from 'component/common/Dialogue/Dialogue';
|
||||||
|
import React from 'react';
|
||||||
|
import { formatUnknownError } from 'utils/formatUnknownError';
|
||||||
|
import useProjectApi from 'hooks/api/actions/useProjectApi/useProjectApi';
|
||||||
|
import useProjects from 'hooks/api/getters/useProjects/useProjects';
|
||||||
|
import useToast from 'hooks/useToast';
|
||||||
|
|
||||||
|
interface IDeleteProjectDialogueProps {
|
||||||
|
project: string;
|
||||||
|
open: boolean;
|
||||||
|
onClose?: () => void;
|
||||||
|
onSuccess?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DeleteProjectDialogue = ({
|
||||||
|
open,
|
||||||
|
onClose,
|
||||||
|
project,
|
||||||
|
onSuccess,
|
||||||
|
}: IDeleteProjectDialogueProps) => {
|
||||||
|
const { deleteProject } = useProjectApi();
|
||||||
|
const { refetch: refetchProjectOverview } = useProjects();
|
||||||
|
const { setToastData, setToastApiError } = useToast();
|
||||||
|
|
||||||
|
const onClick = async (e: React.SyntheticEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
try {
|
||||||
|
await deleteProject(project);
|
||||||
|
refetchProjectOverview();
|
||||||
|
setToastData({
|
||||||
|
title: 'Deleted project',
|
||||||
|
type: 'success',
|
||||||
|
text: 'Successfully deleted project',
|
||||||
|
});
|
||||||
|
onSuccess?.();
|
||||||
|
} catch (ex: unknown) {
|
||||||
|
setToastApiError(formatUnknownError(ex));
|
||||||
|
}
|
||||||
|
onClose?.();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialogue
|
||||||
|
open={open}
|
||||||
|
onClick={onClick}
|
||||||
|
onClose={onClose}
|
||||||
|
title="Really delete project"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
@ -18,7 +18,7 @@ export const useStyles = makeStyles()(theme => ({
|
|||||||
marginBottom: '1rem',
|
marginBottom: '1rem',
|
||||||
},
|
},
|
||||||
innerContainer: {
|
innerContainer: {
|
||||||
padding: '1rem 2rem',
|
padding: '1.25rem 2rem',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
},
|
},
|
||||||
|
@ -4,21 +4,44 @@ import useLoading from 'hooks/useLoading';
|
|||||||
import ApiError from 'component/common/ApiError/ApiError';
|
import ApiError from 'component/common/ApiError/ApiError';
|
||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
import { useStyles } from './Project.styles';
|
import { useStyles } from './Project.styles';
|
||||||
import { Tab, Tabs } from '@mui/material';
|
import { styled, Tab, Tabs } from '@mui/material';
|
||||||
import { Edit } from '@mui/icons-material';
|
import { Delete, Edit } from '@mui/icons-material';
|
||||||
import useToast from 'hooks/useToast';
|
import useToast from 'hooks/useToast';
|
||||||
import useQueryParams from 'hooks/useQueryParams';
|
import useQueryParams from 'hooks/useQueryParams';
|
||||||
import { useEffect } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { ProjectAccess } from '../ProjectAccess/ProjectAccess';
|
import { ProjectAccess } from '../ProjectAccess/ProjectAccess';
|
||||||
import ProjectEnvironment from '../ProjectEnvironment/ProjectEnvironment';
|
import ProjectEnvironment from '../ProjectEnvironment/ProjectEnvironment';
|
||||||
import { ProjectFeaturesArchive } from './ProjectFeaturesArchive/ProjectFeaturesArchive';
|
import { ProjectFeaturesArchive } from './ProjectFeaturesArchive/ProjectFeaturesArchive';
|
||||||
import ProjectOverview from './ProjectOverview';
|
import ProjectOverview from './ProjectOverview';
|
||||||
import ProjectHealth from './ProjectHealth/ProjectHealth';
|
import ProjectHealth from './ProjectHealth/ProjectHealth';
|
||||||
import PermissionIconButton from 'component/common/PermissionIconButton/PermissionIconButton';
|
import PermissionIconButton from 'component/common/PermissionIconButton/PermissionIconButton';
|
||||||
import { UPDATE_PROJECT } from 'component/providers/AccessProvider/permissions';
|
import {
|
||||||
|
DELETE_PROJECT,
|
||||||
|
UPDATE_PROJECT,
|
||||||
|
} from 'component/providers/AccessProvider/permissions';
|
||||||
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
||||||
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||||
import { Routes, Route, useLocation } from 'react-router-dom';
|
import { Routes, Route, useLocation } from 'react-router-dom';
|
||||||
|
import { DeleteProjectDialogue } from './DeleteProject/DeleteProjectDialogue';
|
||||||
|
|
||||||
|
const StyledDiv = styled('div')(() => ({
|
||||||
|
display: 'flex',
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledName = styled('div')(({ theme }) => ({
|
||||||
|
overflow: 'hidden',
|
||||||
|
textOverflow: 'ellipsis',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
paddingBottom: theme.spacing(2),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledTitle = styled('span')(({ theme }) => ({
|
||||||
|
fontSize: theme.fontSizes.smallBody,
|
||||||
|
fontWeight: 'normal',
|
||||||
|
}));
|
||||||
|
const StyledText = styled(StyledTitle)(({ theme }) => ({
|
||||||
|
color: theme.palette.grey[800],
|
||||||
|
}));
|
||||||
|
|
||||||
const Project = () => {
|
const Project = () => {
|
||||||
const projectId = useRequiredPathParam('projectId');
|
const projectId = useRequiredPathParam('projectId');
|
||||||
@ -33,6 +56,8 @@ const Project = () => {
|
|||||||
const basePath = `/projects/${projectId}`;
|
const basePath = `/projects/${projectId}`;
|
||||||
const projectName = project?.name || projectId;
|
const projectName = project?.name || projectId;
|
||||||
|
|
||||||
|
const [showDelDialog, setShowDelDialog] = useState(false);
|
||||||
|
|
||||||
const tabs = [
|
const tabs = [
|
||||||
{
|
{
|
||||||
title: 'Overview',
|
title: 'Overview',
|
||||||
@ -85,21 +110,60 @@ const Project = () => {
|
|||||||
<div className={styles.header}>
|
<div className={styles.header}>
|
||||||
<div className={styles.innerContainer}>
|
<div className={styles.innerContainer}>
|
||||||
<h2 className={styles.title}>
|
<h2 className={styles.title}>
|
||||||
<div className={styles.titleText} data-loading>
|
<div>
|
||||||
{projectName}
|
<StyledName data-loading>{projectName}</StyledName>
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={Boolean(project.description)}
|
||||||
|
show={
|
||||||
|
<StyledDiv>
|
||||||
|
<StyledTitle data-loading>
|
||||||
|
Description:
|
||||||
|
</StyledTitle>
|
||||||
|
<StyledText data-loading>
|
||||||
|
{project.description}
|
||||||
|
</StyledText>
|
||||||
|
</StyledDiv>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<StyledDiv>
|
||||||
|
<StyledTitle data-loading>
|
||||||
|
projectId:
|
||||||
|
</StyledTitle>
|
||||||
|
<StyledText data-loading>
|
||||||
|
{projectId}
|
||||||
|
</StyledText>
|
||||||
|
</StyledDiv>
|
||||||
</div>
|
</div>
|
||||||
<PermissionIconButton
|
<StyledDiv>
|
||||||
permission={UPDATE_PROJECT}
|
<PermissionIconButton
|
||||||
projectId={projectId}
|
permission={UPDATE_PROJECT}
|
||||||
sx={{ visibility: isOss() ? 'hidden' : 'visible' }}
|
projectId={projectId}
|
||||||
onClick={() =>
|
sx={{
|
||||||
navigate(`/projects/${projectId}/edit`)
|
visibility: isOss() ? 'hidden' : 'visible',
|
||||||
}
|
}}
|
||||||
tooltipProps={{ title: 'Edit project' }}
|
onClick={() =>
|
||||||
data-loading
|
navigate(`/projects/${projectId}/edit`)
|
||||||
>
|
}
|
||||||
<Edit />
|
tooltipProps={{ title: 'Edit project' }}
|
||||||
</PermissionIconButton>
|
data-loading
|
||||||
|
>
|
||||||
|
<Edit />
|
||||||
|
</PermissionIconButton>
|
||||||
|
<PermissionIconButton
|
||||||
|
permission={DELETE_PROJECT}
|
||||||
|
projectId={projectId}
|
||||||
|
sx={{
|
||||||
|
visibility: isOss() ? 'hidden' : 'visible',
|
||||||
|
}}
|
||||||
|
onClick={() => {
|
||||||
|
setShowDelDialog(true);
|
||||||
|
}}
|
||||||
|
tooltipProps={{ title: 'Delete project' }}
|
||||||
|
data-loading
|
||||||
|
>
|
||||||
|
<Delete />
|
||||||
|
</PermissionIconButton>
|
||||||
|
</StyledDiv>
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
<ConditionallyRender
|
<ConditionallyRender
|
||||||
@ -132,6 +196,16 @@ const Project = () => {
|
|||||||
</Tabs>
|
</Tabs>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<DeleteProjectDialogue
|
||||||
|
project={projectId}
|
||||||
|
open={showDelDialog}
|
||||||
|
onClose={() => {
|
||||||
|
setShowDelDialog(false);
|
||||||
|
}}
|
||||||
|
onSuccess={() => {
|
||||||
|
navigate('/projects');
|
||||||
|
}}
|
||||||
|
/>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="health" element={<ProjectHealth />} />
|
<Route path="health" element={<ProjectHealth />} />
|
||||||
<Route path="access/*" element={<ProjectAccess />} />
|
<Route path="access/*" element={<ProjectAccess />} />
|
||||||
|
@ -47,8 +47,8 @@ const StyledAutocompleteWrapper = styled('div')(({ theme }) => ({
|
|||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const StyledButtonContainer = styled('div')(() => ({
|
const StyledButtonContainer = styled('div')(({ theme }) => ({
|
||||||
marginTop: 'auto',
|
marginTop: theme.spacing(6),
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
justifyContent: 'flex-end',
|
justifyContent: 'flex-end',
|
||||||
}));
|
}));
|
||||||
|
@ -9,7 +9,7 @@ const StyledDescription = styled('div')(({ theme }) => ({
|
|||||||
padding: theme.spacing(3),
|
padding: theme.spacing(3),
|
||||||
backgroundColor: theme.palette.neutral.light,
|
backgroundColor: theme.palette.neutral.light,
|
||||||
color: theme.palette.text.secondary,
|
color: theme.palette.text.secondary,
|
||||||
fontSize: theme.fontSizes.smallerBody,
|
fontSize: theme.fontSizes.smallBody,
|
||||||
borderRadius: theme.shape.borderRadiusMedium,
|
borderRadius: theme.shape.borderRadiusMedium,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { useEffect, useMemo, useState, VFC } from 'react';
|
import { useEffect, useMemo, useState, VFC } from 'react';
|
||||||
import { SortingRule, useFlexLayout, useSortBy, useTable } from 'react-table';
|
import { SortingRule, useFlexLayout, useSortBy, useTable } from 'react-table';
|
||||||
import { VirtualizedTable, TablePlaceholder } from 'component/common/Table';
|
import { VirtualizedTable, TablePlaceholder } from 'component/common/Table';
|
||||||
import { Button, useMediaQuery, useTheme } from '@mui/material';
|
import { styled, useMediaQuery, useTheme } from '@mui/material';
|
||||||
import { Delete, Edit } from '@mui/icons-material';
|
import { Add, Delete, Edit } from '@mui/icons-material';
|
||||||
import { sortTypes } from 'utils/sortTypes';
|
import { sortTypes } from 'utils/sortTypes';
|
||||||
import useProjectAccess, {
|
import useProjectAccess, {
|
||||||
ENTITY_TYPE,
|
ENTITY_TYPE,
|
||||||
@ -38,9 +38,11 @@ import { IUser } from 'interfaces/user';
|
|||||||
import { IGroup } from 'interfaces/group';
|
import { IGroup } from 'interfaces/group';
|
||||||
import { LinkCell } from 'component/common/Table/cells/LinkCell/LinkCell';
|
import { LinkCell } from 'component/common/Table/cells/LinkCell/LinkCell';
|
||||||
import { UserAvatar } from 'component/common/UserAvatar/UserAvatar';
|
import { UserAvatar } from 'component/common/UserAvatar/UserAvatar';
|
||||||
|
import ResponsiveButton from 'component/common/ResponsiveButton/ResponsiveButton';
|
||||||
import { ProjectAccessCreate } from 'component/project/ProjectAccess/ProjectAccessCreate/ProjectAccessCreate';
|
import { ProjectAccessCreate } from 'component/project/ProjectAccess/ProjectAccessCreate/ProjectAccessCreate';
|
||||||
import { ProjectAccessEditUser } from 'component/project/ProjectAccess/ProjectAccessEditUser/ProjectAccessEditUser';
|
import { ProjectAccessEditUser } from 'component/project/ProjectAccess/ProjectAccessEditUser/ProjectAccessEditUser';
|
||||||
import { ProjectAccessEditGroup } from 'component/project/ProjectAccess/ProjectAccessEditGroup/ProjectAccessEditGroup';
|
import { ProjectAccessEditGroup } from 'component/project/ProjectAccess/ProjectAccessEditGroup/ProjectAccessEditGroup';
|
||||||
|
import useHiddenColumns from 'hooks/useHiddenColumns';
|
||||||
|
|
||||||
export type PageQueryType = Partial<
|
export type PageQueryType = Partial<
|
||||||
Record<'sort' | 'order' | 'search', string>
|
Record<'sort' | 'order' | 'search', string>
|
||||||
@ -53,6 +55,20 @@ const { value: storedParams, setValue: setStoredParams } = createLocalStorage(
|
|||||||
defaultSort
|
defaultSort
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const StyledUserAvatars = styled('div')(({ theme }) => ({
|
||||||
|
display: 'inline-flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
marginLeft: theme.spacing(1),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledEmptyAvatar = styled(UserAvatar)(({ theme }) => ({
|
||||||
|
marginRight: theme.spacing(-3.5),
|
||||||
|
}));
|
||||||
|
const StyledGroupAvatar = styled(UserAvatar)(({ theme }) => ({
|
||||||
|
outline: `${theme.spacing(0.25)} solid ${theme.palette.background.paper}`,
|
||||||
|
}));
|
||||||
|
|
||||||
export const ProjectAccessTable: VFC = () => {
|
export const ProjectAccessTable: VFC = () => {
|
||||||
const projectId = useRequiredPathParam('projectId');
|
const projectId = useRequiredPathParam('projectId');
|
||||||
|
|
||||||
@ -77,11 +93,15 @@ export const ProjectAccessTable: VFC = () => {
|
|||||||
Header: 'Avatar',
|
Header: 'Avatar',
|
||||||
accessor: 'imageUrl',
|
accessor: 'imageUrl',
|
||||||
Cell: ({ row: { original: row } }: any) => (
|
Cell: ({ row: { original: row } }: any) => (
|
||||||
<TextCell>
|
<StyledUserAvatars>
|
||||||
<UserAvatar user={row.entity}>
|
<ConditionallyRender
|
||||||
|
condition={row.type === ENTITY_TYPE.GROUP}
|
||||||
|
show={<StyledEmptyAvatar />}
|
||||||
|
/>
|
||||||
|
<StyledGroupAvatar user={row.entity}>
|
||||||
{row.entity.users?.length}
|
{row.entity.users?.length}
|
||||||
</UserAvatar>
|
</StyledGroupAvatar>
|
||||||
</TextCell>
|
</StyledUserAvatars>
|
||||||
),
|
),
|
||||||
maxWidth: 85,
|
maxWidth: 85,
|
||||||
disableSortBy: true,
|
disableSortBy: true,
|
||||||
@ -124,6 +144,7 @@ export const ProjectAccessTable: VFC = () => {
|
|||||||
searchable: true,
|
searchable: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
id: 'role',
|
||||||
Header: 'Role',
|
Header: 'Role',
|
||||||
accessor: (row: IProjectAccess) =>
|
accessor: (row: IProjectAccess) =>
|
||||||
access?.roles.find(({ id }) => id === row.entity.roleId)
|
access?.roles.find(({ id }) => id === row.entity.roleId)
|
||||||
@ -145,6 +166,7 @@ export const ProjectAccessTable: VFC = () => {
|
|||||||
maxWidth: 150,
|
maxWidth: 150,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
id: 'lastLogin',
|
||||||
Header: 'Last login',
|
Header: 'Last login',
|
||||||
accessor: (row: IProjectAccess) => {
|
accessor: (row: IProjectAccess) => {
|
||||||
if (row.type === ENTITY_TYPE.USER) {
|
if (row.type === ENTITY_TYPE.USER) {
|
||||||
@ -240,6 +262,7 @@ export const ProjectAccessTable: VFC = () => {
|
|||||||
headerGroups,
|
headerGroups,
|
||||||
rows,
|
rows,
|
||||||
prepareRow,
|
prepareRow,
|
||||||
|
setHiddenColumns,
|
||||||
state: { sortBy },
|
state: { sortBy },
|
||||||
} = useTable(
|
} = useTable(
|
||||||
{
|
{
|
||||||
@ -258,6 +281,12 @@ export const ProjectAccessTable: VFC = () => {
|
|||||||
useFlexLayout
|
useFlexLayout
|
||||||
);
|
);
|
||||||
|
|
||||||
|
useHiddenColumns(
|
||||||
|
setHiddenColumns,
|
||||||
|
['imageUrl', 'username', 'role', 'added', 'lastLogin'],
|
||||||
|
isSmallScreen
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const tableState: PageQueryType = {};
|
const tableState: PageQueryType = {};
|
||||||
tableState.sort = sortBy[0].id;
|
tableState.sort = sortBy[0].id;
|
||||||
@ -332,14 +361,15 @@ export const ProjectAccessTable: VFC = () => {
|
|||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Button
|
<ResponsiveButton
|
||||||
component={Link}
|
onClick={() => navigate('create')}
|
||||||
to={`create`}
|
maxWidth="700px"
|
||||||
variant="contained"
|
Icon={Add}
|
||||||
color="primary"
|
permission={UPDATE_PROJECT}
|
||||||
|
projectId={projectId}
|
||||||
>
|
>
|
||||||
Assign {entityType}
|
Assign {entityType}
|
||||||
</Button>
|
</ResponsiveButton>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
@ -20,17 +20,22 @@ import { IGroup, IGroupUser } from 'interfaces/group';
|
|||||||
import { VFC, useState } from 'react';
|
import { VFC, useState } from 'react';
|
||||||
import { SortingRule, useFlexLayout, useSortBy, useTable } from 'react-table';
|
import { SortingRule, useFlexLayout, useSortBy, useTable } from 'react-table';
|
||||||
import { sortTypes } from 'utils/sortTypes';
|
import { sortTypes } from 'utils/sortTypes';
|
||||||
|
import useHiddenColumns from 'hooks/useHiddenColumns';
|
||||||
|
|
||||||
const StyledPageContent = styled(PageContent)(({ theme }) => ({
|
const StyledPageContent = styled(PageContent)(({ theme }) => ({
|
||||||
height: '100vh',
|
height: '100vh',
|
||||||
overflow: 'auto',
|
overflow: 'auto',
|
||||||
padding: theme.spacing(7.5, 6),
|
padding: theme.spacing(7.5, 6),
|
||||||
|
[theme.breakpoints.down('md')]: {
|
||||||
|
padding: theme.spacing(4, 2),
|
||||||
|
},
|
||||||
'& .header': {
|
'& .header': {
|
||||||
padding: theme.spacing(0, 0, 2, 0),
|
padding: theme.spacing(0, 0, 2, 0),
|
||||||
},
|
},
|
||||||
'& .body': {
|
'& .body': {
|
||||||
padding: theme.spacing(3, 0, 0, 0),
|
padding: theme.spacing(3, 0, 0, 0),
|
||||||
},
|
},
|
||||||
|
borderRadius: `${theme.spacing(1.5, 0, 0, 1.5)} !important`,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const StyledTitle = styled('div')(({ theme }) => ({
|
const StyledTitle = styled('div')(({ theme }) => ({
|
||||||
@ -38,7 +43,7 @@ const StyledTitle = styled('div')(({ theme }) => ({
|
|||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
'& > span': {
|
'& > span': {
|
||||||
color: theme.palette.text.secondary,
|
color: theme.palette.text.secondary,
|
||||||
fontSize: theme.fontSizes.smallBody,
|
fontSize: theme.fontSizes.bodySize,
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@ -80,6 +85,7 @@ const columns = [
|
|||||||
filterName: 'type',
|
filterName: 'type',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
id: 'joined',
|
||||||
Header: 'Joined',
|
Header: 'Joined',
|
||||||
accessor: 'joinedAt',
|
accessor: 'joinedAt',
|
||||||
Cell: DateCell,
|
Cell: DateCell,
|
||||||
@ -87,6 +93,7 @@ const columns = [
|
|||||||
maxWidth: 150,
|
maxWidth: 150,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
id: 'lastLogin',
|
||||||
Header: 'Last login',
|
Header: 'Last login',
|
||||||
accessor: (row: IGroupUser) => row.seenAt || '',
|
accessor: (row: IGroupUser) => row.seenAt || '',
|
||||||
Cell: ({ row: { original: user } }: any) => (
|
Cell: ({ row: { original: user } }: any) => (
|
||||||
@ -135,7 +142,7 @@ export const ProjectGroupView: VFC<IProjectGroupViewProps> = ({
|
|||||||
group?.users ?? []
|
group?.users ?? []
|
||||||
);
|
);
|
||||||
|
|
||||||
const { headerGroups, rows, prepareRow } = useTable(
|
const { headerGroups, rows, prepareRow, setHiddenColumns } = useTable(
|
||||||
{
|
{
|
||||||
columns: columns as any[],
|
columns: columns as any[],
|
||||||
data,
|
data,
|
||||||
@ -149,6 +156,12 @@ export const ProjectGroupView: VFC<IProjectGroupViewProps> = ({
|
|||||||
useFlexLayout
|
useFlexLayout
|
||||||
);
|
);
|
||||||
|
|
||||||
|
useHiddenColumns(
|
||||||
|
setHiddenColumns,
|
||||||
|
['imageUrl', 'name', 'joined', 'lastLogin'],
|
||||||
|
useMediaQuery(theme.breakpoints.down('md'))
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SidebarModal
|
<SidebarModal
|
||||||
open={open}
|
open={open}
|
||||||
|
@ -32,6 +32,7 @@ export const useStyles = makeStyles()(theme => ({
|
|||||||
boxOrient: 'vertical',
|
boxOrient: 'vertical',
|
||||||
textOverflow: 'ellipsis',
|
textOverflow: 'ellipsis',
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
|
alignItems: 'flex-start',
|
||||||
},
|
},
|
||||||
|
|
||||||
projectIcon: {
|
projectIcon: {
|
||||||
|
@ -4,21 +4,17 @@ import MoreVertIcon from '@mui/icons-material/MoreVert';
|
|||||||
import { ReactComponent as ProjectIcon } from 'assets/icons/projectIcon.svg';
|
import { ReactComponent as ProjectIcon } from 'assets/icons/projectIcon.svg';
|
||||||
import React, { useState, SyntheticEvent, useContext } from 'react';
|
import React, { useState, SyntheticEvent, useContext } from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { Dialogue } from 'component/common/Dialogue/Dialogue';
|
|
||||||
import useProjectApi from 'hooks/api/actions/useProjectApi/useProjectApi';
|
|
||||||
import useProjects from 'hooks/api/getters/useProjects/useProjects';
|
|
||||||
import { Delete, Edit } from '@mui/icons-material';
|
import { Delete, Edit } from '@mui/icons-material';
|
||||||
import { getProjectEditPath } from 'utils/routePathHelpers';
|
import { getProjectEditPath } from 'utils/routePathHelpers';
|
||||||
import PermissionIconButton from 'component/common/PermissionIconButton/PermissionIconButton';
|
import PermissionIconButton from 'component/common/PermissionIconButton/PermissionIconButton';
|
||||||
import useToast from 'hooks/useToast';
|
|
||||||
import {
|
import {
|
||||||
UPDATE_PROJECT,
|
UPDATE_PROJECT,
|
||||||
DELETE_PROJECT,
|
DELETE_PROJECT,
|
||||||
} from 'component/providers/AccessProvider/permissions';
|
} from 'component/providers/AccessProvider/permissions';
|
||||||
import { formatUnknownError } from 'utils/formatUnknownError';
|
|
||||||
import AccessContext from 'contexts/AccessContext';
|
import AccessContext from 'contexts/AccessContext';
|
||||||
import { DEFAULT_PROJECT_ID } from 'hooks/api/getters/useDefaultProject/useDefaultProjectId';
|
import { DEFAULT_PROJECT_ID } from 'hooks/api/getters/useDefaultProject/useDefaultProjectId';
|
||||||
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||||
|
import { DeleteProjectDialogue } from '../Project/DeleteProject/DeleteProjectDialogue';
|
||||||
|
|
||||||
interface IProjectCardProps {
|
interface IProjectCardProps {
|
||||||
name: string;
|
name: string;
|
||||||
@ -40,36 +36,15 @@ export const ProjectCard = ({
|
|||||||
const { classes } = useStyles();
|
const { classes } = useStyles();
|
||||||
const { hasAccess } = useContext(AccessContext);
|
const { hasAccess } = useContext(AccessContext);
|
||||||
const { isOss } = useUiConfig();
|
const { isOss } = useUiConfig();
|
||||||
const { refetch: refetchProjectOverview } = useProjects();
|
|
||||||
const [anchorEl, setAnchorEl] = useState(null);
|
const [anchorEl, setAnchorEl] = useState(null);
|
||||||
const [showDelDialog, setShowDelDialog] = useState(false);
|
const [showDelDialog, setShowDelDialog] = useState(false);
|
||||||
const { deleteProject } = useProjectApi();
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { setToastData, setToastApiError } = useToast();
|
|
||||||
|
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
const handleClick = e => {
|
const handleClick = e => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
setAnchorEl(e.currentTarget);
|
setAnchorEl(e.currentTarget);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onRemoveProject = async (e: React.SyntheticEvent) => {
|
|
||||||
e.preventDefault();
|
|
||||||
try {
|
|
||||||
await deleteProject(id);
|
|
||||||
refetchProjectOverview();
|
|
||||||
setToastData({
|
|
||||||
title: 'Deleted project',
|
|
||||||
type: 'success',
|
|
||||||
text: 'Successfully deleted project',
|
|
||||||
});
|
|
||||||
} catch (e: unknown) {
|
|
||||||
setToastApiError(formatUnknownError(e));
|
|
||||||
}
|
|
||||||
setShowDelDialog(false);
|
|
||||||
setAnchorEl(null);
|
|
||||||
};
|
|
||||||
|
|
||||||
const canDeleteProject =
|
const canDeleteProject =
|
||||||
hasAccess(DELETE_PROJECT, id) && id !== DEFAULT_PROJECT_ID;
|
hasAccess(DELETE_PROJECT, id) && id !== DEFAULT_PROJECT_ID;
|
||||||
|
|
||||||
@ -152,15 +127,13 @@ export const ProjectCard = ({
|
|||||||
<p data-loading>members</p>
|
<p data-loading>members</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Dialogue
|
<DeleteProjectDialogue
|
||||||
|
project={id}
|
||||||
open={showDelDialog}
|
open={showDelDialog}
|
||||||
onClick={onRemoveProject}
|
onClose={() => {
|
||||||
onClose={event => {
|
|
||||||
event.preventDefault();
|
|
||||||
setAnchorEl(null);
|
setAnchorEl(null);
|
||||||
setShowDelDialog(false);
|
setShowDelDialog(false);
|
||||||
}}
|
}}
|
||||||
title="Really delete project"
|
|
||||||
/>
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
|
15
frontend/src/hooks/useHiddenColumns.ts
Normal file
15
frontend/src/hooks/useHiddenColumns.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { useEffect } from 'react';
|
||||||
|
import { IdType } from 'react-table';
|
||||||
|
|
||||||
|
const useHiddenColumns = (
|
||||||
|
setHiddenColumns: <D>(param: Array<IdType<D>>) => void,
|
||||||
|
hiddenColumns: string[],
|
||||||
|
condition: boolean
|
||||||
|
) => {
|
||||||
|
useEffect(() => {
|
||||||
|
const hidden = condition ? hiddenColumns : [];
|
||||||
|
setHiddenColumns(hidden);
|
||||||
|
}, [setHiddenColumns, condition]);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useHiddenColumns;
|
Loading…
Reference in New Issue
Block a user