From 98d9763f8e7554298d41671d95e6dfa8f7345d8e Mon Sep 17 00:00:00 2001 From: Anmol Singh Bhatia <121005188+anmolsinghbhatia@users.noreply.github.com> Date: Mon, 31 Jul 2023 17:10:23 +0530 Subject: [PATCH] feat: sidebar project ordering functionality added (#1725) --- apps/app/components/project/sidebar-list.tsx | 162 ++++++++++++++---- .../project/single-sidebar-project.tsx | 24 ++- apps/app/services/project.service.ts | 1 + apps/app/types/projects.d.ts | 1 + 4 files changed, 154 insertions(+), 34 deletions(-) diff --git a/apps/app/components/project/sidebar-list.tsx b/apps/app/components/project/sidebar-list.tsx index 477cb49b1..5a35d5625 100644 --- a/apps/app/components/project/sidebar-list.tsx +++ b/apps/app/components/project/sidebar-list.tsx @@ -1,7 +1,10 @@ import React, { useState, FC } from "react"; import { useRouter } from "next/router"; +import { mutate } from "swr"; +// react-beautiful-dnd +import { DragDropContext, Draggable, DropResult, Droppable } from "react-beautiful-dnd"; // hooks import useToast from "hooks/use-toast"; import useTheme from "hooks/use-theme"; @@ -9,12 +12,17 @@ import useUserAuth from "hooks/use-user-auth"; import useProjects from "hooks/use-projects"; // components import { DeleteProjectModal, SingleSidebarProject } from "components/project"; +// services +import projectService from "services/project.service"; // icons import { PlusIcon } from "@heroicons/react/24/outline"; // helpers import { copyTextToClipboard } from "helpers/string.helper"; +import { orderArrayBy } from "helpers/array.helper"; // types import { IProject } from "types"; +// fetch-keys +import { PROJECTS_LIST } from "constants/fetch-keys"; export const ProjectSidebarList: FC = () => { const [deleteProjectModal, setDeleteProjectModal] = useState(false); @@ -32,6 +40,14 @@ export const ProjectSidebarList: FC = () => { const { projects: allProjects } = useProjects(); const favoriteProjects = allProjects?.filter((p) => p.is_favorite); + const orderedAllProjects = allProjects + ? orderArrayBy(allProjects, "sort_order", "ascending") + : []; + + const orderedFavProjects = favoriteProjects + ? orderArrayBy(favoriteProjects, "sort_order", "ascending") + : []; + const handleDeleteProject = (project: IProject) => { setProjectToDelete(project); setDeleteProjectModal(true); @@ -49,6 +65,54 @@ export const ProjectSidebarList: FC = () => { }); }; + const onDragEnd = async (result: DropResult) => { + const { source, destination, draggableId } = result; + + if (!destination || !workspaceSlug) return; + if (source.index === destination.index) return; + + const projectList = + destination.droppableId === "all-projects" ? orderedAllProjects : orderedFavProjects; + + let updatedSortOrder = projectList[source.index].sort_order; + if (destination.index === 0) { + updatedSortOrder = projectList[0].sort_order - 1000; + } else if (destination.index === projectList.length - 1) { + updatedSortOrder = projectList[projectList.length - 1].sort_order + 1000; + } else { + const destinationSortingOrder = projectList[destination.index].sort_order; + const relativeDestinationSortingOrder = + source.index < destination.index + ? projectList[destination.index + 1].sort_order + : projectList[destination.index - 1].sort_order; + + updatedSortOrder = Math.round( + (destinationSortingOrder + relativeDestinationSortingOrder) / 2 + ); + } + + mutate( + PROJECTS_LIST(workspaceSlug as string, { is_favorite: "all" }), + (prevData) => { + if (!prevData) return prevData; + return prevData.map((p) => + p.id === draggableId ? { ...p, sort_order: updatedSortOrder } : p + ); + }, + false + ); + + await projectService + .setProjectView(workspaceSlug as string, draggableId, { sort_order: updatedSortOrder }) + .catch(() => { + setToastAlert({ + type: "error", + title: "Error!", + message: "Something went wrong. Please try again.", + }); + }); + }; + return ( <> { user={user} />
- {favoriteProjects && favoriteProjects.length > 0 && ( -
- {!sidebarCollapse && ( -
Favorites
+ + + {(provided) => ( +
+ {orderedFavProjects && orderedFavProjects.length > 0 && ( +
+ {!sidebarCollapse && ( +
+ Favorites +
+ )} + {orderedFavProjects.map((project, index) => ( + + {(provided, snapshot) => ( +
+ handleDeleteProject(project)} + handleCopyText={() => handleCopyText(project.id)} + shortContextMenu + /> +
+ )} +
+ ))} + {provided.placeholder} +
+ )} +
)} - {favoriteProjects.map((project) => ( - handleDeleteProject(project)} - handleCopyText={() => handleCopyText(project.id)} - shortContextMenu - /> - ))} -
- )} - {allProjects && allProjects.length > 0 && ( -
- {!sidebarCollapse && ( -
Projects
+ + + + + {(provided) => ( +
+ {orderedAllProjects && orderedAllProjects.length > 0 && ( +
+ {!sidebarCollapse && ( +
Projects
+ )} + {orderedAllProjects.map((project, index) => ( + + {(provided, snapshot) => ( +
+ handleDeleteProject(project)} + handleCopyText={() => handleCopyText(project.id)} + /> +
+ )} +
+ ))} + {provided.placeholder} +
+ )} +
)} - {allProjects.map((project) => ( - handleDeleteProject(project)} - handleCopyText={() => handleCopyText(project.id)} - /> - ))} -
- )} + + {allProjects && allProjects.length === 0 && ( = ({ > diff --git a/apps/app/services/project.service.ts b/apps/app/services/project.service.ts index 03e1b9eed..d25bcd9a6 100644 --- a/apps/app/services/project.service.ts +++ b/apps/app/services/project.service.ts @@ -254,6 +254,7 @@ class ProjectServices extends APIService { view_props?: ProjectViewTheme; default_props?: ProjectViewTheme; preferences?: ProjectPreferences; + sort_order?: number; } ): Promise { await this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/project-views/`, data) diff --git a/apps/app/types/projects.d.ts b/apps/app/types/projects.d.ts index 0ac3b1d91..6ea201391 100644 --- a/apps/app/types/projects.d.ts +++ b/apps/app/types/projects.d.ts @@ -45,6 +45,7 @@ export interface IProject { network: number; page_view: boolean; project_lead: IUserLite | string | null; + sort_order: number; slug: string; total_cycles: number; total_members: number;