refactor: enhance components modularity and introduce new UI componenets (#6192)

* feat: add navigation dropdown component

* chore: enhance title/ description loader and componenet modularity

* chore: issue store filter update

* chore: added few icons to ui package

* chore: improvements for tabs componenet

* chore: enhance sidebar modularity

* chore: update issue and router store to add support for additional issue layouts

* chore: enhanced cycle componenets modularity

* feat: added project grouping header for cycles list

* chore: enhanced project dropdown componenet by adding multiple selection functionality

* chore: enhanced rich text editor modularity by taking members ids as props for mentions

* chore: added functionality to filter disabled layouts in issue-layout dropdown

* chore: added support to pass project ids as props in project card list

* feat: multi select project modal

* chore: seperate out project componenet for reusability

* chore: command pallete store improvements

* fix: build errors
This commit is contained in:
Prateek Shourya 2024-12-12 21:40:57 +05:30 committed by GitHub
parent 9ad8b43408
commit 54f828cbfa
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
82 changed files with 1044 additions and 320 deletions

View file

@ -1,5 +1,6 @@
"use client";
import { useMemo } from "react";
import { observer } from "mobx-react";
import { Disclosure } from "@headlessui/react";
// ui
@ -22,68 +23,80 @@ import { ActiveCycleIssueDetails } from "@/store/issue/cycle";
interface IActiveCycleDetails {
workspaceSlug: string;
projectId: string;
cycleId?: string;
showHeader?: boolean;
}
export const ActiveCycleRoot: React.FC<IActiveCycleDetails> = observer((props) => {
const { workspaceSlug, projectId } = props;
const { currentProjectActiveCycle, currentProjectActiveCycleId } = useCycle();
const { workspaceSlug, projectId, cycleId: propsCycleId, showHeader = true } = props;
const { currentProjectActiveCycleId } = useCycle();
// derived values
const cycleId = propsCycleId ?? currentProjectActiveCycleId;
// fetch cycle details
const {
handleFiltersUpdate,
cycle: activeCycle,
cycleIssueDetails,
} = useCyclesDetails({ workspaceSlug, projectId, cycleId: currentProjectActiveCycleId });
} = useCyclesDetails({ workspaceSlug, projectId, cycleId });
const ActiveCyclesComponent = useMemo(
() => (
<>
{!cycleId || !activeCycle ? (
<EmptyState type={EmptyStateType.PROJECT_CYCLE_ACTIVE} size="sm" />
) : (
<div className="flex flex-col border-b border-custom-border-200">
{cycleId && (
<CyclesListItem
key={cycleId}
cycleId={cycleId}
workspaceSlug={workspaceSlug}
projectId={projectId}
className="!border-b-transparent"
/>
)}
<Row className="bg-custom-background-100 pt-3 pb-6">
<div className="grid grid-cols-1 bg-custom-background-100 gap-3 lg:grid-cols-2 xl:grid-cols-3">
<ActiveCycleProgress
handleFiltersUpdate={handleFiltersUpdate}
projectId={projectId}
workspaceSlug={workspaceSlug}
cycle={activeCycle}
/>
<ActiveCycleProductivity workspaceSlug={workspaceSlug} projectId={projectId} cycle={activeCycle} />
<ActiveCycleStats
workspaceSlug={workspaceSlug}
projectId={projectId}
cycle={activeCycle}
cycleId={cycleId}
handleFiltersUpdate={handleFiltersUpdate}
cycleIssueDetails={cycleIssueDetails as ActiveCycleIssueDetails}
/>
</div>
</Row>
</div>
)}
</>
),
[cycleId, activeCycle, workspaceSlug, projectId, handleFiltersUpdate, cycleIssueDetails]
);
return (
<>
<Disclosure as="div" className="flex flex-shrink-0 flex-col" defaultOpen>
{({ open }) => (
<>
<Disclosure.Button className="sticky top-0 z-[2] w-full flex-shrink-0 border-b border-custom-border-200 bg-custom-background-90 cursor-pointer">
<CycleListGroupHeader title="Active cycle" type="current" isExpanded={open} />
</Disclosure.Button>
<Disclosure.Panel>
{!currentProjectActiveCycle ? (
<EmptyState type={EmptyStateType.PROJECT_CYCLE_ACTIVE} size="sm" />
) : (
<div className="flex flex-col border-b border-custom-border-200">
{currentProjectActiveCycleId && (
<CyclesListItem
key={currentProjectActiveCycleId}
cycleId={currentProjectActiveCycleId}
workspaceSlug={workspaceSlug}
projectId={projectId}
className="!border-b-transparent"
/>
)}
<Row className="bg-custom-background-100 pt-3 pb-6">
<div className="grid grid-cols-1 bg-custom-background-100 gap-3 lg:grid-cols-2 xl:grid-cols-3">
<ActiveCycleProgress
handleFiltersUpdate={handleFiltersUpdate}
projectId={projectId}
workspaceSlug={workspaceSlug}
cycle={activeCycle}
/>
<ActiveCycleProductivity
workspaceSlug={workspaceSlug}
projectId={projectId}
cycle={activeCycle}
/>
<ActiveCycleStats
workspaceSlug={workspaceSlug}
projectId={projectId}
cycle={activeCycle}
cycleId={currentProjectActiveCycleId}
handleFiltersUpdate={handleFiltersUpdate}
cycleIssueDetails={cycleIssueDetails as ActiveCycleIssueDetails}
/>
</div>
</Row>
</div>
)}
</Disclosure.Panel>
</>
)}
</Disclosure>
{showHeader ? (
<Disclosure as="div" className="flex flex-shrink-0 flex-col" defaultOpen>
{({ open }) => (
<>
<Disclosure.Button className="sticky top-0 z-[2] w-full flex-shrink-0 border-b border-custom-border-200 bg-custom-background-90 cursor-pointer">
<CycleListGroupHeader title="Active cycle" type="current" isExpanded={open} />
</Disclosure.Button>
<Disclosure.Panel>{ActiveCyclesComponent}</Disclosure.Panel>
</>
)}
</Disclosure>
) : (
<>{ActiveCyclesComponent}</>
)}
</>
);
});

