Skip to content

Commit

Permalink
feat: Manage user's groups (#4312)
Browse files Browse the repository at this point in the history
  • Loading branch information
kyle-ssg authored Nov 6, 2024
1 parent 57ad28c commit 89b153c
Show file tree
Hide file tree
Showing 4 changed files with 188 additions and 16 deletions.
12 changes: 9 additions & 3 deletions frontend/web/components/ActionButton.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,23 @@
import { FC } from 'react'
import classNames from 'classnames'
import Icon from './Icon'
import Button from './base/forms/Button'
import Button, { ButtonType } from './base/forms/Button'

type ActionButtonType = {
onClick: () => void
'data-test'?: string
size?: ButtonType['size']
}

const ActionButton: FC<ActionButtonType> = ({ onClick, ...rest }) => {
const ActionButton: FC<ActionButtonType> = ({
onClick,
size = 'xSmall',
...rest
}) => {
return (
<Button
className={classNames('btn btn-with-icon btn-xs')}
size={size}
className={classNames('btn btn-with-icon')}
data-test={rest['data-test']}
onClick={(e) => {
e.stopPropagation()
Expand Down
4 changes: 2 additions & 2 deletions frontend/web/components/UserAction.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ export const FeatureAction: FC<FeatureActionProps> = ({
return (
<div>
<div ref={btnRef}>
<ActionButton onClick={() => setIsOpen(true)} />
<ActionButton size='small' onClick={() => setIsOpen(true)} />
</div>

{isOpen && (
Expand All @@ -111,7 +111,7 @@ export const FeatureAction: FC<FeatureActionProps> = ({
}}
>
<Icon name='edit' width={18} fill='#9DA4AE' />
<span>Edit Permissions</span>
<span>Manage user</span>
</div>
)}

Expand Down
150 changes: 150 additions & 0 deletions frontend/web/components/UsersGroups.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
import React, { FC } from 'react'
import {
useGetGroupsQuery,
useUpdateGroupMutation,
} from 'common/services/useGroup'
import { sortBy } from 'lodash'
import { User, UserGroup, UserGroupSummary } from 'common/types/responses'
import Switch from './Switch'
import PanelSearch from './PanelSearch'
import ErrorMessage from './ErrorMessage'

type UsersGroupsType = {
user: User
orgId: number
}
const widths = [120]

const UsersGroups: FC<UsersGroupsType> = ({ orgId, user }) => {
const {
data,
error: groupsError,
isLoading,
} = useGetGroupsQuery({ orgId })
const [updateGroup, { error: saveError, isLoading: isSaving }] =
useUpdateGroupMutation({})
const error = groupsError || saveError
const onGroupsUpdated = (res: { error?: any }) => {
if (res.error) {
toast('Error updating group', 'danger')
} else {
toast('Updated user groups')
}
}
return isLoading ? (
<div className='text-center'>
<Loader />
</div>
) : (
<>
<ErrorMessage error={error} />
<PanelSearch
noResultsText={(search: string) =>
search ? (
<Flex className='text-center'>
No results found for <strong>{search}</strong>
</Flex>
) : (
<Flex className='text-center'>This group has no members</Flex>
)
}
id='org-members-list'
title='Groups'
className='no-pad overflow-visible'
renderSearchWithNoResults
items={sortBy(data?.results, 'name')}
filterRow={(item: UserGroupSummary, search: string) => {
const strToSearch = `${item.name} ${item.external_id}`
return strToSearch.toLowerCase().indexOf(search.toLowerCase()) !== -1
}}
header={
<>
<Row className='table-header'>
<Flex className='table-column px-3'>
<div>Group</div>
</Flex>
<div className='table-column ml-1' style={{ width: widths[0] }}>
Member
</div>
<div className='table-column ml-1' style={{ width: widths[1] }}>
Admin
</div>
</Row>
</>
}
renderRow={(group: UserGroup) => {
const { external_id, id, name, users } = group
const groupUser = users.find((v) => v.id === user.id)
const isInGroup = !!groupUser
const isAdmin = groupUser?.group_admin
return (
<Row className='list-item' key={id}>
<Flex className='table-column px-3'>
<div className='font-weight-medium'>{name}</div>
<div className='text-muted'>{external_id}</div>
</Flex>
<div className='table-column' style={{ width: widths[0] }}>
<Switch
disabled={isSaving}
checked={isInGroup}
onChange={(v: boolean) => {
if (v) {
updateGroup({
data: { ...group, users: group.users.concat([user]) },
orgId: `${orgId}`,
users: group.users.concat([user]),
usersToAddAdmin: null,
usersToRemove: null,
usersToRemoveAdmin: null,
}).then(onGroupsUpdated)
} else {
updateGroup({
data: group,
orgId: `${orgId}`,
users: group.users,
usersToAddAdmin: null,
usersToRemove: [user.id],
usersToRemoveAdmin: null,
}).then(onGroupsUpdated)
}
}}
/>
</div>
<div className='table-column' style={{ width: widths[1] }}>
<Switch
disabled={isSaving}
checked={isAdmin}
onChange={(v: boolean) => {
if (v) {
updateGroup({
data: { ...group, users: group.users.concat([user]) },
orgId: `${orgId}`,
users: isInGroup
? group.users
: group.users.concat([user]),
usersToAddAdmin: [user.id],
usersToRemove: null,
usersToRemoveAdmin: null,
}).then(onGroupsUpdated)
} else {
updateGroup({
data: group,
orgId: `${orgId}`,
users: group.users,
usersToAddAdmin: null,
usersToRemove: null,
usersToRemoveAdmin: [user.id],
}).then(onGroupsUpdated)
}
}}
/>
</div>
</Row>
)
}}
/>
</>
)
}

export default UsersGroups
38 changes: 27 additions & 11 deletions frontend/web/components/pages/UsersAndPermissionsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import sortBy from 'lodash/sortBy'
import UserAction from 'components/UserAction'
import Icon from 'components/Icon'
import RolesTable from 'components/RolesTable'
import UsersGroups from 'components/UsersGroups'
import PlanBasedBanner, { getPlanBasedOption } from 'components/PlanBasedAccess'

type UsersAndPermissionsPageType = {
Expand Down Expand Up @@ -103,9 +104,28 @@ const UsersAndPermissionsInner: FC<UsersAndPermissionsInnerType> = ({

const editUserPermissions = (user: User, organisationId: number) => {
openModal(
'Edit Organisation Permissions',
<div className='p-4'>
<PermissionsTabs uncontrolled user={user} orgId={organisationId} />
user.first_name || user.last_name
? `${user.first_name} ${user.last_name}`
: `${user.email}`,
<div>
<Tabs uncontrolled hideNavOnSingleTab>
{user.role !== 'ADMIN' && (
<TabItem tabLabel='Permissions'>
<div className='pt-4'>
<PermissionsTabs
uncontrolled
user={user}
orgId={organisationId}
/>
</div>
</TabItem>
)}
<TabItem tabLabel='Groups'>
<div className='pt-4'>
<UsersGroups user={user} orgId={organisationId} />
</div>
</TabItem>
</Tabs>
</div>,
'p-0 side-modal',
)
Expand Down Expand Up @@ -463,17 +483,13 @@ const UsersAndPermissionsInner: FC<UsersAndPermissionsInnerType> = ({
)
}
const onEditClick = () => {
if (role !== 'ADMIN') {
editUserPermissions(user, organisation.id)
}
editUserPermissions(user, organisation.id)
}
return (
<Row
data-test={`user-${i}`}
space
className={classNames('list-item', {
clickable: role !== 'ADMIN',
})}
className={classNames('list-item clickable')}
onClick={onEditClick}
key={id}
>
Expand Down Expand Up @@ -553,13 +569,13 @@ const UsersAndPermissionsInner: FC<UsersAndPermissionsInnerType> = ({
style={{
width: widths[2],
}}
className='table-column text-end'
className='table-column d-flex justify-content-end'
>
<UserAction
onRemove={onRemoveClick}
onEdit={onEditClick}
canRemove={AccountStore.isAdmin()}
canEdit={role !== 'ADMIN'}
canEdit={AccountStore.isAdmin()}
/>
</div>
</Row>
Expand Down

0 comments on commit 89b153c

Please sign in to comment.