From c30b478226216fc9dae1d67bc06a6530339b34bc Mon Sep 17 00:00:00 2001 From: Hugo Costa Date: Wed, 30 Aug 2023 13:57:38 -0300 Subject: [PATCH 01/61] feat: User Panel > Tab ALL (#29622) * all tab initial * remove "-" fallback * change 'status' to 'registration status' --------- Co-authored-by: dougfabris Co-authored-by: Felipe <84182706+felipe-rod123@users.noreply.github.com> --- .../views/admin/users/AdminUsersPage.tsx | 27 ++++++++++++++++--- .../admin/users/UsersTable/UsersTable.tsx | 2 +- .../admin/users/UsersTable/UsersTableRow.tsx | 21 ++++++++------- .../rocketchat-i18n/i18n/en.i18n.json | 3 +++ 4 files changed, 39 insertions(+), 14 deletions(-) diff --git a/apps/meteor/client/views/admin/users/AdminUsersPage.tsx b/apps/meteor/client/views/admin/users/AdminUsersPage.tsx index 24622a44bcc6..838a383d4fc9 100644 --- a/apps/meteor/client/views/admin/users/AdminUsersPage.tsx +++ b/apps/meteor/client/views/admin/users/AdminUsersPage.tsx @@ -1,7 +1,7 @@ -import { Button, ButtonGroup } from '@rocket.chat/fuselage'; +import { Button, ButtonGroup, Tabs } from '@rocket.chat/fuselage'; import { usePermission, useRouteParameter, useTranslation, useRouter } from '@rocket.chat/ui-contexts'; import type { ReactElement } from 'react'; -import React, { useEffect, useRef } from 'react'; +import React, { useEffect, useRef, useState } from 'react'; import UserPageHeaderContentWithSeatsCap from '../../../../ee/client/views/admin/users/UserPageHeaderContentWithSeatsCap'; import { useSeatsCap } from '../../../../ee/client/views/admin/users/useSeatsCap'; @@ -25,15 +25,17 @@ const UsersPage = (): ReactElement => { const canCreateUser = usePermission('create-user'); const canBulkCreateUser = usePermission('bulk-register-user'); + const [tab, setTab] = useState('all'); + useEffect(() => { if (!context || !seatsCap) { return; } - if (seatsCap.activeUsers >= seatsCap.maxActiveUsers && !['edit', 'info'].includes(context)) { + if (seatsCap.activeUsers >= seatsCap.maxActiveUsers && context && !['edit', 'info'].includes(context)) { router.navigate('/admin/users'); } - }, [router, context, seatsCap]); + }, [context, seatsCap, tab, router]); const handleReload = (): void => { seatsCap?.reload(); @@ -62,6 +64,23 @@ const UsersPage = (): ReactElement => { )} + + setTab('all')}> + {t('All')} + + setTab('tab-invited')}> + {t('Invited')} + + setTab('tab-new')}> + {t('New_users')} + + setTab('tab-active')}> + {t('Active')} + + setTab('tab-deactivated')}> + {t('Deactivated')} + + diff --git a/apps/meteor/client/views/admin/users/UsersTable/UsersTable.tsx b/apps/meteor/client/views/admin/users/UsersTable/UsersTable.tsx index 981e75483d03..44175ec47de7 100644 --- a/apps/meteor/client/views/admin/users/UsersTable/UsersTable.tsx +++ b/apps/meteor/client/views/admin/users/UsersTable/UsersTable.tsx @@ -132,7 +132,7 @@ const UsersTable = ({ reload }: UsersTableProps): ReactElement | null => { ), - {t('Status')} + {t('Registration_status')} , ], [mediaQuery, setSort, sortBy, sortDirection, t], diff --git a/apps/meteor/client/views/admin/users/UsersTable/UsersTableRow.tsx b/apps/meteor/client/views/admin/users/UsersTable/UsersTableRow.tsx index 2572b7446c42..64e3f4858ffa 100644 --- a/apps/meteor/client/views/admin/users/UsersTable/UsersTableRow.tsx +++ b/apps/meteor/client/views/admin/users/UsersTable/UsersTableRow.tsx @@ -1,25 +1,25 @@ +import { UserStatus as Status } from '@rocket.chat/core-typings'; import type { IRole, IUser } from '@rocket.chat/core-typings'; import { Box } from '@rocket.chat/fuselage'; -import { capitalize } from '@rocket.chat/string-helpers'; -import type { TranslationKey } from '@rocket.chat/ui-contexts'; import { useTranslation } from '@rocket.chat/ui-contexts'; import type { ReactElement } from 'react'; import React from 'react'; import { Roles } from '../../../../../app/models/client'; import { GenericTableRow, GenericTableCell } from '../../../../components/GenericTable'; +import { UserStatus } from '../../../../components/UserStatus'; import UserAvatar from '../../../../components/avatar/UserAvatar'; type UsersTableRowProps = { - user: Pick; + user: Pick; onClick: (id: IUser['_id']) => void; mediaQuery: boolean; }; const UsersTableRow = ({ user, onClick, mediaQuery }: UsersTableRowProps): ReactElement => { const t = useTranslation(); - const { _id, emails, username, name, roles, status, active, avatarETag } = user; - const statusText = active ? t(capitalize(status as string) as TranslationKey) : t('Disabled'); + const { _id, emails, username, name, status, roles, active, avatarETag } = user; + const registrationStatusText = active ? t('Active') : t('Deactivated'); const roleNames = (roles || []) .map((roleId) => (Roles.findOne(roleId, { fields: { name: 1 } }) as IRole | undefined)?.name) @@ -41,12 +41,14 @@ const UsersTableRow = ({ user, onClick, mediaQuery }: UsersTableRowProps): React + + + {name || username} {!mediaQuery && name && ( - {' '} - {`@${username}`}{' '} + {`@${username}`} )} @@ -57,14 +59,15 @@ const UsersTableRow = ({ user, onClick, mediaQuery }: UsersTableRowProps): React {username} - {' '} + )} + {emails?.length && emails[0].address} {mediaQuery && {roleNames}} - {statusText} + {registrationStatusText} ); diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json index d1e0248fba48..93aac3827b2c 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json @@ -1521,6 +1521,7 @@ "DDP_Rate_Limit_User_Interval_Time": "Limit by User: interval time", "DDP_Rate_Limit_User_Requests_Allowed": "Limit by User: requests allowed", "Deactivate": "Deactivate", + "Deactivated": "Deactivated", "Decline": "Decline", "Decode_Key": "Decode Key", "default": "default", @@ -2669,6 +2670,7 @@ "Invitation_Subject": "Invitation Subject", "Invitation_Subject_Default": "You have been invited to [Site_Name]", "Invite": "Invite", + "Invited": "Invited", "Invites": "Invites", "Invite_and_add_members_to_this_workspace_to_start_communicating": "Invite and add members to this workspace to start communicating.", "Invite_Link": "Invite Link", @@ -5999,5 +6001,6 @@ "All_visible": "All visible", "Filter_by_room": "Filter by room type", "Filter_by_visibility": "Filter by visibility", + "Registration_status": "Registration status", "Theme_Appearence": "Theme Appearence" } From 90da483aa7094fa9893a25cab15a7d09726aebaf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrique=20Guimar=C3=A3es=20Ribeiro?= Date: Wed, 4 Oct 2023 16:32:28 -0300 Subject: [PATCH 02/61] feat: Implement users page active tab (#30242) * feat: :sparkles: Filter users list by active users Implemented the necessary resources to filter the list of users and to return only those who are active, when the active tab is enabled. * feat: :sparkles: Implement new roles filter in the admin users page Implemented a new filter on the users' page that retrieves the roles list and creates a dropdown menu. When any of its options is selected, it will filter the users' list to display only those with the selected roles. Additionally, I made some minor adjustments to the MultiSelectCustom component and introduced some new hooks. * WIP: Remove filters * feat: :sparkles: WIP: Implement new actions menu for users page table * feat: :sparkles: Finish users table menu Completed the implementation of the users table actions menu by preventing the propagation of the click event that triggered the opening of the contextual bar when the menu was clicked. Also, ensured that the contextual bar only opens when the "Enter" or "Space" keys are used for keyboard navigation and enhanced the options menu in the contextual bar as specified in Figma. * refactor: :recycle: Remove status badge from contextual bar and reorg Removed the status from the users page contextual bar (the need to remove this may not be concrete, for now it will be only commented until the final decision is made). Also changed the order of the contextual bar info and changed some minor styles to follow figma specs. * Typecheck * Re-add icon back to InfoPanelTitle * Hide registration status and justify actions menu * Review, reintroduce contextual bar full name and status --- .../components/InfoPanel/InfoPanelTitle.tsx | 6 +- .../components/UserCard/UserCardRoles.tsx | 2 +- .../client/components/UserInfo/UserInfo.tsx | 40 ++--- .../admin/users/AdminUserInfoActions.tsx | 10 +- .../views/admin/users/AdminUsersPage.tsx | 12 +- .../admin/users/UsersTable/UsersTable.tsx | 152 +++++++++--------- .../admin/users/UsersTable/UsersTableRow.tsx | 94 ++++++++++- .../admin/users/hooks/useFilterActiveUsers.ts | 10 ++ .../admin/users/hooks/useFilterUsersByrole.ts | 12 ++ .../views/admin/users/hooks/useListUsers.ts | 69 ++++++++ .../rocketchat-i18n/i18n/en.i18n.json | 6 +- .../MultiSelectCustom/MultiSelectCustom.tsx | 4 +- .../MultiSelectCustomAnchor.tsx | 9 +- .../MultiSelectCustomList.tsx | 4 +- 14 files changed, 307 insertions(+), 123 deletions(-) create mode 100644 apps/meteor/client/views/admin/users/hooks/useFilterActiveUsers.ts create mode 100644 apps/meteor/client/views/admin/users/hooks/useFilterUsersByrole.ts create mode 100644 apps/meteor/client/views/admin/users/hooks/useListUsers.ts diff --git a/apps/meteor/client/components/InfoPanel/InfoPanelTitle.tsx b/apps/meteor/client/components/InfoPanel/InfoPanelTitle.tsx index 7ea4de6d9867..615f6762efd1 100644 --- a/apps/meteor/client/components/InfoPanel/InfoPanelTitle.tsx +++ b/apps/meteor/client/components/InfoPanel/InfoPanelTitle.tsx @@ -5,15 +5,15 @@ import React from 'react'; type InfoPanelTitleProps = { title: string; - icon: ReactNode; + icon?: ReactNode; }; const isValidIcon = (icon: ReactNode): icon is IconName => typeof icon === 'string'; const InfoPanelTitle: FC = ({ title, icon }) => ( - + {isValidIcon(icon) ? : icon} - + {title} diff --git a/apps/meteor/client/components/UserCard/UserCardRoles.tsx b/apps/meteor/client/components/UserCard/UserCardRoles.tsx index 9cd22ebdff35..f7a977d4a4dc 100644 --- a/apps/meteor/client/components/UserCard/UserCardRoles.tsx +++ b/apps/meteor/client/components/UserCard/UserCardRoles.tsx @@ -6,7 +6,7 @@ import UserCardInfo from './UserCardInfo'; const UserCardRoles = ({ children }: { children: ReactNode }): ReactElement => ( - + {children} diff --git a/apps/meteor/client/components/UserInfo/UserInfo.tsx b/apps/meteor/client/components/UserInfo/UserInfo.tsx index bdce27d028ad..8a42f1b87026 100644 --- a/apps/meteor/client/components/UserInfo/UserInfo.tsx +++ b/apps/meteor/client/components/UserInfo/UserInfo.tsx @@ -81,12 +81,19 @@ const UserInfo = ({ {userDisplayName && } {statusText && ( - + )} + {nickname && ( + + {t('Nickname')} + {nickname} + + )} + {roles.length !== 0 && ( {t('Roles')} @@ -94,6 +101,13 @@ const UserInfo = ({ )} + {username && username !== name && ( + + {t('Username')} + {username} + + )} + {Number.isInteger(utcOffset) && ( {t('Local_Time')} @@ -101,10 +115,12 @@ const UserInfo = ({ )} - {username && username !== name && ( + {bio && ( - {t('Username')} - {username} + {t('Bio')} + + + )} @@ -122,22 +138,6 @@ const UserInfo = ({ )} - {nickname && ( - - {t('Nickname')} - {nickname} - - )} - - {bio && ( - - {t('Bio')} - - - - - )} - {phone && ( {t('Phone')} diff --git a/apps/meteor/client/views/admin/users/AdminUserInfoActions.tsx b/apps/meteor/client/views/admin/users/AdminUserInfoActions.tsx index d003b47ba2af..dd0bf833f72b 100644 --- a/apps/meteor/client/views/admin/users/AdminUserInfoActions.tsx +++ b/apps/meteor/client/views/admin/users/AdminUserInfoActions.tsx @@ -83,8 +83,8 @@ const AdminUserInfoActions = ({ ...(changeAdminStatusAction && !isFederatedUser && { makeAdmin: changeAdminStatusAction }), ...(resetE2EKeyAction && !isFederatedUser && { resetE2EKey: resetE2EKeyAction }), ...(resetTOTPAction && !isFederatedUser && { resetTOTP: resetTOTPAction }), - ...(deleteUserAction && { delete: deleteUserAction }), ...(changeUserStatusAction && !isFederatedUser && { changeActiveStatus: changeUserStatusAction }), + ...(deleteUserAction && { delete: deleteUserAction }), }), [ t, @@ -116,7 +116,13 @@ const AdminUserInfoActions = ({ secondary flexShrink={0} key='menu' - renderItem={({ label: { label, icon }, ...props }): ReactElement =>