View file

@ -1,2 +1,3 @@
export * from "./applied-filters";
export * from "./issue-types";
export * from "./team-project";

View file

@ -0,0 +1,12 @@
"use client";
import React from "react";
import { observer } from "mobx-react";
type Props = {
appliedFilters: string[] | null;
handleUpdate: (val: string) => void;
searchQuery: string;
};
export const FilterTeamProjects: React.FC<Props> = observer(() => null);

View file

@ -0,0 +1,4 @@
// types
import { IGroupByColumn } from "@plane/types";
export const getTeamProjectColumns = (): IGroupByColumn[] | undefined => undefined;

View file

@ -0,0 +1 @@
export const SidebarTeamsList = () => null;

View file

@ -1,17 +1,19 @@
"use client";
// icons
import { Home, Inbox, PenSquare } from "lucide-react";
import { Briefcase, Home, Inbox, Layers, PenSquare, BarChart2 } from "lucide-react";
// ui
import { UserActivityIcon } from "@plane/ui";
import { UserActivityIcon, ContrastIcon } from "@plane/ui";
import { Props } from "@/components/icons/types";
// constants
import { TLinkOptions } from "@/constants/dashboard";
// plane web constants
import { EUserPermissions } from "@/plane-web/constants/user-permissions";
// plane web types
import { TSidebarUserMenuItemKeys } from "@/plane-web/types/dashboard";
import { TSidebarUserMenuItemKeys, TSidebarWorkspaceMenuItemKeys } from "@/plane-web/types/dashboard";
export type TSidebarUserMenuItems = {
key: TSidebarUserMenuItemKeys;
export type TSidebarMenuItems<T extends TSidebarUserMenuItemKeys | TSidebarWorkspaceMenuItemKeys> = {
key: T;
label: string;
href: string;
access: EUserPermissions[];
@ -19,6 +21,8 @@ export type TSidebarUserMenuItems = {
Icon: React.FC<Props>;
};
export type TSidebarUserMenuItems = TSidebarMenuItems<TSidebarUserMenuItemKeys>;
export const SIDEBAR_USER_MENU_ITEMS: TSidebarUserMenuItems[] = [
{
key: "home",
@ -54,3 +58,47 @@ export const SIDEBAR_USER_MENU_ITEMS: TSidebarUserMenuItems[] = [
Icon: PenSquare,
},
];
export type TSidebarWorkspaceMenuItems = TSidebarMenuItems<TSidebarWorkspaceMenuItemKeys>;
export const SIDEBAR_WORKSPACE_MENU: Partial<Record<TSidebarWorkspaceMenuItemKeys, TSidebarWorkspaceMenuItems>> = {
projects: {
key: "projects",
label: "Projects",
href: `/projects`,
access: [EUserPermissions.ADMIN, EUserPermissions.MEMBER, EUserPermissions.GUEST],
highlight: (pathname: string, baseUrl: string) => pathname === `${baseUrl}/projects/`,
Icon: Briefcase,
},
"all-issues": {
key: "all-issues",
label: "Views",
href: `/workspace-views/all-issues`,
access: [EUserPermissions.ADMIN, EUserPermissions.MEMBER, EUserPermissions.GUEST],
highlight: (pathname: string, baseUrl: string) => pathname.includes(`${baseUrl}/workspace-views/`),
Icon: Layers,
},
"active-cycles": {
key: "active-cycles",
label: "Cycles",
href: `/active-cycles`,
access: [EUserPermissions.ADMIN, EUserPermissions.MEMBER],
highlight: (pathname: string, baseUrl: string) => pathname === `${baseUrl}/active-cycles/`,
Icon: ContrastIcon,
},
analytics: {
key: "analytics",
label: "Analytics",
href: `/analytics`,
access: [EUserPermissions.ADMIN, EUserPermissions.MEMBER],
highlight: (pathname: string, baseUrl: string) => pathname.includes(`${baseUrl}/analytics/`),
Icon: BarChart2,
},
};
export const SIDEBAR_WORKSPACE_MENU_ITEMS: TSidebarWorkspaceMenuItems[] = [
SIDEBAR_WORKSPACE_MENU?.projects,
SIDEBAR_WORKSPACE_MENU?.["all-issues"],
SIDEBAR_WORKSPACE_MENU?.["active-cycles"],
SIDEBAR_WORKSPACE_MENU?.analytics,
].filter((item): item is TSidebarWorkspaceMenuItems => item !== undefined);

View file

@ -1,4 +1,6 @@
import { TIssueActivityComment } from "@plane/types";
// constants
import { ILayoutDisplayFiltersOptions } from "@/constants/issue";
export enum EActivityFilterType {
ACTIVITY = "ACTIVITY",
@ -19,7 +21,7 @@ export const ACTIVITY_FILTER_TYPE_OPTIONS: Record<EActivityFilterType, { label:
export const defaultActivityFilters: TActivityFilters[] = [EActivityFilterType.ACTIVITY, EActivityFilterType.COMMENT];
export type TActivityFilterOption = {
key: EActivityFilterType;
key: TActivityFilters;
label: string;
isSelected: boolean;
onClick: () => void;
@ -32,3 +34,7 @@ export const filterActivityOnSelectedFilters = (
activity.filter((activity) => filter.includes(activity.activity_type as TActivityFilters));
export const ENABLE_ISSUE_DEPENDENCIES = false;
export const ADDITIONAL_ISSUE_DISPLAY_FILTERS_BY_LAYOUT: {
[pageType: string]: { [layoutType: string]: ILayoutDisplayFiltersOptions };
} = {};

View file

@ -1,5 +1,8 @@
// plane web types
import { TSidebarUserMenuItemKeys } from "@/plane-web/types/dashboard";
import { TSidebarUserMenuItemKeys, TSidebarWorkspaceMenuItemKeys } from "@/plane-web/types/dashboard";
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export const isUserFeatureEnabled = (featureKey: TSidebarUserMenuItemKeys) => true;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export const isWorkspaceFeatureEnabled = (featureKey: TSidebarWorkspaceMenuItemKeys, workspaceSlug: string) => true;

View file

@ -0,0 +1,15 @@
import { IssueActions } from "@/hooks/use-issues-actions";
export const useTeamIssueActions: () => IssueActions = () => ({
fetchIssues: () => Promise.resolve(undefined),
fetchNextIssues: () => Promise.resolve(undefined),
removeIssue: () => Promise.resolve(undefined),
updateFilters: () => Promise.resolve(undefined),
});
export const useTeamViewIssueActions: () => IssueActions = () => ({
fetchIssues: () => Promise.resolve(undefined),
fetchNextIssues: () => Promise.resolve(undefined),
removeIssue: () => Promise.resolve(undefined),
updateFilters: () => Promise.resolve(undefined),
});

View file

@ -1,12 +1,26 @@
import { makeObservable } from "mobx";
import { computed, makeObservable } from "mobx";
// types / constants
import { BaseCommandPaletteStore, IBaseCommandPaletteStore } from "@/store/base-command-palette.store";
export type ICommandPaletteStore = IBaseCommandPaletteStore;
export interface ICommandPaletteStore extends IBaseCommandPaletteStore {
// computed
isAnyModalOpen: boolean;
}
export class CommandPaletteStore extends BaseCommandPaletteStore implements ICommandPaletteStore {
constructor() {
super();
makeObservable(this, {});
makeObservable(this, {
// computed
isAnyModalOpen: computed,
});
}
/**
* Checks whether any modal is open or not in the base command palette.
* @returns boolean
*/
get isAnyModalOpen(): boolean {
return Boolean(super.getCoreModalsState());
}
}

View file

@ -0,0 +1,12 @@
import { IProjectViewIssuesFilter, ProjectViewIssuesFilter } from "@/store/issue/project-views";
import { IIssueRootStore } from "@/store/issue/root.store";
// @ts-nocheck - This class will never be used, extending similar class to avoid type errors
export type ITeamViewIssuesFilter = IProjectViewIssuesFilter;
// @ts-nocheck - This class will never be used, extending similar class to avoid type errors
export class TeamViewIssuesFilter extends ProjectViewIssuesFilter implements IProjectViewIssuesFilter {
constructor(_rootStore: IIssueRootStore) {
super(_rootStore);
}
}

View file

@ -0,0 +1,2 @@
export * from "./filter.store";
export * from "./issue.store";

View file

@ -0,0 +1,13 @@
import { IProjectViewIssues, ProjectViewIssues } from "@/store/issue/project-views";
import { IIssueRootStore } from "@/store/issue/root.store";
import { ITeamViewIssuesFilter } from "./filter.store";
// @ts-nocheck - This class will never be used, extending similar class to avoid type errors
export type ITeamViewIssues = IProjectViewIssues;
// @ts-nocheck - This class will never be used, extending similar class to avoid type errors
export class TeamViewIssues extends ProjectViewIssues implements IProjectViewIssues {
constructor(_rootStore: IIssueRootStore, teamViewFilterStore: ITeamViewIssuesFilter) {
super(_rootStore, teamViewFilterStore);
}
}

View file

@ -0,0 +1,12 @@
import { IProjectIssuesFilter, ProjectIssuesFilter } from "@/store/issue/project";
import { IIssueRootStore } from "@/store/issue/root.store";
// @ts-nocheck - This class will never be used, extending similar class to avoid type errors
export type ITeamIssuesFilter = IProjectIssuesFilter;
// @ts-nocheck - This class will never be used, extending similar class to avoid type errors
export class TeamIssuesFilter extends ProjectIssuesFilter implements IProjectIssuesFilter {
constructor(_rootStore: IIssueRootStore) {
super(_rootStore);
}
}

View file

@ -0,0 +1,2 @@
export * from "./filter.store";
export * from "./issue.store";

View file

@ -0,0 +1,13 @@
import { IProjectIssues, ProjectIssues } from "@/store/issue/project";
import { IIssueRootStore } from "@/store/issue/root.store";
import { ITeamIssuesFilter } from "./filter.store";
// @ts-nocheck - This class will never be used, extending similar class to avoid type errors
export type ITeamIssues = IProjectIssues;
// @ts-nocheck - This class will never be used, extending similar class to avoid type errors
export class TeamIssues extends ProjectIssues implements IProjectIssues {
constructor(_rootStore: IIssueRootStore, teamIssueFilterStore: ITeamIssuesFilter) {
super(_rootStore, teamIssueFilterStore);
}
}

View file

@ -1 +1,3 @@
export type TSidebarUserMenuItemKeys = "home" | "your-work" | "notifications" | "drafts";
export type TSidebarWorkspaceMenuItemKeys = "projects" | "all-issues" | "active-cycles" | "analytics";