Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: gantt sidebar #2705

Merged
merged 3 commits into from
Nov 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions web/components/cycles/gantt-chart/cycles-list-layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { CycleService } from "services/cycle.service";
import useUser from "hooks/use-user";
import useProjectDetails from "hooks/use-project-details";
// components
import { GanttChartRoot, IBlockUpdateData } from "components/gantt-chart";
import { GanttChartRoot, IBlockUpdateData, CycleGanttSidebar } from "components/gantt-chart";
import { CycleGanttBlock, CycleGanttSidebarBlock } from "components/cycles";
// types
import { ICycle } from "types";
Expand Down Expand Up @@ -85,8 +85,8 @@ export const CyclesListGanttChartView: FC<Props> = ({ cycles, mutateCycles }) =>
loaderTitle="Cycles"
blocks={cycles ? blockFormat(cycles) : null}
blockUpdateHandler={(block, payload) => handleCycleUpdate(block, payload)}
sidebarToRender={(props) => <CycleGanttSidebar {...props} />}
blockToRender={(data: ICycle) => <CycleGanttBlock data={data} />}
sidebarBlockToRender={(data: ICycle) => <CycleGanttSidebarBlock data={data} />}
enableBlockLeftResize={false}
enableBlockRightResize={false}
enableBlockMove={false}
Expand Down
15 changes: 5 additions & 10 deletions web/components/gantt-chart/chart/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { FC, useEffect, useState } from "react";
// icons
// components
import { GanttChartBlocks } from "components/gantt-chart";
import { GanttSidebar } from "../sidebar";
// import { GanttSidebar } from "../sidebar";
// import { HourChartView } from "./hours";
// import { DayChartView } from "./day";
// import { WeekChartView } from "./week";
Expand Down Expand Up @@ -40,7 +40,7 @@ type ChartViewRootProps = {
blocks: IGanttBlock[] | null;
blockUpdateHandler: (block: any, payload: IBlockUpdateData) => void;
blockToRender: (data: any) => React.ReactNode;
sidebarBlockToRender: (block: any) => React.ReactNode;
sidebarToRender: (props: any) => React.ReactNode;
enableBlockLeftResize: boolean;
enableBlockRightResize: boolean;
enableBlockMove: boolean;
Expand All @@ -54,7 +54,7 @@ export const ChartViewRoot: FC<ChartViewRootProps> = ({
blocks = null,
loaderTitle,
blockUpdateHandler,
sidebarBlockToRender,
sidebarToRender,
blockToRender,
enableBlockLeftResize,
enableBlockRightResize,
Expand Down Expand Up @@ -285,13 +285,8 @@ export const ChartViewRoot: FC<ChartViewRootProps> = ({
<h6>{title}</h6>
<h6>Duration</h6>
</div>
<GanttSidebar
title={title}
blockUpdateHandler={blockUpdateHandler}
blocks={chartBlocks}
sidebarBlockToRender={sidebarBlockToRender}
enableReorder={enableReorder}
/>

{sidebarToRender && sidebarToRender({ title, blockUpdateHandler, blocks, enableReorder })}
</div>
<div
className="relative flex h-full w-full flex-1 flex-col overflow-hidden overflow-x-auto horizontal-scroll-enable"
Expand Down
1 change: 1 addition & 0 deletions web/components/gantt-chart/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ export * from "./helpers";
export * from "./hooks";
export * from "./root";
export * from "./types";
export * from "./sidebar";
6 changes: 3 additions & 3 deletions web/components/gantt-chart/root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ type GanttChartRootProps = {
blocks: IGanttBlock[] | null;
blockUpdateHandler: (block: any, payload: IBlockUpdateData) => void;
blockToRender: (data: any) => React.ReactNode;
sidebarBlockToRender: (block: any) => React.ReactNode;
sidebarToRender: (props: any) => React.ReactNode;
enableBlockLeftResize?: boolean;
enableBlockRightResize?: boolean;
enableBlockMove?: boolean;
Expand All @@ -27,7 +27,7 @@ export const GanttChartRoot: FC<GanttChartRootProps> = ({
blocks,
loaderTitle = "blocks",
blockUpdateHandler,
sidebarBlockToRender,
sidebarToRender,
blockToRender,
enableBlockLeftResize = true,
enableBlockRightResize = true,
Expand All @@ -42,7 +42,7 @@ export const GanttChartRoot: FC<GanttChartRootProps> = ({
blocks={blocks}
loaderTitle={loaderTitle}
blockUpdateHandler={blockUpdateHandler}
sidebarBlockToRender={sidebarBlockToRender}
sidebarToRender={sidebarToRender}
blockToRender={blockToRender}
enableBlockLeftResize={enableBlockLeftResize}
enableBlockRightResize={enableBlockRightResize}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,26 @@ import { DragDropContext, Draggable, DropResult } from "@hello-pangea/dnd";
import StrictModeDroppable from "components/dnd/StrictModeDroppable";
import { MoreVertical } from "lucide-react";
// hooks
import { useChart } from "./hooks";
import { useChart } from "components/gantt-chart/hooks";
// ui
import { Loader } from "@plane/ui";
// components
import { CycleGanttSidebarBlock } from "components/cycles";
// helpers
import { findTotalDaysInRange } from "helpers/date-time.helper";
// types
import { IBlockUpdateData, IGanttBlock } from "./types";
import { IBlockUpdateData, IGanttBlock } from "components/gantt-chart/types";

type Props = {
title: string;
blockUpdateHandler: (block: any, payload: IBlockUpdateData) => void;
blocks: IGanttBlock[] | null;
SidebarBlockRender: React.FC<any>;
enableReorder: boolean;
};

export const GanttSidebar: React.FC<Props> = (props) => {
export const CycleGanttSidebar: React.FC<Props> = (props) => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { title, blockUpdateHandler, blocks, SidebarBlockRender, enableReorder } = props;
const { title, blockUpdateHandler, blocks, enableReorder } = props;

const router = useRouter();
const { cycleId } = router.query;
Expand Down Expand Up @@ -128,7 +129,7 @@ export const GanttSidebar: React.FC<Props> = (props) => {
)}
<div className="flex-grow truncate h-full flex items-center justify-between gap-2">
<div className="flex-grow truncate">
<SidebarBlockRender data={block.data} />
<CycleGanttSidebarBlock data={block.data} />
</div>
<div className="flex-shrink-0 text-sm text-custom-text-200">
{duration} day{duration > 1 ? "s" : ""}
Expand Down
4 changes: 4 additions & 0 deletions web/components/gantt-chart/sidebar/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export * from "./cycle-sidebar";
export * from "./module-sidebar";
export * from "./sidebar";
export * from "./project-view-sidebar";
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,26 @@ import { DragDropContext, Draggable, DropResult } from "@hello-pangea/dnd";
import StrictModeDroppable from "components/dnd/StrictModeDroppable";
import { MoreVertical } from "lucide-react";
// hooks
import { useChart } from "./hooks";
import { useChart } from "components/gantt-chart/hooks";
// ui
import { Loader } from "@plane/ui";
// components
import { ModuleGanttSidebarBlock } from "components/modules";
// helpers
import { findTotalDaysInRange } from "helpers/date-time.helper";
// types
import { IBlockUpdateData, IGanttBlock } from "./types";
import { IBlockUpdateData, IGanttBlock } from "components/gantt-chart";

type Props = {
title: string;
blockUpdateHandler: (block: any, payload: IBlockUpdateData) => void;
blocks: IGanttBlock[] | null;
SidebarBlockRender: React.FC<any>;
enableReorder: boolean;
};

export const GanttSidebar: React.FC<Props> = (props) => {
export const ModuleGanttSidebar: React.FC<Props> = (props) => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { title, blockUpdateHandler, blocks, SidebarBlockRender, enableReorder } = props;
const { title, blockUpdateHandler, blocks, enableReorder } = props;

const router = useRouter();
const { cycleId } = router.query;
Expand Down Expand Up @@ -128,7 +129,7 @@ export const GanttSidebar: React.FC<Props> = (props) => {
)}
<div className="flex-grow truncate h-full flex items-center justify-between gap-2">
<div className="flex-grow truncate">
<SidebarBlockRender data={block.data} />
<ModuleGanttSidebarBlock data={block.data} />
</div>
<div className="flex-shrink-0 text-sm text-custom-text-200">
{duration} day{duration > 1 ? "s" : ""}
Expand Down
160 changes: 160 additions & 0 deletions web/components/gantt-chart/sidebar/project-view-sidebar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
import { useRouter } from "next/router";
import { DragDropContext, Draggable, DropResult } from "@hello-pangea/dnd";
import StrictModeDroppable from "components/dnd/StrictModeDroppable";
import { MoreVertical } from "lucide-react";
// hooks
import { useChart } from "components/gantt-chart/hooks";
// ui
import { Loader } from "@plane/ui";
// components
import { IssueGanttSidebarBlock } from "components/issues";
// helpers
import { findTotalDaysInRange } from "helpers/date-time.helper";
// types
import { IBlockUpdateData, IGanttBlock } from "components/gantt-chart/types";

type Props = {
title: string;
blockUpdateHandler: (block: any, payload: IBlockUpdateData) => void;
blocks: IGanttBlock[] | null;
enableReorder: boolean;
enableQuickIssueCreate?: boolean;
};

export const ProjectViewGanttSidebar: React.FC<Props> = (props) => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { title, blockUpdateHandler, blocks, enableReorder } = props;

const router = useRouter();
const { cycleId } = router.query;

const { activeBlock, dispatch } = useChart();

// update the active block on hover
const updateActiveBlock = (block: IGanttBlock | null) => {
dispatch({
type: "PARTIAL_UPDATE",
payload: {
activeBlock: block,
},
});
};

const handleOrderChange = (result: DropResult) => {
if (!blocks) return;

const { source, destination } = result;

// return if dropped outside the list
if (!destination) return;

// return if dropped on the same index
if (source.index === destination.index) return;

let updatedSortOrder = blocks[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;
// 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;
// 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 relativeDestinationSortingOrder =
source.index < destination.index
? blocks[destination.index + 1].sort_order
: blocks[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);

// call the block update handler with the updated sort order, new and old index
blockUpdateHandler(removedElement.data, {
sort_order: {
destinationIndex: destination.index,
newSortOrder: updatedSortOrder,
sourceIndex: source.index,
},
});
};

return (
<DragDropContext onDragEnd={handleOrderChange}>
<StrictModeDroppable droppableId="gantt-sidebar">
{(droppableProvided) => (
<div
id={`gantt-sidebar-${cycleId}`}
className="max-h-full overflow-y-auto pl-2.5 mt-3"
ref={droppableProvided.innerRef}
{...droppableProvided.droppableProps}
>
<>
{blocks ? (
blocks.map((block, index) => {
const duration = findTotalDaysInRange(block.start_date ?? "", block.target_date ?? "", true);

return (
<Draggable
key={`sidebar-block-${block.id}`}
draggableId={`sidebar-block-${block.id}`}
index={index}
isDragDisabled={!enableReorder}
>
{(provided, snapshot) => (
<div
className={`h-11 ${snapshot.isDragging ? "bg-custom-background-80 rounded" : ""}`}
onMouseEnter={() => updateActiveBlock(block)}
onMouseLeave={() => updateActiveBlock(null)}
ref={provided.innerRef}
{...provided.draggableProps}
>
<div
id={`sidebar-block-${block.id}`}
className={`group h-full w-full flex items-center gap-2 rounded-l px-2 pr-4 ${
activeBlock?.id === block.id ? "bg-custom-background-80" : ""
}`}
>
{enableReorder && (
<button
type="button"
className="rounded p-0.5 text-custom-sidebar-text-200 flex flex-shrink-0 opacity-0 group-hover:opacity-100"
{...provided.dragHandleProps}
>
<MoreVertical className="h-3.5 w-3.5" />
<MoreVertical className="h-3.5 w-3.5 -ml-5" />
</button>
)}
<div className="flex-grow truncate h-full flex items-center justify-between gap-2">
<div className="flex-grow truncate">
<IssueGanttSidebarBlock data={block.data} handleIssue={blockUpdateHandler} />
</div>
<div className="flex-shrink-0 text-sm text-custom-text-200">
{duration} day{duration > 1 ? "s" : ""}
</div>
</div>
</div>
</div>
)}
</Draggable>
);
})
) : (
<Loader className="pr-2 space-y-3">
<Loader.Item height="34px" />
<Loader.Item height="34px" />
<Loader.Item height="34px" />
<Loader.Item height="34px" />
</Loader>
)}
{droppableProvided.placeholder}
</>
</div>
)}
</StrictModeDroppable>
</DragDropContext>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,27 @@ import { DragDropContext, Draggable, DropResult } from "@hello-pangea/dnd";
import StrictModeDroppable from "components/dnd/StrictModeDroppable";
import { MoreVertical } from "lucide-react";
// hooks
import { useChart } from "./hooks";
import { useChart } from "components/gantt-chart/hooks";
// ui
import { Loader } from "@plane/ui";
// components
import { GanttInlineCreateIssueForm } from "components/issues";
import { GanttInlineCreateIssueForm, IssueGanttSidebarBlock } from "components/issues";
// helpers
import { findTotalDaysInRange } from "helpers/date-time.helper";
// types
import { IBlockUpdateData, IGanttBlock } from "./types";
import { IGanttBlock, IBlockUpdateData } from "components/gantt-chart/types";

type Props = {
title: string;
blockUpdateHandler: (block: any, payload: IBlockUpdateData) => void;
blocks: IGanttBlock[] | null;
sidebarBlockToRender: (block: any) => React.ReactNode;
enableReorder: boolean;
enableQuickIssueCreate?: boolean;
};

export const GanttSidebar: React.FC<Props> = (props) => {
export const IssueGanttSidebar: React.FC<Props> = (props) => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { title, blockUpdateHandler, blocks, sidebarBlockToRender, enableReorder, enableQuickIssueCreate } = props;
const { title, blockUpdateHandler, blocks, enableReorder, enableQuickIssueCreate } = props;

const router = useRouter();
const { cycleId } = router.query;
Expand Down Expand Up @@ -130,7 +129,9 @@ export const GanttSidebar: React.FC<Props> = (props) => {
</button>
)}
<div className="flex-grow truncate h-full flex items-center justify-between gap-2">
<div className="flex-grow truncate">{sidebarBlockToRender(block.data)}</div>
<div className="flex-grow truncate">
<IssueGanttSidebarBlock data={block.data} handleIssue={blockUpdateHandler} />
</div>
<div className="flex-shrink-0 text-sm text-custom-text-200">
{duration} day{duration > 1 ? "s" : ""}
</div>
Expand All @@ -151,7 +152,7 @@ export const GanttSidebar: React.FC<Props> = (props) => {
)}
{droppableProvided.placeholder}
</>
<GanttInlineCreateIssueForm />
{enableQuickIssueCreate && <GanttInlineCreateIssueForm />}
</div>
)}
</StrictModeDroppable>
Expand Down
Loading
Loading