restructure gantt layout charts

This commit is contained in:
rahulramesha 2024-03-18 13:12:10 +05:30
parent ae339bc19c
commit ce43067bc1
17 changed files with 322 additions and 209 deletions

View File

@ -1,13 +1,14 @@
import { FC } from "react"; import { FC, useCallback } from "react";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
// hooks // hooks
import { CycleGanttBlock } from "components/cycles"; import { CycleGanttBlock } from "components/cycles";
import { GanttChartRoot, IBlockUpdateData, CycleGanttSidebar } from "components/gantt-chart"; import { GanttChartRoot, IBlockUpdateData, CycleGanttSidebar, ChartDataType } from "components/gantt-chart";
import { useCycle } from "hooks/store"; import { useCycle } from "hooks/store";
// components // components
// types // types
import { ICycle } from "@plane/types"; import { ICycle } from "@plane/types";
import { getMonthChartItemPositionWidthInMonth } from "components/gantt-chart/views";
// constants // constants
type Props = { type Props = {
@ -23,6 +24,28 @@ export const CyclesListGanttChartView: FC<Props> = observer((props) => {
// store hooks // store hooks
const { getCycleById, updateCycleDetails } = useCycle(); const { getCycleById, updateCycleDetails } = useCycle();
const getBlockById = useCallback(
(id: string, currentViewData?: ChartDataType | undefined) => {
const cycle = getCycleById(id);
const block = {
data: cycle,
id: cycle?.id ?? "",
sort_order: cycle?.sort_order ?? 0,
start_date: cycle?.start_date ? new Date(cycle?.start_date) : undefined,
target_date: cycle?.end_date ? new Date(cycle?.end_date) : undefined,
};
if (currentViewData) {
return {
...block,
position: getMonthChartItemPositionWidthInMonth(currentViewData, block),
};
}
return block;
},
[getCycleById]
);
const handleCycleUpdate = async (cycle: ICycle, data: IBlockUpdateData) => { const handleCycleUpdate = async (cycle: ICycle, data: IBlockUpdateData) => {
if (!workspaceSlug || !cycle) return; if (!workspaceSlug || !cycle) return;
@ -32,28 +55,13 @@ export const CyclesListGanttChartView: FC<Props> = observer((props) => {
await updateCycleDetails(workspaceSlug.toString(), cycle.project_id, cycle.id, payload); await updateCycleDetails(workspaceSlug.toString(), cycle.project_id, cycle.id, payload);
}; };
const blockFormat = (blocks: (ICycle | null)[]) => {
if (!blocks) return [];
const filteredBlocks = blocks.filter((b) => b !== null && b.start_date && b.end_date);
const structuredBlocks = filteredBlocks.map((block) => ({
data: block,
id: block?.id ?? "",
sort_order: block?.sort_order ?? 0,
start_date: new Date(block?.start_date ?? ""),
target_date: new Date(block?.end_date ?? ""),
}));
return structuredBlocks;
};
return ( return (
<div className="h-full w-full overflow-y-auto"> <div className="h-full w-full overflow-y-auto">
<GanttChartRoot <GanttChartRoot
title="Cycles" title="Cycles"
loaderTitle="Cycles" loaderTitle="Cycles"
blocks={cycleIds ? blockFormat(cycleIds.map((c) => getCycleById(c))) : null} blockIds={cycleIds}
getBlockById={getBlockById}
blockUpdateHandler={(block, payload) => handleCycleUpdate(block, payload)} blockUpdateHandler={(block, payload) => handleCycleUpdate(block, payload)}
sidebarToRender={(props) => <CycleGanttSidebar {...props} />} sidebarToRender={(props) => <CycleGanttSidebar {...props} />}
blockToRender={(data: ICycle) => <CycleGanttBlock cycleId={data.id} />} blockToRender={(data: ICycle) => <CycleGanttBlock cycleId={data.id} />}

View File

@ -10,10 +10,12 @@ import { useIssueDetail } from "hooks/store";
import { BLOCK_HEIGHT } from "../constants"; import { BLOCK_HEIGHT } from "../constants";
import { ChartAddBlock, ChartDraggable } from "../helpers"; import { ChartAddBlock, ChartDraggable } from "../helpers";
import { useGanttChart } from "../hooks"; import { useGanttChart } from "../hooks";
import { IBlockUpdateData, IGanttBlock } from "../types"; import { ChartDataType, IBlockUpdateData, IGanttBlock } from "../types";
type Props = { type Props = {
block: IGanttBlock; blockId: string;
getBlockById: (id: string, currentViewData?: ChartDataType | undefined) => IGanttBlock;
showAllBlocks: boolean;
blockToRender: (data: any) => React.ReactNode; blockToRender: (data: any) => React.ReactNode;
blockUpdateHandler: (block: any, payload: IBlockUpdateData) => void; blockUpdateHandler: (block: any, payload: IBlockUpdateData) => void;
enableBlockLeftResize: boolean; enableBlockLeftResize: boolean;
@ -25,7 +27,9 @@ type Props = {
export const GanttChartBlock: React.FC<Props> = observer((props) => { export const GanttChartBlock: React.FC<Props> = observer((props) => {
const { const {
block, blockId,
getBlockById,
showAllBlocks,
blockToRender, blockToRender,
blockUpdateHandler, blockUpdateHandler,
enableBlockLeftResize, enableBlockLeftResize,
@ -35,9 +39,14 @@ export const GanttChartBlock: React.FC<Props> = observer((props) => {
ganttContainerRef, ganttContainerRef,
} = props; } = props;
// store hooks // store hooks
const { updateActiveBlockId, isBlockActive } = useGanttChart(); const { currentViewData, updateActiveBlockId, isBlockActive } = useGanttChart();
const { peekIssue } = useIssueDetail(); const { peekIssue } = useIssueDetail();
const block = getBlockById(blockId, currentViewData);
// hide the block if it doesn't have start and target dates and showAllBlocks is false
if (!block || (!showAllBlocks && !(block.start_date && block.target_date))) return null;
const isBlockVisibleOnChart = block.start_date && block.target_date; const isBlockVisibleOnChart = block.start_date && block.target_date;
const handleChartBlockPosition = ( const handleChartBlockPosition = (
@ -72,7 +81,6 @@ export const GanttChartBlock: React.FC<Props> = observer((props) => {
return ( return (
<div <div
key={`block-${block.id}`}
className="relative min-w-full w-max" className="relative min-w-full w-max"
style={{ style={{
height: `${BLOCK_HEIGHT}px`, height: `${BLOCK_HEIGHT}px`,
@ -80,11 +88,11 @@ export const GanttChartBlock: React.FC<Props> = observer((props) => {
> >
<div <div
className={cn("relative h-full", { className={cn("relative h-full", {
"bg-custom-background-80": isBlockActive(block.id), "bg-custom-background-80": isBlockActive(blockId),
"rounded-l border border-r-0 border-custom-primary-70 hover:border-custom-primary-70": "rounded-l border border-r-0 border-custom-primary-70 hover:border-custom-primary-70":
peekIssue?.issueId === block.data.id, peekIssue?.issueId === block.data.id,
})} })}
onMouseEnter={() => updateActiveBlockId(block.id)} onMouseEnter={() => updateActiveBlockId(blockId)}
onMouseLeave={() => updateActiveBlockId(null)} onMouseLeave={() => updateActiveBlockId(null)}
> >
{isBlockVisibleOnChart ? ( {isBlockVisibleOnChart ? (

View File

@ -1,14 +1,15 @@
import { FC } from "react"; import { FC } from "react";
// components // components
import { HEADER_HEIGHT } from "../constants"; import { HEADER_HEIGHT } from "../constants";
import { IBlockUpdateData, IGanttBlock } from "../types"; import { ChartDataType, IBlockUpdateData, IGanttBlock } from "../types";
import { GanttChartBlock } from "./block"; import { GanttChartBlock } from "./block";
// types // types
// constants // constants
export type GanttChartBlocksProps = { export type GanttChartBlocksProps = {
itemsContainerWidth: number; itemsContainerWidth: number;
blocks: IGanttBlock[] | null; blockIds: string[];
getBlockById: (id: string, currentViewData?: ChartDataType | undefined) => IGanttBlock;
blockToRender: (data: any) => React.ReactNode; blockToRender: (data: any) => React.ReactNode;
blockUpdateHandler: (block: any, payload: IBlockUpdateData) => void; blockUpdateHandler: (block: any, payload: IBlockUpdateData) => void;
enableBlockLeftResize: boolean; enableBlockLeftResize: boolean;
@ -22,9 +23,10 @@ export type GanttChartBlocksProps = {
export const GanttChartBlocksList: FC<GanttChartBlocksProps> = (props) => { export const GanttChartBlocksList: FC<GanttChartBlocksProps> = (props) => {
const { const {
itemsContainerWidth, itemsContainerWidth,
blocks, blockIds,
blockToRender, blockToRender,
blockUpdateHandler, blockUpdateHandler,
getBlockById,
enableBlockLeftResize, enableBlockLeftResize,
enableBlockRightResize, enableBlockRightResize,
enableBlockMove, enableBlockMove,
@ -41,14 +43,13 @@ export const GanttChartBlocksList: FC<GanttChartBlocksProps> = (props) => {
transform: `translateY(${HEADER_HEIGHT}px)`, transform: `translateY(${HEADER_HEIGHT}px)`,
}} }}
> >
{blocks?.map((block) => { {blockIds?.map((blockId) => {
// hide the block if it doesn't have start and target dates and showAllBlocks is false
if (!showAllBlocks && !(block.start_date && block.target_date)) return;
return ( return (
<GanttChartBlock <GanttChartBlock
key={block.id} key={blockId}
block={block} blockId={blockId}
getBlockById={getBlockById}
showAllBlocks={showAllBlocks}
blockToRender={blockToRender} blockToRender={blockToRender}
blockUpdateHandler={blockUpdateHandler} blockUpdateHandler={blockUpdateHandler}
enableBlockLeftResize={enableBlockLeftResize} enableBlockLeftResize={enableBlockLeftResize}

View File

@ -10,7 +10,7 @@ import { IGanttBlock, TGanttViews } from "../types";
// constants // constants
type Props = { type Props = {
blocks: IGanttBlock[] | null; blockIds: string[];
fullScreenMode: boolean; fullScreenMode: boolean;
handleChartView: (view: TGanttViews) => void; handleChartView: (view: TGanttViews) => void;
handleToday: () => void; handleToday: () => void;
@ -20,7 +20,7 @@ type Props = {
}; };
export const GanttChartHeader: React.FC<Props> = observer((props) => { export const GanttChartHeader: React.FC<Props> = observer((props) => {
const { blocks, fullScreenMode, handleChartView, handleToday, loaderTitle, title, toggleFullScreenMode } = props; const { blockIds, fullScreenMode, handleChartView, handleToday, loaderTitle, title, toggleFullScreenMode } = props;
// chart hook // chart hook
const { currentView } = useGanttChart(); const { currentView } = useGanttChart();
@ -28,7 +28,9 @@ export const GanttChartHeader: React.FC<Props> = observer((props) => {
<div className="relative flex w-full flex-shrink-0 flex-wrap items-center gap-2 whitespace-nowrap px-2.5 py-2"> <div className="relative flex w-full flex-shrink-0 flex-wrap items-center gap-2 whitespace-nowrap px-2.5 py-2">
<div className="flex items-center gap-2 text-lg font-medium">{title}</div> <div className="flex items-center gap-2 text-lg font-medium">{title}</div>
<div className="ml-auto"> <div className="ml-auto">
<div className="ml-auto text-sm font-medium">{blocks ? `${blocks.length} ${loaderTitle}` : "Loading..."}</div> <div className="ml-auto text-sm font-medium">
{blockIds ? `${blockIds.length} ${loaderTitle}` : "Loading..."}
</div>
</div> </div>
<div className="flex flex-wrap items-center gap-2"> <div className="flex flex-wrap items-center gap-2">

View File

@ -4,6 +4,7 @@ import { observer } from "mobx-react";
// components // components
import { import {
BiWeekChartView, BiWeekChartView,
ChartDataType,
DayChartView, DayChartView,
GanttChartBlocksList, GanttChartBlocksList,
GanttChartSidebar, GanttChartSidebar,
@ -21,11 +22,12 @@ import { cn } from "helpers/common.helper";
import { useGanttChart } from "../hooks/use-gantt-chart"; import { useGanttChart } from "../hooks/use-gantt-chart";
type Props = { type Props = {
blocks: IGanttBlock[] | null; blockIds: string[];
getBlockById: (id: string, currentViewData?: ChartDataType | undefined) => IGanttBlock;
loadMoreBlocks?: () => void;
blockToRender: (data: any) => React.ReactNode; blockToRender: (data: any) => React.ReactNode;
blockUpdateHandler: (block: any, payload: IBlockUpdateData) => void; blockUpdateHandler: (block: any, payload: IBlockUpdateData) => void;
bottomSpacing: boolean; bottomSpacing: boolean;
chartBlocks: IGanttBlock[] | null;
enableBlockLeftResize: boolean; enableBlockLeftResize: boolean;
enableBlockMove: boolean; enableBlockMove: boolean;
enableBlockRightResize: boolean; enableBlockRightResize: boolean;
@ -41,11 +43,12 @@ type Props = {
export const GanttChartMainContent: React.FC<Props> = observer((props) => { export const GanttChartMainContent: React.FC<Props> = observer((props) => {
const { const {
blocks, blockIds,
getBlockById,
loadMoreBlocks,
blockToRender, blockToRender,
blockUpdateHandler, blockUpdateHandler,
bottomSpacing, bottomSpacing,
chartBlocks,
enableBlockLeftResize, enableBlockLeftResize,
enableBlockMove, enableBlockMove,
enableBlockRightResize, enableBlockRightResize,
@ -104,7 +107,9 @@ export const GanttChartMainContent: React.FC<Props> = observer((props) => {
onScroll={onScroll} onScroll={onScroll}
> >
<GanttChartSidebar <GanttChartSidebar
blocks={blocks} blockIds={blockIds}
getBlockById={getBlockById}
loadMoreBlocks={loadMoreBlocks}
blockUpdateHandler={blockUpdateHandler} blockUpdateHandler={blockUpdateHandler}
enableReorder={enableReorder} enableReorder={enableReorder}
sidebarToRender={sidebarToRender} sidebarToRender={sidebarToRender}
@ -116,7 +121,8 @@ export const GanttChartMainContent: React.FC<Props> = observer((props) => {
{currentViewData && ( {currentViewData && (
<GanttChartBlocksList <GanttChartBlocksList
itemsContainerWidth={itemsContainerWidth} itemsContainerWidth={itemsContainerWidth}
blocks={chartBlocks} blockIds={blockIds}
getBlockById={getBlockById}
blockToRender={blockToRender} blockToRender={blockToRender}
blockUpdateHandler={blockUpdateHandler} blockUpdateHandler={blockUpdateHandler}
enableBlockLeftResize={enableBlockLeftResize} enableBlockLeftResize={enableBlockLeftResize}

View File

@ -13,17 +13,13 @@ import { currentViewDataWithView } from "../data";
// constants // constants
import { useGanttChart } from "../hooks/use-gantt-chart"; import { useGanttChart } from "../hooks/use-gantt-chart";
import { ChartDataType, IBlockUpdateData, IGanttBlock, TGanttViews } from "../types"; import { ChartDataType, IBlockUpdateData, IGanttBlock, TGanttViews } from "../types";
import { import { generateMonthChart, getNumberOfDaysBetweenTwoDatesInMonth } from "../views";
generateMonthChart,
getNumberOfDaysBetweenTwoDatesInMonth,
getMonthChartItemPositionWidthInMonth,
} from "../views";
type ChartViewRootProps = { type ChartViewRootProps = {
border: boolean; border: boolean;
title: string; title: string;
loaderTitle: string; loaderTitle: string;
blocks: IGanttBlock[] | null; blockIds: string[];
blockUpdateHandler: (block: any, payload: IBlockUpdateData) => void; blockUpdateHandler: (block: any, payload: IBlockUpdateData) => void;
blockToRender: (data: any) => React.ReactNode; blockToRender: (data: any) => React.ReactNode;
sidebarToRender: (props: any) => React.ReactNode; sidebarToRender: (props: any) => React.ReactNode;
@ -34,6 +30,8 @@ type ChartViewRootProps = {
enableAddBlock: boolean; enableAddBlock: boolean;
bottomSpacing: boolean; bottomSpacing: boolean;
showAllBlocks: boolean; showAllBlocks: boolean;
getBlockById: (id: string, currentViewData?: ChartDataType | undefined) => IGanttBlock;
loadMoreBlocks?: () => void;
quickAdd?: React.JSX.Element | undefined; quickAdd?: React.JSX.Element | undefined;
}; };
@ -41,7 +39,9 @@ export const ChartViewRoot: FC<ChartViewRootProps> = observer((props) => {
const { const {
border, border,
title, title,
blocks = null, blockIds,
getBlockById,
loadMoreBlocks,
loaderTitle, loaderTitle,
blockUpdateHandler, blockUpdateHandler,
sidebarToRender, sidebarToRender,
@ -58,25 +58,10 @@ export const ChartViewRoot: FC<ChartViewRootProps> = observer((props) => {
// states // states
const [itemsContainerWidth, setItemsContainerWidth] = useState(0); const [itemsContainerWidth, setItemsContainerWidth] = useState(0);
const [fullScreenMode, setFullScreenMode] = useState(false); const [fullScreenMode, setFullScreenMode] = useState(false);
const [chartBlocks, setChartBlocks] = useState<IGanttBlock[] | null>(null);
// hooks // hooks
const { currentView, currentViewData, renderView, updateCurrentView, updateCurrentViewData, updateRenderView } = const { currentView, currentViewData, renderView, updateCurrentView, updateCurrentViewData, updateRenderView } =
useGanttChart(); useGanttChart();
// rendering the block structure
const renderBlockStructure = (view: any, blocks: IGanttBlock[] | null) =>
blocks
? blocks.map((block: any) => ({
...block,
position: getMonthChartItemPositionWidthInMonth(view, block),
}))
: [];
useEffect(() => {
if (!currentViewData || !blocks) return;
setChartBlocks(() => renderBlockStructure(currentViewData, blocks));
}, [currentViewData, blocks]);
const updateCurrentViewRenderPayload = (side: null | "left" | "right", view: TGanttViews) => { const updateCurrentViewRenderPayload = (side: null | "left" | "right", view: TGanttViews) => {
const selectedCurrentView: TGanttViews = view; const selectedCurrentView: TGanttViews = view;
const selectedCurrentViewData: ChartDataType | undefined = const selectedCurrentViewData: ChartDataType | undefined =
@ -166,7 +151,7 @@ export const ChartViewRoot: FC<ChartViewRootProps> = observer((props) => {
})} })}
> >
<GanttChartHeader <GanttChartHeader
blocks={blocks} blockIds={blockIds}
fullScreenMode={fullScreenMode} fullScreenMode={fullScreenMode}
toggleFullScreenMode={() => setFullScreenMode((prevData) => !prevData)} toggleFullScreenMode={() => setFullScreenMode((prevData) => !prevData)}
handleChartView={(key) => updateCurrentViewRenderPayload(null, key)} handleChartView={(key) => updateCurrentViewRenderPayload(null, key)}
@ -175,11 +160,12 @@ export const ChartViewRoot: FC<ChartViewRootProps> = observer((props) => {
title={title} title={title}
/> />
<GanttChartMainContent <GanttChartMainContent
blocks={blocks} blockIds={blockIds}
getBlockById={getBlockById}
loadMoreBlocks={loadMoreBlocks}
blockToRender={blockToRender} blockToRender={blockToRender}
blockUpdateHandler={blockUpdateHandler} blockUpdateHandler={blockUpdateHandler}
bottomSpacing={bottomSpacing} bottomSpacing={bottomSpacing}
chartBlocks={chartBlocks}
enableBlockLeftResize={enableBlockLeftResize} enableBlockLeftResize={enableBlockLeftResize}
enableBlockMove={enableBlockMove} enableBlockMove={enableBlockMove}
enableBlockRightResize={enableBlockRightResize} enableBlockRightResize={enableBlockRightResize}

View File

@ -1,6 +1,6 @@
import { FC } from "react"; import { FC } from "react";
// components // components
import { ChartViewRoot, IBlockUpdateData, IGanttBlock } from "components/gantt-chart"; import { ChartDataType, ChartViewRoot, IBlockUpdateData, IGanttBlock } from "components/gantt-chart";
// context // context
import { GanttStoreProvider } from "components/gantt-chart/contexts"; import { GanttStoreProvider } from "components/gantt-chart/contexts";
@ -8,11 +8,13 @@ type GanttChartRootProps = {
border?: boolean; border?: boolean;
title: string; title: string;
loaderTitle: string; loaderTitle: string;
blocks: IGanttBlock[] | null; blockIds: string[];
blockUpdateHandler: (block: any, payload: IBlockUpdateData) => void; blockUpdateHandler: (block: any, payload: IBlockUpdateData) => void;
blockToRender: (data: any) => React.ReactNode; blockToRender: (data: any) => React.ReactNode;
sidebarToRender: (props: any) => React.ReactNode; sidebarToRender: (props: any) => React.ReactNode;
quickAdd?: React.JSX.Element | undefined; quickAdd?: React.JSX.Element | undefined;
getBlockById: (id: string, currentViewData?: ChartDataType | undefined) => IGanttBlock;
loadMoreBlocks?: () => void;
enableBlockLeftResize?: boolean; enableBlockLeftResize?: boolean;
enableBlockRightResize?: boolean; enableBlockRightResize?: boolean;
enableBlockMove?: boolean; enableBlockMove?: boolean;
@ -26,11 +28,13 @@ export const GanttChartRoot: FC<GanttChartRootProps> = (props) => {
const { const {
border = true, border = true,
title, title,
blocks, blockIds,
loaderTitle = "blocks", loaderTitle = "blocks",
blockUpdateHandler, blockUpdateHandler,
sidebarToRender, sidebarToRender,
blockToRender, blockToRender,
getBlockById,
loadMoreBlocks,
enableBlockLeftResize = false, enableBlockLeftResize = false,
enableBlockRightResize = false, enableBlockRightResize = false,
enableBlockMove = false, enableBlockMove = false,
@ -46,7 +50,9 @@ export const GanttChartRoot: FC<GanttChartRootProps> = (props) => {
<ChartViewRoot <ChartViewRoot
border={border} border={border}
title={title} title={title}
blocks={blocks} blockIds={blockIds}
getBlockById={getBlockById}
loadMoreBlocks={loadMoreBlocks}
loaderTitle={loaderTitle} loaderTitle={loaderTitle}
blockUpdateHandler={blockUpdateHandler} blockUpdateHandler={blockUpdateHandler}
sidebarToRender={sidebarToRender} sidebarToRender={sidebarToRender}

View File

@ -2,22 +2,23 @@ import { DragDropContext, Draggable, DropResult, Droppable } from "@hello-pangea
// ui // ui
import { Loader } from "@plane/ui"; import { Loader } from "@plane/ui";
// components // components
import { IBlockUpdateData, IGanttBlock } from "components/gantt-chart/types"; import { ChartDataType, IBlockUpdateData, IGanttBlock } from "components/gantt-chart/types";
import { CyclesSidebarBlock } from "./block"; import { CyclesSidebarBlock } from "./block";
// types // types
type Props = { type Props = {
title: string; title: string;
blockUpdateHandler: (block: any, payload: IBlockUpdateData) => void; blockUpdateHandler: (block: any, payload: IBlockUpdateData) => void;
blocks: IGanttBlock[] | null; getBlockById: (id: string, currentViewData?: ChartDataType | undefined) => IGanttBlock;
blockIds: string[];
enableReorder: boolean; enableReorder: boolean;
}; };
export const CycleGanttSidebar: React.FC<Props> = (props) => { export const CycleGanttSidebar: React.FC<Props> = (props) => {
const { blockUpdateHandler, blocks, enableReorder } = props; const { blockUpdateHandler, blockIds, getBlockById, enableReorder } = props;
const handleOrderChange = (result: DropResult) => { const handleOrderChange = (result: DropResult) => {
if (!blocks) return; if (!blockIds) return;
const { source, destination } = result; const { source, destination } = result;
@ -27,29 +28,30 @@ export const CycleGanttSidebar: React.FC<Props> = (props) => {
// return if dropped on the same index // return if dropped on the same index
if (source.index === destination.index) return; if (source.index === destination.index) return;
let updatedSortOrder = blocks[source.index].sort_order; let updatedSortOrder = getBlockById(blockIds[source.index]).sort_order;
// update the sort order to the lowest if dropped at the top // update the sort order to the lowest if dropped at the top
if (destination.index === 0) updatedSortOrder = blocks[0].sort_order - 1000; if (destination.index === 0) updatedSortOrder = getBlockById(blockIds[0]).sort_order - 1000;
// update the sort order to the highest if dropped at the bottom // update the sort order to the highest if dropped at the bottom
else if (destination.index === blocks.length - 1) updatedSortOrder = blocks[blocks.length - 1].sort_order + 1000; else if (destination.index === blockIds.length - 1)
updatedSortOrder = getBlockById(blockIds[blockIds.length - 1]).sort_order + 1000;
// update the sort order to the average of the two adjacent blocks if dropped in between // update the sort order to the average of the two adjacent blocks if dropped in between
else { else {
const destinationSortingOrder = blocks[destination.index].sort_order; const destinationSortingOrder = getBlockById(blockIds[destination.index]).sort_order;
const relativeDestinationSortingOrder = const relativeDestinationSortingOrder =
source.index < destination.index source.index < destination.index
? blocks[destination.index + 1].sort_order ? getBlockById(blockIds[destination.index + 1]).sort_order
: blocks[destination.index - 1].sort_order; : getBlockById(blockIds[destination.index - 1]).sort_order;
updatedSortOrder = (destinationSortingOrder + relativeDestinationSortingOrder) / 2; updatedSortOrder = (destinationSortingOrder + relativeDestinationSortingOrder) / 2;
} }
// extract the element from the source index and insert it at the destination index without updating the entire array // extract the element from the source index and insert it at the destination index without updating the entire array
const removedElement = blocks.splice(source.index, 1)[0]; const removedElement = blockIds.splice(source.index, 1)[0];
blocks.splice(destination.index, 0, removedElement); blockIds.splice(destination.index, 0, removedElement);
// call the block update handler with the updated sort order, new and old index // call the block update handler with the updated sort order, new and old index
blockUpdateHandler(removedElement.data, { blockUpdateHandler(getBlockById(removedElement).data, {
sort_order: { sort_order: {
destinationIndex: destination.index, destinationIndex: destination.index,
newSortOrder: updatedSortOrder, newSortOrder: updatedSortOrder,
@ -64,24 +66,28 @@ export const CycleGanttSidebar: React.FC<Props> = (props) => {
{(droppableProvided) => ( {(droppableProvided) => (
<div className="h-full" ref={droppableProvided.innerRef} {...droppableProvided.droppableProps}> <div className="h-full" ref={droppableProvided.innerRef} {...droppableProvided.droppableProps}>
<> <>
{blocks ? ( {blockIds ? (
blocks.map((block, index) => ( blockIds.map((blockId, index) => {
<Draggable const block = getBlockById(blockId);
key={`sidebar-block-${block.id}`} if (!block.start_date || !block.target_date) return null;
draggableId={`sidebar-block-${block.id}`} return (
index={index} <Draggable
isDragDisabled={!enableReorder} key={`sidebar-block-${block.id}`}
> draggableId={`sidebar-block-${block.id}`}
{(provided, snapshot) => ( index={index}
<CyclesSidebarBlock isDragDisabled={!enableReorder}
block={block} >
enableReorder={enableReorder} {(provided, snapshot) => (
provided={provided} <CyclesSidebarBlock
snapshot={snapshot} block={block}
/> enableReorder={enableReorder}
)} provided={provided}
</Draggable> snapshot={snapshot}
)) />
)}
</Draggable>
);
})
) : ( ) : (
<Loader className="space-y-3 pr-2"> <Loader className="space-y-3 pr-2">
<Loader.Item height="34px" /> <Loader.Item height="34px" />

View File

@ -0,0 +1,34 @@
import { Draggable } from "@hello-pangea/dnd";
import { observer } from "mobx-react";
import { IssuesSidebarBlock } from "./block";
import { IGanttBlock } from "components/gantt-chart/types";
interface Props {
blockId: string;
enableReorder: boolean;
index: number;
showAllBlocks: boolean;
getBlockById: (blockId: string) => IGanttBlock;
}
export const IssueDraggableBlock = observer((props: Props) => {
const { blockId, enableReorder, index, showAllBlocks, getBlockById } = props;
const block = getBlockById(blockId);
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 null;
return (
<Draggable
key={`sidebar-block-${blockId}`}
draggableId={`sidebar-block-${blockId}`}
index={index}
isDragDisabled={!enableReorder}
>
{(provided, snapshot) => (
<IssuesSidebarBlock block={block} enableReorder={enableReorder} provided={provided} snapshot={snapshot} />
)}
</Draggable>
);
});

View File

@ -4,20 +4,29 @@ import { DragDropContext, Draggable, Droppable, DropResult } from "@hello-pangea
import { Loader } from "@plane/ui"; import { Loader } from "@plane/ui";
// types // types
import { IGanttBlock, IBlockUpdateData } from "components/gantt-chart/types"; import { IGanttBlock, IBlockUpdateData } from "components/gantt-chart/types";
import { IssuesSidebarBlock } from "./block"; import { observer } from "mobx-react";
import { IssueDraggableBlock } from "./issue-draggable-block";
import { useIntersectionObserver } from "hooks/use-intersection-observer";
import { useRef } from "react";
type Props = { type Props = {
blockUpdateHandler: (block: any, payload: IBlockUpdateData) => void; blockUpdateHandler: (block: any, payload: IBlockUpdateData) => void;
blocks: IGanttBlock[] | null; getBlockById: (id: string) => IGanttBlock;
loadMoreBlocks?: () => void;
blockIds: string[];
enableReorder: boolean; enableReorder: boolean;
showAllBlocks?: boolean; showAllBlocks?: boolean;
}; };
export const IssueGanttSidebar: React.FC<Props> = (props) => { export const IssueGanttSidebar: React.FC<Props> = observer((props) => {
const { blockUpdateHandler, blocks, enableReorder, showAllBlocks = false } = props; const { blockUpdateHandler, blockIds, getBlockById, enableReorder, loadMoreBlocks, showAllBlocks = false } = props;
const intersectionRef = useRef<HTMLSpanElement | null>(null);
useIntersectionObserver(undefined, intersectionRef, loadMoreBlocks);
const handleOrderChange = (result: DropResult) => { const handleOrderChange = (result: DropResult) => {
if (!blocks) return; if (!blockIds) return;
const { source, destination } = result; const { source, destination } = result;
@ -27,29 +36,30 @@ export const IssueGanttSidebar: React.FC<Props> = (props) => {
// return if dropped on the same index // return if dropped on the same index
if (source.index === destination.index) return; if (source.index === destination.index) return;
let updatedSortOrder = blocks[source.index].sort_order; let updatedSortOrder = getBlockById(blockIds[source.index]).sort_order;
// update the sort order to the lowest if dropped at the top // update the sort order to the lowest if dropped at the top
if (destination.index === 0) updatedSortOrder = blocks[0].sort_order - 1000; if (destination.index === 0) updatedSortOrder = getBlockById(blockIds[0]).sort_order - 1000;
// update the sort order to the highest if dropped at the bottom // update the sort order to the highest if dropped at the bottom
else if (destination.index === blocks.length - 1) updatedSortOrder = blocks[blocks.length - 1].sort_order + 1000; else if (destination.index === blockIds.length - 1)
updatedSortOrder = getBlockById(blockIds[blockIds.length - 1])!.sort_order + 1000;
// update the sort order to the average of the two adjacent blocks if dropped in between // update the sort order to the average of the two adjacent blocks if dropped in between
else { else {
const destinationSortingOrder = blocks[destination.index].sort_order; const destinationSortingOrder = getBlockById(blockIds[destination.index]).sort_order;
const relativeDestinationSortingOrder = const relativeDestinationSortingOrder =
source.index < destination.index source.index < destination.index
? blocks[destination.index + 1].sort_order ? getBlockById(blockIds[destination.index + 1]).sort_order
: blocks[destination.index - 1].sort_order; : getBlockById(blockIds[destination.index - 1]).sort_order;
updatedSortOrder = (destinationSortingOrder + relativeDestinationSortingOrder) / 2; updatedSortOrder = (destinationSortingOrder + relativeDestinationSortingOrder) / 2;
} }
// extract the element from the source index and insert it at the destination index without updating the entire array // extract the element from the source index and insert it at the destination index without updating the entire array
const removedElement = blocks.splice(source.index, 1)[0]; const removedElement = blockIds.splice(source.index, 1)[0];
blocks.splice(destination.index, 0, removedElement); blockIds.splice(destination.index, 0, removedElement);
// call the block update handler with the updated sort order, new and old index // call the block update handler with the updated sort order, new and old index
blockUpdateHandler(removedElement.data, { blockUpdateHandler(getBlockById(removedElement).data, {
sort_order: { sort_order: {
destinationIndex: destination.index, destinationIndex: destination.index,
newSortOrder: updatedSortOrder, newSortOrder: updatedSortOrder,
@ -64,31 +74,19 @@ export const IssueGanttSidebar: React.FC<Props> = (props) => {
{(droppableProvided) => ( {(droppableProvided) => (
<div ref={droppableProvided.innerRef} {...droppableProvided.droppableProps}> <div ref={droppableProvided.innerRef} {...droppableProvided.droppableProps}>
<> <>
{blocks ? ( {blockIds ? (
blocks.map((block, index) => { <>
const isBlockVisibleOnSidebar = block.start_date && block.target_date; {blockIds.map((blockId, index) => (
<IssueDraggableBlock
// hide the block if it doesn't have start and target dates and showAllBlocks is false blockId={blockId}
if (!showAllBlocks && !isBlockVisibleOnSidebar) return; enableReorder={enableReorder}
return (
<Draggable
key={`sidebar-block-${block.id}`}
draggableId={`sidebar-block-${block.id}`}
index={index} index={index}
isDragDisabled={!enableReorder} showAllBlocks={showAllBlocks}
> getBlockById={getBlockById}
{(provided, snapshot) => ( />
<IssuesSidebarBlock ))}
block={block} <span ref={intersectionRef} className="h-5 w-10 bg-custom-background-80 rounded animate-pulse" />
enableReorder={enableReorder} </>
provided={provided}
snapshot={snapshot}
/>
)}
</Draggable>
);
})
) : ( ) : (
<Loader className="space-y-3 pr-2"> <Loader className="space-y-3 pr-2">
<Loader.Item height="34px" /> <Loader.Item height="34px" />
@ -104,4 +102,4 @@ export const IssueGanttSidebar: React.FC<Props> = (props) => {
</Droppable> </Droppable>
</DragDropContext> </DragDropContext>
); );
}; });

View File

@ -2,22 +2,23 @@ import { DragDropContext, Draggable, Droppable, DropResult } from "@hello-pangea
// ui // ui
import { Loader } from "@plane/ui"; import { Loader } from "@plane/ui";
// components // components
import { IBlockUpdateData, IGanttBlock } from "components/gantt-chart"; import { ChartDataType, IBlockUpdateData, IGanttBlock } from "components/gantt-chart";
import { ModulesSidebarBlock } from "./block"; import { ModulesSidebarBlock } from "./block";
// types // types
type Props = { type Props = {
title: string; title: string;
blockUpdateHandler: (block: any, payload: IBlockUpdateData) => void; blockUpdateHandler: (block: any, payload: IBlockUpdateData) => void;
blocks: IGanttBlock[] | null; getBlockById: (id: string, currentViewData?: ChartDataType | undefined) => IGanttBlock;
blockIds: string[];
enableReorder: boolean; enableReorder: boolean;
}; };
export const ModuleGanttSidebar: React.FC<Props> = (props) => { export const ModuleGanttSidebar: React.FC<Props> = (props) => {
const { blockUpdateHandler, blocks, enableReorder } = props; const { blockUpdateHandler, blockIds, getBlockById, enableReorder } = props;
const handleOrderChange = (result: DropResult) => { const handleOrderChange = (result: DropResult) => {
if (!blocks) return; if (!blockIds) return;
const { source, destination } = result; const { source, destination } = result;
@ -27,29 +28,30 @@ export const ModuleGanttSidebar: React.FC<Props> = (props) => {
// return if dropped on the same index // return if dropped on the same index
if (source.index === destination.index) return; if (source.index === destination.index) return;
let updatedSortOrder = blocks[source.index].sort_order; let updatedSortOrder = getBlockById(blockIds[source.index]).sort_order;
// update the sort order to the lowest if dropped at the top // update the sort order to the lowest if dropped at the top
if (destination.index === 0) updatedSortOrder = blocks[0].sort_order - 1000; if (destination.index === 0) updatedSortOrder = getBlockById(blockIds[0]).sort_order - 1000;
// update the sort order to the highest if dropped at the bottom // update the sort order to the highest if dropped at the bottom
else if (destination.index === blocks.length - 1) updatedSortOrder = blocks[blocks.length - 1].sort_order + 1000; else if (destination.index === blockIds.length - 1)
updatedSortOrder = getBlockById(blockIds[blockIds.length - 1]).sort_order + 1000;
// update the sort order to the average of the two adjacent blocks if dropped in between // update the sort order to the average of the two adjacent blocks if dropped in between
else { else {
const destinationSortingOrder = blocks[destination.index].sort_order; const destinationSortingOrder = getBlockById(blockIds[destination.index]).sort_order;
const relativeDestinationSortingOrder = const relativeDestinationSortingOrder =
source.index < destination.index source.index < destination.index
? blocks[destination.index + 1].sort_order ? getBlockById(blockIds[destination.index + 1]).sort_order
: blocks[destination.index - 1].sort_order; : getBlockById(blockIds[destination.index - 1]).sort_order;
updatedSortOrder = (destinationSortingOrder + relativeDestinationSortingOrder) / 2; updatedSortOrder = (destinationSortingOrder + relativeDestinationSortingOrder) / 2;
} }
// extract the element from the source index and insert it at the destination index without updating the entire array // extract the element from the source index and insert it at the destination index without updating the entire array
const removedElement = blocks.splice(source.index, 1)[0]; const removedElement = blockIds.splice(source.index, 1)[0];
blocks.splice(destination.index, 0, removedElement); blockIds.splice(destination.index, 0, removedElement);
// call the block update handler with the updated sort order, new and old index // call the block update handler with the updated sort order, new and old index
blockUpdateHandler(removedElement.data, { blockUpdateHandler(getBlockById(removedElement).data, {
sort_order: { sort_order: {
destinationIndex: destination.index, destinationIndex: destination.index,
newSortOrder: updatedSortOrder, newSortOrder: updatedSortOrder,
@ -64,24 +66,27 @@ export const ModuleGanttSidebar: React.FC<Props> = (props) => {
{(droppableProvided) => ( {(droppableProvided) => (
<div className="h-full" ref={droppableProvided.innerRef} {...droppableProvided.droppableProps}> <div className="h-full" ref={droppableProvided.innerRef} {...droppableProvided.droppableProps}>
<> <>
{blocks ? ( {blockIds ? (
blocks.map((block, index) => ( blockIds.map((blockId, index) => {
<Draggable const block = getBlockById(blockId);
key={`sidebar-block-${block.id}`} return (
draggableId={`sidebar-block-${block.id}`} <Draggable
index={index} key={`sidebar-block-${block.id}`}
isDragDisabled={!enableReorder} draggableId={`sidebar-block-${block.id}`}
> index={index}
{(provided, snapshot) => ( isDragDisabled={!enableReorder}
<ModulesSidebarBlock >
block={block} {(provided, snapshot) => (
enableReorder={enableReorder} <ModulesSidebarBlock
provided={provided} block={block}
snapshot={snapshot} enableReorder={enableReorder}
/> provided={provided}
)} snapshot={snapshot}
</Draggable> />
)) )}
</Draggable>
);
})
) : ( ) : (
<Loader className="space-y-3 pr-2"> <Loader className="space-y-3 pr-2">
<Loader.Item height="34px" /> <Loader.Item height="34px" />

View File

@ -1,19 +1,30 @@
// components // components
import { IBlockUpdateData, IGanttBlock } from "components/gantt-chart"; import { ChartDataType, IBlockUpdateData, IGanttBlock } from "components/gantt-chart";
// constants // constants
import { HEADER_HEIGHT, SIDEBAR_WIDTH } from "../constants"; import { HEADER_HEIGHT, SIDEBAR_WIDTH } from "../constants";
type Props = { type Props = {
blocks: IGanttBlock[] | null; blockIds: string[];
blockUpdateHandler: (block: any, payload: IBlockUpdateData) => void; blockUpdateHandler: (block: any, payload: IBlockUpdateData) => void;
loadMoreBlocks?: () => void;
enableReorder: boolean; enableReorder: boolean;
sidebarToRender: (props: any) => React.ReactNode; sidebarToRender: (props: any) => React.ReactNode;
title: string; title: string;
getBlockById: (id: string, currentViewData?: ChartDataType | undefined) => IGanttBlock;
quickAdd?: React.JSX.Element | undefined; quickAdd?: React.JSX.Element | undefined;
}; };
export const GanttChartSidebar: React.FC<Props> = (props) => { export const GanttChartSidebar: React.FC<Props> = (props) => {
const { blocks, blockUpdateHandler, enableReorder, sidebarToRender, title, quickAdd } = props; const {
blockIds,
blockUpdateHandler,
enableReorder,
sidebarToRender,
getBlockById,
loadMoreBlocks,
title,
quickAdd,
} = props;
return ( return (
<div <div
@ -35,7 +46,8 @@ export const GanttChartSidebar: React.FC<Props> = (props) => {
</div> </div>
<div className="min-h-full h-max bg-custom-background-100 overflow-x-hidden overflow-y-auto"> <div className="min-h-full h-max bg-custom-background-100 overflow-x-hidden overflow-y-auto">
{sidebarToRender && sidebarToRender({ title, blockUpdateHandler, blocks, enableReorder })} {sidebarToRender &&
sidebarToRender({ title, blockUpdateHandler, blockIds, getBlockById, enableReorder, loadMoreBlocks })}
</div> </div>
{quickAdd ? quickAdd : null} {quickAdd ? quickAdd : null}
</div> </div>

View File

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

View File

@ -167,7 +167,7 @@ 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; if (!itemStartDate || !itemTargetDate) return;
startDate.setHours(0, 0, 0, 0); startDate.setHours(0, 0, 0, 0);
itemStartDate.setHours(0, 0, 0, 0); itemStartDate.setHours(0, 0, 0, 0);

View File

@ -1,20 +1,22 @@
import React from "react"; import React, { useCallback } from "react";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
// hooks // hooks
import { GanttChartRoot, IBlockUpdateData, IssueGanttSidebar } from "components/gantt-chart"; import { ChartDataType, GanttChartRoot, IBlockUpdateData, IssueGanttSidebar } from "components/gantt-chart";
import { GanttQuickAddIssueForm, IssueGanttBlock } from "components/issues"; import { GanttQuickAddIssueForm, IssueGanttBlock } from "components/issues";
import { EUserProjectRoles } from "constants/project"; import { EUserProjectRoles } from "constants/project";
import { renderIssueBlocksStructure } from "helpers/issue.helper"; import { getIssueBlocksStructure } from "helpers/issue.helper";
import { useIssues, useUser } from "hooks/store"; import { useIssues, useUser } from "hooks/store";
import { useIssuesActions } from "hooks/use-issues-actions"; import { useIssuesActions } from "hooks/use-issues-actions";
// components // components
// helpers // helpers
// types // types
import { TIssue, TUnGroupedIssues } from "@plane/types"; import { TIssue, TIssueGroup } from "@plane/types";
// constants // constants
import { EIssueLayoutTypes, EIssuesStoreType } from "constants/issue"; import { EIssueLayoutTypes, EIssuesStoreType } from "constants/issue";
import { IssueLayoutHOC } from "../issue-layout-HOC"; import { IssueLayoutHOC } from "../issue-layout-HOC";
import useSWR from "swr";
import { getMonthChartItemPositionWidthInMonth } from "components/gantt-chart/views";
type GanttStoreType = type GanttStoreType =
| EIssuesStoreType.PROJECT | EIssuesStoreType.PROJECT
@ -32,19 +34,40 @@ export const BaseGanttRoot: React.FC<IBaseGanttRoot> = observer((props: IBaseGan
const router = useRouter(); const router = useRouter();
const { workspaceSlug } = router.query; const { workspaceSlug } = router.query;
const { issues, issuesFilter } = useIssues(storeType); const { issues, issuesFilter, issueMap } = useIssues(storeType);
const { updateIssue } = useIssuesActions(storeType); const { fetchIssues, fetchNextIssues, updateIssue } = useIssuesActions(storeType);
// store hooks // store hooks
const { const {
membership: { currentProjectRole }, membership: { currentProjectRole },
} = useUser(); } = useUser();
const { issueMap } = useIssues();
const appliedDisplayFilters = issuesFilter.issueFilters?.displayFilters; const appliedDisplayFilters = issuesFilter.issueFilters?.displayFilters;
const issueIds = (issues.groupedIssueIds ?? []) as TUnGroupedIssues; useSWR(`ISSUE_GANTT_LAYOUT_${storeType}`, () => fetchIssues("init-loader", { canGroup: false, perPageCount: 100 }), {
revalidateOnFocus: false,
revalidateOnReconnect: false,
});
const issuesObject = issues.groupedIssueIds?.["All Issues"] as TIssueGroup;
const { enableIssueCreation } = issues?.viewFlags || {}; const { enableIssueCreation } = issues?.viewFlags || {};
const issuesArray = issueIds.map((id) => issueMap?.[id]); const loadMoreIssues = useCallback(() => {
fetchNextIssues();
}, [fetchNextIssues]);
const getBlockById = useCallback(
(id: string, currentViewData?: ChartDataType | undefined) => {
const issue = issueMap[id];
const block = getIssueBlocksStructure(issue);
if (currentViewData) {
return {
...block,
position: getMonthChartItemPositionWidthInMonth(currentViewData, block),
};
}
return block;
},
[issueMap]
);
const updateIssueBlockStructure = async (issue: TIssue, data: IBlockUpdateData) => { const updateIssueBlockStructure = async (issue: TIssue, data: IBlockUpdateData) => {
if (!workspaceSlug) return; if (!workspaceSlug) return;
@ -64,7 +87,8 @@ export const BaseGanttRoot: React.FC<IBaseGanttRoot> = observer((props: IBaseGan
border={false} border={false}
title="Issues" title="Issues"
loaderTitle="Issues" loaderTitle="Issues"
blocks={issues ? renderIssueBlocksStructure(issuesArray) : null} blockIds={issuesObject?.issueIds}
getBlockById={getBlockById}
blockUpdateHandler={updateIssueBlockStructure} blockUpdateHandler={updateIssueBlockStructure}
blockToRender={(data: TIssue) => <IssueGanttBlock issueId={data.id} />} blockToRender={(data: TIssue) => <IssueGanttBlock issueId={data.id} />}
sidebarToRender={(props) => <IssueGanttSidebar {...props} showAllBlocks />} sidebarToRender={(props) => <IssueGanttSidebar {...props} showAllBlocks />}
@ -78,6 +102,7 @@ export const BaseGanttRoot: React.FC<IBaseGanttRoot> = observer((props: IBaseGan
<GanttQuickAddIssueForm quickAddCallback={issues.quickAddIssue} viewId={viewId} /> <GanttQuickAddIssueForm quickAddCallback={issues.quickAddIssue} viewId={viewId} />
) : undefined ) : undefined
} }
loadMoreBlocks={loadMoreIssues}
showAllBlocks showAllBlocks
/> />
</div> </div>

View File

@ -2,11 +2,13 @@ import { observer } from "mobx-react-lite";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
// mobx store // mobx store
// components // components
import { GanttChartRoot, IBlockUpdateData, ModuleGanttSidebar } from "components/gantt-chart"; import { ChartDataType, GanttChartRoot, IBlockUpdateData, ModuleGanttSidebar } from "components/gantt-chart";
import { ModuleGanttBlock } from "components/modules"; import { ModuleGanttBlock } from "components/modules";
import { useModule, useProject } from "hooks/store"; import { useModule, useProject } from "hooks/store";
// types // types
import { IModule } from "@plane/types"; import { IModule } from "@plane/types";
import { useCallback } from "react";
import { getMonthChartItemPositionWidthInMonth } from "components/gantt-chart/views";
export const ModulesListGanttChartView: React.FC = observer(() => { export const ModulesListGanttChartView: React.FC = observer(() => {
// router // router
@ -14,7 +16,7 @@ export const ModulesListGanttChartView: React.FC = observer(() => {
const { workspaceSlug } = router.query; const { workspaceSlug } = router.query;
// store // store
const { currentProjectDetails } = useProject(); const { currentProjectDetails } = useProject();
const { projectModuleIds, moduleMap, updateModuleDetails } = useModule(); const { projectModuleIds, getModuleById, updateModuleDetails } = useModule();
const handleModuleUpdate = async (module: IModule, data: IBlockUpdateData) => { const handleModuleUpdate = async (module: IModule, data: IBlockUpdateData) => {
if (!workspaceSlug || !module) return; if (!workspaceSlug || !module) return;
@ -25,26 +27,39 @@ export const ModulesListGanttChartView: React.FC = observer(() => {
await updateModuleDetails(workspaceSlug.toString(), module.project_id, module.id, payload); await updateModuleDetails(workspaceSlug.toString(), module.project_id, module.id, payload);
}; };
const blockFormat = (blocks: string[]) => const getBlockById = useCallback(
blocks?.map((blockId) => { (id: string, currentViewData?: ChartDataType | undefined) => {
const block = moduleMap[blockId]; const module = getModuleById(id);
return {
data: block, const block = {
id: block.id, data: module,
sort_order: block.sort_order, id: module?.id ?? "",
start_date: block.start_date ? new Date(block.start_date) : null, sort_order: module?.sort_order ?? 0,
target_date: block.target_date ? new Date(block.target_date) : null, start_date: module?.start_date ? new Date(module.start_date) : undefined,
target_date: module?.target_date ? new Date(module.target_date) : undefined,
}; };
}); if (currentViewData) {
return {
...block,
position: getMonthChartItemPositionWidthInMonth(currentViewData, block),
};
}
return block;
},
[getModuleById]
);
const isAllowed = currentProjectDetails?.member_role === 20 || currentProjectDetails?.member_role === 15; const isAllowed = currentProjectDetails?.member_role === 20 || currentProjectDetails?.member_role === 15;
if (!projectModuleIds) return null;
return ( return (
<div className="h-full w-full overflow-y-auto"> <div className="h-full w-full overflow-y-auto">
<GanttChartRoot <GanttChartRoot
title="Modules" title="Modules"
loaderTitle="Modules" loaderTitle="Modules"
blocks={projectModuleIds ? blockFormat(projectModuleIds) : null} blockIds={projectModuleIds}
getBlockById={getBlockById}
sidebarToRender={(props) => <ModuleGanttSidebar {...props} />} sidebarToRender={(props) => <ModuleGanttSidebar {...props} />}
blockUpdateHandler={(block, payload) => handleModuleUpdate(block, payload)} blockUpdateHandler={(block, payload) => handleModuleUpdate(block, payload)}
blockToRender={(data: IModule) => <ModuleGanttBlock moduleId={data.id} />} blockToRender={(data: IModule) => <ModuleGanttBlock moduleId={data.id} />}

View File

@ -156,14 +156,15 @@ export const shouldHighlightIssueDueDate = (
// if the issue is overdue, highlight the due date // if the issue is overdue, highlight the due date
return targetDateDistance <= 0; return targetDateDistance <= 0;
}; };
export const renderIssueBlocksStructure = (blocks: TIssue[]): IGanttBlock[] => export const getIssueBlocksStructure = (block: TIssue): IGanttBlock => {
blocks?.map((block) => ({ return {
data: block, data: block,
id: block.id, id: block.id,
sort_order: block.sort_order, sort_order: block.sort_order,
start_date: block.start_date ? new Date(block.start_date) : null, start_date: block.start_date ? new Date(block.start_date) : undefined,
target_date: block.target_date ? new Date(block.target_date) : null, target_date: block.target_date ? new Date(block.target_date) : undefined,
})); };
};
export function getChangedIssuefields(formData: Partial<TIssue>, dirtyFields: { [key: string]: boolean | undefined }) { export function getChangedIssuefields(formData: Partial<TIssue>, dirtyFields: { [key: string]: boolean | undefined }) {
const changedFields: Partial<TIssue> = {}; const changedFields: Partial<TIssue> = {};