dev: show all issues on the gantt chart (#3487)

This commit is contained in:
Aaryan Khandelwal 2024-01-30 14:25:15 +05:30 committed by GitHub
parent d53a086206
commit 9debd81a50
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
30 changed files with 234 additions and 166 deletions

View File

@ -64,8 +64,7 @@ export type TIssueParams =
| "order_by" | "order_by"
| "type" | "type"
| "sub_issue" | "sub_issue"
| "show_empty_groups" | "show_empty_groups";
| "start_target_date";
export type TCalendarLayouts = "month" | "week"; export type TCalendarLayouts = "month" | "week";
@ -93,7 +92,6 @@ export interface IIssueDisplayFilterOptions {
layout?: TIssueLayouts; layout?: TIssueLayouts;
order_by?: TIssueOrderByOptions; order_by?: TIssueOrderByOptions;
show_empty_groups?: boolean; show_empty_groups?: boolean;
start_target_date?: boolean;
sub_issue?: boolean; sub_issue?: boolean;
type?: TIssueTypeFilters; type?: TIssueTypeFilters;
} }

View File

@ -40,9 +40,9 @@ export const IssueLink = ({ activity }: { activity: IIssueActivity }) => {
}`}`} }`}`}
target={activity.issue === null ? "_self" : "_blank"} target={activity.issue === null ? "_self" : "_blank"}
rel={activity.issue === null ? "" : "noopener noreferrer"} rel={activity.issue === null ? "" : "noopener noreferrer"}
className="inline-flex items-center gap-1 font-medium text-custom-text-100 hover:underline whitespace-nowrap" className="inline-flex items-center gap-1 font-medium text-custom-text-100 hover:underline"
> >
{`${activity.project_detail.identifier}-${activity.issue_detail.sequence_id}`}{" "} <span className="whitespace-nowrap">{`${activity.project_detail.identifier}-${activity.issue_detail.sequence_id}`}</span>{" "}
<span className="font-normal">{activity.issue_detail?.name}</span> <span className="font-normal">{activity.issue_detail?.name}</span>
</a> </a>
) : ( ) : (
@ -267,7 +267,7 @@ const activityDetails: {
<span className="flex-shrink truncate font-medium text-custom-text-100">{activity.new_value}</span> <span className="flex-shrink truncate font-medium text-custom-text-100">{activity.new_value}</span>
</span> </span>
{showIssue && ( {showIssue && (
<span> <span className="">
{" "} {" "}
to <IssueLink activity={activity} /> to <IssueLink activity={activity} />
</span> </span>

View File

@ -86,7 +86,7 @@ export const OverviewStatsWidget: React.FC<WidgetProps> = observer((props) => {
)} )}
> >
<h5 className="font-semibold text-xl">{stat.count}</h5> <h5 className="font-semibold text-xl">{stat.count}</h5>
<p className="text-custom-text-300">{stat.title}</p> <p className="text-custom-text-300 text-sm xl:text-base">{stat.title}</p>
</Link> </Link>
</div> </div>
); );

View File

@ -1,23 +0,0 @@
import React, { useState, useEffect } from "react";
// react beautiful dnd
import { Droppable, DroppableProps } from "@hello-pangea/dnd";
const StrictModeDroppable = ({ children, ...props }: DroppableProps) => {
const [enabled, setEnabled] = useState(false);
useEffect(() => {
const animation = requestAnimationFrame(() => setEnabled(true));
return () => {
cancelAnimationFrame(animation);
setEnabled(false);
};
}, []);
if (!enabled) return null;
return <Droppable {...props}>{children}</Droppable>;
};
export default StrictModeDroppable;

View File

@ -2,7 +2,7 @@ import { FC } from "react";
// hooks // hooks
import { useChart } from "../hooks"; import { useChart } from "../hooks";
// helpers // helpers
import { ChartDraggable } from "../helpers/draggable"; import { ChartAddBlock, ChartDraggable } from "components/gantt-chart";
import { renderFormattedPayloadDate } from "helpers/date-time.helper"; import { renderFormattedPayloadDate } from "helpers/date-time.helper";
// types // types
import { IBlockUpdateData, IGanttBlock } from "../types"; import { IBlockUpdateData, IGanttBlock } from "../types";
@ -15,6 +15,7 @@ export type GanttChartBlocksProps = {
enableBlockLeftResize: boolean; enableBlockLeftResize: boolean;
enableBlockRightResize: boolean; enableBlockRightResize: boolean;
enableBlockMove: boolean; enableBlockMove: boolean;
showAllBlocks: boolean;
}; };
export const GanttChartBlocks: FC<GanttChartBlocksProps> = (props) => { export const GanttChartBlocks: FC<GanttChartBlocksProps> = (props) => {
@ -26,6 +27,7 @@ export const GanttChartBlocks: FC<GanttChartBlocksProps> = (props) => {
enableBlockLeftResize, enableBlockLeftResize,
enableBlockRightResize, enableBlockRightResize,
enableBlockMove, enableBlockMove,
showAllBlocks,
} = props; } = props;
const { activeBlock, dispatch } = useChart(); const { activeBlock, dispatch } = useChart();
@ -45,6 +47,8 @@ export const GanttChartBlocks: FC<GanttChartBlocksProps> = (props) => {
totalBlockShifts: number, totalBlockShifts: number,
dragDirection: "left" | "right" | "move" dragDirection: "left" | "right" | "move"
) => { ) => {
if (!block.start_date || !block.target_date) return;
const originalStartDate = new Date(block.start_date); const originalStartDate = new Date(block.start_date);
const updatedStartDate = new Date(originalStartDate); const updatedStartDate = new Date(originalStartDate);
@ -75,27 +79,31 @@ export const GanttChartBlocks: FC<GanttChartBlocksProps> = (props) => {
> >
{blocks && {blocks &&
blocks.length > 0 && blocks.length > 0 &&
blocks.map( blocks.map((block) => {
(block) => // hide the block if it doesn't have start and target dates and showAllBlocks is false
block.start_date && if (!showAllBlocks && !(block.start_date && block.target_date)) return;
block.target_date && (
<div const isBlockVisibleOnChart = block.start_date && block.target_date;
key={`block-${block.id}`}
className={`h-11 ${activeBlock?.id === block.id ? "bg-custom-background-80" : ""}`} return (
onMouseEnter={() => updateActiveBlock(block)} <div
onMouseLeave={() => updateActiveBlock(null)} key={`block-${block.id}`}
> className={`h-11 ${activeBlock?.id === block.id ? "bg-custom-background-80" : ""}`}
<ChartDraggable onMouseEnter={() => updateActiveBlock(block)}
block={block} onMouseLeave={() => updateActiveBlock(null)}
blockToRender={blockToRender} >
handleBlock={(...args) => handleChartBlockPosition(block, ...args)} {!isBlockVisibleOnChart && <ChartAddBlock block={block} blockUpdateHandler={blockUpdateHandler} />}
enableBlockLeftResize={enableBlockLeftResize} <ChartDraggable
enableBlockRightResize={enableBlockRightResize} block={block}
enableBlockMove={enableBlockMove} blockToRender={blockToRender}
/> handleBlock={(...args) => handleChartBlockPosition(block, ...args)}
</div> enableBlockLeftResize={enableBlockLeftResize}
) enableBlockRightResize={enableBlockRightResize}
)} enableBlockMove={enableBlockMove}
/>
</div>
);
})}
</div> </div>
); );
}; };

