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:
parent
9ad8b43408
commit
54f828cbfa
82 changed files with 1044 additions and 320 deletions
|
|
@ -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}</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,2 +1,3 @@
|
|||
export * from "./applied-filters";
|
||||
export * from "./issue-types";
|
||||
export * from "./team-project";
|
||||
|
|
|
|||
12
web/ce/components/issues/filters/team-project.tsx
Normal file
12
web/ce/components/issues/filters/team-project.tsx
Normal 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);
|
||||
4
web/ce/components/issues/issue-layouts/utils.tsx
Normal file
4
web/ce/components/issues/issue-layouts/utils.tsx
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
// types
|
||||
import { IGroupByColumn } from "@plane/types";
|
||||
|
||||
export const getTeamProjectColumns = (): IGroupByColumn[] | undefined => undefined;
|
||||
|
|
@ -0,0 +1 @@
|
|||
export const SidebarTeamsList = () => null;
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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 };
|
||||
} = {};
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
15
web/ce/helpers/issue-action-helper.ts
Normal file
15
web/ce/helpers/issue-action-helper.ts
Normal 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),
|
||||
});
|
||||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
12
web/ce/store/issue/team-views/filter.store.ts
Normal file
12
web/ce/store/issue/team-views/filter.store.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
2
web/ce/store/issue/team-views/index.ts
Normal file
2
web/ce/store/issue/team-views/index.ts
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
export * from "./filter.store";
|
||||
export * from "./issue.store";
|
||||
13
web/ce/store/issue/team-views/issue.store.ts
Normal file
13
web/ce/store/issue/team-views/issue.store.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
12
web/ce/store/issue/team/filter.store.ts
Normal file
12
web/ce/store/issue/team/filter.store.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
2
web/ce/store/issue/team/index.ts
Normal file
2
web/ce/store/issue/team/index.ts
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
export * from "./filter.store";
|
||||
export * from "./issue.store";
|
||||
13
web/ce/store/issue/team/issue.store.ts
Normal file
13
web/ce/store/issue/team/issue.store.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
|
|
@ -1 +1,3 @@
|
|||
export type TSidebarUserMenuItemKeys = "home" | "your-work" | "notifications" | "drafts";
|
||||
|
||||
export type TSidebarWorkspaceMenuItemKeys = "projects" | "all-issues" | "active-cycles" | "analytics";
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue