From dab8f0b2610f4d4cacf4691d62c40a7bd3eac676 Mon Sep 17 00:00:00 2001 From: Scott Wilson <scottraywilson@gmail.com> Date: Thu, 28 Nov 2024 14:29:41 -0800 Subject: [PATCH] improvement: secret tags table pagination --- .../UserProjectsSection/UserGroupsTable.tsx | 5 +- .../SecretTagsSection/SecretTagsSection.tsx | 3 +- .../SecretTagsSection/SecretTagsTable.tsx | 175 +++++++++++++----- 3 files changed, 130 insertions(+), 53 deletions(-) diff --git a/frontend/src/views/Org/UserPage/components/UserProjectsSection/UserGroupsTable.tsx b/frontend/src/views/Org/UserPage/components/UserProjectsSection/UserGroupsTable.tsx index 999ffe794..af136d7ff 100644 --- a/frontend/src/views/Org/UserPage/components/UserProjectsSection/UserGroupsTable.tsx +++ b/frontend/src/views/Org/UserPage/components/UserProjectsSection/UserGroupsTable.tsx @@ -57,7 +57,7 @@ export const UserGroupsTable = ({ handlePopUpOpen, orgMembership }: Props) => { const filteredGroupMemberships = useMemo( () => groupMemberships - ?.filter((group) => group.name.toLowerCase().includes(search.trim().toLowerCase())) + .filter((group) => group.name.toLowerCase().includes(search.trim().toLowerCase())) .sort((a, b) => { const [membershipOne, membershipTwo] = orderDirection === OrderByDirection.ASC ? [a, b] : [b, a]; @@ -72,13 +72,14 @@ export const UserGroupsTable = ({ handlePopUpOpen, orgMembership }: Props) => { offset, setPage }); + return ( <div> <Input value={search} onChange={(e) => setSearch(e.target.value)} leftIcon={<FontAwesomeIcon icon={faMagnifyingGlass} />} - placeholder="Search projects..." + placeholder="Search groups..." /> <TableContainer className="mt-4"> <Table> diff --git a/frontend/src/views/Settings/ProjectSettingsPage/components/SecretTagsSection/SecretTagsSection.tsx b/frontend/src/views/Settings/ProjectSettingsPage/components/SecretTagsSection/SecretTagsSection.tsx index d1ba06835..26be8eb17 100644 --- a/frontend/src/views/Settings/ProjectSettingsPage/components/SecretTagsSection/SecretTagsSection.tsx +++ b/frontend/src/views/Settings/ProjectSettingsPage/components/SecretTagsSection/SecretTagsSection.tsx @@ -19,7 +19,6 @@ import { SecretTagsTable } from "./SecretTagsTable"; type DeleteModalData = { name: string; id: string }; export const SecretTagsSection = (): JSX.Element => { - const { popUp, handlePopUpToggle, handlePopUpClose, handlePopUpOpen } = usePopUp([ "CreateSecretTag", "deleteTagConfirmation" @@ -65,7 +64,7 @@ export const SecretTagsSection = (): JSX.Element => { }} isDisabled={!isAllowed} > - Create tag + Create Tag </Button> )} </ProjectPermissionCan> diff --git a/frontend/src/views/Settings/ProjectSettingsPage/components/SecretTagsSection/SecretTagsTable.tsx b/frontend/src/views/Settings/ProjectSettingsPage/components/SecretTagsSection/SecretTagsTable.tsx index cc68b0700..b6793ea1d 100644 --- a/frontend/src/views/Settings/ProjectSettingsPage/components/SecretTagsSection/SecretTagsTable.tsx +++ b/frontend/src/views/Settings/ProjectSettingsPage/components/SecretTagsSection/SecretTagsTable.tsx @@ -1,10 +1,20 @@ -import { faTags, faTrashCan } from "@fortawesome/free-solid-svg-icons"; +import { useMemo } from "react"; +import { + faArrowDown, + faArrowUp, + faMagnifyingGlass, + faSearch, + faTag, + faTrashCan +} from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { ProjectPermissionCan } from "@app/components/permissions"; import { EmptyState, IconButton, + Input, + Pagination, Table, TableContainer, TableSkeleton, @@ -15,7 +25,9 @@ import { Tr } from "@app/components/v2"; import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context"; +import { usePagination, useResetPageHelper } from "@app/hooks"; import { useGetWsTags } from "@app/hooks/api"; +import { OrderByDirection } from "@app/hooks/api/generic/types"; import { UsePopUpState } from "@app/hooks/usePopUp"; type Props = { @@ -31,59 +43,124 @@ type Props = { ) => void; }; +enum TagsOrderBy { + Slug = "slug" +} + export const SecretTagsTable = ({ handlePopUpOpen }: Props) => { const { currentWorkspace } = useWorkspace(); - const { data, isLoading } = useGetWsTags(currentWorkspace?.id ?? ""); + const { data: tags = [], isLoading } = useGetWsTags(currentWorkspace?.id ?? ""); + + const { + search, + setSearch, + setPage, + page, + perPage, + setPerPage, + offset, + orderDirection, + toggleOrderDirection + } = usePagination(TagsOrderBy.Slug, { initPerPage: 10 }); + + const filteredTags = useMemo( + () => + tags + .filter((tag) => tag.slug.toLowerCase().includes(search.trim().toLowerCase())) + .sort((a, b) => { + const [tagOne, tagTwo] = orderDirection === OrderByDirection.ASC ? [a, b] : [b, a]; + + return tagOne.slug.toLowerCase().localeCompare(tagTwo.slug.toLowerCase()); + }), + [tags, orderDirection, search] + ); + + useResetPageHelper({ + totalCount: filteredTags.length, + offset, + setPage + }); return ( - <TableContainer className="mt-4"> - <Table> - <THead> - <Tr> - <Th>Slug</Th> - <Th aria-label="button" /> - </Tr> - </THead> - <TBody> - {isLoading && <TableSkeleton columns={3} innerKey="secret-tags" />} - {!isLoading && - data && - data.map(({ id, slug }) => ( - <Tr key={id}> - <Td>{slug}</Td> - <Td className="flex items-center justify-end"> - <ProjectPermissionCan - I={ProjectPermissionActions.Delete} - a={ProjectPermissionSub.Tags} - > - {(isAllowed) => ( - <IconButton - onClick={() => - handlePopUpOpen("deleteTagConfirmation", { - name: slug, - id - }) - } - colorSchema="danger" - ariaLabel="update" - isDisabled={!isAllowed} - > - <FontAwesomeIcon icon={faTrashCan} /> - </IconButton> - )} - </ProjectPermissionCan> - </Td> - </Tr> - ))} - {!isLoading && data && data?.length === 0 && ( + <div> + <Input + value={search} + onChange={(e) => setSearch(e.target.value)} + leftIcon={<FontAwesomeIcon icon={faMagnifyingGlass} />} + placeholder="Search tags..." + /> + <TableContainer className="mt-4"> + <Table> + <THead> <Tr> - <Td colSpan={3}> - <EmptyState title="No secret tags found" icon={faTags} /> - </Td> + <Th className="w-full"> + <div className="flex items-center"> + Slug + <IconButton + variant="plain" + className="ml-2" + ariaLabel="sort" + onClick={toggleOrderDirection} + > + <FontAwesomeIcon + icon={orderDirection === OrderByDirection.DESC ? faArrowUp : faArrowDown} + /> + </IconButton> + </div> + </Th> + <Th aria-label="button" /> </Tr> - )} - </TBody> - </Table> - </TableContainer> + </THead> + <TBody> + {isLoading && <TableSkeleton columns={3} innerKey="secret-tags" />} + {!isLoading && + filteredTags.slice(offset, perPage * page).map(({ id, slug }) => ( + <Tr key={id}> + <Td>{slug}</Td> + <Td className="flex items-center justify-end"> + <ProjectPermissionCan + I={ProjectPermissionActions.Delete} + a={ProjectPermissionSub.Tags} + > + {(isAllowed) => ( + <IconButton + onClick={() => + handlePopUpOpen("deleteTagConfirmation", { + name: slug, + id + }) + } + size="xs" + colorSchema="danger" + ariaLabel="update" + variant="plain" + isDisabled={!isAllowed} + > + <FontAwesomeIcon icon={faTrashCan} /> + </IconButton> + )} + </ProjectPermissionCan> + </Td> + </Tr> + ))} + </TBody> + </Table> + {Boolean(filteredTags.length) && ( + <Pagination + count={filteredTags.length} + page={page} + perPage={perPage} + onChangePage={setPage} + onChangePerPage={setPerPage} + /> + )} + {!isLoading && !filteredTags?.length && ( + <EmptyState + title={tags.length ? "No tags match search..." : "No tags found for project"} + icon={tags.length ? faSearch : faTag} + /> + )} + </TableContainer> + </div> ); };