View File

@ -46,22 +46,25 @@ type ChartViewRootProps = {
enableBlockMove: boolean; enableBlockMove: boolean;
enableReorder: boolean; enableReorder: boolean;
bottomSpacing: boolean; bottomSpacing: boolean;
showAllBlocks: boolean;
}; };
export const ChartViewRoot: FC<ChartViewRootProps> = ({ export const ChartViewRoot: FC<ChartViewRootProps> = (props) => {
border, const {
title, border,
blocks = null, title,
loaderTitle, blocks = null,
blockUpdateHandler, loaderTitle,
sidebarToRender, blockUpdateHandler,
blockToRender, sidebarToRender,
enableBlockLeftResize, blockToRender,
enableBlockRightResize, enableBlockLeftResize,
enableBlockMove, enableBlockRightResize,
enableReorder, enableBlockMove,
bottomSpacing, enableReorder,
}) => { bottomSpacing,
showAllBlocks,
} = props;
// states // states
const [itemsContainerWidth, setItemsContainerWidth] = useState<number>(0); const [itemsContainerWidth, setItemsContainerWidth] = useState<number>(0);
const [fullScreenMode, setFullScreenMode] = useState<boolean>(false); const [fullScreenMode, setFullScreenMode] = useState<boolean>(false);
@ -311,6 +314,7 @@ export const ChartViewRoot: FC<ChartViewRootProps> = ({
enableBlockLeftResize={enableBlockLeftResize} enableBlockLeftResize={enableBlockLeftResize}
enableBlockRightResize={enableBlockRightResize} enableBlockRightResize={enableBlockRightResize}
enableBlockMove={enableBlockMove} enableBlockMove={enableBlockMove}
showAllBlocks={showAllBlocks}
/> />
)} )}
</div> </div>

View File

@ -1,5 +1,4 @@
import { FC } from "react"; import { FC } from "react";
// hooks // hooks
import { useChart } from "../hooks"; import { useChart } from "../hooks";
// types // types

View File

