code refactor and improvement (#6203)
* chore: package code refactoring * chore: component restructuring and refactor * chore: comment create improvement
This commit is contained in:
parent
442b0fd7e5
commit
438cc33046
134 changed files with 1336 additions and 506 deletions
|
|
@ -39,3 +39,8 @@ export enum EServerGroupByToFilterOptions {
|
|||
"project_id" = "project",
|
||||
"created_by" = "created_by",
|
||||
}
|
||||
|
||||
export enum EIssueServiceType {
|
||||
ISSUES = "issues",
|
||||
EPICS = "epics",
|
||||
}
|
||||
|
|
|
|||
6
packages/types/src/issues/issue.d.ts
vendored
6
packages/types/src/issues/issue.d.ts
vendored
|
|
@ -1,3 +1,4 @@
|
|||
import { EIssueServiceType } from "@plane/constants";
|
||||
import { TIssuePriorities } from "../issues";
|
||||
import { TIssueAttachment } from "./issue_attachment";
|
||||
import { TIssueLink } from "./issue_link";
|
||||
|
|
@ -39,6 +40,7 @@ export type TBaseIssue = {
|
|||
updated_by: string;
|
||||
|
||||
is_draft: boolean;
|
||||
is_epic?: boolean;
|
||||
};
|
||||
|
||||
export type IssueRelation = {
|
||||
|
|
@ -121,3 +123,7 @@ export type TIssueDetailWidget =
|
|||
| "relations"
|
||||
| "links"
|
||||
| "attachments";
|
||||
|
||||
export type TIssueServiceType =
|
||||
| EIssueServiceType.ISSUES
|
||||
| EIssueServiceType.EPICS;
|
||||
|
|
|
|||
1
packages/types/src/project/projects.d.ts
vendored
1
packages/types/src/project/projects.d.ts
vendored
|
|
@ -136,6 +136,7 @@ export type TProjectIssuesSearchParams = {
|
|||
issue_id?: string;
|
||||
workspace_search: boolean;
|
||||
target_date?: string;
|
||||
epic?: boolean;
|
||||
};
|
||||
|
||||
export interface ISearchIssueResponse {
|
||||
|
|
|
|||
1
web/ce/components/epics/epic-modal/index.ts
Normal file
1
web/ce/components/epics/epic-modal/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
export * from "./modal";
|
||||
19
web/ce/components/epics/epic-modal/modal.tsx
Normal file
19
web/ce/components/epics/epic-modal/modal.tsx
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
"use client";
|
||||
import React, { FC } from "react";
|
||||
import { TIssue } from "@plane/types";
|
||||
|
||||
export interface EpicModalProps {
|
||||
data?: Partial<TIssue>;
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
beforeFormSubmit?: () => Promise<void>;
|
||||
onSubmit?: (res: TIssue) => Promise<void>;
|
||||
fetchIssueDetails?: boolean;
|
||||
primaryButtonText?: {
|
||||
default: string;
|
||||
loading: string;
|
||||
};
|
||||
isProjectSelectionDisabled?: boolean;
|
||||
}
|
||||
|
||||
export const CreateUpdateEpicModal: FC<EpicModalProps> = (props) => <></>;
|
||||
1
web/ce/components/epics/index.ts
Normal file
1
web/ce/components/epics/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
export * from "./epic-modal";
|
||||
|
|
@ -1 +1,9 @@
|
|||
export const TimelineDependencyPaths = () => <></>;
|
||||
import { FC } from "react";
|
||||
|
||||
type Props = {
|
||||
isEpic?: boolean;
|
||||
};
|
||||
export const TimelineDependencyPaths: FC<Props> = (props) => {
|
||||
const { isEpic = false } = props;
|
||||
return <></>;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,9 +1,12 @@
|
|||
import { TIssueServiceType } from "@plane/types";
|
||||
|
||||
export type TIssueAdditionalPropertyValuesUpdateProps = {
|
||||
issueId: string;
|
||||
issueTypeId: string;
|
||||
projectId: string;
|
||||
workspaceSlug: string;
|
||||
isDisabled: boolean;
|
||||
issueServiceType?: TIssueServiceType;
|
||||
};
|
||||
|
||||
export const IssueAdditionalPropertyValuesUpdate: React.FC<TIssueAdditionalPropertyValuesUpdateProps> = () => <></>;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { TDeDupeIssue } from "@plane/types";
|
||||
|
||||
export const useDebouncedDuplicateIssues = (
|
||||
workspaceSlug: string | undefined,
|
||||
workspaceId: string | undefined,
|
||||
projectId: string | undefined,
|
||||
formData: { name: string | undefined; description_html?: string | undefined; issueId?: string | undefined }
|
||||
|
|
|
|||
15
web/ce/store/issue/epic/filter.store.ts
Normal file
15
web/ce/store/issue/epic/filter.store.ts
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
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 IProjectEpicsFilter = IProjectIssuesFilter;
|
||||
|
||||
// @ts-nocheck - This class will never be used, extending similar class to avoid type errors
|
||||
export class ProjectEpicsFilter extends ProjectIssuesFilter implements IProjectEpicsFilter {
|
||||
constructor(_rootStore: IIssueRootStore) {
|
||||
super(_rootStore);
|
||||
|
||||
// root store
|
||||
this.rootIssueStore = _rootStore;
|
||||
}
|
||||
}
|
||||
2
web/ce/store/issue/epic/index.ts
Normal file
2
web/ce/store/issue/epic/index.ts
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
export * from "./filter.store";
|
||||
export * from "./issue.store";
|
||||
14
web/ce/store/issue/epic/issue.store.ts
Normal file
14
web/ce/store/issue/epic/issue.store.ts
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import { IProjectIssues, ProjectIssues } from "@/store/issue/project";
|
||||
import { IIssueRootStore } from "@/store/issue/root.store";
|
||||
import { IProjectEpicsFilter } from "./filter.store";
|
||||
|
||||
// @ts-nocheck - This class will never be used, extending similar class to avoid type errors
|
||||
|
||||
export type IProjectEpics = IProjectIssues;
|
||||
|
||||
// @ts-nocheck - This class will never be used, extending similar class to avoid type errors
|
||||
export class ProjectEpics extends ProjectIssues implements IProjectEpics {
|
||||
constructor(_rootStore: IIssueRootStore, issueFilterStore: IProjectEpicsFilter) {
|
||||
super(_rootStore, issueFilterStore);
|
||||
}
|
||||
}
|
||||
|
|
@ -7,7 +7,14 @@ import uniq from "lodash/uniq";
|
|||
import update from "lodash/update";
|
||||
import { action, makeObservable, observable, runInAction } from "mobx";
|
||||
import { computedFn } from "mobx-utils";
|
||||
import { TIssueActivityComment, TIssueActivity, TIssueActivityMap, TIssueActivityIdMap } from "@plane/types";
|
||||
import { EIssueServiceType } from "@plane/constants";
|
||||
import {
|
||||
TIssueActivityComment,
|
||||
TIssueActivity,
|
||||
TIssueActivityMap,
|
||||
TIssueActivityIdMap,
|
||||
TIssueServiceType,
|
||||
} from "@plane/types";
|
||||
// plane web constants
|
||||
import { EActivityFilterType } from "@/plane-web/constants/issues";
|
||||
// services
|
||||
|
|
@ -29,7 +36,7 @@ export interface IIssueActivityStoreActions {
|
|||
|
||||
export interface IIssueActivityStore extends IIssueActivityStoreActions {
|
||||
// observables
|
||||
sortOrder: 'asc' | 'desc'
|
||||
sortOrder: "asc" | "desc";
|
||||
loader: TActivityLoader;
|
||||
activities: TIssueActivityIdMap;
|
||||
activityMap: TIssueActivityMap;
|
||||
|
|
@ -37,20 +44,24 @@ export interface IIssueActivityStore extends IIssueActivityStoreActions {
|
|||
getActivitiesByIssueId: (issueId: string) => string[] | undefined;
|
||||
getActivityById: (activityId: string) => TIssueActivity | undefined;
|
||||
getActivityCommentByIssueId: (issueId: string) => TIssueActivityComment[] | undefined;
|
||||
toggleSortOrder: ()=>void;
|
||||
toggleSortOrder: () => void;
|
||||
}
|
||||
|
||||
export class IssueActivityStore implements IIssueActivityStore {
|
||||
// observables
|
||||
sortOrder: "asc" | "desc" = 'asc';
|
||||
sortOrder: "asc" | "desc" = "asc";
|
||||
loader: TActivityLoader = "fetch";
|
||||
activities: TIssueActivityIdMap = {};
|
||||
activityMap: TIssueActivityMap = {};
|
||||
|
||||
// services
|
||||
serviceType;
|
||||
issueActivityService;
|
||||
|
||||
constructor(protected store: CoreRootStore) {
|
||||
constructor(
|
||||
protected store: CoreRootStore,
|
||||
serviceType: TIssueServiceType = EIssueServiceType.ISSUES
|
||||
) {
|
||||
makeObservable(this, {
|
||||
// observables
|
||||
sortOrder: observable.ref,
|
||||
|
|
@ -59,10 +70,11 @@ export class IssueActivityStore implements IIssueActivityStore {
|
|||
activityMap: observable,
|
||||
// actions
|
||||
fetchActivities: action,
|
||||
toggleSortOrder: action
|
||||
toggleSortOrder: action,
|
||||
});
|
||||
this.serviceType = serviceType;
|
||||
// services
|
||||
this.issueActivityService = new IssueActivityService();
|
||||
this.issueActivityService = new IssueActivityService(this.serviceType);
|
||||
}
|
||||
|
||||
// helper methods
|
||||
|
|
@ -81,8 +93,10 @@ export class IssueActivityStore implements IIssueActivityStore {
|
|||
|
||||
let activityComments: TIssueActivityComment[] = [];
|
||||
|
||||
const currentStore = this.serviceType === EIssueServiceType.EPICS ? this.store.epic : this.store.issue;
|
||||
|
||||
const activities = this.getActivitiesByIssueId(issueId) || [];
|
||||
const comments = this.store.issue.issueDetail.comment.getCommentsByIssueId(issueId) || [];
|
||||
const comments = currentStore.issueDetail.comment.getCommentsByIssueId(issueId) || [];
|
||||
|
||||
activities.forEach((activityId) => {
|
||||
const activity = this.getActivityById(activityId);
|
||||
|
|
@ -95,7 +109,7 @@ export class IssueActivityStore implements IIssueActivityStore {
|
|||
});
|
||||
|
||||
comments.forEach((commentId) => {
|
||||
const comment = this.store.issue.issueDetail.comment.getCommentById(commentId);
|
||||
const comment = currentStore.issueDetail.comment.getCommentById(commentId);
|
||||
if (!comment) return;
|
||||
activityComments.push({
|
||||
id: comment.id,
|
||||
|
|
@ -104,14 +118,14 @@ export class IssueActivityStore implements IIssueActivityStore {
|
|||
});
|
||||
});
|
||||
|
||||
activityComments = orderBy(activityComments, (e)=>new Date(e.created_at || 0), this.sortOrder);
|
||||
activityComments = orderBy(activityComments, (e) => new Date(e.created_at || 0), this.sortOrder);
|
||||
|
||||
return activityComments;
|
||||
});
|
||||
|
||||
toggleSortOrder = ()=>{
|
||||
this.sortOrder = this.sortOrder === 'asc' ? 'desc' : 'asc';
|
||||
}
|
||||
toggleSortOrder = () => {
|
||||
this.sortOrder = this.sortOrder === "asc" ? "desc" : "asc";
|
||||
};
|
||||
|
||||
// actions
|
||||
public async fetchActivities(
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import React from "react";
|
||||
import React, { useState } from "react";
|
||||
// editor
|
||||
import { EditorRefApi, ILiteTextEditor, LiteTextEditorWithRef } from "@plane/editor";
|
||||
// types
|
||||
|
|
@ -27,6 +27,7 @@ interface LiteTextEditorWrapperProps
|
|||
showAccessSpecifier?: boolean;
|
||||
showSubmitButton?: boolean;
|
||||
isSubmitting?: boolean;
|
||||
showToolbarInitially?: boolean;
|
||||
uploadFile: (file: File) => Promise<string>;
|
||||
}
|
||||
|
||||
|
|
@ -41,10 +42,13 @@ export const LiteTextEditor = React.forwardRef<EditorRefApi, LiteTextEditorWrapp
|
|||
showAccessSpecifier = false,
|
||||
showSubmitButton = true,
|
||||
isSubmitting = false,
|
||||
showToolbarInitially = true,
|
||||
placeholder = "Add comment...",
|
||||
uploadFile,
|
||||
...rest
|
||||
} = props;
|
||||
// states
|
||||
const [isFocused, setIsFocused] = useState(showToolbarInitially);
|
||||
// store hooks
|
||||
const { data: currentUser } = useUser();
|
||||
const {
|
||||
|
|
@ -73,7 +77,11 @@ export const LiteTextEditor = React.forwardRef<EditorRefApi, LiteTextEditorWrapp
|
|||
const editorRef = isMutableRefObject<EditorRefApi>(ref) ? ref.current : null;
|
||||
|
||||
return (
|
||||
<div className="border border-custom-border-200 rounded p-3 space-y-3">
|
||||
<div
|
||||
className={cn("relative border border-custom-border-200 rounded p-3")}
|
||||
onFocus={() => !showToolbarInitially && setIsFocused(true)}
|
||||
onBlur={() => !showToolbarInitially && setIsFocused(false)}
|
||||
>
|
||||
<LiteTextEditorWithRef
|
||||
ref={ref}
|
||||
disabledExtensions={disabledExtensions}
|
||||
|
|
@ -92,6 +100,12 @@ export const LiteTextEditor = React.forwardRef<EditorRefApi, LiteTextEditorWrapp
|
|||
containerClassName={cn(containerClassName, "relative")}
|
||||
{...rest}
|
||||
/>
|
||||
<div
|
||||
className={cn(
|
||||
"transition-all duration-300 ease-out origin-top overflow-hidden",
|
||||
isFocused ? "max-h-[200px] opacity-100 scale-y-100 mt-3" : "max-h-0 opacity-0 scale-y-0 invisible"
|
||||
)}
|
||||
>
|
||||
<IssueCommentToolbar
|
||||
accessSpecifier={accessSpecifier}
|
||||
executeCommand={(item) => {
|
||||
|
|
@ -111,6 +125,7 @@ export const LiteTextEditor = React.forwardRef<EditorRefApi, LiteTextEditorWrapp
|
|||
showSubmitButton={showSubmitButton}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -56,6 +56,7 @@ type Props = {
|
|||
targetDate?: Date
|
||||
) => ChartDataType | undefined;
|
||||
quickAdd?: React.JSX.Element | undefined;
|
||||
isEpic?: boolean;
|
||||
};
|
||||
|
||||
export const GanttChartMainContent: React.FC<Props> = observer((props) => {
|
||||
|
|
@ -79,6 +80,7 @@ export const GanttChartMainContent: React.FC<Props> = observer((props) => {
|
|||
updateCurrentViewRenderPayload,
|
||||
quickAdd,
|
||||
updateBlockDates,
|
||||
isEpic = false,
|
||||
} = props;
|
||||
// refs
|
||||
const ganttContainerRef = useRef<HTMLDivElement>(null);
|
||||
|
|
@ -159,7 +161,7 @@ export const GanttChartMainContent: React.FC<Props> = observer((props) => {
|
|||
entities={{
|
||||
[GANTT_SELECT_GROUP]: blockIds ?? [],
|
||||
}}
|
||||
disabled={!isBulkOperationsEnabled}
|
||||
disabled={!isBulkOperationsEnabled || isEpic}
|
||||
>
|
||||
{(helpers) => (
|
||||
<>
|
||||
|
|
@ -187,6 +189,7 @@ export const GanttChartMainContent: React.FC<Props> = observer((props) => {
|
|||
title={title}
|
||||
quickAdd={quickAdd}
|
||||
selectionHelpers={helpers}
|
||||
isEpic={isEpic}
|
||||
/>
|
||||
<div className="relative min-h-full h-max flex-shrink-0 flex-grow">
|
||||
<ActiveChartView />
|
||||
|
|
@ -208,7 +211,7 @@ export const GanttChartMainContent: React.FC<Props> = observer((props) => {
|
|||
selectionHelpers={helpers}
|
||||
ganttContainerRef={ganttContainerRef}
|
||||
/>
|
||||
<TimelineDependencyPaths />
|
||||
<TimelineDependencyPaths isEpic={isEpic} />
|
||||
<TimelineDraggablePath />
|
||||
<GanttChartBlocksList
|
||||
blockIds={blockIds}
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ type ChartViewRootProps = {
|
|||
canLoadMoreBlocks?: boolean;
|
||||
quickAdd?: React.JSX.Element | undefined;
|
||||
showToday: boolean;
|
||||
isEpic?: boolean;
|
||||
};
|
||||
|
||||
const timelineViewHelpers = {
|
||||
|
|
@ -71,6 +72,7 @@ export const ChartViewRoot: FC<ChartViewRootProps> = observer((props) => {
|
|||
quickAdd,
|
||||
showToday,
|
||||
updateBlockDates,
|
||||
isEpic = false,
|
||||
} = props;
|
||||
// states
|
||||
const [itemsContainerWidth, setItemsContainerWidth] = useState(0);
|
||||
|
|
@ -204,6 +206,7 @@ export const ChartViewRoot: FC<ChartViewRootProps> = observer((props) => {
|
|||
updateCurrentViewRenderPayload={updateCurrentViewRenderPayload}
|
||||
quickAdd={quickAdd}
|
||||
updateBlockDates={updateBlockDates}
|
||||
isEpic={isEpic}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ type GanttChartRootProps = {
|
|||
bottomSpacing?: boolean;
|
||||
showAllBlocks?: boolean;
|
||||
showToday?: boolean;
|
||||
isEpic?: boolean;
|
||||
};
|
||||
|
||||
export const GanttChartRoot: FC<GanttChartRootProps> = observer((props) => {
|
||||
|
|
@ -50,6 +51,7 @@ export const GanttChartRoot: FC<GanttChartRootProps> = observer((props) => {
|
|||
showToday = true,
|
||||
quickAdd,
|
||||
updateBlockDates,
|
||||
isEpic = false,
|
||||
} = props;
|
||||
|
||||
const { setBlockIds } = useTimeLineChartStore();
|
||||
|
|
@ -81,6 +83,7 @@ export const GanttChartRoot: FC<GanttChartRootProps> = observer((props) => {
|
|||
quickAdd={quickAdd}
|
||||
showToday={showToday}
|
||||
updateBlockDates={updateBlockDates}
|
||||
isEpic={isEpic}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -19,10 +19,11 @@ type Props = {
|
|||
enableSelection: boolean;
|
||||
isDragging: boolean;
|
||||
selectionHelpers?: TSelectionHelper;
|
||||
isEpic?: boolean;
|
||||
};
|
||||
|
||||
export const IssuesSidebarBlock = observer((props: Props) => {
|
||||
const { block, enableSelection, isDragging, selectionHelpers } = props;
|
||||
const { block, enableSelection, isDragging, selectionHelpers, isEpic = false } = props;
|
||||
// store hooks
|
||||
const { updateActiveBlockId, isBlockActive, getNumberOfDaysFromPosition } = useTimeLineChartStore();
|
||||
const { getIsIssuePeeked } = useIssueDetail();
|
||||
|
|
@ -73,7 +74,7 @@ export const IssuesSidebarBlock = observer((props: Props) => {
|
|||
)}
|
||||
<div className="flex h-full flex-grow items-center justify-between gap-2 truncate">
|
||||
<div className="flex-grow truncate">
|
||||
<IssueGanttSidebarBlock issueId={block.data.id} />
|
||||
<IssueGanttSidebarBlock issueId={block.data.id} isEpic={isEpic} />
|
||||
</div>
|
||||
{duration && (
|
||||
<div className="flex-shrink-0 text-sm text-custom-text-200">
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ type Props = {
|
|||
enableSelection: boolean;
|
||||
showAllBlocks?: boolean;
|
||||
selectionHelpers?: TSelectionHelper;
|
||||
isEpic?: boolean;
|
||||
};
|
||||
|
||||
export const IssueGanttSidebar: React.FC<Props> = observer((props) => {
|
||||
|
|
@ -42,6 +43,7 @@ export const IssueGanttSidebar: React.FC<Props> = observer((props) => {
|
|||
ganttContainerRef,
|
||||
showAllBlocks = false,
|
||||
selectionHelpers,
|
||||
isEpic = false,
|
||||
} = props;
|
||||
|
||||
const { getBlockById } = useTimeLineChart(ETimeLineTypeType.ISSUE);
|
||||
|
|
@ -101,6 +103,7 @@ export const IssueGanttSidebar: React.FC<Props> = observer((props) => {
|
|||
enableSelection={enableSelection}
|
||||
isDragging={isDragging}
|
||||
selectionHelpers={selectionHelpers}
|
||||
isEpic={isEpic}
|
||||
/>
|
||||
)}
|
||||
</GanttDnDHOC>
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ type Props = {
|
|||
title: string;
|
||||
quickAdd?: React.JSX.Element | undefined;
|
||||
selectionHelpers: TSelectionHelper;
|
||||
isEpic?: boolean;
|
||||
};
|
||||
|
||||
export const GanttChartSidebar: React.FC<Props> = observer((props) => {
|
||||
|
|
@ -38,6 +39,7 @@ export const GanttChartSidebar: React.FC<Props> = observer((props) => {
|
|||
title,
|
||||
quickAdd,
|
||||
selectionHelpers,
|
||||
isEpic = false,
|
||||
} = props;
|
||||
|
||||
const isGroupSelectionEmpty = selectionHelpers.isGroupSelected(GANTT_SELECT_GROUP) === "empty";
|
||||
|
|
@ -90,6 +92,7 @@ export const GanttChartSidebar: React.FC<Props> = observer((props) => {
|
|||
ganttContainerRef,
|
||||
loadMoreBlocks,
|
||||
selectionHelpers,
|
||||
isEpic,
|
||||
})}
|
||||
</Row>
|
||||
{quickAdd ? quickAdd : null}
|
||||
|
|
|
|||
|
|
@ -65,11 +65,16 @@ export const InboxIssueMainContent: React.FC<Props> = observer((props) => {
|
|||
const projectDetails = issue?.project_id ? getProjectById(issue?.project_id) : undefined;
|
||||
|
||||
// debounced duplicate issues swr
|
||||
const { duplicateIssues } = useDebouncedDuplicateIssues(projectDetails?.workspace.toString(), projectId, {
|
||||
const { duplicateIssues } = useDebouncedDuplicateIssues(
|
||||
workspaceSlug,
|
||||
projectDetails?.workspace.toString(),
|
||||
projectId,
|
||||
{
|
||||
name: issue?.name,
|
||||
description_html: getTextContent(issue?.description_html),
|
||||
issueId: issue?.id,
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
if (!issue) return <></>;
|
||||
|
||||
|
|
|
|||
|
|
@ -87,10 +87,15 @@ export const InboxIssueCreateRoot: FC<TInboxIssueCreateRoot> = observer((props)
|
|||
const { getIndex } = getTabIndex(ETabIndices.INTAKE_ISSUE_FORM, isMobile);
|
||||
|
||||
// debounced duplicate issues swr
|
||||
const { duplicateIssues } = useDebouncedDuplicateIssues(projectDetails?.workspace.toString(), projectId, {
|
||||
const { duplicateIssues } = useDebouncedDuplicateIssues(
|
||||
workspaceSlug,
|
||||
projectDetails?.workspace.toString(),
|
||||
projectId,
|
||||
{
|
||||
name: formData?.name,
|
||||
description_html: formData?.description_html,
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
const handleEscKeyDown = (event: KeyboardEvent) => {
|
||||
if (descriptionEditorRef.current?.isEditorReadyToDiscard()) {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ import { FC, useCallback, useState } from "react";
|
|||
import { observer } from "mobx-react";
|
||||
import { FileRejection, useDropzone } from "react-dropzone";
|
||||
import { UploadCloud } from "lucide-react";
|
||||
import { EIssueServiceType } from "@plane/constants";
|
||||
import { TIssueServiceType } from "@plane/types";
|
||||
// hooks
|
||||
import { TOAST_TYPE, setToast } from "@plane/ui";
|
||||
import { useIssueDetail } from "@/hooks/store";
|
||||
|
|
@ -21,10 +23,18 @@ type TIssueAttachmentItemList = {
|
|||
issueId: string;
|
||||
attachmentHelpers: TAttachmentHelpers;
|
||||
disabled?: boolean;
|
||||
issueServiceType?: TIssueServiceType;
|
||||
};
|
||||
|
||||
export const IssueAttachmentItemList: FC<TIssueAttachmentItemList> = observer((props) => {
|
||||
const { workspaceSlug, projectId, issueId, attachmentHelpers, disabled } = props;
|
||||
const {
|
||||
workspaceSlug,
|
||||
projectId,
|
||||
issueId,
|
||||
attachmentHelpers,
|
||||
disabled,
|
||||
issueServiceType = EIssueServiceType.ISSUES,
|
||||
} = props;
|
||||
// states
|
||||
const [isUploading, setIsUploading] = useState(false);
|
||||
// store hooks
|
||||
|
|
@ -33,7 +43,7 @@ export const IssueAttachmentItemList: FC<TIssueAttachmentItemList> = observer((p
|
|||
attachmentDeleteModalId,
|
||||
toggleDeleteAttachmentModal,
|
||||
fetchActivities,
|
||||
} = useIssueDetail();
|
||||
} = useIssueDetail(issueServiceType);
|
||||
const { operations: attachmentOperations, snapshot: attachmentSnapshot } = attachmentHelpers;
|
||||
const { create: createAttachment } = attachmentOperations;
|
||||
const { uploadStatus } = attachmentSnapshot;
|
||||
|
|
@ -104,6 +114,7 @@ export const IssueAttachmentItemList: FC<TIssueAttachmentItemList> = observer((p
|
|||
onClose={() => toggleDeleteAttachmentModal(null)}
|
||||
attachmentOperations={attachmentOperations}
|
||||
attachmentId={attachmentDeleteModalId}
|
||||
issueServiceType={issueServiceType}
|
||||
/>
|
||||
)}
|
||||
<div
|
||||
|
|
@ -122,7 +133,12 @@ export const IssueAttachmentItemList: FC<TIssueAttachmentItemList> = observer((p
|
|||
</div>
|
||||
)}
|
||||
{issueAttachments?.map((attachmentId) => (
|
||||
<IssueAttachmentsListItem key={attachmentId} attachmentId={attachmentId} disabled={disabled} />
|
||||
<IssueAttachmentsListItem
|
||||
key={attachmentId}
|
||||
attachmentId={attachmentId}
|
||||
disabled={disabled}
|
||||
issueServiceType={issueServiceType}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@
|
|||
import { FC } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { Trash } from "lucide-react";
|
||||
import { EIssueServiceType } from "@plane/constants";
|
||||
import { TIssueServiceType } from "@plane/types";
|
||||
// ui
|
||||
import { CustomMenu, Tooltip } from "@plane/ui";
|
||||
// components
|
||||
|
|
@ -19,17 +21,18 @@ import { usePlatformOS } from "@/hooks/use-platform-os";
|
|||
type TIssueAttachmentsListItem = {
|
||||
attachmentId: string;
|
||||
disabled?: boolean;
|
||||
issueServiceType?: TIssueServiceType;
|
||||
};
|
||||
|
||||
export const IssueAttachmentsListItem: FC<TIssueAttachmentsListItem> = observer((props) => {
|
||||
// props
|
||||
const { attachmentId, disabled } = props;
|
||||
const { attachmentId, disabled, issueServiceType = EIssueServiceType.ISSUES } = props;
|
||||
// store hooks
|
||||
const { getUserDetails } = useMember();
|
||||
const {
|
||||
attachment: { getAttachmentById },
|
||||
toggleDeleteAttachmentModal,
|
||||
} = useIssueDetail();
|
||||
} = useIssueDetail(issueServiceType);
|
||||
// derived values
|
||||
const attachment = attachmentId ? getAttachmentById(attachmentId) : undefined;
|
||||
const fileName = getFileName(attachment?.attributes.name ?? "");
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
import { FC, useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
// constants
|
||||
import { EIssueServiceType } from "@plane/constants";
|
||||
// types
|
||||
import { TIssueServiceType } from "@plane/types";
|
||||
// ui
|
||||
import { AlertModalCore } from "@plane/ui";
|
||||
// helper
|
||||
|
|
@ -17,17 +20,18 @@ type Props = {
|
|||
onClose: () => void;
|
||||
attachmentId: string;
|
||||
attachmentOperations: TAttachmentOperationsRemoveModal;
|
||||
issueServiceType?: TIssueServiceType;
|
||||
};
|
||||
|
||||
export const IssueAttachmentDeleteModal: FC<Props> = observer((props) => {
|
||||
const { isOpen, onClose, attachmentId, attachmentOperations } = props;
|
||||
const { isOpen, onClose, attachmentId, attachmentOperations, issueServiceType = EIssueServiceType.ISSUES } = props;
|
||||
// states
|
||||
const [loader, setLoader] = useState(false);
|
||||
|
||||
// store hooks
|
||||
const {
|
||||
attachment: { getAttachmentById },
|
||||
} = useIssueDetail();
|
||||
} = useIssueDetail(issueServiceType);
|
||||
|
||||
// derived values
|
||||
const attachment = attachmentId ? getAttachmentById(attachmentId) : undefined;
|
||||
|
|
|
|||
|
|
@ -4,24 +4,17 @@ import { useCallback, useState } from "react";
|
|||
import { observer } from "mobx-react";
|
||||
// types
|
||||
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions } from "@plane/types";
|
||||
// ui
|
||||
import { Button } from "@plane/ui";
|
||||
|
||||
// components
|
||||
import { DisplayFiltersSelection, FiltersDropdown, FilterSelection, LayoutSelection } from "@/components/issues";
|
||||
// constants
|
||||
import {
|
||||
EIssueFilterType,
|
||||
EIssuesStoreType,
|
||||
EIssueLayoutTypes,
|
||||
ISSUE_DISPLAY_FILTERS_BY_LAYOUT,
|
||||
} from "@/constants/issue";
|
||||
import { EIssueFilterType, EIssueLayoutTypes, EIssuesStoreType, ISSUE_STORE_TO_FILTERS_MAP } from "@/constants/issue";
|
||||
// helpers
|
||||
import { isIssueFilterActive } from "@/helpers/filter.helper";
|
||||
// hooks
|
||||
import { useLabel, useProjectState, useMember, useIssues } from "@/hooks/store";
|
||||
// plane web types
|
||||
import { TProject } from "@/plane-web/types";
|
||||
// local components
|
||||
import { ProjectAnalyticsModal } from "../analytics";
|
||||
|
||||
type Props = {
|
||||
|
|
@ -29,8 +22,16 @@ type Props = {
|
|||
projectId: string;
|
||||
workspaceSlug: string;
|
||||
canUserCreateIssue: boolean | undefined;
|
||||
storeType?: EIssuesStoreType.PROJECT | EIssuesStoreType.EPIC;
|
||||
};
|
||||
const HeaderFilters = observer(({ currentProjectDetails, projectId, workspaceSlug, canUserCreateIssue }: Props) => {
|
||||
const HeaderFilters = observer((props: Props) => {
|
||||
const {
|
||||
currentProjectDetails,
|
||||
projectId,
|
||||
workspaceSlug,
|
||||
canUserCreateIssue,
|
||||
storeType = EIssuesStoreType.PROJECT,
|
||||
} = props;
|
||||
// states
|
||||
const [analyticsModal, setAnalyticsModal] = useState(false);
|
||||
// store hooks
|
||||
|
|
@ -39,11 +40,12 @@ const HeaderFilters = observer(({ currentProjectDetails, projectId, workspaceSlu
|
|||
} = useMember();
|
||||
const {
|
||||
issuesFilter: { issueFilters, updateFilters },
|
||||
} = useIssues(EIssuesStoreType.PROJECT);
|
||||
|
||||
} = useIssues(storeType);
|
||||
const { projectStates } = useProjectState();
|
||||
const { projectLabels } = useLabel();
|
||||
// derived values
|
||||
const activeLayout = issueFilters?.displayFilters?.layout;
|
||||
const layoutDisplayFiltersOptions = ISSUE_STORE_TO_FILTERS_MAP[storeType]?.[activeLayout];
|
||||
|
||||
const handleFiltersUpdate = useCallback(
|
||||
(key: keyof IIssueFilterOptions, value: string | string[]) => {
|
||||
|
|
@ -113,7 +115,7 @@ const HeaderFilters = observer(({ currentProjectDetails, projectId, workspaceSlu
|
|||
handleFiltersUpdate={handleFiltersUpdate}
|
||||
displayFilters={issueFilters?.displayFilters ?? {}}
|
||||
handleDisplayFiltersUpdate={handleDisplayFilters}
|
||||
layoutDisplayFiltersOptions={activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined}
|
||||
layoutDisplayFiltersOptions={layoutDisplayFiltersOptions}
|
||||
labels={projectLabels}
|
||||
memberIds={projectMemberIds ?? undefined}
|
||||
states={projectStates}
|
||||
|
|
@ -123,7 +125,7 @@ const HeaderFilters = observer(({ currentProjectDetails, projectId, workspaceSlu
|
|||
</FiltersDropdown>
|
||||
<FiltersDropdown title="Display" placement="bottom-end">
|
||||
<DisplayFiltersSelection
|
||||
layoutDisplayFiltersOptions={activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined}
|
||||
layoutDisplayFiltersOptions={layoutDisplayFiltersOptions}
|
||||
displayFilters={issueFilters?.displayFilters ?? {}}
|
||||
handleDisplayFiltersUpdate={handleDisplayFilters}
|
||||
displayProperties={issueFilters?.displayProperties ?? {}}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
"use client";
|
||||
import React, { FC } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { EIssueServiceType } from "@plane/constants";
|
||||
import { TIssueServiceType } from "@plane/types";
|
||||
// components
|
||||
import { IssueAttachmentItemList } from "@/components/issues/attachment";
|
||||
// helper
|
||||
|
|
@ -11,12 +13,13 @@ type Props = {
|
|||
projectId: string;
|
||||
issueId: string;
|
||||
disabled: boolean;
|
||||
issueServiceType?: TIssueServiceType;
|
||||
};
|
||||
|
||||
export const IssueAttachmentsCollapsibleContent: FC<Props> = observer((props) => {
|
||||
const { workspaceSlug, projectId, issueId, disabled } = props;
|
||||
const { workspaceSlug, projectId, issueId, disabled, issueServiceType = EIssueServiceType.ISSUES } = props;
|
||||
// helper
|
||||
const attachmentHelpers = useAttachmentOperations(workspaceSlug, projectId, issueId);
|
||||
const attachmentHelpers = useAttachmentOperations(workspaceSlug, projectId, issueId, issueServiceType);
|
||||
return (
|
||||
<IssueAttachmentItemList
|
||||
workspaceSlug={workspaceSlug}
|
||||
|
|
@ -24,6 +27,7 @@ export const IssueAttachmentsCollapsibleContent: FC<Props> = observer((props) =>
|
|||
issueId={issueId}
|
||||
disabled={disabled}
|
||||
attachmentHelpers={attachmentHelpers}
|
||||
issueServiceType={issueServiceType}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
"use client";
|
||||
import { useMemo } from "react";
|
||||
import { EIssueServiceType } from "@plane/constants";
|
||||
import { TIssueServiceType } from "@plane/types";
|
||||
// plane ui
|
||||
import { TOAST_TYPE, setPromiseToast, setToast } from "@plane/ui";
|
||||
// hooks
|
||||
|
|
@ -24,11 +26,12 @@ export type TAttachmentHelpers = {
|
|||
export const useAttachmentOperations = (
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
issueId: string
|
||||
issueId: string,
|
||||
issueServiceType: TIssueServiceType = EIssueServiceType.ISSUES
|
||||
): TAttachmentHelpers => {
|
||||
const {
|
||||
attachment: { createAttachment, removeAttachment, getAttachmentsUploadStatusByIssueId },
|
||||
} = useIssueDetail();
|
||||
} = useIssueDetail(issueServiceType);
|
||||
const { captureIssueEvent } = useEventTracker();
|
||||
|
||||
const attachmentOperations: TAttachmentOperations = useMemo(
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ import React, { FC, useCallback, useState } from "react";
|
|||
import { observer } from "mobx-react";
|
||||
import { FileRejection, useDropzone } from "react-dropzone";
|
||||
import { Plus } from "lucide-react";
|
||||
import { EIssueServiceType } from "@plane/constants";
|
||||
import { TIssueServiceType } from "@plane/types";
|
||||
// plane ui
|
||||
import { TOAST_TYPE, setToast } from "@plane/ui";
|
||||
// hooks
|
||||
|
|
@ -19,18 +21,31 @@ type Props = {
|
|||
issueId: string;
|
||||
customButton?: React.ReactNode;
|
||||
disabled?: boolean;
|
||||
issueServiceType?: TIssueServiceType;
|
||||
};
|
||||
|
||||
export const IssueAttachmentActionButton: FC<Props> = observer((props) => {
|
||||
const { workspaceSlug, projectId, issueId, customButton, disabled = false } = props;
|
||||
const {
|
||||
workspaceSlug,
|
||||
projectId,
|
||||
issueId,
|
||||
customButton,
|
||||
disabled = false,
|
||||
issueServiceType = EIssueServiceType.ISSUES,
|
||||
} = props;
|
||||
// state
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
// store hooks
|
||||
const { setLastWidgetAction, fetchActivities } = useIssueDetail();
|
||||
const { setLastWidgetAction, fetchActivities } = useIssueDetail(issueServiceType);
|
||||
// file size
|
||||
const { maxFileSize } = useFileSize();
|
||||
// operations
|
||||
const { operations: attachmentOperations } = useAttachmentOperations(workspaceSlug, projectId, issueId);
|
||||
const { operations: attachmentOperations } = useAttachmentOperations(
|
||||
workspaceSlug,
|
||||
projectId,
|
||||
issueId,
|
||||
issueServiceType
|
||||
);
|
||||
// handlers
|
||||
const handleFetchPropertyActivities = useCallback(() => {
|
||||
fetchActivities(workspaceSlug, projectId, issueId);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
"use client";
|
||||
import React, { FC } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { EIssueServiceType } from "@plane/constants";
|
||||
import { TIssueServiceType } from "@plane/types";
|
||||
import { Collapsible } from "@plane/ui";
|
||||
// components
|
||||
import {
|
||||
|
|
@ -15,12 +17,13 @@ type Props = {
|
|||
projectId: string;
|
||||
issueId: string;
|
||||
disabled?: boolean;
|
||||
issueServiceType?: TIssueServiceType;
|
||||
};
|
||||
|
||||
export const AttachmentsCollapsible: FC<Props> = observer((props) => {
|
||||
const { workspaceSlug, projectId, issueId, disabled = false } = props;
|
||||
const { workspaceSlug, projectId, issueId, disabled = false, issueServiceType = EIssueServiceType.ISSUES } = props;
|
||||
// store hooks
|
||||
const { openWidgets, toggleOpenWidget } = useIssueDetail();
|
||||
const { openWidgets, toggleOpenWidget } = useIssueDetail(issueServiceType);
|
||||
|
||||
// derived values
|
||||
const isCollapsibleOpen = openWidgets.includes("attachments");
|
||||
|
|
@ -36,6 +39,7 @@ export const AttachmentsCollapsible: FC<Props> = observer((props) => {
|
|||
projectId={projectId}
|
||||
issueId={issueId}
|
||||
disabled={disabled}
|
||||
issueServiceType={issueServiceType}
|
||||
/>
|
||||
}
|
||||
buttonClassName="w-full"
|
||||
|
|
@ -45,6 +49,7 @@ export const AttachmentsCollapsible: FC<Props> = observer((props) => {
|
|||
projectId={projectId}
|
||||
issueId={issueId}
|
||||
disabled={disabled}
|
||||
issueServiceType={issueServiceType}
|
||||
/>
|
||||
</Collapsible>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
"use client";
|
||||
import React, { FC, useMemo } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { EIssueServiceType } from "@plane/constants";
|
||||
import { TIssueServiceType } from "@plane/types";
|
||||
import { CollapsibleButton } from "@plane/ui";
|
||||
// components
|
||||
import { IssueAttachmentActionButton } from "@/components/issues/issue-detail-widgets";
|
||||
|
|
@ -13,14 +15,15 @@ type Props = {
|
|||
projectId: string;
|
||||
issueId: string;
|
||||
disabled: boolean;
|
||||
issueServiceType?: TIssueServiceType;
|
||||
};
|
||||
|
||||
export const IssueAttachmentsCollapsibleTitle: FC<Props> = observer((props) => {
|
||||
const { isOpen, workspaceSlug, projectId, issueId, disabled } = props;
|
||||
const { isOpen, workspaceSlug, projectId, issueId, disabled, issueServiceType = EIssueServiceType.ISSUES } = props;
|
||||
// store hooks
|
||||
const {
|
||||
issue: { getIssueById },
|
||||
} = useIssueDetail();
|
||||
} = useIssueDetail(issueServiceType);
|
||||
|
||||
// derived values
|
||||
const issue = getIssueById(issueId);
|
||||
|
|
@ -48,6 +51,7 @@ export const IssueAttachmentsCollapsibleTitle: FC<Props> = observer((props) => {
|
|||
projectId={projectId}
|
||||
issueId={issueId}
|
||||
disabled={disabled}
|
||||
issueServiceType={issueServiceType}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
"use client";
|
||||
import React, { FC } from "react";
|
||||
import { EIssueServiceType } from "@plane/constants";
|
||||
import { TIssueServiceType } from "@plane/types";
|
||||
// components
|
||||
import { LinkList } from "../../issue-detail/links";
|
||||
// helper
|
||||
|
|
@ -10,13 +12,21 @@ type Props = {
|
|||
projectId: string;
|
||||
issueId: string;
|
||||
disabled: boolean;
|
||||
issueServiceType?: TIssueServiceType;
|
||||
};
|
||||
|
||||
export const IssueLinksCollapsibleContent: FC<Props> = (props) => {
|
||||
const { workspaceSlug, projectId, issueId, disabled } = props;
|
||||
const { workspaceSlug, projectId, issueId, disabled, issueServiceType = EIssueServiceType.ISSUES } = props;
|
||||
|
||||
// helper
|
||||
const handleLinkOperations = useLinkOperations(workspaceSlug, projectId, issueId);
|
||||
const handleLinkOperations = useLinkOperations(workspaceSlug, projectId, issueId, issueServiceType);
|
||||
|
||||
return <LinkList issueId={issueId} linkOperations={handleLinkOperations} disabled={disabled} />;
|
||||
return (
|
||||
<LinkList
|
||||
issueId={issueId}
|
||||
linkOperations={handleLinkOperations}
|
||||
disabled={disabled}
|
||||
issueServiceType={issueServiceType}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,14 +1,20 @@
|
|||
"use client";
|
||||
import { useMemo } from "react";
|
||||
import { TIssueLink } from "@plane/types";
|
||||
import { EIssueServiceType } from "@plane/constants";
|
||||
import { TIssueLink, TIssueServiceType } from "@plane/types";
|
||||
import { TOAST_TYPE, setToast } from "@plane/ui";
|
||||
// hooks
|
||||
import { useIssueDetail } from "@/hooks/store";
|
||||
// types
|
||||
import { TLinkOperations } from "../../issue-detail/links";
|
||||
|
||||
export const useLinkOperations = (workspaceSlug: string, projectId: string, issueId: string): TLinkOperations => {
|
||||
const { createLink, updateLink, removeLink } = useIssueDetail();
|
||||
export const useLinkOperations = (
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
issueId: string,
|
||||
issueServiceType: TIssueServiceType = EIssueServiceType.ISSUES
|
||||
): TLinkOperations => {
|
||||
const { createLink, updateLink, removeLink } = useIssueDetail(issueServiceType);
|
||||
|
||||
const handleLinkOperations: TLinkOperations = useMemo(
|
||||
() => ({
|
||||
|
|
|
|||
|
|
@ -2,18 +2,21 @@
|
|||
import React, { FC } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { Plus } from "lucide-react";
|
||||
import { EIssueServiceType } from "@plane/constants";
|
||||
import { TIssueServiceType } from "@plane/types";
|
||||
// hooks
|
||||
import { useIssueDetail } from "@/hooks/store";
|
||||
|
||||
type Props = {
|
||||
customButton?: React.ReactNode;
|
||||
disabled?: boolean;
|
||||
issueServiceType?: TIssueServiceType;
|
||||
};
|
||||
|
||||
export const IssueLinksActionButton: FC<Props> = observer((props) => {
|
||||
const { customButton, disabled = false } = props;
|
||||
const { customButton, disabled = false, issueServiceType = EIssueServiceType.ISSUES } = props;
|
||||
// store hooks
|
||||
const { toggleIssueLinkModal } = useIssueDetail();
|
||||
const { toggleIssueLinkModal } = useIssueDetail(issueServiceType);
|
||||
|
||||
// handlers
|
||||
const handleOnClick = (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
"use client";
|
||||
import React, { FC } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { EIssueServiceType } from "@plane/constants";
|
||||
import { TIssueServiceType } from "@plane/types";
|
||||
import { Collapsible } from "@plane/ui";
|
||||
// components
|
||||
import { IssueLinksCollapsibleContent, IssueLinksCollapsibleTitle } from "@/components/issues/issue-detail-widgets";
|
||||
|
|
@ -12,12 +14,13 @@ type Props = {
|
|||
projectId: string;
|
||||
issueId: string;
|
||||
disabled?: boolean;
|
||||
issueServiceType?: TIssueServiceType;
|
||||
};
|
||||
|
||||
export const LinksCollapsible: FC<Props> = observer((props) => {
|
||||
const { workspaceSlug, projectId, issueId, disabled = false } = props;
|
||||
const { workspaceSlug, projectId, issueId, disabled = false, issueServiceType = EIssueServiceType.ISSUES } = props;
|
||||
// store hooks
|
||||
const { openWidgets, toggleOpenWidget } = useIssueDetail();
|
||||
const { openWidgets, toggleOpenWidget } = useIssueDetail(issueServiceType);
|
||||
|
||||
// derived values
|
||||
const isCollapsibleOpen = openWidgets.includes("links");
|
||||
|
|
@ -26,7 +29,14 @@ export const LinksCollapsible: FC<Props> = observer((props) => {
|
|||
<Collapsible
|
||||
isOpen={isCollapsibleOpen}
|
||||
onToggle={() => toggleOpenWidget("links")}
|
||||
title={<IssueLinksCollapsibleTitle isOpen={isCollapsibleOpen} issueId={issueId} disabled={disabled} />}
|
||||
title={
|
||||
<IssueLinksCollapsibleTitle
|
||||
isOpen={isCollapsibleOpen}
|
||||
issueId={issueId}
|
||||
disabled={disabled}
|
||||
issueServiceType={issueServiceType}
|
||||
/>
|
||||
}
|
||||
buttonClassName="w-full"
|
||||
>
|
||||
<IssueLinksCollapsibleContent
|
||||
|
|
@ -34,6 +44,7 @@ export const LinksCollapsible: FC<Props> = observer((props) => {
|
|||
projectId={projectId}
|
||||
issueId={issueId}
|
||||
disabled={disabled}
|
||||
issueServiceType={issueServiceType}
|
||||
/>
|
||||
</Collapsible>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
"use client";
|
||||
import React, { FC, useMemo } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { EIssueServiceType } from "@plane/constants";
|
||||
import { TIssueServiceType } from "@plane/types";
|
||||
import { CollapsibleButton } from "@plane/ui";
|
||||
// components
|
||||
import { IssueLinksActionButton } from "@/components/issues/issue-detail-widgets";
|
||||
|
|
@ -11,14 +13,15 @@ type Props = {
|
|||
isOpen: boolean;
|
||||
issueId: string;
|
||||
disabled: boolean;
|
||||
issueServiceType?: TIssueServiceType;
|
||||
};
|
||||
|
||||
export const IssueLinksCollapsibleTitle: FC<Props> = observer((props) => {
|
||||
const { isOpen, issueId, disabled } = props;
|
||||
const { isOpen, issueId, disabled, issueServiceType = EIssueServiceType.ISSUES } = props;
|
||||
// store hooks
|
||||
const {
|
||||
issue: { getIssueById },
|
||||
} = useIssueDetail();
|
||||
} = useIssueDetail(issueServiceType);
|
||||
|
||||
// derived values
|
||||
const issue = getIssueById(issueId);
|
||||
|
|
@ -40,7 +43,9 @@ export const IssueLinksCollapsibleTitle: FC<Props> = observer((props) => {
|
|||
isOpen={isOpen}
|
||||
title="Links"
|
||||
indicatorElement={indicatorElement}
|
||||
actionItemElement={!disabled && <IssueLinksActionButton disabled={disabled} />}
|
||||
actionItemElement={
|
||||
!disabled && <IssueLinksActionButton issueServiceType={issueServiceType} disabled={disabled} />
|
||||
}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
"use client";
|
||||
import { FC, useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { TIssue, TIssueRelationIdMap } from "@plane/types";
|
||||
import { EIssueServiceType } from "@plane/constants";
|
||||
import { TIssue, TIssueRelationIdMap, TIssueServiceType } from "@plane/types";
|
||||
import { Collapsible } from "@plane/ui";
|
||||
// components
|
||||
import { RelationIssueList } from "@/components/issues";
|
||||
|
|
@ -20,6 +21,7 @@ type Props = {
|
|||
projectId: string;
|
||||
issueId: string;
|
||||
disabled: boolean;
|
||||
issueServiceType?: TIssueServiceType;
|
||||
};
|
||||
|
||||
type TIssueCrudState = { toggle: boolean; issueId: string | undefined; issue: TIssue | undefined };
|
||||
|
|
@ -33,7 +35,7 @@ export type TRelationObject = {
|
|||
};
|
||||
|
||||
export const RelationsCollapsibleContent: FC<Props> = observer((props) => {
|
||||
const { workspaceSlug, projectId, issueId, disabled = false } = props;
|
||||
const { workspaceSlug, projectId, issueId, disabled = false, issueServiceType = EIssueServiceType.ISSUES } = props;
|
||||
// state
|
||||
const [issueCrudState, setIssueCrudState] = useState<{
|
||||
update: TIssueCrudState;
|
||||
|
|
@ -56,7 +58,7 @@ export const RelationsCollapsibleContent: FC<Props> = observer((props) => {
|
|||
relation: { getRelationsByIssueId },
|
||||
toggleDeleteIssueModal,
|
||||
toggleCreateIssueModal,
|
||||
} = useIssueDetail();
|
||||
} = useIssueDetail(issueServiceType);
|
||||
|
||||
// helper
|
||||
const issueOperations = useRelationOperations();
|
||||
|
|
@ -129,6 +131,7 @@ export const RelationsCollapsibleContent: FC<Props> = observer((props) => {
|
|||
disabled={disabled}
|
||||
issueOperations={issueOperations}
|
||||
handleIssueCrudState={handleIssueCrudState}
|
||||
issueServiceType={issueServiceType}
|
||||
/>
|
||||
</Collapsible>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
"use client";
|
||||
import { useMemo } from "react";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { TIssue } from "@plane/types";
|
||||
import { EIssueServiceType } from "@plane/constants";
|
||||
import { TIssue, TIssueServiceType } from "@plane/types";
|
||||
import { TOAST_TYPE, setToast } from "@plane/ui";
|
||||
// constants
|
||||
import { ISSUE_DELETED, ISSUE_UPDATED } from "@/constants/event-tracker";
|
||||
|
|
@ -16,8 +17,10 @@ export type TRelationIssueOperations = {
|
|||
remove: (workspaceSlug: string, projectId: string, issueId: string) => Promise<void>;
|
||||
};
|
||||
|
||||
export const useRelationOperations = (): TRelationIssueOperations => {
|
||||
const { updateIssue, removeIssue } = useIssueDetail();
|
||||
export const useRelationOperations = (
|
||||
issueServiceType: TIssueServiceType = EIssueServiceType.ISSUES
|
||||
): TRelationIssueOperations => {
|
||||
const { updateIssue, removeIssue } = useIssueDetail(issueServiceType);
|
||||
const { captureIssueEvent } = useEventTracker();
|
||||
const pathname = usePathname();
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@
|
|||
import React, { FC } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { Plus } from "lucide-react";
|
||||
import { EIssueServiceType } from "@plane/constants";
|
||||
import { TIssueServiceType } from "@plane/types";
|
||||
import { CustomMenu } from "@plane/ui";
|
||||
// hooks
|
||||
import { useIssueDetail } from "@/hooks/store";
|
||||
|
|
@ -13,12 +15,13 @@ type Props = {
|
|||
issueId: string;
|
||||
customButton?: React.ReactNode;
|
||||
disabled?: boolean;
|
||||
issueServiceType?: TIssueServiceType;
|
||||
};
|
||||
|
||||
export const RelationActionButton: FC<Props> = observer((props) => {
|
||||
const { customButton, issueId, disabled = false } = props;
|
||||
const { customButton, issueId, disabled = false, issueServiceType = EIssueServiceType.ISSUES } = props;
|
||||
// store hooks
|
||||
const { toggleRelationModal, setRelationKey } = useIssueDetail();
|
||||
const { toggleRelationModal, setRelationKey } = useIssueDetail(issueServiceType);
|
||||
|
||||
const ISSUE_RELATION_OPTIONS = useTimeLineRelationOptions();
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
"use client";
|
||||
import React, { FC } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { EIssueServiceType } from "@plane/constants";
|
||||
import { TIssueServiceType } from "@plane/types";
|
||||
import { Collapsible } from "@plane/ui";
|
||||
// components
|
||||
import { RelationsCollapsibleContent, RelationsCollapsibleTitle } from "@/components/issues/issue-detail-widgets";
|
||||
|
|
@ -12,12 +14,13 @@ type Props = {
|
|||
projectId: string;
|
||||
issueId: string;
|
||||
disabled?: boolean;
|
||||
issueServiceType?: TIssueServiceType;
|
||||
};
|
||||
|
||||
export const RelationsCollapsible: FC<Props> = observer((props) => {
|
||||
const { workspaceSlug, projectId, issueId, disabled = false } = props;
|
||||
const { workspaceSlug, projectId, issueId, disabled = false, issueServiceType = EIssueServiceType.ISSUES } = props;
|
||||
// store hooks
|
||||
const { openWidgets, toggleOpenWidget } = useIssueDetail();
|
||||
const { openWidgets, toggleOpenWidget } = useIssueDetail(issueServiceType);
|
||||
|
||||
// derived values
|
||||
const isCollapsibleOpen = openWidgets.includes("relations");
|
||||
|
|
@ -26,7 +29,14 @@ export const RelationsCollapsible: FC<Props> = observer((props) => {
|
|||
<Collapsible
|
||||
isOpen={isCollapsibleOpen}
|
||||
onToggle={() => toggleOpenWidget("relations")}
|
||||
title={<RelationsCollapsibleTitle isOpen={isCollapsibleOpen} issueId={issueId} disabled={disabled} />}
|
||||
title={
|
||||
<RelationsCollapsibleTitle
|
||||
isOpen={isCollapsibleOpen}
|
||||
issueId={issueId}
|
||||
disabled={disabled}
|
||||
issueServiceType={issueServiceType}
|
||||
/>
|
||||
}
|
||||
buttonClassName="w-full"
|
||||
>
|
||||
<RelationsCollapsibleContent
|
||||
|
|
@ -34,6 +44,7 @@ export const RelationsCollapsible: FC<Props> = observer((props) => {
|
|||
projectId={projectId}
|
||||
issueId={issueId}
|
||||
disabled={disabled}
|
||||
issueServiceType={issueServiceType}
|
||||
/>
|
||||
</Collapsible>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
"use client";
|
||||
import React, { FC, useMemo } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { EIssueServiceType } from "@plane/constants";
|
||||
import { TIssueServiceType } from "@plane/types";
|
||||
import { CollapsibleButton } from "@plane/ui";
|
||||
// components
|
||||
import { RelationActionButton } from "@/components/issues/issue-detail-widgets";
|
||||
|
|
@ -13,14 +15,15 @@ type Props = {
|
|||
isOpen: boolean;
|
||||
issueId: string;
|
||||
disabled: boolean;
|
||||
issueServiceType?: TIssueServiceType;
|
||||
};
|
||||
|
||||
export const RelationsCollapsibleTitle: FC<Props> = observer((props) => {
|
||||
const { isOpen, issueId, disabled } = props;
|
||||
const { isOpen, issueId, disabled, issueServiceType = EIssueServiceType.ISSUES } = props;
|
||||
// store hook
|
||||
const {
|
||||
relation: { getRelationCountByIssueId },
|
||||
} = useIssueDetail();
|
||||
} = useIssueDetail(issueServiceType);
|
||||
|
||||
const ISSUE_RELATION_OPTIONS = useTimeLineRelationOptions();
|
||||
// derived values
|
||||
|
|
@ -41,7 +44,9 @@ export const RelationsCollapsibleTitle: FC<Props> = observer((props) => {
|
|||
isOpen={isOpen}
|
||||
title="Relations"
|
||||
indicatorElement={indicatorElement}
|
||||
actionItemElement={!disabled && <RelationActionButton issueId={issueId} disabled={disabled} />}
|
||||
actionItemElement={
|
||||
!disabled && <RelationActionButton issueId={issueId} disabled={disabled} issueServiceType={issueServiceType} />
|
||||
}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
"use client";
|
||||
import React, { FC, useCallback, useEffect, useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { TIssue } from "@plane/types";
|
||||
import { EIssueServiceType } from "@plane/constants";
|
||||
import { TIssue, TIssueServiceType } from "@plane/types";
|
||||
// components
|
||||
import { DeleteIssueModal } from "@/components/issues/delete-issue-modal";
|
||||
import { CreateUpdateIssueModal } from "@/components/issues/issue-modal";
|
||||
|
|
@ -16,12 +17,13 @@ type Props = {
|
|||
projectId: string;
|
||||
parentIssueId: string;
|
||||
disabled: boolean;
|
||||
issueServiceType?: TIssueServiceType;
|
||||
};
|
||||
|
||||
type TIssueCrudState = { toggle: boolean; parentIssueId: string | undefined; issue: TIssue | undefined };
|
||||
|
||||
export const SubIssuesCollapsibleContent: FC<Props> = observer((props) => {
|
||||
const { workspaceSlug, projectId, parentIssueId, disabled } = props;
|
||||
const { workspaceSlug, projectId, parentIssueId, disabled, issueServiceType = EIssueServiceType.ISSUES } = props;
|
||||
// state
|
||||
const [issueCrudState, setIssueCrudState] = useState<{
|
||||
create: TIssueCrudState;
|
||||
|
|
@ -58,7 +60,7 @@ export const SubIssuesCollapsibleContent: FC<Props> = observer((props) => {
|
|||
} = useIssueDetail();
|
||||
|
||||
// helpers
|
||||
const subIssueOperations = useSubIssueOperations();
|
||||
const subIssueOperations = useSubIssueOperations(issueServiceType);
|
||||
const subIssueHelpers = subIssueHelpersByIssueId(`${parentIssueId}_root`);
|
||||
|
||||
// handler
|
||||
|
|
@ -95,7 +97,6 @@ export const SubIssuesCollapsibleContent: FC<Props> = observer((props) => {
|
|||
|
||||
useEffect(() => {
|
||||
handleFetchSubIssues();
|
||||
|
||||
return () => {
|
||||
handleFetchSubIssues();
|
||||
};
|
||||
|
|
@ -123,6 +124,7 @@ export const SubIssuesCollapsibleContent: FC<Props> = observer((props) => {
|
|||
disabled={!disabled}
|
||||
handleIssueCrudState={handleIssueCrudState}
|
||||
subIssueOperations={subIssueOperations}
|
||||
issueServiceType={issueServiceType}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
"use client";
|
||||
import { useMemo } from "react";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { TIssue } from "@plane/types";
|
||||
import { EIssueServiceType } from "@plane/constants";
|
||||
import { TIssue, TIssueServiceType } from "@plane/types";
|
||||
import { TOAST_TYPE, setToast } from "@plane/ui";
|
||||
// helper
|
||||
import { copyTextToClipboard } from "@/helpers/string.helper";
|
||||
|
|
@ -16,15 +17,17 @@ export type TRelationIssueOperations = {
|
|||
remove: (workspaceSlug: string, projectId: string, issueId: string) => Promise<void>;
|
||||
};
|
||||
|
||||
export const useSubIssueOperations = (): TSubIssueOperations => {
|
||||
export const useSubIssueOperations = (
|
||||
issueServiceType: TIssueServiceType = EIssueServiceType.ISSUES
|
||||
): TSubIssueOperations => {
|
||||
const {
|
||||
subIssues: { setSubIssueHelpers },
|
||||
fetchSubIssues,
|
||||
createSubIssues,
|
||||
updateSubIssue,
|
||||
removeSubIssue,
|
||||
deleteSubIssue,
|
||||
} = useIssueDetail();
|
||||
const { removeSubIssue } = useIssueDetail(issueServiceType);
|
||||
const { captureIssueEvent } = useEventTracker();
|
||||
const pathname = usePathname();
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,8 @@
|
|||
import React, { FC } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { LayersIcon, Plus } from "lucide-react";
|
||||
import { TIssue } from "@plane/types";
|
||||
import { EIssueServiceType } from "@plane/constants";
|
||||
import { TIssue, TIssueServiceType } from "@plane/types";
|
||||
import { CustomMenu } from "@plane/ui";
|
||||
// hooks
|
||||
import { useEventTracker, useIssueDetail } from "@/hooks/store";
|
||||
|
|
@ -11,10 +12,11 @@ type Props = {
|
|||
issueId: string;
|
||||
customButton?: React.ReactNode;
|
||||
disabled?: boolean;
|
||||
issueServiceType?: TIssueServiceType;
|
||||
};
|
||||
|
||||
export const SubIssuesActionButton: FC<Props> = observer((props) => {
|
||||
const { issueId, customButton, disabled = false } = props;
|
||||
const { issueId, customButton, disabled = false, issueServiceType = EIssueServiceType.ISSUES } = props;
|
||||
// store hooks
|
||||
const {
|
||||
issue: { getIssueById },
|
||||
|
|
@ -22,7 +24,7 @@ export const SubIssuesActionButton: FC<Props> = observer((props) => {
|
|||
toggleSubIssuesModal,
|
||||
setIssueCrudOperationState,
|
||||
issueCrudOperationState,
|
||||
} = useIssueDetail();
|
||||
} = useIssueDetail(issueServiceType);
|
||||
const { setTrackElement } = useEventTracker();
|
||||
|
||||
// derived values
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
"use client";
|
||||
import React, { FC } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { EIssueServiceType } from "@plane/constants";
|
||||
import { TIssueServiceType } from "@plane/types";
|
||||
import { Collapsible } from "@plane/ui";
|
||||
// components
|
||||
import { SubIssuesCollapsibleContent, SubIssuesCollapsibleTitle } from "@/components/issues/issue-detail-widgets";
|
||||
|
|
@ -12,13 +14,14 @@ type Props = {
|
|||
projectId: string;
|
||||
issueId: string;
|
||||
disabled?: boolean;
|
||||
issueServiceType?: TIssueServiceType;
|
||||
};
|
||||
|
||||
export const SubIssuesCollapsible: FC<Props> = observer((props) => {
|
||||
const { workspaceSlug, projectId, issueId, disabled = false } = props;
|
||||
const { workspaceSlug, projectId, issueId, disabled = false, issueServiceType = EIssueServiceType.ISSUES } = props;
|
||||
|
||||
// store hooks
|
||||
const { openWidgets, toggleOpenWidget } = useIssueDetail();
|
||||
const { openWidgets, toggleOpenWidget } = useIssueDetail(issueServiceType);
|
||||
|
||||
// derived state
|
||||
const isCollapsibleOpen = openWidgets.includes("sub-issues");
|
||||
|
|
@ -27,7 +30,14 @@ export const SubIssuesCollapsible: FC<Props> = observer((props) => {
|
|||
<Collapsible
|
||||
isOpen={isCollapsibleOpen}
|
||||
onToggle={() => toggleOpenWidget("sub-issues")}
|
||||
title={<SubIssuesCollapsibleTitle isOpen={isCollapsibleOpen} parentIssueId={issueId} disabled={disabled} />}
|
||||
title={
|
||||
<SubIssuesCollapsibleTitle
|
||||
isOpen={isCollapsibleOpen}
|
||||
parentIssueId={issueId}
|
||||
disabled={disabled}
|
||||
issueServiceType={issueServiceType}
|
||||
/>
|
||||
}
|
||||
buttonClassName="w-full"
|
||||
>
|
||||
<SubIssuesCollapsibleContent
|
||||
|
|
@ -35,6 +45,7 @@ export const SubIssuesCollapsible: FC<Props> = observer((props) => {
|
|||
projectId={projectId}
|
||||
parentIssueId={issueId}
|
||||
disabled={disabled}
|
||||
issueServiceType={issueServiceType}
|
||||
/>
|
||||
</Collapsible>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
"use client";
|
||||
import React, { FC, useMemo } from "react";
|
||||
import React, { FC } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { EIssueServiceType } from "@plane/constants";
|
||||
import { TIssueServiceType } from "@plane/types";
|
||||
import { CircularProgressIndicator, CollapsibleButton } from "@plane/ui";
|
||||
// components
|
||||
import { SubIssuesActionButton } from "@/components/issues/issue-detail-widgets";
|
||||
|
|
@ -11,14 +13,15 @@ type Props = {
|
|||
isOpen: boolean;
|
||||
parentIssueId: string;
|
||||
disabled: boolean;
|
||||
issueServiceType?: TIssueServiceType;
|
||||
};
|
||||
|
||||
export const SubIssuesCollapsibleTitle: FC<Props> = observer((props) => {
|
||||
const { isOpen, parentIssueId, disabled } = props;
|
||||
const { isOpen, parentIssueId, disabled, issueServiceType = EIssueServiceType.ISSUES } = props;
|
||||
// store hooks
|
||||
const {
|
||||
subIssues: { subIssuesByIssueId, stateDistributionByIssueId },
|
||||
} = useIssueDetail();
|
||||
} = useIssueDetail(issueServiceType);
|
||||
|
||||
// derived data
|
||||
const subIssuesDistribution = stateDistributionByIssueId(parentIssueId);
|
||||
|
|
@ -32,25 +35,23 @@ export const SubIssuesCollapsibleTitle: FC<Props> = observer((props) => {
|
|||
const totalCount = subIssues.length;
|
||||
const percentage = completedCount && totalCount ? (completedCount / totalCount) * 100 : 0;
|
||||
|
||||
// indicator element
|
||||
const indicatorElement = useMemo(
|
||||
() => (
|
||||
return (
|
||||
<CollapsibleButton
|
||||
isOpen={isOpen}
|
||||
title="Sub-issues"
|
||||
indicatorElement={
|
||||
<div className="flex items-center gap-1.5 text-custom-text-300 text-sm">
|
||||
<CircularProgressIndicator size={18} percentage={percentage} strokeWidth={3} />
|
||||
<span>
|
||||
{completedCount}/{totalCount} Done
|
||||
</span>
|
||||
</div>
|
||||
),
|
||||
[completedCount, totalCount, percentage]
|
||||
);
|
||||
|
||||
return (
|
||||
<CollapsibleButton
|
||||
isOpen={isOpen}
|
||||
title="Sub-issues"
|
||||
indicatorElement={indicatorElement}
|
||||
actionItemElement={!disabled && <SubIssuesActionButton issueId={parentIssueId} disabled={disabled} />}
|
||||
}
|
||||
actionItemElement={
|
||||
!disabled && (
|
||||
<SubIssuesActionButton issueId={parentIssueId} disabled={disabled} issueServiceType={issueServiceType} />
|
||||
)
|
||||
}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -2,7 +2,8 @@
|
|||
|
||||
import { FC, useMemo } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { IIssueLabel, TIssue } from "@plane/types";
|
||||
import { EIssueServiceType } from "@plane/constants";
|
||||
import { IIssueLabel, TIssue, TIssueServiceType } from "@plane/types";
|
||||
// components
|
||||
import { TOAST_TYPE, setToast } from "@plane/ui";
|
||||
// hooks
|
||||
|
|
@ -21,6 +22,7 @@ export type TIssueLabel = {
|
|||
disabled: boolean;
|
||||
isInboxIssue?: boolean;
|
||||
onLabelUpdate?: (labelIds: string[]) => void;
|
||||
issueServiceType?: TIssueServiceType;
|
||||
};
|
||||
|
||||
export type TLabelOperations = {
|
||||
|
|
@ -29,13 +31,21 @@ export type TLabelOperations = {
|
|||
};
|
||||
|
||||
export const IssueLabel: FC<TIssueLabel> = observer((props) => {
|
||||
const { workspaceSlug, projectId, issueId, disabled = false, isInboxIssue = false, onLabelUpdate } = props;
|
||||
const {
|
||||
workspaceSlug,
|
||||
projectId,
|
||||
issueId,
|
||||
disabled = false,
|
||||
isInboxIssue = false,
|
||||
onLabelUpdate,
|
||||
issueServiceType = EIssueServiceType.ISSUES,
|
||||
} = props;
|
||||
// hooks
|
||||
const { updateIssue } = useIssueDetail();
|
||||
const { updateIssue } = useIssueDetail(issueServiceType);
|
||||
const { createLabel } = useLabel();
|
||||
const {
|
||||
issue: { getIssueById },
|
||||
} = useIssueDetail();
|
||||
} = useIssueDetail(issueServiceType);
|
||||
const { getIssueInboxByIssueId } = useProjectInbox();
|
||||
const { allowPermissions } = useUserPermissions();
|
||||
|
||||
|
|
|
|||
|
|
@ -3,8 +3,9 @@
|
|||
import { FC, useEffect } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import { EIssueServiceType } from "@plane/constants";
|
||||
// plane types
|
||||
import type { TIssueLinkEditableFields } from "@plane/types";
|
||||
import type { TIssueLinkEditableFields, TIssueServiceType } from "@plane/types";
|
||||
// plane ui
|
||||
import { Button, Input, ModalCore } from "@plane/ui";
|
||||
// hooks
|
||||
|
|
@ -22,6 +23,7 @@ export type TIssueLinkCreateEditModal = {
|
|||
isModalOpen: boolean;
|
||||
handleOnClose?: () => void;
|
||||
linkOperations: TLinkOperationsModal;
|
||||
issueServiceType?: TIssueServiceType;
|
||||
};
|
||||
|
||||
const defaultValues: TIssueLinkCreateFormFieldOptions = {
|
||||
|
|
@ -31,7 +33,7 @@ const defaultValues: TIssueLinkCreateFormFieldOptions = {
|
|||
|
||||
export const IssueLinkCreateUpdateModal: FC<TIssueLinkCreateEditModal> = observer((props) => {
|
||||
// props
|
||||
const { isModalOpen, handleOnClose, linkOperations } = props;
|
||||
const { isModalOpen, handleOnClose, linkOperations, issueServiceType = EIssueServiceType.ISSUES } = props;
|
||||
// react hook form
|
||||
const {
|
||||
formState: { errors, isSubmitting },
|
||||
|
|
@ -42,7 +44,7 @@ export const IssueLinkCreateUpdateModal: FC<TIssueLinkCreateEditModal> = observe
|
|||
defaultValues,
|
||||
});
|
||||
// store hooks
|
||||
const { issueLinkData: preloadedData, setIssueLinkData } = useIssueDetail();
|
||||
const { issueLinkData: preloadedData, setIssueLinkData } = useIssueDetail(issueServiceType);
|
||||
|
||||
const onClose = () => {
|
||||
setIssueLinkData(null);
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@
|
|||
import { FC } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { Pencil, Trash2, LinkIcon, ExternalLink } from "lucide-react";
|
||||
import { EIssueServiceType } from "@plane/constants";
|
||||
import { TIssueServiceType } from "@plane/types";
|
||||
// ui
|
||||
import { Tooltip, TOAST_TYPE, setToast, CustomMenu } from "@plane/ui";
|
||||
// helpers
|
||||
|
|
@ -17,17 +19,18 @@ type TIssueLinkItem = {
|
|||
linkId: string;
|
||||
linkOperations: TLinkOperationsModal;
|
||||
isNotAllowed: boolean;
|
||||
issueServiceType?: TIssueServiceType;
|
||||
};
|
||||
|
||||
export const IssueLinkItem: FC<TIssueLinkItem> = observer((props) => {
|
||||
// props
|
||||
const { linkId, linkOperations, isNotAllowed } = props;
|
||||
const { linkId, linkOperations, isNotAllowed, issueServiceType = EIssueServiceType.ISSUES } = props;
|
||||
// hooks
|
||||
const {
|
||||
toggleIssueLinkModal: toggleIssueLinkModalStore,
|
||||
setIssueLinkData,
|
||||
link: { getLinkById },
|
||||
} = useIssueDetail();
|
||||
} = useIssueDetail(issueServiceType);
|
||||
const { isMobile } = usePlatformOS();
|
||||
const linkDetail = getLinkById(linkId);
|
||||
if (!linkDetail) return <></>;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
import { FC } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { EIssueServiceType } from "@plane/constants";
|
||||
import { TIssueServiceType } from "@plane/types";
|
||||
// computed
|
||||
import { useIssueDetail } from "@/hooks/store";
|
||||
import { IssueLinkItem } from "./link-item";
|
||||
|
|
@ -12,15 +14,16 @@ type TLinkList = {
|
|||
issueId: string;
|
||||
linkOperations: TLinkOperationsModal;
|
||||
disabled?: boolean;
|
||||
issueServiceType?: TIssueServiceType;
|
||||
};
|
||||
|
||||
export const LinkList: FC<TLinkList> = observer((props) => {
|
||||
// props
|
||||
const { issueId, linkOperations, disabled = false } = props;
|
||||
const { issueId, linkOperations, disabled = false, issueServiceType = EIssueServiceType.ISSUES } = props;
|
||||
// hooks
|
||||
const {
|
||||
link: { getLinksByIssueId },
|
||||
} = useIssueDetail();
|
||||
} = useIssueDetail(issueServiceType);
|
||||
|
||||
const issueLinks = getLinksByIssueId(issueId);
|
||||
|
||||
|
|
@ -29,7 +32,13 @@ export const LinkList: FC<TLinkList> = observer((props) => {
|
|||
return (
|
||||
<div className="flex flex-col gap-2 py-4">
|
||||
{issueLinks.map((linkId) => (
|
||||
<IssueLinkItem key={linkId} linkId={linkId} linkOperations={linkOperations} isNotAllowed={disabled} />
|
||||
<IssueLinkItem
|
||||
key={linkId}
|
||||
linkId={linkId}
|
||||
linkOperations={linkOperations}
|
||||
isNotAllowed={disabled}
|
||||
issueServiceType={issueServiceType}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -55,11 +55,16 @@ export const IssueMainContent: React.FC<Props> = observer((props) => {
|
|||
const issue = issueId ? getIssueById(issueId) : undefined;
|
||||
|
||||
// debounced duplicate issues swr
|
||||
const { duplicateIssues } = useDebouncedDuplicateIssues(projectDetails?.workspace.toString(), projectDetails?.id, {
|
||||
const { duplicateIssues } = useDebouncedDuplicateIssues(
|
||||
workspaceSlug,
|
||||
projectDetails?.workspace.toString(),
|
||||
projectDetails?.id,
|
||||
{
|
||||
name: issue?.name,
|
||||
description_html: getTextContent(issue?.description_html),
|
||||
issueId: issue?.id,
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (isSubmitting === "submitted") {
|
||||
|
|
|
|||
|
|
@ -88,6 +88,7 @@ export const IssueParentSelect: React.FC<TIssueParentSelect> = observer((props)
|
|||
isOpen={isParentIssueModalOpen === issueId}
|
||||
handleClose={() => toggleParentIssueModal(null)}
|
||||
onChange={(issue: any) => handleParentIssue(issue?.id)}
|
||||
searchEpic
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import { TIssue } from "@plane/types";
|
|||
import { TOAST_TYPE, setPromiseToast, setToast } from "@plane/ui";
|
||||
// components
|
||||
import { EmptyState } from "@/components/common";
|
||||
import { IssuePeekOverview } from "@/components/issues";
|
||||
import { IssueDetailsSidebar, IssuePeekOverview } from "@/components/issues";
|
||||
// constants
|
||||
import { ISSUE_UPDATED, ISSUE_DELETED, ISSUE_ARCHIVED } from "@/constants/event-tracker";
|
||||
import { EIssuesStoreType } from "@/constants/issue";
|
||||
|
|
@ -21,7 +21,6 @@ import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/u
|
|||
import emptyIssue from "@/public/empty-state/issue.svg";
|
||||
// local components
|
||||
import { IssueMainContent } from "./main-content";
|
||||
import { IssueDetailsSidebar } from "./sidebar";
|
||||
|
||||
export type TIssueOperations = {
|
||||
fetch: (workspaceSlug: string, projectId: string, issueId: string, loader?: boolean) => Promise<void>;
|
||||
|
|
|
|||
|
|
@ -25,18 +25,20 @@ export type CalendarStoreType =
|
|||
| EIssuesStoreType.CYCLE
|
||||
| EIssuesStoreType.PROJECT_VIEW
|
||||
| EIssuesStoreType.TEAM
|
||||
| EIssuesStoreType.TEAM_VIEW;
|
||||
| EIssuesStoreType.TEAM_VIEW
|
||||
| EIssuesStoreType.EPIC;
|
||||
|
||||
interface IBaseCalendarRoot {
|
||||
QuickActions: FC<IQuickActionProps>;
|
||||
addIssuesToView?: (issueIds: string[]) => Promise<any>;
|
||||
isCompletedCycle?: boolean;
|
||||
viewId?: string | undefined;
|
||||
isEpic?: boolean;
|
||||
canEditPropertiesBasedOnProject?: (projectId: string) => boolean;
|
||||
}
|
||||
|
||||
export const BaseCalendarRoot = observer((props: IBaseCalendarRoot) => {
|
||||
const { QuickActions, addIssuesToView, isCompletedCycle = false, viewId, canEditPropertiesBasedOnProject } = props;
|
||||
const { QuickActions, addIssuesToView, isCompletedCycle = false, viewId, isEpic = false, canEditPropertiesBasedOnProject } = props;
|
||||
|
||||
// router
|
||||
const { workspaceSlug } = useParams();
|
||||
|
|
@ -173,6 +175,7 @@ export const BaseCalendarRoot = observer((props: IBaseCalendarRoot) => {
|
|||
updateFilters={updateFilters}
|
||||
handleDragAndDrop={handleDragAndDrop}
|
||||
canEditProperties={canEditProperties}
|
||||
isEpic={isEpic}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ import { renderFormattedPayloadDate } from "@/helpers/date-time.helper";
|
|||
import { useIssues } from "@/hooks/store";
|
||||
import useSize from "@/hooks/use-window-size";
|
||||
// store
|
||||
import { IProjectEpicsFilter } from "@/plane-web/store/issue/epic";
|
||||
import { ICycleIssuesFilter } from "@/store/issue/cycle";
|
||||
import { ICalendarStore } from "@/store/issue/issue_calendar_view.store";
|
||||
import { IModuleIssuesFilter } from "@/store/issue/module";
|
||||
|
|
@ -39,7 +40,12 @@ import { TRenderQuickActions } from "../list/list-view-types";
|
|||
import type { ICalendarWeek } from "./types";
|
||||
|
||||
type Props = {
|
||||
issuesFilterStore: IProjectIssuesFilter | IModuleIssuesFilter | ICycleIssuesFilter | IProjectViewIssuesFilter;
|
||||
issuesFilterStore:
|
||||
| IProjectIssuesFilter
|
||||
| IModuleIssuesFilter
|
||||
| ICycleIssuesFilter
|
||||
| IProjectViewIssuesFilter
|
||||
| IProjectEpicsFilter;
|
||||
issues: TIssueMap | undefined;
|
||||
groupedIssueIds: TGroupedIssues;
|
||||
layout: "month" | "week" | undefined;
|
||||
|
|
@ -64,6 +70,7 @@ type Props = {
|
|||
filters: IIssueFilterOptions | IIssueDisplayFilterOptions | IIssueDisplayProperties | TIssueKanbanFilters
|
||||
) => Promise<void>;
|
||||
canEditProperties: (projectId: string | undefined) => boolean;
|
||||
isEpic?: boolean;
|
||||
};
|
||||
|
||||
export const CalendarChart: React.FC<Props> = observer((props) => {
|
||||
|
|
@ -84,6 +91,7 @@ export const CalendarChart: React.FC<Props> = observer((props) => {
|
|||
updateFilters,
|
||||
canEditProperties,
|
||||
readOnly = false,
|
||||
isEpic = false,
|
||||
} = props;
|
||||
// states
|
||||
const [selectedDate, setSelectedDate] = useState<Date>(new Date());
|
||||
|
|
@ -167,6 +175,7 @@ export const CalendarChart: React.FC<Props> = observer((props) => {
|
|||
addIssuesToView={addIssuesToView}
|
||||
readOnly={readOnly}
|
||||
canEditProperties={canEditProperties}
|
||||
isEpic={isEpic}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
|
@ -190,6 +199,7 @@ export const CalendarChart: React.FC<Props> = observer((props) => {
|
|||
addIssuesToView={addIssuesToView}
|
||||
readOnly={readOnly}
|
||||
canEditProperties={canEditProperties}
|
||||
isEpic={isEpic}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
|
@ -216,6 +226,7 @@ export const CalendarChart: React.FC<Props> = observer((props) => {
|
|||
canEditProperties={canEditProperties}
|
||||
isDragDisabled
|
||||
isMobileView
|
||||
isEpic={isEpic}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -243,6 +254,7 @@ export const CalendarChart: React.FC<Props> = observer((props) => {
|
|||
canEditProperties={canEditProperties}
|
||||
isDragDisabled
|
||||
isMobileView
|
||||
isEpic={isEpic}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import { MONTHS_LIST } from "@/constants/calendar";
|
|||
import { cn } from "@/helpers/common.helper";
|
||||
import { renderFormattedPayloadDate } from "@/helpers/date-time.helper";
|
||||
// types
|
||||
import { IProjectEpicsFilter } from "@/plane-web/store/issue/epic";
|
||||
import { ICycleIssuesFilter } from "@/store/issue/cycle";
|
||||
import { IModuleIssuesFilter } from "@/store/issue/module";
|
||||
import { IProjectIssuesFilter } from "@/store/issue/project";
|
||||
|
|
@ -25,7 +26,12 @@ import { IProjectViewIssuesFilter } from "@/store/issue/project-views";
|
|||
import { TRenderQuickActions } from "../list/list-view-types";
|
||||
|
||||
type Props = {
|
||||
issuesFilterStore: IProjectIssuesFilter | IModuleIssuesFilter | ICycleIssuesFilter | IProjectViewIssuesFilter;
|
||||
issuesFilterStore:
|
||||
| IProjectIssuesFilter
|
||||
| IModuleIssuesFilter
|
||||
| ICycleIssuesFilter
|
||||
| IProjectViewIssuesFilter
|
||||
| IProjectEpicsFilter;
|
||||
date: ICalendarDate;
|
||||
issues: TIssueMap | undefined;
|
||||
groupedIssueIds: TGroupedIssues;
|
||||
|
|
@ -47,6 +53,7 @@ type Props = {
|
|||
selectedDate: Date;
|
||||
setSelectedDate: (date: Date) => void;
|
||||
canEditProperties: (projectId: string | undefined) => boolean;
|
||||
isEpic?: boolean;
|
||||
};
|
||||
|
||||
export const CalendarDayTile: React.FC<Props> = observer((props) => {
|
||||
|
|
@ -68,6 +75,7 @@ export const CalendarDayTile: React.FC<Props> = observer((props) => {
|
|||
handleDragAndDrop,
|
||||
setSelectedDate,
|
||||
canEditProperties,
|
||||
isEpic = false,
|
||||
} = props;
|
||||
|
||||
const [isDraggingOver, setIsDraggingOver] = useState(false);
|
||||
|
|
@ -185,6 +193,7 @@ export const CalendarDayTile: React.FC<Props> = observer((props) => {
|
|||
quickAddCallback={quickAddCallback}
|
||||
readOnly={readOnly}
|
||||
canEditProperties={canEditProperties}
|
||||
isEpic={isEpic}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import { Popover, Transition } from "@headlessui/react";
|
|||
import { MONTHS_LIST } from "@/constants/calendar";
|
||||
import { getDate } from "@/helpers/date-time.helper";
|
||||
import { useCalendarView } from "@/hooks/store";
|
||||
import { IProjectEpicsFilter } from "@/plane-web/store/issue/epic";
|
||||
import { ICycleIssuesFilter } from "@/store/issue/cycle";
|
||||
import { IModuleIssuesFilter } from "@/store/issue/module";
|
||||
import { IProjectIssuesFilter } from "@/store/issue/project";
|
||||
|
|
@ -16,7 +17,12 @@ import { IProjectViewIssuesFilter } from "@/store/issue/project-views";
|
|||
// helpers
|
||||
|
||||
interface Props {
|
||||
issuesFilterStore: IProjectIssuesFilter | IModuleIssuesFilter | ICycleIssuesFilter | IProjectViewIssuesFilter;
|
||||
issuesFilterStore:
|
||||
| IProjectIssuesFilter
|
||||
| IModuleIssuesFilter
|
||||
| ICycleIssuesFilter
|
||||
| IProjectViewIssuesFilter
|
||||
| IProjectEpicsFilter;
|
||||
}
|
||||
export const CalendarMonthsDropdown: React.FC<Props> = observer((props: Props) => {
|
||||
const { issuesFilterStore } = props;
|
||||
|
|
|
|||
|
|
@ -23,13 +23,19 @@ import { CALENDAR_LAYOUTS } from "@/constants/calendar";
|
|||
import { EIssueFilterType } from "@/constants/issue";
|
||||
import { useCalendarView } from "@/hooks/store";
|
||||
import useSize from "@/hooks/use-window-size";
|
||||
import { IProjectEpicsFilter } from "@/plane-web/store/issue/epic";
|
||||
import { ICycleIssuesFilter } from "@/store/issue/cycle";
|
||||
import { IModuleIssuesFilter } from "@/store/issue/module";
|
||||
import { IProjectIssuesFilter } from "@/store/issue/project";
|
||||
import { IProjectViewIssuesFilter } from "@/store/issue/project-views";
|
||||
|
||||
interface ICalendarHeader {
|
||||
issuesFilterStore: IProjectIssuesFilter | IModuleIssuesFilter | ICycleIssuesFilter | IProjectViewIssuesFilter;
|
||||
issuesFilterStore:
|
||||
| IProjectIssuesFilter
|
||||
| IModuleIssuesFilter
|
||||
| ICycleIssuesFilter
|
||||
| IProjectViewIssuesFilter
|
||||
| IProjectEpicsFilter;
|
||||
updateFilters?: (
|
||||
projectId: string,
|
||||
filterType: EIssueFilterType,
|
||||
|
|
|
|||
|
|
@ -13,13 +13,19 @@ import { CalendarMonthsDropdown, CalendarOptionsDropdown } from "@/components/is
|
|||
// icons
|
||||
import { EIssueFilterType } from "@/constants/issue";
|
||||
import { useCalendarView } from "@/hooks/store/use-calendar-view";
|
||||
import { IProjectEpicsFilter } from "@/plane-web/store/issue/epic";
|
||||
import { ICycleIssuesFilter } from "@/store/issue/cycle";
|
||||
import { IModuleIssuesFilter } from "@/store/issue/module";
|
||||
import { IProjectIssuesFilter } from "@/store/issue/project";
|
||||
import { IProjectViewIssuesFilter } from "@/store/issue/project-views";
|
||||
|
||||
interface ICalendarHeader {
|
||||
issuesFilterStore: IProjectIssuesFilter | IModuleIssuesFilter | ICycleIssuesFilter | IProjectViewIssuesFilter;
|
||||
issuesFilterStore:
|
||||
| IProjectIssuesFilter
|
||||
| IModuleIssuesFilter
|
||||
| ICycleIssuesFilter
|
||||
| IProjectViewIssuesFilter
|
||||
| IProjectEpicsFilter;
|
||||
updateFilters?: (
|
||||
projectId: string,
|
||||
filterType: EIssueFilterType,
|
||||
|
|
|
|||
|
|
@ -15,11 +15,12 @@ type Props = {
|
|||
issueId: string;
|
||||
quickActions: TRenderQuickActions;
|
||||
isDragDisabled: boolean;
|
||||
isEpic?: boolean;
|
||||
canEditProperties: (projectId: string | undefined) => boolean;
|
||||
};
|
||||
|
||||
export const CalendarIssueBlockRoot: React.FC<Props> = observer((props) => {
|
||||
const { issueId, quickActions, isDragDisabled, canEditProperties } = props;
|
||||
const { issueId, quickActions, isDragDisabled, isEpic = false, canEditProperties } = props;
|
||||
|
||||
const issueRef = useRef<HTMLAnchorElement | null>(null);
|
||||
const [isDragging, setIsDragging] = useState(false);
|
||||
|
|
@ -58,5 +59,13 @@ export const CalendarIssueBlockRoot: React.FC<Props> = observer((props) => {
|
|||
|
||||
if (!issue) return null;
|
||||
|
||||
return <CalendarIssueBlock isDragging={isDragging} issue={issue} quickActions={quickActions} ref={issueRef} />;
|
||||
return (
|
||||
<CalendarIssueBlock
|
||||
isDragging={isDragging}
|
||||
issue={issue}
|
||||
quickActions={quickActions}
|
||||
ref={issueRef}
|
||||
isEpic={isEpic}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -28,11 +28,12 @@ type Props = {
|
|||
issue: TIssue;
|
||||
quickActions: TRenderQuickActions;
|
||||
isDragging?: boolean;
|
||||
isEpic?: boolean;
|
||||
};
|
||||
|
||||
export const CalendarIssueBlock = observer(
|
||||
forwardRef<HTMLAnchorElement, Props>((props, ref) => {
|
||||
const { issue, quickActions, isDragging = false } = props;
|
||||
const { issue, quickActions, isDragging = false, isEpic = false } = props;
|
||||
// states
|
||||
const [isMenuActive, setIsMenuActive] = useState(false);
|
||||
// refs
|
||||
|
|
@ -42,7 +43,7 @@ export const CalendarIssueBlock = observer(
|
|||
const { workspaceSlug, projectId } = useParams();
|
||||
const { getProjectStates } = useProjectState();
|
||||
const { getIsIssuePeeked } = useIssueDetail();
|
||||
const { handleRedirection } = useIssuePeekOverviewRedirection();
|
||||
const { handleRedirection } = useIssuePeekOverviewRedirection(isEpic);
|
||||
const { isMobile } = usePlatformOS();
|
||||
const storeType = useIssueStoreType() as CalendarStoreType;
|
||||
const { issuesFilter } = useIssues(storeType);
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ type Props = {
|
|||
readOnly?: boolean;
|
||||
isMobileView?: boolean;
|
||||
canEditProperties: (projectId: string | undefined) => boolean;
|
||||
isEpic?: boolean;
|
||||
};
|
||||
|
||||
export const CalendarIssueBlocks: React.FC<Props> = observer((props) => {
|
||||
|
|
@ -39,6 +40,7 @@ export const CalendarIssueBlocks: React.FC<Props> = observer((props) => {
|
|||
readOnly,
|
||||
isMobileView = false,
|
||||
canEditProperties,
|
||||
isEpic = false,
|
||||
} = props;
|
||||
const formattedDatePayload = renderFormattedPayloadDate(date);
|
||||
|
||||
|
|
@ -66,6 +68,7 @@ export const CalendarIssueBlocks: React.FC<Props> = observer((props) => {
|
|||
quickActions={quickActions}
|
||||
isDragDisabled={isDragDisabled || isMobileView}
|
||||
canEditProperties={canEditProperties}
|
||||
isEpic={isEpic}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import { CalendarDayTile } from "@/components/issues";
|
|||
// helpers
|
||||
import { renderFormattedPayloadDate } from "@/helpers/date-time.helper";
|
||||
// types
|
||||
import { IProjectEpicsFilter } from "@/plane-web/store/issue/epic";
|
||||
import { ICycleIssuesFilter } from "@/store/issue/cycle";
|
||||
import { IModuleIssuesFilter } from "@/store/issue/module";
|
||||
import { IProjectIssuesFilter } from "@/store/issue/project";
|
||||
|
|
@ -13,7 +14,12 @@ import { TRenderQuickActions } from "../list/list-view-types";
|
|||
import { ICalendarDate, ICalendarWeek } from "./types";
|
||||
|
||||
type Props = {
|
||||
issuesFilterStore: IProjectIssuesFilter | IModuleIssuesFilter | ICycleIssuesFilter | IProjectViewIssuesFilter;
|
||||
issuesFilterStore:
|
||||
| IProjectIssuesFilter
|
||||
| IModuleIssuesFilter
|
||||
| ICycleIssuesFilter
|
||||
| IProjectViewIssuesFilter
|
||||
| IProjectEpicsFilter;
|
||||
issues: TIssueMap | undefined;
|
||||
groupedIssueIds: TGroupedIssues;
|
||||
week: ICalendarWeek | undefined;
|
||||
|
|
@ -35,6 +41,7 @@ type Props = {
|
|||
selectedDate: Date;
|
||||
setSelectedDate: (date: Date) => void;
|
||||
canEditProperties: (projectId: string | undefined) => boolean;
|
||||
isEpic?: boolean;
|
||||
};
|
||||
|
||||
export const CalendarWeekDays: React.FC<Props> = observer((props) => {
|
||||
|
|
@ -56,6 +63,7 @@ export const CalendarWeekDays: React.FC<Props> = observer((props) => {
|
|||
selectedDate,
|
||||
setSelectedDate,
|
||||
canEditProperties,
|
||||
isEpic = false,
|
||||
} = props;
|
||||
|
||||
const calendarLayout = issuesFilterStore?.issueFilters?.displayFilters?.calendar?.layout ?? "month";
|
||||
|
|
@ -92,6 +100,7 @@ export const CalendarWeekDays: React.FC<Props> = observer((props) => {
|
|||
readOnly={readOnly}
|
||||
handleDragAndDrop={handleDragAndDrop}
|
||||
canEditProperties={canEditProperties}
|
||||
isEpic={isEpic}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import { ProjectDraftEmptyState } from "./draft-issues";
|
|||
import { GlobalViewEmptyState } from "./global-view";
|
||||
import { ModuleEmptyState } from "./module";
|
||||
import { ProfileViewEmptyState } from "./profile-view";
|
||||
import { ProjectEpicsEmptyState } from "./project-epic";
|
||||
import { ProjectEmptyState } from "./project-issues";
|
||||
import { ProjectViewEmptyState } from "./project-view";
|
||||
|
||||
|
|
@ -31,6 +32,8 @@ export const IssueLayoutEmptyState = (props: Props) => {
|
|||
return <GlobalViewEmptyState />;
|
||||
case EIssuesStoreType.PROFILE:
|
||||
return <ProfileViewEmptyState />;
|
||||
case EIssuesStoreType.EPIC:
|
||||
return <ProjectEpicsEmptyState />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
// types
|
||||
// components
|
||||
import { EmptyState } from "@/components/empty-state";
|
||||
// constants
|
||||
import { EmptyStateType } from "@/constants/empty-state";
|
||||
// hooks
|
||||
|
||||
export const ProjectEpicsEmptyState: React.FC = () => (
|
||||
<div className="relative h-full w-full overflow-y-auto">
|
||||
<EmptyState type={EmptyStateType.PROJECT_NO_EPICS} primaryButtonOnClick={() => {}} />
|
||||
</div>
|
||||
);
|
||||
|
|
@ -1,29 +1,32 @@
|
|||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
// types
|
||||
import { IIssueFilterOptions } from "@plane/types";
|
||||
// hooks
|
||||
// components
|
||||
// ui
|
||||
import { Header, EHeaderVariant } from "@plane/ui";
|
||||
// components
|
||||
import { AppliedFiltersList, SaveFilterView } from "@/components/issues";
|
||||
// constants
|
||||
import { EIssueFilterType, EIssuesStoreType } from "@/constants/issue";
|
||||
// hooks
|
||||
import { useLabel, useProjectState, useUserPermissions } from "@/hooks/store";
|
||||
import { useIssues } from "@/hooks/store/use-issues";
|
||||
// plane web constants
|
||||
import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions";
|
||||
|
||||
// types
|
||||
type TProjectAppliedFiltersRootProps = {
|
||||
storeType?: EIssuesStoreType.PROJECT | EIssuesStoreType.EPIC;
|
||||
};
|
||||
|
||||
export const ProjectAppliedFiltersRoot: React.FC = observer(() => {
|
||||
export const ProjectAppliedFiltersRoot: React.FC<TProjectAppliedFiltersRootProps> = observer((props) => {
|
||||
const { storeType = EIssuesStoreType.PROJECT } = props;
|
||||
// router
|
||||
const { workspaceSlug, projectId } = useParams() as {
|
||||
workspaceSlug: string;
|
||||
projectId: string;
|
||||
};
|
||||
const { workspaceSlug, projectId } = useParams();
|
||||
// store hooks
|
||||
const { projectLabels } = useLabel();
|
||||
const {
|
||||
issuesFilter: { issueFilters, updateFilters },
|
||||
} = useIssues(EIssuesStoreType.PROJECT);
|
||||
} = useIssues(storeType);
|
||||
const { allowPermissions } = useUserPermissions();
|
||||
|
||||
const { projectStates } = useProjectState();
|
||||
|
|
@ -84,8 +87,8 @@ export const ProjectAppliedFiltersRoot: React.FC = observer(() => {
|
|||
<Header.RightItem>
|
||||
{isEditingAllowed && (
|
||||
<SaveFilterView
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
workspaceSlug={workspaceSlug?.toString()}
|
||||
projectId={projectId?.toString()}
|
||||
filterParams={{
|
||||
filters: appliedFilters,
|
||||
display_filters: issueFilters?.displayFilters,
|
||||
|
|
|
|||
|
|
@ -27,16 +27,18 @@ import { IssueLayoutHOC } from "../issue-layout-HOC";
|
|||
interface IBaseGanttRoot {
|
||||
viewId?: string | undefined;
|
||||
isCompletedCycle?: boolean;
|
||||
isEpic?: boolean;
|
||||
}
|
||||
|
||||
export type GanttStoreType =
|
||||
| EIssuesStoreType.PROJECT
|
||||
| EIssuesStoreType.MODULE
|
||||
| EIssuesStoreType.CYCLE
|
||||
| EIssuesStoreType.PROJECT_VIEW;
|
||||
| EIssuesStoreType.PROJECT_VIEW
|
||||
| EIssuesStoreType.EPIC;
|
||||
|
||||
export const BaseGanttRoot: React.FC<IBaseGanttRoot> = observer((props: IBaseGanttRoot) => {
|
||||
const { viewId, isCompletedCycle = false } = props;
|
||||
const { viewId, isCompletedCycle = false, isEpic = false } = props;
|
||||
// router
|
||||
const { workspaceSlug, projectId } = useParams();
|
||||
|
||||
|
|
@ -123,8 +125,8 @@ export const BaseGanttRoot: React.FC<IBaseGanttRoot> = observer((props: IBaseGan
|
|||
loaderTitle="Issues"
|
||||
blockIds={issuesIds}
|
||||
blockUpdateHandler={updateIssueBlockStructure}
|
||||
blockToRender={(data: TIssue) => <IssueGanttBlock issueId={data.id} />}
|
||||
sidebarToRender={(props) => <IssueGanttSidebar {...props} showAllBlocks />}
|
||||
blockToRender={(data: TIssue) => <IssueGanttBlock issueId={data.id} isEpic={isEpic} />}
|
||||
sidebarToRender={(props) => <IssueGanttSidebar {...props} showAllBlocks isEpic={isEpic} />}
|
||||
enableBlockLeftResize={isAllowed}
|
||||
enableBlockRightResize={isAllowed}
|
||||
enableBlockMove={isAllowed}
|
||||
|
|
@ -136,6 +138,7 @@ export const BaseGanttRoot: React.FC<IBaseGanttRoot> = observer((props: IBaseGan
|
|||
canLoadMoreBlocks={nextPageResults}
|
||||
updateBlockDates={updateBlockDates}
|
||||
showAllBlocks
|
||||
isEpic={isEpic}
|
||||
/>
|
||||
</div>
|
||||
</TimeLineTypeContext.Provider>
|
||||
|
|
|
|||
|
|
@ -21,10 +21,11 @@ import { GanttStoreType } from "./base-gantt-root";
|
|||
|
||||
type Props = {
|
||||
issueId: string;
|
||||
isEpic?: boolean;
|
||||
};
|
||||
|
||||
export const IssueGanttBlock: React.FC<Props> = observer((props) => {
|
||||
const { issueId } = props;
|
||||
const { issueId, isEpic } = props;
|
||||
// router
|
||||
const { workspaceSlug: routerWorkspaceSlug } = useParams();
|
||||
const workspaceSlug = routerWorkspaceSlug?.toString();
|
||||
|
|
@ -35,7 +36,7 @@ export const IssueGanttBlock: React.FC<Props> = observer((props) => {
|
|||
} = useIssueDetail();
|
||||
// hooks
|
||||
const { isMobile } = usePlatformOS();
|
||||
const { handleRedirection } = useIssuePeekOverviewRedirection();
|
||||
const { handleRedirection } = useIssuePeekOverviewRedirection(isEpic);
|
||||
|
||||
// derived values
|
||||
const issueDetails = getIssueById(issueId);
|
||||
|
|
@ -78,7 +79,7 @@ export const IssueGanttBlock: React.FC<Props> = observer((props) => {
|
|||
|
||||
// rendering issues on gantt sidebar
|
||||
export const IssueGanttSidebarBlock: React.FC<Props> = observer((props) => {
|
||||
const { issueId } = props;
|
||||
const { issueId, isEpic = false } = props;
|
||||
// router
|
||||
const { workspaceSlug: routerWorkspaceSlug } = useParams();
|
||||
const workspaceSlug = routerWorkspaceSlug?.toString();
|
||||
|
|
@ -91,7 +92,7 @@ export const IssueGanttSidebarBlock: React.FC<Props> = observer((props) => {
|
|||
const { issuesFilter } = useIssues(storeType);
|
||||
|
||||
// handlers
|
||||
const { handleRedirection } = useIssuePeekOverviewRedirection();
|
||||
const { handleRedirection } = useIssuePeekOverviewRedirection(isEpic);
|
||||
|
||||
// derived values
|
||||
const issueDetails = getIssueById(issueId);
|
||||
|
|
@ -105,7 +106,7 @@ export const IssueGanttSidebarBlock: React.FC<Props> = observer((props) => {
|
|||
return (
|
||||
<ControlLink
|
||||
id={`issue-${issueId}`}
|
||||
href={`/${workspaceSlug}/projects/${issueDetails?.project_id}/issues/${issueDetails?.id}`}
|
||||
href={`/${workspaceSlug}/projects/${issueDetails?.project_id}/${isEpic ? "epics" : "issues"}/${issueDetails?.id}`}
|
||||
onClick={handleIssuePeekOverview}
|
||||
className="line-clamp-1 w-full cursor-pointer text-sm text-custom-text-100"
|
||||
disabled={!!issueDetails?.tempId}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import { dropTargetForElements } from "@atlaskit/pragmatic-drag-and-drop/element
|
|||
import { autoScrollForElements } from "@atlaskit/pragmatic-drag-and-drop-auto-scroll/element";
|
||||
import { observer } from "mobx-react";
|
||||
import { useParams, usePathname } from "next/navigation";
|
||||
import { EIssueServiceType } from "@plane/constants";
|
||||
import { DeleteIssueModal } from "@/components/issues";
|
||||
//constants
|
||||
import { ISSUE_DELETED } from "@/constants/event-tracker";
|
||||
|
|
@ -34,7 +35,8 @@ export type KanbanStoreType =
|
|||
| EIssuesStoreType.DRAFT
|
||||
| EIssuesStoreType.PROFILE
|
||||
| EIssuesStoreType.TEAM
|
||||
| EIssuesStoreType.TEAM_VIEW;
|
||||
| EIssuesStoreType.TEAM_VIEW
|
||||
| EIssuesStoreType.EPIC;
|
||||
|
||||
export interface IBaseKanBanLayout {
|
||||
QuickActions: FC<IQuickActionProps>;
|
||||
|
|
@ -42,10 +44,18 @@ export interface IBaseKanBanLayout {
|
|||
canEditPropertiesBasedOnProject?: (projectId: string) => boolean;
|
||||
isCompletedCycle?: boolean;
|
||||
viewId?: string | undefined;
|
||||
isEpic?: boolean;
|
||||
}
|
||||
|
||||
export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBaseKanBanLayout) => {
|
||||
const { QuickActions, addIssuesToView, canEditPropertiesBasedOnProject, isCompletedCycle = false, viewId } = props;
|
||||
const {
|
||||
QuickActions,
|
||||
addIssuesToView,
|
||||
canEditPropertiesBasedOnProject,
|
||||
isCompletedCycle = false,
|
||||
viewId,
|
||||
isEpic = false,
|
||||
} = props;
|
||||
// router
|
||||
const { workspaceSlug, projectId } = useParams();
|
||||
const pathname = usePathname();
|
||||
|
|
@ -56,7 +66,7 @@ export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBas
|
|||
const { issueMap, issuesFilter, issues } = useIssues(storeType);
|
||||
const {
|
||||
issue: { getIssueById },
|
||||
} = useIssueDetail();
|
||||
} = useIssueDetail(isEpic ? EIssueServiceType.EPICS : EIssueServiceType.ISSUES);
|
||||
const {
|
||||
fetchIssues,
|
||||
fetchNextIssues,
|
||||
|
|
@ -275,6 +285,7 @@ export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBas
|
|||
scrollableContainerRef={scrollableContainerRef}
|
||||
handleOnDrop={handleOnDrop}
|
||||
loadMoreIssues={fetchMoreIssues}
|
||||
isEpic={isEpic}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import { IssueIdentifier } from "@/plane-web/components/issues";
|
|||
import { TRenderQuickActions } from "../list/list-view-types";
|
||||
import { IssueProperties } from "../properties/all-properties";
|
||||
import { getIssueBlockId } from "../utils";
|
||||
import { EIssueServiceType } from "@plane/constants";
|
||||
|
||||
interface IssueBlockProps {
|
||||
issueId: string;
|
||||
|
|
@ -41,6 +42,7 @@ interface IssueBlockProps {
|
|||
canEditProperties: (projectId: string | undefined) => boolean;
|
||||
scrollableContainerRef?: MutableRefObject<HTMLDivElement | null>;
|
||||
shouldRenderByDefault?: boolean;
|
||||
isEpic?: boolean;
|
||||
}
|
||||
|
||||
interface IssueDetailsBlockProps {
|
||||
|
|
@ -50,10 +52,11 @@ interface IssueDetailsBlockProps {
|
|||
updateIssue: ((projectId: string | null, issueId: string, data: Partial<TIssue>) => Promise<void>) | undefined;
|
||||
quickActions: TRenderQuickActions;
|
||||
isReadOnly: boolean;
|
||||
isEpic?: boolean;
|
||||
}
|
||||
|
||||
const KanbanIssueDetailsBlock: React.FC<IssueDetailsBlockProps> = observer((props) => {
|
||||
const { cardRef, issue, updateIssue, quickActions, isReadOnly, displayProperties } = props;
|
||||
const { cardRef, issue, updateIssue, quickActions, isReadOnly, displayProperties, isEpic = false } = props;
|
||||
// hooks
|
||||
const { isMobile } = usePlatformOS();
|
||||
|
||||
|
|
@ -99,6 +102,7 @@ const KanbanIssueDetailsBlock: React.FC<IssueDetailsBlockProps> = observer((prop
|
|||
activeLayout="Kanban"
|
||||
updateIssue={updateIssue}
|
||||
isReadOnly={isReadOnly}
|
||||
isEpic={isEpic}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
|
@ -118,6 +122,7 @@ export const KanbanIssueBlock: React.FC<IssueBlockProps> = observer((props) => {
|
|||
canEditProperties,
|
||||
scrollableContainerRef,
|
||||
shouldRenderByDefault,
|
||||
isEpic = false,
|
||||
} = props;
|
||||
|
||||
const cardRef = useRef<HTMLAnchorElement | null>(null);
|
||||
|
|
@ -125,8 +130,8 @@ export const KanbanIssueBlock: React.FC<IssueBlockProps> = observer((props) => {
|
|||
const { workspaceSlug: routerWorkspaceSlug } = useParams();
|
||||
const workspaceSlug = routerWorkspaceSlug?.toString();
|
||||
// hooks
|
||||
const { getIsIssuePeeked } = useIssueDetail();
|
||||
const { handleRedirection } = useIssuePeekOverviewRedirection();
|
||||
const { getIsIssuePeeked } = useIssueDetail(isEpic ? EIssueServiceType.EPICS : EIssueServiceType.ISSUES);
|
||||
const { handleRedirection } = useIssuePeekOverviewRedirection(isEpic);
|
||||
const { isMobile } = usePlatformOS();
|
||||
|
||||
// handlers
|
||||
|
|
@ -210,7 +215,7 @@ export const KanbanIssueBlock: React.FC<IssueBlockProps> = observer((props) => {
|
|||
>
|
||||
<ControlLink
|
||||
id={getIssueBlockId(issueId, groupId, subGroupId)}
|
||||
href={`/${workspaceSlug}/projects/${issue.project_id}/${issue.archived_at ? "archives/" : ""}issues/${
|
||||
href={`/${workspaceSlug}/projects/${issue.project_id}/${issue.archived_at ? "archives/" : ""}${isEpic ? "epics" : "issues"}/${
|
||||
issue.id
|
||||
}`}
|
||||
ref={cardRef}
|
||||
|
|
@ -238,6 +243,7 @@ export const KanbanIssueBlock: React.FC<IssueBlockProps> = observer((props) => {
|
|||
updateIssue={updateIssue}
|
||||
quickActions={quickActions}
|
||||
isReadOnly={!canEditIssueProperties}
|
||||
isEpic={isEpic}
|
||||
/>
|
||||
</RenderIfVisible>
|
||||
</ControlLink>
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ interface IssueBlocksListProps {
|
|||
canDropOverIssue: boolean;
|
||||
canDragIssuesInCurrentGrouping: boolean;
|
||||
scrollableContainerRef?: MutableRefObject<HTMLDivElement | null>;
|
||||
isEpic?: boolean;
|
||||
}
|
||||
|
||||
export const KanbanIssueBlocksList: React.FC<IssueBlocksListProps> = observer((props) => {
|
||||
|
|
@ -33,6 +34,7 @@ export const KanbanIssueBlocksList: React.FC<IssueBlocksListProps> = observer((p
|
|||
quickActions,
|
||||
canEditProperties,
|
||||
scrollableContainerRef,
|
||||
isEpic = false,
|
||||
} = props;
|
||||
|
||||
return (
|
||||
|
|
@ -62,6 +64,7 @@ export const KanbanIssueBlocksList: React.FC<IssueBlocksListProps> = observer((p
|
|||
canDragIssuesInCurrentGrouping={canDragIssuesInCurrentGrouping}
|
||||
canEditProperties={canEditProperties}
|
||||
scrollableContainerRef={scrollableContainerRef}
|
||||
isEpic={isEpic}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@ export interface IKanBan {
|
|||
handleOnDrop: (source: GroupDropLocation, destination: GroupDropLocation) => Promise<void>;
|
||||
showEmptyGroup?: boolean;
|
||||
subGroupIndex?: number;
|
||||
isEpic?: boolean;
|
||||
}
|
||||
|
||||
export const KanBan: React.FC<IKanBan> = observer((props) => {
|
||||
|
|
@ -86,6 +87,7 @@ export const KanBan: React.FC<IKanBan> = observer((props) => {
|
|||
isDropDisabled,
|
||||
dropErrorMessage,
|
||||
subGroupIndex = 0,
|
||||
isEpic = false,
|
||||
} = props;
|
||||
// store hooks
|
||||
const storeType = useIssueStoreType();
|
||||
|
|
@ -164,6 +166,7 @@ export const KanBan: React.FC<IKanBan> = observer((props) => {
|
|||
addIssuesToView={addIssuesToView}
|
||||
collapsedGroups={collapsedGroups}
|
||||
handleCollapsedGroups={handleCollapsedGroups}
|
||||
isEpic={isEpic}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -207,6 +210,7 @@ export const KanBan: React.FC<IKanBan> = observer((props) => {
|
|||
scrollableContainerRef={scrollableContainerRef}
|
||||
loadMoreIssues={loadMoreIssues}
|
||||
handleOnDrop={handleOnDrop}
|
||||
isEpic={isEpic}
|
||||
/>
|
||||
</RenderIfVisible>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@ import { CreateUpdateIssueModal } from "@/components/issues";
|
|||
// hooks
|
||||
import { useEventTracker } from "@/hooks/store";
|
||||
import { useIssueStoreType } from "@/hooks/use-issue-layout-store";
|
||||
import { CreateUpdateEpicModal } from "@/plane-web/components/epics/epic-modal";
|
||||
// types
|
||||
// Plane-web
|
||||
import { WorkFlowGroupTree } from "@/plane-web/components/workflow";
|
||||
|
||||
|
|
@ -30,6 +32,7 @@ interface IHeaderGroupByCard {
|
|||
issuePayload: Partial<TIssue>;
|
||||
disableIssueCreation?: boolean;
|
||||
addIssuesToView?: (issueIds: string[]) => Promise<TIssue>;
|
||||
isEpic?: boolean;
|
||||
}
|
||||
|
||||
export const HeaderGroupByCard: FC<IHeaderGroupByCard> = observer((props) => {
|
||||
|
|
@ -45,6 +48,7 @@ export const HeaderGroupByCard: FC<IHeaderGroupByCard> = observer((props) => {
|
|||
issuePayload,
|
||||
disableIssueCreation,
|
||||
addIssuesToView,
|
||||
isEpic = false,
|
||||
} = props;
|
||||
const verticalAlignPosition = sub_group_by ? false : collapsedGroups?.group_by.includes(column_id);
|
||||
// states
|
||||
|
|
@ -86,6 +90,9 @@ export const HeaderGroupByCard: FC<IHeaderGroupByCard> = observer((props) => {
|
|||
|
||||
return (
|
||||
<>
|
||||
{isEpic ? (
|
||||
<CreateUpdateEpicModal isOpen={isOpen} onClose={() => setIsOpen(false)} data={issuePayload} />
|
||||
) : (
|
||||
<CreateUpdateIssueModal
|
||||
isOpen={isOpen}
|
||||
onClose={() => setIsOpen(false)}
|
||||
|
|
@ -93,6 +100,7 @@ export const HeaderGroupByCard: FC<IHeaderGroupByCard> = observer((props) => {
|
|||
storeType={storeType}
|
||||
isDraft={isDraftIssue}
|
||||
/>
|
||||
)}
|
||||
|
||||
{renderExistingIssueModal && (
|
||||
<ExistingIssuesListModal
|
||||
|
|
|
|||
|
|
@ -56,6 +56,7 @@ interface IKanbanGroup {
|
|||
scrollableContainerRef?: MutableRefObject<HTMLDivElement | null>;
|
||||
handleOnDrop: (source: GroupDropLocation, destination: GroupDropLocation) => Promise<void>;
|
||||
orderBy: TIssueOrderByOptions | undefined;
|
||||
isEpic?: boolean;
|
||||
}
|
||||
|
||||
export const KanbanGroup = observer((props: IKanbanGroup) => {
|
||||
|
|
@ -79,6 +80,7 @@ export const KanbanGroup = observer((props: IKanbanGroup) => {
|
|||
quickAddCallback,
|
||||
scrollableContainerRef,
|
||||
handleOnDrop,
|
||||
isEpic =false
|
||||
} = props;
|
||||
// hooks
|
||||
const projectState = useProjectState();
|
||||
|
|
@ -294,6 +296,7 @@ export const KanbanGroup = observer((props: IKanbanGroup) => {
|
|||
scrollableContainerRef={scrollableContainerRef}
|
||||
canDropOverIssue={!canOverlayBeVisible}
|
||||
canDragIssuesInCurrentGrouping={canDragIssuesInCurrentGrouping}
|
||||
isEpic={isEpic}
|
||||
/>
|
||||
|
||||
{shouldLoadMore && (isSubGroup ? <>{loadMore}</> : <KanbanIssueBlockLoader ref={setIntersectionElement} />)}
|
||||
|
|
|
|||
|
|
@ -28,7 +28,8 @@ type ListStoreType =
|
|||
| EIssuesStoreType.ARCHIVED
|
||||
| EIssuesStoreType.WORKSPACE_DRAFT
|
||||
| EIssuesStoreType.TEAM
|
||||
| EIssuesStoreType.TEAM_VIEW;
|
||||
| EIssuesStoreType.TEAM_VIEW
|
||||
| EIssuesStoreType.EPIC;
|
||||
|
||||
interface IBaseListRoot {
|
||||
QuickActions: FC<IQuickActionProps>;
|
||||
|
|
@ -36,9 +37,17 @@ interface IBaseListRoot {
|
|||
canEditPropertiesBasedOnProject?: (projectId: string) => boolean;
|
||||
viewId?: string | undefined;
|
||||
isCompletedCycle?: boolean;
|
||||
isEpic?: boolean;
|
||||
}
|
||||
export const BaseListRoot = observer((props: IBaseListRoot) => {
|
||||
const { QuickActions, viewId, addIssuesToView, canEditPropertiesBasedOnProject, isCompletedCycle = false } = props;
|
||||
const {
|
||||
QuickActions,
|
||||
viewId,
|
||||
addIssuesToView,
|
||||
canEditPropertiesBasedOnProject,
|
||||
isCompletedCycle = false,
|
||||
isEpic = false,
|
||||
} = props;
|
||||
// router
|
||||
const storeType = useIssueStoreType() as ListStoreType;
|
||||
//stores
|
||||
|
|
@ -157,6 +166,7 @@ export const BaseListRoot = observer((props: IBaseListRoot) => {
|
|||
handleOnDrop={handleOnDrop}
|
||||
handleCollapsedGroups={handleCollapsedGroups}
|
||||
collapsedGroups={collapsedGroups}
|
||||
isEpic={isEpic}
|
||||
/>
|
||||
</div>
|
||||
</IssueLayoutHOC>
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import { combine } from "@atlaskit/pragmatic-drag-and-drop/combine";
|
|||
import { dropTargetForElements } from "@atlaskit/pragmatic-drag-and-drop/element/adapter";
|
||||
import { attachInstruction, extractInstruction } from "@atlaskit/pragmatic-drag-and-drop-hitbox/tree-item";
|
||||
import { observer } from "mobx-react";
|
||||
import { EIssueServiceType } from "@plane/constants";
|
||||
// plane helpers
|
||||
import { useOutsideClickDetector } from "@plane/hooks";
|
||||
// types
|
||||
|
|
@ -39,6 +40,7 @@ type Props = {
|
|||
isParentIssueBeingDragged?: boolean;
|
||||
isLastChild?: boolean;
|
||||
shouldRenderByDefault?: boolean;
|
||||
isEpic?: boolean;
|
||||
};
|
||||
|
||||
export const IssueBlockRoot: FC<Props> = observer((props) => {
|
||||
|
|
@ -59,6 +61,7 @@ export const IssueBlockRoot: FC<Props> = observer((props) => {
|
|||
isLastChild = false,
|
||||
selectionHelpers,
|
||||
shouldRenderByDefault,
|
||||
isEpic = false,
|
||||
} = props;
|
||||
// states
|
||||
const [isExpanded, setExpanded] = useState<boolean>(false);
|
||||
|
|
@ -69,7 +72,7 @@ export const IssueBlockRoot: FC<Props> = observer((props) => {
|
|||
// hooks
|
||||
const { isMobile } = usePlatformOS();
|
||||
// store hooks
|
||||
const { subIssues: subIssuesStore } = useIssueDetail();
|
||||
const { subIssues: subIssuesStore } = useIssueDetail(isEpic ? EIssueServiceType.EPICS : EIssueServiceType.ISSUES);
|
||||
|
||||
const isSubIssue = nestingLevel !== 0;
|
||||
|
||||
|
|
@ -150,10 +153,12 @@ export const IssueBlockRoot: FC<Props> = observer((props) => {
|
|||
canDrag={!isSubIssue && isDragAllowed}
|
||||
isCurrentBlockDragging={isParentIssueBeingDragged || isCurrentBlockDragging}
|
||||
setIsCurrentBlockDragging={setIsCurrentBlockDragging}
|
||||
isEpic={isEpic}
|
||||
/>
|
||||
</RenderIfVisible>
|
||||
|
||||
{isExpanded &&
|
||||
!isEpic &&
|
||||
subIssues?.map((subIssueId) => (
|
||||
<IssueBlockRoot
|
||||
key={`${subIssueId}`}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import { draggable } from "@atlaskit/pragmatic-drag-and-drop/element/adapter";
|
|||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
import { ChevronRight } from "lucide-react";
|
||||
import { EIssueServiceType } from "@plane/constants";
|
||||
// types
|
||||
import { TIssue, IIssueDisplayProperties, TIssueMap } from "@plane/types";
|
||||
// ui
|
||||
|
|
@ -40,6 +41,7 @@ interface IssueBlockProps {
|
|||
isCurrentBlockDragging: boolean;
|
||||
setIsCurrentBlockDragging: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
canDrag: boolean;
|
||||
isEpic?: boolean;
|
||||
}
|
||||
|
||||
export const IssueBlock = observer((props: IssueBlockProps) => {
|
||||
|
|
@ -59,6 +61,7 @@ export const IssueBlock = observer((props: IssueBlockProps) => {
|
|||
isCurrentBlockDragging,
|
||||
setIsCurrentBlockDragging,
|
||||
canDrag,
|
||||
isEpic = false,
|
||||
} = props;
|
||||
// ref
|
||||
const issueRef = useRef<HTMLDivElement | null>(null);
|
||||
|
|
@ -69,7 +72,12 @@ export const IssueBlock = observer((props: IssueBlockProps) => {
|
|||
// hooks
|
||||
const { sidebarCollapsed: isSidebarCollapsed } = useAppTheme();
|
||||
const { getProjectIdentifierById } = useProject();
|
||||
const { getIsIssuePeeked, peekIssue, setPeekIssue, subIssues: subIssuesStore } = useIssueDetail();
|
||||
const {
|
||||
getIsIssuePeeked,
|
||||
peekIssue,
|
||||
setPeekIssue,
|
||||
subIssues: subIssuesStore,
|
||||
} = useIssueDetail(isEpic ? EIssueServiceType.EPICS : EIssueServiceType.ISSUES);
|
||||
|
||||
const handleIssuePeekOverview = (issue: TIssue) =>
|
||||
workspaceSlug &&
|
||||
|
|
@ -143,7 +151,7 @@ export const IssueBlock = observer((props: IssueBlockProps) => {
|
|||
return (
|
||||
<ControlLink
|
||||
id={`issue-${issue.id}`}
|
||||
href={`/${workspaceSlug}/projects/${issue.project_id}/${issue.archived_at ? "archives/" : ""}issues/${issue.id}`}
|
||||
href={`/${workspaceSlug}/projects/${issue.project_id}/${issue.archived_at ? "archives/" : ""}${isEpic ? "epics" : "issues"}/${issue.id}`}
|
||||
onClick={() => handleIssuePeekOverview(issue)}
|
||||
className="w-full cursor-pointer"
|
||||
disabled={!!issue?.tempId || issue?.is_draft}
|
||||
|
|
@ -178,7 +186,7 @@ export const IssueBlock = observer((props: IssueBlockProps) => {
|
|||
<div className="flex flex-grow items-center gap-0.5 truncate">
|
||||
<div className="flex items-center gap-1" style={isSubIssue ? { marginLeft } : {}}>
|
||||
{/* select checkbox */}
|
||||
{projectId && canSelectIssues && (
|
||||
{projectId && canSelectIssues && !isEpic && (
|
||||
<Tooltip
|
||||
tooltipContent={
|
||||
<>
|
||||
|
|
@ -220,7 +228,7 @@ export const IssueBlock = observer((props: IssueBlockProps) => {
|
|||
|
||||
{/* sub-issues chevron */}
|
||||
<div className="size-4 grid place-items-center flex-shrink-0">
|
||||
{subIssuesCount > 0 && (
|
||||
{subIssuesCount > 0 && !isEpic && (
|
||||
<button
|
||||
type="button"
|
||||
className="size-4 grid place-items-center rounded-sm text-custom-text-400 hover:text-custom-text-300"
|
||||
|
|
@ -275,6 +283,7 @@ export const IssueBlock = observer((props: IssueBlockProps) => {
|
|||
updateIssue={updateIssue}
|
||||
displayProperties={displayProperties}
|
||||
activeLayout="List"
|
||||
isEpic={isEpic}
|
||||
/>
|
||||
<div
|
||||
className={cn("hidden", {
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ interface Props {
|
|||
isDragAllowed: boolean;
|
||||
canDropOverIssue: boolean;
|
||||
selectionHelpers: TSelectionHelper;
|
||||
isEpic?: boolean;
|
||||
}
|
||||
|
||||
export const IssueBlocksList: FC<Props> = (props) => {
|
||||
|
|
@ -34,6 +35,7 @@ export const IssueBlocksList: FC<Props> = (props) => {
|
|||
selectionHelpers,
|
||||
isDragAllowed,
|
||||
canDropOverIssue,
|
||||
isEpic = false,
|
||||
} = props;
|
||||
|
||||
return (
|
||||
|
|
@ -57,6 +59,7 @@ export const IssueBlocksList: FC<Props> = (props) => {
|
|||
isLastChild={index === issueIds.length - 1}
|
||||
isDragAllowed={isDragAllowed}
|
||||
canDropOverIssue={canDropOverIssue}
|
||||
isEpic={isEpic}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ export interface IList {
|
|||
loadMoreIssues: (groupId?: string) => void;
|
||||
handleCollapsedGroups: (value: string) => void;
|
||||
collapsedGroups: TIssueKanbanFilters;
|
||||
isEpic?: boolean;
|
||||
}
|
||||
|
||||
export const List: React.FC<IList> = observer((props) => {
|
||||
|
|
@ -70,6 +71,7 @@ export const List: React.FC<IList> = observer((props) => {
|
|||
loadMoreIssues,
|
||||
handleCollapsedGroups,
|
||||
collapsedGroups,
|
||||
isEpic = false,
|
||||
} = props;
|
||||
|
||||
const storeType = useIssueStoreType();
|
||||
|
|
@ -121,7 +123,11 @@ export const List: React.FC<IList> = observer((props) => {
|
|||
return (
|
||||
<div className="relative size-full flex flex-col">
|
||||
{groups && (
|
||||
<MultipleSelectGroup containerRef={containerRef} entities={entities} disabled={!isBulkOperationsEnabled}>
|
||||
<MultipleSelectGroup
|
||||
containerRef={containerRef}
|
||||
entities={entities}
|
||||
disabled={!isBulkOperationsEnabled || isEpic}
|
||||
>
|
||||
{(helpers) => (
|
||||
<>
|
||||
<div
|
||||
|
|
@ -153,6 +159,7 @@ export const List: React.FC<IList> = observer((props) => {
|
|||
selectionHelpers={helpers}
|
||||
handleCollapsedGroups={handleCollapsedGroups}
|
||||
collapsedGroups={collapsedGroups}
|
||||
isEpic={isEpic}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -18,6 +18,8 @@ import { cn } from "@/helpers/common.helper";
|
|||
import { useEventTracker } from "@/hooks/store";
|
||||
import { useIssueStoreType } from "@/hooks/use-issue-layout-store";
|
||||
import { TSelectionHelper } from "@/hooks/use-multiple-select";
|
||||
// plane-web
|
||||
import { CreateUpdateEpicModal } from "@/plane-web/components/epics/epic-modal";
|
||||
// Plane-web
|
||||
import { WorkFlowGroupTree } from "@/plane-web/components/workflow";
|
||||
|
||||
|
|
@ -33,6 +35,7 @@ interface IHeaderGroupByCard {
|
|||
addIssuesToView?: (issueIds: string[]) => Promise<TIssue>;
|
||||
selectionHelpers: TSelectionHelper;
|
||||
handleCollapsedGroups: (value: string) => void;
|
||||
isEpic?: boolean;
|
||||
}
|
||||
|
||||
export const HeaderGroupByCard = observer((props: IHeaderGroupByCard) => {
|
||||
|
|
@ -48,6 +51,7 @@ export const HeaderGroupByCard = observer((props: IHeaderGroupByCard) => {
|
|||
addIssuesToView,
|
||||
selectionHelpers,
|
||||
handleCollapsedGroups,
|
||||
isEpic = false,
|
||||
} = props;
|
||||
// states
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
|
@ -157,6 +161,9 @@ export const HeaderGroupByCard = observer((props: IHeaderGroupByCard) => {
|
|||
</div>
|
||||
))}
|
||||
|
||||
{isEpic ? (
|
||||
<CreateUpdateEpicModal isOpen={isOpen} onClose={() => setIsOpen(false)} data={issuePayload} />
|
||||
) : (
|
||||
<CreateUpdateIssueModal
|
||||
isOpen={isOpen}
|
||||
onClose={() => setIsOpen(false)}
|
||||
|
|
@ -164,6 +171,7 @@ export const HeaderGroupByCard = observer((props: IHeaderGroupByCard) => {
|
|||
storeType={storeType}
|
||||
isDraft={isDraftIssue}
|
||||
/>
|
||||
)}
|
||||
|
||||
{renderExistingIssueModal && (
|
||||
<ExistingIssuesListModal
|
||||
|
|
|
|||
|
|
@ -64,6 +64,7 @@ interface Props {
|
|||
selectionHelpers: TSelectionHelper;
|
||||
handleCollapsedGroups: (value: string) => void;
|
||||
collapsedGroups: TIssueKanbanFilters;
|
||||
isEpic?: boolean;
|
||||
}
|
||||
|
||||
export const ListGroup = observer((props: Props) => {
|
||||
|
|
@ -90,6 +91,7 @@ export const ListGroup = observer((props: Props) => {
|
|||
selectionHelpers,
|
||||
handleCollapsedGroups,
|
||||
collapsedGroups,
|
||||
isEpic = false,
|
||||
} = props;
|
||||
|
||||
const [isDraggingOverColumn, setIsDraggingOverColumn] = useState(false);
|
||||
|
|
@ -266,6 +268,7 @@ export const ListGroup = observer((props: Props) => {
|
|||
addIssuesToView={addIssuesToView}
|
||||
selectionHelpers={selectionHelpers}
|
||||
handleCollapsedGroups={handleCollapsedGroups}
|
||||
isEpic={isEpic}
|
||||
/>
|
||||
</Row>
|
||||
{shouldExpand && (
|
||||
|
|
@ -292,6 +295,7 @@ export const ListGroup = observer((props: Props) => {
|
|||
isDragAllowed={isDragAllowed}
|
||||
canDropOverIssue={!canOverlayBeVisible}
|
||||
selectionHelpers={selectionHelpers}
|
||||
isEpic={isEpic}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
|
|
|||
|
|
@ -42,10 +42,11 @@ export interface IIssueProperties {
|
|||
isReadOnly: boolean;
|
||||
className: string;
|
||||
activeLayout: string;
|
||||
isEpic?: boolean;
|
||||
}
|
||||
|
||||
export const IssueProperties: React.FC<IIssueProperties> = observer((props) => {
|
||||
const { issue, updateIssue, displayProperties, activeLayout, isReadOnly, className } = props;
|
||||
const { issue, updateIssue, displayProperties, activeLayout, isReadOnly, className, isEpic = false } = props;
|
||||
// store hooks
|
||||
const { getProjectById } = useProject();
|
||||
const { labelMap } = useLabel();
|
||||
|
|
@ -376,6 +377,8 @@ export const IssueProperties: React.FC<IIssueProperties> = observer((props) => {
|
|||
</div>
|
||||
</WithDisplayPropertiesHOC>
|
||||
|
||||
{!isEpic && (
|
||||
<>
|
||||
{/* modules */}
|
||||
{projectDetails?.module_view && (
|
||||
<WithDisplayPropertiesHOC displayProperties={displayProperties} displayPropertyKey="modules">
|
||||
|
|
@ -413,6 +416,8 @@ export const IssueProperties: React.FC<IIssueProperties> = observer((props) => {
|
|||
</div>
|
||||
</WithDisplayPropertiesHOC>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* estimates */}
|
||||
{projectId && areEstimateEnabledByProjectId(projectId?.toString()) && (
|
||||
|
|
|
|||
|
|
@ -26,17 +26,19 @@ export type SpreadsheetStoreType =
|
|||
| EIssuesStoreType.CYCLE
|
||||
| EIssuesStoreType.PROJECT_VIEW
|
||||
| EIssuesStoreType.TEAM
|
||||
| EIssuesStoreType.TEAM_VIEW;
|
||||
| EIssuesStoreType.TEAM_VIEW
|
||||
| EIssuesStoreType.EPIC;
|
||||
|
||||
interface IBaseSpreadsheetRoot {
|
||||
QuickActions: FC<IQuickActionProps>;
|
||||
canEditPropertiesBasedOnProject?: (projectId: string) => boolean;
|
||||
isCompletedCycle?: boolean;
|
||||
viewId?: string | undefined;
|
||||
isEpic?: boolean;
|
||||
}
|
||||
|
||||
export const BaseSpreadsheetRoot = observer((props: IBaseSpreadsheetRoot) => {
|
||||
const { QuickActions, canEditPropertiesBasedOnProject, isCompletedCycle = false, viewId } = props;
|
||||
const { QuickActions, canEditPropertiesBasedOnProject, isCompletedCycle = false, viewId, isEpic = false } = props;
|
||||
// router
|
||||
const { projectId } = useParams();
|
||||
// store hooks
|
||||
|
|
@ -126,6 +128,7 @@ export const BaseSpreadsheetRoot = observer((props: IBaseSpreadsheetRoot) => {
|
|||
disableIssueCreation={!enableIssueCreation || !isEditingAllowed || isCompletedCycle}
|
||||
canLoadMoreIssues={!!nextPageResults}
|
||||
loadMoreIssues={fetchNextIssues}
|
||||
isEpic={isEpic}
|
||||
/>
|
||||
</IssueLayoutHOC>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { Dispatch, MouseEvent, MutableRefObject, SetStateAction, useRef, useStat
|
|||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
import { ChevronRight, MoreHorizontal } from "lucide-react";
|
||||
import { EIssueServiceType } from "@plane/constants";
|
||||
// plane helpers
|
||||
import { useOutsideClickDetector } from "@plane/hooks";
|
||||
// types
|
||||
|
|
@ -44,6 +45,7 @@ interface Props {
|
|||
spacingLeft?: number;
|
||||
selectionHelpers: TSelectionHelper;
|
||||
shouldRenderByDefault?: boolean;
|
||||
isEpic?: boolean;
|
||||
}
|
||||
|
||||
export const SpreadsheetIssueRow = observer((props: Props) => {
|
||||
|
|
@ -62,11 +64,12 @@ export const SpreadsheetIssueRow = observer((props: Props) => {
|
|||
spacingLeft = 6,
|
||||
selectionHelpers,
|
||||
shouldRenderByDefault,
|
||||
isEpic = false,
|
||||
} = props;
|
||||
// states
|
||||
const [isExpanded, setExpanded] = useState<boolean>(false);
|
||||
// store hooks
|
||||
const { subIssues: subIssuesStore } = useIssueDetail();
|
||||
const { subIssues: subIssuesStore } = useIssueDetail(isEpic ? EIssueServiceType.EPICS : EIssueServiceType.ISSUES);
|
||||
const { issueMap } = useIssues();
|
||||
|
||||
// derived values
|
||||
|
|
@ -110,10 +113,12 @@ export const SpreadsheetIssueRow = observer((props: Props) => {
|
|||
setExpanded={setExpanded}
|
||||
spreadsheetColumnsList={spreadsheetColumnsList}
|
||||
selectionHelpers={selectionHelpers}
|
||||
isEpic={isEpic}
|
||||
/>
|
||||
</RenderIfVisible>
|
||||
|
||||
{isExpanded &&
|
||||
!isEpic &&
|
||||
subIssues?.map((subIssueId: string) => (
|
||||
<SpreadsheetIssueRow
|
||||
key={subIssueId}
|
||||
|
|
@ -152,6 +157,7 @@ interface IssueRowDetailsProps {
|
|||
spreadsheetColumnsList: (keyof IIssueDisplayProperties)[];
|
||||
spacingLeft?: number;
|
||||
selectionHelpers: TSelectionHelper;
|
||||
isEpic?: boolean;
|
||||
}
|
||||
|
||||
const IssueRowDetails = observer((props: IssueRowDetailsProps) => {
|
||||
|
|
@ -170,6 +176,7 @@ const IssueRowDetails = observer((props: IssueRowDetailsProps) => {
|
|||
spreadsheetColumnsList,
|
||||
spacingLeft = 6,
|
||||
selectionHelpers,
|
||||
isEpic = false,
|
||||
} = props;
|
||||
// states
|
||||
const [isMenuActive, setIsMenuActive] = useState(false);
|
||||
|
|
@ -180,8 +187,8 @@ const IssueRowDetails = observer((props: IssueRowDetailsProps) => {
|
|||
const { workspaceSlug, projectId } = useParams();
|
||||
// hooks
|
||||
const { getProjectIdentifierById } = useProject();
|
||||
const { getIsIssuePeeked, peekIssue } = useIssueDetail();
|
||||
const { handleRedirection } = useIssuePeekOverviewRedirection();
|
||||
const { getIsIssuePeeked, peekIssue } = useIssueDetail(isEpic ? EIssueServiceType.EPICS : EIssueServiceType.ISSUES);
|
||||
const { handleRedirection } = useIssuePeekOverviewRedirection(isEpic);
|
||||
const { isMobile } = usePlatformOS();
|
||||
|
||||
// handlers
|
||||
|
|
@ -243,7 +250,7 @@ const IssueRowDetails = observer((props: IssueRowDetailsProps) => {
|
|||
className="relative md:sticky left-0 z-10 group/list-block bg-custom-background-100"
|
||||
>
|
||||
<ControlLink
|
||||
href={`/${workspaceSlug}/projects/${issueDetail.project_id}/issues/${issueId}`}
|
||||
href={`/${workspaceSlug}/projects/${issueDetail.project_id}/${isEpic ? "epics" : "issues"}/${issueId}`}
|
||||
onClick={() => handleIssuePeekOverview(issueDetail)}
|
||||
className={cn(
|
||||
"group clickable cursor-pointer h-11 w-[28rem] flex items-center text-sm after:absolute border-r-[0.5px] z-10 border-custom-border-200 bg-transparent group-[.selected-issue-row]:bg-custom-primary-100/5 group-[.selected-issue-row]:hover:bg-custom-primary-100/10",
|
||||
|
|
@ -307,7 +314,7 @@ const IssueRowDetails = observer((props: IssueRowDetailsProps) => {
|
|||
|
||||
{/* sub-issues chevron */}
|
||||
<div className="grid place-items-center size-4">
|
||||
{subIssuesCount > 0 && (
|
||||
{subIssuesCount > 0 && !isEpic && (
|
||||
<button
|
||||
type="button"
|
||||
className="grid place-items-center size-4 rounded-sm text-custom-text-400 hover:text-custom-text-300"
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ type Props = {
|
|||
loadMoreIssues: () => void;
|
||||
spreadsheetColumnsList: (keyof IIssueDisplayProperties)[];
|
||||
selectionHelpers: TSelectionHelper;
|
||||
isEpic?: boolean;
|
||||
};
|
||||
|
||||
export const SpreadsheetTable = observer((props: Props) => {
|
||||
|
|
@ -47,6 +48,7 @@ export const SpreadsheetTable = observer((props: Props) => {
|
|||
loadMoreIssues,
|
||||
spreadsheetColumnsList,
|
||||
selectionHelpers,
|
||||
isEpic = false,
|
||||
} = props;
|
||||
|
||||
// states
|
||||
|
|
@ -127,6 +129,7 @@ export const SpreadsheetTable = observer((props: Props) => {
|
|||
isScrolled={isScrolled}
|
||||
spreadsheetColumnsList={spreadsheetColumnsList}
|
||||
selectionHelpers={selectionHelpers}
|
||||
isEpic={isEpic}
|
||||
/>
|
||||
))}
|
||||
</tbody>
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ type Props = {
|
|||
enableQuickCreateIssue?: boolean;
|
||||
disableIssueCreation?: boolean;
|
||||
isWorkspaceLevel?: boolean;
|
||||
isEpic?: boolean;
|
||||
};
|
||||
|
||||
export const SpreadsheetView: React.FC<Props> = observer((props) => {
|
||||
|
|
@ -51,6 +52,7 @@ export const SpreadsheetView: React.FC<Props> = observer((props) => {
|
|||
canLoadMoreIssues,
|
||||
loadMoreIssues,
|
||||
isWorkspaceLevel = false,
|
||||
isEpic = false,
|
||||
} = props;
|
||||
// refs
|
||||
const containerRef = useRef<HTMLTableElement | null>(null);
|
||||
|
|
@ -85,7 +87,7 @@ export const SpreadsheetView: React.FC<Props> = observer((props) => {
|
|||
entities={{
|
||||
[SPREADSHEET_SELECT_GROUP]: issueIds,
|
||||
}}
|
||||
disabled={!isBulkOperationsEnabled}
|
||||
disabled={!isBulkOperationsEnabled || isEpic}
|
||||
>
|
||||
{(helpers) => (
|
||||
<>
|
||||
|
|
@ -105,6 +107,7 @@ export const SpreadsheetView: React.FC<Props> = observer((props) => {
|
|||
loadMoreIssues={loadMoreIssues}
|
||||
spreadsheetColumnsList={spreadsheetColumnsList}
|
||||
selectionHelpers={helpers}
|
||||
isEpic={isEpic}
|
||||
/>
|
||||
</div>
|
||||
<div className="border-t border-custom-border-100">
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ import { FileService } from "@/services/file.service";
|
|||
const fileService = new FileService();
|
||||
// local components
|
||||
import { DraftIssueLayout } from "./draft-issue-layout";
|
||||
import { IssueFormRoot } from "./form";
|
||||
import { type IssueFormProps, IssueFormRoot } from "./form";
|
||||
|
||||
export const CreateUpdateIssueModalBase: React.FC<IssuesModalProps> = observer((props) => {
|
||||
const {
|
||||
|
|
@ -41,7 +41,9 @@ export const CreateUpdateIssueModalBase: React.FC<IssuesModalProps> = observer((
|
|||
} = props;
|
||||
const issueStoreType = useIssueStoreType();
|
||||
|
||||
const storeType = issueStoreFromProps ?? issueStoreType;
|
||||
const storeType = (issueStoreFromProps ? issueStoreFromProps : issueStoreType === EIssuesStoreType.EPIC)
|
||||
? EIssuesStoreType.PROJECT
|
||||
: issueStoreType;
|
||||
// ref
|
||||
const issueTitleRef = useRef<HTMLInputElement>(null);
|
||||
// states
|
||||
|
|
@ -333,6 +335,30 @@ export const CreateUpdateIssueModalBase: React.FC<IssuesModalProps> = observer((
|
|||
// don't open the modal if there are no projects
|
||||
if (!projectIdsWithCreatePermissions || projectIdsWithCreatePermissions.length === 0 || !activeProjectId) return null;
|
||||
|
||||
const commonIssueModalProps: IssueFormProps = {
|
||||
issueTitleRef: issueTitleRef,
|
||||
data: {
|
||||
...data,
|
||||
description_html: description,
|
||||
cycle_id: data?.cycle_id ? data?.cycle_id : cycleId ? cycleId.toString() : null,
|
||||
module_ids: data?.module_ids ? data?.module_ids : moduleId ? [moduleId.toString()] : null,
|
||||
},
|
||||
onAssetUpload: handleUpdateUploadedAssetIds,
|
||||
onClose: handleClose,
|
||||
onSubmit: (payload) => handleFormSubmit(payload, isDraft),
|
||||
projectId: activeProjectId,
|
||||
isCreateMoreToggleEnabled: createMore,
|
||||
onCreateMoreToggleChange: handleCreateMoreToggleChange,
|
||||
isDraft: isDraft,
|
||||
moveToIssue: moveToIssue,
|
||||
modalTitle: modalTitle,
|
||||
primaryButtonText: primaryButtonText,
|
||||
isDuplicateModalOpen: isDuplicateModalOpen,
|
||||
handleDuplicateIssueModal: handleDuplicateIssueModal,
|
||||
isProjectSelectionDisabled: isProjectSelectionDisabled,
|
||||
storeType: storeType,
|
||||
};
|
||||
|
||||
return (
|
||||
<ModalCore
|
||||
isOpen={isOpen}
|
||||
|
|
@ -342,51 +368,9 @@ export const CreateUpdateIssueModalBase: React.FC<IssuesModalProps> = observer((
|
|||
className="!bg-transparent rounded-lg shadow-none transition-[width] ease-linear"
|
||||
>
|
||||
{withDraftIssueWrapper ? (
|
||||
<DraftIssueLayout
|
||||
changesMade={changesMade}
|
||||
data={{
|
||||
...data,
|
||||
description_html: description,
|
||||
cycle_id: data?.cycle_id ? data?.cycle_id : cycleId ? cycleId.toString() : null,
|
||||
module_ids: data?.module_ids ? data?.module_ids : moduleId ? [moduleId.toString()] : null,
|
||||
}}
|
||||
issueTitleRef={issueTitleRef}
|
||||
onAssetUpload={handleUpdateUploadedAssetIds}
|
||||
onChange={handleFormChange}
|
||||
onClose={handleClose}
|
||||
onSubmit={(payload) => handleFormSubmit(payload, isDraft)}
|
||||
projectId={activeProjectId}
|
||||
isCreateMoreToggleEnabled={createMore}
|
||||
onCreateMoreToggleChange={handleCreateMoreToggleChange}
|
||||
isDraft={isDraft}
|
||||
moveToIssue={moveToIssue}
|
||||
isDuplicateModalOpen={isDuplicateModalOpen}
|
||||
handleDuplicateIssueModal={handleDuplicateIssueModal}
|
||||
isProjectSelectionDisabled={isProjectSelectionDisabled}
|
||||
/>
|
||||
<DraftIssueLayout {...commonIssueModalProps} changesMade={changesMade} onChange={handleFormChange} />
|
||||
) : (
|
||||
<IssueFormRoot
|
||||
issueTitleRef={issueTitleRef}
|
||||
data={{
|
||||
...data,
|
||||
description_html: description,
|
||||
cycle_id: data?.cycle_id ? data?.cycle_id : cycleId ? cycleId.toString() : null,
|
||||
module_ids: data?.module_ids ? data?.module_ids : moduleId ? [moduleId.toString()] : null,
|
||||
}}
|
||||
onAssetUpload={handleUpdateUploadedAssetIds}
|
||||
onClose={handleClose}
|
||||
isCreateMoreToggleEnabled={createMore}
|
||||
onCreateMoreToggleChange={handleCreateMoreToggleChange}
|
||||
onSubmit={(payload) => handleFormSubmit(payload, isDraft)}
|
||||
projectId={activeProjectId}
|
||||
isDraft={isDraft}
|
||||
moveToIssue={moveToIssue}
|
||||
modalTitle={modalTitle}
|
||||
primaryButtonText={primaryButtonText}
|
||||
isDuplicateModalOpen={isDuplicateModalOpen}
|
||||
handleDuplicateIssueModal={handleDuplicateIssueModal}
|
||||
isProjectSelectionDisabled={isProjectSelectionDisabled}
|
||||
/>
|
||||
<IssueFormRoot {...commonIssueModalProps} />
|
||||
)}
|
||||
</ModalCore>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -332,6 +332,7 @@ export const IssueDefaultProperties: React.FC<TIssueDefaultPropertiesProps> = ob
|
|||
}}
|
||||
projectId={projectId ?? undefined}
|
||||
issueId={isDraft ? undefined : id}
|
||||
searchEpic
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
export * from "./issue-modal";
|
||||
export * from "./issue-modal-context";
|
||||
|
|
|
|||
|
|
@ -16,51 +16,15 @@ import { isEmptyHtmlString } from "@/helpers/string.helper";
|
|||
import { useIssueModal } from "@/hooks/context/use-issue-modal";
|
||||
import { useEventTracker, useWorkspaceDraftIssues } from "@/hooks/store";
|
||||
// local components
|
||||
import { IssueFormRoot } from "./form";
|
||||
import { IssueFormRoot, type IssueFormProps } from "./form";
|
||||
|
||||
export interface DraftIssueProps {
|
||||
export interface DraftIssueProps extends IssueFormProps {
|
||||
changesMade: Partial<TIssue> | null;
|
||||
data?: Partial<TIssue>;
|
||||
issueTitleRef: React.MutableRefObject<HTMLInputElement | null>;
|
||||
isCreateMoreToggleEnabled: boolean;
|
||||
onAssetUpload: (assetId: string) => void;
|
||||
onCreateMoreToggleChange: (value: boolean) => void;
|
||||
onChange: (formData: Partial<TIssue> | null) => void;
|
||||
onClose: (saveDraftIssueInLocalStorage?: boolean) => void;
|
||||
onSubmit: (formData: Partial<TIssue>, is_draft_issue?: boolean) => Promise<void>;
|
||||
projectId: string;
|
||||
isDraft: boolean;
|
||||
moveToIssue?: boolean;
|
||||
modalTitle?: string;
|
||||
primaryButtonText?: {
|
||||
default: string;
|
||||
loading: string;
|
||||
};
|
||||
isDuplicateModalOpen: boolean;
|
||||
handleDuplicateIssueModal: (isOpen: boolean) => void;
|
||||
isProjectSelectionDisabled?: boolean;
|
||||
}
|
||||
|
||||
export const DraftIssueLayout: React.FC<DraftIssueProps> = observer((props) => {
|
||||
const {
|
||||
changesMade,
|
||||
data,
|
||||
issueTitleRef,
|
||||
onAssetUpload,
|
||||
onChange,
|
||||
onClose,
|
||||
onSubmit,
|
||||
projectId,
|
||||
isCreateMoreToggleEnabled,
|
||||
onCreateMoreToggleChange,
|
||||
isDraft,
|
||||
moveToIssue = false,
|
||||
modalTitle,
|
||||
primaryButtonText,
|
||||
isDuplicateModalOpen,
|
||||
handleDuplicateIssueModal,
|
||||
isProjectSelectionDisabled = false,
|
||||
} = props;
|
||||
const { changesMade, data, onChange, onClose, projectId } = props;
|
||||
// states
|
||||
const [issueDiscardModal, setIssueDiscardModal] = useState(false);
|
||||
// router params
|
||||
|
|
@ -74,7 +38,7 @@ export const DraftIssueLayout: React.FC<DraftIssueProps> = observer((props) => {
|
|||
|
||||
const handleClose = () => {
|
||||
if (data?.id) {
|
||||
onClose(false);
|
||||
onClose();
|
||||
setIssueDiscardModal(false);
|
||||
} else {
|
||||
if (changesMade) {
|
||||
|
|
@ -93,11 +57,11 @@ export const DraftIssueLayout: React.FC<DraftIssueProps> = observer((props) => {
|
|||
delete changesMade.description_html;
|
||||
});
|
||||
if (isEmpty(changesMade)) {
|
||||
onClose(false);
|
||||
onClose();
|
||||
setIssueDiscardModal(false);
|
||||
} else setIssueDiscardModal(true);
|
||||
} else {
|
||||
onClose(false);
|
||||
onClose();
|
||||
setIssueDiscardModal(false);
|
||||
}
|
||||
}
|
||||
|
|
@ -126,7 +90,7 @@ export const DraftIssueLayout: React.FC<DraftIssueProps> = observer((props) => {
|
|||
});
|
||||
onChange(null);
|
||||
setIssueDiscardModal(false);
|
||||
onClose(false);
|
||||
onClose();
|
||||
return res;
|
||||
})
|
||||
.catch(() => {
|
||||
|
|
@ -162,27 +126,10 @@ export const DraftIssueLayout: React.FC<DraftIssueProps> = observer((props) => {
|
|||
onDiscard={() => {
|
||||
onChange(null);
|
||||
setIssueDiscardModal(false);
|
||||
onClose(false);
|
||||
onClose();
|
||||
}}
|
||||
/>
|
||||
<IssueFormRoot
|
||||
isCreateMoreToggleEnabled={isCreateMoreToggleEnabled}
|
||||
onCreateMoreToggleChange={onCreateMoreToggleChange}
|
||||
data={data}
|
||||
issueTitleRef={issueTitleRef}
|
||||
onAssetUpload={onAssetUpload}
|
||||
onChange={onChange}
|
||||
onClose={handleClose}
|
||||
onSubmit={onSubmit}
|
||||
projectId={projectId}
|
||||
isDraft={isDraft}
|
||||
moveToIssue={moveToIssue}
|
||||
modalTitle={modalTitle}
|
||||
primaryButtonText={primaryButtonText}
|
||||
isDuplicateModalOpen={isDuplicateModalOpen}
|
||||
handleDuplicateIssueModal={handleDuplicateIssueModal}
|
||||
isProjectSelectionDisabled={isProjectSelectionDisabled}
|
||||
/>
|
||||
<IssueFormRoot {...props} onClose={handleClose} />
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import {
|
|||
IssueTitleInput,
|
||||
} from "@/components/issues/issue-modal/components";
|
||||
import { CreateLabelModal } from "@/components/labels";
|
||||
import { EIssuesStoreType } from "@/constants/issue";
|
||||
import { ETabIndices } from "@/constants/tab-indices";
|
||||
// helpers
|
||||
import { cn } from "@/helpers/common.helper";
|
||||
|
|
@ -72,6 +73,7 @@ export interface IssueFormProps {
|
|||
isDuplicateModalOpen: boolean;
|
||||
handleDuplicateIssueModal: (isOpen: boolean) => void;
|
||||
isProjectSelectionDisabled?: boolean;
|
||||
storeType: EIssuesStoreType;
|
||||
}
|
||||
|
||||
export const IssueFormRoot: FC<IssueFormProps> = observer((props) => {
|
||||
|
|
@ -86,8 +88,8 @@ export const IssueFormRoot: FC<IssueFormProps> = observer((props) => {
|
|||
isCreateMoreToggleEnabled,
|
||||
onCreateMoreToggleChange,
|
||||
isDraft,
|
||||
moveToIssue,
|
||||
modalTitle = `${data?.id ? "Update" : isDraft ? "Create a draft" : "Create new issue"}`,
|
||||
moveToIssue = false,
|
||||
modalTitle,
|
||||
primaryButtonText = {
|
||||
default: `${data?.id ? "Update" : isDraft ? "Save to Drafts" : "Save"}`,
|
||||
loading: `${data?.id ? "Updating" : "Saving"}`,
|
||||
|
|
@ -95,6 +97,7 @@ export const IssueFormRoot: FC<IssueFormProps> = observer((props) => {
|
|||
isDuplicateModalOpen,
|
||||
handleDuplicateIssueModal,
|
||||
isProjectSelectionDisabled = false,
|
||||
storeType,
|
||||
} = props;
|
||||
|
||||
// states
|
||||
|
|
@ -280,6 +283,7 @@ export const IssueFormRoot: FC<IssueFormProps> = observer((props) => {
|
|||
|
||||
// debounced duplicate issues swr
|
||||
const { duplicateIssues } = useDebouncedDuplicateIssues(
|
||||
workspaceSlug?.toString(),
|
||||
projectDetails?.workspace.toString(),
|
||||
projectId ?? undefined,
|
||||
{
|
||||
|
|
@ -373,7 +377,7 @@ export const IssueFormRoot: FC<IssueFormProps> = observer((props) => {
|
|||
disabled={!!data?.id || !!data?.sourceIssueId || isProjectSelectionDisabled}
|
||||
handleFormChange={handleFormChange}
|
||||
/>
|
||||
{projectId && (
|
||||
{projectId && storeType !== EIssuesStoreType.EPIC && (
|
||||
<IssueTypeSelect
|
||||
control={control}
|
||||
projectId={projectId}
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ type Props = {
|
|||
onChange: (issue: ISearchIssueResponse) => void;
|
||||
projectId: string | undefined;
|
||||
issueId?: string;
|
||||
searchEpic?: boolean;
|
||||
};
|
||||
|
||||
// services
|
||||
|
|
@ -41,6 +42,7 @@ export const ParentIssuesListModal: React.FC<Props> = ({
|
|||
onChange,
|
||||
projectId,
|
||||
issueId,
|
||||
searchEpic = false,
|
||||
}) => {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
|
|
@ -72,6 +74,7 @@ export const ParentIssuesListModal: React.FC<Props> = ({
|
|||
parent: true,
|
||||
issue_id: issueId,
|
||||
workspace_search: isWorkspaceLevel,
|
||||
epic: searchEpic ? true : undefined,
|
||||
})
|
||||
.then((res) => setIssues(res))
|
||||
.finally(() => {
|
||||
|
|
|
|||
|
|
@ -57,11 +57,16 @@ export const PeekOverviewIssueDetails: FC<IPeekOverviewIssueDetails> = observer(
|
|||
const issue = issueId ? getIssueById(issueId) : undefined;
|
||||
const projectDetails = issue?.project_id ? getProjectById(issue?.project_id) : undefined;
|
||||
// debounced duplicate issues swr
|
||||
const { duplicateIssues } = useDebouncedDuplicateIssues(projectDetails?.workspace.toString(), projectDetails?.id, {
|
||||
const { duplicateIssues } = useDebouncedDuplicateIssues(
|
||||
workspaceSlug,
|
||||
projectDetails?.workspace.toString(),
|
||||
projectDetails?.id,
|
||||
{
|
||||
name: issue?.name,
|
||||
description_html: getTextContent(issue?.description_html),
|
||||
issueId: issue?.id,
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
if (!issue || !issue.project_id) return <></>;
|
||||
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import { ISSUE_UPDATED, ISSUE_DELETED, ISSUE_ARCHIVED, ISSUE_RESTORED } from "@/
|
|||
import { EIssuesStoreType } from "@/constants/issue";
|
||||
// hooks
|
||||
import { useEventTracker, useIssueDetail, useIssues, useUserPermissions } from "@/hooks/store";
|
||||
import { useIssuesStore } from "@/hooks/use-issue-layout-store";
|
||||
import { useIssueStoreType } from "@/hooks/use-issue-layout-store";
|
||||
// plane web constants
|
||||
import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions";
|
||||
|
||||
|
|
@ -22,10 +22,16 @@ interface IIssuePeekOverview {
|
|||
embedIssue?: boolean;
|
||||
embedRemoveCurrentNotification?: () => void;
|
||||
is_draft?: boolean;
|
||||
storeType?: EIssuesStoreType;
|
||||
}
|
||||
|
||||
export const IssuePeekOverview: FC<IIssuePeekOverview> = observer((props) => {
|
||||
const { embedIssue = false, embedRemoveCurrentNotification, is_draft = false } = props;
|
||||
const {
|
||||
embedIssue = false,
|
||||
embedRemoveCurrentNotification,
|
||||
is_draft = false,
|
||||
storeType: issueStoreFromProps,
|
||||
} = props;
|
||||
// router
|
||||
const pathname = usePathname();
|
||||
// store hook
|
||||
|
|
@ -40,8 +46,9 @@ export const IssuePeekOverview: FC<IIssuePeekOverview> = observer((props) => {
|
|||
issue: { fetchIssue, getIsFetchingIssueDetails },
|
||||
fetchActivities,
|
||||
} = useIssueDetail();
|
||||
|
||||
const { issues } = useIssuesStore();
|
||||
const issueStoreType = useIssueStoreType();
|
||||
const storeType = issueStoreFromProps ?? issueStoreType;
|
||||
const { issues } = useIssues(storeType);
|
||||
const { captureIssueEvent } = useEventTracker();
|
||||
// state
|
||||
const [error, setError] = useState(false);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
import { FC, useRef, useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
// constants
|
||||
import { EIssueServiceType } from "@plane/constants";
|
||||
// types
|
||||
import { TNameDescriptionLoader } from "@plane/types";
|
||||
// components
|
||||
|
|
@ -65,6 +67,7 @@ export const IssueView: FC<IIssueView> = observer((props) => {
|
|||
toggleArchiveIssueModal,
|
||||
issue: { getIssueById, getIsLocalDBIssueDescription },
|
||||
} = useIssueDetail();
|
||||
const { isAnyModalOpen: isAnyEpicModalOpen } = useIssueDetail(EIssueServiceType.EPICS);
|
||||
const issue = getIssueById(issueId);
|
||||
// remove peek id
|
||||
const removeRoutePeekId = () => {
|
||||
|
|
@ -78,7 +81,7 @@ export const IssueView: FC<IIssueView> = observer((props) => {
|
|||
issuePeekOverviewRef,
|
||||
() => {
|
||||
if (!embedIssue) {
|
||||
if (!isAnyModalOpen) {
|
||||
if (!isAnyModalOpen && !isAnyEpicModalOpen) {
|
||||
removeRoutePeekId();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,8 @@ import React, { FC } from "react";
|
|||
import { observer } from "mobx-react";
|
||||
import { X, Pencil, Trash, Link as LinkIcon } from "lucide-react";
|
||||
// Plane
|
||||
import { TIssue } from "@plane/types";
|
||||
import { EIssueServiceType } from "@plane/constants";
|
||||
import { TIssue, TIssueServiceType } from "@plane/types";
|
||||
import { ControlLink, CustomMenu, Tooltip } from "@plane/ui";
|
||||
// components
|
||||
import { RelationIssueProperty } from "@/components/issues/relations";
|
||||
|
|
@ -27,6 +28,7 @@ type Props = {
|
|||
disabled: boolean;
|
||||
issueOperations: TRelationIssueOperations;
|
||||
handleIssueCrudState: (key: "update" | "delete", issueId: string, issue?: TIssue | null) => void;
|
||||
issueServiceType?: TIssueServiceType;
|
||||
};
|
||||
|
||||
export const RelationIssueListItem: FC<Props> = observer((props) => {
|
||||
|
|
@ -39,6 +41,7 @@ export const RelationIssueListItem: FC<Props> = observer((props) => {
|
|||
disabled = false,
|
||||
issueOperations,
|
||||
handleIssueCrudState,
|
||||
issueServiceType = EIssueServiceType.ISSUES,
|
||||
} = props;
|
||||
|
||||
// store hooks
|
||||
|
|
@ -47,7 +50,7 @@ export const RelationIssueListItem: FC<Props> = observer((props) => {
|
|||
removeRelation,
|
||||
toggleCreateIssueModal,
|
||||
toggleDeleteIssueModal,
|
||||
} = useIssueDetail();
|
||||
} = useIssueDetail(issueServiceType);
|
||||
const project = useProject();
|
||||
const { getProjectStates } = useProjectState();
|
||||
const { handleRedirection } = useIssuePeekOverviewRedirection();
|
||||
|
|
@ -137,6 +140,7 @@ export const RelationIssueListItem: FC<Props> = observer((props) => {
|
|||
issueId={relationIssueId}
|
||||
disabled={disabled}
|
||||
issueOperations={issueOperations}
|
||||
issueServiceType={issueServiceType}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex-shrink-0 text-sm">
|
||||
|
|
|
|||
|
|
@ -2,7 +2,8 @@
|
|||
import React, { FC } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
// Plane
|
||||
import { TIssue } from "@plane/types";
|
||||
import { EIssueServiceType } from "@plane/constants";
|
||||
import { TIssue, TIssueServiceType } from "@plane/types";
|
||||
// components
|
||||
import { RelationIssueListItem } from "@/components/issues/relations";
|
||||
// Plane-web
|
||||
|
|
@ -19,6 +20,7 @@ type Props = {
|
|||
issueOperations: TRelationIssueOperations;
|
||||
handleIssueCrudState: (key: "update" | "delete", issueId: string, issue?: TIssue | null) => void;
|
||||
disabled?: boolean;
|
||||
issueServiceType?: TIssueServiceType;
|
||||
};
|
||||
|
||||
export const RelationIssueList: FC<Props> = observer((props) => {
|
||||
|
|
@ -31,6 +33,7 @@ export const RelationIssueList: FC<Props> = observer((props) => {
|
|||
disabled = false,
|
||||
issueOperations,
|
||||
handleIssueCrudState,
|
||||
issueServiceType = EIssueServiceType.ISSUES,
|
||||
} = props;
|
||||
|
||||
return (
|
||||
|
|
@ -48,6 +51,7 @@ export const RelationIssueList: FC<Props> = observer((props) => {
|
|||
disabled={disabled}
|
||||
handleIssueCrudState={handleIssueCrudState}
|
||||
issueOperations={issueOperations}
|
||||
issueServiceType={issueServiceType}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
"use client";
|
||||
import React, { FC } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { EIssueServiceType } from "@plane/constants";
|
||||
// components
|
||||
import { TIssuePriorities } from "@plane/types";
|
||||
import { TIssuePriorities, TIssueServiceType } from "@plane/types";
|
||||
import { PriorityDropdown, MemberDropdown, StateDropdown } from "@/components/dropdowns";
|
||||
// hooks
|
||||
import { useIssueDetail } from "@/hooks/store";
|
||||
|
|
@ -14,14 +15,15 @@ type Props = {
|
|||
issueId: string;
|
||||
disabled: boolean;
|
||||
issueOperations: TRelationIssueOperations;
|
||||
issueServiceType?: TIssueServiceType;
|
||||
};
|
||||
|
||||
export const RelationIssueProperty: FC<Props> = observer((props) => {
|
||||
const { workspaceSlug, issueId, disabled, issueOperations } = props;
|
||||
const { workspaceSlug, issueId, disabled, issueOperations, issueServiceType = EIssueServiceType.ISSUES } = props;
|
||||
// hooks
|
||||
const {
|
||||
issue: { getIssueById },
|
||||
} = useIssueDetail();
|
||||
} = useIssueDetail(issueServiceType);
|
||||
|
||||
// derived value
|
||||
const issue = getIssueById(issueId);
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
import React from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { ChevronRight, X, Pencil, Trash, Link as LinkIcon, Loader } from "lucide-react";
|
||||
import { TIssue } from "@plane/types";
|
||||
import { TIssue, TIssueServiceType } from "@plane/types";
|
||||
// ui
|
||||
import { ControlLink, CustomMenu, Tooltip } from "@plane/ui";
|
||||
// helpers
|
||||
|
|
@ -36,6 +36,7 @@ export interface ISubIssues {
|
|||
) => void;
|
||||
subIssueOperations: TSubIssueOperations;
|
||||
issueId: string;
|
||||
issueServiceType?: TIssueServiceType;
|
||||
}
|
||||
|
||||
export const IssueListItem: React.FC<ISubIssues> = observer((props) => {
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue