[WEB-1921] fix: issue widgets modal and code refactor (#5106)
* fix: celery fix * chore: issue relationkey and issueCrudOperation state added to issueDetail store * chore: moved issue detail widget modal to root * chore: code refactor * chore: default open widget updated
This commit is contained in:
parent
24973c1386
commit
dd3b0f6a3f
16 changed files with 322 additions and 337 deletions
|
|
@ -276,8 +276,6 @@ CELERY_IMPORTS = (
|
||||||
"plane.bgtasks.api_logs_task",
|
"plane.bgtasks.api_logs_task",
|
||||||
# management tasks
|
# management tasks
|
||||||
"plane.bgtasks.dummy_data_task",
|
"plane.bgtasks.dummy_data_task",
|
||||||
# backfill tasks
|
|
||||||
"plane.db.backfills.backfill_0070_page_versions",
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Sentry Settings
|
# Sentry Settings
|
||||||
|
|
|
||||||
|
|
@ -22,8 +22,6 @@ export const IssueDetailWidgetActionButtons: FC<Props> = (props) => {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center flex-wrap gap-2">
|
<div className="flex items-center flex-wrap gap-2">
|
||||||
<SubIssuesActionButton
|
<SubIssuesActionButton
|
||||||
workspaceSlug={workspaceSlug}
|
|
||||||
projectId={projectId}
|
|
||||||
issueId={issueId}
|
issueId={issueId}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
customButton={
|
customButton={
|
||||||
|
|
@ -34,8 +32,6 @@ export const IssueDetailWidgetActionButtons: FC<Props> = (props) => {
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<RelationActionButton
|
<RelationActionButton
|
||||||
workspaceSlug={workspaceSlug}
|
|
||||||
projectId={projectId}
|
|
||||||
issueId={issueId}
|
issueId={issueId}
|
||||||
customButton={
|
customButton={
|
||||||
<IssueDetailWidgetButton
|
<IssueDetailWidgetButton
|
||||||
|
|
@ -45,9 +41,6 @@ export const IssueDetailWidgetActionButtons: FC<Props> = (props) => {
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<IssueLinksActionButton
|
<IssueLinksActionButton
|
||||||
workspaceSlug={workspaceSlug}
|
|
||||||
projectId={projectId}
|
|
||||||
issueId={issueId}
|
|
||||||
customButton={
|
customButton={
|
||||||
<IssueDetailWidgetButton
|
<IssueDetailWidgetButton
|
||||||
title="Add Links"
|
title="Add Links"
|
||||||
|
|
|
||||||
|
|
@ -20,15 +20,15 @@ type Props = {
|
||||||
export const AttachmentsCollapsible: FC<Props> = observer((props) => {
|
export const AttachmentsCollapsible: FC<Props> = observer((props) => {
|
||||||
const { workspaceSlug, projectId, issueId, disabled = false } = props;
|
const { workspaceSlug, projectId, issueId, disabled = false } = props;
|
||||||
// store hooks
|
// store hooks
|
||||||
const { activeIssueDetailWidgets, toggleActiveIssueDetailWidget } = useIssueDetail();
|
const { openWidgets, toggleOpenWidget } = useIssueDetail();
|
||||||
|
|
||||||
// derived values
|
// derived values
|
||||||
const isCollapsibleOpen = activeIssueDetailWidgets.includes("attachments");
|
const isCollapsibleOpen = openWidgets.includes("attachments");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Collapsible
|
<Collapsible
|
||||||
isOpen={isCollapsibleOpen}
|
isOpen={isCollapsibleOpen}
|
||||||
onToggle={() => toggleActiveIssueDetailWidget("attachments")}
|
onToggle={() => toggleOpenWidget("attachments")}
|
||||||
title={
|
title={
|
||||||
<IssueAttachmentsCollapsibleTitle
|
<IssueAttachmentsCollapsibleTitle
|
||||||
isOpen={isCollapsibleOpen}
|
isOpen={isCollapsibleOpen}
|
||||||
|
|
|
||||||
|
|
@ -6,3 +6,4 @@ export * from "./sub-issues";
|
||||||
export * from "./widget-button";
|
export * from "./widget-button";
|
||||||
export * from "./issue-detail-widget-collapsibles";
|
export * from "./issue-detail-widget-collapsibles";
|
||||||
export * from "./action-buttons";
|
export * from "./action-buttons";
|
||||||
|
export * from "./issue-detail-widget-modals";
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,178 @@
|
||||||
|
import React, { FC } from "react";
|
||||||
|
import { observer } from "mobx-react";
|
||||||
|
import { ISearchIssueResponse, TIssue } from "@plane/types";
|
||||||
|
import { setToast, TOAST_TYPE } from "@plane/ui";
|
||||||
|
// components
|
||||||
|
import { ExistingIssuesListModal } from "@/components/core";
|
||||||
|
import { CreateUpdateIssueModal } from "@/components/issues/issue-modal";
|
||||||
|
// hooks
|
||||||
|
import { useIssueDetail } from "@/hooks/store";
|
||||||
|
|
||||||
|
import { IssueLinkCreateUpdateModal } from "../issue-detail/links/create-update-link-modal";
|
||||||
|
// helpers
|
||||||
|
import { useLinkOperations } from "./links/helper";
|
||||||
|
import { useSubIssueOperations } from "./sub-issues/helper";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
workspaceSlug: string;
|
||||||
|
projectId: string;
|
||||||
|
issueId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const IssueDetailWidgetModals: FC<Props> = observer((props) => {
|
||||||
|
const { workspaceSlug, projectId, issueId } = props;
|
||||||
|
// store hooks
|
||||||
|
const {
|
||||||
|
isIssueLinkModalOpen,
|
||||||
|
toggleIssueLinkModal: toggleIssueLinkModalStore,
|
||||||
|
isCreateIssueModalOpen,
|
||||||
|
toggleCreateIssueModal,
|
||||||
|
isSubIssuesModalOpen,
|
||||||
|
toggleSubIssuesModal,
|
||||||
|
relationKey,
|
||||||
|
isRelationModalOpen,
|
||||||
|
setRelationKey,
|
||||||
|
setLastWidgetAction,
|
||||||
|
toggleRelationModal,
|
||||||
|
createRelation,
|
||||||
|
issueCrudOperationState,
|
||||||
|
setIssueCrudOperationState,
|
||||||
|
} = useIssueDetail();
|
||||||
|
|
||||||
|
// helper hooks
|
||||||
|
const subIssueOperations = useSubIssueOperations();
|
||||||
|
const handleLinkOperations = useLinkOperations(workspaceSlug, projectId, issueId);
|
||||||
|
|
||||||
|
// handlers
|
||||||
|
const handleIssueCrudState = (
|
||||||
|
key: "create" | "existing",
|
||||||
|
_parentIssueId: string | null,
|
||||||
|
issue: TIssue | null = null
|
||||||
|
) => {
|
||||||
|
setIssueCrudOperationState({
|
||||||
|
...issueCrudOperationState,
|
||||||
|
[key]: {
|
||||||
|
toggle: !issueCrudOperationState[key].toggle,
|
||||||
|
parentIssueId: _parentIssueId,
|
||||||
|
issue: issue,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleExistingIssuesModalClose = () => {
|
||||||
|
handleIssueCrudState("existing", null, null);
|
||||||
|
setLastWidgetAction("sub-issues");
|
||||||
|
toggleSubIssuesModal(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleExistingIssuesModalOnSubmit = async (_issue: ISearchIssueResponse[]) =>
|
||||||
|
subIssueOperations.addSubIssue(
|
||||||
|
workspaceSlug,
|
||||||
|
projectId,
|
||||||
|
issueId,
|
||||||
|
_issue.map((issue) => issue.id)
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleCreateUpdateModalClose = () => {
|
||||||
|
handleIssueCrudState("create", null, null);
|
||||||
|
toggleCreateIssueModal(false);
|
||||||
|
setLastWidgetAction("sub-issues");
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCreateUpdateModalOnSubmit = async (_issue: TIssue) => {
|
||||||
|
if (_issue.parent_id) {
|
||||||
|
await subIssueOperations.addSubIssue(workspaceSlug, projectId, issueId, [_issue.id]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleIssueLinkModalOnClose = () => {
|
||||||
|
toggleIssueLinkModalStore(false);
|
||||||
|
setLastWidgetAction("links");
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRelationOnClose = () => {
|
||||||
|
setRelationKey(null);
|
||||||
|
toggleRelationModal(null, null);
|
||||||
|
setLastWidgetAction("relations");
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleExistingIssueModalOnSubmit = async (data: ISearchIssueResponse[]) => {
|
||||||
|
if (!relationKey) return;
|
||||||
|
if (data.length === 0) {
|
||||||
|
setToast({
|
||||||
|
type: TOAST_TYPE.ERROR,
|
||||||
|
title: "Error!",
|
||||||
|
message: "Please select at least one issue.",
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await createRelation(
|
||||||
|
workspaceSlug,
|
||||||
|
projectId,
|
||||||
|
issueId,
|
||||||
|
relationKey,
|
||||||
|
data.map((i) => i.id)
|
||||||
|
);
|
||||||
|
|
||||||
|
toggleRelationModal(null, null);
|
||||||
|
};
|
||||||
|
|
||||||
|
// helpers
|
||||||
|
const createUpdateModalData = { parent_id: issueCrudOperationState?.create?.parentIssueId };
|
||||||
|
|
||||||
|
const existingIssuesModalSearchParams = {
|
||||||
|
sub_issue: true,
|
||||||
|
issue_id: issueCrudOperationState?.existing?.parentIssueId,
|
||||||
|
};
|
||||||
|
|
||||||
|
// render conditions
|
||||||
|
const shouldRenderExistingIssuesModal =
|
||||||
|
issueCrudOperationState?.existing?.toggle &&
|
||||||
|
issueCrudOperationState?.existing?.parentIssueId &&
|
||||||
|
isSubIssuesModalOpen;
|
||||||
|
|
||||||
|
const shouldRenderCreateUpdateModal =
|
||||||
|
issueCrudOperationState?.create?.toggle && issueCrudOperationState?.create?.parentIssueId && isCreateIssueModalOpen;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<IssueLinkCreateUpdateModal
|
||||||
|
isModalOpen={isIssueLinkModalOpen}
|
||||||
|
handleOnClose={handleIssueLinkModalOnClose}
|
||||||
|
linkOperations={handleLinkOperations}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{shouldRenderCreateUpdateModal && (
|
||||||
|
<CreateUpdateIssueModal
|
||||||
|
isOpen={issueCrudOperationState?.create?.toggle}
|
||||||
|
data={createUpdateModalData}
|
||||||
|
onClose={handleCreateUpdateModalClose}
|
||||||
|
onSubmit={handleCreateUpdateModalOnSubmit}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{shouldRenderExistingIssuesModal && (
|
||||||
|
<ExistingIssuesListModal
|
||||||
|
workspaceSlug={workspaceSlug}
|
||||||
|
projectId={projectId}
|
||||||
|
isOpen={issueCrudOperationState?.existing?.toggle}
|
||||||
|
handleClose={handleExistingIssuesModalClose}
|
||||||
|
searchParams={existingIssuesModalSearchParams}
|
||||||
|
handleOnSubmit={handleExistingIssuesModalOnSubmit}
|
||||||
|
workspaceLevelToggle
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<ExistingIssuesListModal
|
||||||
|
workspaceSlug={workspaceSlug}
|
||||||
|
projectId={projectId}
|
||||||
|
isOpen={isRelationModalOpen?.issueId === issueId && isRelationModalOpen?.relationType === relationKey}
|
||||||
|
handleClose={handleRelationOnClose}
|
||||||
|
searchParams={{ issue_relation: true, issue_id: issueId }}
|
||||||
|
handleOnSubmit={handleExistingIssueModalOnSubmit}
|
||||||
|
workspaceLevelToggle
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
@ -1,63 +1,30 @@
|
||||||
"use client";
|
"use client";
|
||||||
import React, { FC, useCallback, useState } from "react";
|
import React, { FC } from "react";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { Plus } from "lucide-react";
|
import { Plus } from "lucide-react";
|
||||||
// hooks
|
// hooks
|
||||||
import { useIssueDetail } from "@/hooks/store";
|
import { useIssueDetail } from "@/hooks/store";
|
||||||
// components
|
|
||||||
import { IssueLinkCreateUpdateModal } from "../../issue-detail/links/create-update-link-modal";
|
|
||||||
// helper
|
|
||||||
import { useLinkOperations } from "./helper";
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
workspaceSlug: string;
|
|
||||||
projectId: string;
|
|
||||||
issueId: string;
|
|
||||||
customButton?: React.ReactNode;
|
customButton?: React.ReactNode;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const IssueLinksActionButton: FC<Props> = observer((props) => {
|
export const IssueLinksActionButton: FC<Props> = observer((props) => {
|
||||||
const { workspaceSlug, projectId, issueId, customButton, disabled = false } = props;
|
const { customButton, disabled = false } = props;
|
||||||
// state
|
|
||||||
const [isIssueLinkModal, setIsIssueLinkModal] = useState(false);
|
|
||||||
|
|
||||||
// store hooks
|
// store hooks
|
||||||
const { toggleIssueLinkModal: toggleIssueLinkModalStore, setLastWidgetAction } = useIssueDetail();
|
const { toggleIssueLinkModal } = useIssueDetail();
|
||||||
|
|
||||||
// helper
|
|
||||||
const handleLinkOperations = useLinkOperations(workspaceSlug, projectId, issueId);
|
|
||||||
|
|
||||||
// handler
|
|
||||||
const toggleIssueLinkModal = useCallback(
|
|
||||||
(modalToggle: boolean) => {
|
|
||||||
toggleIssueLinkModalStore(modalToggle);
|
|
||||||
setIsIssueLinkModal(modalToggle);
|
|
||||||
},
|
|
||||||
[toggleIssueLinkModalStore]
|
|
||||||
);
|
|
||||||
|
|
||||||
|
// handlers
|
||||||
const handleOnClick = (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
const handleOnClick = (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
toggleIssueLinkModal(true);
|
toggleIssueLinkModal(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleOnClose = () => {
|
|
||||||
toggleIssueLinkModal(false);
|
|
||||||
setLastWidgetAction("links");
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
|
||||||
<IssueLinkCreateUpdateModal
|
|
||||||
isModalOpen={isIssueLinkModal}
|
|
||||||
handleOnClose={handleOnClose}
|
|
||||||
linkOperations={handleLinkOperations}
|
|
||||||
/>
|
|
||||||
<button type="button" onClick={handleOnClick} disabled={disabled}>
|
<button type="button" onClick={handleOnClick} disabled={disabled}>
|
||||||
{customButton ? customButton : <Plus className="h-4 w-4" />}
|
{customButton ? customButton : <Plus className="h-4 w-4" />}
|
||||||
</button>
|
</button>
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -17,24 +17,16 @@ type Props = {
|
||||||
export const LinksCollapsible: FC<Props> = observer((props) => {
|
export const LinksCollapsible: FC<Props> = observer((props) => {
|
||||||
const { workspaceSlug, projectId, issueId, disabled = false } = props;
|
const { workspaceSlug, projectId, issueId, disabled = false } = props;
|
||||||
// store hooks
|
// store hooks
|
||||||
const { activeIssueDetailWidgets, toggleActiveIssueDetailWidget } = useIssueDetail();
|
const { openWidgets, toggleOpenWidget } = useIssueDetail();
|
||||||
|
|
||||||
// derived values
|
// derived values
|
||||||
const isCollapsibleOpen = activeIssueDetailWidgets.includes("links");
|
const isCollapsibleOpen = openWidgets.includes("links");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Collapsible
|
<Collapsible
|
||||||
isOpen={isCollapsibleOpen}
|
isOpen={isCollapsibleOpen}
|
||||||
onToggle={() => toggleActiveIssueDetailWidget("links")}
|
onToggle={() => toggleOpenWidget("links")}
|
||||||
title={
|
title={<IssueLinksCollapsibleTitle isOpen={isCollapsibleOpen} issueId={issueId} disabled={disabled} />}
|
||||||
<IssueLinksCollapsibleTitle
|
|
||||||
isOpen={isCollapsibleOpen}
|
|
||||||
workspaceSlug={workspaceSlug}
|
|
||||||
projectId={projectId}
|
|
||||||
issueId={issueId}
|
|
||||||
disabled={disabled}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
<IssueLinksCollapsibleContent
|
<IssueLinksCollapsibleContent
|
||||||
workspaceSlug={workspaceSlug}
|
workspaceSlug={workspaceSlug}
|
||||||
|
|
|
||||||
|
|
@ -9,14 +9,12 @@ import { useIssueDetail } from "@/hooks/store";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
workspaceSlug: string;
|
|
||||||
projectId: string;
|
|
||||||
issueId: string;
|
issueId: string;
|
||||||
disabled: boolean;
|
disabled: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const IssueLinksCollapsibleTitle: FC<Props> = observer((props) => {
|
export const IssueLinksCollapsibleTitle: FC<Props> = observer((props) => {
|
||||||
const { isOpen, workspaceSlug, projectId, issueId, disabled } = props;
|
const { isOpen, issueId, disabled } = props;
|
||||||
// store hooks
|
// store hooks
|
||||||
const {
|
const {
|
||||||
issue: { getIssueById },
|
issue: { getIssueById },
|
||||||
|
|
@ -42,14 +40,7 @@ export const IssueLinksCollapsibleTitle: FC<Props> = observer((props) => {
|
||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
title="Links"
|
title="Links"
|
||||||
indicatorElement={indicatorElement}
|
indicatorElement={indicatorElement}
|
||||||
actionItemElement={
|
actionItemElement={<IssueLinksActionButton disabled={disabled} />}
|
||||||
<IssueLinksActionButton
|
|
||||||
workspaceSlug={workspaceSlug}
|
|
||||||
projectId={projectId}
|
|
||||||
issueId={issueId}
|
|
||||||
disabled={disabled}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,30 +1,24 @@
|
||||||
"use client";
|
"use client";
|
||||||
import React, { FC, useState } from "react";
|
import React, { FC } from "react";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { Plus } from "lucide-react";
|
import { Plus } from "lucide-react";
|
||||||
import { ISearchIssueResponse, TIssueRelationTypes } from "@plane/types";
|
import { TIssueRelationTypes } from "@plane/types";
|
||||||
import { CustomMenu, TOAST_TYPE, setToast } from "@plane/ui";
|
import { CustomMenu } from "@plane/ui";
|
||||||
// components
|
|
||||||
import { ExistingIssuesListModal } from "@/components/core";
|
|
||||||
// hooks
|
// hooks
|
||||||
import { useIssueDetail } from "@/hooks/store";
|
import { useIssueDetail } from "@/hooks/store";
|
||||||
// helper
|
// helper
|
||||||
import { ISSUE_RELATION_OPTIONS } from "./helper";
|
import { ISSUE_RELATION_OPTIONS } from "./helper";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
workspaceSlug: string;
|
|
||||||
projectId: string;
|
|
||||||
issueId: string;
|
issueId: string;
|
||||||
customButton?: React.ReactNode;
|
customButton?: React.ReactNode;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const RelationActionButton: FC<Props> = observer((props) => {
|
export const RelationActionButton: FC<Props> = observer((props) => {
|
||||||
const { workspaceSlug, projectId, customButton, issueId, disabled = false } = props;
|
const { customButton, issueId, disabled = false } = props;
|
||||||
// state
|
|
||||||
const [relationKey, setRelationKey] = useState<TIssueRelationTypes | null>(null);
|
|
||||||
// store hooks
|
// store hooks
|
||||||
const { createRelation, isRelationModalOpen, toggleRelationModal, setLastWidgetAction } = useIssueDetail();
|
const { toggleRelationModal, setRelationKey } = useIssueDetail();
|
||||||
|
|
||||||
// handlers
|
// handlers
|
||||||
const handleOnClick = (relationKey: TIssueRelationTypes) => {
|
const handleOnClick = (relationKey: TIssueRelationTypes) => {
|
||||||
|
|
@ -32,40 +26,10 @@ export const RelationActionButton: FC<Props> = observer((props) => {
|
||||||
toggleRelationModal(issueId, relationKey);
|
toggleRelationModal(issueId, relationKey);
|
||||||
};
|
};
|
||||||
|
|
||||||
// submit handler
|
|
||||||
const onSubmit = async (data: ISearchIssueResponse[]) => {
|
|
||||||
if (!relationKey) return;
|
|
||||||
if (data.length === 0) {
|
|
||||||
setToast({
|
|
||||||
type: TOAST_TYPE.ERROR,
|
|
||||||
title: "Error!",
|
|
||||||
message: "Please select at least one issue.",
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await createRelation(
|
|
||||||
workspaceSlug,
|
|
||||||
projectId,
|
|
||||||
issueId,
|
|
||||||
relationKey,
|
|
||||||
data.map((i) => i.id)
|
|
||||||
);
|
|
||||||
|
|
||||||
toggleRelationModal(null, null);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleOnClose = () => {
|
|
||||||
setRelationKey(null);
|
|
||||||
toggleRelationModal(null, null);
|
|
||||||
setLastWidgetAction("relations");
|
|
||||||
};
|
|
||||||
|
|
||||||
// button element
|
// button element
|
||||||
const customButtonElement = customButton ? <>{customButton}</> : <Plus className="h-4 w-4" />;
|
const customButtonElement = customButton ? <>{customButton}</> : <Plus className="h-4 w-4" />;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
|
||||||
<CustomMenu customButton={customButtonElement} placement="bottom-start" disabled={disabled} closeOnSelect>
|
<CustomMenu customButton={customButtonElement} placement="bottom-start" disabled={disabled} closeOnSelect>
|
||||||
{ISSUE_RELATION_OPTIONS.map((item, index) => (
|
{ISSUE_RELATION_OPTIONS.map((item, index) => (
|
||||||
<CustomMenu.MenuItem
|
<CustomMenu.MenuItem
|
||||||
|
|
@ -83,16 +47,5 @@ export const RelationActionButton: FC<Props> = observer((props) => {
|
||||||
</CustomMenu.MenuItem>
|
</CustomMenu.MenuItem>
|
||||||
))}
|
))}
|
||||||
</CustomMenu>
|
</CustomMenu>
|
||||||
|
|
||||||
<ExistingIssuesListModal
|
|
||||||
workspaceSlug={workspaceSlug}
|
|
||||||
projectId={projectId}
|
|
||||||
isOpen={isRelationModalOpen?.issueId === issueId && isRelationModalOpen?.relationType === relationKey}
|
|
||||||
handleClose={handleOnClose}
|
|
||||||
searchParams={{ issue_relation: true, issue_id: issueId }}
|
|
||||||
handleOnSubmit={onSubmit}
|
|
||||||
workspaceLevelToggle
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -17,24 +17,16 @@ type Props = {
|
||||||
export const RelationsCollapsible: FC<Props> = observer((props) => {
|
export const RelationsCollapsible: FC<Props> = observer((props) => {
|
||||||
const { workspaceSlug, projectId, issueId, disabled = false } = props;
|
const { workspaceSlug, projectId, issueId, disabled = false } = props;
|
||||||
// store hooks
|
// store hooks
|
||||||
const { activeIssueDetailWidgets, toggleActiveIssueDetailWidget } = useIssueDetail();
|
const { openWidgets, toggleOpenWidget } = useIssueDetail();
|
||||||
|
|
||||||
// derived values
|
// derived values
|
||||||
const isCollapsibleOpen = activeIssueDetailWidgets.includes("relations");
|
const isCollapsibleOpen = openWidgets.includes("relations");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Collapsible
|
<Collapsible
|
||||||
isOpen={isCollapsibleOpen}
|
isOpen={isCollapsibleOpen}
|
||||||
onToggle={() => toggleActiveIssueDetailWidget("relations")}
|
onToggle={() => toggleOpenWidget("relations")}
|
||||||
title={
|
title={<RelationsCollapsibleTitle isOpen={isCollapsibleOpen} issueId={issueId} disabled={disabled} />}
|
||||||
<RelationsCollapsibleTitle
|
|
||||||
isOpen={isCollapsibleOpen}
|
|
||||||
workspaceSlug={workspaceSlug}
|
|
||||||
projectId={projectId}
|
|
||||||
issueId={issueId}
|
|
||||||
disabled={disabled}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
<RelationsCollapsibleContent
|
<RelationsCollapsibleContent
|
||||||
workspaceSlug={workspaceSlug}
|
workspaceSlug={workspaceSlug}
|
||||||
|
|
|
||||||
|
|
@ -9,14 +9,12 @@ import { useIssueDetail } from "@/hooks/store";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
workspaceSlug: string;
|
|
||||||
projectId: string;
|
|
||||||
issueId: string;
|
issueId: string;
|
||||||
disabled: boolean;
|
disabled: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const RelationsCollapsibleTitle: FC<Props> = observer((props) => {
|
export const RelationsCollapsibleTitle: FC<Props> = observer((props) => {
|
||||||
const { isOpen, workspaceSlug, projectId, issueId, disabled } = props;
|
const { isOpen, issueId, disabled } = props;
|
||||||
// store hook
|
// store hook
|
||||||
const {
|
const {
|
||||||
relation: { getRelationsByIssueId },
|
relation: { getRelationsByIssueId },
|
||||||
|
|
@ -41,14 +39,7 @@ export const RelationsCollapsibleTitle: FC<Props> = observer((props) => {
|
||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
title="Relations"
|
title="Relations"
|
||||||
indicatorElement={indicatorElement}
|
indicatorElement={indicatorElement}
|
||||||
actionItemElement={
|
actionItemElement={<RelationActionButton issueId={issueId} disabled={disabled} />}
|
||||||
<RelationActionButton
|
|
||||||
workspaceSlug={workspaceSlug}
|
|
||||||
projectId={projectId}
|
|
||||||
issueId={issueId}
|
|
||||||
disabled={disabled}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import React, { FC } from "react";
|
||||||
import {
|
import {
|
||||||
IssueDetailWidgetActionButtons,
|
IssueDetailWidgetActionButtons,
|
||||||
IssueDetailWidgetCollapsibles,
|
IssueDetailWidgetCollapsibles,
|
||||||
|
IssueDetailWidgetModals,
|
||||||
} from "@/components/issues/issue-detail-widgets";
|
} from "@/components/issues/issue-detail-widgets";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
|
@ -16,6 +17,7 @@ type Props = {
|
||||||
export const IssueDetailWidgets: FC<Props> = (props) => {
|
export const IssueDetailWidgets: FC<Props> = (props) => {
|
||||||
const { workspaceSlug, projectId, issueId, disabled } = props;
|
const { workspaceSlug, projectId, issueId, disabled } = props;
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
<div className="flex flex-col gap-5">
|
<div className="flex flex-col gap-5">
|
||||||
<IssueDetailWidgetActionButtons
|
<IssueDetailWidgetActionButtons
|
||||||
workspaceSlug={workspaceSlug}
|
workspaceSlug={workspaceSlug}
|
||||||
|
|
@ -30,5 +32,7 @@ export const IssueDetailWidgets: FC<Props> = (props) => {
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<IssueDetailWidgetModals workspaceSlug={workspaceSlug} projectId={projectId} issueId={issueId} />
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,58 +1,34 @@
|
||||||
"use client";
|
"use client";
|
||||||
import React, { FC, useState } from "react";
|
import React, { FC } from "react";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { LayersIcon, Plus } from "lucide-react";
|
import { LayersIcon, Plus } from "lucide-react";
|
||||||
import { ISearchIssueResponse, TIssue } from "@plane/types";
|
import { TIssue } from "@plane/types";
|
||||||
import { CustomMenu } from "@plane/ui";
|
import { CustomMenu } from "@plane/ui";
|
||||||
// components
|
|
||||||
import { ExistingIssuesListModal } from "@/components/core";
|
|
||||||
import { CreateUpdateIssueModal } from "@/components/issues/issue-modal";
|
|
||||||
// hooks
|
// hooks
|
||||||
import { useEventTracker, useIssueDetail } from "@/hooks/store";
|
import { useEventTracker, useIssueDetail } from "@/hooks/store";
|
||||||
// helper
|
|
||||||
import { useSubIssueOperations } from "./helper";
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
workspaceSlug: string;
|
|
||||||
projectId: string;
|
|
||||||
issueId: string;
|
issueId: string;
|
||||||
customButton?: React.ReactNode;
|
customButton?: React.ReactNode;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
type TIssueCrudState = { toggle: boolean; parentIssueId: string | undefined; issue: TIssue | undefined };
|
|
||||||
|
|
||||||
export const SubIssuesActionButton: FC<Props> = observer((props) => {
|
export const SubIssuesActionButton: FC<Props> = observer((props) => {
|
||||||
const { workspaceSlug, projectId, issueId, customButton, disabled = false } = props;
|
const { issueId, customButton, disabled = false } = props;
|
||||||
// state
|
|
||||||
const [issueCrudState, setIssueCrudState] = useState<{
|
|
||||||
create: TIssueCrudState;
|
|
||||||
existing: TIssueCrudState;
|
|
||||||
}>({
|
|
||||||
create: {
|
|
||||||
toggle: false,
|
|
||||||
parentIssueId: undefined,
|
|
||||||
issue: undefined,
|
|
||||||
},
|
|
||||||
existing: {
|
|
||||||
toggle: false,
|
|
||||||
parentIssueId: undefined,
|
|
||||||
issue: undefined,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
// store hooks
|
// store hooks
|
||||||
const {
|
const {
|
||||||
issue: { getIssueById },
|
issue: { getIssueById },
|
||||||
isCreateIssueModalOpen,
|
|
||||||
toggleCreateIssueModal,
|
toggleCreateIssueModal,
|
||||||
isSubIssuesModalOpen,
|
|
||||||
toggleSubIssuesModal,
|
toggleSubIssuesModal,
|
||||||
setLastWidgetAction,
|
setIssueCrudOperationState,
|
||||||
|
issueCrudOperationState,
|
||||||
} = useIssueDetail();
|
} = useIssueDetail();
|
||||||
const { setTrackElement } = useEventTracker();
|
const { setTrackElement } = useEventTracker();
|
||||||
|
|
||||||
// helper
|
// derived values
|
||||||
const subIssueOperations = useSubIssueOperations();
|
const issue = getIssueById(issueId);
|
||||||
|
|
||||||
|
if (!issue) return <></>;
|
||||||
|
|
||||||
// handlers
|
// handlers
|
||||||
const handleIssueCrudState = (
|
const handleIssueCrudState = (
|
||||||
|
|
@ -60,21 +36,16 @@ export const SubIssuesActionButton: FC<Props> = observer((props) => {
|
||||||
_parentIssueId: string | null,
|
_parentIssueId: string | null,
|
||||||
issue: TIssue | null = null
|
issue: TIssue | null = null
|
||||||
) => {
|
) => {
|
||||||
setIssueCrudState({
|
setIssueCrudOperationState({
|
||||||
...issueCrudState,
|
...issueCrudOperationState,
|
||||||
[key]: {
|
[key]: {
|
||||||
toggle: !issueCrudState[key].toggle,
|
toggle: !issueCrudOperationState[key].toggle,
|
||||||
parentIssueId: _parentIssueId,
|
parentIssueId: _parentIssueId,
|
||||||
issue: issue,
|
issue: issue,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// derived values
|
|
||||||
const issue = getIssueById(issueId);
|
|
||||||
|
|
||||||
if (!issue) return <></>;
|
|
||||||
|
|
||||||
const handleCreateNew = () => {
|
const handleCreateNew = () => {
|
||||||
setTrackElement("Issue detail nested sub-issue");
|
setTrackElement("Issue detail nested sub-issue");
|
||||||
handleIssueCrudState("create", issueId, null);
|
handleIssueCrudState("create", issueId, null);
|
||||||
|
|
@ -87,32 +58,6 @@ export const SubIssuesActionButton: FC<Props> = observer((props) => {
|
||||||
toggleSubIssuesModal(issue.id);
|
toggleSubIssuesModal(issue.id);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleExistingIssuesModalClose = () => {
|
|
||||||
handleIssueCrudState("existing", null, null);
|
|
||||||
setLastWidgetAction("sub-issues");
|
|
||||||
toggleSubIssuesModal(null);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleExistingIssuesModalOnSubmit = async (_issue: ISearchIssueResponse[]) =>
|
|
||||||
subIssueOperations.addSubIssue(
|
|
||||||
workspaceSlug,
|
|
||||||
projectId,
|
|
||||||
issueId,
|
|
||||||
_issue.map((issue) => issue.id)
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleCreateUpdateModalClose = () => {
|
|
||||||
handleIssueCrudState("create", null, null);
|
|
||||||
toggleCreateIssueModal(false);
|
|
||||||
setLastWidgetAction("sub-issues");
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleCreateUpdateModalOnSubmit = async (_issue: TIssue) => {
|
|
||||||
if (_issue.parent_id) {
|
|
||||||
await subIssueOperations.addSubIssue(workspaceSlug, projectId, issueId, [_issue.id]);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// options
|
// options
|
||||||
const optionItems = [
|
const optionItems = [
|
||||||
{
|
{
|
||||||
|
|
@ -127,21 +72,10 @@ export const SubIssuesActionButton: FC<Props> = observer((props) => {
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
// create update modal
|
// button element
|
||||||
const shouldRenderCreateUpdateModal =
|
|
||||||
issueCrudState?.create?.toggle && issueCrudState?.create?.parentIssueId && isCreateIssueModalOpen;
|
|
||||||
|
|
||||||
const createUpdateModalData = { parent_id: issueCrudState?.create?.parentIssueId };
|
|
||||||
|
|
||||||
// existing issues modal
|
|
||||||
const shouldRenderExistingIssuesModal =
|
|
||||||
issueCrudState?.existing?.toggle && issueCrudState?.existing?.parentIssueId && isSubIssuesModalOpen;
|
|
||||||
|
|
||||||
const existingIssuesModalSearchParams = { sub_issue: true, issue_id: issueCrudState?.existing?.parentIssueId };
|
|
||||||
|
|
||||||
const customButtonElement = customButton ? <>{customButton}</> : <Plus className="h-4 w-4" />;
|
const customButtonElement = customButton ? <>{customButton}</> : <Plus className="h-4 w-4" />;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
|
||||||
<CustomMenu customButton={customButtonElement} placement="bottom-start" disabled={disabled} closeOnSelect>
|
<CustomMenu customButton={customButtonElement} placement="bottom-start" disabled={disabled} closeOnSelect>
|
||||||
{optionItems.map((item, index) => (
|
{optionItems.map((item, index) => (
|
||||||
<CustomMenu.MenuItem
|
<CustomMenu.MenuItem
|
||||||
|
|
@ -159,27 +93,5 @@ export const SubIssuesActionButton: FC<Props> = observer((props) => {
|
||||||
</CustomMenu.MenuItem>
|
</CustomMenu.MenuItem>
|
||||||
))}
|
))}
|
||||||
</CustomMenu>
|
</CustomMenu>
|
||||||
|
|
||||||
{shouldRenderCreateUpdateModal && (
|
|
||||||
<CreateUpdateIssueModal
|
|
||||||
isOpen={issueCrudState?.create?.toggle}
|
|
||||||
data={createUpdateModalData}
|
|
||||||
onClose={handleCreateUpdateModalClose}
|
|
||||||
onSubmit={handleCreateUpdateModalOnSubmit}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{shouldRenderExistingIssuesModal && (
|
|
||||||
<ExistingIssuesListModal
|
|
||||||
workspaceSlug={workspaceSlug}
|
|
||||||
projectId={projectId}
|
|
||||||
isOpen={issueCrudState?.existing?.toggle}
|
|
||||||
handleClose={handleExistingIssuesModalClose}
|
|
||||||
searchParams={existingIssuesModalSearchParams}
|
|
||||||
handleOnSubmit={handleExistingIssuesModalOnSubmit}
|
|
||||||
workspaceLevelToggle
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -18,24 +18,16 @@ export const SubIssuesCollapsible: FC<Props> = observer((props) => {
|
||||||
const { workspaceSlug, projectId, issueId, disabled = false } = props;
|
const { workspaceSlug, projectId, issueId, disabled = false } = props;
|
||||||
|
|
||||||
// store hooks
|
// store hooks
|
||||||
const { activeIssueDetailWidgets, toggleActiveIssueDetailWidget } = useIssueDetail();
|
const { openWidgets, toggleOpenWidget } = useIssueDetail();
|
||||||
|
|
||||||
// derived state
|
// derived state
|
||||||
const isCollapsibleOpen = activeIssueDetailWidgets.includes("sub-issues");
|
const isCollapsibleOpen = openWidgets.includes("sub-issues");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Collapsible
|
<Collapsible
|
||||||
isOpen={isCollapsibleOpen}
|
isOpen={isCollapsibleOpen}
|
||||||
onToggle={() => toggleActiveIssueDetailWidget("sub-issues")}
|
onToggle={() => toggleOpenWidget("sub-issues")}
|
||||||
title={
|
title={<SubIssuesCollapsibleTitle isOpen={isCollapsibleOpen} parentIssueId={issueId} disabled={disabled} />}
|
||||||
<SubIssuesCollapsibleTitle
|
|
||||||
isOpen={isCollapsibleOpen}
|
|
||||||
workspaceSlug={workspaceSlug}
|
|
||||||
projectId={projectId}
|
|
||||||
parentIssueId={issueId}
|
|
||||||
disabled={disabled}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
<SubIssuesCollapsibleContent
|
<SubIssuesCollapsibleContent
|
||||||
workspaceSlug={workspaceSlug}
|
workspaceSlug={workspaceSlug}
|
||||||
|
|
|
||||||
|
|
@ -9,14 +9,12 @@ import { useIssueDetail } from "@/hooks/store";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
workspaceSlug: string;
|
|
||||||
projectId: string;
|
|
||||||
parentIssueId: string;
|
parentIssueId: string;
|
||||||
disabled: boolean;
|
disabled: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SubIssuesCollapsibleTitle: FC<Props> = observer((props) => {
|
export const SubIssuesCollapsibleTitle: FC<Props> = observer((props) => {
|
||||||
const { isOpen, workspaceSlug, projectId, parentIssueId, disabled } = props;
|
const { isOpen, parentIssueId, disabled } = props;
|
||||||
// store hooks
|
// store hooks
|
||||||
const {
|
const {
|
||||||
subIssues: { subIssuesByIssueId, stateDistributionByIssueId },
|
subIssues: { subIssuesByIssueId, stateDistributionByIssueId },
|
||||||
|
|
@ -52,14 +50,7 @@ export const SubIssuesCollapsibleTitle: FC<Props> = observer((props) => {
|
||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
title="Sub-issues"
|
title="Sub-issues"
|
||||||
indicatorElement={indicatorElement}
|
indicatorElement={indicatorElement}
|
||||||
actionItemElement={
|
actionItemElement={<SubIssuesActionButton issueId={parentIssueId} disabled={disabled} />}
|
||||||
<SubIssuesActionButton
|
|
||||||
workspaceSlug={workspaceSlug}
|
|
||||||
projectId={projectId}
|
|
||||||
issueId={parentIssueId}
|
|
||||||
disabled={disabled}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,13 @@ export type TIssueRelationModal = {
|
||||||
relationType: TIssueRelationTypes | null;
|
relationType: TIssueRelationTypes | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type TIssueCrudState = { toggle: boolean; parentIssueId: string | undefined; issue: TIssue | undefined };
|
||||||
|
|
||||||
|
export type TIssueCrudOperationState = {
|
||||||
|
create: TIssueCrudState;
|
||||||
|
existing: TIssueCrudState;
|
||||||
|
};
|
||||||
|
|
||||||
export interface IIssueDetail
|
export interface IIssueDetail
|
||||||
extends IIssueStoreActions,
|
extends IIssueStoreActions,
|
||||||
IIssueReactionStoreActions,
|
IIssueReactionStoreActions,
|
||||||
|
|
@ -51,7 +58,9 @@ export interface IIssueDetail
|
||||||
IIssueCommentReactionStoreActions {
|
IIssueCommentReactionStoreActions {
|
||||||
// observables
|
// observables
|
||||||
peekIssue: TPeekIssue | undefined;
|
peekIssue: TPeekIssue | undefined;
|
||||||
activeIssueDetailWidgets: TIssueDetailWidget[];
|
relationKey: TIssueRelationTypes | null;
|
||||||
|
issueCrudOperationState: TIssueCrudOperationState;
|
||||||
|
openWidgets: TIssueDetailWidget[];
|
||||||
lastWidgetAction: TIssueDetailWidget | null;
|
lastWidgetAction: TIssueDetailWidget | null;
|
||||||
isCreateIssueModalOpen: boolean;
|
isCreateIssueModalOpen: boolean;
|
||||||
isIssueLinkModalOpen: boolean;
|
isIssueLinkModalOpen: boolean;
|
||||||
|
|
@ -75,9 +84,11 @@ export interface IIssueDetail
|
||||||
toggleRelationModal: (issueId: string | null, relationType: TIssueRelationTypes | null) => void;
|
toggleRelationModal: (issueId: string | null, relationType: TIssueRelationTypes | null) => void;
|
||||||
toggleSubIssuesModal: (value: string | null) => void;
|
toggleSubIssuesModal: (value: string | null) => void;
|
||||||
toggleDeleteAttachmentModal: (attachmentId: string | null) => void;
|
toggleDeleteAttachmentModal: (attachmentId: string | null) => void;
|
||||||
setActiveIssueDetailWidgets: (state: TIssueDetailWidget[]) => void;
|
setOpenWidgets: (state: TIssueDetailWidget[]) => void;
|
||||||
setLastWidgetAction: (action: TIssueDetailWidget) => void;
|
setLastWidgetAction: (action: TIssueDetailWidget) => void;
|
||||||
toggleActiveIssueDetailWidget: (state: TIssueDetailWidget) => void;
|
toggleOpenWidget: (state: TIssueDetailWidget) => void;
|
||||||
|
setRelationKey: (relationKey: TIssueRelationTypes | null) => void;
|
||||||
|
setIssueCrudOperationState: (state: TIssueCrudOperationState) => void;
|
||||||
// store
|
// store
|
||||||
rootIssueStore: IIssueRootStore;
|
rootIssueStore: IIssueRootStore;
|
||||||
issue: IIssueStore;
|
issue: IIssueStore;
|
||||||
|
|
@ -95,7 +106,20 @@ export interface IIssueDetail
|
||||||
export class IssueDetail implements IIssueDetail {
|
export class IssueDetail implements IIssueDetail {
|
||||||
// observables
|
// observables
|
||||||
peekIssue: TPeekIssue | undefined = undefined;
|
peekIssue: TPeekIssue | undefined = undefined;
|
||||||
activeIssueDetailWidgets: TIssueDetailWidget[] = ["sub-issues"];
|
relationKey: TIssueRelationTypes | null = null;
|
||||||
|
issueCrudOperationState: TIssueCrudOperationState = {
|
||||||
|
create: {
|
||||||
|
toggle: false,
|
||||||
|
parentIssueId: undefined,
|
||||||
|
issue: undefined,
|
||||||
|
},
|
||||||
|
existing: {
|
||||||
|
toggle: false,
|
||||||
|
parentIssueId: undefined,
|
||||||
|
issue: undefined,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
openWidgets: TIssueDetailWidget[] = ["sub-issues", "links", "attachments"];
|
||||||
lastWidgetAction: TIssueDetailWidget | null = null;
|
lastWidgetAction: TIssueDetailWidget | null = null;
|
||||||
isCreateIssueModalOpen: boolean = false;
|
isCreateIssueModalOpen: boolean = false;
|
||||||
isIssueLinkModalOpen: boolean = false;
|
isIssueLinkModalOpen: boolean = false;
|
||||||
|
|
@ -122,6 +146,8 @@ export class IssueDetail implements IIssueDetail {
|
||||||
makeObservable(this, {
|
makeObservable(this, {
|
||||||
// observables
|
// observables
|
||||||
peekIssue: observable,
|
peekIssue: observable,
|
||||||
|
relationKey: observable,
|
||||||
|
issueCrudOperationState: observable,
|
||||||
isCreateIssueModalOpen: observable,
|
isCreateIssueModalOpen: observable,
|
||||||
isIssueLinkModalOpen: observable.ref,
|
isIssueLinkModalOpen: observable.ref,
|
||||||
isParentIssueModalOpen: observable.ref,
|
isParentIssueModalOpen: observable.ref,
|
||||||
|
|
@ -130,7 +156,7 @@ export class IssueDetail implements IIssueDetail {
|
||||||
isRelationModalOpen: observable.ref,
|
isRelationModalOpen: observable.ref,
|
||||||
isSubIssuesModalOpen: observable.ref,
|
isSubIssuesModalOpen: observable.ref,
|
||||||
attachmentDeleteModalId: observable.ref,
|
attachmentDeleteModalId: observable.ref,
|
||||||
activeIssueDetailWidgets: observable.ref,
|
openWidgets: observable.ref,
|
||||||
lastWidgetAction: observable.ref,
|
lastWidgetAction: observable.ref,
|
||||||
// computed
|
// computed
|
||||||
isAnyModalOpen: computed,
|
isAnyModalOpen: computed,
|
||||||
|
|
@ -144,9 +170,11 @@ export class IssueDetail implements IIssueDetail {
|
||||||
toggleRelationModal: action,
|
toggleRelationModal: action,
|
||||||
toggleSubIssuesModal: action,
|
toggleSubIssuesModal: action,
|
||||||
toggleDeleteAttachmentModal: action,
|
toggleDeleteAttachmentModal: action,
|
||||||
setActiveIssueDetailWidgets: action,
|
setOpenWidgets: action,
|
||||||
setLastWidgetAction: action,
|
setLastWidgetAction: action,
|
||||||
toggleActiveIssueDetailWidget: action,
|
toggleOpenWidget: action,
|
||||||
|
setRelationKey: action,
|
||||||
|
setIssueCrudOperationState: action,
|
||||||
});
|
});
|
||||||
|
|
||||||
// store
|
// store
|
||||||
|
|
@ -181,6 +209,8 @@ export class IssueDetail implements IIssueDetail {
|
||||||
getIsIssuePeeked = (issueId: string) => this.peekIssue?.issueId === issueId;
|
getIsIssuePeeked = (issueId: string) => this.peekIssue?.issueId === issueId;
|
||||||
|
|
||||||
// actions
|
// actions
|
||||||
|
setRelationKey = (relationKey: TIssueRelationTypes | null) => (this.relationKey = relationKey);
|
||||||
|
setIssueCrudOperationState = (state: TIssueCrudOperationState) => (this.issueCrudOperationState = state);
|
||||||
setPeekIssue = (peekIssue: TPeekIssue | undefined) => (this.peekIssue = peekIssue);
|
setPeekIssue = (peekIssue: TPeekIssue | undefined) => (this.peekIssue = peekIssue);
|
||||||
toggleCreateIssueModal = (value: boolean) => (this.isCreateIssueModalOpen = value);
|
toggleCreateIssueModal = (value: boolean) => (this.isCreateIssueModalOpen = value);
|
||||||
toggleIssueLinkModal = (value: boolean) => (this.isIssueLinkModalOpen = value);
|
toggleIssueLinkModal = (value: boolean) => (this.isIssueLinkModalOpen = value);
|
||||||
|
|
@ -191,17 +221,17 @@ export class IssueDetail implements IIssueDetail {
|
||||||
(this.isRelationModalOpen = { issueId, relationType });
|
(this.isRelationModalOpen = { issueId, relationType });
|
||||||
toggleSubIssuesModal = (issueId: string | null) => (this.isSubIssuesModalOpen = issueId);
|
toggleSubIssuesModal = (issueId: string | null) => (this.isSubIssuesModalOpen = issueId);
|
||||||
toggleDeleteAttachmentModal = (attachmentId: string | null) => (this.attachmentDeleteModalId = attachmentId);
|
toggleDeleteAttachmentModal = (attachmentId: string | null) => (this.attachmentDeleteModalId = attachmentId);
|
||||||
setActiveIssueDetailWidgets = (state: TIssueDetailWidget[]) => {
|
setOpenWidgets = (state: TIssueDetailWidget[]) => {
|
||||||
this.activeIssueDetailWidgets = state;
|
this.openWidgets = state;
|
||||||
if (this.lastWidgetAction) this.lastWidgetAction = null;
|
if (this.lastWidgetAction) this.lastWidgetAction = null;
|
||||||
};
|
};
|
||||||
setLastWidgetAction = (action: TIssueDetailWidget) => {
|
setLastWidgetAction = (action: TIssueDetailWidget) => {
|
||||||
this.activeIssueDetailWidgets = [action];
|
this.openWidgets = [action];
|
||||||
};
|
};
|
||||||
toggleActiveIssueDetailWidget = (state: TIssueDetailWidget) => {
|
toggleOpenWidget = (state: TIssueDetailWidget) => {
|
||||||
if (this.activeIssueDetailWidgets && this.activeIssueDetailWidgets.includes(state))
|
if (this.openWidgets && this.openWidgets.includes(state))
|
||||||
this.activeIssueDetailWidgets = this.activeIssueDetailWidgets.filter((s) => s !== state);
|
this.openWidgets = this.openWidgets.filter((s) => s !== state);
|
||||||
else this.activeIssueDetailWidgets = [state, ...this.activeIssueDetailWidgets];
|
else this.openWidgets = [state, ...this.openWidgets];
|
||||||
};
|
};
|
||||||
|
|
||||||
// issue
|
// issue
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue