fix: peekoverview (#2603)

* fix: peekoverview mutation fix

* fix: peekoverview mutation fix

* fix: sub-issue peekoverview
This commit is contained in:
Anmol Singh Bhatia 2023-11-02 16:02:34 +05:30 committed by GitHub
parent 4512651f8b
commit 0072160891
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 301 additions and 239 deletions

View File

@ -85,8 +85,8 @@ export const CyclesListGanttChartView: FC<Props> = ({ cycles, mutateCycles }) =>
loaderTitle="Cycles"
blocks={cycles ? blockFormat(cycles) : null}
blockUpdateHandler={(block, payload) => handleCycleUpdate(block, payload)}
SidebarBlockRender={CycleGanttSidebarBlock}
BlockRender={CycleGanttBlock}
blockToRender={(data: ICycle) => <CycleGanttBlock data={data} />}
sidebarBlockToRender={(data: ICycle) => <CycleGanttSidebarBlock data={data} />}
enableBlockLeftResize={false}
enableBlockRightResize={false}
enableBlockMove={false}

View File

@ -11,7 +11,7 @@ import { IBlockUpdateData, IGanttBlock } from "../types";
export const GanttChartBlocks: FC<{
itemsContainerWidth: number;
blocks: IGanttBlock[] | null;
BlockRender: React.FC<any>;
blockToRender: (data: any) => React.ReactNode;
blockUpdateHandler: (block: any, payload: IBlockUpdateData) => void;
enableBlockLeftResize: boolean;
enableBlockRightResize: boolean;
@ -19,7 +19,7 @@ export const GanttChartBlocks: FC<{
}> = ({
itemsContainerWidth,
blocks,
BlockRender,
blockToRender,
blockUpdateHandler,
enableBlockLeftResize,
enableBlockRightResize,
@ -49,11 +49,9 @@ export const GanttChartBlocks: FC<{
const updatedTargetDate = new Date(originalTargetDate);
// update the start date on left resize
if (dragDirection === "left")
updatedStartDate.setDate(originalStartDate.getDate() - totalBlockShifts);
if (dragDirection === "left") updatedStartDate.setDate(originalStartDate.getDate() - totalBlockShifts);
// update the target date on right resize
else if (dragDirection === "right")
updatedTargetDate.setDate(originalTargetDate.getDate() + totalBlockShifts);
else if (dragDirection === "right") updatedTargetDate.setDate(originalTargetDate.getDate() + totalBlockShifts);
// update both the dates on x-axis move
else if (dragDirection === "move") {
updatedStartDate.setDate(originalStartDate.getDate() + totalBlockShifts);
@ -86,7 +84,7 @@ export const GanttChartBlocks: FC<{
>
<ChartDraggable
block={block}
BlockRender={BlockRender}
blockToRender={blockToRender}
handleBlock={(...args) => handleChartBlockPosition(block, ...args)}
enableBlockLeftResize={enableBlockLeftResize}
enableBlockRightResize={enableBlockRightResize}

View File

@ -39,8 +39,8 @@ type ChartViewRootProps = {
loaderTitle: string;
blocks: IGanttBlock[] | null;
blockUpdateHandler: (block: any, payload: IBlockUpdateData) => void;
SidebarBlockRender: React.FC<any>;
BlockRender: React.FC<any>;
blockToRender: (data: any) => React.ReactNode;
sidebarBlockToRender: (block: any) => React.ReactNode;
enableBlockLeftResize: boolean;
enableBlockRightResize: boolean;
enableBlockMove: boolean;
@ -54,8 +54,8 @@ export const ChartViewRoot: FC<ChartViewRootProps> = ({
blocks = null,
loaderTitle,
blockUpdateHandler,
SidebarBlockRender,
BlockRender,
sidebarBlockToRender,
blockToRender,
enableBlockLeftResize,
enableBlockRightResize,
enableBlockMove,
@ -289,7 +289,7 @@ export const ChartViewRoot: FC<ChartViewRootProps> = ({
title={title}
blockUpdateHandler={blockUpdateHandler}
blocks={chartBlocks}
SidebarBlockRender={SidebarBlockRender}
sidebarBlockToRender={sidebarBlockToRender}
enableReorder={enableReorder}
/>
</div>
@ -311,7 +311,7 @@ export const ChartViewRoot: FC<ChartViewRootProps> = ({
<GanttChartBlocks
itemsContainerWidth={itemsContainerWidth}
blocks={chartBlocks}
BlockRender={BlockRender}
blockToRender={blockToRender}
blockUpdateHandler={blockUpdateHandler}
enableBlockLeftResize={enableBlockLeftResize}
enableBlockRightResize={enableBlockRightResize}

View File

@ -9,7 +9,7 @@ import { IGanttBlock } from "../types";
type Props = {
block: IGanttBlock;
BlockRender: React.FC<any>;
blockToRender: (data: any) => React.ReactNode;
handleBlock: (totalBlockShifts: number, dragDirection: "left" | "right" | "move") => void;
enableBlockLeftResize: boolean;
enableBlockRightResize: boolean;
@ -18,7 +18,7 @@ type Props = {
export const ChartDraggable: React.FC<Props> = ({
block,
BlockRender,
blockToRender,
handleBlock,
enableBlockLeftResize,
enableBlockRightResize,
@ -286,7 +286,7 @@ export const ChartDraggable: React.FC<Props> = ({
className={`relative z-[2] rounded h-8 w-full flex items-center ${isMoving ? "pointer-events-none" : ""}`}
onMouseDown={handleBlockMove}
>
<BlockRender data={block.data} />
{blockToRender(block.data)}
</div>
{/* right resize drag handle */}
{enableBlockRightResize && (

View File

@ -12,8 +12,8 @@ type GanttChartRootProps = {
loaderTitle: string;
blocks: IGanttBlock[] | null;
blockUpdateHandler: (block: any, payload: IBlockUpdateData) => void;
SidebarBlockRender: FC<any>;
BlockRender: FC<any>;
blockToRender: (data: any) => React.ReactNode;
sidebarBlockToRender: (block: any) => React.ReactNode;
enableBlockLeftResize?: boolean;
enableBlockRightResize?: boolean;
enableBlockMove?: boolean;
@ -27,8 +27,8 @@ export const GanttChartRoot: FC<GanttChartRootProps> = ({
blocks,
loaderTitle = "blocks",
blockUpdateHandler,
SidebarBlockRender,
BlockRender,
sidebarBlockToRender,
blockToRender,
enableBlockLeftResize = true,
enableBlockRightResize = true,
enableBlockMove = true,
@ -42,8 +42,8 @@ export const GanttChartRoot: FC<GanttChartRootProps> = ({
blocks={blocks}
loaderTitle={loaderTitle}
blockUpdateHandler={blockUpdateHandler}
SidebarBlockRender={SidebarBlockRender}
BlockRender={BlockRender}
sidebarBlockToRender={sidebarBlockToRender}
blockToRender={blockToRender}
enableBlockLeftResize={enableBlockLeftResize}
enableBlockRightResize={enableBlockRightResize}
enableBlockMove={enableBlockMove}

View File

@ -17,14 +17,14 @@ type Props = {
title: string;
blockUpdateHandler: (block: any, payload: IBlockUpdateData) => void;
blocks: IGanttBlock[] | null;
SidebarBlockRender: React.FC<any>;
sidebarBlockToRender: (block: any) => React.ReactNode;
enableReorder: boolean;
enableQuickIssueCreate?: boolean;
};
export const GanttSidebar: React.FC<Props> = (props) => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { title, blockUpdateHandler, blocks, SidebarBlockRender, enableReorder, enableQuickIssueCreate } = props;
const { title, blockUpdateHandler, blocks, sidebarBlockToRender, enableReorder, enableQuickIssueCreate } = props;
const router = useRouter();
const { cycleId } = router.query;
@ -130,9 +130,7 @@ export const GanttSidebar: React.FC<Props> = (props) => {
</button>
)}
<div className="flex-grow truncate h-full flex items-center justify-between gap-2">
<div className="flex-grow truncate">
<SidebarBlockRender data={block.data} />
</div>
<div className="flex-grow truncate">{sidebarBlockToRender(block.data)}</div>
<div className="flex-shrink-0 text-sm text-custom-text-200">
{duration} day{duration > 1 ? "s" : ""}
</div>

View File

@ -15,11 +15,12 @@ type Props = {
issues: IIssueGroupedStructure | null;
layout: "month" | "week" | undefined;
showWeekends: boolean;
handleIssues: (date: string, issue: IIssue, action: "update" | "delete") => void;
quickActions: (issue: IIssue) => React.ReactNode;
};
export const CalendarChart: React.FC<Props> = observer((props) => {
const { issues, layout, showWeekends, quickActions } = props;
const { issues, layout, showWeekends, handleIssues, quickActions } = props;
const { calendar: calendarStore } = useMobxStore();
@ -49,6 +50,7 @@ export const CalendarChart: React.FC<Props> = observer((props) => {
week={week}
issues={issues}
enableQuickIssueCreate
handleIssues={handleIssues}
quickActions={quickActions}
/>
))}
@ -59,6 +61,7 @@ export const CalendarChart: React.FC<Props> = observer((props) => {
week={calendarStore.allDaysOfActiveWeek}
issues={issues}
enableQuickIssueCreate
handleIssues={handleIssues}
quickActions={quickActions}
/>
)}

View File

@ -16,12 +16,13 @@ import { IIssue } from "types";
type Props = {
date: ICalendarDate;
issues: IIssueGroupedStructure | null;
handleIssues: (date: string, issue: IIssue, action: "update" | "delete") => void;
quickActions: (issue: IIssue) => React.ReactNode;
enableQuickIssueCreate?: boolean;
};
export const CalendarDayTile: React.FC<Props> = observer((props) => {
const { date, issues, quickActions, enableQuickIssueCreate } = props;
const { date, issues, handleIssues, quickActions, enableQuickIssueCreate } = props;
const { issueFilter: issueFilterStore } = useMobxStore();
@ -63,7 +64,7 @@ export const CalendarDayTile: React.FC<Props> = observer((props) => {
{...provided.droppableProps}
ref={provided.innerRef}
>
<CalendarIssueBlocks issues={issuesList} quickActions={quickActions} />
<CalendarIssueBlocks issues={issuesList} handleIssues={handleIssues} quickActions={quickActions} />
{enableQuickIssueCreate && (
<div className="py-1 px-2">
<CalendarInlineCreateIssueForm

View File

@ -2,16 +2,20 @@ import Link from "next/link";
import { useRouter } from "next/router";
import { observer } from "mobx-react-lite";
import { Draggable } from "@hello-pangea/dnd";
// components
import { IssuePeekOverview } from "components/issues/issue-peek-overview";
import { Tooltip } from "@plane/ui";
// types
import { IIssue } from "types";
type Props = {
issues: IIssue[] | null;
handleIssues: (date: string, issue: IIssue, action: "update" | "delete") => void;
quickActions: (issue: IIssue) => React.ReactNode;
};
export const CalendarIssueBlocks: React.FC<Props> = observer((props) => {
const { issues, quickActions } = props;
const { issues, handleIssues, quickActions } = props;
const router = useRouter();
const { workspaceSlug } = router.query;
@ -47,7 +51,19 @@ export const CalendarIssueBlocks: React.FC<Props> = observer((props) => {
<div className="text-xs text-custom-text-300 flex-shrink-0">
{issue.project_detail.identifier}-{issue.sequence_id}
</div>
<h6 className="text-xs flex-grow truncate">{issue.name}</h6>
<IssuePeekOverview
workspaceSlug={issue?.workspace_detail?.slug}
projectId={issue?.project_detail?.id}
issueId={issue?.id}
// TODO: add the logic here
handleIssue={(issueToUpdate) => {
handleIssues(issue.target_date ?? "", { ...issue, ...issueToUpdate }, "update");
}}
>
<Tooltip tooltipHeading="Title" tooltipContent={issue.name}>
<span className="text-xs flex-grow truncate">{issue.name}</span>
</Tooltip>
</IssuePeekOverview>
<div className="hidden group-hover/calendar-block:block">{quickActions(issue)}</div>
</a>
</Link>

View File

@ -64,6 +64,7 @@ export const CycleCalendarLayout: React.FC = observer(() => {
issues={issues as IIssueGroupedStructure | null}
layout={issueFilterStore.userDisplayFilters.calendar?.layout}
showWeekends={issueFilterStore.userDisplayFilters.calendar?.show_weekends ?? false}
handleIssues={handleIssues}
quickActions={(issue) => (
<CycleIssueQuickActions
issue={issue}

View File

@ -66,6 +66,7 @@ export const ModuleCalendarLayout: React.FC = observer(() => {
issues={issues as IIssueGroupedStructure | null}
layout={issueFilterStore.userDisplayFilters.calendar?.layout}
showWeekends={issueFilterStore.userDisplayFilters.calendar?.show_weekends ?? false}
handleIssues={handleIssues}
quickActions={(issue) => (
<ModuleIssueQuickActions
issue={issue}

View File

@ -57,6 +57,7 @@ export const CalendarLayout: React.FC = observer(() => {
issues={issues as IIssueGroupedStructure | null}
layout={issueFilterStore.userDisplayFilters.calendar?.layout}
showWeekends={issueFilterStore.userDisplayFilters.calendar?.show_weekends ?? false}
handleIssues={handleIssues}
quickActions={(issue) => (
<ProjectIssueQuickActions
issue={issue}

View File

@ -57,6 +57,7 @@ export const ProjectViewCalendarLayout: React.FC = observer(() => {
issues={issues as IIssueGroupedStructure | null}
layout={issueFilterStore.userDisplayFilters.calendar?.layout}
showWeekends={issueFilterStore.userDisplayFilters.calendar?.show_weekends ?? false}
handleIssues={handleIssues}
quickActions={(issue) => (
<ProjectIssueQuickActions
issue={issue}

View File

@ -14,12 +14,13 @@ import { IIssue } from "types";
type Props = {
issues: IIssueGroupedStructure | null;
week: ICalendarWeek | undefined;
handleIssues: (date: string, issue: IIssue, action: "update" | "delete") => void;
quickActions: (issue: IIssue) => React.ReactNode;
enableQuickIssueCreate?: boolean;
};
export const CalendarWeekDays: React.FC<Props> = observer((props) => {
const { issues, week, quickActions, enableQuickIssueCreate } = props;
const { issues, week, handleIssues, quickActions, enableQuickIssueCreate } = props;
const { issueFilter: issueFilterStore } = useMobxStore();
@ -42,6 +43,7 @@ export const CalendarWeekDays: React.FC<Props> = observer((props) => {
key={renderDateFormat(date.date)}
date={date}
issues={issues}
handleIssues={handleIssues}
quickActions={quickActions}
enableQuickIssueCreate={enableQuickIssueCreate}
/>

View File

@ -1,29 +1,28 @@
import { useRouter } from "next/router";
// ui
import { Tooltip, StateGroupIcon } from "@plane/ui";
import { IssuePeekOverview } from "components/issues/issue-peek-overview";
import { IBlockUpdateData } from "components/gantt-chart";
// helpers
import { renderShortDate } from "helpers/date-time.helper";
// types
import { IIssue } from "types";
export const IssueGanttBlock = ({ data }: { data: IIssue }) => {
const router = useRouter();
const openPeekOverview = () => {
const { query } = router;
router.push({
pathname: router.pathname,
query: { ...query, peekIssue: data.id },
});
};
return (
export const IssueGanttBlock = ({
data,
handleIssue,
}: {
data: IIssue;
handleIssue: (block: IIssue, payload: IBlockUpdateData) => void;
}) => (
<IssuePeekOverview
workspaceSlug={data?.workspace_detail?.slug}
projectId={data?.project_detail?.id}
issueId={data?.id}
handleIssue={(issueToUpdate) => handleIssue({ ...data, ...issueToUpdate }, {})}
>
<div
className="flex items-center relative h-full w-full rounded cursor-pointer"
style={{ backgroundColor: data?.state_detail?.color }}
onClick={openPeekOverview}
>
<div className="absolute top-0 left-0 h-full w-full bg-custom-background-100/50" />
<Tooltip
@ -37,32 +36,36 @@ export const IssueGanttBlock = ({ data }: { data: IIssue }) => {
}
position="top-left"
>
<div className="relative text-custom-text-100 text-sm truncate py-1 px-2.5 w-full">{data?.name}</div>
<Tooltip tooltipHeading="Title" tooltipContent={data.name}>
<div className="relative text-custom-text-100 text-sm truncate py-1 px-2.5 w-full">{data?.name}</div>
</Tooltip>
</Tooltip>
</div>
);
};
</IssuePeekOverview>
);
// rendering issues on gantt sidebar
export const IssueGanttSidebarBlock = ({ data }: { data: IIssue }) => {
const router = useRouter();
const openPeekOverview = () => {
const { query } = router;
router.push({
pathname: router.pathname,
query: { ...query, peekIssue: data.id },
});
};
return (
<div className="relative w-full flex items-center gap-2 h-full cursor-pointer" onClick={openPeekOverview}>
export const IssueGanttSidebarBlock = ({
data,
handleIssue,
}: {
data: IIssue;
handleIssue: (block: IIssue, payload: IBlockUpdateData) => void;
}) => (
<IssuePeekOverview
workspaceSlug={data?.workspace_detail?.slug}
projectId={data?.project_detail?.id}
issueId={data?.id}
handleIssue={(issueToUpdate) => handleIssue({ ...data, ...issueToUpdate }, {})}
>
<div className="relative w-full flex items-center gap-2 h-full cursor-pointer">
<StateGroupIcon stateGroup={data?.state_detail?.group} color={data?.state_detail?.color} />
<div className="text-xs text-custom-text-300 flex-shrink-0">
{data?.project_detail?.identifier} {data?.sequence_id}
</div>
<h6 className="text-sm font-medium flex-grow truncate">{data?.name}</h6>
<Tooltip tooltipHeading="Title" tooltipContent={data.name}>
<span className="text-sm font-medium flex-grow truncate">{data?.name}</span>
</Tooltip>
</div>
);
};
</IssuePeekOverview>
);

View File

@ -8,6 +8,7 @@ import { GanttChartRoot, IBlockUpdateData, renderIssueBlocksStructure } from "co
import { IssueGanttBlock, IssueGanttSidebarBlock } from "components/issues";
// types
import { IIssueUnGroupedStructure } from "store/issue";
import { IIssue } from "types";
export const CycleGanttLayout: React.FC = observer(() => {
const router = useRouter();
@ -38,8 +39,8 @@ export const CycleGanttLayout: React.FC = observer(() => {
loaderTitle="Issues"
blocks={issues ? renderIssueBlocksStructure(issues as IIssueUnGroupedStructure) : null}
blockUpdateHandler={updateIssue}
BlockRender={IssueGanttBlock}
SidebarBlockRender={IssueGanttSidebarBlock}
blockToRender={(data: IIssue) => <IssueGanttBlock data={data} handleIssue={updateIssue} />}
sidebarBlockToRender={(data: IIssue) => <IssueGanttSidebarBlock data={data} handleIssue={updateIssue} />}
enableBlockLeftResize={isAllowed}
enableBlockRightResize={isAllowed}
enableBlockMove={isAllowed}

View File

@ -8,6 +8,7 @@ import { GanttChartRoot, IBlockUpdateData, renderIssueBlocksStructure } from "co
import { IssueGanttBlock, IssueGanttSidebarBlock } from "components/issues";
// types
import { IIssueUnGroupedStructure } from "store/issue";
import { IIssue } from "types";
export const ModuleGanttLayout: React.FC = observer(() => {
const router = useRouter();
@ -38,8 +39,8 @@ export const ModuleGanttLayout: React.FC = observer(() => {
loaderTitle="Issues"
blocks={issues ? renderIssueBlocksStructure(issues as IIssueUnGroupedStructure) : null}
blockUpdateHandler={updateIssue}
BlockRender={IssueGanttBlock}
SidebarBlockRender={IssueGanttSidebarBlock}
blockToRender={(data: IIssue) => <IssueGanttBlock data={data} handleIssue={updateIssue} />}
sidebarBlockToRender={(data: IIssue) => <IssueGanttSidebarBlock data={data} handleIssue={updateIssue} />}
enableBlockLeftResize={isAllowed}
enableBlockRightResize={isAllowed}
enableBlockMove={isAllowed}

View File

@ -10,6 +10,7 @@ import { GanttChartRoot, IBlockUpdateData, renderIssueBlocksStructure } from "co
import { IssueGanttBlock, IssueGanttSidebarBlock } from "components/issues";
// types
import { IIssueUnGroupedStructure } from "store/issue";
import { IIssue } from "types";
export const ProjectViewGanttLayout: React.FC = observer(() => {
const router = useRouter();
@ -40,8 +41,8 @@ export const ProjectViewGanttLayout: React.FC = observer(() => {
loaderTitle="Issues"
blocks={issues ? renderIssueBlocksStructure(issues as IIssueUnGroupedStructure) : null}
blockUpdateHandler={updateIssue}
BlockRender={IssueGanttBlock}
SidebarBlockRender={IssueGanttSidebarBlock}
blockToRender={(data: IIssue) => <IssueGanttBlock data={data} handleIssue={updateIssue} />}
sidebarBlockToRender={(data: IIssue) => <IssueGanttSidebarBlock data={data} handleIssue={updateIssue} />}
enableBlockLeftResize={isAllowed}
enableBlockRightResize={isAllowed}
enableBlockMove={isAllowed}

View File

@ -1,3 +1,4 @@
import React from "react";
import { useRouter } from "next/router";
import { observer } from "mobx-react-lite";
// hooks
@ -8,6 +9,7 @@ import { GanttChartRoot, IBlockUpdateData, renderIssueBlocksStructure } from "co
import { IssueGanttBlock, IssueGanttSidebarBlock } from "components/issues";
// types
import { IIssueUnGroupedStructure } from "store/issue";
import { IIssue } from "types";
export const GanttLayout: React.FC = observer(() => {
const router = useRouter();
@ -21,7 +23,7 @@ export const GanttLayout: React.FC = observer(() => {
const issues = issueStore.getIssues;
const updateIssue = (block: any, payload: IBlockUpdateData) => {
const updateIssue = (block: IIssue, payload: IBlockUpdateData) => {
if (!workspaceSlug) return;
issueStore.updateGanttIssueStructure(workspaceSlug.toString(), block, payload);
@ -38,8 +40,8 @@ export const GanttLayout: React.FC = observer(() => {
loaderTitle="Issues"
blocks={issues ? renderIssueBlocksStructure(issues as IIssueUnGroupedStructure) : null}
blockUpdateHandler={updateIssue}
BlockRender={IssueGanttBlock}
SidebarBlockRender={IssueGanttSidebarBlock}
blockToRender={(data: IIssue) => <IssueGanttBlock data={data} handleIssue={updateIssue} />}
sidebarBlockToRender={(data: IIssue) => <IssueGanttSidebarBlock data={data} handleIssue={updateIssue} />}
enableBlockLeftResize={isAllowed}
enableBlockRightResize={isAllowed}
enableBlockMove={isAllowed}

View File

@ -1,6 +1,8 @@
import { Draggable } from "@hello-pangea/dnd";
// components
import { KanBanProperties } from "./properties";
import { Tooltip } from "@plane/ui";
import { IssuePeekOverview } from "components/issues/issue-peek-overview";
// types
import { IIssueDisplayProperties, IIssue } from "types";
@ -57,7 +59,23 @@ export const KanbanIssueBlock: React.FC<IssueBlockProps> = (props) => {
{issue.project_detail.identifier}-{issue.sequence_id}
</div>
)}
<div className="line-clamp-2 text-sm font-medium text-custom-text-100">{issue.name}</div>
<IssuePeekOverview
workspaceSlug={issue?.workspace_detail?.slug}
projectId={issue?.project_detail?.id}
issueId={issue?.id}
handleIssue={(issueToUpdate) => {
handleIssues(
!sub_group_id && sub_group_id === "null" ? null : sub_group_id,
!columnId && columnId === "null" ? null : columnId,
{ ...issue, ...issueToUpdate },
"update"
);
}}
>
<Tooltip tooltipHeading="Title" tooltipContent={issue.name}>
<div className="line-clamp-2 text-sm font-medium text-custom-text-100">{issue.name}</div>
</Tooltip>
</IssuePeekOverview>
<div>
<KanBanProperties
sub_group_id={sub_group_id}

View File

@ -36,8 +36,9 @@ export const IssueBlock: React.FC<IssueBlockProps> = (props) => {
workspaceSlug={issue?.workspace_detail?.slug}
projectId={issue?.project_detail?.id}
issueId={issue?.id}
// TODO: add the logic here
handleIssue={() => {}}
handleIssue={(issueToUpdate) => {
handleIssues(!columnId && columnId === "null" ? null : columnId, issueToUpdate as IIssue, "update");
}}
>
<Tooltip tooltipHeading="Title" tooltipContent={issue.name}>
<div className="line-clamp-1 text-sm font-medium text-custom-text-100 w-full">{issue.name}</div>

View File

@ -4,6 +4,9 @@ import { Popover2 } from "@blueprintjs/popover2";
import { MoreHorizontal, Pencil, Trash2, ChevronRight, Link } from "lucide-react";
// hooks
import useToast from "hooks/use-toast";
// components
import { IssuePeekOverview } from "components/issues/issue-peek-overview";
import { Tooltip } from "@plane/ui";
// helpers
import { copyUrlToClipboard } from "helpers/string.helper";
// types
@ -13,6 +16,7 @@ type Props = {
issue: IIssue;
expanded: boolean;
handleToggleExpand: (issueId: string) => void;
handleUpdateIssue: (issue: IIssue, data: Partial<IIssue>) => void;
properties: IIssueDisplayProperties;
handleEditIssue: (issue: IIssue) => void;
handleDeleteIssue: (issue: IIssue) => void;
@ -24,6 +28,7 @@ export const IssueColumn: React.FC<Props> = ({
issue,
expanded,
handleToggleExpand,
handleUpdateIssue,
properties,
handleEditIssue,
handleDeleteIssue,
@ -38,15 +43,6 @@ export const IssueColumn: React.FC<Props> = ({
const { setToastAlert } = useToast();
const openPeekOverview = () => {
const { query } = router;
router.push({
pathname: router.pathname,
query: { ...query, peekIssue: issue.id },
});
};
const handleCopyText = () => {
copyUrlToClipboard(`${workspaceSlug}/projects/${issue.project}/issues/${issue.id}`).then(() => {
setToastAlert({
@ -142,15 +138,20 @@ export const IssueColumn: React.FC<Props> = ({
)}
</div>
)}
<span className="flex items-center px-4 py-2.5 h-full truncate flex-grow">
<button
type="button"
className="truncate text-custom-text-100 text-left cursor-pointer w-full text-[0.825rem]"
onClick={openPeekOverview}
>
{issue.name}
</button>
</span>
<IssuePeekOverview
workspaceSlug={issue?.workspace_detail?.slug}
projectId={issue?.project_detail?.id}
issueId={issue?.id}
handleIssue={(issueToUpdate) => handleUpdateIssue(issueToUpdate as IIssue, issueToUpdate)}
>
<Tooltip tooltipHeading="Title" tooltipContent={issue.name}>
<span className="flex items-center px-4 py-2.5 h-full truncate flex-grow">
<div className="truncate text-custom-text-100 text-left cursor-pointer w-full text-[0.825rem]">
{issue.name}
</div>
</span>
</Tooltip>
</IssuePeekOverview>
</div>
);
};

View File

@ -11,6 +11,7 @@ type Props = {
issue: IIssue;
expandedIssues: string[];
setExpandedIssues: React.Dispatch<React.SetStateAction<string[]>>;
handleUpdateIssue: (issue: IIssue, data: Partial<IIssue>) => void;
properties: IIssueDisplayProperties;
handleIssueAction: (issue: IIssue, action: "copy" | "delete" | "edit") => void;
disableUserActions: boolean;
@ -21,6 +22,7 @@ export const SpreadsheetIssuesColumn: React.FC<Props> = ({
issue,
expandedIssues,
setExpandedIssues,
handleUpdateIssue,
properties,
handleIssueAction,
disableUserActions,
@ -49,6 +51,7 @@ export const SpreadsheetIssuesColumn: React.FC<Props> = ({
expanded={isExpanded}
handleToggleExpand={handleToggleExpand}
properties={properties}
handleUpdateIssue={handleUpdateIssue}
handleEditIssue={() => handleIssueAction(issue, "edit")}
handleDeleteIssue={() => handleIssueAction(issue, "delete")}
disableUserActions={disableUserActions}
@ -64,6 +67,7 @@ export const SpreadsheetIssuesColumn: React.FC<Props> = ({
key={subIssue.id}
issue={subIssue}
expandedIssues={expandedIssues}
handleUpdateIssue={handleUpdateIssue}
setExpandedIssues={setExpandedIssues}
properties={properties}
handleIssueAction={handleIssueAction}

View File

@ -104,6 +104,7 @@ export const SpreadsheetView: React.FC<Props> = observer((props) => {
key={`${issue.id}_${index}`}
issue={issue}
expandedIssues={expandedIssues}
handleUpdateIssue={handleUpdateIssue}
setExpandedIssues={setExpandedIssues}
properties={displayProperties}
handleIssueAction={handleIssueAction}

View File

@ -22,10 +22,7 @@ export const IssuePeekOverview: FC<IIssuePeekOverview> = observer((props) => {
const { issueDetail: issueDetailStore }: RootStore = useMobxStore();
const issueUpdate = (_data: Partial<IIssue>) => {
if (handleIssue) {
handleIssue(_data);
issueDetailStore.updateIssue(workspaceSlug, projectId, issueId, _data);
}
handleIssue(_data);
};
const issueReactionCreate = (reaction: string) =>

View File

@ -1,12 +1,9 @@
import React from "react";
// next imports
import { useRouter } from "next/router";
// swr
import { mutate } from "swr";
// lucide icons
import { ChevronDown, ChevronRight, X, Pencil, Trash, Link as LinkIcon, Loader } from "lucide-react";
// components
import { IssuePeekOverview } from "components/issues/peek-overview";
import { IssuePeekOverview } from "../issue-peek-overview";
import { SubIssuesRootList } from "./issues-list";
import { IssueProperty } from "./properties";
// ui
@ -15,7 +12,6 @@ import { CustomMenu, Tooltip } from "@plane/ui";
import { IUser, IIssue } from "types";
import { ISubIssuesRootLoaders, ISubIssuesRootLoadersHandler } from "./root";
// fetch keys
import { SUB_ISSUES } from "constants/fetch-keys";
export interface ISubIssues {
workspaceSlug: string;
@ -34,6 +30,7 @@ export interface ISubIssues {
issueId: string,
issue?: IIssue | null
) => void;
handleUpdateIssue: (issue: IIssue, data: Partial<IIssue>) => void;
}
export const SubIssues: React.FC<ISubIssues> = ({
@ -49,49 +46,47 @@ export const SubIssues: React.FC<ISubIssues> = ({
handleIssuesLoader,
copyText,
handleIssueCrudOperation,
}) => {
const router = useRouter();
const { query } = router;
const { peekIssue } = query as { peekIssue: string };
handleUpdateIssue,
}) => (
<div>
{issue && (
<div
className="relative flex items-center gap-2 py-1 px-2 w-full h-full hover:bg-custom-background-90 group transition-all border-b border-custom-border-100"
style={{ paddingLeft: `${spacingLeft}px` }}
>
<div className="flex-shrink-0 w-[22px] h-[22px]">
{issue?.sub_issues_count > 0 && (
<>
{issuesLoader.sub_issues.includes(issue?.id) ? (
<div className="w-full h-full flex justify-center items-center rounded-sm bg-custom-background-80 transition-all cursor-not-allowed">
<Loader width={14} strokeWidth={2} className="animate-spin" />
</div>
) : (
<div
className="w-full h-full flex justify-center items-center rounded-sm hover:bg-custom-background-80 transition-all cursor-pointer"
onClick={() => handleIssuesLoader({ key: "visibility", issueId: issue?.id })}
>
{issuesLoader && issuesLoader.visibility.includes(issue?.id) ? (
<ChevronDown width={14} strokeWidth={2} />
) : (
<ChevronRight width={14} strokeWidth={2} />
)}
</div>
)}
</>
)}
</div>
const openPeekOverview = (issue_id: string) => {
router.push({
pathname: router.pathname,
query: { ...query, peekIssue: issue_id },
});
};
return (
<div>
{issue && (
<div
className="relative flex items-center gap-2 py-1 px-2 w-full h-full hover:bg-custom-background-90 group transition-all border-b border-custom-border-100"
style={{ paddingLeft: `${spacingLeft}px` }}
<IssuePeekOverview
workspaceSlug={issue?.workspace_detail?.slug}
projectId={issue?.project_detail?.id}
issueId={issue?.id}
handleIssue={(issueToUpdate) => {
console.log("issueToUpdate", issueToUpdate);
handleUpdateIssue(issue, { ...issue, ...issueToUpdate });
}}
>
<div className="flex-shrink-0 w-[22px] h-[22px]">
{issue?.sub_issues_count > 0 && (
<>
{issuesLoader.sub_issues.includes(issue?.id) ? (
<div className="w-full h-full flex justify-center items-center rounded-sm bg-custom-background-80 transition-all cursor-not-allowed">
<Loader width={14} strokeWidth={2} className="animate-spin" />
</div>
) : (
<div
className="w-full h-full flex justify-center items-center rounded-sm hover:bg-custom-background-80 transition-all cursor-pointer"
onClick={() => handleIssuesLoader({ key: "visibility", issueId: issue?.id })}
>
{issuesLoader && issuesLoader.visibility.includes(issue?.id) ? (
<ChevronDown width={14} strokeWidth={2} />
) : (
<ChevronRight width={14} strokeWidth={2} />
)}
</div>
)}
</>
)}
</div>
<div className="w-full flex items-center gap-2 cursor-pointer" onClick={() => openPeekOverview(issue?.id)}>
<div className="w-full flex items-center gap-2 cursor-pointer">
<div
className="flex-shrink-0 w-[6px] h-[6px] rounded-full"
style={{
@ -105,94 +100,86 @@ export const SubIssues: React.FC<ISubIssues> = ({
<div className="line-clamp-1 text-xs text-custom-text-100 pr-2">{issue?.name}</div>
</Tooltip>
</div>
</IssuePeekOverview>
<div className="flex-shrink-0 text-sm">
<IssueProperty
workspaceSlug={workspaceSlug}
parentIssue={parentIssue}
issue={issue}
user={user}
editable={editable}
/>
</div>
<div className="flex-shrink-0 text-sm">
<IssueProperty
workspaceSlug={workspaceSlug}
parentIssue={parentIssue}
issue={issue}
user={user}
editable={editable}
/>
</div>
<div className="flex-shrink-0 text-sm">
<CustomMenu width="auto" ellipsis>
{editable && (
<CustomMenu.MenuItem onClick={() => handleIssueCrudOperation("edit", parentIssue?.id, issue)}>
<div className="flex items-center justify-start gap-2">
<Pencil width={14} strokeWidth={2} />
<span>Edit issue</span>
</div>
</CustomMenu.MenuItem>
)}
{editable && (
<CustomMenu.MenuItem onClick={() => handleIssueCrudOperation("delete", parentIssue?.id, issue)}>
<div className="flex items-center justify-start gap-2">
<Trash width={14} strokeWidth={2} />
<span>Delete issue</span>
</div>
</CustomMenu.MenuItem>
)}
<CustomMenu.MenuItem
onClick={() => copyText(`${workspaceSlug}/projects/${issue.project}/issues/${issue.id}`)}
>
<div className="flex-shrink-0 text-sm">
<CustomMenu width="auto" ellipsis>
{editable && (
<CustomMenu.MenuItem onClick={() => handleIssueCrudOperation("edit", parentIssue?.id, issue)}>
<div className="flex items-center justify-start gap-2">
<LinkIcon width={14} strokeWidth={2} />
<span>Copy issue link</span>
<Pencil width={14} strokeWidth={2} />
<span>Edit issue</span>
</div>
</CustomMenu.MenuItem>
</CustomMenu>
</div>
)}
{editable && (
<>
{issuesLoader.delete.includes(issue?.id) ? (
<div className="flex-shrink-0 w-[22px] h-[22px] rounded-sm bg-red-200/10 text-red-500 transition-all cursor-not-allowed overflow-hidden flex justify-center items-center">
<Loader width={14} strokeWidth={2} className="animate-spin" />
{editable && (
<CustomMenu.MenuItem onClick={() => handleIssueCrudOperation("delete", parentIssue?.id, issue)}>
<div className="flex items-center justify-start gap-2">
<Trash width={14} strokeWidth={2} />
<span>Delete issue</span>
</div>
) : (
<div
className="flex-shrink-0 invisible group-hover:visible w-[22px] h-[22px] rounded-sm hover:bg-custom-background-80 transition-all cursor-pointer overflow-hidden flex justify-center items-center"
onClick={() => {
handleIssuesLoader({ key: "delete", issueId: issue?.id });
removeIssueFromSubIssues(parentIssue?.id, issue);
}}
>
<X width={14} strokeWidth={2} />
</div>
)}
</>
)}
</CustomMenu.MenuItem>
)}
<CustomMenu.MenuItem
onClick={() => copyText(`${workspaceSlug}/projects/${issue.project}/issues/${issue.id}`)}
>
<div className="flex items-center justify-start gap-2">
<LinkIcon width={14} strokeWidth={2} />
<span>Copy issue link</span>
</div>
</CustomMenu.MenuItem>
</CustomMenu>
</div>
)}
{issuesLoader.visibility.includes(issue?.id) && issue?.sub_issues_count > 0 && (
<SubIssuesRootList
workspaceSlug={workspaceSlug}
projectId={projectId}
parentIssue={issue}
spacingLeft={spacingLeft + 22}
user={user}
editable={editable}
removeIssueFromSubIssues={removeIssueFromSubIssues}
issuesLoader={issuesLoader}
handleIssuesLoader={handleIssuesLoader}
copyText={copyText}
handleIssueCrudOperation={handleIssueCrudOperation}
/>
)}
{editable && (
<>
{issuesLoader.delete.includes(issue?.id) ? (
<div className="flex-shrink-0 w-[22px] h-[22px] rounded-sm bg-red-200/10 text-red-500 transition-all cursor-not-allowed overflow-hidden flex justify-center items-center">
<Loader width={14} strokeWidth={2} className="animate-spin" />
</div>
) : (
<div
className="flex-shrink-0 invisible group-hover:visible w-[22px] h-[22px] rounded-sm hover:bg-custom-background-80 transition-all cursor-pointer overflow-hidden flex justify-center items-center"
onClick={() => {
handleIssuesLoader({ key: "delete", issueId: issue?.id });
removeIssueFromSubIssues(parentIssue?.id, issue);
}}
>
<X width={14} strokeWidth={2} />
</div>
)}
</>
)}
</div>
)}
{peekIssue && peekIssue === issue?.id && (
<IssuePeekOverview
handleMutation={() => parentIssue && parentIssue?.id && mutate(SUB_ISSUES(parentIssue?.id))}
projectId={issue?.project ?? ""}
workspaceSlug={workspaceSlug ?? ""}
readOnly={!editable}
/>
)}
</div>
);
};
{issuesLoader.visibility.includes(issue?.id) && issue?.sub_issues_count > 0 && (
<SubIssuesRootList
workspaceSlug={workspaceSlug}
projectId={projectId}
parentIssue={issue}
spacingLeft={spacingLeft + 22}
user={user}
editable={editable}
removeIssueFromSubIssues={removeIssueFromSubIssues}
issuesLoader={issuesLoader}
handleIssuesLoader={handleIssuesLoader}
copyText={copyText}
handleIssueCrudOperation={handleIssueCrudOperation}
handleUpdateIssue={handleUpdateIssue}
/>
)}
</div>
);

View File

@ -27,6 +27,7 @@ export interface ISubIssuesRootList {
issueId: string,
issue?: IIssue | null
) => void;
handleUpdateIssue: (issue: IIssue, data: Partial<IIssue>) => void;
}
const issueService = new IssueService();
@ -43,6 +44,7 @@ export const SubIssuesRootList: React.FC<ISubIssuesRootList> = ({
handleIssuesLoader,
copyText,
handleIssueCrudOperation,
handleUpdateIssue,
}) => {
const { data: issues, isLoading } = useSWR(
workspaceSlug && projectId && parentIssue && parentIssue?.id ? SUB_ISSUES(parentIssue?.id) : null,
@ -82,6 +84,7 @@ export const SubIssuesRootList: React.FC<ISubIssuesRootList> = ({
handleIssuesLoader={handleIssuesLoader}
copyText={copyText}
handleIssueCrudOperation={handleIssueCrudOperation}
handleUpdateIssue={handleUpdateIssue}
/>
))}

View File

@ -1,4 +1,4 @@
import React from "react";
import React, { useCallback } from "react";
// next imports
import { useRouter } from "next/router";
// swr
@ -13,6 +13,7 @@ import { ProgressBar } from "./progressbar";
// ui
import { CustomMenu } from "@plane/ui";
// hooks
import { useMobxStore } from "lib/mobx/store-provider";
import { useProjectMyMembership } from "contexts/project-member.context";
import useToast from "hooks/use-toast";
// helpers
@ -49,6 +50,8 @@ export const SubIssuesRoot: React.FC<ISubIssuesRoot> = ({ parentIssue, user }) =
peekIssue: string;
};
const { issue: issueStore, issueDetail: issueDetailStore } = useMobxStore();
const { memberRole } = useProjectMyMembership();
const { setToastAlert } = useToast();
@ -158,6 +161,21 @@ export const SubIssuesRoot: React.FC<ISubIssuesRoot> = ({ parentIssue, user }) =
});
};
const handleUpdateIssue = useCallback(
(issue: IIssue, data: Partial<IIssue>) => {
if (!workspaceSlug || !projectId || !user) return;
const payload = {
...issue,
...data,
};
issueStore.updateIssueStructure(null, null, payload);
issueDetailStore.updateIssue(workspaceSlug.toString(), projectId.toString(), issue.id, data);
},
[issueStore, issueDetailStore, projectId, user, workspaceSlug]
);
const isEditable = memberRole?.isGuest || memberRole?.isViewer ? false : true;
const mutateSubIssues = (parentIssueId: string | null) => {
@ -228,6 +246,7 @@ export const SubIssuesRoot: React.FC<ISubIssuesRoot> = ({ parentIssue, user }) =
handleIssuesLoader={handleIssuesLoader}
copyText={copyText}
handleIssueCrudOperation={handleIssueCrudOperation}
handleUpdateIssue={handleUpdateIssue}
/>
</div>
)}

View File

@ -45,8 +45,8 @@ export const ModulesListGanttChartView: React.FC = observer(() => {
loaderTitle="Modules"
blocks={modules ? blockFormat(modules) : null}
blockUpdateHandler={(block, payload) => handleModuleUpdate(block, payload)}
SidebarBlockRender={ModuleGanttSidebarBlock}
BlockRender={ModuleGanttBlock}
sidebarBlockToRender={ModuleGanttSidebarBlock}
blockToRender={(data: IModule) => <ModuleGanttBlock data={data} />}
enableBlockLeftResize={isAllowed}
enableBlockRightResize={isAllowed}
enableBlockMove={isAllowed}

View File

@ -265,9 +265,10 @@ export class IssueStore implements IIssueStore {
...i,
...(i.id === issue.id
? {
...issue,
sort_order: payload.sort_order?.newSortOrder ?? i.sort_order,
start_date: payload.start_date,
target_date: payload.target_date,
start_date: payload.start_date ?? i.start_date,
target_date: payload.target_date ?? i.target_date,
}
: {}),
}));
@ -288,7 +289,7 @@ export class IssueStore implements IIssueStore {
};
});
const newPayload: any = { ...payload };
const newPayload: any = { ...issue, ...payload };
if (newPayload.sort_order && payload.sort_order) newPayload.sort_order = payload.sort_order.newSortOrder;