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" loaderTitle="Cycles"
blocks={cycles ? blockFormat(cycles) : null} blocks={cycles ? blockFormat(cycles) : null}
blockUpdateHandler={(block, payload) => handleCycleUpdate(block, payload)} blockUpdateHandler={(block, payload) => handleCycleUpdate(block, payload)}
SidebarBlockRender={CycleGanttSidebarBlock} blockToRender={(data: ICycle) => <CycleGanttBlock data={data} />}
BlockRender={CycleGanttBlock} sidebarBlockToRender={(data: ICycle) => <CycleGanttSidebarBlock data={data} />}
enableBlockLeftResize={false} enableBlockLeftResize={false}
enableBlockRightResize={false} enableBlockRightResize={false}
enableBlockMove={false} enableBlockMove={false}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -2,16 +2,20 @@ import Link from "next/link";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { Draggable } from "@hello-pangea/dnd"; import { Draggable } from "@hello-pangea/dnd";
// components
import { IssuePeekOverview } from "components/issues/issue-peek-overview";
import { Tooltip } from "@plane/ui";
// types // types
import { IIssue } from "types"; import { IIssue } from "types";
type Props = { type Props = {
issues: IIssue[] | null; issues: IIssue[] | null;
handleIssues: (date: string, issue: IIssue, action: "update" | "delete") => void;
quickActions: (issue: IIssue) => React.ReactNode; quickActions: (issue: IIssue) => React.ReactNode;
}; };
export const CalendarIssueBlocks: React.FC<Props> = observer((props) => { export const CalendarIssueBlocks: React.FC<Props> = observer((props) => {
const { issues, quickActions } = props; const { issues, handleIssues, quickActions } = props;
const router = useRouter(); const router = useRouter();
const { workspaceSlug } = router.query; 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"> <div className="text-xs text-custom-text-300 flex-shrink-0">
{issue.project_detail.identifier}-{issue.sequence_id} {issue.project_detail.identifier}-{issue.sequence_id}
</div> </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> <div className="hidden group-hover/calendar-block:block">{quickActions(issue)}</div>
</a> </a>
</Link> </Link>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,8 @@
import { Draggable } from "@hello-pangea/dnd"; import { Draggable } from "@hello-pangea/dnd";
// components // components
import { KanBanProperties } from "./properties"; import { KanBanProperties } from "./properties";
import { Tooltip } from "@plane/ui";
import { IssuePeekOverview } from "components/issues/issue-peek-overview";
// types // types
import { IIssueDisplayProperties, IIssue } from "types"; import { IIssueDisplayProperties, IIssue } from "types";
@ -57,7 +59,23 @@ export const KanbanIssueBlock: React.FC<IssueBlockProps> = (props) => {
{issue.project_detail.identifier}-{issue.sequence_id} {issue.project_detail.identifier}-{issue.sequence_id}
</div> </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> <div>
<KanBanProperties <KanBanProperties
sub_group_id={sub_group_id} sub_group_id={sub_group_id}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -265,9 +265,10 @@ export class IssueStore implements IIssueStore {
...i, ...i,
...(i.id === issue.id ...(i.id === issue.id
? { ? {
...issue,
sort_order: payload.sort_order?.newSortOrder ?? i.sort_order, sort_order: payload.sort_order?.newSortOrder ?? i.sort_order,
start_date: payload.start_date, start_date: payload.start_date ?? i.start_date,
target_date: payload.target_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; if (newPayload.sort_order && payload.sort_order) newPayload.sort_order = payload.sort_order.newSortOrder;