Skip to content

Commit

Permalink
[WEB-2818] chore: project navigation items code refactor (#6170)
Browse files Browse the repository at this point in the history
* chore: project navigation items code refactor

* fix: build error

* chore: code refactor

* chore: code refactor
  • Loading branch information
anmolsinghbhatia authored Dec 9, 2024
1 parent a85e592 commit 5c907db
Show file tree
Hide file tree
Showing 6 changed files with 190 additions and 122 deletions.
1 change: 1 addition & 0 deletions web/ce/components/sidebar/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from "./app-switcher";
export * from "./project-navigation-root";
15 changes: 15 additions & 0 deletions web/ce/components/sidebar/project-navigation-root.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
"use client";

import { FC } from "react";
// components
import { ProjectNavigation } from "@/components/workspace";

type TProjectItemsRootProps = {
workspaceSlug: string;
projectId: string;
};

export const ProjectNavigationRoot: FC<TProjectItemsRootProps> = (props) => {
const { workspaceSlug, projectId } = props;
return <ProjectNavigation workspaceSlug={workspaceSlug} projectId={projectId} />;
};
1 change: 1 addition & 0 deletions web/core/components/workspace/sidebar/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export * from "./favorites";
export * from "./help-section";
export * from "./projects-list-item";
export * from "./projects-list";
export * from "./project-navigation";
export * from "./quick-actions";
export * from "./user-menu";
export * from "./workspace-menu";
163 changes: 163 additions & 0 deletions web/core/components/workspace/sidebar/project-navigation.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
"use client";

import React, { FC, useCallback, useMemo } from "react";
import { observer } from "mobx-react";
import Link from "next/link";
import { usePathname } from "next/navigation";
import { FileText, Layers } from "lucide-react";
// plane ui
import { Tooltip, DiceIcon, ContrastIcon, LayersIcon, Intake } from "@plane/ui";
// components
import { SidebarNavItem } from "@/components/sidebar";
// hooks
import { useAppTheme, useProject, useUserPermissions } from "@/hooks/store";
import { usePlatformOS } from "@/hooks/use-platform-os";
// plane-web constants
import { EUserPermissions } from "@/plane-web/constants";
import { EUserPermissionsLevel } from "@/plane-web/constants/user-permissions";

export type TNavigationItem = {
name: string;
href: string;
icon: React.ElementType;
access: EUserPermissions[];
shouldRender: boolean;
sortOrder: number;
};

type TProjectItemsProps = {
workspaceSlug: string;
projectId: string;
additionalNavigationItems?: (workspaceSlug: string, projectId: string) => TNavigationItem[];
};

export const ProjectNavigation: FC<TProjectItemsProps> = observer((props) => {
const { workspaceSlug, projectId, additionalNavigationItems } = props;
// store hooks
const { sidebarCollapsed: isSidebarCollapsed, toggleSidebar } = useAppTheme();
const { getProjectById } = useProject();
const { isMobile } = usePlatformOS();
const { allowPermissions } = useUserPermissions();
// pathname
const pathname = usePathname();
// derived values
const project = getProjectById(projectId);
// handlers
const handleProjectClick = () => {
if (window.innerWidth < 768) {
toggleSidebar();
}
};

if (!project) return null;

const baseNavigation = useCallback(
(workspaceSlug: string, projectId: string): TNavigationItem[] => [
{
name: "Issues",
href: `/${workspaceSlug}/projects/${projectId}/issues`,
icon: LayersIcon,
access: [EUserPermissions.ADMIN, EUserPermissions.MEMBER, EUserPermissions.GUEST],
shouldRender: true,
sortOrder: 1,
},
{
name: "Cycles",
href: `/${workspaceSlug}/projects/${projectId}/cycles`,
icon: ContrastIcon,
access: [EUserPermissions.ADMIN, EUserPermissions.MEMBER],
shouldRender: project.cycle_view,
sortOrder: 2,
},
{
name: "Modules",
href: `/${workspaceSlug}/projects/${projectId}/modules`,
icon: DiceIcon,
access: [EUserPermissions.ADMIN, EUserPermissions.MEMBER],
shouldRender: project.module_view,
sortOrder: 3,
},
{
name: "Views",
href: `/${workspaceSlug}/projects/${projectId}/views`,
icon: Layers,
access: [EUserPermissions.ADMIN, EUserPermissions.MEMBER, EUserPermissions.GUEST],
shouldRender: project.issue_views_view,
sortOrder: 4,
},
{
name: "Pages",
href: `/${workspaceSlug}/projects/${projectId}/pages`,
icon: FileText,
access: [EUserPermissions.ADMIN, EUserPermissions.MEMBER, EUserPermissions.GUEST],
shouldRender: project.page_view,
sortOrder: 5,
},
{
name: "Intake",
href: `/${workspaceSlug}/projects/${projectId}/inbox`,
icon: Intake,
access: [EUserPermissions.ADMIN, EUserPermissions.MEMBER, EUserPermissions.GUEST],
shouldRender: project.inbox_view,
sortOrder: 6,
},
],
[project]
);

// memoized navigation items and adding additional navigation items
const navigationItemsMemo = useMemo(() => {
const navigationItems = (workspaceSlug: string, projectId: string): TNavigationItem[] => {
const navItems = baseNavigation(workspaceSlug, projectId);

if (additionalNavigationItems) {
navItems.push(...additionalNavigationItems(workspaceSlug, projectId));
}

return navItems;
};

// sort navigation items by sortOrder
const sortedNavigationItems = navigationItems(workspaceSlug, projectId).sort(
(a, b) => (a.sortOrder || 0) - (b.sortOrder || 0)
);

return sortedNavigationItems;
}, [workspaceSlug, projectId, baseNavigation, additionalNavigationItems]);

return (
<>
{navigationItemsMemo.map((item) => {
if (!item.shouldRender) return;

const hasAccess = allowPermissions(item.access, EUserPermissionsLevel.PROJECT, workspaceSlug, project.id);
if (!hasAccess) return null;

return (
<Tooltip
key={item.name}
isMobile={isMobile}
tooltipContent={`${project?.name}: ${item.name}`}
position="right"
className="ml-2"
disabled={!isSidebarCollapsed}
>
<Link href={item.href} onClick={handleProjectClick}>
<SidebarNavItem
className={`pl-[18px] ${isSidebarCollapsed ? "p-0 size-7 justify-center mx-auto" : ""}`}
isActive={pathname.includes(item.href)}
>
<div className="flex items-center gap-1.5 py-[1px]">
<item.icon
className={`flex-shrink-0 size-4 ${item.name === "Intake" ? "stroke-1" : "stroke-[1.5]"}`}
/>
{!isSidebarCollapsed && <span className="text-xs font-medium">{item.name}</span>}
</div>
</SidebarNavItem>
</Link>
</Tooltip>
);
})}
</>
);
});
126 changes: 7 additions & 119 deletions web/core/components/workspace/sidebar/projects-list-item.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,46 +8,24 @@ import { setCustomNativeDragPreview } from "@atlaskit/pragmatic-drag-and-drop/el
import { attachInstruction, extractInstruction } from "@atlaskit/pragmatic-drag-and-drop-hitbox/tree-item";
import { observer } from "mobx-react";
import Link from "next/link";
import { useParams, usePathname, useRouter } from "next/navigation";
import { useParams, useRouter } from "next/navigation";
import { createRoot } from "react-dom/client";
import {
PenSquare,
LinkIcon,
Star,
FileText,
Settings,
Share2,
LogOut,
MoreHorizontal,
ChevronRight,
Layers,
} from "lucide-react";
import { LinkIcon, Star, Settings, Share2, LogOut, MoreHorizontal, ChevronRight } from "lucide-react";
import { Disclosure, Transition } from "@headlessui/react";
// plane helpers
import { useOutsideClickDetector } from "@plane/hooks";
// ui
import {
CustomMenu,
Tooltip,
ArchiveIcon,
DiceIcon,
ContrastIcon,
LayersIcon,
setPromiseToast,
DropIndicator,
DragHandle,
Intake,
ControlLink,
} from "@plane/ui";
import { CustomMenu, Tooltip, ArchiveIcon, setPromiseToast, DropIndicator, DragHandle, ControlLink } from "@plane/ui";
// components
import { Logo } from "@/components/common";
import { LeaveProjectModal, PublishProjectModal } from "@/components/project";
import { SidebarNavItem } from "@/components/sidebar";
// helpers
import { cn } from "@/helpers/common.helper";
// hooks
import { useAppTheme, useEventTracker, useProject, useUserPermissions } from "@/hooks/store";
import { usePlatformOS } from "@/hooks/use-platform-os";
// plane-web components
import { ProjectNavigationRoot } from "@/plane-web/components/sidebar";
// constants
import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions";
import { HIGHLIGHT_CLASS, highlightIssueOnDrop } from "../../issues/issue-layouts/utils";
Expand All @@ -66,50 +44,11 @@ type Props = {
isLastChild: boolean;
};

const navigation = (workspaceSlug: string, projectId: string) => [
{
name: "Issues",
href: `/${workspaceSlug}/projects/${projectId}/issues`,
Icon: LayersIcon,
access: [EUserPermissions.ADMIN, EUserPermissions.MEMBER, EUserPermissions.GUEST],
},
{
name: "Cycles",
href: `/${workspaceSlug}/projects/${projectId}/cycles`,
Icon: ContrastIcon,
access: [EUserPermissions.ADMIN, EUserPermissions.MEMBER],
},
{
name: "Modules",
href: `/${workspaceSlug}/projects/${projectId}/modules`,
Icon: DiceIcon,
access: [EUserPermissions.ADMIN, EUserPermissions.MEMBER],
},
{
name: "Views",
href: `/${workspaceSlug}/projects/${projectId}/views`,
Icon: Layers,
access: [EUserPermissions.ADMIN, EUserPermissions.MEMBER, EUserPermissions.GUEST],
},
{
name: "Pages",
href: `/${workspaceSlug}/projects/${projectId}/pages`,
Icon: FileText,
access: [EUserPermissions.ADMIN, EUserPermissions.MEMBER, EUserPermissions.GUEST],
},
{
name: "Intake",
href: `/${workspaceSlug}/projects/${projectId}/inbox`,
Icon: Intake,
access: [EUserPermissions.ADMIN, EUserPermissions.MEMBER, EUserPermissions.GUEST],
},
];

export const SidebarProjectsListItem: React.FC<Props> = observer((props) => {
const { projectId, handleCopyText, disableDrag, disableDrop, isLastChild, handleOnProjectDrop, projectListType } =
props;
// store hooks
const { sidebarCollapsed: isSidebarCollapsed, toggleSidebar } = useAppTheme();
const { sidebarCollapsed: isSidebarCollapsed } = useAppTheme();
const { setTrackElement } = useEventTracker();
const { addProjectToFavorites, removeProjectFromFavorites, getProjectById } = useProject();
const { isMobile } = usePlatformOS();
Expand All @@ -128,8 +67,6 @@ export const SidebarProjectsListItem: React.FC<Props> = observer((props) => {
// router
const router = useRouter();
const { workspaceSlug, projectId: URLProjectId } = useParams();
// pathname
const pathname = usePathname();
// derived values
const project = getProjectById(projectId);
// auth
Expand Down Expand Up @@ -185,12 +122,6 @@ export const SidebarProjectsListItem: React.FC<Props> = observer((props) => {
setLeaveProjectModal(true);
};

const handleProjectClick = () => {
if (window.innerWidth < 768) {
toggleSidebar();
}
};

useEffect(() => {
const element = projectRef.current;
const dragHandleElement = dragHandleRef.current;
Expand Down Expand Up @@ -503,50 +434,7 @@ export const SidebarProjectsListItem: React.FC<Props> = observer((props) => {
>
{isProjectListOpen && (
<Disclosure.Panel as="div" className="flex flex-col gap-0.5 mt-1">
{navigation(workspaceSlug?.toString(), project?.id).map((item) => {
if (
(item.name === "Cycles" && !project.cycle_view) ||
(item.name === "Modules" && !project.module_view) ||
(item.name === "Views" && !project.issue_views_view) ||
(item.name === "Pages" && !project.page_view) ||
(item.name === "Intake" && !project.inbox_view)
)
return;
return (
<>
{allowPermissions(
item.access,
EUserPermissionsLevel.PROJECT,
workspaceSlug.toString(),
project.id
) && (
<Tooltip
key={item.name}
isMobile={isMobile}
tooltipContent={`${project?.name}: ${item.name}`}
position="right"
className="ml-2"
disabled={!isSidebarCollapsed}
>
<Link key={item.name} href={item.href} onClick={handleProjectClick}>
<SidebarNavItem
key={item.name}
className={`pl-[18px] ${isSidebarCollapsed ? "p-0 size-7 justify-center mx-auto" : ""}`}
isActive={pathname.includes(item.href)}
>
<div className="flex items-center gap-1.5 py-[1px]">
<item.Icon
className={`flex-shrink-0 size-4 ${item.name === "Intake" ? "stroke-1" : "stroke-[1.5]"}`}
/>
{!isSidebarCollapsed && <span className="text-xs font-medium">{item.name}</span>}
</div>
</SidebarNavItem>
</Link>
</Tooltip>
)}
</>
);
})}
<ProjectNavigationRoot workspaceSlug={workspaceSlug.toString()} projectId={projectId.toString()} />
</Disclosure.Panel>
)}
</Transition>
Expand Down
6 changes: 3 additions & 3 deletions web/core/local-db/utils/load-workspace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ export const syncIssuesWithDeletedModules = async (deletedModuleIds: string[]) =
return;
}

const issues = await persistence.getIssues("", "", { modules: deletedModuleIds.join(","), cursor: "10000:0:0" }, {});
const issues = await persistence.getIssues("", "", { module: deletedModuleIds.join(","), cursor: "10000:0:0" }, {});
if (issues?.results && Array.isArray(issues.results)) {
const promises = issues.results.map(async (issue: TIssue) => {
const updatedIssue = {
Expand All @@ -177,7 +177,7 @@ export const syncIssuesWithDeletedCycles = async (deletedCycleIds: string[]) =>
return;
}

const issues = await persistence.getIssues("", "", { cycles: deletedCycleIds.join(","), cursor: "10000:0:0" }, {});
const issues = await persistence.getIssues("", "", { cycle: deletedCycleIds.join(","), cursor: "10000:0:0" }, {});
if (issues?.results && Array.isArray(issues.results)) {
const promises = issues.results.map(async (issue: TIssue) => {
const updatedIssue = {
Expand All @@ -204,7 +204,7 @@ export const syncIssuesWithDeletedStates = async (deletedStateIds: string[]) =>
return;
}

const issues = await persistence.getIssues("", "", { states: deletedStateIds.join(","), cursor: "10000:0:0" }, {});
const issues = await persistence.getIssues("", "", { state: deletedStateIds.join(","), cursor: "10000:0:0" }, {});
if (issues?.results && Array.isArray(issues.results)) {
const promises = issues.results.map(async (issue: TIssue) => {
const updatedIssue = {
Expand Down

0 comments on commit 5c907db

Please sign in to comment.