From bbf52c9a48330971eb317021098127fffe24476b Mon Sep 17 00:00:00 2001 From: Scott Wilson Date: Tue, 26 Nov 2024 11:47:59 -0800 Subject: [PATCH 1/3] improvement: add pagination to the project overview page with minor UI adjustments --- frontend/src/hooks/usePagination.tsx | 11 +- .../src/pages/org/[id]/overview/index.tsx | 230 ++++++++++++------ 2 files changed, 163 insertions(+), 78 deletions(-) diff --git a/frontend/src/hooks/usePagination.tsx b/frontend/src/hooks/usePagination.tsx index 3d3002b4c..b89a4f92c 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(""); diff --git a/frontend/src/pages/org/[id]/overview/index.tsx b/frontend/src/pages/org/[id]/overview/index.tsx index 75c2a904f..54dcc60de 100644 --- a/frontend/src/pages/org/[id]/overview/index.tsx +++ b/frontend/src/pages/org/[id]/overview/index.tsx @@ -1,6 +1,6 @@ // REFACTOR(akhilmhdh): This file needs to be split into multiple components too complex -import { useEffect, useMemo, useState } from "react"; +import { ReactNode, useEffect, useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; import Head from "next/head"; import Link from "next/link"; @@ -9,20 +9,22 @@ import { IconProp } from "@fortawesome/fontawesome-svg-core"; import { faSlack } from "@fortawesome/free-brands-svg-icons"; import { faFolderOpen, faStar } from "@fortawesome/free-regular-svg-icons"; import { + faArrowDownAZ, faArrowRight, faArrowUpRightFromSquare, + faArrowUpZA, faBorderAll, faCheck, faCheckCircle, faClipboard, faExclamationCircle, - faFileShield, faHandPeace, faList, faMagnifyingGlass, faNetworkWired, faPlug, faPlus, + faSearch, faStar as faSolidStar, faUserPlus } from "@fortawesome/free-solid-svg-icons"; @@ -32,7 +34,15 @@ import * as Tabs from "@radix-ui/react-tabs"; import { createNotification } from "@app/components/notifications"; import { OrgPermissionCan } from "@app/components/permissions"; import onboardingCheck from "@app/components/utilities/checks/OnboardingCheck"; -import { Button, IconButton, Input, Skeleton, UpgradePlanModal } from "@app/components/v2"; +import { + Button, + IconButton, + Input, + Pagination, + Skeleton, + Tooltip, + UpgradePlanModal +} from "@app/components/v2"; import { NewProjectModal } from "@app/components/v2/projects"; import { OrgPermissionActions, @@ -42,7 +52,9 @@ import { useUser, useWorkspace } from "@app/context"; +import { usePagination, useResetPageHelper } from "@app/hooks"; import { useRegisterUserAction } from "@app/hooks/api"; +import { OrderByDirection } from "@app/hooks/api/generic/types"; // import { fetchUserWsKey } from "@app/hooks/api/keys/queries"; import { useFetchServerStatus } from "@app/hooks/api/serverDetails"; import { Workspace } from "@app/hooks/api/types"; @@ -81,6 +93,10 @@ enum ProjectsViewMode { LIST = "list" } +enum ProjectOrderBy { + Name = "name" +} + function copyToClipboard(id: string, setState: (value: boolean) => void) { // Get the text field const copyText = document.getElementById(id) as HTMLInputElement; @@ -496,26 +512,40 @@ const OrganizationPage = () => { }); }, []); - const isWorkspaceEmpty = !isWorkspaceLoading && orgWorkspaces?.length === 0; - const filteredWorkspaces = orgWorkspaces.filter((ws) => - ws?.name?.toLowerCase().includes(searchFilter.toLowerCase()) + const isWorkspaceEmpty = !isProjectViewLoading && orgWorkspaces?.length === 0; + + const { setPage, perPage, setPerPage, page, offset, limit, setOrderDirection, orderDirection } = + usePagination(ProjectOrderBy.Name, { initPerPage: 24 }); + + const filteredWorkspaces = useMemo( + () => + orgWorkspaces + .filter((ws) => ws?.name?.toLowerCase().includes(searchFilter.toLowerCase())) + .sort((a, b) => + orderDirection === OrderByDirection.ASC + ? a.name.localeCompare(b.name) + : b.name.localeCompare(a.name) + ), + [searchFilter, page, perPage, orderDirection, offset, limit] ); - const { workspacesWithFaveProp, favoriteWorkspaces, nonFavoriteWorkspaces } = useMemo(() => { + useResetPageHelper({ + setPage, + offset, + totalCount: filteredWorkspaces.length + }); + + const { workspacesWithFaveProp } = useMemo(() => { const workspacesWithFav = filteredWorkspaces .map((w): Workspace & { isFavorite: boolean } => ({ ...w, isFavorite: Boolean(projectFavorites?.includes(w.id)) })) - .sort((a, b) => Number(b.isFavorite) - Number(a.isFavorite)); - - const favWorkspaces = workspacesWithFav.filter((w) => w.isFavorite); - const nonFavWorkspaces = workspacesWithFav.filter((w) => !w.isFavorite); + .sort((a, b) => Number(b.isFavorite) - Number(a.isFavorite)) + .slice(offset, limit * page); return { - workspacesWithFaveProp: workspacesWithFav, - favoriteWorkspaces: favWorkspaces, - nonFavoriteWorkspaces: nonFavWorkspaces + workspacesWithFaveProp: workspacesWithFav }; }, [filteredWorkspaces, projectFavorites]); @@ -566,7 +596,7 @@ const OrganizationPage = () => { {isFavorite ? ( { e.stopPropagation(); removeProjectFromFavorites(workspace.id); @@ -623,11 +653,10 @@ const OrganizationPage = () => { key={workspace.id} className={`min-w-72 group grid h-14 cursor-pointer grid-cols-6 border-t border-l border-r border-mineshaft-600 bg-mineshaft-800 px-6 hover:bg-mineshaft-700 ${ index === 0 && "rounded-t-md" - } ${index === filteredWorkspaces.length - 1 && "rounded-b-md border-b"}`} + }`} >
- -
{workspace.name}
+
{workspace.name}
@@ -636,7 +665,7 @@ const OrganizationPage = () => { {isFavorite ? ( { e.stopPropagation(); removeProjectFromFavorites(workspace.id); @@ -656,63 +685,75 @@ const OrganizationPage = () => {
); - const projectsGridView = ( - <> - {favoriteWorkspaces.length > 0 && ( - <> -

Favorites

-
0 && "border-b border-mineshaft-600" - } py-4 lg:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-4`} - > - {favoriteWorkspaces.map((workspace) => renderProjectGridItem(workspace, true))} -
- - )} -
- {isProjectViewLoading && - Array.apply(0, Array(3)).map((_x, i) => ( -
-
- -
-
- -
-
- -
-
- ))} - {!isProjectViewLoading && - nonFavoriteWorkspaces.map((workspace) => renderProjectGridItem(workspace, false))} -
- - ); + let projectsComponents: ReactNode; - const projectsListView = ( -
- {isProjectViewLoading && - Array.apply(0, Array(3)).map((_x, i) => ( -
- + if (filteredWorkspaces.length || isProjectViewLoading) { + switch (projectsViewMode) { + case ProjectsViewMode.GRID: + projectsComponents = ( +
+ {isProjectViewLoading && + Array.apply(0, Array(3)).map((_x, i) => ( +
+
+ +
+
+ +
+
+ +
+
+ ))} + {!isProjectViewLoading && ( + <> + {workspacesWithFaveProp.map((workspace) => + renderProjectGridItem(workspace, workspace.isFavorite) + )} + + )}
- ))} - {!isProjectViewLoading && - workspacesWithFaveProp.map((workspace, ind) => - renderProjectListItem(workspace, workspace.isFavorite, ind) - )} -
- ); + ); + + break; + case ProjectsViewMode.LIST: + default: + projectsComponents = ( +
+ {isProjectViewLoading && + Array.apply(0, Array(3)).map((_x, i) => ( +
+ +
+ ))} + {!isProjectViewLoading && + workspacesWithFaveProp.map((workspace, ind) => + renderProjectListItem(workspace, workspace.isFavorite, ind) + )} +
+ ); + break; + } + } else if (orgWorkspaces.length) { + projectsComponents = ( +
+ +
No projects match search...
+
+ ); + } return (
@@ -754,6 +795,28 @@ const OrganizationPage = () => { onChange={(e) => setSearchFilter(e.target.value)} leftIcon={} /> +
+ + + setOrderDirection((prev) => + prev === OrderByDirection.ASC ? OrderByDirection.DESC : OrderByDirection.ASC + ) + } + > + + + +
{ )}
- {projectsViewMode === ProjectsViewMode.LIST ? projectsListView : projectsGridView} + {projectsComponents} + {!isProjectViewLoading && Boolean(filteredWorkspaces.length) && ( + + )} {isWorkspaceEmpty && ( -
+
Date: Tue, 26 Nov 2024 17:25:13 -0800 Subject: [PATCH 2/3] fix: lowercase name compare for sort --- frontend/src/pages/org/[id]/overview/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/pages/org/[id]/overview/index.tsx b/frontend/src/pages/org/[id]/overview/index.tsx index 54dcc60de..ed5b13100 100644 --- a/frontend/src/pages/org/[id]/overview/index.tsx +++ b/frontend/src/pages/org/[id]/overview/index.tsx @@ -523,8 +523,8 @@ const OrganizationPage = () => { .filter((ws) => ws?.name?.toLowerCase().includes(searchFilter.toLowerCase())) .sort((a, b) => orderDirection === OrderByDirection.ASC - ? a.name.localeCompare(b.name) - : b.name.localeCompare(a.name) + ? a.name.toLowerCase().localeCompare(b.name.toLowerCase()) + : b.name.toLowerCase().localeCompare(a.name.toLowerCase()) ), [searchFilter, page, perPage, orderDirection, offset, limit] ); From cf5f49d14ede1b5a70cb01da0f311ad20e7e1880 Mon Sep 17 00:00:00 2001 From: Scott Wilson Date: Tue, 26 Nov 2024 17:26:49 -0800 Subject: [PATCH 3/3] chore: use toggle order --- frontend/src/hooks/usePagination.tsx | 6 +++++- frontend/src/pages/org/[id]/overview/index.tsx | 18 +++++++++++------- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/frontend/src/hooks/usePagination.tsx b/frontend/src/hooks/usePagination.tsx index b89a4f92c..e4c9fddbb 100644 --- a/frontend/src/hooks/usePagination.tsx +++ b/frontend/src/hooks/usePagination.tsx @@ -33,6 +33,10 @@ export const usePagination = ( search, setSearch, orderBy, - setOrderBy + setOrderBy, + toggleOrderDirection: () => + setOrderDirection((prev) => + prev === OrderByDirection.DESC ? OrderByDirection.ASC : OrderByDirection.DESC + ) }; }; diff --git a/frontend/src/pages/org/[id]/overview/index.tsx b/frontend/src/pages/org/[id]/overview/index.tsx index ed5b13100..45fc7f3d2 100644 --- a/frontend/src/pages/org/[id]/overview/index.tsx +++ b/frontend/src/pages/org/[id]/overview/index.tsx @@ -514,8 +514,16 @@ const OrganizationPage = () => { const isWorkspaceEmpty = !isProjectViewLoading && orgWorkspaces?.length === 0; - const { setPage, perPage, setPerPage, page, offset, limit, setOrderDirection, orderDirection } = - usePagination(ProjectOrderBy.Name, { initPerPage: 24 }); + const { + setPage, + perPage, + setPerPage, + page, + offset, + limit, + toggleOrderDirection, + orderDirection + } = usePagination(ProjectOrderBy.Name, { initPerPage: 24 }); const filteredWorkspaces = useMemo( () => @@ -805,11 +813,7 @@ const OrganizationPage = () => { variant="plain" size="xs" colorSchema="secondary" - onClick={() => - setOrderDirection((prev) => - prev === OrderByDirection.ASC ? OrderByDirection.DESC : OrderByDirection.ASC - ) - } + onClick={toggleOrderDirection} >