diff --git a/frontend/src/component/admin/apiToken/ApiTokenTable/ApiTokenTable.tsx b/frontend/src/component/admin/apiToken/ApiTokenTable/ApiTokenTable.tsx index 273ca43376..013ea29f3a 100644 --- a/frontend/src/component/admin/apiToken/ApiTokenTable/ApiTokenTable.tsx +++ b/frontend/src/component/admin/apiToken/ApiTokenTable/ApiTokenTable.tsx @@ -18,18 +18,19 @@ import { CopyApiTokenButton } from 'component/admin/apiToken/CopyApiTokenButton/ import { RemoveApiTokenButton } from 'component/admin/apiToken/RemoveApiTokenButton/RemoveApiTokenButton'; import { DateCell } from 'component/common/Table/cells/DateCell/DateCell'; import { sortTypes } from 'utils/sortTypes'; -import { useEffect, useMemo } from 'react'; +import { useMemo } from 'react'; import theme from 'themes/theme'; import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; import { ProjectsList } from 'component/admin/apiToken/ProjectsList/ProjectsList'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { HighlightCell } from 'component/common/Table/cells/HighlightCell/HighlightCell'; import { Search } from 'component/common/Search/Search'; +import useHiddenColumns from 'hooks/useHiddenColumns'; export const ApiTokenTable = () => { const { tokens, loading } = useApiTokens(); - const hiddenColumns = useHiddenColumns(); const initialState = useMemo(() => ({ sortBy: [{ id: 'createdAt' }] }), []); + const { uiConfig } = useUiConfig(); const { getTableProps, @@ -52,9 +53,16 @@ export const ApiTokenTable = () => { useSortBy ); - useEffect(() => { - setHiddenColumns(hiddenColumns); - }, [setHiddenColumns, hiddenColumns]); + useHiddenColumns( + setHiddenColumns, + ['Icon', 'createdAt'], + useMediaQuery(theme.breakpoints.down('md')) + ); + useHiddenColumns( + setHiddenColumns, + ['projects', 'environment'], + !uiConfig.flags.E + ); return ( { ); }; -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 = [ { id: 'Icon', diff --git a/frontend/src/component/admin/groups/Group/Group.tsx b/frontend/src/component/admin/groups/Group/Group.tsx index 6a40d812ca..8ded2867ec 100644 --- a/frontend/src/component/admin/groups/Group/Group.tsx +++ b/frontend/src/component/admin/groups/Group/Group.tsx @@ -1,13 +1,12 @@ import { useEffect, useMemo, useState, VFC } from 'react'; import { - Button, IconButton, styled, Tooltip, useMediaQuery, useTheme, } 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 { TablePlaceholder, VirtualizedTable } from 'component/common/Table'; 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 { GroupUserRoleCell } from 'component/admin/groups/GroupUserRoleCell/GroupUserRoleCell'; 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 { MainHeader } from 'component/common/MainHeader/MainHeader'; import { useRequiredPathParam } from 'hooks/useRequiredPathParam'; import { RemoveGroup } from 'component/admin/groups/RemoveGroup/RemoveGroup'; -import { Link } from 'react-router-dom'; import { ActionCell } from 'component/common/Table/cells/ActionCell/ActionCell'; import { AddGroupUser } from './AddGroupUser/AddGroupUser'; import { EditGroupUser } from './EditGroupUser/EditGroupUser'; import { RemoveGroupUser } from './RemoveGroupUser/RemoveGroupUser'; import { UserAvatar } from 'component/common/UserAvatar/UserAvatar'; +import ResponsiveButton from 'component/common/ResponsiveButton/ResponsiveButton'; import { UG_EDIT_BTN_ID, UG_DELETE_BTN_ID, @@ -140,30 +139,36 @@ export const Group: VFC = () => { Cell: ({ row: { original: rowUser } }: any) => ( - { - setSelectedUser(rowUser); - setEditUserOpen(true); - }} - > - - + + { + setSelectedUser(rowUser); + setEditUserOpen(true); + }} + > + + + - { - setSelectedUser(rowUser); - setRemoveUserOpen(true); - }} - > - - + + { + setSelectedUser(rowUser); + setRemoveUserOpen(true); + }} + > + + + ), @@ -171,7 +176,7 @@ export const Group: VFC = () => { disableSortBy: true, }, ], - [setSelectedUser, setRemoveUserOpen] + [setSelectedUser, setRemoveUserOpen, group?.users.length] ); const [searchParams, setSearchParams] = useSearchParams(); @@ -306,16 +311,17 @@ export const Group: VFC = () => { } /> - + } > @@ -376,13 +382,13 @@ export const Group: VFC = () => { diff --git a/frontend/src/component/admin/groups/GroupForm/GroupFormUsersTable/GroupFormUsersTable.tsx b/frontend/src/component/admin/groups/GroupForm/GroupFormUsersTable/GroupFormUsersTable.tsx index b25d8cf80c..c9193a329d 100644 --- a/frontend/src/component/admin/groups/GroupForm/GroupFormUsersTable/GroupFormUsersTable.tsx +++ b/frontend/src/component/admin/groups/GroupForm/GroupFormUsersTable/GroupFormUsersTable.tsx @@ -1,5 +1,5 @@ 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 { IGroupUser } from 'interfaces/group'; 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 { sortTypes } from 'utils/sortTypes'; import { UserAvatar } from 'component/common/UserAvatar/UserAvatar'; +import theme from 'themes/theme'; +import useHiddenColumns from 'hooks/useHiddenColumns'; interface IGroupFormUsersTableProps { users: IGroupUser[]; @@ -106,7 +108,7 @@ export const GroupFormUsersTable: VFC = ({ [setUsers] ); - const { headerGroups, rows, prepareRow } = useTable( + const { headerGroups, rows, prepareRow, setHiddenColumns } = useTable( { columns: columns as any[], data: users as any[], @@ -119,6 +121,12 @@ export const GroupFormUsersTable: VFC = ({ useFlexLayout ); + useHiddenColumns( + setHiddenColumns, + ['imageUrl', 'name'], + useMediaQuery(theme.breakpoints.down('md')) + ); + return ( 0} diff --git a/frontend/src/component/admin/groups/GroupsList/GroupsList.tsx b/frontend/src/component/admin/groups/GroupsList/GroupsList.tsx index 7cc52ddd9c..8c4b063bff 100644 --- a/frontend/src/component/admin/groups/GroupsList/GroupsList.tsx +++ b/frontend/src/component/admin/groups/GroupsList/GroupsList.tsx @@ -1,6 +1,6 @@ import { useEffect, useMemo, useState, VFC } from 'react'; 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 { PageContent } from 'component/common/PageContent/PageContent'; 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 { GroupCard } from './GroupCard/GroupCard'; 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'; type PageQueryType = Partial>; @@ -33,6 +36,7 @@ const groupsSearch = (group: IGroup, searchValue: string) => { }; export const GroupsList: VFC = () => { + const navigate = useNavigate(); const { groups = [], loading } = useGroups(); const [searchParams, setSearchParams] = useSearchParams(); const [searchValue, setSearchValue] = useState( @@ -81,15 +85,17 @@ export const GroupsList: VFC = () => { } /> - + } > diff --git a/frontend/src/component/common/MainHeader/MainHeader.tsx b/frontend/src/component/common/MainHeader/MainHeader.tsx index 2b46d0237b..9c5464bfdb 100644 --- a/frontend/src/component/common/MainHeader/MainHeader.tsx +++ b/frontend/src/component/common/MainHeader/MainHeader.tsx @@ -13,7 +13,7 @@ const StyledMainHeader = styled(Paper)(({ theme }) => ({ const StyledTitleHeader = styled('div')(({ theme }) => ({ display: 'flex', - alignItems: 'center', + alignItems: 'flex-start', justifyContent: 'space-between', })); diff --git a/frontend/src/component/project/ProjectAccess/ProjectAccessAssign/ProjectAccessAssign.tsx b/frontend/src/component/project/ProjectAccess/ProjectAccessAssign/ProjectAccessAssign.tsx index 3cdb95adf3..ddb8d64ce5 100644 --- a/frontend/src/component/project/ProjectAccess/ProjectAccessAssign/ProjectAccessAssign.tsx +++ b/frontend/src/component/project/ProjectAccess/ProjectAccessAssign/ProjectAccessAssign.tsx @@ -47,8 +47,8 @@ const StyledAutocompleteWrapper = styled('div')(({ theme }) => ({ }, })); -const StyledButtonContainer = styled('div')(() => ({ - marginTop: 'auto', +const StyledButtonContainer = styled('div')(({ theme }) => ({ + marginTop: theme.spacing(6), display: 'flex', justifyContent: 'flex-end', })); diff --git a/frontend/src/component/project/ProjectAccess/ProjectAccessAssign/ProjectRoleDescription/ProjectRoleDescription.tsx b/frontend/src/component/project/ProjectAccess/ProjectAccessAssign/ProjectRoleDescription/ProjectRoleDescription.tsx index f9d2276693..75c9090270 100644 --- a/frontend/src/component/project/ProjectAccess/ProjectAccessAssign/ProjectRoleDescription/ProjectRoleDescription.tsx +++ b/frontend/src/component/project/ProjectAccess/ProjectAccessAssign/ProjectRoleDescription/ProjectRoleDescription.tsx @@ -9,7 +9,7 @@ const StyledDescription = styled('div')(({ theme }) => ({ padding: theme.spacing(3), backgroundColor: theme.palette.neutral.light, color: theme.palette.text.secondary, - fontSize: theme.fontSizes.smallerBody, + fontSize: theme.fontSizes.smallBody, borderRadius: theme.shape.borderRadiusMedium, })); diff --git a/frontend/src/component/project/ProjectAccess/ProjectAccessTable/ProjectAccessTable.tsx b/frontend/src/component/project/ProjectAccess/ProjectAccessTable/ProjectAccessTable.tsx index 01ddcb5828..29754c44d8 100644 --- a/frontend/src/component/project/ProjectAccess/ProjectAccessTable/ProjectAccessTable.tsx +++ b/frontend/src/component/project/ProjectAccess/ProjectAccessTable/ProjectAccessTable.tsx @@ -1,8 +1,8 @@ import { useEffect, useMemo, useState, VFC } from 'react'; import { SortingRule, useFlexLayout, useSortBy, useTable } from 'react-table'; import { VirtualizedTable, TablePlaceholder } from 'component/common/Table'; -import { Button, useMediaQuery, useTheme } from '@mui/material'; -import { Delete, Edit } from '@mui/icons-material'; +import { styled, useMediaQuery, useTheme } from '@mui/material'; +import { Add, Delete, Edit } from '@mui/icons-material'; import { sortTypes } from 'utils/sortTypes'; import useProjectAccess, { ENTITY_TYPE, @@ -38,9 +38,11 @@ import { IUser } from 'interfaces/user'; import { IGroup } from 'interfaces/group'; import { LinkCell } from 'component/common/Table/cells/LinkCell/LinkCell'; import { UserAvatar } from 'component/common/UserAvatar/UserAvatar'; +import ResponsiveButton from 'component/common/ResponsiveButton/ResponsiveButton'; import { ProjectAccessCreate } from 'component/project/ProjectAccess/ProjectAccessCreate/ProjectAccessCreate'; import { ProjectAccessEditUser } from 'component/project/ProjectAccess/ProjectAccessEditUser/ProjectAccessEditUser'; import { ProjectAccessEditGroup } from 'component/project/ProjectAccess/ProjectAccessEditGroup/ProjectAccessEditGroup'; +import useHiddenColumns from 'hooks/useHiddenColumns'; export type PageQueryType = Partial< Record<'sort' | 'order' | 'search', string> @@ -53,6 +55,20 @@ const { value: storedParams, setValue: setStoredParams } = createLocalStorage( 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 = () => { const projectId = useRequiredPathParam('projectId'); @@ -77,11 +93,15 @@ export const ProjectAccessTable: VFC = () => { Header: 'Avatar', accessor: 'imageUrl', Cell: ({ row: { original: row } }: any) => ( - - + + } + /> + {row.entity.users?.length} - - + + ), maxWidth: 85, disableSortBy: true, @@ -124,6 +144,7 @@ export const ProjectAccessTable: VFC = () => { searchable: true, }, { + id: 'role', Header: 'Role', accessor: (row: IProjectAccess) => access?.roles.find(({ id }) => id === row.entity.roleId) @@ -145,6 +166,7 @@ export const ProjectAccessTable: VFC = () => { maxWidth: 150, }, { + id: 'lastLogin', Header: 'Last login', accessor: (row: IProjectAccess) => { if (row.type === ENTITY_TYPE.USER) { @@ -240,6 +262,7 @@ export const ProjectAccessTable: VFC = () => { headerGroups, rows, prepareRow, + setHiddenColumns, state: { sortBy }, } = useTable( { @@ -258,6 +281,12 @@ export const ProjectAccessTable: VFC = () => { useFlexLayout ); + useHiddenColumns( + setHiddenColumns, + ['imageUrl', 'username', 'role', 'added', 'lastLogin'], + isSmallScreen + ); + useEffect(() => { const tableState: PageQueryType = {}; tableState.sort = sortBy[0].id; @@ -332,14 +361,15 @@ export const ProjectAccessTable: VFC = () => { } /> - + } > diff --git a/frontend/src/component/project/ProjectAccess/ProjectGroupView/ProjectGroupView.tsx b/frontend/src/component/project/ProjectAccess/ProjectGroupView/ProjectGroupView.tsx index 39a2c954f2..57a54187fc 100644 --- a/frontend/src/component/project/ProjectAccess/ProjectGroupView/ProjectGroupView.tsx +++ b/frontend/src/component/project/ProjectAccess/ProjectGroupView/ProjectGroupView.tsx @@ -20,17 +20,22 @@ import { IGroup, IGroupUser } from 'interfaces/group'; import { VFC, useState } from 'react'; import { SortingRule, useFlexLayout, useSortBy, useTable } from 'react-table'; import { sortTypes } from 'utils/sortTypes'; +import useHiddenColumns from 'hooks/useHiddenColumns'; const StyledPageContent = styled(PageContent)(({ theme }) => ({ height: '100vh', overflow: 'auto', padding: theme.spacing(7.5, 6), + [theme.breakpoints.down('md')]: { + padding: theme.spacing(4, 2), + }, '& .header': { padding: theme.spacing(0, 0, 2, 0), }, '& .body': { padding: theme.spacing(3, 0, 0, 0), }, + borderRadius: `${theme.spacing(1.5, 0, 0, 1.5)} !important`, })); const StyledTitle = styled('div')(({ theme }) => ({ @@ -38,7 +43,7 @@ const StyledTitle = styled('div')(({ theme }) => ({ flexDirection: 'column', '& > span': { color: theme.palette.text.secondary, - fontSize: theme.fontSizes.smallBody, + fontSize: theme.fontSizes.bodySize, }, })); @@ -80,6 +85,7 @@ const columns = [ filterName: 'type', }, { + id: 'joined', Header: 'Joined', accessor: 'joinedAt', Cell: DateCell, @@ -87,6 +93,7 @@ const columns = [ maxWidth: 150, }, { + id: 'lastLogin', Header: 'Last login', accessor: (row: IGroupUser) => row.seenAt || '', Cell: ({ row: { original: user } }: any) => ( @@ -135,7 +142,7 @@ export const ProjectGroupView: VFC = ({ group?.users ?? [] ); - const { headerGroups, rows, prepareRow } = useTable( + const { headerGroups, rows, prepareRow, setHiddenColumns } = useTable( { columns: columns as any[], data, @@ -149,6 +156,12 @@ export const ProjectGroupView: VFC = ({ useFlexLayout ); + useHiddenColumns( + setHiddenColumns, + ['imageUrl', 'name', 'joined', 'lastLogin'], + useMediaQuery(theme.breakpoints.down('md')) + ); + return ( (param: Array>) => void, + hiddenColumns: string[], + condition: boolean +) => { + useEffect(() => { + const hidden = condition ? hiddenColumns : []; + setHiddenColumns(hidden); + }, [setHiddenColumns, condition]); +}; + +export default useHiddenColumns;