mirror of
https://github.com/makeplane/plane
synced 2025-08-07 19:59:33 +00:00
Compare commits
5 Commits
feat-bulk-
...
chore-issu
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
80937780cc | ||
|
|
1c46acbc66 | ||
|
|
d451cca4f3 | ||
|
|
f0ed24647d | ||
|
|
8c69dc6609 |
@@ -18,6 +18,7 @@ export const PopoverMenu = <T,>(props: TPopoverMenu<T>) => {
|
||||
popoverClassName = "",
|
||||
keyExtractor,
|
||||
render,
|
||||
popoverButtonRef,
|
||||
} = props;
|
||||
|
||||
return (
|
||||
@@ -32,6 +33,7 @@ export const PopoverMenu = <T,>(props: TPopoverMenu<T>) => {
|
||||
panelClassName
|
||||
)}
|
||||
popoverClassName={popoverClassName}
|
||||
popoverButtonRef={popoverButtonRef}
|
||||
>
|
||||
<Fragment>
|
||||
{data.map((item, index) => (
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { Fragment, useState } from "react";
|
||||
import React, { Fragment, Ref, useState } from "react";
|
||||
import { usePopper } from "react-popper";
|
||||
import { Popover as HeadlessReactPopover, Transition } from "@headlessui/react";
|
||||
// helpers
|
||||
@@ -17,9 +17,10 @@ export const Popover = (props: TPopover) => {
|
||||
disabled = false,
|
||||
panelClassName = "",
|
||||
children,
|
||||
popoverButtonRef,
|
||||
} = props;
|
||||
// states
|
||||
const [referenceElement, setReferenceElement] = useState<HTMLButtonElement | null>(null);
|
||||
const [referenceElement, setReferenceElement] = useState<HTMLDivElement | null>(null);
|
||||
const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(null);
|
||||
|
||||
// react-popper derived values
|
||||
@@ -37,19 +38,21 @@ export const Popover = (props: TPopover) => {
|
||||
|
||||
return (
|
||||
<HeadlessReactPopover className={cn("relative flex h-full w-full items-center justify-center", popoverClassName)}>
|
||||
<HeadlessReactPopover.Button
|
||||
ref={setReferenceElement}
|
||||
className={cn(
|
||||
{
|
||||
"flex justify-center items-center text-base h-6 w-6 rounded transition-all bg-custom-background-90 hover:bg-custom-background-80":
|
||||
!button,
|
||||
},
|
||||
buttonClassName
|
||||
)}
|
||||
disabled={disabled}
|
||||
>
|
||||
{button ? button : <EllipsisVertical className="h-3 w-3" />}
|
||||
</HeadlessReactPopover.Button>
|
||||
<div ref={setReferenceElement} className="w-full">
|
||||
<HeadlessReactPopover.Button
|
||||
ref={popoverButtonRef as Ref<HTMLButtonElement>}
|
||||
className={cn(
|
||||
{
|
||||
"flex justify-center items-center text-base h-6 w-6 rounded transition-all bg-custom-background-90 hover:bg-custom-background-80":
|
||||
!button,
|
||||
},
|
||||
buttonClassName
|
||||
)}
|
||||
disabled={disabled}
|
||||
>
|
||||
{button ? button : <EllipsisVertical className="h-3 w-3" />}
|
||||
</HeadlessReactPopover.Button>
|
||||
</div>
|
||||
|
||||
<Transition
|
||||
as={Fragment}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ReactNode } from "react";
|
||||
import { MutableRefObject, ReactNode } from "react";
|
||||
import { Placement } from "@popperjs/core";
|
||||
|
||||
export type TPopoverButtonDefaultOptions = {
|
||||
@@ -15,6 +15,7 @@ export type TPopoverDefaultOptions = TPopoverButtonDefaultOptions & {
|
||||
// panel styling
|
||||
panelClassName?: string;
|
||||
popoverClassName?: string;
|
||||
popoverButtonRef?: MutableRefObject<HTMLButtonElement | null>;
|
||||
};
|
||||
|
||||
export type TPopover = TPopoverDefaultOptions & {
|
||||
|
||||
@@ -8,6 +8,7 @@ type TIssueActivityWorklog = {
|
||||
projectId: string;
|
||||
issueId: string;
|
||||
activityComment: TIssueActivityComment;
|
||||
ends?: "top" | "bottom";
|
||||
};
|
||||
|
||||
export const IssueActivityWorklog: FC<TIssueActivityWorklog> = () => <></>;
|
||||
|
||||
@@ -4,6 +4,57 @@ import { Props } from "@/components/icons/types";
|
||||
// constants
|
||||
import { EUserWorkspaceRoles } from "@/constants/workspace";
|
||||
|
||||
export const WORKSPACE_SETTINGS = {
|
||||
general: {
|
||||
key: "general",
|
||||
label: "General",
|
||||
href: `/settings`,
|
||||
access: EUserWorkspaceRoles.GUEST,
|
||||
highlight: (pathname: string, baseUrl: string) => pathname === `${baseUrl}/settings/`,
|
||||
Icon: SettingIcon,
|
||||
},
|
||||
members: {
|
||||
key: "members",
|
||||
label: "Members",
|
||||
href: `/settings/members`,
|
||||
access: EUserWorkspaceRoles.GUEST,
|
||||
highlight: (pathname: string, baseUrl: string) => pathname === `${baseUrl}/settings/members/`,
|
||||
Icon: SettingIcon,
|
||||
},
|
||||
"billing-and-plans": {
|
||||
key: "billing-and-plans",
|
||||
label: "Billing and plans",
|
||||
href: `/settings/billing`,
|
||||
access: EUserWorkspaceRoles.ADMIN,
|
||||
highlight: (pathname: string, baseUrl: string) => pathname === `${baseUrl}/settings/billing/`,
|
||||
Icon: SettingIcon,
|
||||
},
|
||||
export: {
|
||||
key: "export",
|
||||
label: "Exports",
|
||||
href: `/settings/exports`,
|
||||
access: EUserWorkspaceRoles.MEMBER,
|
||||
highlight: (pathname: string, baseUrl: string) => pathname === `${baseUrl}/settings/exports/`,
|
||||
Icon: SettingIcon,
|
||||
},
|
||||
webhooks: {
|
||||
key: "webhooks",
|
||||
label: "Webhooks",
|
||||
href: `/settings/webhooks`,
|
||||
access: EUserWorkspaceRoles.ADMIN,
|
||||
highlight: (pathname: string, baseUrl: string) => pathname === `${baseUrl}/settings/webhooks/`,
|
||||
Icon: SettingIcon,
|
||||
},
|
||||
"api-tokens": {
|
||||
key: "api-tokens",
|
||||
label: "API tokens",
|
||||
href: `/settings/api-tokens`,
|
||||
access: EUserWorkspaceRoles.ADMIN,
|
||||
highlight: (pathname: string, baseUrl: string) => pathname === `${baseUrl}/settings/api-tokens/`,
|
||||
Icon: SettingIcon,
|
||||
},
|
||||
};
|
||||
|
||||
export const WORKSPACE_SETTINGS_LINKS: {
|
||||
key: string;
|
||||
label: string;
|
||||
@@ -12,52 +63,10 @@ export const WORKSPACE_SETTINGS_LINKS: {
|
||||
highlight: (pathname: string, baseUrl: string) => boolean;
|
||||
Icon: React.FC<Props>;
|
||||
}[] = [
|
||||
{
|
||||
key: "general",
|
||||
label: "General",
|
||||
href: `/settings`,
|
||||
access: EUserWorkspaceRoles.GUEST,
|
||||
highlight: (pathname: string, baseUrl: string) => pathname === `${baseUrl}/settings/`,
|
||||
Icon: SettingIcon,
|
||||
},
|
||||
{
|
||||
key: "members",
|
||||
label: "Members",
|
||||
href: `/settings/members`,
|
||||
access: EUserWorkspaceRoles.GUEST,
|
||||
highlight: (pathname: string, baseUrl: string) => pathname === `${baseUrl}/settings/members/`,
|
||||
Icon: SettingIcon,
|
||||
},
|
||||
{
|
||||
key: "billing-and-plans",
|
||||
label: "Billing and plans",
|
||||
href: `/settings/billing`,
|
||||
access: EUserWorkspaceRoles.ADMIN,
|
||||
highlight: (pathname: string, baseUrl: string) => pathname === `${baseUrl}/settings/billing/`,
|
||||
Icon: SettingIcon,
|
||||
},
|
||||
{
|
||||
key: "export",
|
||||
label: "Exports",
|
||||
href: `/settings/exports`,
|
||||
access: EUserWorkspaceRoles.MEMBER,
|
||||
highlight: (pathname: string, baseUrl: string) => pathname === `${baseUrl}/settings/exports/`,
|
||||
Icon: SettingIcon,
|
||||
},
|
||||
{
|
||||
key: "webhooks",
|
||||
label: "Webhooks",
|
||||
href: `/settings/webhooks`,
|
||||
access: EUserWorkspaceRoles.ADMIN,
|
||||
highlight: (pathname: string, baseUrl: string) => pathname === `${baseUrl}/settings/webhooks/`,
|
||||
Icon: SettingIcon,
|
||||
},
|
||||
{
|
||||
key: "api-tokens",
|
||||
label: "API tokens",
|
||||
href: `/settings/api-tokens`,
|
||||
access: EUserWorkspaceRoles.ADMIN,
|
||||
highlight: (pathname: string, baseUrl: string) => pathname === `${baseUrl}/settings/api-tokens/`,
|
||||
Icon: SettingIcon,
|
||||
},
|
||||
WORKSPACE_SETTINGS["general"],
|
||||
WORKSPACE_SETTINGS["members"],
|
||||
WORKSPACE_SETTINGS["billing-and-plans"],
|
||||
WORKSPACE_SETTINGS["export"],
|
||||
WORKSPACE_SETTINGS["webhooks"],
|
||||
WORKSPACE_SETTINGS["api-tokens"],
|
||||
];
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
/* eslint-disable no-useless-catch */
|
||||
|
||||
import concat from "lodash/concat";
|
||||
import set from "lodash/set";
|
||||
import sortBy from "lodash/sortBy";
|
||||
@@ -5,10 +7,12 @@ import uniq from "lodash/uniq";
|
||||
import update from "lodash/update";
|
||||
import { action, makeObservable, observable, runInAction } from "mobx";
|
||||
import { TIssueActivityComment, TIssueActivity, TIssueActivityMap, TIssueActivityIdMap } from "@plane/types";
|
||||
// plane web constants
|
||||
import { EActivityFilterType } from "@/plane-web/constants/issues";
|
||||
// plane web store types
|
||||
import { RootStore } from "@/plane-web/store/root.store";
|
||||
// services
|
||||
import { IssueActivityService } from "@/services/issue";
|
||||
// types
|
||||
import { IIssueDetail } from "./root.store";
|
||||
|
||||
export type TActivityLoader = "fetch" | "mutate" | undefined;
|
||||
|
||||
@@ -38,12 +42,11 @@ export class IssueActivityStore implements IIssueActivityStore {
|
||||
loader: TActivityLoader = "fetch";
|
||||
activities: TIssueActivityIdMap = {};
|
||||
activityMap: TIssueActivityMap = {};
|
||||
// root store
|
||||
rootIssueDetailStore: IIssueDetail;
|
||||
|
||||
// services
|
||||
issueActivityService;
|
||||
|
||||
constructor(rootStore: IIssueDetail) {
|
||||
constructor(protected store: RootStore) {
|
||||
makeObservable(this, {
|
||||
// observables
|
||||
loader: observable.ref,
|
||||
@@ -52,8 +55,6 @@ export class IssueActivityStore implements IIssueActivityStore {
|
||||
// actions
|
||||
fetchActivities: action,
|
||||
});
|
||||
// root store
|
||||
this.rootIssueDetailStore = rootStore;
|
||||
// services
|
||||
this.issueActivityService = new IssueActivityService();
|
||||
}
|
||||
@@ -69,50 +70,46 @@ export class IssueActivityStore implements IIssueActivityStore {
|
||||
return this.activityMap[activityId] ?? undefined;
|
||||
};
|
||||
|
||||
getActivityCommentByIssueId = (issueId: string) => {
|
||||
public getActivityCommentByIssueId(issueId: string) {
|
||||
if (!issueId) return undefined;
|
||||
|
||||
let activityComments: TIssueActivityComment[] = [];
|
||||
|
||||
const activities = this.getActivitiesByIssueId(issueId) || [];
|
||||
const comments = this.rootIssueDetailStore.comment.getCommentsByIssueId(issueId) || [];
|
||||
const comments = this.store.issue.issueDetail.comment.getCommentsByIssueId(issueId) || [];
|
||||
|
||||
activities.forEach((activityId) => {
|
||||
const activity = this.getActivityById(activityId);
|
||||
if (!activity) return;
|
||||
activityComments.push({
|
||||
id: activity.id,
|
||||
activity_type: "ACTIVITY",
|
||||
activity_type: EActivityFilterType.ACTIVITY,
|
||||
created_at: activity.created_at,
|
||||
});
|
||||
});
|
||||
|
||||
comments.forEach((commentId) => {
|
||||
const comment = this.rootIssueDetailStore.comment.getCommentById(commentId);
|
||||
const comment = this.store.issue.issueDetail.comment.getCommentById(commentId);
|
||||
if (!comment) return;
|
||||
activityComments.push({
|
||||
id: comment.id,
|
||||
activity_type: "COMMENT",
|
||||
activity_type: EActivityFilterType.COMMENT,
|
||||
created_at: comment.created_at,
|
||||
});
|
||||
});
|
||||
|
||||
activityComments = sortBy(activityComments, "created_at");
|
||||
activityComments = activityComments.map((activityComment) => ({
|
||||
id: activityComment.id,
|
||||
activity_type: activityComment.activity_type,
|
||||
}));
|
||||
|
||||
return activityComments;
|
||||
};
|
||||
}
|
||||
|
||||
// actions
|
||||
fetchActivities = async (
|
||||
public async fetchActivities(
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
issueId: string,
|
||||
loaderType: TActivityLoader = "fetch"
|
||||
) => {
|
||||
) {
|
||||
try {
|
||||
this.loader = loaderType;
|
||||
|
||||
@@ -140,7 +137,8 @@ export class IssueActivityStore implements IIssueActivityStore {
|
||||
|
||||
return activities;
|
||||
} catch (error) {
|
||||
this.loader = undefined;
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -62,6 +62,7 @@ export const IssueActivityCommentRoot: FC<TIssueActivityCommentRoot> = observer(
|
||||
projectId={projectId}
|
||||
issueId={issueId}
|
||||
activityComment={activityComment}
|
||||
ends={index === 0 ? "top" : index === filteredActivityComments.length - 1 ? "bottom" : undefined}
|
||||
/>
|
||||
) : (
|
||||
<></>
|
||||
|
||||
@@ -10,8 +10,14 @@ import {
|
||||
TIssueRelationTypes,
|
||||
TIssueDetailWidget,
|
||||
} from "@plane/types";
|
||||
// plane web store
|
||||
import {
|
||||
IIssueActivityStore,
|
||||
IssueActivityStore,
|
||||
IIssueActivityStoreActions,
|
||||
TActivityLoader,
|
||||
} from "@/plane-web/store/issue/issue-details/activity.store";
|
||||
import { IIssueRootStore } from "../root.store";
|
||||
import { IIssueActivityStore, IssueActivityStore, IIssueActivityStoreActions, TActivityLoader } from "./activity.store";
|
||||
import { IIssueAttachmentStore, IssueAttachmentStore, IIssueAttachmentStoreActions } from "./attachment.store";
|
||||
import { IIssueCommentStore, IssueCommentStore, IIssueCommentStoreActions, TCommentLoader } from "./comment.store";
|
||||
import {
|
||||
@@ -187,7 +193,7 @@ export class IssueDetail implements IIssueDetail {
|
||||
this.issue = new IssueStore(this);
|
||||
this.reaction = new IssueReactionStore(this);
|
||||
this.attachment = new IssueAttachmentStore(rootStore);
|
||||
this.activity = new IssueActivityStore(this);
|
||||
this.activity = new IssueActivityStore(rootStore.rootStore);
|
||||
this.comment = new IssueCommentStore(this);
|
||||
this.commentReaction = new IssueCommentReactionStore(this);
|
||||
this.subIssues = new IssueSubIssuesStore(this);
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import isEmpty from "lodash/isEmpty";
|
||||
import { autorun, makeObservable, observable } from "mobx";
|
||||
import { ICycle, IIssueLabel, IModule, IProject, IState, IUserLite } from "@plane/types";
|
||||
// plane web root store
|
||||
import { RootStore } from "@/plane-web/store/root.store";
|
||||
// root store
|
||||
import { IWorkspaceMembership } from "@/store/member/workspace-member.store";
|
||||
import { CoreRootStore } from "../root.store";
|
||||
import { IStateStore, StateStore } from "../state.store";
|
||||
// issues data store
|
||||
import { IArchivedIssuesFilter, ArchivedIssuesFilter, IArchivedIssues, ArchivedIssues } from "./archived";
|
||||
@@ -43,7 +44,7 @@ export interface IIssueRootStore {
|
||||
moduleMap: Record<string, IModule> | undefined;
|
||||
cycleMap: Record<string, ICycle> | undefined;
|
||||
|
||||
rootStore: CoreRootStore;
|
||||
rootStore: RootStore;
|
||||
|
||||
issues: IIssueStore;
|
||||
|
||||
@@ -98,7 +99,7 @@ export class IssueRootStore implements IIssueRootStore {
|
||||
moduleMap: Record<string, IModule> | undefined = undefined;
|
||||
cycleMap: Record<string, ICycle> | undefined = undefined;
|
||||
|
||||
rootStore: CoreRootStore;
|
||||
rootStore: RootStore;
|
||||
|
||||
issues: IIssueStore;
|
||||
|
||||
@@ -133,7 +134,7 @@ export class IssueRootStore implements IIssueRootStore {
|
||||
issueKanBanView: IIssueKanBanViewStore;
|
||||
issueCalendarView: ICalendarStore;
|
||||
|
||||
constructor(rootStore: CoreRootStore) {
|
||||
constructor(rootStore: RootStore) {
|
||||
makeObservable(this, {
|
||||
workspaceSlug: observable.ref,
|
||||
projectId: observable.ref,
|
||||
|
||||
1
web/ee/store/issue/issue-details/activity.store.ts
Normal file
1
web/ee/store/issue/issue-details/activity.store.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from "ce/store/issue/issue-details/activity.store";
|
||||
Reference in New Issue
Block a user