From 586b901318e407329e5e1948dc0f2eeae0a128d3 Mon Sep 17 00:00:00 2001 From: Scott Wilson Date: Tue, 26 Nov 2024 17:17:18 -0800 Subject: [PATCH 1/7] improvement: add pagination, filtering and sort to users projects table with minor UI improvements --- frontend/src/hooks/usePagination.tsx | 17 +- .../UserProjectsSection/UserProjectRow.tsx | 2 +- .../UserProjectsSection/UserProjectsTable.tsx | 146 ++++++++++++++---- 3 files changed, 132 insertions(+), 33 deletions(-) diff --git a/frontend/src/hooks/usePagination.tsx b/frontend/src/hooks/usePagination.tsx index 3d3002b4c..e4c9fddbb 100644 --- a/frontend/src/hooks/usePagination.tsx +++ b/frontend/src/hooks/usePagination.tsx @@ -3,9 +3,16 @@ import { useState } from "react"; import { OrderByDirection } from "@app/hooks/api/generic/types"; import { useDebounce } from "@app/hooks/useDebounce"; -export const usePagination = (initialOrderBy: T) => { +export const usePagination = ( + initialOrderBy: T, + { + initPerPage = 100 + }: { + initPerPage?: number; + } = {} +) => { const [page, setPage] = useState(1); - const [perPage, setPerPage] = useState(100); + const [perPage, setPerPage] = useState(initPerPage); const [orderDirection, setOrderDirection] = useState(OrderByDirection.ASC); const [orderBy, setOrderBy] = useState(initialOrderBy); const [search, setSearch] = useState(""); @@ -26,6 +33,10 @@ export const usePagination = (initialOrderBy: T) => { search, setSearch, orderBy, - setOrderBy + setOrderBy, + toggleOrderDirection: () => + setOrderDirection((prev) => + prev === OrderByDirection.DESC ? OrderByDirection.ASC : OrderByDirection.DESC + ) }; }; diff --git a/frontend/src/views/Org/UserPage/components/UserProjectsSection/UserProjectRow.tsx b/frontend/src/views/Org/UserPage/components/UserProjectsSection/UserProjectRow.tsx index fff2007ef..f519312c5 100644 --- a/frontend/src/views/Org/UserPage/components/UserProjectsSection/UserProjectRow.tsx +++ b/frontend/src/views/Org/UserPage/components/UserProjectsSection/UserProjectRow.tsx @@ -58,7 +58,7 @@ export const UserProjectRow = ({ }); }} > - {project.name} + {project.name} {`${formatRoleName(roles[0].role, roles[0].customRoleName)}${ roles.length > 1 ? ` (+${roles.length - 1})` : "" }`} diff --git a/frontend/src/views/Org/UserPage/components/UserProjectsSection/UserProjectsTable.tsx b/frontend/src/views/Org/UserPage/components/UserProjectsSection/UserProjectsTable.tsx index 88e1a2925..17b4b0ba6 100644 --- a/frontend/src/views/Org/UserPage/components/UserProjectsSection/UserProjectsTable.tsx +++ b/frontend/src/views/Org/UserPage/components/UserProjectsSection/UserProjectsTable.tsx @@ -1,7 +1,18 @@ -import { faFolder } from "@fortawesome/free-solid-svg-icons"; +import { useMemo } from "react"; +import { + faArrowDown, + faArrowUp, + faFolder, + faMagnifyingGlass, + faSearch +} from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { EmptyState, + IconButton, + Input, + Pagination, Table, TableContainer, TableSkeleton, @@ -11,7 +22,9 @@ import { Tr } from "@app/components/v2"; import { useOrganization } from "@app/context"; +import { usePagination, useResetPageHelper } from "@app/hooks"; import { useGetOrgMembershipProjectMemberships } from "@app/hooks/api"; +import { OrderByDirection } from "@app/hooks/api/generic/types"; import { UsePopUpState } from "@app/hooks/usePopUp"; import { UserProjectRow } from "./UserProjectRow"; @@ -21,42 +34,117 @@ type Props = { handlePopUpOpen: (popUpName: keyof UsePopUpState<["removeUserFromProject"]>, data?: {}) => void; }; +enum UserProjectsOrderBy { + Name = "Name" +} + export const UserProjectsTable = ({ membershipId, handlePopUpOpen }: Props) => { const { currentOrg } = useOrganization(); const orgId = currentOrg?.id || ""; + const { + search, + setSearch, + setPage, + page, + perPage, + setPerPage, + offset, + limit, + orderDirection, + toggleOrderDirection + } = usePagination(UserProjectsOrderBy.Name, { initPerPage: 10 }); - const { data: projectMemberships, isLoading } = useGetOrgMembershipProjectMemberships( + const { data: projectMemberships = [], isLoading } = useGetOrgMembershipProjectMemberships( orgId, membershipId ); + const filteredProjects = useMemo( + () => + projectMemberships + ?.filter((membership) => + membership.project.name.toLowerCase().includes(search.trim().toLowerCase()) + ) + .sort((a, b) => { + const [membershipOne, membershipTwo] = + orderDirection === OrderByDirection.ASC ? [a, b] : [b, a]; + + return membershipOne.project.name + .toLowerCase() + .localeCompare(membershipTwo.project.name.toLowerCase()); + }) ?? [], + [projectMemberships, orderDirection, search] + ); + + useResetPageHelper({ + totalCount: filteredProjects.length, + offset, + setPage + }); + return ( - - - - - - - - - - {isLoading && } - {!isLoading && - projectMemberships?.map((membership) => { - return ( - - ); - })} - -
NameRole -
- {!isLoading && !projectMemberships?.length && ( - - )} -
+
+ setSearch(e.target.value)} + leftIcon={} + placeholder="Search projects..." + /> + + + + + + + + + + {isLoading && } + {!isLoading && + filteredProjects?.slice(offset, limit * page)?.map((membership) => { + return ( + + ); + })} + +
+
+ Name + + + +
+
Role +
+ + {!isLoading && !filteredProjects?.length && ( + + )} +
+
); }; From ecea79f040b406ca93931ac74374363ef236e4a1 Mon Sep 17 00:00:00 2001 From: Scott Wilson Date: Tue, 26 Nov 2024 17:20:49 -0800 Subject: [PATCH 2/7] fix: hide pagination when no search match --- .../UserProjectsSection/UserProjectsTable.tsx | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/frontend/src/views/Org/UserPage/components/UserProjectsSection/UserProjectsTable.tsx b/frontend/src/views/Org/UserPage/components/UserProjectsSection/UserProjectsTable.tsx index 17b4b0ba6..ab50937d9 100644 --- a/frontend/src/views/Org/UserPage/components/UserProjectsSection/UserProjectsTable.tsx +++ b/frontend/src/views/Org/UserPage/components/UserProjectsSection/UserProjectsTable.tsx @@ -127,13 +127,15 @@ export const UserProjectsTable = ({ membershipId, handlePopUpOpen }: Props) => { })} - + {Boolean(filteredProjects.length) && ( + + )} {!isLoading && !filteredProjects?.length && ( Date: Wed, 27 Nov 2024 12:05:46 -0800 Subject: [PATCH 3/7] improvement: add pagination, sort and filtering to identity projects table with minor UI adjustments --- .../IdentityProjectRow.tsx | 2 +- .../IdentityProjectsTable.tsx | 152 ++++++++++++++---- .../UserProjectsSection/UserProjectsTable.tsx | 13 +- 3 files changed, 129 insertions(+), 38 deletions(-) diff --git a/frontend/src/views/Org/IdentityPage/components/IdentityProjectsSection/IdentityProjectRow.tsx b/frontend/src/views/Org/IdentityPage/components/IdentityProjectsSection/IdentityProjectRow.tsx index 0a9301d56..4cbd1362d 100644 --- a/frontend/src/views/Org/IdentityPage/components/IdentityProjectsSection/IdentityProjectRow.tsx +++ b/frontend/src/views/Org/IdentityPage/components/IdentityProjectsSection/IdentityProjectRow.tsx @@ -62,7 +62,7 @@ export const IdentityProjectRow = ({ }); }} > - {project.name} + {project.name} {`${formatRoleName(roles[0].role, roles[0].customRoleName)}${ roles.length > 1 ? ` (+${roles.length - 1})` : "" }`} diff --git a/frontend/src/views/Org/IdentityPage/components/IdentityProjectsSection/IdentityProjectsTable.tsx b/frontend/src/views/Org/IdentityPage/components/IdentityProjectsSection/IdentityProjectsTable.tsx index a005ad1c8..86d6185f5 100644 --- a/frontend/src/views/Org/IdentityPage/components/IdentityProjectsSection/IdentityProjectsTable.tsx +++ b/frontend/src/views/Org/IdentityPage/components/IdentityProjectsSection/IdentityProjectsTable.tsx @@ -1,7 +1,18 @@ -import { faFolder } from "@fortawesome/free-solid-svg-icons"; +import { useMemo } from "react"; +import { + faArrowDown, + faArrowUp, + faFolder, + faMagnifyingGlass, + faSearch +} from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { EmptyState, + IconButton, + Input, + Pagination, Table, TableContainer, TableSkeleton, @@ -10,7 +21,9 @@ import { THead, Tr } from "@app/components/v2"; +import { usePagination, useResetPageHelper } from "@app/hooks"; import { useGetIdentityProjectMemberships } from "@app/hooks/api"; +import { OrderByDirection } from "@app/hooks/api/generic/types"; import { UsePopUpState } from "@app/hooks/usePopUp"; import { IdentityProjectRow } from "./IdentityProjectRow"; @@ -23,36 +36,115 @@ type Props = { ) => void; }; +enum IdentityProjectsOrderBy { + Name = "name" +} + export const IdentityProjectsTable = ({ identityId, handlePopUpOpen }: Props) => { - const { data: projectMemberships, isLoading } = useGetIdentityProjectMemberships(identityId); + const { data: projectMemberships = [], isLoading } = useGetIdentityProjectMemberships(identityId); + + const { + search, + setSearch, + setPage, + page, + perPage, + setPerPage, + offset, + orderDirection, + toggleOrderDirection + } = usePagination(IdentityProjectsOrderBy.Name, { initPerPage: 10 }); + + const filteredProjectMemberships = useMemo( + () => + projectMemberships + ?.filter((membership) => + membership.project.name.toLowerCase().includes(search.trim().toLowerCase()) + ) + .sort((a, b) => { + const [membershipOne, membershipTwo] = + orderDirection === OrderByDirection.ASC ? [a, b] : [b, a]; + + return membershipOne.project.name + .toLowerCase() + .localeCompare(membershipTwo.project.name.toLowerCase()); + }) ?? [], + [projectMemberships, orderDirection, search] + ); + + useResetPageHelper({ + totalCount: filteredProjectMemberships.length, + offset, + setPage + }); + return ( - - - - - - - - - - - {isLoading && } - {!isLoading && - projectMemberships?.map((membership) => { - return ( - - ); - })} - -
NameRoleAdded On -
- {!isLoading && !projectMemberships?.length && ( - - )} -
+
+ setSearch(e.target.value)} + leftIcon={} + placeholder="Search projects..." + /> + + + + + + + + + + + + {isLoading && } + {!isLoading && + filteredProjectMemberships.slice(offset, perPage * page).map((membership) => { + return ( + + ); + })} + +
+
+ Name + + + +
+
RoleAdded On +
+ {Boolean(filteredProjectMemberships.length) && ( + + )} + {!isLoading && !filteredProjectMemberships?.length && ( + + )} +
+
); }; diff --git a/frontend/src/views/Org/UserPage/components/UserProjectsSection/UserProjectsTable.tsx b/frontend/src/views/Org/UserPage/components/UserProjectsSection/UserProjectsTable.tsx index ab50937d9..eda562055 100644 --- a/frontend/src/views/Org/UserPage/components/UserProjectsSection/UserProjectsTable.tsx +++ b/frontend/src/views/Org/UserPage/components/UserProjectsSection/UserProjectsTable.tsx @@ -49,7 +49,6 @@ export const UserProjectsTable = ({ membershipId, handlePopUpOpen }: Props) => { perPage, setPerPage, offset, - limit, orderDirection, toggleOrderDirection } = usePagination(UserProjectsOrderBy.Name, { initPerPage: 10 }); @@ -59,7 +58,7 @@ export const UserProjectsTable = ({ membershipId, handlePopUpOpen }: Props) => { membershipId ); - const filteredProjects = useMemo( + const filteredProjectMemberships = useMemo( () => projectMemberships ?.filter((membership) => @@ -77,7 +76,7 @@ export const UserProjectsTable = ({ membershipId, handlePopUpOpen }: Props) => { ); useResetPageHelper({ - totalCount: filteredProjects.length, + totalCount: filteredProjectMemberships.length, offset, setPage }); @@ -116,7 +115,7 @@ export const UserProjectsTable = ({ membershipId, handlePopUpOpen }: Props) => { {isLoading && } {!isLoading && - filteredProjects?.slice(offset, limit * page)?.map((membership) => { + filteredProjectMemberships.slice(offset, perPage * page).map((membership) => { return ( { })} - {Boolean(filteredProjects.length) && ( + {Boolean(filteredProjectMemberships.length) && ( )} - {!isLoading && !filteredProjects?.length && ( + {!isLoading && !filteredProjectMemberships?.length && ( Date: Wed, 27 Nov 2024 18:41:20 -0800 Subject: [PATCH 4/7] improvement: add pagination to project members table --- .../IdentityProjectsTable.tsx | 2 +- .../UserProjectsSection/UserProjectsTable.tsx | 2 +- .../MembersTab/components/MembersTable.tsx | 152 +++++++++++++++--- 3 files changed, 133 insertions(+), 23 deletions(-) diff --git a/frontend/src/views/Org/IdentityPage/components/IdentityProjectsSection/IdentityProjectsTable.tsx b/frontend/src/views/Org/IdentityPage/components/IdentityProjectsSection/IdentityProjectsTable.tsx index 86d6185f5..195f74111 100644 --- a/frontend/src/views/Org/IdentityPage/components/IdentityProjectsSection/IdentityProjectsTable.tsx +++ b/frontend/src/views/Org/IdentityPage/components/IdentityProjectsSection/IdentityProjectsTable.tsx @@ -68,7 +68,7 @@ export const IdentityProjectsTable = ({ identityId, handlePopUpOpen }: Props) => return membershipOne.project.name .toLowerCase() .localeCompare(membershipTwo.project.name.toLowerCase()); - }) ?? [], + }), [projectMemberships, orderDirection, search] ); diff --git a/frontend/src/views/Org/UserPage/components/UserProjectsSection/UserProjectsTable.tsx b/frontend/src/views/Org/UserPage/components/UserProjectsSection/UserProjectsTable.tsx index eda562055..9f4a8bc03 100644 --- a/frontend/src/views/Org/UserPage/components/UserProjectsSection/UserProjectsTable.tsx +++ b/frontend/src/views/Org/UserPage/components/UserProjectsSection/UserProjectsTable.tsx @@ -71,7 +71,7 @@ export const UserProjectsTable = ({ membershipId, handlePopUpOpen }: Props) => { return membershipOne.project.name .toLowerCase() .localeCompare(membershipTwo.project.name.toLowerCase()); - }) ?? [], + }), [projectMemberships, orderDirection, search] ); diff --git a/frontend/src/views/Project/MembersPage/components/MembersTab/components/MembersTable.tsx b/frontend/src/views/Project/MembersPage/components/MembersTab/components/MembersTable.tsx index 591d2663b..d81d4005c 100644 --- a/frontend/src/views/Project/MembersPage/components/MembersTab/components/MembersTable.tsx +++ b/frontend/src/views/Project/MembersPage/components/MembersTab/components/MembersTable.tsx @@ -1,9 +1,12 @@ -import { useMemo, useState } from "react"; +import { useMemo } from "react"; import { useRouter } from "next/router"; import { + faArrowDown, + faArrowUp, faClock, faEllipsisV, faMagnifyingGlass, + faSearch, faTrash, faUsers } from "@fortawesome/free-solid-svg-icons"; @@ -18,6 +21,7 @@ import { HoverCardTrigger, IconButton, Input, + Pagination, Table, TableContainer, TableSkeleton, @@ -35,7 +39,9 @@ import { useUser, useWorkspace } from "@app/context"; +import { usePagination, useResetPageHelper } from "@app/hooks"; import { useGetWorkspaceUsers } from "@app/hooks/api"; +import { OrderByDirection } from "@app/hooks/api/generic/types"; import { ProjectMembershipRole } from "@app/hooks/api/roles/types"; import { UsePopUpState } from "@app/hooks/usePopUp"; @@ -54,9 +60,12 @@ type Props = { ) => void; }; -export const MembersTable = ({ handlePopUpOpen }: Props) => { - const [searchMemberFilter, setSearchMemberFilter] = useState(""); +enum MembersOrderBy { + Name = "firstName", + Email = "email" +} +export const MembersTable = ({ handlePopUpOpen }: Props) => { const { currentWorkspace } = useWorkspace(); const { user } = useUser(); const router = useRouter(); @@ -64,26 +73,77 @@ export const MembersTable = ({ handlePopUpOpen }: Props) => { const userId = user?.id || ""; const workspaceId = currentWorkspace?.id || ""; - const { data: members, isLoading: isMembersLoading } = useGetWorkspaceUsers(workspaceId); + const { + search, + setSearch, + setPage, + page, + perPage, + setPerPage, + offset, + orderDirection, + orderBy, + setOrderBy, + setOrderDirection, + toggleOrderDirection + } = usePagination(MembersOrderBy.Name); - const filterdUsers = useMemo( + const { data: members = [], isLoading: isMembersLoading } = useGetWorkspaceUsers(workspaceId); + + const filteredUsers = useMemo( () => - members?.filter( - ({ user: u, inviteEmail }) => - u?.firstName?.toLowerCase().includes(searchMemberFilter.toLowerCase()) || - u?.lastName?.toLowerCase().includes(searchMemberFilter.toLowerCase()) || - u?.username?.toLowerCase().includes(searchMemberFilter.toLowerCase()) || - u?.email?.toLowerCase().includes(searchMemberFilter.toLowerCase()) || - inviteEmail?.includes(searchMemberFilter.toLowerCase()) - ), - [members, searchMemberFilter] + members + ?.filter( + ({ user: u, inviteEmail }) => + u?.firstName?.toLowerCase().includes(search.toLowerCase()) || + u?.lastName?.toLowerCase().includes(search.toLowerCase()) || + u?.username?.toLowerCase().includes(search.toLowerCase()) || + u?.email?.toLowerCase().includes(search.toLowerCase()) || + inviteEmail?.toLowerCase().includes(search.toLowerCase()) + ) + .sort((a, b) => { + const [memberOne, memberTwo] = orderDirection === OrderByDirection.ASC ? [a, b] : [b, a]; + + let valueOne: string; + let valueTwo: string; + + switch (orderBy) { + case MembersOrderBy.Email: + valueOne = memberOne.user.email || memberOne.inviteEmail; + valueTwo = memberTwo.user.email || memberTwo.inviteEmail; + break; + case MembersOrderBy.Name: + default: + valueOne = memberOne.user.firstName; + valueTwo = memberTwo.user.firstName; + } + + return valueOne.toLowerCase().localeCompare(valueTwo.toLowerCase()); + }), + [members, search, orderDirection, orderBy] ); + useResetPageHelper({ + totalCount: filteredUsers.length, + offset, + setPage + }); + + const handleSort = (column: MembersOrderBy) => { + if (column === orderBy) { + toggleOrderDirection(); + return; + } + + setOrderBy(column); + setOrderDirection(OrderByDirection.ASC); + }; + return (
setSearchMemberFilter(e.target.value)} + value={search} + onChange={(e) => setSearch(e.target.value)} leftIcon={} placeholder="Search members..." /> @@ -91,8 +151,44 @@ export const MembersTable = ({ handlePopUpOpen }: Props) => { - - + + @@ -100,7 +196,7 @@ export const MembersTable = ({ handlePopUpOpen }: Props) => { {isMembersLoading && } {!isMembersLoading && - filterdUsers?.map((projectMember) => { + filteredUsers.slice(offset, perPage * page).map((projectMember) => { const { user: u, inviteEmail, id: membershipId, roles } = projectMember; const name = u.firstName || u.lastName ? `${u.firstName} ${u.lastName || ""}` : "-"; const email = u?.email || inviteEmail; @@ -239,8 +335,22 @@ export const MembersTable = ({ handlePopUpOpen }: Props) => { })}
NameUsername +
+ Name + handleSort(MembersOrderBy.Name)} + > + + +
+
+
+ Email + handleSort(MembersOrderBy.Email)} + > + + +
+
Role
- {!isMembersLoading && filterdUsers?.length === 0 && ( - + {Boolean(filteredUsers.length) && ( + + )} + {!isMembersLoading && !filteredUsers?.length && ( + )}
From 588b3c77f9bfafd4ac2c5cb90dd50b6e442012e1 Mon Sep 17 00:00:00 2001 From: Scott Wilson Date: Wed, 27 Nov 2024 19:23:54 -0800 Subject: [PATCH 5/7] improvement: add pagination/sort to org members table --- .../OrgMembersSection/OrgMembersTable.tsx | 540 +++++++++++------- 1 file changed, 331 insertions(+), 209 deletions(-) diff --git a/frontend/src/views/Org/MembersPage/components/OrgMembersTab/components/OrgMembersSection/OrgMembersTable.tsx b/frontend/src/views/Org/MembersPage/components/OrgMembersTab/components/OrgMembersSection/OrgMembersTable.tsx index ae879e88c..2533b54d0 100644 --- a/frontend/src/views/Org/MembersPage/components/OrgMembersTab/components/OrgMembersSection/OrgMembersTable.tsx +++ b/frontend/src/views/Org/MembersPage/components/OrgMembersTab/components/OrgMembersSection/OrgMembersTable.tsx @@ -1,6 +1,13 @@ -import { useCallback, useMemo, useState } from "react"; +import { useCallback, useMemo } from "react"; import { useRouter } from "next/router"; -import { faEllipsis, faMagnifyingGlass, faUsers } from "@fortawesome/free-solid-svg-icons"; +import { + faArrowDown, + faArrowUp, + faEllipsis, + faMagnifyingGlass, + faSearch, + faUsers +} from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { twMerge } from "tailwind-merge"; @@ -14,7 +21,9 @@ import { DropdownMenuItem, DropdownMenuTrigger, EmptyState, + IconButton, Input, + Pagination, Select, SelectItem, Table, @@ -33,6 +42,7 @@ import { useSubscription, useUser } from "@app/context"; +import { usePagination, useResetPageHelper } from "@app/hooks"; import { useAddUsersToOrg, useFetchServerStatus, @@ -40,6 +50,7 @@ import { useGetOrgUsers, useUpdateOrgMembership } from "@app/hooks/api"; +import { OrderByDirection } from "@app/hooks/api/generic/types"; import { UsePopUpState } from "@app/hooks/usePopUp"; type Props = { @@ -54,6 +65,11 @@ type Props = { setCompleteInviteLinks: (links: Array<{ email: string; link: string }> | null) => void; }; +enum OrgMembersOrderBy { + Name = "firstName", + Email = "email" +} + export const OrgMembersTable = ({ handlePopUpOpen, setCompleteInviteLinks }: Props) => { const router = useRouter(); const { subscription } = useSubscription(); @@ -64,10 +80,8 @@ export const OrgMembersTable = ({ handlePopUpOpen, setCompleteInviteLinks }: Pro const { data: roles, isLoading: isRolesLoading } = useGetOrgRoles(orgId); - const [searchMemberFilter, setSearchMemberFilter] = useState(""); - const { data: serverDetails } = useFetchServerStatus(); - const { data: members, isLoading: isMembersLoading } = useGetOrgUsers(orgId); + const { data: members = [], isLoading: isMembersLoading } = useGetOrgUsers(orgId); const { mutateAsync: addUsersMutateAsync } = useAddUsersToOrg(); const { mutateAsync: updateOrgMembership } = useUpdateOrgMembership(); @@ -144,24 +158,75 @@ export const OrgMembersTable = ({ handlePopUpOpen, setCompleteInviteLinks }: Pro [roles] ); - const filterdUser = useMemo( + const { + search, + setSearch, + setPage, + page, + perPage, + setPerPage, + offset, + orderDirection, + orderBy, + setOrderBy, + setOrderDirection, + toggleOrderDirection + } = usePagination(OrgMembersOrderBy.Name); + + const filteredUsers = useMemo( () => - members?.filter( - ({ user: u, inviteEmail }) => - u?.firstName?.toLowerCase().includes(searchMemberFilter.toLowerCase()) || - u?.lastName?.toLowerCase().includes(searchMemberFilter.toLowerCase()) || - u?.username?.toLowerCase().includes(searchMemberFilter.toLowerCase()) || - u?.email?.toLowerCase().includes(searchMemberFilter.toLowerCase()) || - inviteEmail?.includes(searchMemberFilter.toLowerCase()) - ), - [members, searchMemberFilter] + members + ?.filter( + ({ user: u, inviteEmail }) => + u?.firstName?.toLowerCase().includes(search.toLowerCase()) || + u?.lastName?.toLowerCase().includes(search.toLowerCase()) || + u?.username?.toLowerCase().includes(search.toLowerCase()) || + u?.email?.toLowerCase().includes(search.toLowerCase()) || + inviteEmail?.toLowerCase().includes(search.toLowerCase()) + ) + .sort((a, b) => { + const [memberOne, memberTwo] = orderDirection === OrderByDirection.ASC ? [a, b] : [b, a]; + + let valueOne: string; + let valueTwo: string; + + switch (orderBy) { + case OrgMembersOrderBy.Email: + valueOne = memberOne.user.email || memberOne.inviteEmail; + valueTwo = memberTwo.user.email || memberTwo.inviteEmail; + break; + case OrgMembersOrderBy.Name: + default: + valueOne = memberOne.user.firstName; + valueTwo = memberTwo.user.firstName; + } + + return valueOne.toLowerCase().localeCompare(valueTwo.toLowerCase()); + }), + [members, search, orderDirection, orderBy] ); + const handleSort = (column: OrgMembersOrderBy) => { + if (column === orderBy) { + toggleOrderDirection(); + return; + } + + setOrderBy(column); + setOrderDirection(OrderByDirection.ASC); + }; + + useResetPageHelper({ + totalCount: filteredUsers.length, + offset, + setPage + }); + return (
setSearchMemberFilter(e.target.value)} + value={search} + onChange={(e) => setSearch(e.target.value)} leftIcon={} placeholder="Search members..." /> @@ -169,8 +234,46 @@ export const OrgMembersTable = ({ handlePopUpOpen, setCompleteInviteLinks }: Pro - - + + @@ -178,212 +281,231 @@ export const OrgMembersTable = ({ handlePopUpOpen, setCompleteInviteLinks }: Pro {isLoading && } {!isLoading && - filterdUser?.map( - ({ user: u, inviteEmail, role, roleId, id: orgMembershipId, status, isActive }) => { - const name = u && u.firstName ? `${u.firstName} ${u.lastName}` : "-"; - const email = u?.email || inviteEmail; - const username = u?.username ?? inviteEmail ?? "-"; - return ( - router.push(`/org/${orgId}/memberships/${orgMembershipId}`)} - > - - - router.push(`/org/${orgId}/memberships/${orgMembershipId}`)} + > + + + - + - - ); - } - )} + if (currentOrg?.scimEnabled) { + createNotification({ + text: "You cannot manage users from Infisical when org-level auth is enforced for your organization", + type: "error" + }); + return; + } + + if (!isActive) { + // activate user + await updateOrgMembership({ + organizationId: orgId, + membershipId: orgMembershipId, + isActive: true + }); + + return; + } + + // deactivate user + handlePopUpOpen("deactivateMember", { + orgMembershipId, + username + }); + }} + disabled={!isAllowed} + > + {`${isActive ? "Deactivate" : "Activate"} User`} + + )} + + + {(isAllowed) => ( + { + e.stopPropagation(); + + if (currentOrg?.scimEnabled) { + createNotification({ + text: "You cannot manage users from Infisical when org-level auth is enforced for your organization", + type: "error" + }); + return; + } + + handlePopUpOpen("removeMember", { + orgMembershipId, + username + }); + }} + disabled={!isAllowed} + > + Remove User + + )} + + + + )} + + + ); + } + )}
NameUsername +
+ Name + handleSort(OrgMembersOrderBy.Name)} + > + + +
+
+
+ Email + handleSort(OrgMembersOrderBy.Email)} + > + + +
+
Role
- {name} - {u.superAdmin && ( - - Server Admin - - )} - {username} - - {(isAllowed) => ( - <> - {!isActive && ( - - )} - {isActive && status === "accepted" && ( - - )} - {isActive && - (status === "invited" || status === "verified") && - email && - serverDetails?.emailConfigured && ( + filteredUsers + .slice(offset, perPage * page) + .map( + ({ + user: u, + inviteEmail, + role, + roleId, + id: orgMembershipId, + status, + isActive + }) => { + const name = u && u.firstName ? `${u.firstName} ${u.lastName}` : "-"; + const email = u?.email || inviteEmail; + const username = u?.username ?? inviteEmail ?? "-"; + return ( +
+ {name} + {u.superAdmin && ( + + Server Admin + + )} + {username} + + {(isAllowed) => ( + <> + {!isActive && ( )} - - )} - - - {userId !== u?.id && ( - - -
- -
-
- - - {(isAllowed) => ( - { - e.stopPropagation(); - router.push(`/org/${orgId}/memberships/${orgMembershipId}`); - }} - disabled={!isAllowed} - > - Edit User - - )} - - - {(isAllowed) => ( - + onRoleChange(orgMembershipId, selectedRole) } - onClick={async (e) => { - e.stopPropagation(); - - if (currentOrg?.scimEnabled) { - createNotification({ - text: "You cannot manage users from Infisical when org-level auth is enforced for your organization", - type: "error" - }); - return; - } - - if (!isActive) { - // activate user - await updateOrgMembership({ - organizationId: orgId, - membershipId: orgMembershipId, - isActive: true - }); - - return; - } - - // deactivate user - handlePopUpOpen("deactivateMember", { - orgMembershipId, - username - }); - }} - disabled={!isAllowed} > - {`${isActive ? "Deactivate" : "Activate"} User`} - + {(roles || []) + .filter(({ slug }) => + slug === "owner" ? isIamOwner || role === "owner" : true + ) + .map(({ slug, name: roleName }) => ( + + {roleName} + + ))} + )} - - - {(isAllowed) => ( - { - e.stopPropagation(); - - if (currentOrg?.scimEnabled) { - createNotification({ - text: "You cannot manage users from Infisical when org-level auth is enforced for your organization", - type: "error" - }); - return; + {isActive && + (status === "invited" || status === "verified") && + email && + serverDetails?.emailConfigured && ( + + )} + + )} + +
+ {userId !== u?.id && ( + + +
+ +
+
+ + + {(isAllowed) => ( + { + e.stopPropagation(); + router.push(`/org/${orgId}/memberships/${orgMembershipId}`); + }} + disabled={!isAllowed} + > + Edit User + + )} + + + {(isAllowed) => ( + { + e.stopPropagation(); - handlePopUpOpen("removeMember", { - orgMembershipId, - username - }); - }} - disabled={!isAllowed} - > - Remove User - - )} - - -
- )} -
- {!isLoading && filterdUser?.length === 0 && ( + {Boolean(filteredUsers.length) && ( + + )} + {!isMembersLoading && !filteredUsers?.length && ( )} From 18a2547b245508f7f53312d94762199604ea5e02 Mon Sep 17 00:00:00 2001 From: Scott Wilson Date: Wed, 27 Nov 2024 20:35:15 -0800 Subject: [PATCH 6/7] improvement: move user groups to own tab and add pagination/search/sort to groups tables --- .../src/views/Org/MembersPage/MembersPage.tsx | 12 +- .../OrgGroupsSection/OrgGroupsTable.tsx | 332 ++++++++++-------- .../OrgMembersTab/OrgMembersTab.tsx | 2 - .../OrgMembersSection/OrgMembersTable.tsx | 2 +- frontend/src/views/Org/Types/TabSections.ts | 19 +- .../views/Project/MembersPage/MembersPage.tsx | 19 +- .../components/GroupsSection/GroupsTable.tsx | 233 ++++++++---- .../components/MembersTab/MembersTab.tsx | 5 - .../MembersTab/components/MembersTable.tsx | 2 +- 9 files changed, 381 insertions(+), 245 deletions(-) diff --git a/frontend/src/views/Org/MembersPage/MembersPage.tsx b/frontend/src/views/Org/MembersPage/MembersPage.tsx index 786e672ac..eb006fef9 100644 --- a/frontend/src/views/Org/MembersPage/MembersPage.tsx +++ b/frontend/src/views/Org/MembersPage/MembersPage.tsx @@ -7,7 +7,7 @@ import { OrgPermissionActions, OrgPermissionSubjects } from "@app/context"; import { withPermission } from "@app/hoc"; import { isTabSection, TabSections } from "@app/views/Org/Types"; -import { OrgIdentityTab, OrgMembersTab, OrgRoleTabSection } from "./components"; +import { OrgGroupsTab, OrgIdentityTab, OrgMembersTab, OrgRoleTabSection } from "./components"; export const MembersPage = withPermission( () => { @@ -25,9 +25,9 @@ export const MembersPage = withPermission( const updateSelectedTab = (tab: string) => { router.push({ pathname: router.pathname, - query: { ...router.query, selectedTab: tab }, + query: { ...router.query, selectedTab: tab } }); - } + }; return (
@@ -36,16 +36,20 @@ export const MembersPage = withPermission( Users + Groups

Machine Identities

- Organization Roles + Organization Roles
+ + + diff --git a/frontend/src/views/Org/MembersPage/components/OrgGroupsTab/components/OrgGroupsSection/OrgGroupsTable.tsx b/frontend/src/views/Org/MembersPage/components/OrgGroupsTab/components/OrgGroupsSection/OrgGroupsTable.tsx index 21582805c..ba1b11f22 100644 --- a/frontend/src/views/Org/MembersPage/components/OrgGroupsTab/components/OrgGroupsSection/OrgGroupsTable.tsx +++ b/frontend/src/views/Org/MembersPage/components/OrgGroupsTab/components/OrgGroupsSection/OrgGroupsTable.tsx @@ -1,9 +1,10 @@ -import { useMemo, useState } from "react"; +import { useMemo } from "react"; import { faArrowDown, faArrowUp, faEllipsis, faMagnifyingGlass, + faSearch, faUsers } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; @@ -19,6 +20,7 @@ import { EmptyState, IconButton, Input, + Pagination, Select, SelectItem, Table, @@ -31,7 +33,7 @@ import { Tr } from "@app/components/v2"; import { OrgPermissionActions, OrgPermissionSubjects, useOrganization } from "@app/context"; -import { useDebounce } from "@app/hooks"; +import { usePagination, useResetPageHelper } from "@app/hooks"; import { useGetOrganizationGroups, useGetOrgRoles, useUpdateGroup } from "@app/hooks/api"; import { OrderByDirection } from "@app/hooks/api/generic/types"; import { UsePopUpState } from "@app/hooks/usePopUp"; @@ -59,14 +61,10 @@ enum GroupsOrderBy { } export const OrgGroupsTable = ({ handlePopUpOpen }: Props) => { - const [searchGroupsFilter, setSearchGroupsFilter] = useState(""); - const [debouncedSearch] = useDebounce(searchGroupsFilter.trim()); const { currentOrg } = useOrganization(); const orgId = currentOrg?.id || ""; - const { isLoading, data: groups } = useGetOrganizationGroups(orgId); + const { isLoading, data: groups = [] } = useGetOrganizationGroups(orgId); const { mutateAsync: updateMutateAsync } = useUpdateGroup(); - const [orderBy, setOrderBy] = useState(GroupsOrderBy.Name); - const [orderDirection, setOrderDirection] = useState(OrderByDirection.ASC); const { data: roles } = useGetOrgRoles(orgId); @@ -90,12 +88,27 @@ export const OrgGroupsTable = ({ handlePopUpOpen }: Props) => { } }; + const { + search, + setSearch, + setPage, + page, + perPage, + setPerPage, + offset, + orderDirection, + orderBy, + setOrderBy, + setOrderDirection, + toggleOrderDirection + } = usePagination(GroupsOrderBy.Name, { initPerPage: 20 }); + const filteredGroups = useMemo(() => { - const filtered = debouncedSearch + const filtered = search ? groups?.filter( ({ name, slug }) => - name.toLowerCase().includes(debouncedSearch.toLowerCase()) || - slug.toLowerCase().includes(debouncedSearch.toLowerCase()) + name.toLowerCase().includes(search.toLowerCase()) || + slug.toLowerCase().includes(search.toLowerCase()) ) : groups; @@ -113,13 +126,11 @@ export const OrgGroupsTable = ({ handlePopUpOpen }: Props) => { }); return orderDirection === OrderByDirection.ASC ? ordered : ordered?.reverse(); - }, [debouncedSearch, groups, orderBy, orderDirection]); + }, [search, groups, orderBy, orderDirection]); const handleSort = (column: GroupsOrderBy) => { if (column === orderBy) { - setOrderDirection((prev) => - prev === OrderByDirection.ASC ? OrderByDirection.DESC : OrderByDirection.ASC - ); + toggleOrderDirection(); return; } @@ -127,11 +138,17 @@ export const OrgGroupsTable = ({ handlePopUpOpen }: Props) => { setOrderDirection(OrderByDirection.ASC); }; + useResetPageHelper({ + totalCount: filteredGroups.length, + offset, + setPage + }); + return (
setSearchGroupsFilter(e.target.value)} + value={search} + onChange={(e) => setSearch(e.target.value)} leftIcon={} placeholder="Search groups..." /> @@ -202,143 +219,160 @@ export const OrgGroupsTable = ({ handlePopUpOpen }: Props) => { {isLoading && } {!isLoading && - filteredGroups?.map(({ id, name, slug, role, customRole }) => { - return ( - - {name} - {slug} - - - {(isAllowed) => { - return ( - + handleChangeRole({ + id, + role: selectedRole + }) + } + > + {(roles || []).map(({ slug: roleSlug, name: roleName }) => ( + + {roleName} + + ))} + + ); + }} + + + + + +
+ +
+
+ + { + e.stopPropagation(); + createNotification({ + text: "Copied group ID to clipboard", + type: "info" + }); + navigator.clipboard.writeText(id); + }} > - {(roles || []).map(({ slug: roleSlug, name: roleName }) => ( - - {roleName} - - ))} - - ); - }} - - - - - -
- -
-
- - { - e.stopPropagation(); - createNotification({ - text: "Copied group ID to clipboard", - type: "info" - }); - navigator.clipboard.writeText(id); - }} - > - Copy Group ID - - - {(isAllowed) => ( - { - e.stopPropagation(); - handlePopUpOpen("groupMembers", { - groupId: id, - slug - }); - }} - disabled={!isAllowed} - > - Manage Users - - )} - - - {(isAllowed) => ( - { - e.stopPropagation(); - handlePopUpOpen("group", { - groupId: id, - name, - slug, - role, - customRole - }); - }} - disabled={!isAllowed} - > - Edit Group - - )} - - - {(isAllowed) => ( - { - e.stopPropagation(); - handlePopUpOpen("deleteGroup", { - groupId: id, - name - }); - }} - disabled={!isAllowed} - > - Delete Group - - )} - - -
- - - ); - })} + Copy Group ID +
+ + {(isAllowed) => ( + { + e.stopPropagation(); + handlePopUpOpen("groupMembers", { + groupId: id, + slug + }); + }} + disabled={!isAllowed} + > + Manage Users + + )} + + + {(isAllowed) => ( + { + e.stopPropagation(); + handlePopUpOpen("group", { + groupId: id, + name, + slug, + role, + customRole + }); + }} + disabled={!isAllowed} + > + Edit Group + + )} + + + {(isAllowed) => ( + { + e.stopPropagation(); + handlePopUpOpen("deleteGroup", { + groupId: id, + name + }); + }} + disabled={!isAllowed} + > + Delete Group + + )} + +
+
+ + + ); + })} - {filteredGroups?.length === 0 && ( + {Boolean(filteredGroups.length) && ( + + )} + {!isLoading && !filteredGroups?.length && ( )} diff --git a/frontend/src/views/Org/MembersPage/components/OrgMembersTab/OrgMembersTab.tsx b/frontend/src/views/Org/MembersPage/components/OrgMembersTab/OrgMembersTab.tsx index 36230c971..1afb9bfbd 100644 --- a/frontend/src/views/Org/MembersPage/components/OrgMembersTab/OrgMembersTab.tsx +++ b/frontend/src/views/Org/MembersPage/components/OrgMembersTab/OrgMembersTab.tsx @@ -1,6 +1,5 @@ import { motion } from "framer-motion"; -import { OrgGroupsSection } from "../OrgGroupsTab/components"; import { OrgMembersSection } from "./components"; export const OrgMembersTab = () => { @@ -13,7 +12,6 @@ export const OrgMembersTab = () => { exit={{ opacity: 0, translateX: 30 }} > - ); }; diff --git a/frontend/src/views/Org/MembersPage/components/OrgMembersTab/components/OrgMembersSection/OrgMembersTable.tsx b/frontend/src/views/Org/MembersPage/components/OrgMembersTab/components/OrgMembersSection/OrgMembersTable.tsx index 2533b54d0..942ad549d 100644 --- a/frontend/src/views/Org/MembersPage/components/OrgMembersTab/components/OrgMembersSection/OrgMembersTable.tsx +++ b/frontend/src/views/Org/MembersPage/components/OrgMembersTab/components/OrgMembersSection/OrgMembersTable.tsx @@ -171,7 +171,7 @@ export const OrgMembersTable = ({ handlePopUpOpen, setCompleteInviteLinks }: Pro setOrderBy, setOrderDirection, toggleOrderDirection - } = usePagination(OrgMembersOrderBy.Name); + } = usePagination(OrgMembersOrderBy.Name, { initPerPage: 20 }); const filteredUsers = useMemo( () => diff --git a/frontend/src/views/Org/Types/TabSections.ts b/frontend/src/views/Org/Types/TabSections.ts index 945370d45..0ddfc4452 100644 --- a/frontend/src/views/Org/Types/TabSections.ts +++ b/frontend/src/views/Org/Types/TabSections.ts @@ -1,9 +1,10 @@ -export enum TabSections { - Member = "members", - Roles = "roles", - Identities = "identities" -} - -export const isTabSection = (value: string): value is TabSections => { - return (Object.values(TabSections) as string[]).includes(value); -} +export enum TabSections { + Member = "members", + Groups = "groups", + Roles = "roles", + Identities = "identities" +} + +export const isTabSection = (value: string): value is TabSections => { + return (Object.values(TabSections) as string[]).includes(value); +}; diff --git a/frontend/src/views/Project/MembersPage/MembersPage.tsx b/frontend/src/views/Project/MembersPage/MembersPage.tsx index f3af1454e..a05787286 100644 --- a/frontend/src/views/Project/MembersPage/MembersPage.tsx +++ b/frontend/src/views/Project/MembersPage/MembersPage.tsx @@ -6,9 +6,14 @@ import { Tab, TabList, TabPanel, Tabs } from "@app/components/v2"; import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context"; import { withProjectPermission } from "@app/hoc"; -import { isTabSection,TabSections } from "../Types"; -import { IdentityTab, MembersTab,ProjectRoleListTab, ServiceTokenTab } from "./components"; - +import { isTabSection, TabSections } from "../Types"; +import { + GroupsTab, + IdentityTab, + MembersTab, + ProjectRoleListTab, + ServiceTokenTab +} from "./components"; export const MembersPage = withProjectPermission( () => { @@ -26,9 +31,9 @@ export const MembersPage = withProjectPermission( const updateSelectedTab = (tab: string) => { router.push({ pathname: router.pathname, - query: { ...router.query, selectedTab: tab }, + query: { ...router.query, selectedTab: tab } }); - } + }; return (
@@ -37,6 +42,7 @@ export const MembersPage = withProjectPermission( Users + Groups

Machine Identities

@@ -48,6 +54,9 @@ export const MembersPage = withProjectPermission( + + + diff --git a/frontend/src/views/Project/MembersPage/components/GroupsTab/components/GroupsSection/GroupsTable.tsx b/frontend/src/views/Project/MembersPage/components/GroupsTab/components/GroupsSection/GroupsTable.tsx index 4bebd7637..f1d9ea1a3 100644 --- a/frontend/src/views/Project/MembersPage/components/GroupsTab/components/GroupsSection/GroupsTable.tsx +++ b/frontend/src/views/Project/MembersPage/components/GroupsTab/components/GroupsSection/GroupsTable.tsx @@ -1,4 +1,12 @@ -import { faServer, faTrash } from "@fortawesome/free-solid-svg-icons"; +import { useMemo } from "react"; +import { + faArrowDown, + faArrowUp, + faMagnifyingGlass, + faSearch, + faTrash, + faUsers +} from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { format } from "date-fns"; @@ -6,6 +14,8 @@ import { ProjectPermissionCan } from "@app/components/permissions"; import { EmptyState, IconButton, + Input, + Pagination, Table, TableContainer, TableSkeleton, @@ -17,7 +27,9 @@ import { Tr } from "@app/components/v2"; import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context"; +import { usePagination, useResetPageHelper } from "@app/hooks"; import { useListWorkspaceGroups } from "@app/hooks/api"; +import { OrderByDirection } from "@app/hooks/api/generic/types"; import { UsePopUpState } from "@app/hooks/usePopUp"; import { GroupRoles } from "./GroupRoles"; @@ -32,76 +44,159 @@ type Props = { ) => void; }; +enum GroupsOrderBy { + Name = "name" +} + export const GroupTable = ({ handlePopUpOpen }: Props) => { const { currentWorkspace } = useWorkspace(); - const { data, isLoading } = useListWorkspaceGroups(currentWorkspace?.id || ""); + + const { + search, + setSearch, + setPage, + page, + perPage, + setPerPage, + offset, + orderDirection, + orderBy, + toggleOrderDirection + } = usePagination(GroupsOrderBy.Name, { initPerPage: 20 }); + + const { data: groupMemberships = [], isLoading } = useListWorkspaceGroups( + currentWorkspace?.id || "" + ); + + const filteredGroupMemberships = useMemo(() => { + const filtered = search + ? groupMemberships?.filter( + ({ group: { name, slug } }) => + name.toLowerCase().includes(search.toLowerCase()) || + slug.toLowerCase().includes(search.toLowerCase()) + ) + : groupMemberships; + + const ordered = filtered?.sort((a, b) => + a.group.name.toLowerCase().localeCompare(b.group.name.toLowerCase()) + ); + + return orderDirection === OrderByDirection.ASC ? ordered : ordered?.reverse(); + }, [search, groupMemberships, orderBy, orderDirection]); + + useResetPageHelper({ + totalCount: filteredGroupMemberships.length, + offset, + setPage + }); + return ( - - - - - - - - - - - {isLoading && } - {!isLoading && - data && - data.length > 0 && - data.map(({ group: { id, name }, roles, createdAt }) => { - return ( - - - - - - - ); - })} - -
NameRoleAdded on -
{name} - - {(isAllowed) => ( - - )} - - {format(new Date(createdAt), "yyyy-MM-dd")} - - {(isAllowed) => ( -
- - { - handlePopUpOpen("deleteGroup", { - id, - name - }); - }} - colorSchema="danger" - variant="plain" - ariaLabel="update" - className="ml-4" - isDisabled={!isAllowed} - > - - - -
- )} -
-
- {!isLoading && data?.length === 0 && ( - - )} -
+
+ setSearch(e.target.value)} + leftIcon={} + placeholder="Search members..." + /> + + + + + + + + + + + {isLoading && } + {!isLoading && + filteredGroupMemberships && + filteredGroupMemberships.length > 0 && + filteredGroupMemberships + .slice(offset, perPage * page) + .map(({ group: { id, name }, roles, createdAt }) => { + return ( + + + + + + + ); + })} + +
+
+ Name + + + +
+
RoleAdded on +
{name} + + {(isAllowed) => ( + + )} + + {format(new Date(createdAt), "yyyy-MM-dd")} + + {(isAllowed) => ( +
+ + { + handlePopUpOpen("deleteGroup", { + id, + name + }); + }} + colorSchema="danger" + variant="plain" + ariaLabel="update" + className="ml-4" + isDisabled={!isAllowed} + > + + + +
+ )} +
+
+ {Boolean(filteredGroupMemberships.length) && ( + + )} + {!isLoading && !filteredGroupMemberships?.length && ( + + )} +
+
); }; diff --git a/frontend/src/views/Project/MembersPage/components/MembersTab/MembersTab.tsx b/frontend/src/views/Project/MembersPage/components/MembersTab/MembersTab.tsx index 25ffc38da..6b5660c83 100644 --- a/frontend/src/views/Project/MembersPage/components/MembersTab/MembersTab.tsx +++ b/frontend/src/views/Project/MembersPage/components/MembersTab/MembersTab.tsx @@ -1,12 +1,8 @@ import { motion } from "framer-motion"; -import { useWorkspace } from "@app/context"; - -import { GroupsSection } from "../GroupsTab/components"; import { MembersSection } from "./components"; export const MembersTab = () => { - const { currentWorkspace } = useWorkspace(); return ( { exit={{ opacity: 0, translateX: 30 }} > - {currentWorkspace?.version && currentWorkspace.version > 1 && } ); }; diff --git a/frontend/src/views/Project/MembersPage/components/MembersTab/components/MembersTable.tsx b/frontend/src/views/Project/MembersPage/components/MembersTab/components/MembersTable.tsx index d81d4005c..d90468620 100644 --- a/frontend/src/views/Project/MembersPage/components/MembersTab/components/MembersTable.tsx +++ b/frontend/src/views/Project/MembersPage/components/MembersTab/components/MembersTable.tsx @@ -86,7 +86,7 @@ export const MembersTable = ({ handlePopUpOpen }: Props) => { setOrderBy, setOrderDirection, toggleOrderDirection - } = usePagination(MembersOrderBy.Name); + } = usePagination(MembersOrderBy.Name, { initPerPage: 20 }); const { data: members = [], isLoading: isMembersLoading } = useGetWorkspaceUsers(workspaceId); From 6c4cb5e08463de6eaca256b2494f2312525736f5 Mon Sep 17 00:00:00 2001 From: Scott Wilson Date: Thu, 28 Nov 2024 08:54:27 -0800 Subject: [PATCH 7/7] improvements: address feedback --- .../components/OrgGroupsSection/OrgGroupsSection.tsx | 2 +- .../components/OrgMembersSection/OrgMembersTable.tsx | 3 +++ .../components/MembersTab/components/AddMemberModal.tsx | 9 ++++++--- .../components/MembersTab/components/MembersTable.tsx | 3 +++ 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/frontend/src/views/Org/MembersPage/components/OrgGroupsTab/components/OrgGroupsSection/OrgGroupsSection.tsx b/frontend/src/views/Org/MembersPage/components/OrgGroupsTab/components/OrgGroupsSection/OrgGroupsSection.tsx index 7738d1353..f72adf61f 100644 --- a/frontend/src/views/Org/MembersPage/components/OrgGroupsTab/components/OrgGroupsSection/OrgGroupsSection.tsx +++ b/frontend/src/views/Org/MembersPage/components/OrgGroupsTab/components/OrgGroupsSection/OrgGroupsSection.tsx @@ -57,7 +57,7 @@ export const OrgGroupsSection = () => { return (
-

User Groups

+

Groups

{(isAllowed) => (