forked from github/plane
6a16a98b03
* fix: handled undefined issue_id in list layout * chore: refactor peek overview and user role validation. * chore: sub issues * fix: sub issues state distribution changed * chore: sub_issues implementation in issue detail page * chore: fixes in cycle/ module layout. * Fix progress chart * Module issues's update/ delete. * Peek Overview for Modules/ Cycle. * Fix Cycle Filters not applying bug. --------- Co-authored-by: Prateek Shourya <prateekshourya29@gmail.com> Co-authored-by: NarayanBavisetti <narayan3119@gmail.com>
236 lines
8.8 KiB
TypeScript
236 lines
8.8 KiB
TypeScript
import { FC } from "react";
|
|
import { observer } from "mobx-react-lite";
|
|
import { CalendarDays, Signal, Tag, Triangle, LayoutPanelTop } from "lucide-react";
|
|
// hooks
|
|
import { useIssueDetail, useProject } from "hooks/store";
|
|
// ui icons
|
|
import { DiceIcon, DoubleCircleIcon, UserGroupIcon, ContrastIcon } from "@plane/ui";
|
|
import {
|
|
IssueLinkRoot,
|
|
IssueCycleSelect,
|
|
IssueModuleSelect,
|
|
IssueParentSelect,
|
|
IssueLabel,
|
|
TIssueOperations,
|
|
} from "components/issues";
|
|
import { EstimateDropdown, PriorityDropdown, ProjectMemberDropdown, StateDropdown } from "components/dropdowns";
|
|
// components
|
|
import { CustomDatePicker } from "components/ui";
|
|
|
|
interface IPeekOverviewProperties {
|
|
workspaceSlug: string;
|
|
projectId: string;
|
|
issueId: string;
|
|
disabled: boolean;
|
|
issueOperations: TIssueOperations;
|
|
}
|
|
|
|
export const PeekOverviewProperties: FC<IPeekOverviewProperties> = observer((props) => {
|
|
const { workspaceSlug, projectId, issueId, issueOperations, disabled } = props;
|
|
// store hooks
|
|
const { getProjectById } = useProject();
|
|
const {
|
|
issue: { getIssueById },
|
|
} = useIssueDetail();
|
|
// derived values
|
|
const issue = getIssueById(issueId);
|
|
if (!issue) return <></>;
|
|
const projectDetails = getProjectById(issue.project_id);
|
|
const isEstimateEnabled = projectDetails?.estimate;
|
|
|
|
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());
|
|
|
|
return (
|
|
<>
|
|
<div className="flex flex-col">
|
|
<div className={`flex w-full flex-col gap-5 py-5 ${disabled ? "opacity-60" : ""}`}>
|
|
{/* state */}
|
|
<div className="flex w-full items-center gap-2">
|
|
<div className="flex w-40 flex-shrink-0 items-center gap-2 text-sm">
|
|
<DoubleCircleIcon className="h-4 w-4 flex-shrink-0" />
|
|
<p>State</p>
|
|
</div>
|
|
<div>
|
|
<StateDropdown
|
|
value={issue?.state_id ?? undefined}
|
|
onChange={(val) => issueOperations.update(workspaceSlug, projectId, issueId, { state_id: val })}
|
|
projectId={projectId?.toString() ?? ""}
|
|
disabled={disabled}
|
|
buttonVariant="background-with-text"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
{/* assignee */}
|
|
<div className="flex w-full items-center gap-2">
|
|
<div className="flex w-40 flex-shrink-0 items-center gap-2 text-sm">
|
|
<UserGroupIcon className="h-4 w-4 flex-shrink-0" />
|
|
<p>Assignees</p>
|
|
</div>
|
|
<div className="h-5 sm:w-1/2">
|
|
<ProjectMemberDropdown
|
|
value={issue?.assignee_ids ?? undefined}
|
|
onChange={(val) => issueOperations.update(workspaceSlug, projectId, issueId, { assignee_ids: val })}
|
|
disabled={disabled}
|
|
projectId={projectId?.toString() ?? ""}
|
|
placeholder="Assignees"
|
|
multiple
|
|
buttonVariant={issue?.assignee_ids?.length > 0 ? "transparent-without-text" : "background-with-text"}
|
|
buttonClassName={issue?.assignee_ids?.length > 0 ? "hover:bg-transparent px-0" : ""}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
{/* priority */}
|
|
<div className="flex w-full items-center gap-2">
|
|
<div className="flex w-40 flex-shrink-0 items-center gap-2 text-sm">
|
|
<Signal className="h-4 w-4 flex-shrink-0" />
|
|
<p>Priority</p>
|
|
</div>
|
|
<div className="h-5">
|
|
<PriorityDropdown
|
|
value={issue?.priority || undefined}
|
|
onChange={(val) => issueOperations.update(workspaceSlug, projectId, issueId, { priority: val })}
|
|
disabled={disabled}
|
|
buttonVariant="background-with-text"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
{/* estimate */}
|
|
{isEstimateEnabled && (
|
|
<div className="flex w-full items-center gap-2">
|
|
<div className="flex w-40 flex-shrink-0 items-center gap-2 text-sm">
|
|
<Triangle className="h-4 w-4 flex-shrink-0 " />
|
|
<p>Estimate</p>
|
|
</div>
|
|
<div>
|
|
<EstimateDropdown
|
|
value={issue?.estimate_point || null}
|
|
onChange={(val) => issueOperations.update(workspaceSlug, projectId, issueId, { estimate_point: val })}
|
|
projectId={projectId}
|
|
disabled={disabled}
|
|
buttonVariant="background-with-text"
|
|
/>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* start date */}
|
|
<div className="flex w-full items-center gap-2">
|
|
<div className="flex w-40 flex-shrink-0 items-center gap-2 text-sm">
|
|
<CalendarDays className="h-4 w-4 flex-shrink-0" />
|
|
<p>Start date</p>
|
|
</div>
|
|
<div>
|
|
<CustomDatePicker
|
|
placeholder="Start date"
|
|
value={issue.start_date || undefined}
|
|
onChange={(val) => issueOperations.update(workspaceSlug, projectId, issueId, { start_date: val })}
|
|
className="border-none bg-custom-background-80"
|
|
maxDate={maxDate ?? undefined}
|
|
disabled={disabled}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
{/* due date */}
|
|
<div className="flex w-full items-center gap-2">
|
|
<div className="flex w-40 flex-shrink-0 items-center gap-2 text-sm">
|
|
<CalendarDays className="h-4 w-4 flex-shrink-0" />
|
|
<p>Due date</p>
|
|
</div>
|
|
<div>
|
|
<CustomDatePicker
|
|
placeholder="Due date"
|
|
value={issue.target_date || undefined}
|
|
onChange={(val) => issueOperations.update(workspaceSlug, projectId, issueId, { target_date: val })}
|
|
className="border-none bg-custom-background-80"
|
|
minDate={minDate ?? undefined}
|
|
disabled={disabled}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
{/* parent */}
|
|
<div className="flex w-full items-center gap-2">
|
|
<div className="flex w-40 flex-shrink-0 items-center gap-2 text-sm">
|
|
<LayoutPanelTop className="h-4 w-4 flex-shrink-0" />
|
|
<p>Parent</p>
|
|
</div>
|
|
<div>
|
|
<IssueParentSelect
|
|
workspaceSlug={workspaceSlug}
|
|
projectId={projectId}
|
|
issueId={issueId}
|
|
issueOperations={issueOperations}
|
|
disabled={disabled}
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<span className="border-t border-custom-border-200" />
|
|
|
|
<div className={`flex w-full flex-col gap-5 py-5 ${disabled ? "opacity-60" : ""}`}>
|
|
{projectDetails?.cycle_view && (
|
|
<div className="flex w-full items-center gap-2">
|
|
<div className="flex w-40 flex-shrink-0 items-center gap-2 text-sm">
|
|
<ContrastIcon className="h-4 w-4 flex-shrink-0" />
|
|
<p>Cycle</p>
|
|
</div>
|
|
<div>
|
|
<IssueCycleSelect
|
|
workspaceSlug={workspaceSlug}
|
|
projectId={projectId}
|
|
issueId={issueId}
|
|
issueOperations={issueOperations}
|
|
disabled={disabled}
|
|
/>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{projectDetails?.module_view && (
|
|
<div className="flex w-full items-center gap-2">
|
|
<div className="flex w-40 flex-shrink-0 items-center gap-2 text-sm">
|
|
<DiceIcon className="h-4 w-4 flex-shrink-0" />
|
|
<p>Module</p>
|
|
</div>
|
|
<div>
|
|
<IssueModuleSelect
|
|
workspaceSlug={workspaceSlug}
|
|
projectId={projectId}
|
|
issueId={issueId}
|
|
issueOperations={issueOperations}
|
|
disabled={disabled}
|
|
/>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
<div className="flex w-full items-start gap-2">
|
|
<div className="flex w-40 flex-shrink-0 items-center gap-2 text-sm">
|
|
<Tag className="h-4 w-4 flex-shrink-0" />
|
|
<p>Label</p>
|
|
</div>
|
|
<div className="flex w-full flex-col gap-3">
|
|
<IssueLabel workspaceSlug={workspaceSlug} projectId={projectId} issueId={issueId} disabled={disabled} />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<span className="border-t border-custom-border-200" />
|
|
|
|
<div className="w-full pt-3">
|
|
<IssueLinkRoot workspaceSlug={workspaceSlug} projectId={projectId} issueId={issueId} disabled={disabled} />
|
|
</div>
|
|
</div>
|
|
</>
|
|
);
|
|
});
|