@ -0,0 +1,91 @@
import { useEffect, useRef, useState } from "react";
import { addDays } from "date-fns";
import { Plus } from "lucide-react";
// hooks
import { useChart } from "../hooks";
// ui
import { Tooltip } from "@plane/ui";
// helpers
import { renderFormattedDate, renderFormattedPayloadDate } from "helpers/date-time.helper";
// types
import { IBlockUpdateData, IGanttBlock } from "../types";
type Props = {
block: IGanttBlock;
blockUpdateHandler: (block: any, payload: IBlockUpdateData) => void;
};
export const ChartAddBlock: React.FC<Props> = (props) => {
const { block, blockUpdateHandler } = props;
// states
const [isButtonVisible, setIsButtonVisible] = useState(false);
const [buttonXPosition, setButtonXPosition] = useState(0);
const [buttonStartDate, setButtonStartDate] = useState<Date | null>(null);
// refs
const containerRef = useRef<HTMLDivElement>(null);
// chart hook
const { currentViewData } = useChart();
const handleButtonClick = () => {
if (!currentViewData) return;
const { startDate: chartStartDate, width } = currentViewData.data;
const columnNumber = buttonXPosition / width;
const startDate = addDays(chartStartDate, columnNumber);
const endDate = addDays(startDate, 1);
blockUpdateHandler(block.data, {
start_date: renderFormattedPayloadDate(startDate) ?? undefined,
target_date: renderFormattedPayloadDate(endDate) ?? undefined,
});
};
useEffect(() => {
const container = containerRef.current;
if (!container) return;
const handleMouseMove = (e: MouseEvent) => {
if (!currentViewData) return;
setButtonXPosition(e.offsetX);
const { startDate: chartStartDate, width } = currentViewData.data;
const columnNumber = buttonXPosition / width;
const startDate = addDays(chartStartDate, columnNumber);
setButtonStartDate(startDate);
};
container.addEventListener("mousemove", handleMouseMove);
return () => {
container?.removeEventListener("mousemove", handleMouseMove);
};
}, [buttonXPosition, currentViewData]);
return (
<div
className="relative h-full w-full"
onMouseEnter={() => setIsButtonVisible(true)}
onMouseLeave={() => setIsButtonVisible(false)}
>
<div ref={containerRef} className="h-full w-full" />
{isButtonVisible && (
<Tooltip tooltipContent={buttonStartDate && renderFormattedDate(buttonStartDate)}>
<button
type="button"
className="absolute top-1/2 -translate-x-1/2 -translate-y-1/2 h-8 w-8 bg-custom-background-80 p-1.5 rounded border border-custom-border-300 grid place-items-center text-custom-text-200 hover:text-custom-text-100"
style={{
marginLeft: `${buttonXPosition}px`,
}}
onClick={handleButtonClick}
>
<Plus className="h-3.5 w-3.5" />
</button>
</Tooltip>
)}
</div>
);
};

View File

@ -3,14 +3,11 @@ import { TIssue } from "@plane/types";
import { IGanttBlock } from "components/gantt-chart"; import { IGanttBlock } from "components/gantt-chart";
export const renderIssueBlocksStructure = (blocks: TIssue[]): IGanttBlock[] => export const renderIssueBlocksStructure = (blocks: TIssue[]): IGanttBlock[] =>
blocks && blocks.length > 0 blocks &&
? blocks blocks.map((block) => ({
.filter((b) => new Date(b?.start_date ?? "") <= new Date(b?.target_date ?? "")) data: block,
.map((block) => ({ id: block.id,
data: block, sort_order: block.sort_order,
id: block.id, start_date: block.start_date ? new Date(block.start_date) : null,
sort_order: block.sort_order, target_date: block.target_date ? new Date(block.target_date) : null,
start_date: new Date(block.start_date ?? ""), }));
target_date: new Date(block.target_date ?? ""),
}))
: [];

View File

