mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
restructure gantt layout charts
This commit is contained in:
parent
ae339bc19c
commit
ce43067bc1
@ -1,13 +1,14 @@
|
||||
import { FC } from "react";
|
||||
import { FC, useCallback } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { useRouter } from "next/router";
|
||||
// hooks
|
||||
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";
|
||||
// components
|
||||
// types
|
||||
import { ICycle } from "@plane/types";
|
||||
import { getMonthChartItemPositionWidthInMonth } from "components/gantt-chart/views";
|
||||
// constants
|
||||
|
||||
type Props = {
|
||||
@ -23,6 +24,28 @@ export const CyclesListGanttChartView: FC<Props> = observer((props) => {
|
||||
// store hooks
|
||||
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) => {
|
||||
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);
|
||||
};
|
||||
|
||||
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 (
|
||||
<div className="h-full w-full overflow-y-auto">
|
||||
<GanttChartRoot
|
||||
title="Cycles"
|
||||
loaderTitle="Cycles"
|
||||
blocks={cycleIds ? blockFormat(cycleIds.map((c) => getCycleById(c))) : null}
|
||||
blockIds={cycleIds}
|
||||
getBlockById={getBlockById}
|
||||
blockUpdateHandler={(block, payload) => handleCycleUpdate(block, payload)}
|
||||
sidebarToRender={(props) => <CycleGanttSidebar {...props} />}
|
||||
blockToRender={(data: ICycle) => <CycleGanttBlock cycleId={data.id} />}
|
||||
|
@ -10,10 +10,12 @@ import { useIssueDetail } from "hooks/store";
|
||||
import { BLOCK_HEIGHT } from "../constants";
|
||||
import { ChartAddBlock, ChartDraggable } from "../helpers";
|
||||
import { useGanttChart } from "../hooks";
|
||||
import { IBlockUpdateData, IGanttBlock } from "../types";
|
||||
import { ChartDataType, IBlockUpdateData, IGanttBlock } from "../types";
|
||||
|
||||
type Props = {
|
||||
block: IGanttBlock;
|
||||
blockId: string;
|
||||
getBlockById: (id: string, currentViewData?: ChartDataType | undefined) => IGanttBlock;
|
||||
showAllBlocks: boolean;
|
||||
blockToRender: (data: any) => React.ReactNode;
|
||||
blockUpdateHandler: (block: any, payload: IBlockUpdateData) => void;
|
||||
enableBlockLeftResize: boolean;
|
||||
@ -25,7 +27,9 @@ type Props = {
|
||||
|
||||
export const GanttChartBlock: React.FC<Props> = observer((props) => {
|
||||
const {
|
||||
block,
|
||||
blockId,
|
||||
getBlockById,
|
||||
showAllBlocks,
|
||||
blockToRender,
|
||||
blockUpdateHandler,
|
||||
enableBlockLeftResize,
|
||||
@ -35,9 +39,14 @@ export const GanttChartBlock: React.FC<Props> = observer((props) => {
|
||||
ganttContainerRef,
|
||||
} = props;
|
||||
// store hooks
|
||||
const { updateActiveBlockId, isBlockActive } = useGanttChart();
|
||||
const { currentViewData, updateActiveBlockId, isBlockActive } = useGanttChart();
|
||||
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 handleChartBlockPosition = (
|
||||
@ -72,7 +81,6 @@ export const GanttChartBlock: React.FC<Props> = observer((props) => {
|
||||
|
||||
return (
|
||||
<div
|
||||
key={`block-${block.id}`}
|
||||
className="relative min-w-full w-max"
|
||||
style={{
|
||||
height: `${BLOCK_HEIGHT}px`,
|
||||
@ -80,11 +88,11 @@ export const GanttChartBlock: React.FC<Props> = observer((props) => {
|
||||
>
|
||||
<div
|
||||
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":
|
||||
peekIssue?.issueId === block.data.id,
|
||||
})}
|
||||
onMouseEnter={() => updateActiveBlockId(block.id)}
|
||||
onMouseEnter={() => updateActiveBlockId(blockId)}
|
||||
onMouseLeave={() => updateActiveBlockId(null)}
|
||||
>
|
||||
{isBlockVisibleOnChart ? (
|
||||
|
@ -1,14 +1,15 @@
|
||||
import { FC } from "react";
|
||||
// components
|
||||
import { HEADER_HEIGHT } from "../constants";
|
||||
import { IBlockUpdateData, IGanttBlock } from "../types";
|
||||
import { ChartDataType, IBlockUpdateData, IGanttBlock } from "../types";
|
||||
import { GanttChartBlock } from "./block";
|
||||
// types
|
||||
// constants
|
||||
|
||||
export type GanttChartBlocksProps = {
|
||||
itemsContainerWidth: number;
|
||||
blocks: IGanttBlock[] | null;
|
||||
blockIds: string[];
|
||||
getBlockById: (id: string, currentViewData?: ChartDataType | undefined) => IGanttBlock;
|
||||
blockToRender: (data: any) => React.ReactNode;
|
||||
blockUpdateHandler: (block: any, payload: IBlockUpdateData) => void;
|
||||
enableBlockLeftResize: boolean;
|
||||
@ -22,9 +23,10 @@ export type GanttChartBlocksProps = {
|
||||
export const GanttChartBlocksList: FC<GanttChartBlocksProps> = (props) => {
|
||||
const {
|
||||
itemsContainerWidth,
|
||||
blocks,
|
||||
blockIds,
|
||||
blockToRender,
|
||||
blockUpdateHandler,
|
||||
getBlockById,
|
||||
enableBlockLeftResize,
|
||||
enableBlockRightResize,
|
||||
enableBlockMove,
|
||||
@ -41,14 +43,13 @@ export const GanttChartBlocksList: FC<GanttChartBlocksProps> = (props) => {
|
||||
transform: `translateY(${HEADER_HEIGHT}px)`,
|
||||
}}
|
||||
>
|
||||
{blocks?.map((block) => {
|
||||
// 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;
|
||||
|
||||
{blockIds?.map((blockId) => {
|
||||
return (
|
||||
<GanttChartBlock
|
||||
key={block.id}
|
||||
block={block}
|
||||
key={blockId}
|
||||
blockId={blockId}
|
||||
getBlockById={getBlockById}
|
||||
showAllBlocks={showAllBlocks}
|
||||
blockToRender={blockToRender}
|
||||
blockUpdateHandler={blockUpdateHandler}
|
||||
enableBlockLeftResize={enableBlockLeftResize}
|
||||
|
@ -10,7 +10,7 @@ import { IGanttBlock, TGanttViews } from "../types";
|
||||
// constants
|
||||
|
||||
type Props = {
|
||||
blocks: IGanttBlock[] | null;
|
||||
blockIds: string[];
|
||||
fullScreenMode: boolean;
|
||||
handleChartView: (view: TGanttViews) => void;
|
||||
handleToday: () => void;
|
||||
@ -20,7 +20,7 @@ type 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
|
||||
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="flex items-center gap-2 text-lg font-medium">{title}</div>
|
||||
<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 className="flex flex-wrap items-center gap-2">
|
||||
|
@ -4,6 +4,7 @@ import { observer } from "mobx-react";
|
||||
// components
|
||||
import {
|
||||
BiWeekChartView,
|
||||
ChartDataType,
|
||||
DayChartView,
|
||||
GanttChartBlocksList,
|
||||
GanttChartSidebar,
|
||||
@ -21,11 +22,12 @@ import { cn } from "helpers/common.helper";
|
||||
import { useGanttChart } from "../hooks/use-gantt-chart";
|
||||
|
||||
type Props = {
|
||||
blocks: IGanttBlock[] | null;
|
||||
blockIds: string[];
|
||||
getBlockById: (id: string, currentViewData?: ChartDataType | undefined) => IGanttBlock;
|
||||
loadMoreBlocks?: () => void;
|
||||
blockToRender: (data: any) => React.ReactNode;
|
||||
blockUpdateHandler: (block: any, payload: IBlockUpdateData) => void;
|
||||
bottomSpacing: boolean;
|
||||
chartBlocks: IGanttBlock[] | null;
|
||||
enableBlockLeftResize: boolean;
|
||||
enableBlockMove: boolean;
|
||||
enableBlockRightResize: boolean;
|
||||
@ -41,11 +43,12 @@ type Props = {
|
||||
|
||||
export const GanttChartMainContent: React.FC<Props> = observer((props) => {
|
||||
const {
|
||||
blocks,
|
||||
blockIds,
|
||||
getBlockById,
|
||||
loadMoreBlocks,
|
||||
blockToRender,
|
||||
blockUpdateHandler,
|
||||
bottomSpacing,
|
||||
chartBlocks,
|
||||
enableBlockLeftResize,
|
||||
enableBlockMove,
|
||||
enableBlockRightResize,
|
||||
@ -104,7 +107,9 @@ export const GanttChartMainContent: React.FC<Props> = observer((props) => {
|
||||
onScroll={onScroll}
|
||||
>
|
||||
<GanttChartSidebar
|
||||
blocks={blocks}
|
||||
blockIds={blockIds}
|
||||
getBlockById={getBlockById}
|
||||
loadMoreBlocks={loadMoreBlocks}
|
||||
blockUpdateHandler={blockUpdateHandler}
|
||||
enableReorder={enableReorder}
|
||||
sidebarToRender={sidebarToRender}
|
||||
@ -116,7 +121,8 @@ export const GanttChartMainContent: React.FC<Props> = observer((props) => {
|
||||
{currentViewData && (
|
||||
<GanttChartBlocksList
|
||||
itemsContainerWidth={itemsContainerWidth}
|
||||
blocks={chartBlocks}
|
||||
blockIds={blockIds}
|
||||
getBlockById={getBlockById}
|
||||
blockToRender={blockToRender}
|
||||
blockUpdateHandler={blockUpdateHandler}
|
||||
enableBlockLeftResize={enableBlockLeftResize}
|
||||
|
@ -13,17 +13,13 @@ import { currentViewDataWithView } from "../data";
|
||||
// constants
|
||||
import { useGanttChart } from "../hooks/use-gantt-chart";
|
||||
import { ChartDataType, IBlockUpdateData, IGanttBlock, TGanttViews } from "../types";
|
||||
import {
|
||||
generateMonthChart,
|
||||
getNumberOfDaysBetweenTwoDatesInMonth,
|
||||
getMonthChartItemPositionWidthInMonth,
|
||||
} from "../views";
|
||||
import { generateMonthChart, getNumberOfDaysBetweenTwoDatesInMonth } from "../views";
|
||||
|
||||
type ChartViewRootProps = {
|
||||
border: boolean;
|
||||
title: string;
|
||||
loaderTitle: string;
|
||||
blocks: IGanttBlock[] | null;
|
||||
blockIds: string[];
|
||||
blockUpdateHandler: (block: any, payload: IBlockUpdateData) => void;
|
||||
blockToRender: (data: any) => React.ReactNode;
|
||||
sidebarToRender: (props: any) => React.ReactNode;
|
||||
@ -34,6 +30,8 @@ type ChartViewRootProps = {
|
||||
enableAddBlock: boolean;
|
||||
bottomSpacing: boolean;
|
||||
showAllBlocks: boolean;
|
||||
getBlockById: (id: string, currentViewData?: ChartDataType | undefined) => IGanttBlock;
|
||||
loadMoreBlocks?: () => void;
|
||||
quickAdd?: React.JSX.Element | undefined;
|
||||
};
|
||||
|
||||
@ -41,7 +39,9 @@ export const ChartViewRoot: FC<ChartViewRootProps> = observer((props) => {
|
||||
const {
|
||||
border,
|
||||
title,
|
||||
blocks = null,
|
||||
blockIds,
|
||||
getBlockById,
|
||||
loadMoreBlocks,
|
||||
loaderTitle,
|
||||
blockUpdateHandler,
|
||||
sidebarToRender,
|
||||
@ -58,25 +58,10 @@ export const ChartViewRoot: FC<ChartViewRootProps> = observer((props) => {
|
||||
// states
|
||||
const [itemsContainerWidth, setItemsContainerWidth] = useState(0);
|
||||
const [fullScreenMode, setFullScreenMode] = useState(false);
|
||||
const [chartBlocks, setChartBlocks] = useState<IGanttBlock[] | null>(null);
|
||||
// hooks
|
||||
const { currentView, currentViewData, renderView, updateCurrentView, updateCurrentViewData, updateRenderView } =
|
||||
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 selectedCurrentView: TGanttViews = view;
|
||||
const selectedCurrentViewData: ChartDataType | undefined =
|
||||
@ -166,7 +151,7 @@ export const ChartViewRoot: FC<ChartViewRootProps> = observer((props) => {
|
||||
})}
|
||||
>
|
||||
<GanttChartHeader
|
||||
blocks={blocks}
|
||||
blockIds={blockIds}
|
||||
fullScreenMode={fullScreenMode}
|
||||
toggleFullScreenMode={() => setFullScreenMode((prevData) => !prevData)}
|
||||
handleChartView={(key) => updateCurrentViewRenderPayload(null, key)}
|
||||
@ -175,11 +160,12 @@ export const ChartViewRoot: FC<ChartViewRootProps> = observer((props) => {
|
||||
title={title}
|
||||
/>
|
||||
<GanttChartMainContent
|
||||
blocks={blocks}
|
||||
blockIds={blockIds}
|
||||
getBlockById={getBlockById}
|
||||
loadMoreBlocks={loadMoreBlocks}
|
||||
blockToRender={blockToRender}
|
||||
blockUpdateHandler={blockUpdateHandler}
|
||||
bottomSpacing={bottomSpacing}
|
||||
chartBlocks={chartBlocks}
|
||||
enableBlockLeftResize={enableBlockLeftResize}
|
||||
enableBlockMove={enableBlockMove}
|
||||
enableBlockRightResize={enableBlockRightResize}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { FC } from "react";
|
||||
// components
|
||||
import { ChartViewRoot, IBlockUpdateData, IGanttBlock } from "components/gantt-chart";
|
||||
import { ChartDataType, ChartViewRoot, IBlockUpdateData, IGanttBlock } from "components/gantt-chart";
|
||||
// context
|
||||
import { GanttStoreProvider } from "components/gantt-chart/contexts";
|
||||
|
||||
@ -8,11 +8,13 @@ type GanttChartRootProps = {
|
||||
border?: boolean;
|
||||
title: string;
|
||||
loaderTitle: string;
|
||||
blocks: IGanttBlock[] | null;
|
||||
blockIds: string[];
|
||||
blockUpdateHandler: (block: any, payload: IBlockUpdateData) => void;
|
||||
blockToRender: (data: any) => React.ReactNode;
|
||||
sidebarToRender: (props: any) => React.ReactNode;
|
||||
quickAdd?: React.JSX.Element | undefined;
|
||||
getBlockById: (id: string, currentViewData?: ChartDataType | undefined) => IGanttBlock;
|
||||
loadMoreBlocks?: () => void;
|
||||
enableBlockLeftResize?: boolean;
|
||||
enableBlockRightResize?: boolean;
|
||||
enableBlockMove?: boolean;
|
||||
@ -26,11 +28,13 @@ export const GanttChartRoot: FC<GanttChartRootProps> = (props) => {
|
||||
const {
|
||||
border = true,
|
||||
title,
|
||||
blocks,
|
||||
blockIds,
|
||||
loaderTitle = "blocks",
|
||||
blockUpdateHandler,
|
||||
sidebarToRender,
|
||||
blockToRender,
|
||||
getBlockById,
|
||||
loadMoreBlocks,
|
||||
enableBlockLeftResize = false,
|
||||
enableBlockRightResize = false,
|
||||
enableBlockMove = false,
|
||||
@ -46,7 +50,9 @@ export const GanttChartRoot: FC<GanttChartRootProps> = (props) => {
|
||||
<ChartViewRoot
|
||||
border={border}
|
||||
title={title}
|
||||
blocks={blocks}
|
||||
blockIds={blockIds}
|
||||
getBlockById={getBlockById}
|
||||
loadMoreBlocks={loadMoreBlocks}
|
||||
loaderTitle={loaderTitle}
|
||||
blockUpdateHandler={blockUpdateHandler}
|
||||
sidebarToRender={sidebarToRender}
|
||||
|
@ -2,22 +2,23 @@ import { DragDropContext, Draggable, DropResult, Droppable } from "@hello-pangea
|
||||
// ui
|
||||
import { Loader } from "@plane/ui";
|
||||
// components
|
||||
import { IBlockUpdateData, IGanttBlock } from "components/gantt-chart/types";
|
||||
import { ChartDataType, IBlockUpdateData, IGanttBlock } from "components/gantt-chart/types";
|
||||
import { CyclesSidebarBlock } from "./block";
|
||||
// types
|
||||
|
||||
type Props = {
|
||||
title: string;
|
||||
blockUpdateHandler: (block: any, payload: IBlockUpdateData) => void;
|
||||
blocks: IGanttBlock[] | null;
|
||||
getBlockById: (id: string, currentViewData?: ChartDataType | undefined) => IGanttBlock;
|
||||
blockIds: string[];
|
||||
enableReorder: boolean;
|
||||
};
|
||||
|
||||
export const CycleGanttSidebar: React.FC<Props> = (props) => {
|
||||
const { blockUpdateHandler, blocks, enableReorder } = props;
|
||||
const { blockUpdateHandler, blockIds, getBlockById, enableReorder } = props;
|
||||
|
||||
const handleOrderChange = (result: DropResult) => {
|
||||
if (!blocks) return;
|
||||
if (!blockIds) return;
|
||||
|
||||
const { source, destination } = result;
|
||||
|
||||
@ -27,29 +28,30 @@ export const CycleGanttSidebar: React.FC<Props> = (props) => {
|
||||
// return if dropped on the same index
|
||||
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
|
||||
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
|
||||
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
|
||||
else {
|
||||
const destinationSortingOrder = blocks[destination.index].sort_order;
|
||||
const destinationSortingOrder = getBlockById(blockIds[destination.index]).sort_order;
|
||||
const relativeDestinationSortingOrder =
|
||||
source.index < destination.index
|
||||
? blocks[destination.index + 1].sort_order
|
||||
: blocks[destination.index - 1].sort_order;
|
||||
? getBlockById(blockIds[destination.index + 1]).sort_order
|
||||
: getBlockById(blockIds[destination.index - 1]).sort_order;
|
||||
|
||||
updatedSortOrder = (destinationSortingOrder + relativeDestinationSortingOrder) / 2;
|
||||
}
|
||||
|
||||
// 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];
|
||||
blocks.splice(destination.index, 0, removedElement);
|
||||
const removedElement = blockIds.splice(source.index, 1)[0];
|
||||
blockIds.splice(destination.index, 0, removedElement);
|
||||
|
||||
// call the block update handler with the updated sort order, new and old index
|
||||
blockUpdateHandler(removedElement.data, {
|
||||
blockUpdateHandler(getBlockById(removedElement).data, {
|
||||
sort_order: {
|
||||
destinationIndex: destination.index,
|
||||
newSortOrder: updatedSortOrder,
|
||||
@ -64,24 +66,28 @@ export const CycleGanttSidebar: React.FC<Props> = (props) => {
|
||||
{(droppableProvided) => (
|
||||
<div className="h-full" ref={droppableProvided.innerRef} {...droppableProvided.droppableProps}>
|
||||
<>
|
||||
{blocks ? (
|
||||
blocks.map((block, index) => (
|
||||
<Draggable
|
||||
key={`sidebar-block-${block.id}`}
|
||||
draggableId={`sidebar-block-${block.id}`}
|
||||
index={index}
|
||||
isDragDisabled={!enableReorder}
|
||||
>
|
||||
{(provided, snapshot) => (
|
||||
<CyclesSidebarBlock
|
||||
block={block}
|
||||
enableReorder={enableReorder}
|
||||
provided={provided}
|
||||
snapshot={snapshot}
|
||||
/>
|
||||
)}
|
||||
</Draggable>
|
||||
))
|
||||
{blockIds ? (
|
||||
blockIds.map((blockId, index) => {
|
||||
const block = getBlockById(blockId);
|
||||
if (!block.start_date || !block.target_date) return null;
|
||||
return (
|
||||
<Draggable
|
||||
key={`sidebar-block-${block.id}`}
|
||||
draggableId={`sidebar-block-${block.id}`}
|
||||
index={index}
|
||||
isDragDisabled={!enableReorder}
|
||||
>
|
||||
{(provided, snapshot) => (
|
||||
<CyclesSidebarBlock
|
||||
block={block}
|
||||
enableReorder={enableReorder}
|
||||
provided={provided}
|
||||
snapshot={snapshot}
|
||||
/>
|
||||
)}
|
||||
</Draggable>
|
||||
);
|
||||
})
|
||||
) : (
|
||||
<Loader className="space-y-3 pr-2">
|
||||
<Loader.Item height="34px" />
|
||||
|
@ -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>
|
||||
);
|
||||
});
|
@ -4,20 +4,29 @@ import { DragDropContext, Draggable, Droppable, DropResult } from "@hello-pangea
|
||||
import { Loader } from "@plane/ui";
|
||||
// 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 = {
|
||||
blockUpdateHandler: (block: any, payload: IBlockUpdateData) => void;
|
||||
blocks: IGanttBlock[] | null;
|
||||
getBlockById: (id: string) => IGanttBlock;
|
||||
loadMoreBlocks?: () => void;
|
||||
blockIds: string[];
|
||||
enableReorder: boolean;
|
||||
showAllBlocks?: boolean;
|
||||
};
|
||||
|
||||
export const IssueGanttSidebar: React.FC<Props> = (props) => {
|
||||
const { blockUpdateHandler, blocks, enableReorder, showAllBlocks = false } = props;
|
||||
export const IssueGanttSidebar: React.FC<Props> = observer((props) => {
|
||||
const { blockUpdateHandler, blockIds, getBlockById, enableReorder, loadMoreBlocks, showAllBlocks = false } = props;
|
||||
|
||||
const intersectionRef = useRef<HTMLSpanElement | null>(null);
|
||||
|
||||
useIntersectionObserver(undefined, intersectionRef, loadMoreBlocks);
|
||||
|
||||
const handleOrderChange = (result: DropResult) => {
|
||||
if (!blocks) return;
|
||||
if (!blockIds) return;
|
||||
|
||||
const { source, destination } = result;
|
||||
|
||||
@ -27,29 +36,30 @@ export const IssueGanttSidebar: React.FC<Props> = (props) => {
|
||||
// return if dropped on the same index
|
||||
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
|
||||
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
|
||||
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
|
||||
else {
|
||||
const destinationSortingOrder = blocks[destination.index].sort_order;
|
||||
const destinationSortingOrder = getBlockById(blockIds[destination.index]).sort_order;
|
||||
const relativeDestinationSortingOrder =
|
||||
source.index < destination.index
|
||||
? blocks[destination.index + 1].sort_order
|
||||
: blocks[destination.index - 1].sort_order;
|
||||
? getBlockById(blockIds[destination.index + 1]).sort_order
|
||||
: getBlockById(blockIds[destination.index - 1]).sort_order;
|
||||
|
||||
updatedSortOrder = (destinationSortingOrder + relativeDestinationSortingOrder) / 2;
|
||||
}
|
||||
|
||||
// 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];
|
||||
blocks.splice(destination.index, 0, removedElement);
|
||||
const removedElement = blockIds.splice(source.index, 1)[0];
|
||||
blockIds.splice(destination.index, 0, removedElement);
|
||||
|
||||
// call the block update handler with the updated sort order, new and old index
|
||||
blockUpdateHandler(removedElement.data, {
|
||||
blockUpdateHandler(getBlockById(removedElement).data, {
|
||||
sort_order: {
|
||||
destinationIndex: destination.index,
|
||||
newSortOrder: updatedSortOrder,
|
||||
@ -64,31 +74,19 @@ export const IssueGanttSidebar: React.FC<Props> = (props) => {
|
||||
{(droppableProvided) => (
|
||||
<div ref={droppableProvided.innerRef} {...droppableProvided.droppableProps}>
|
||||
<>
|
||||
{blocks ? (
|
||||
blocks.map((block, index) => {
|
||||
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;
|
||||
|
||||
return (
|
||||
<Draggable
|
||||
key={`sidebar-block-${block.id}`}
|
||||
draggableId={`sidebar-block-${block.id}`}
|
||||
{blockIds ? (
|
||||
<>
|
||||
{blockIds.map((blockId, index) => (
|
||||
<IssueDraggableBlock
|
||||
blockId={blockId}
|
||||
enableReorder={enableReorder}
|
||||
index={index}
|
||||
isDragDisabled={!enableReorder}
|
||||
>
|
||||
{(provided, snapshot) => (
|
||||
<IssuesSidebarBlock
|
||||
block={block}
|
||||
enableReorder={enableReorder}
|
||||
provided={provided}
|
||||
snapshot={snapshot}
|
||||
/>
|
||||
)}
|
||||
</Draggable>
|
||||
);
|
||||
})
|
||||
showAllBlocks={showAllBlocks}
|
||||
getBlockById={getBlockById}
|
||||
/>
|
||||
))}
|
||||
<span ref={intersectionRef} className="h-5 w-10 bg-custom-background-80 rounded animate-pulse" />
|
||||
</>
|
||||
) : (
|
||||
<Loader className="space-y-3 pr-2">
|
||||
<Loader.Item height="34px" />
|
||||
@ -104,4 +102,4 @@ export const IssueGanttSidebar: React.FC<Props> = (props) => {
|
||||
</Droppable>
|
||||
</DragDropContext>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
@ -2,22 +2,23 @@ import { DragDropContext, Draggable, Droppable, DropResult } from "@hello-pangea
|
||||
// ui
|
||||
import { Loader } from "@plane/ui";
|
||||
// components
|
||||
import { IBlockUpdateData, IGanttBlock } from "components/gantt-chart";
|
||||
import { ChartDataType, IBlockUpdateData, IGanttBlock } from "components/gantt-chart";
|
||||
import { ModulesSidebarBlock } from "./block";
|
||||
// types
|
||||
|
||||
type Props = {
|
||||
title: string;
|
||||
blockUpdateHandler: (block: any, payload: IBlockUpdateData) => void;
|
||||
blocks: IGanttBlock[] | null;
|
||||
getBlockById: (id: string, currentViewData?: ChartDataType | undefined) => IGanttBlock;
|
||||
blockIds: string[];
|
||||
enableReorder: boolean;
|
||||
};
|
||||
|
||||
export const ModuleGanttSidebar: React.FC<Props> = (props) => {
|
||||
const { blockUpdateHandler, blocks, enableReorder } = props;
|
||||
const { blockUpdateHandler, blockIds, getBlockById, enableReorder } = props;
|
||||
|
||||
const handleOrderChange = (result: DropResult) => {
|
||||
if (!blocks) return;
|
||||
if (!blockIds) return;
|
||||
|
||||
const { source, destination } = result;
|
||||
|
||||
@ -27,29 +28,30 @@ export const ModuleGanttSidebar: React.FC<Props> = (props) => {
|
||||
// return if dropped on the same index
|
||||
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
|
||||
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
|
||||
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
|
||||
else {
|
||||
const destinationSortingOrder = blocks[destination.index].sort_order;
|
||||
const destinationSortingOrder = getBlockById(blockIds[destination.index]).sort_order;
|
||||
const relativeDestinationSortingOrder =
|
||||
source.index < destination.index
|
||||
? blocks[destination.index + 1].sort_order
|
||||
: blocks[destination.index - 1].sort_order;
|
||||
? getBlockById(blockIds[destination.index + 1]).sort_order
|
||||
: getBlockById(blockIds[destination.index - 1]).sort_order;
|
||||
|
||||
updatedSortOrder = (destinationSortingOrder + relativeDestinationSortingOrder) / 2;
|
||||
}
|
||||
|
||||
// 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];
|
||||
blocks.splice(destination.index, 0, removedElement);
|
||||
const removedElement = blockIds.splice(source.index, 1)[0];
|
||||
blockIds.splice(destination.index, 0, removedElement);
|
||||
|
||||
// call the block update handler with the updated sort order, new and old index
|
||||
blockUpdateHandler(removedElement.data, {
|
||||
blockUpdateHandler(getBlockById(removedElement).data, {
|
||||
sort_order: {
|
||||
destinationIndex: destination.index,
|
||||
newSortOrder: updatedSortOrder,
|
||||
@ -64,24 +66,27 @@ export const ModuleGanttSidebar: React.FC<Props> = (props) => {
|
||||
{(droppableProvided) => (
|
||||
<div className="h-full" ref={droppableProvided.innerRef} {...droppableProvided.droppableProps}>
|
||||
<>
|
||||
{blocks ? (
|
||||
blocks.map((block, index) => (
|
||||
<Draggable
|
||||
key={`sidebar-block-${block.id}`}
|
||||
draggableId={`sidebar-block-${block.id}`}
|
||||
index={index}
|
||||
isDragDisabled={!enableReorder}
|
||||
>
|
||||
{(provided, snapshot) => (
|
||||
<ModulesSidebarBlock
|
||||
block={block}
|
||||
enableReorder={enableReorder}
|
||||
provided={provided}
|
||||
snapshot={snapshot}
|
||||
/>
|
||||
)}
|
||||
</Draggable>
|
||||
))
|
||||
{blockIds ? (
|
||||
blockIds.map((blockId, index) => {
|
||||
const block = getBlockById(blockId);
|
||||
return (
|
||||
<Draggable
|
||||
key={`sidebar-block-${block.id}`}
|
||||
draggableId={`sidebar-block-${block.id}`}
|
||||
index={index}
|
||||
isDragDisabled={!enableReorder}
|
||||
>
|
||||
{(provided, snapshot) => (
|
||||
<ModulesSidebarBlock
|
||||
block={block}
|
||||
enableReorder={enableReorder}
|
||||
provided={provided}
|
||||
snapshot={snapshot}
|
||||
/>
|
||||
)}
|
||||
</Draggable>
|
||||
);
|
||||
})
|
||||
) : (
|
||||
<Loader className="space-y-3 pr-2">
|
||||
<Loader.Item height="34px" />
|
||||
|
@ -1,19 +1,30 @@
|
||||
// components
|
||||
import { IBlockUpdateData, IGanttBlock } from "components/gantt-chart";
|
||||
import { ChartDataType, IBlockUpdateData, IGanttBlock } from "components/gantt-chart";
|
||||
// constants
|
||||
import { HEADER_HEIGHT, SIDEBAR_WIDTH } from "../constants";
|
||||
|
||||
type Props = {
|
||||
blocks: IGanttBlock[] | null;
|
||||
blockIds: string[];
|
||||
blockUpdateHandler: (block: any, payload: IBlockUpdateData) => void;
|
||||
loadMoreBlocks?: () => void;
|
||||
enableReorder: boolean;
|
||||
sidebarToRender: (props: any) => React.ReactNode;
|
||||
title: string;
|
||||
getBlockById: (id: string, currentViewData?: ChartDataType | undefined) => IGanttBlock;
|
||||
quickAdd?: React.JSX.Element | undefined;
|
||||
};
|
||||
|
||||
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 (
|
||||
<div
|
||||
@ -35,7 +46,8 @@ export const GanttChartSidebar: React.FC<Props> = (props) => {
|
||||
</div>
|
||||
|
||||
<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>
|
||||
{quickAdd ? quickAdd : null}
|
||||
</div>
|
||||
|
@ -6,8 +6,8 @@ export interface IGanttBlock {
|
||||
width: number;
|
||||
};
|
||||
sort_order: number;
|
||||
start_date: Date | null;
|
||||
target_date: Date | null;
|
||||
start_date: Date | undefined;
|
||||
target_date: Date | undefined;
|
||||
}
|
||||
|
||||
export interface IBlockUpdateData {
|
||||
|
@ -167,7 +167,7 @@ export const getMonthChartItemPositionWidthInMonth = (chartData: ChartDataType,
|
||||
const { startDate } = chartData.data;
|
||||
const { start_date: itemStartDate, target_date: itemTargetDate } = itemData;
|
||||
|
||||
if (!itemStartDate || !itemTargetDate) return null;
|
||||
if (!itemStartDate || !itemTargetDate) return;
|
||||
|
||||
startDate.setHours(0, 0, 0, 0);
|
||||
itemStartDate.setHours(0, 0, 0, 0);
|
||||
|
@ -1,20 +1,22 @@
|
||||
import React from "react";
|
||||
import React, { useCallback } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { useRouter } from "next/router";
|
||||
// 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 { EUserProjectRoles } from "constants/project";
|
||||
import { renderIssueBlocksStructure } from "helpers/issue.helper";
|
||||
import { getIssueBlocksStructure } from "helpers/issue.helper";
|
||||
import { useIssues, useUser } from "hooks/store";
|
||||
import { useIssuesActions } from "hooks/use-issues-actions";
|
||||
// components
|
||||
// helpers
|
||||
// types
|
||||
import { TIssue, TUnGroupedIssues } from "@plane/types";
|
||||
import { TIssue, TIssueGroup } from "@plane/types";
|
||||
// constants
|
||||
import { EIssueLayoutTypes, EIssuesStoreType } from "constants/issue";
|
||||
import { IssueLayoutHOC } from "../issue-layout-HOC";
|
||||
import useSWR from "swr";
|
||||
import { getMonthChartItemPositionWidthInMonth } from "components/gantt-chart/views";
|
||||
|
||||
type GanttStoreType =
|
||||
| EIssuesStoreType.PROJECT
|
||||
@ -32,19 +34,40 @@ export const BaseGanttRoot: React.FC<IBaseGanttRoot> = observer((props: IBaseGan
|
||||
const router = useRouter();
|
||||
const { workspaceSlug } = router.query;
|
||||
|
||||
const { issues, issuesFilter } = useIssues(storeType);
|
||||
const { updateIssue } = useIssuesActions(storeType);
|
||||
const { issues, issuesFilter, issueMap } = useIssues(storeType);
|
||||
const { fetchIssues, fetchNextIssues, updateIssue } = useIssuesActions(storeType);
|
||||
// store hooks
|
||||
const {
|
||||
membership: { currentProjectRole },
|
||||
} = useUser();
|
||||
const { issueMap } = useIssues();
|
||||
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 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) => {
|
||||
if (!workspaceSlug) return;
|
||||
@ -64,7 +87,8 @@ export const BaseGanttRoot: React.FC<IBaseGanttRoot> = observer((props: IBaseGan
|
||||
border={false}
|
||||
title="Issues"
|
||||
loaderTitle="Issues"
|
||||
blocks={issues ? renderIssueBlocksStructure(issuesArray) : null}
|
||||
blockIds={issuesObject?.issueIds}
|
||||
getBlockById={getBlockById}
|
||||
blockUpdateHandler={updateIssueBlockStructure}
|
||||
blockToRender={(data: TIssue) => <IssueGanttBlock issueId={data.id} />}
|
||||
sidebarToRender={(props) => <IssueGanttSidebar {...props} showAllBlocks />}
|
||||
@ -78,6 +102,7 @@ export const BaseGanttRoot: React.FC<IBaseGanttRoot> = observer((props: IBaseGan
|
||||
<GanttQuickAddIssueForm quickAddCallback={issues.quickAddIssue} viewId={viewId} />
|
||||
) : undefined
|
||||
}
|
||||
loadMoreBlocks={loadMoreIssues}
|
||||
showAllBlocks
|
||||
/>
|
||||
</div>
|
||||
|
@ -2,11 +2,13 @@ import { observer } from "mobx-react-lite";
|
||||
import { useRouter } from "next/router";
|
||||
// mobx store
|
||||
// components
|
||||
import { GanttChartRoot, IBlockUpdateData, ModuleGanttSidebar } from "components/gantt-chart";
|
||||
import { ChartDataType, GanttChartRoot, IBlockUpdateData, ModuleGanttSidebar } from "components/gantt-chart";
|
||||
import { ModuleGanttBlock } from "components/modules";
|
||||
import { useModule, useProject } from "hooks/store";
|
||||
// types
|
||||
import { IModule } from "@plane/types";
|
||||
import { useCallback } from "react";
|
||||
import { getMonthChartItemPositionWidthInMonth } from "components/gantt-chart/views";
|
||||
|
||||
export const ModulesListGanttChartView: React.FC = observer(() => {
|
||||
// router
|
||||
@ -14,7 +16,7 @@ export const ModulesListGanttChartView: React.FC = observer(() => {
|
||||
const { workspaceSlug } = router.query;
|
||||
// store
|
||||
const { currentProjectDetails } = useProject();
|
||||
const { projectModuleIds, moduleMap, updateModuleDetails } = useModule();
|
||||
const { projectModuleIds, getModuleById, updateModuleDetails } = useModule();
|
||||
|
||||
const handleModuleUpdate = async (module: IModule, data: IBlockUpdateData) => {
|
||||
if (!workspaceSlug || !module) return;
|
||||
@ -25,26 +27,39 @@ export const ModulesListGanttChartView: React.FC = observer(() => {
|
||||
await updateModuleDetails(workspaceSlug.toString(), module.project_id, module.id, payload);
|
||||
};
|
||||
|
||||
const blockFormat = (blocks: string[]) =>
|
||||
blocks?.map((blockId) => {
|
||||
const block = moduleMap[blockId];
|
||||
return {
|
||||
data: block,
|
||||
id: block.id,
|
||||
sort_order: block.sort_order,
|
||||
start_date: block.start_date ? new Date(block.start_date) : null,
|
||||
target_date: block.target_date ? new Date(block.target_date) : null,
|
||||
const getBlockById = useCallback(
|
||||
(id: string, currentViewData?: ChartDataType | undefined) => {
|
||||
const module = getModuleById(id);
|
||||
|
||||
const block = {
|
||||
data: module,
|
||||
id: module?.id ?? "",
|
||||
sort_order: module?.sort_order ?? 0,
|
||||
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;
|
||||
|
||||
if (!projectModuleIds) return null;
|
||||
|
||||
return (
|
||||
<div className="h-full w-full overflow-y-auto">
|
||||
<GanttChartRoot
|
||||
title="Modules"
|
||||
loaderTitle="Modules"
|
||||
blocks={projectModuleIds ? blockFormat(projectModuleIds) : null}
|
||||
blockIds={projectModuleIds}
|
||||
getBlockById={getBlockById}
|
||||
sidebarToRender={(props) => <ModuleGanttSidebar {...props} />}
|
||||
blockUpdateHandler={(block, payload) => handleModuleUpdate(block, payload)}
|
||||
blockToRender={(data: IModule) => <ModuleGanttBlock moduleId={data.id} />}
|
||||
|
@ -156,14 +156,15 @@ export const shouldHighlightIssueDueDate = (
|
||||
// if the issue is overdue, highlight the due date
|
||||
return targetDateDistance <= 0;
|
||||
};
|
||||
export const renderIssueBlocksStructure = (blocks: TIssue[]): IGanttBlock[] =>
|
||||
blocks?.map((block) => ({
|
||||
export const getIssueBlocksStructure = (block: TIssue): IGanttBlock => {
|
||||
return {
|
||||
data: block,
|
||||
id: block.id,
|
||||
sort_order: block.sort_order,
|
||||
start_date: block.start_date ? new Date(block.start_date) : null,
|
||||
target_date: block.target_date ? new Date(block.target_date) : null,
|
||||
}));
|
||||
start_date: block.start_date ? new Date(block.start_date) : undefined,
|
||||
target_date: block.target_date ? new Date(block.target_date) : undefined,
|
||||
};
|
||||
};
|
||||
|
||||
export function getChangedIssuefields(formData: Partial<TIssue>, dirtyFields: { [key: string]: boolean | undefined }) {
|
||||
const changedFields: Partial<TIssue> = {};
|
||||
|
Loading…
Reference in New Issue
Block a user