[WEB-3396] chore: work items parent select improvement (#6608)
* chore: work items parent select improvements * chore: code refactor
This commit is contained in:
parent
4353cc0c4a
commit
8a792d381b
8 changed files with 120 additions and 59 deletions
|
|
@ -2,3 +2,4 @@ export * from "./issue-identifier";
|
||||||
export * from "./issue-properties-activity";
|
export * from "./issue-properties-activity";
|
||||||
export * from "./issue-type-switcher";
|
export * from "./issue-type-switcher";
|
||||||
export * from "./issue-type-activity";
|
export * from "./issue-type-activity";
|
||||||
|
export * from "./parent-select-root";
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,82 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
import { observer } from "mobx-react";
|
||||||
|
import { useTranslation } from "@plane/i18n";
|
||||||
|
// ui
|
||||||
|
import { TOAST_TYPE, setToast } from "@plane/ui";
|
||||||
|
// components
|
||||||
|
import { IssueParentSelect, TIssueOperations } from "@/components/issues";
|
||||||
|
// hooks
|
||||||
|
import { useIssueDetail } from "@/hooks/store";
|
||||||
|
|
||||||
|
type TIssueParentSelect = {
|
||||||
|
className?: string;
|
||||||
|
disabled?: boolean;
|
||||||
|
issueId: string;
|
||||||
|
issueOperations: TIssueOperations;
|
||||||
|
projectId: string;
|
||||||
|
workspaceSlug: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const IssueParentSelectRoot: React.FC<TIssueParentSelect> = observer((props) => {
|
||||||
|
const { issueId, issueOperations, projectId, workspaceSlug } = props;
|
||||||
|
const { t } = useTranslation();
|
||||||
|
// store hooks
|
||||||
|
const {
|
||||||
|
issue: { getIssueById },
|
||||||
|
} = useIssueDetail();
|
||||||
|
const {
|
||||||
|
toggleParentIssueModal,
|
||||||
|
removeSubIssue,
|
||||||
|
subIssues: { setSubIssueHelpers, fetchSubIssues },
|
||||||
|
} = useIssueDetail();
|
||||||
|
|
||||||
|
// derived values
|
||||||
|
const issue = getIssueById(issueId);
|
||||||
|
const parentIssue = issue?.parent_id ? getIssueById(issue.parent_id) : undefined;
|
||||||
|
|
||||||
|
const handleParentIssue = async (_issueId: string | null = null) => {
|
||||||
|
try {
|
||||||
|
await issueOperations.update(workspaceSlug, projectId, issueId, { parent_id: _issueId });
|
||||||
|
await issueOperations.fetch(workspaceSlug, projectId, issueId, false);
|
||||||
|
if (_issueId) await fetchSubIssues(workspaceSlug, projectId, _issueId);
|
||||||
|
toggleParentIssueModal(null);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("something went wrong while fetching the issue");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRemoveSubIssue = async (
|
||||||
|
workspaceSlug: string,
|
||||||
|
projectId: string,
|
||||||
|
parentIssueId: string,
|
||||||
|
issueId: string
|
||||||
|
) => {
|
||||||
|
try {
|
||||||
|
setSubIssueHelpers(parentIssueId, "issue_loader", issueId);
|
||||||
|
await removeSubIssue(workspaceSlug, projectId, parentIssueId, issueId);
|
||||||
|
await fetchSubIssues(workspaceSlug, projectId, parentIssueId);
|
||||||
|
setSubIssueHelpers(parentIssueId, "issue_loader", issueId);
|
||||||
|
} catch (error) {
|
||||||
|
setToast({
|
||||||
|
type: TOAST_TYPE.ERROR,
|
||||||
|
title: t("common.error.label"),
|
||||||
|
message: t("common.something_went_wrong"),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const workItemLink = `/${workspaceSlug}/projects/${parentIssue?.project_id}/issues/${parentIssue?.id}`;
|
||||||
|
|
||||||
|
if (!issue) return <></>;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<IssueParentSelect
|
||||||
|
{...props}
|
||||||
|
handleParentIssue={handleParentIssue}
|
||||||
|
handleRemoveSubIssue={handleRemoveSubIssue}
|
||||||
|
workItemLink={workItemLink}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
@ -6,7 +6,7 @@ import Link from "next/link";
|
||||||
import { Pencil, X } from "lucide-react";
|
import { Pencil, X } from "lucide-react";
|
||||||
import { useTranslation } from "@plane/i18n";
|
import { useTranslation } from "@plane/i18n";
|
||||||
// ui
|
// ui
|
||||||
import { TOAST_TYPE, Tooltip, setToast } from "@plane/ui";
|
import { Tooltip } from "@plane/ui";
|
||||||
// components
|
// components
|
||||||
import { ParentIssuesListModal } from "@/components/issues";
|
import { ParentIssuesListModal } from "@/components/issues";
|
||||||
// helpers
|
// helpers
|
||||||
|
|
@ -17,31 +17,41 @@ import { usePlatformOS } from "@/hooks/use-platform-os";
|
||||||
// plane web components
|
// plane web components
|
||||||
import { IssueIdentifier } from "@/plane-web/components/issues";
|
import { IssueIdentifier } from "@/plane-web/components/issues";
|
||||||
// types
|
// types
|
||||||
import { TIssueOperations } from "./root";
|
|
||||||
|
|
||||||
type TIssueParentSelect = {
|
type TIssueParentSelect = {
|
||||||
className?: string;
|
className?: string;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
issueId: string;
|
issueId: string;
|
||||||
issueOperations: TIssueOperations;
|
|
||||||
projectId: string;
|
projectId: string;
|
||||||
workspaceSlug: string;
|
workspaceSlug: string;
|
||||||
|
handleParentIssue: (_issueId?: string | null) => Promise<void>;
|
||||||
|
handleRemoveSubIssue: (
|
||||||
|
workspaceSlug: string,
|
||||||
|
projectId: string,
|
||||||
|
parentIssueId: string,
|
||||||
|
issueId: string
|
||||||
|
) => Promise<void>;
|
||||||
|
workItemLink: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const IssueParentSelect: React.FC<TIssueParentSelect> = observer((props) => {
|
export const IssueParentSelect: React.FC<TIssueParentSelect> = observer((props) => {
|
||||||
const { className = "", disabled = false, issueId, issueOperations, projectId, workspaceSlug } = props;
|
const {
|
||||||
|
className = "",
|
||||||
|
disabled = false,
|
||||||
|
issueId,
|
||||||
|
projectId,
|
||||||
|
workspaceSlug,
|
||||||
|
handleParentIssue,
|
||||||
|
handleRemoveSubIssue,
|
||||||
|
workItemLink,
|
||||||
|
} = props;
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
// store hooks
|
// store hooks
|
||||||
const { getProjectById } = useProject();
|
const { getProjectById } = useProject();
|
||||||
const {
|
const {
|
||||||
issue: { getIssueById },
|
issue: { getIssueById },
|
||||||
} = useIssueDetail();
|
} = useIssueDetail();
|
||||||
const {
|
const { isParentIssueModalOpen, toggleParentIssueModal } = useIssueDetail();
|
||||||
isParentIssueModalOpen,
|
|
||||||
toggleParentIssueModal,
|
|
||||||
removeSubIssue,
|
|
||||||
subIssues: { setSubIssueHelpers, fetchSubIssues },
|
|
||||||
} = useIssueDetail();
|
|
||||||
|
|
||||||
// derived values
|
// derived values
|
||||||
const issue = getIssueById(issueId);
|
const issue = getIssueById(issueId);
|
||||||
|
|
@ -49,36 +59,6 @@ export const IssueParentSelect: React.FC<TIssueParentSelect> = observer((props)
|
||||||
const parentIssueProjectDetails =
|
const parentIssueProjectDetails =
|
||||||
parentIssue && parentIssue.project_id ? getProjectById(parentIssue.project_id) : undefined;
|
parentIssue && parentIssue.project_id ? getProjectById(parentIssue.project_id) : undefined;
|
||||||
const { isMobile } = usePlatformOS();
|
const { isMobile } = usePlatformOS();
|
||||||
const handleParentIssue = async (_issueId: string | null = null) => {
|
|
||||||
try {
|
|
||||||
await issueOperations.update(workspaceSlug, projectId, issueId, { parent_id: _issueId });
|
|
||||||
await issueOperations.fetch(workspaceSlug, projectId, issueId, false);
|
|
||||||
_issueId && (await fetchSubIssues(workspaceSlug, projectId, _issueId));
|
|
||||||
toggleParentIssueModal(null);
|
|
||||||
} catch (error) {
|
|
||||||
console.error("something went wrong while fetching the issue");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleRemoveSubIssue = async (
|
|
||||||
workspaceSlug: string,
|
|
||||||
projectId: string,
|
|
||||||
parentIssueId: string,
|
|
||||||
issueId: string
|
|
||||||
) => {
|
|
||||||
try {
|
|
||||||
setSubIssueHelpers(parentIssueId, "issue_loader", issueId);
|
|
||||||
await removeSubIssue(workspaceSlug, projectId, parentIssueId, issueId);
|
|
||||||
await fetchSubIssues(workspaceSlug, projectId, parentIssueId);
|
|
||||||
setSubIssueHelpers(parentIssueId, "issue_loader", issueId);
|
|
||||||
} catch (error) {
|
|
||||||
setToast({
|
|
||||||
type: TOAST_TYPE.ERROR,
|
|
||||||
title: t("common.error.label"),
|
|
||||||
message: t("common.something_went_wrong"),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!issue) return <></>;
|
if (!issue) return <></>;
|
||||||
|
|
||||||
|
|
@ -109,12 +89,7 @@ export const IssueParentSelect: React.FC<TIssueParentSelect> = observer((props)
|
||||||
{issue.parent_id && parentIssue ? (
|
{issue.parent_id && parentIssue ? (
|
||||||
<div className="flex items-center gap-1 bg-green-500/20 rounded px-1.5 py-1">
|
<div className="flex items-center gap-1 bg-green-500/20 rounded px-1.5 py-1">
|
||||||
<Tooltip tooltipHeading="Title" tooltipContent={parentIssue.name} isMobile={isMobile}>
|
<Tooltip tooltipHeading="Title" tooltipContent={parentIssue.name} isMobile={isMobile}>
|
||||||
<Link
|
<Link href={workItemLink} target="_blank" rel="noopener noreferrer" onClick={(e) => e.stopPropagation()}>
|
||||||
href={`/${workspaceSlug}/projects/${parentIssue.project_id}/issues/${parentIssue?.id}`}
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
onClick={(e) => e.stopPropagation()}
|
|
||||||
>
|
|
||||||
{parentIssue?.project_id && parentIssueProjectDetails && (
|
{parentIssue?.project_id && parentIssueProjectDetails && (
|
||||||
<IssueIdentifier
|
<IssueIdentifier
|
||||||
projectId={parentIssue.project_id}
|
projectId={parentIssue.project_id}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,14 @@ import { FC, useMemo } from "react";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { usePathname } from "next/navigation";
|
import { usePathname } from "next/navigation";
|
||||||
// types
|
// types
|
||||||
import { EIssuesStoreType, ISSUE_UPDATED, ISSUE_DELETED, ISSUE_ARCHIVED,EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
import {
|
||||||
|
EIssuesStoreType,
|
||||||
|
ISSUE_UPDATED,
|
||||||
|
ISSUE_DELETED,
|
||||||
|
ISSUE_ARCHIVED,
|
||||||
|
EUserPermissions,
|
||||||
|
EUserPermissionsLevel,
|
||||||
|
} from "@plane/constants";
|
||||||
import { useTranslation } from "@plane/i18n";
|
import { useTranslation } from "@plane/i18n";
|
||||||
import { TIssue } from "@plane/types";
|
import { TIssue } from "@plane/types";
|
||||||
// ui
|
// ui
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ import {
|
||||||
StateDropdown,
|
StateDropdown,
|
||||||
} from "@/components/dropdowns";
|
} from "@/components/dropdowns";
|
||||||
import { ButtonAvatars } from "@/components/dropdowns/member/avatar";
|
import { ButtonAvatars } from "@/components/dropdowns/member/avatar";
|
||||||
import { IssueCycleSelect, IssueLabel, IssueModuleSelect, IssueParentSelect } from "@/components/issues";
|
import { IssueCycleSelect, IssueLabel, IssueModuleSelect } from "@/components/issues";
|
||||||
// helpers
|
// helpers
|
||||||
import { cn } from "@/helpers/common.helper";
|
import { cn } from "@/helpers/common.helper";
|
||||||
import { getDate, renderFormattedPayloadDate } from "@/helpers/date-time.helper";
|
import { getDate, renderFormattedPayloadDate } from "@/helpers/date-time.helper";
|
||||||
|
|
@ -25,7 +25,7 @@ import { shouldHighlightIssueDueDate } from "@/helpers/issue.helper";
|
||||||
import { useProjectEstimates, useIssueDetail, useProject, useProjectState, useMember } from "@/hooks/store";
|
import { useProjectEstimates, useIssueDetail, useProject, useProjectState, useMember } from "@/hooks/store";
|
||||||
// plane web components
|
// plane web components
|
||||||
import { IssueAdditionalPropertyValuesUpdate } from "@/plane-web/components/issue-types/values";
|
import { IssueAdditionalPropertyValuesUpdate } from "@/plane-web/components/issue-types/values";
|
||||||
import { IssueWorklogProperty } from "@/plane-web/components/issues";
|
import { IssueParentSelectRoot, IssueWorklogProperty } from "@/plane-web/components/issues";
|
||||||
// components
|
// components
|
||||||
import type { TIssueOperations } from "./root";
|
import type { TIssueOperations } from "./root";
|
||||||
|
|
||||||
|
|
@ -261,7 +261,7 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
|
||||||
<LayoutPanelTop className="h-4 w-4 flex-shrink-0" />
|
<LayoutPanelTop className="h-4 w-4 flex-shrink-0" />
|
||||||
<span>{t("common.parent")}</span>
|
<span>{t("common.parent")}</span>
|
||||||
</div>
|
</div>
|
||||||
<IssueParentSelect
|
<IssueParentSelectRoot
|
||||||
className="h-full w-3/5 flex-grow"
|
className="h-full w-3/5 flex-grow"
|
||||||
workspaceSlug={workspaceSlug}
|
workspaceSlug={workspaceSlug}
|
||||||
projectId={projectId}
|
projectId={projectId}
|
||||||
|
|
|
||||||
|
|
@ -16,13 +16,7 @@ import {
|
||||||
StateDropdown,
|
StateDropdown,
|
||||||
} from "@/components/dropdowns";
|
} from "@/components/dropdowns";
|
||||||
import { ButtonAvatars } from "@/components/dropdowns/member/avatar";
|
import { ButtonAvatars } from "@/components/dropdowns/member/avatar";
|
||||||
import {
|
import { IssueCycleSelect, IssueModuleSelect, IssueLabel, TIssueOperations } from "@/components/issues";
|
||||||
IssueCycleSelect,
|
|
||||||
IssueModuleSelect,
|
|
||||||
IssueParentSelect,
|
|
||||||
IssueLabel,
|
|
||||||
TIssueOperations,
|
|
||||||
} from "@/components/issues";
|
|
||||||
// helpers
|
// helpers
|
||||||
import { cn } from "@/helpers/common.helper";
|
import { cn } from "@/helpers/common.helper";
|
||||||
import { getDate, renderFormattedPayloadDate } from "@/helpers/date-time.helper";
|
import { getDate, renderFormattedPayloadDate } from "@/helpers/date-time.helper";
|
||||||
|
|
@ -30,7 +24,7 @@ import { shouldHighlightIssueDueDate } from "@/helpers/issue.helper";
|
||||||
import { useIssueDetail, useMember, useProject, useProjectState } from "@/hooks/store";
|
import { useIssueDetail, useMember, useProject, useProjectState } from "@/hooks/store";
|
||||||
// plane web components
|
// plane web components
|
||||||
import { IssueAdditionalPropertyValuesUpdate } from "@/plane-web/components/issue-types/values";
|
import { IssueAdditionalPropertyValuesUpdate } from "@/plane-web/components/issue-types/values";
|
||||||
import { IssueWorklogProperty } from "@/plane-web/components/issues";
|
import { IssueParentSelectRoot, IssueWorklogProperty } from "@/plane-web/components/issues";
|
||||||
|
|
||||||
interface IPeekOverviewProperties {
|
interface IPeekOverviewProperties {
|
||||||
workspaceSlug: string;
|
workspaceSlug: string;
|
||||||
|
|
@ -269,7 +263,7 @@ export const PeekOverviewProperties: FC<IPeekOverviewProperties> = observer((pro
|
||||||
<LayoutPanelTop className="h-4 w-4 flex-shrink-0" />
|
<LayoutPanelTop className="h-4 w-4 flex-shrink-0" />
|
||||||
<p>{t("common.parent")}</p>
|
<p>{t("common.parent")}</p>
|
||||||
</div>
|
</div>
|
||||||
<IssueParentSelect
|
<IssueParentSelectRoot
|
||||||
className="w-3/4 flex-grow h-full"
|
className="w-3/4 flex-grow h-full"
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
issueId={issueId}
|
issueId={issueId}
|
||||||
|
|
|
||||||
|
|
@ -2,3 +2,4 @@ export * from "./issue-identifier";
|
||||||
export * from "./issue-properties-activity";
|
export * from "./issue-properties-activity";
|
||||||
export * from "./issue-type-switcher";
|
export * from "./issue-type-switcher";
|
||||||
export * from "./issue-type-activity";
|
export * from "./issue-type-activity";
|
||||||
|
export * from "./parent-select-root";
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
export * from "ce/components/issues/issue-details/parent-select-root";
|
||||||
Loading…
Add table
Add a link
Reference in a new issue