@ -1,6 +1,4 @@
import React, { useEffect, useRef, useState } from "react"; import React, { useEffect, useRef, useState } from "react";
// icons
import { ArrowLeft, ArrowRight } from "lucide-react"; import { ArrowLeft, ArrowRight } from "lucide-react";
// hooks // hooks
import { useChart } from "../hooks"; import { useChart } from "../hooks";
@ -16,23 +14,17 @@ type Props = {
enableBlockMove: boolean; enableBlockMove: boolean;
}; };
export const ChartDraggable: React.FC<Props> = ({ export const ChartDraggable: React.FC<Props> = (props) => {
block, const { block, blockToRender, handleBlock, enableBlockLeftResize, enableBlockRightResize, enableBlockMove } = props;
blockToRender, // states
handleBlock,
enableBlockLeftResize,
enableBlockRightResize,
enableBlockMove,
}) => {
const [isLeftResizing, setIsLeftResizing] = useState(false); const [isLeftResizing, setIsLeftResizing] = useState(false);
const [isRightResizing, setIsRightResizing] = useState(false); const [isRightResizing, setIsRightResizing] = useState(false);
const [isMoving, setIsMoving] = useState(false); const [isMoving, setIsMoving] = useState(false);
const [posFromLeft, setPosFromLeft] = useState<number | null>(null); const [posFromLeft, setPosFromLeft] = useState<number | null>(null);
// refs
const resizableRef = useRef<HTMLDivElement>(null); const resizableRef = useRef<HTMLDivElement>(null);
// chart hook
const { currentViewData, scrollLeft } = useChart(); const { currentViewData, scrollLeft } = useChart();
// check if cursor reaches either end while resizing/dragging // check if cursor reaches either end while resizing/dragging
const checkScrollEnd = (e: MouseEvent): number => { const checkScrollEnd = (e: MouseEvent): number => {
const SCROLL_THRESHOLD = 70; const SCROLL_THRESHOLD = 70;
@ -68,7 +60,6 @@ export const ChartDraggable: React.FC<Props> = ({
return delWidth; return delWidth;
}; };
// handle block resize from the left end // handle block resize from the left end
const handleBlockLeftResize = (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => { const handleBlockLeftResize = (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
if (!currentViewData || !resizableRef.current || !block.position) return; if (!currentViewData || !resizableRef.current || !block.position) return;
@ -120,7 +111,6 @@ export const ChartDraggable: React.FC<Props> = ({
document.addEventListener("mousemove", handleMouseMove); document.addEventListener("mousemove", handleMouseMove);
document.addEventListener("mouseup", handleMouseUp); document.addEventListener("mouseup", handleMouseUp);
}; };
// handle block resize from the right end // handle block resize from the right end
const handleBlockRightResize = (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => { const handleBlockRightResize = (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
if (!currentViewData || !resizableRef.current || !block.position) return; if (!currentViewData || !resizableRef.current || !block.position) return;
@ -163,7 +153,6 @@ export const ChartDraggable: React.FC<Props> = ({
document.addEventListener("mousemove", handleMouseMove); document.addEventListener("mousemove", handleMouseMove);
document.addEventListener("mouseup", handleMouseUp); document.addEventListener("mouseup", handleMouseUp);
}; };
// handle block x-axis move // handle block x-axis move
const handleBlockMove = (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => { const handleBlockMove = (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
if (!enableBlockMove || !currentViewData || !resizableRef.current || !block.position) return; if (!enableBlockMove || !currentViewData || !resizableRef.current || !block.position) return;
@ -210,7 +199,6 @@ export const ChartDraggable: React.FC<Props> = ({
document.addEventListener("mousemove", handleMouseMove); document.addEventListener("mousemove", handleMouseMove);
document.addEventListener("mouseup", handleMouseUp); document.addEventListener("mouseup", handleMouseUp);
}; };
// scroll to a hidden block // scroll to a hidden block
const handleScrollToBlock = () => { const handleScrollToBlock = () => {
const scrollContainer = document.querySelector("#scroll-container") as HTMLElement; const scrollContainer = document.querySelector("#scroll-container") as HTMLElement;
@ -220,7 +208,6 @@ export const ChartDraggable: React.FC<Props> = ({
// update container's scroll position to the block's position // update container's scroll position to the block's position
scrollContainer.scrollLeft = block.position.marginLeft - 4; scrollContainer.scrollLeft = block.position.marginLeft - 4;
}; };
// update block position from viewport's left end on scroll // update block position from viewport's left end on scroll
useEffect(() => { useEffect(() => {
const block = resizableRef.current; const block = resizableRef.current;
@ -229,7 +216,6 @@ export const ChartDraggable: React.FC<Props> = ({
setPosFromLeft(block.getBoundingClientRect().left); setPosFromLeft(block.getBoundingClientRect().left);
}, [scrollLeft]); }, [scrollLeft]);
// check if block is hidden on either side // check if block is hidden on either side
const isBlockHiddenOnLeft = const isBlockHiddenOnLeft =
block.position?.marginLeft && block.position?.marginLeft &&

View File

@ -1 +1,3 @@
export * from "./add-block";
export * from "./block-structure"; export * from "./block-structure";
export * from "./draggable";

View File

@ -19,36 +19,43 @@ type GanttChartRootProps = {
enableBlockMove?: boolean; enableBlockMove?: boolean;
enableReorder?: boolean; enableReorder?: boolean;
bottomSpacing?: boolean; bottomSpacing?: boolean;
showAllBlocks?: boolean;
}; };
export const GanttChartRoot: FC<GanttChartRootProps> = ({ export const GanttChartRoot: FC<GanttChartRootProps> = (props) => {
border = true, const {
title, border = true,
blocks, title,
loaderTitle = "blocks", blocks,
blockUpdateHandler, loaderTitle = "blocks",
sidebarToRender, blockUpdateHandler,
blockToRender, sidebarToRender,
enableBlockLeftResize = true, blockToRender,
enableBlockRightResize = true, enableBlockLeftResize = true,
enableBlockMove = true, enableBlockRightResize = true,
enableReorder = true, enableBlockMove = true,
bottomSpacing = false, enableReorder = true,
}) => ( bottomSpacing = false,
<ChartContextProvider> showAllBlocks = false,
<ChartViewRoot } = props;
border={border}
title={title} return (
blocks={blocks} <ChartContextProvider>
loaderTitle={loaderTitle} <ChartViewRoot
blockUpdateHandler={blockUpdateHandler} border={border}
sidebarToRender={sidebarToRender} title={title}
blockToRender={blockToRender} blocks={blocks}
enableBlockLeftResize={enableBlockLeftResize} loaderTitle={loaderTitle}
enableBlockRightResize={enableBlockRightResize} blockUpdateHandler={blockUpdateHandler}
enableBlockMove={enableBlockMove} sidebarToRender={sidebarToRender}
enableReorder={enableReorder} blockToRender={blockToRender}
bottomSpacing={bottomSpacing} enableBlockLeftResize={enableBlockLeftResize}
/> enableBlockRightResize={enableBlockRightResize}
</ChartContextProvider> enableBlockMove={enableBlockMove}
); enableReorder={enableReorder}
bottomSpacing={bottomSpacing}
showAllBlocks={showAllBlocks}
/>
</ChartContextProvider>
);
};

View File

@ -1,6 +1,5 @@
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { DragDropContext, Draggable, DropResult } from "@hello-pangea/dnd"; import { DragDropContext, Draggable, DropResult, Droppable } from "@hello-pangea/dnd";
import StrictModeDroppable from "components/dnd/StrictModeDroppable";
import { MoreVertical } from "lucide-react"; import { MoreVertical } from "lucide-react";
// hooks // hooks
import { useChart } from "components/gantt-chart/hooks"; import { useChart } from "components/gantt-chart/hooks";
@ -83,7 +82,7 @@ export const CycleGanttSidebar: React.FC<Props> = (props) => {
return ( return (
<DragDropContext onDragEnd={handleOrderChange}> <DragDropContext onDragEnd={handleOrderChange}>
<StrictModeDroppable droppableId="gantt-sidebar"> <Droppable droppableId="gantt-sidebar">
{(droppableProvided) => ( {(droppableProvided) => (
<div <div
id={`gantt-sidebar-${cycleId}`} id={`gantt-sidebar-${cycleId}`}
@ -153,7 +152,7 @@ export const CycleGanttSidebar: React.FC<Props> = (props) => {
</> </>
</div> </div>
)} )}
</StrictModeDroppable> </Droppable>
</DragDropContext> </DragDropContext>
); );
}; };

View File

@ -1,6 +1,5 @@
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { DragDropContext, Draggable, DropResult } from "@hello-pangea/dnd"; import { DragDropContext, Draggable, Droppable, DropResult } from "@hello-pangea/dnd";
import StrictModeDroppable from "components/dnd/StrictModeDroppable";
import { MoreVertical } from "lucide-react"; import { MoreVertical } from "lucide-react";
// hooks // hooks
import { useChart } from "components/gantt-chart/hooks"; import { useChart } from "components/gantt-chart/hooks";
@ -83,7 +82,7 @@ export const ModuleGanttSidebar: React.FC<Props> = (props) => {
return ( return (
<DragDropContext onDragEnd={handleOrderChange}> <DragDropContext onDragEnd={handleOrderChange}>
<StrictModeDroppable droppableId="gantt-sidebar"> <Droppable droppableId="gantt-sidebar">
{(droppableProvided) => ( {(droppableProvided) => (
<div <div
id={`gantt-sidebar-${cycleId}`} id={`gantt-sidebar-${cycleId}`}
@ -153,7 +152,7 @@ export const ModuleGanttSidebar: React.FC<Props> = (props) => {
</> </>
</div> </div>
)} )}
</StrictModeDroppable> </Droppable>
</DragDropContext> </DragDropContext>
); );
}; };

View File

@ -1,6 +1,5 @@
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { DragDropContext, Draggable, DropResult } from "@hello-pangea/dnd"; import { DragDropContext, Draggable, Droppable, DropResult } from "@hello-pangea/dnd";
import StrictModeDroppable from "components/dnd/StrictModeDroppable";
import { MoreVertical } from "lucide-react"; import { MoreVertical } from "lucide-react";
// hooks // hooks
import { useChart } from "components/gantt-chart/hooks"; import { useChart } from "components/gantt-chart/hooks";
@ -84,7 +83,7 @@ export const ProjectViewGanttSidebar: React.FC<Props> = (props) => {
return ( return (
<DragDropContext onDragEnd={handleOrderChange}> <DragDropContext onDragEnd={handleOrderChange}>
<StrictModeDroppable droppableId="gantt-sidebar"> <Droppable droppableId="gantt-sidebar">
{(droppableProvided) => ( {(droppableProvided) => (
<div <div
id={`gantt-sidebar-${cycleId}`} id={`gantt-sidebar-${cycleId}`}
@ -154,7 +153,7 @@ export const ProjectViewGanttSidebar: React.FC<Props> = (props) => {
</> </>
</div> </div>
)} )}
</StrictModeDroppable> </Droppable>
</DragDropContext> </DragDropContext>
); );
}; };

View File

@ -1,6 +1,5 @@
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { DragDropContext, Draggable, DropResult } from "@hello-pangea/dnd"; import { DragDropContext, Draggable, Droppable, DropResult } from "@hello-pangea/dnd";
import StrictModeDroppable from "components/dnd/StrictModeDroppable";
import { MoreVertical } from "lucide-react"; import { MoreVertical } from "lucide-react";
// hooks // hooks
import { useChart } from "components/gantt-chart/hooks"; import { useChart } from "components/gantt-chart/hooks";
@ -27,10 +26,10 @@ type Props = {
) => Promise<TIssue | undefined>; ) => Promise<TIssue | undefined>;
viewId?: string; viewId?: string;
disableIssueCreation?: boolean; disableIssueCreation?: boolean;
showAllBlocks?: boolean;
}; };
export const IssueGanttSidebar: React.FC<Props> = (props) => { export const IssueGanttSidebar: React.FC<Props> = (props) => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { const {
blockUpdateHandler, blockUpdateHandler,
blocks, blocks,
@ -39,6 +38,7 @@ export const IssueGanttSidebar: React.FC<Props> = (props) => {
quickAddCallback, quickAddCallback,
viewId, viewId,
disableIssueCreation, disableIssueCreation,
showAllBlocks = false,
} = props; } = props;
const router = useRouter(); const router = useRouter();
@ -100,7 +100,7 @@ export const IssueGanttSidebar: React.FC<Props> = (props) => {
return ( return (
<DragDropContext onDragEnd={handleOrderChange}> <DragDropContext onDragEnd={handleOrderChange}>
<StrictModeDroppable droppableId="gantt-sidebar"> <Droppable droppableId="gantt-sidebar">
{(droppableProvided) => ( {(droppableProvided) => (
<div <div
id={`gantt-sidebar-${cycleId}`} id={`gantt-sidebar-${cycleId}`}
@ -111,7 +111,15 @@ export const IssueGanttSidebar: React.FC<Props> = (props) => {
<> <>
{blocks ? ( {blocks ? (
blocks.map((block, index) => { blocks.map((block, index) => {
const duration = findTotalDaysInRange(block.start_date ?? "", block.target_date ?? ""); const isBlockVisibleOnSidebar = block.start_date && block.target_date;
// hide the block if it doesn't have start and target dates and showAllBlocks is false
if (!showAllBlocks && !isBlockVisibleOnSidebar) return;
const duration =
!block.start_date || !block.target_date
? null
: findTotalDaysInRange(block.start_date, block.target_date);
return ( return (
<Draggable <Draggable
@ -149,7 +157,11 @@ export const IssueGanttSidebar: React.FC<Props> = (props) => {
<IssueGanttSidebarBlock data={block.data} /> <IssueGanttSidebarBlock data={block.data} />
</div> </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 && (
<span>
{duration} day{duration > 1 ? "s" : ""}
</span>
)}
</div> </div>
</div> </div>
</div> </div>
@ -173,7 +185,7 @@ export const IssueGanttSidebar: React.FC<Props> = (props) => {
)} )}
</div> </div>
)} )}
</StrictModeDroppable> </Droppable>
</DragDropContext> </DragDropContext>
); );
}; };

View File

@ -13,8 +13,8 @@ export interface IGanttBlock {
width: number; width: number;
}; };
sort_order: number; sort_order: number;
start_date: Date; start_date: Date | null;
target_date: Date; target_date: Date | null;
} }
export interface IBlockUpdateData { export interface IBlockUpdateData {

View File

@ -167,6 +167,8 @@ export const getMonthChartItemPositionWidthInMonth = (chartData: ChartDataType,
const { startDate } = chartData.data; const { startDate } = chartData.data;
const { start_date: itemStartDate, target_date: itemTargetDate } = itemData; const { start_date: itemStartDate, target_date: itemTargetDate } = itemData;
if (!itemStartDate || !itemTargetDate) return null;
startDate.setHours(0, 0, 0, 0); startDate.setHours(0, 0, 0, 0);
itemStartDate.setHours(0, 0, 0, 0); itemStartDate.setHours(0, 0, 0, 0);
itemTargetDate.setHours(0, 0, 0, 0); itemTargetDate.setHours(0, 0, 0, 0);

View File

@ -13,11 +13,12 @@ import {
} from "components/gantt-chart"; } from "components/gantt-chart";
// types // types
import { TIssue, TUnGroupedIssues } from "@plane/types"; import { TIssue, TUnGroupedIssues } from "@plane/types";
import { EUserProjectRoles } from "constants/project";
import { ICycleIssues, ICycleIssuesFilter } from "store/issue/cycle"; import { ICycleIssues, ICycleIssuesFilter } from "store/issue/cycle";
import { IModuleIssues, IModuleIssuesFilter } from "store/issue/module"; import { IModuleIssues, IModuleIssuesFilter } from "store/issue/module";
import { IProjectIssues, IProjectIssuesFilter } from "store/issue/project"; import { IProjectIssues, IProjectIssuesFilter } from "store/issue/project";
import { IProjectViewIssues, IProjectViewIssuesFilter } from "store/issue/project-views"; import { IProjectViewIssues, IProjectViewIssuesFilter } from "store/issue/project-views";
// constants
import { EUserProjectRoles } from "constants/project";
import { EIssueActions } from "../types"; import { EIssueActions } from "../types";
interface IBaseGanttRoot { interface IBaseGanttRoot {
@ -76,12 +77,14 @@ export const BaseGanttRoot: React.FC<IBaseGanttRoot> = observer((props: IBaseGan
viewId={viewId} viewId={viewId}
enableQuickIssueCreate enableQuickIssueCreate
disableIssueCreation={!enableIssueCreation || !isAllowed} disableIssueCreation={!enableIssueCreation || !isAllowed}
showAllBlocks
/> />
)} )}
enableBlockLeftResize={isAllowed} enableBlockLeftResize={isAllowed}
enableBlockRightResize={isAllowed} enableBlockRightResize={isAllowed}
enableBlockMove={isAllowed} enableBlockMove={isAllowed}
enableReorder={appliedDisplayFilters?.order_by === "sort_order" && isAllowed} enableReorder={appliedDisplayFilters?.order_by === "sort_order" && isAllowed}
showAllBlocks
/> />
</div> </div>
</> </>

View File

@ -13,7 +13,6 @@ const paramsToKey = (params: any) => {
start_date, start_date,
target_date, target_date,
sub_issue, sub_issue,
start_target_date,
project, project,
layout, layout,
subscriber, subscriber,
@ -28,7 +27,6 @@ const paramsToKey = (params: any) => {
let createdByKey = created_by ? created_by.split(",") : []; let createdByKey = created_by ? created_by.split(",") : [];
let labelsKey = labels ? labels.split(",") : []; let labelsKey = labels ? labels.split(",") : [];
let subscriberKey = subscriber ? subscriber.split(",") : []; let subscriberKey = subscriber ? subscriber.split(",") : [];
const startTargetDate = start_target_date ? `${start_target_date}`.toUpperCase() : "FALSE";
const startDateKey = start_date ?? ""; const startDateKey = start_date ?? "";
const targetDateKey = target_date ?? ""; const targetDateKey = target_date ?? "";
const type = params.type ? params.type.toUpperCase() : "NULL"; const type = params.type ? params.type.toUpperCase() : "NULL";
@ -47,7 +45,7 @@ const paramsToKey = (params: any) => {
labelsKey = labelsKey.sort().join("_"); labelsKey = labelsKey.sort().join("_");
subscriberKey = subscriberKey.sort().join("_"); subscriberKey = subscriberKey.sort().join("_");
return `${layoutKey}_${projectKey}_${stateGroupKey}_${stateKey}_${priorityKey}_${assigneesKey}_${mentionsKey}_${createdByKey}_${type}_${groupBy}_${orderBy}_${labelsKey}_${startDateKey}_${targetDateKey}_${sub_issue}_${startTargetDate}_${subscriberKey}`; return `${layoutKey}_${projectKey}_${stateGroupKey}_${stateKey}_${priorityKey}_${assigneesKey}_${mentionsKey}_${createdByKey}_${type}_${groupBy}_${orderBy}_${labelsKey}_${startDateKey}_${targetDateKey}_${sub_issue}_${subscriberKey}`;
}; };
const myIssuesParamsToKey = (params: any) => { const myIssuesParamsToKey = (params: any) => {

View File

@ -105,9 +105,6 @@ export const handleIssueQueryParamsByLayout = (
}); });
} }
// add start_target_date query param for the gantt_chart layout
if (layout === "gantt_chart") queryParams.push("start_target_date");
return queryParams; return queryParams;
}; };

View File

@ -89,7 +89,6 @@ export class ArchivedIssuesFilter extends IssueFilterHelperStore implements IArc
filteredParams filteredParams
); );
if (userFilters?.displayFilters?.layout === "gantt_chart") filteredRouteParams.start_target_date = true;
if (userFilters?.displayFilters?.layout === "spreadsheet") filteredRouteParams.sub_issue = false; if (userFilters?.displayFilters?.layout === "spreadsheet") filteredRouteParams.sub_issue = false;
return filteredRouteParams; return filteredRouteParams;

View File

@ -90,7 +90,6 @@ export class CycleIssuesFilter extends IssueFilterHelperStore implements ICycleI
filteredParams filteredParams
); );
if (userFilters?.displayFilters?.layout === "gantt_chart") filteredRouteParams.start_target_date = true;
if (userFilters?.displayFilters?.layout === "spreadsheet") filteredRouteParams.sub_issue = false; if (userFilters?.displayFilters?.layout === "spreadsheet") filteredRouteParams.sub_issue = false;
return filteredRouteParams; return filteredRouteParams;

View File

@ -89,7 +89,6 @@ export class DraftIssuesFilter extends IssueFilterHelperStore implements IDraftI
filteredParams filteredParams
); );
if (userFilters?.displayFilters?.layout === "gantt_chart") filteredRouteParams.start_target_date = true;
if (userFilters?.displayFilters?.layout === "spreadsheet") filteredRouteParams.sub_issue = false; if (userFilters?.displayFilters?.layout === "spreadsheet") filteredRouteParams.sub_issue = false;
return filteredRouteParams; return filteredRouteParams;

View File

@ -81,7 +81,6 @@ export class IssueFilterHelperStore implements IIssueFilterHelperStore {
// display filters // display filters
type: displayFilters?.type || undefined, type: displayFilters?.type || undefined,
sub_issue: displayFilters?.sub_issue ?? true, sub_issue: displayFilters?.sub_issue ?? true,
start_target_date: displayFilters?.start_target_date ?? true,
}; };
const issueFiltersParams: Partial<Record<TIssueParams, boolean | string>> = {}; const issueFiltersParams: Partial<Record<TIssueParams, boolean | string>> = {};
@ -170,7 +169,6 @@ export class IssueFilterHelperStore implements IIssueFilterHelperStore {
type: filters?.type || null, type: filters?.type || null,
sub_issue: filters?.sub_issue || false, sub_issue: filters?.sub_issue || false,
show_empty_groups: filters?.show_empty_groups || false, show_empty_groups: filters?.show_empty_groups || false,
start_target_date: filters?.start_target_date || false,
}; };
}; };

View File

@ -90,7 +90,6 @@ export class ModuleIssuesFilter extends IssueFilterHelperStore implements IModul
filteredParams filteredParams
); );
if (userFilters?.displayFilters?.layout === "gantt_chart") filteredRouteParams.start_target_date = true;
if (userFilters?.displayFilters?.layout === "spreadsheet") filteredRouteParams.sub_issue = false; if (userFilters?.displayFilters?.layout === "spreadsheet") filteredRouteParams.sub_issue = false;
return filteredRouteParams; return filteredRouteParams;

View File

@ -93,7 +93,6 @@ export class ProfileIssuesFilter extends IssueFilterHelperStore implements IProf
filteredParams filteredParams
); );
if (userFilters?.displayFilters?.layout === "gantt_chart") filteredRouteParams.start_target_date = true;
if (userFilters?.displayFilters?.layout === "spreadsheet") filteredRouteParams.sub_issue = false; if (userFilters?.displayFilters?.layout === "spreadsheet") filteredRouteParams.sub_issue = false;
return filteredRouteParams; return filteredRouteParams;

View File

@ -90,7 +90,6 @@ export class ProjectViewIssuesFilter extends IssueFilterHelperStore implements I
filteredParams filteredParams
); );
if (userFilters?.displayFilters?.layout === "gantt_chart") filteredRouteParams.start_target_date = true;
if (userFilters?.displayFilters?.layout === "spreadsheet") filteredRouteParams.sub_issue = false; if (userFilters?.displayFilters?.layout === "spreadsheet") filteredRouteParams.sub_issue = false;
return filteredRouteParams; return filteredRouteParams;

View File

@ -89,7 +89,6 @@ export class ProjectIssuesFilter extends IssueFilterHelperStore implements IProj
filteredParams filteredParams
); );
if (userFilters?.displayFilters?.layout === "gantt_chart") filteredRouteParams.start_target_date = true;
if (userFilters?.displayFilters?.layout === "spreadsheet") filteredRouteParams.sub_issue = false; if (userFilters?.displayFilters?.layout === "spreadsheet") filteredRouteParams.sub_issue = false;
return filteredRouteParams; return filteredRouteParams;

View File

@ -99,7 +99,6 @@ export class WorkspaceIssuesFilter extends IssueFilterHelperStore implements IWo
filteredParams filteredParams
); );
if (userFilters?.displayFilters?.layout === "gantt_chart") filteredRouteParams.start_target_date = true;
if (userFilters?.displayFilters?.layout === "spreadsheet") filteredRouteParams.sub_issue = false; if (userFilters?.displayFilters?.layout === "spreadsheet") filteredRouteParams.sub_issue = false;
return filteredRouteParams; return filteredRouteParams;