[WEB-2907] chore: issue store updated and code refactor (#6279)
* chore: issue and epic store updated and code refactor * chore: layout ux copy updated
This commit is contained in:
parent
36b3328c5e
commit
756a71ca78
18 changed files with 71 additions and 50 deletions
|
|
@ -30,10 +30,11 @@ export type TQuickAddIssueFormRoot = {
|
||||||
register: UseFormRegister<TIssue>;
|
register: UseFormRegister<TIssue>;
|
||||||
onSubmit: () => void;
|
onSubmit: () => void;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
|
isEpic: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const QuickAddIssueFormRoot: FC<TQuickAddIssueFormRoot> = observer((props) => {
|
export const QuickAddIssueFormRoot: FC<TQuickAddIssueFormRoot> = observer((props) => {
|
||||||
const { isOpen, layout, projectId, hasError = false, setFocus, register, onSubmit, onClose } = props;
|
const { isOpen, layout, projectId, hasError = false, setFocus, register, onSubmit, onClose, isEpic } = props;
|
||||||
// store hooks
|
// store hooks
|
||||||
const { getProjectById } = useProject();
|
const { getProjectById } = useProject();
|
||||||
// derived values
|
// derived values
|
||||||
|
|
@ -70,6 +71,7 @@ export const QuickAddIssueFormRoot: FC<TQuickAddIssueFormRoot> = observer((props
|
||||||
hasError={hasError}
|
hasError={hasError}
|
||||||
register={register}
|
register={register}
|
||||||
onSubmit={onSubmit}
|
onSubmit={onSubmit}
|
||||||
|
isEpic={isEpic}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -93,10 +93,11 @@ export class IssueActivityStore implements IIssueActivityStore {
|
||||||
|
|
||||||
let activityComments: TIssueActivityComment[] = [];
|
let activityComments: TIssueActivityComment[] = [];
|
||||||
|
|
||||||
const currentStore = this.serviceType === EIssueServiceType.EPICS ? this.store.epic : this.store.issue;
|
const currentStore =
|
||||||
|
this.serviceType === EIssueServiceType.EPICS ? this.store.issue.epicDetail : this.store.issue.issueDetail;
|
||||||
|
|
||||||
const activities = this.getActivitiesByIssueId(issueId) || [];
|
const activities = this.getActivitiesByIssueId(issueId) || [];
|
||||||
const comments = currentStore.issueDetail.comment.getCommentsByIssueId(issueId) || [];
|
const comments = currentStore.comment.getCommentsByIssueId(issueId) || [];
|
||||||
|
|
||||||
activities.forEach((activityId) => {
|
activities.forEach((activityId) => {
|
||||||
const activity = this.getActivityById(activityId);
|
const activity = this.getActivityById(activityId);
|
||||||
|
|
@ -109,7 +110,7 @@ export class IssueActivityStore implements IIssueActivityStore {
|
||||||
});
|
});
|
||||||
|
|
||||||
comments.forEach((commentId) => {
|
comments.forEach((commentId) => {
|
||||||
const comment = currentStore.issueDetail.comment.getCommentById(commentId);
|
const comment = currentStore.comment.getCommentById(commentId);
|
||||||
if (!comment) return;
|
if (!comment) return;
|
||||||
activityComments.push({
|
activityComments.push({
|
||||||
id: comment.id,
|
id: comment.id,
|
||||||
|
|
|
||||||
|
|
@ -10,10 +10,11 @@ type TCreateIssueToastActionItems = {
|
||||||
workspaceSlug: string;
|
workspaceSlug: string;
|
||||||
projectId: string;
|
projectId: string;
|
||||||
issueId: string;
|
issueId: string;
|
||||||
|
isEpic?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CreateIssueToastActionItems: FC<TCreateIssueToastActionItems> = observer((props) => {
|
export const CreateIssueToastActionItems: FC<TCreateIssueToastActionItems> = observer((props) => {
|
||||||
const { workspaceSlug, projectId, issueId } = props;
|
const { workspaceSlug, projectId, issueId, isEpic = false } = props;
|
||||||
// state
|
// state
|
||||||
const [copied, setCopied] = useState(false);
|
const [copied, setCopied] = useState(false);
|
||||||
// store hooks
|
// store hooks
|
||||||
|
|
@ -26,7 +27,7 @@ export const CreateIssueToastActionItems: FC<TCreateIssueToastActionItems> = obs
|
||||||
|
|
||||||
if (!issue) return null;
|
if (!issue) return null;
|
||||||
|
|
||||||
const issueLink = `${workspaceSlug}/projects/${projectId}/issues/${issueId}`;
|
const issueLink = `${workspaceSlug}/projects/${projectId}/${isEpic ? "epics" : "issues"}/${issueId}`;
|
||||||
|
|
||||||
const copyToClipboard = async (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
const copyToClipboard = async (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -43,12 +44,12 @@ export const CreateIssueToastActionItems: FC<TCreateIssueToastActionItems> = obs
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center gap-1 text-xs text-custom-text-200">
|
<div className="flex items-center gap-1 text-xs text-custom-text-200">
|
||||||
<a
|
<a
|
||||||
href={`/${workspaceSlug}/projects/${projectId}/issues/${issueId}/`}
|
href={`/${workspaceSlug}/projects/${projectId}/${isEpic ? "epics" : "issues"}/${issueId}/`}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="text-custom-primary px-2 py-1 hover:bg-custom-background-90 font-medium rounded"
|
className="text-custom-primary px-2 py-1 hover:bg-custom-background-90 font-medium rounded"
|
||||||
>
|
>
|
||||||
View issue
|
{`View ${isEpic ? "epic" : "issue"}`}
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
{copied ? (
|
{copied ? (
|
||||||
|
|
|
||||||
|
|
@ -24,10 +24,11 @@ type TCalendarQuickAddIssueActions = {
|
||||||
quickAddCallback?: (projectId: string | null | undefined, data: TIssue) => Promise<TIssue | undefined>;
|
quickAddCallback?: (projectId: string | null | undefined, data: TIssue) => Promise<TIssue | undefined>;
|
||||||
addIssuesToView?: (issueIds: string[]) => Promise<any>;
|
addIssuesToView?: (issueIds: string[]) => Promise<any>;
|
||||||
onOpen?: () => void;
|
onOpen?: () => void;
|
||||||
|
isEpic?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CalendarQuickAddIssueActions: FC<TCalendarQuickAddIssueActions> = observer((props) => {
|
export const CalendarQuickAddIssueActions: FC<TCalendarQuickAddIssueActions> = observer((props) => {
|
||||||
const { prePopulatedData, quickAddCallback, addIssuesToView, onOpen } = props;
|
const { prePopulatedData, quickAddCallback, addIssuesToView, onOpen, isEpic = false } = props;
|
||||||
// router
|
// router
|
||||||
const { workspaceSlug, projectId, moduleId } = useParams();
|
const { workspaceSlug, projectId, moduleId } = useParams();
|
||||||
// states
|
// states
|
||||||
|
|
@ -118,15 +119,16 @@ export const CalendarQuickAddIssueActions: FC<TCalendarQuickAddIssueActions> = o
|
||||||
customButton={
|
customButton={
|
||||||
<div className="flex w-full items-center gap-x-[6px] rounded-md px-2 py-1.5 text-custom-text-350 hover:text-custom-text-300">
|
<div className="flex w-full items-center gap-x-[6px] rounded-md px-2 py-1.5 text-custom-text-350 hover:text-custom-text-300">
|
||||||
<PlusIcon className="h-3.5 w-3.5 stroke-2 flex-shrink-0" />
|
<PlusIcon className="h-3.5 w-3.5 stroke-2 flex-shrink-0" />
|
||||||
<span className="text-sm font-medium flex-shrink-0">New issue</span>
|
<span className="text-sm font-medium flex-shrink-0">{`New ${isEpic ? "Epic" : "Issue"}`}</span>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<CustomMenu.MenuItem onClick={handleNewIssue}>New issue</CustomMenu.MenuItem>
|
<CustomMenu.MenuItem onClick={handleNewIssue}>{`New ${isEpic ? "Epic" : "Issue"}`}</CustomMenu.MenuItem>
|
||||||
<CustomMenu.MenuItem onClick={handleExistingIssue}>Add existing issue</CustomMenu.MenuItem>
|
{!isEpic && <CustomMenu.MenuItem onClick={handleExistingIssue}>Add existing issue</CustomMenu.MenuItem>}
|
||||||
</CustomMenu>
|
</CustomMenu>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
isEpic={isEpic}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -98,6 +98,7 @@ export const KanBan: React.FC<IKanBan> = observer((props) => {
|
||||||
groupBy: group_by as GroupByColumnTypes,
|
groupBy: group_by as GroupByColumnTypes,
|
||||||
includeNone: true,
|
includeNone: true,
|
||||||
isWorkspaceLevel: isWorkspaceLevel(storeType),
|
isWorkspaceLevel: isWorkspaceLevel(storeType),
|
||||||
|
isEpic: isEpic,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!list) return null;
|
if (!list) return null;
|
||||||
|
|
|
||||||
|
|
@ -82,7 +82,7 @@ export const KanbanGroup = observer((props: IKanbanGroup) => {
|
||||||
quickAddCallback,
|
quickAddCallback,
|
||||||
scrollableContainerRef,
|
scrollableContainerRef,
|
||||||
handleOnDrop,
|
handleOnDrop,
|
||||||
isEpic =false
|
isEpic = false,
|
||||||
} = props;
|
} = props;
|
||||||
// hooks
|
// hooks
|
||||||
const projectState = useProjectState();
|
const projectState = useProjectState();
|
||||||
|
|
@ -285,6 +285,7 @@ export const KanbanGroup = observer((props: IKanbanGroup) => {
|
||||||
dropErrorMessage={dropErrorMessage}
|
dropErrorMessage={dropErrorMessage}
|
||||||
orderBy={orderBy}
|
orderBy={orderBy}
|
||||||
isDraggingOverColumn={isDraggingOverColumn}
|
isDraggingOverColumn={isDraggingOverColumn}
|
||||||
|
isEpic={isEpic}
|
||||||
/>
|
/>
|
||||||
<KanbanIssueBlocksList
|
<KanbanIssueBlocksList
|
||||||
sub_group_id={sub_group_id}
|
sub_group_id={sub_group_id}
|
||||||
|
|
@ -312,6 +313,7 @@ export const KanbanGroup = observer((props: IKanbanGroup) => {
|
||||||
...(group_by && prePopulateQuickAddData(group_by, sub_group_by, groupId, sub_group_id)),
|
...(group_by && prePopulateQuickAddData(group_by, sub_group_by, groupId, sub_group_id)),
|
||||||
}}
|
}}
|
||||||
quickAddCallback={quickAddCallback}
|
quickAddCallback={quickAddCallback}
|
||||||
|
isEpic={isEpic}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -84,6 +84,7 @@ export const List: React.FC<IList> = observer((props) => {
|
||||||
groupBy: group_by as GroupByColumnTypes,
|
groupBy: group_by as GroupByColumnTypes,
|
||||||
includeNone: true,
|
includeNone: true,
|
||||||
isWorkspaceLevel: isWorkspaceLevel(storeType),
|
isWorkspaceLevel: isWorkspaceLevel(storeType),
|
||||||
|
isEpic: isEpic,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Enable Auto Scroll for Main Kanban
|
// Enable Auto Scroll for Main Kanban
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,13 @@ import { observer } from "mobx-react";
|
||||||
import { TQuickAddIssueForm } from "../root";
|
import { TQuickAddIssueForm } from "../root";
|
||||||
|
|
||||||
export const CalendarQuickAddIssueForm: FC<TQuickAddIssueForm> = observer((props) => {
|
export const CalendarQuickAddIssueForm: FC<TQuickAddIssueForm> = observer((props) => {
|
||||||
const { ref, isOpen, projectDetail, register, onSubmit } = props;
|
const { ref, isOpen, projectDetail, register, onSubmit, isEpic } = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`z-20 w-full transition-all ${isOpen ? "scale-100 opacity-100" : "pointer-events-none scale-95 opacity-0"
|
className={`z-20 w-full transition-all ${
|
||||||
}`}
|
isOpen ? "scale-100 opacity-100" : "pointer-events-none scale-95 opacity-0"
|
||||||
|
}`}
|
||||||
>
|
>
|
||||||
<form
|
<form
|
||||||
ref={ref}
|
ref={ref}
|
||||||
|
|
@ -19,9 +20,9 @@ export const CalendarQuickAddIssueForm: FC<TQuickAddIssueForm> = observer((props
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
placeholder="Issue Title"
|
placeholder={isEpic ? "Epic Title" : "Issue Title"}
|
||||||
{...register("name", {
|
{...register("name", {
|
||||||
required: "Issue title is required.",
|
required: `${isEpic ? "Epic" : "Issue"} title is required.`,
|
||||||
})}
|
})}
|
||||||
className="w-full rounded-md bg-transparent py-1.5 pr-2 text-sm md:text-xs font-medium leading-5 text-custom-text-200 outline-none"
|
className="w-full rounded-md bg-transparent py-1.5 pr-2 text-sm md:text-xs font-medium leading-5 text-custom-text-200 outline-none"
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import { cn } from "@/helpers/common.helper";
|
||||||
import { TQuickAddIssueForm } from "../root";
|
import { TQuickAddIssueForm } from "../root";
|
||||||
|
|
||||||
export const GanttQuickAddIssueForm: FC<TQuickAddIssueForm> = observer((props) => {
|
export const GanttQuickAddIssueForm: FC<TQuickAddIssueForm> = observer((props) => {
|
||||||
const { ref, projectDetail, hasError, register, onSubmit } = props;
|
const { ref, projectDetail, hasError, register, onSubmit, isEpic } = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cn("shadow-custom-shadow-sm", hasError && "border border-red-500/20 bg-red-500/10")}>
|
<div className={cn("shadow-custom-shadow-sm", hasError && "border border-red-500/20 bg-red-500/10")}>
|
||||||
|
|
@ -18,15 +18,15 @@ export const GanttQuickAddIssueForm: FC<TQuickAddIssueForm> = observer((props) =
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
placeholder="Issue Title"
|
placeholder={isEpic ? "Epic Title" : "Issue Title"}
|
||||||
{...register("name", {
|
{...register("name", {
|
||||||
required: "Issue title is required.",
|
required: `${isEpic ? "Epic" : "Issue"} title is required.`,
|
||||||
})}
|
})}
|
||||||
className="w-full rounded-md bg-transparent px-2 py-3 text-sm font-medium leading-5 text-custom-text-200 outline-none"
|
className="w-full rounded-md bg-transparent px-2 py-3 text-sm font-medium leading-5 text-custom-text-200 outline-none"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<div className="px-3 py-2 text-xs bg-custom-background-100 italic text-custom-text-200">{`Press 'Enter' to add another issue`}</div>
|
<div className="px-3 py-2 text-xs bg-custom-background-100 italic text-custom-text-200">{`Press 'Enter' to add another ${isEpic ? "epic" : "issue"}`}</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import { observer } from "mobx-react";
|
||||||
import { TQuickAddIssueForm } from "../root";
|
import { TQuickAddIssueForm } from "../root";
|
||||||
|
|
||||||
export const KanbanQuickAddIssueForm: FC<TQuickAddIssueForm> = observer((props) => {
|
export const KanbanQuickAddIssueForm: FC<TQuickAddIssueForm> = observer((props) => {
|
||||||
const { ref, projectDetail, register, onSubmit } = props;
|
const { ref, projectDetail, register, onSubmit, isEpic } = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="m-1 overflow-hidden rounded shadow-custom-shadow-sm">
|
<div className="m-1 overflow-hidden rounded shadow-custom-shadow-sm">
|
||||||
|
|
@ -12,7 +12,7 @@ export const KanbanQuickAddIssueForm: FC<TQuickAddIssueForm> = observer((props)
|
||||||
<h4 className="text-xs font-medium leading-5 text-custom-text-300">{projectDetail?.identifier ?? "..."}</h4>
|
<h4 className="text-xs font-medium leading-5 text-custom-text-300">{projectDetail?.identifier ?? "..."}</h4>
|
||||||
<input
|
<input
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
placeholder="Issue Title"
|
placeholder={isEpic ? "Epic Title" : "Issue Title"}
|
||||||
{...register("name", {
|
{...register("name", {
|
||||||
required: "Issue title is required.",
|
required: "Issue title is required.",
|
||||||
})}
|
})}
|
||||||
|
|
@ -20,7 +20,7 @@ export const KanbanQuickAddIssueForm: FC<TQuickAddIssueForm> = observer((props)
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<div className="px-3 py-2 text-xs italic text-custom-text-200">{`Press 'Enter' to add another issue`}</div>
|
<div className="px-3 py-2 text-xs italic text-custom-text-200">{`Press 'Enter' to add another ${isEpic ? "epic" : "issue"}`}</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import { observer } from "mobx-react";
|
||||||
import { TQuickAddIssueForm } from "../root";
|
import { TQuickAddIssueForm } from "../root";
|
||||||
|
|
||||||
export const ListQuickAddIssueForm: FC<TQuickAddIssueForm> = observer((props) => {
|
export const ListQuickAddIssueForm: FC<TQuickAddIssueForm> = observer((props) => {
|
||||||
const { ref, projectDetail, register, onSubmit } = props;
|
const { ref, projectDetail, register, onSubmit, isEpic } = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="shadow-custom-shadow-sm">
|
<div className="shadow-custom-shadow-sm">
|
||||||
|
|
@ -17,15 +17,15 @@ export const ListQuickAddIssueForm: FC<TQuickAddIssueForm> = observer((props) =>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
placeholder="Issue Title"
|
placeholder={isEpic ? "Epic Title" : "Issue Title"}
|
||||||
{...register("name", {
|
{...register("name", {
|
||||||
required: "Issue title is required.",
|
required: `${isEpic ? "Epic" : "Issue"} title is required.`,
|
||||||
})}
|
})}
|
||||||
className="w-full rounded-md bg-transparent px-2 py-3 text-sm font-medium leading-5 text-custom-text-200 outline-none"
|
className="w-full rounded-md bg-transparent px-2 py-3 text-sm font-medium leading-5 text-custom-text-200 outline-none"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<div className="px-3 py-2 text-xs italic text-custom-text-200">{`Press 'Enter' to add another issue`}</div>
|
<div className="px-3 py-2 text-xs italic text-custom-text-200">{`Press 'Enter' to add another ${isEpic ? "epic" : "issue"}`}</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import { observer } from "mobx-react";
|
||||||
import { TQuickAddIssueForm } from "../root";
|
import { TQuickAddIssueForm } from "../root";
|
||||||
|
|
||||||
export const SpreadsheetQuickAddIssueForm: FC<TQuickAddIssueForm> = observer((props) => {
|
export const SpreadsheetQuickAddIssueForm: FC<TQuickAddIssueForm> = observer((props) => {
|
||||||
const { ref, projectDetail, register, onSubmit } = props;
|
const { ref, projectDetail, register, onSubmit, isEpic } = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="pb-2">
|
<div className="pb-2">
|
||||||
|
|
@ -16,15 +16,15 @@ export const SpreadsheetQuickAddIssueForm: FC<TQuickAddIssueForm> = observer((pr
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
placeholder="Issue Title"
|
placeholder={isEpic ? "Epic Title" : "Issue Title"}
|
||||||
{...register("name", {
|
{...register("name", {
|
||||||
required: "Issue title is required.",
|
required: `${isEpic ? "Epic" : "Issue"} title is required.`,
|
||||||
})}
|
})}
|
||||||
className="w-full rounded-md bg-transparent py-3 text-sm leading-5 text-custom-text-200 outline-none"
|
className="w-full rounded-md bg-transparent py-3 text-sm leading-5 text-custom-text-200 outline-none"
|
||||||
/>
|
/>
|
||||||
</form>
|
</form>
|
||||||
<p className="ml-3 mt-3 text-xs italic text-custom-text-200">
|
<p className="ml-3 mt-3 text-xs italic text-custom-text-200">
|
||||||
Press {"'"}Enter{"'"} to add another issue
|
{`Press Enter to add another ${isEpic ? "epic" : "issue"}`}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import { useParams, usePathname } from "next/navigation";
|
||||||
import { useForm, UseFormRegister } from "react-hook-form";
|
import { useForm, UseFormRegister } from "react-hook-form";
|
||||||
import { PlusIcon } from "lucide-react";
|
import { PlusIcon } from "lucide-react";
|
||||||
// plane constants
|
// plane constants
|
||||||
import { EIssueLayoutTypes } from "@plane/constants";
|
import { EIssueLayoutTypes, EIssueServiceType } from "@plane/constants";
|
||||||
// types
|
// types
|
||||||
import { IProject, TIssue } from "@plane/types";
|
import { IProject, TIssue } from "@plane/types";
|
||||||
// ui
|
// ui
|
||||||
|
|
@ -30,9 +30,11 @@ export type TQuickAddIssueForm = {
|
||||||
hasError: boolean;
|
hasError: boolean;
|
||||||
register: UseFormRegister<TIssue>;
|
register: UseFormRegister<TIssue>;
|
||||||
onSubmit: () => void;
|
onSubmit: () => void;
|
||||||
|
isEpic: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TQuickAddIssueButton = {
|
export type TQuickAddIssueButton = {
|
||||||
|
isEpic?: boolean;
|
||||||
onClick: () => void;
|
onClick: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -45,6 +47,7 @@ type TQuickAddIssueRoot = {
|
||||||
containerClassName?: string;
|
containerClassName?: string;
|
||||||
setIsQuickAddOpen?: (isOpen: boolean) => void;
|
setIsQuickAddOpen?: (isOpen: boolean) => void;
|
||||||
quickAddCallback?: (projectId: string | null | undefined, data: TIssue) => Promise<TIssue | undefined>;
|
quickAddCallback?: (projectId: string | null | undefined, data: TIssue) => Promise<TIssue | undefined>;
|
||||||
|
isEpic?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const defaultValues: Partial<TIssue> = {
|
const defaultValues: Partial<TIssue> = {
|
||||||
|
|
@ -61,6 +64,7 @@ export const QuickAddIssueRoot: FC<TQuickAddIssueRoot> = observer((props) => {
|
||||||
containerClassName = "",
|
containerClassName = "",
|
||||||
setIsQuickAddOpen,
|
setIsQuickAddOpen,
|
||||||
quickAddCallback,
|
quickAddCallback,
|
||||||
|
isEpic = false,
|
||||||
} = props;
|
} = props;
|
||||||
// router
|
// router
|
||||||
const { workspaceSlug, projectId } = useParams();
|
const { workspaceSlug, projectId } = useParams();
|
||||||
|
|
@ -109,15 +113,16 @@ export const QuickAddIssueRoot: FC<TQuickAddIssueRoot> = observer((props) => {
|
||||||
if (quickAddCallback) {
|
if (quickAddCallback) {
|
||||||
const quickAddPromise = quickAddCallback(projectId.toString(), { ...payload });
|
const quickAddPromise = quickAddCallback(projectId.toString(), { ...payload });
|
||||||
setPromiseToast<any>(quickAddPromise, {
|
setPromiseToast<any>(quickAddPromise, {
|
||||||
loading: "Adding issue...",
|
loading: `Adding ${isEpic ? "epic" : "issue"}...`,
|
||||||
success: {
|
success: {
|
||||||
title: "Success!",
|
title: "Success!",
|
||||||
message: () => "Issue created successfully.",
|
message: () => `${isEpic ? "Epic" : "Issue"} created successfully.`,
|
||||||
actionItems: (data) => (
|
actionItems: (data) => (
|
||||||
<CreateIssueToastActionItems
|
<CreateIssueToastActionItems
|
||||||
workspaceSlug={workspaceSlug.toString()}
|
workspaceSlug={workspaceSlug.toString()}
|
||||||
projectId={projectId.toString()}
|
projectId={projectId.toString()}
|
||||||
issueId={data.id}
|
issueId={data.id}
|
||||||
|
isEpic={isEpic}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|
@ -165,10 +170,11 @@ export const QuickAddIssueRoot: FC<TQuickAddIssueRoot> = observer((props) => {
|
||||||
register={register}
|
register={register}
|
||||||
onSubmit={handleSubmit(onSubmitHandler)}
|
onSubmit={handleSubmit(onSubmitHandler)}
|
||||||
onClose={() => handleIsOpen(false)}
|
onClose={() => handleIsOpen(false)}
|
||||||
|
isEpic={isEpic}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
{QuickAddButton && <QuickAddButton onClick={() => handleIsOpen(true)} />}
|
{QuickAddButton && <QuickAddButton isEpic={isEpic} onClick={() => handleIsOpen(true)} />}
|
||||||
{customQuickAddButton && <>{customQuickAddButton}</>}
|
{customQuickAddButton && <>{customQuickAddButton}</>}
|
||||||
{!QuickAddButton && !customQuickAddButton && (
|
{!QuickAddButton && !customQuickAddButton && (
|
||||||
<div
|
<div
|
||||||
|
|
@ -176,7 +182,7 @@ export const QuickAddIssueRoot: FC<TQuickAddIssueRoot> = observer((props) => {
|
||||||
onClick={() => handleIsOpen(true)}
|
onClick={() => handleIsOpen(true)}
|
||||||
>
|
>
|
||||||
<PlusIcon className="h-3.5 w-3.5 stroke-2" />
|
<PlusIcon className="h-3.5 w-3.5 stroke-2" />
|
||||||
<span className="text-sm font-medium">New Issue</span>
|
<span className="text-sm font-medium">{`New ${isEpic ? "Epic" : "Issue"}`}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
|
|
||||||
|
|
@ -68,6 +68,7 @@ type TGetGroupByColumns = {
|
||||||
groupBy: GroupByColumnTypes | null;
|
groupBy: GroupByColumnTypes | null;
|
||||||
includeNone: boolean;
|
includeNone: boolean;
|
||||||
isWorkspaceLevel: boolean;
|
isWorkspaceLevel: boolean;
|
||||||
|
isEpic?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
// NOTE: Type of groupBy is different compared to what's being passed from the components.
|
// NOTE: Type of groupBy is different compared to what's being passed from the components.
|
||||||
|
|
@ -77,13 +78,14 @@ export const getGroupByColumns = ({
|
||||||
groupBy,
|
groupBy,
|
||||||
includeNone,
|
includeNone,
|
||||||
isWorkspaceLevel,
|
isWorkspaceLevel,
|
||||||
|
isEpic = false,
|
||||||
}: TGetGroupByColumns): IGroupByColumn[] | undefined => {
|
}: TGetGroupByColumns): IGroupByColumn[] | undefined => {
|
||||||
// If no groupBy is specified and includeNone is true, return "All Issues" group
|
// If no groupBy is specified and includeNone is true, return "All Issues" group
|
||||||
if (!groupBy && includeNone) {
|
if (!groupBy && includeNone) {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
id: "All Issues",
|
id: "All Issues",
|
||||||
name: "All Issues",
|
name: isEpic ? "All Epics" : "All Issues",
|
||||||
payload: {},
|
payload: {},
|
||||||
icon: undefined,
|
icon: undefined,
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,6 @@ import { IIssueDetail } from "@/store/issue/issue-details/root.store";
|
||||||
export const useIssueDetail = (serviceType: TIssueServiceType = EIssueServiceType.ISSUES): IIssueDetail => {
|
export const useIssueDetail = (serviceType: TIssueServiceType = EIssueServiceType.ISSUES): IIssueDetail => {
|
||||||
const context = useContext(StoreContext);
|
const context = useContext(StoreContext);
|
||||||
if (context === undefined) throw new Error("useIssueDetail must be used within StoreProvider");
|
if (context === undefined) throw new Error("useIssueDetail must be used within StoreProvider");
|
||||||
if (serviceType === EIssueServiceType.EPICS) return context.epic.issueDetail;
|
if (serviceType === EIssueServiceType.EPICS) return context.issue.epicDetail;
|
||||||
else return context.issue.issueDetail;
|
else return context.issue.issueDetail;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import update from "lodash/update";
|
||||||
import { action, makeObservable, observable, runInAction } from "mobx";
|
import { action, makeObservable, observable, runInAction } from "mobx";
|
||||||
import { computedFn } from "mobx-utils";
|
import { computedFn } from "mobx-utils";
|
||||||
// types
|
// types
|
||||||
import { TIssue, TIssueServiceType } from "@plane/types";
|
import { TIssue } from "@plane/types";
|
||||||
// helpers
|
// helpers
|
||||||
import { getCurrentDateTimeInISO } from "@/helpers/date-time.helper";
|
import { getCurrentDateTimeInISO } from "@/helpers/date-time.helper";
|
||||||
// services
|
// services
|
||||||
|
|
@ -30,7 +30,7 @@ export class IssueStore implements IIssueStore {
|
||||||
// service
|
// service
|
||||||
issueService;
|
issueService;
|
||||||
|
|
||||||
constructor(serviceType: TIssueServiceType) {
|
constructor() {
|
||||||
makeObservable(this, {
|
makeObservable(this, {
|
||||||
// observable
|
// observable
|
||||||
issuesMap: observable,
|
issuesMap: observable,
|
||||||
|
|
@ -39,8 +39,7 @@ export class IssueStore implements IIssueStore {
|
||||||
updateIssue: action,
|
updateIssue: action,
|
||||||
removeIssue: action,
|
removeIssue: action,
|
||||||
});
|
});
|
||||||
|
this.issueService = new IssueService();
|
||||||
this.issueService = new IssueService(serviceType);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// actions
|
// actions
|
||||||
|
|
@ -85,7 +84,10 @@ export class IssueStore implements IIssueStore {
|
||||||
set(this.issuesMap, [issueId, key], issue[key as keyof TIssue]);
|
set(this.issuesMap, [issueId, key], issue[key as keyof TIssue]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
updatePersistentLayer(issueId);
|
|
||||||
|
if (!this.issuesMap[issueId]?.is_epic) {
|
||||||
|
updatePersistentLayer(issueId);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -67,6 +67,7 @@ export interface IIssueRootStore {
|
||||||
issues: IIssueStore;
|
issues: IIssueStore;
|
||||||
|
|
||||||
issueDetail: IIssueDetail;
|
issueDetail: IIssueDetail;
|
||||||
|
epicDetail: IIssueDetail;
|
||||||
|
|
||||||
workspaceIssuesFilter: IWorkspaceIssuesFilter;
|
workspaceIssuesFilter: IWorkspaceIssuesFilter;
|
||||||
workspaceIssues: IWorkspaceIssues;
|
workspaceIssues: IWorkspaceIssues;
|
||||||
|
|
@ -134,6 +135,7 @@ export class IssueRootStore implements IIssueRootStore {
|
||||||
issues: IIssueStore;
|
issues: IIssueStore;
|
||||||
|
|
||||||
issueDetail: IIssueDetail;
|
issueDetail: IIssueDetail;
|
||||||
|
epicDetail: IIssueDetail;
|
||||||
|
|
||||||
workspaceIssuesFilter: IWorkspaceIssuesFilter;
|
workspaceIssuesFilter: IWorkspaceIssuesFilter;
|
||||||
workspaceIssues: IWorkspaceIssues;
|
workspaceIssues: IWorkspaceIssues;
|
||||||
|
|
@ -221,9 +223,10 @@ export class IssueRootStore implements IIssueRootStore {
|
||||||
if (!isEmpty(rootStore?.cycle?.cycleMap)) this.cycleMap = rootStore?.cycle?.cycleMap;
|
if (!isEmpty(rootStore?.cycle?.cycleMap)) this.cycleMap = rootStore?.cycle?.cycleMap;
|
||||||
});
|
});
|
||||||
|
|
||||||
this.issues = new IssueStore(this.serviceType);
|
this.issues = new IssueStore();
|
||||||
|
|
||||||
this.issueDetail = new IssueDetail(this, this.serviceType);
|
this.issueDetail = new IssueDetail(this, EIssueServiceType.ISSUES);
|
||||||
|
this.epicDetail = new IssueDetail(this, EIssueServiceType.EPICS);
|
||||||
|
|
||||||
this.workspaceIssuesFilter = new WorkspaceIssuesFilter(this);
|
this.workspaceIssuesFilter = new WorkspaceIssuesFilter(this);
|
||||||
this.workspaceIssues = new WorkspaceIssues(this, this.workspaceIssuesFilter);
|
this.workspaceIssues = new WorkspaceIssues(this, this.workspaceIssuesFilter);
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,6 @@ export class CoreRootStore {
|
||||||
projectView: IProjectViewStore;
|
projectView: IProjectViewStore;
|
||||||
globalView: IGlobalViewStore;
|
globalView: IGlobalViewStore;
|
||||||
issue: IIssueRootStore;
|
issue: IIssueRootStore;
|
||||||
epic: IIssueRootStore;
|
|
||||||
state: IStateStore;
|
state: IStateStore;
|
||||||
label: ILabelStore;
|
label: ILabelStore;
|
||||||
dashboard: IDashboardStore;
|
dashboard: IDashboardStore;
|
||||||
|
|
@ -77,7 +76,6 @@ export class CoreRootStore {
|
||||||
this.projectView = new ProjectViewStore(this);
|
this.projectView = new ProjectViewStore(this);
|
||||||
this.globalView = new GlobalViewStore(this);
|
this.globalView = new GlobalViewStore(this);
|
||||||
this.issue = new IssueRootStore(this as unknown as RootStore);
|
this.issue = new IssueRootStore(this as unknown as RootStore);
|
||||||
this.epic = new IssueRootStore(this as unknown as RootStore, EIssueServiceType.EPICS);
|
|
||||||
this.state = new StateStore(this as unknown as RootStore);
|
this.state = new StateStore(this as unknown as RootStore);
|
||||||
this.label = new LabelStore(this);
|
this.label = new LabelStore(this);
|
||||||
this.dashboard = new DashboardStore(this);
|
this.dashboard = new DashboardStore(this);
|
||||||
|
|
@ -109,7 +107,6 @@ export class CoreRootStore {
|
||||||
this.projectView = new ProjectViewStore(this);
|
this.projectView = new ProjectViewStore(this);
|
||||||
this.globalView = new GlobalViewStore(this);
|
this.globalView = new GlobalViewStore(this);
|
||||||
this.issue = new IssueRootStore(this as unknown as RootStore);
|
this.issue = new IssueRootStore(this as unknown as RootStore);
|
||||||
this.epic = new IssueRootStore(this as unknown as RootStore, EIssueServiceType.EPICS);
|
|
||||||
this.state = new StateStore(this as unknown as RootStore);
|
this.state = new StateStore(this as unknown as RootStore);
|
||||||
this.label = new LabelStore(this);
|
this.label = new LabelStore(this);
|
||||||
this.dashboard = new DashboardStore(this);
|
this.dashboard = new DashboardStore(this);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue