import React, { useState } from "react"; import { useRouter } from "next/router"; import { observer } from "mobx-react-lite"; import { LinkIcon, Signal, Tag, Trash2, Triangle, LayoutPanelTop, XCircle, CircleDot, CopyPlus, CalendarClock, CalendarCheck2, } from "lucide-react"; // hooks import { useEstimate, useIssueDetail, useProject, useProjectState, useUser } from "hooks/store"; import useToast from "hooks/use-toast"; // components import { DeleteIssueModal, IssueLinkRoot, IssueRelationSelect, IssueCycleSelect, IssueModuleSelect, IssueParentSelect, IssueLabel, } from "components/issues"; import { IssueSubscription } from "./subscription"; import { DateDropdown, EstimateDropdown, PriorityDropdown, ProjectMemberDropdown, StateDropdown, } from "components/dropdowns"; // icons import { ContrastIcon, DiceIcon, DoubleCircleIcon, RelatedIcon, StateGroupIcon, UserGroupIcon } from "@plane/ui"; // helpers import { renderFormattedPayloadDate } from "helpers/date-time.helper"; import { copyTextToClipboard } from "helpers/string.helper"; // types import type { TIssueOperations } from "./root"; type Props = { workspaceSlug: string; projectId: string; issueId: string; issueOperations: TIssueOperations; is_archived: boolean; is_editable: boolean; }; export const IssueDetailsSidebar: React.FC = observer((props) => { const { workspaceSlug, projectId, issueId, issueOperations, is_archived, is_editable } = props; // router const router = useRouter(); const { inboxIssueId } = router.query; // store hooks const { getProjectById } = useProject(); const { currentUser } = useUser(); const { projectStates } = useProjectState(); const { areEstimatesEnabledForCurrentProject } = useEstimate(); const { setToastAlert } = useToast(); const { issue: { getIssueById }, } = useIssueDetail(); // states const [deleteIssueModal, setDeleteIssueModal] = useState(false); const issue = getIssueById(issueId); if (!issue) return <>; const handleCopyText = () => { const originURL = typeof window !== "undefined" && window.location.origin ? window.location.origin : ""; copyTextToClipboard(`${originURL}/${workspaceSlug}/projects/${projectId}/issues/${issue.id}`).then(() => { setToastAlert({ type: "success", title: "Link Copied!", message: "Issue link copied to clipboard.", }); }); }; const projectDetails = issue ? getProjectById(issue.project_id) : null; const minDate = issue.start_date ? new Date(issue.start_date) : null; minDate?.setDate(minDate.getDate()); const maxDate = issue.target_date ? new Date(issue.target_date) : null; maxDate?.setDate(maxDate.getDate()); const currentIssueState = projectStates?.find((s) => s.id === issue.state_id); return ( <> {workspaceSlug && projectId && issue && ( setDeleteIssueModal(false)} isOpen={deleteIssueModal} data={issue} onSubmit={async () => { await issueOperations.remove(workspaceSlug, projectId, issueId); router.push(`/${workspaceSlug}/projects/${projectId}/issues`); }} /> )}
{currentIssueState ? ( ) : inboxIssueId ? ( ) : null}

{projectDetails?.identifier}-{issue?.sequence_id}

{currentUser && !is_archived && ( )} {is_editable && ( )}
Properties
{/* TODO: render properties using a common component */}
State
issueOperations.update(workspaceSlug, projectId, issueId, { state_id: val })} projectId={projectId?.toString() ?? ""} disabled={!is_editable} buttonVariant="transparent-with-text" className="w-3/5 flex-grow group" buttonContainerClassName="w-full text-left" buttonClassName="text-sm" dropdownArrow dropdownArrowClassName="h-3.5 w-3.5 hidden group-hover:inline" />
Assignees
issueOperations.update(workspaceSlug, projectId, issueId, { assignee_ids: val })} disabled={!is_editable} projectId={projectId?.toString() ?? ""} placeholder="Add assignees" multiple buttonVariant={issue?.assignee_ids?.length > 1 ? "transparent-without-text" : "transparent-with-text"} className="w-3/5 flex-grow group" buttonContainerClassName="w-full text-left" buttonClassName={`text-sm justify-between ${ issue?.assignee_ids.length > 0 ? "" : "text-custom-text-400" }`} hideIcon={issue.assignee_ids?.length === 0} dropdownArrow dropdownArrowClassName="h-3.5 w-3.5 hidden group-hover:inline" />
Priority
issueOperations.update(workspaceSlug, projectId, issueId, { priority: val })} disabled={!is_editable} buttonVariant="border-with-text" className="w-3/5 flex-grow rounded px-2 hover:bg-custom-background-80" buttonContainerClassName="w-full text-left" buttonClassName="w-min h-auto whitespace-nowrap" />
Start date
issueOperations.update(workspaceSlug, projectId, issueId, { start_date: val ? renderFormattedPayloadDate(val) : null, }) } maxDate={maxDate ?? undefined} disabled={!is_editable} buttonVariant="transparent-with-text" className="w-3/5 flex-grow group" buttonContainerClassName="w-full text-left" buttonClassName={`text-sm ${issue?.start_date ? "" : "text-custom-text-400"}`} hideIcon clearIconClassName="h-3 w-3 hidden group-hover:inline" showPlaceholderIcon />
Due date
issueOperations.update(workspaceSlug, projectId, issueId, { target_date: val ? renderFormattedPayloadDate(val) : null, }) } minDate={minDate ?? undefined} disabled={!is_editable} buttonVariant="transparent-with-text" className="w-3/5 flex-grow group" buttonContainerClassName="w-full text-left" buttonClassName={`text-sm ${issue?.target_date ? "" : "text-custom-text-400"}`} hideIcon clearIconClassName="h-3 w-3 hidden group-hover:inline" showPlaceholderIcon />
{areEstimatesEnabledForCurrentProject && (
Estimate
issueOperations.update(workspaceSlug, projectId, issueId, { estimate_point: val })} projectId={projectId} disabled={!is_editable} buttonVariant="transparent-with-text" className="w-3/5 flex-grow group" buttonContainerClassName="w-full text-left" buttonClassName={`text-sm ${issue?.estimate_point !== null ? "" : "text-custom-text-400"}`} placeholder="None" hideIcon dropdownArrow dropdownArrowClassName="h-3.5 w-3.5 hidden group-hover:inline" />
)} {projectDetails?.module_view && (
Module
)} {projectDetails?.cycle_view && (
Cycle
)}
Parent
Relates to
Blocking
Blocked by
Duplicate of
Labels
); });