Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WEB-2818] chore: project navigation items code refactor #6170

Merged
merged 4 commits into from
Dec 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Critical: Required imports are missing

Several icons and UI components that are used in the component are missing from the imports. This will cause runtime errors.

Add back the following imports:

-import { LinkIcon, Star, Settings, Share2, LogOut, MoreHorizontal, ChevronRight } from "lucide-react";
+import { ChevronRight, LinkIcon, LogOut, MoreHorizontal, Settings, Share2, Star } from "lucide-react";

-import { CustomMenu, Tooltip, ArchiveIcon, setPromiseToast, DropIndicator, DragHandle, ControlLink } from "@plane/ui";
+import { ArchiveIcon, ControlLink, CustomMenu, DragHandle, DropIndicator, setPromiseToast, Tooltip } from "@plane/ui";

Also applies to: 18-18

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
Loading