Skip to content

Commit

Permalink
refactor: Gantt chart layout (#3585)
Browse files Browse the repository at this point in the history
* chore: gantt sidebar and main content scroll sync

* chore: add arrow navigation position logic

* refactor: scroll position update logic

* refactor: gantt chart components

* refactor: gantt sidebar

* fix: vertical scroll issue

* fix: move to the hidden block button flickering

* refactor: gantt sidebar components

* chore: move timeline header outside

* fix gantt scroll issue

* fix: sticky position issues

* fix: infinite timeline scroll logic

* chore: removed unnecessary import statements

---------

Co-authored-by: rahulramesha <[email protected]>
  • Loading branch information
aaryan610 and rahulramesha authored Feb 12, 2024
1 parent 3eb819c commit 963d26c
Show file tree
Hide file tree
Showing 36 changed files with 1,035 additions and 829 deletions.
55 changes: 39 additions & 16 deletions web/components/cycles/gantt-chart/blocks.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,30 @@
import { useRouter } from "next/router";
import { observer } from "mobx-react";
// hooks
import { useApplication, useCycle } from "hooks/store";
// ui
import { Tooltip, ContrastIcon } from "@plane/ui";
// helpers
import { renderFormattedDate } from "helpers/date-time.helper";
// types
import { ICycle } from "@plane/types";

export const CycleGanttBlock = ({ data }: { data: ICycle }) => {
type Props = {
cycleId: string;
};

export const CycleGanttBlock: React.FC<Props> = observer((props) => {
const { cycleId } = props;
// router
const router = useRouter();
const { workspaceSlug } = router.query;
// store hooks
const {
router: { workspaceSlug },
} = useApplication();
const { getCycleById } = useCycle();
// derived values
const cycleDetails = getCycleById(cycleId);

const cycleStatus = cycleDetails?.status.toLocaleLowerCase();

const cycleStatus = data.status.toLocaleLowerCase();
return (
<div
className="relative flex h-full w-full items-center rounded"
Expand All @@ -26,36 +40,45 @@ export const CycleGanttBlock = ({ data }: { data: ICycle }) => {
? "rgb(var(--color-text-200))"
: "",
}}
onClick={() => router.push(`/${workspaceSlug}/projects/${data?.project}/cycles/${data?.id}`)}
onClick={() => router.push(`/${workspaceSlug}/projects/${cycleDetails?.project}/cycles/${cycleDetails?.id}`)}
>
<div className="absolute left-0 top-0 h-full w-full bg-custom-background-100/50" />
<Tooltip
tooltipContent={
<div className="space-y-1">
<h5>{data?.name}</h5>
<h5>{cycleDetails?.name}</h5>
<div>
{renderFormattedDate(data?.start_date ?? "")} to {renderFormattedDate(data?.end_date ?? "")}
{renderFormattedDate(cycleDetails?.start_date ?? "")} to{" "}
{renderFormattedDate(cycleDetails?.end_date ?? "")}
</div>
</div>
}
position="top-left"
>
<div className="relative w-full truncate px-2.5 py-1 text-sm text-custom-text-100">{data?.name}</div>
<div className="relative w-full truncate px-2.5 py-1 text-sm text-custom-text-100">{cycleDetails?.name}</div>
</Tooltip>
</div>
);
};
});

export const CycleGanttSidebarBlock = ({ data }: { data: ICycle }) => {
export const CycleGanttSidebarBlock: React.FC<Props> = observer((props) => {
const { cycleId } = props;
// router
const router = useRouter();
const { workspaceSlug } = router.query;
// store hooks
const {
router: { workspaceSlug },
} = useApplication();
const { getCycleById } = useCycle();
// derived values
const cycleDetails = getCycleById(cycleId);

const cycleStatus = data.status.toLocaleLowerCase();
const cycleStatus = cycleDetails?.status.toLocaleLowerCase();

return (
<div
className="relative flex h-full w-full items-center gap-2"
onClick={() => router.push(`/${workspaceSlug}/projects/${data?.project}/cycles/${data?.id}`)}
onClick={() => router.push(`/${workspaceSlug}/projects/${cycleDetails?.project}/cycles/${cycleDetails?.id}`)}
>
<ContrastIcon
className="h-5 w-5 flex-shrink-0"
Expand All @@ -71,7 +94,7 @@ export const CycleGanttSidebarBlock = ({ data }: { data: ICycle }) => {
: ""
}`}
/>
<h6 className="flex-grow truncate text-sm font-medium">{data?.name}</h6>
<h6 className="flex-grow truncate text-sm font-medium">{cycleDetails?.name}</h6>
</div>
);
};
});
2 changes: 1 addition & 1 deletion web/components/cycles/gantt-chart/cycles-list-layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export const CyclesListGanttChartView: FC<Props> = observer((props) => {
blocks={cycleIds ? blockFormat(cycleIds.map((c) => getCycleById(c))) : null}
blockUpdateHandler={(block, payload) => handleCycleUpdate(block, payload)}
sidebarToRender={(props) => <CycleGanttSidebar {...props} />}
blockToRender={(data: ICycle) => <CycleGanttBlock data={data} />}
blockToRender={(data: ICycle) => <CycleGanttBlock cycleId={data.id} />}
enableBlockLeftResize={false}
enableBlockRightResize={false}
enableBlockMove={false}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { observer } from "mobx-react";
import { FC } from "react";
// hooks
import { useIssueDetail } from "hooks/store";
Expand All @@ -8,6 +9,8 @@ import { renderFormattedPayloadDate } from "helpers/date-time.helper";
import { cn } from "helpers/common.helper";
// types
import { IBlockUpdateData, IGanttBlock } from "../types";
// constants
import { BLOCK_HEIGHT, HEADER_HEIGHT } from "../constants";

export type GanttChartBlocksProps = {
itemsContainerWidth: number;
Expand All @@ -20,7 +23,7 @@ export type GanttChartBlocksProps = {
showAllBlocks: boolean;
};

export const GanttChartBlocks: FC<GanttChartBlocksProps> = (props) => {
export const GanttChartBlocksList: FC<GanttChartBlocksProps> = observer((props) => {
const {
itemsContainerWidth,
blocks,
Expand All @@ -31,9 +34,10 @@ export const GanttChartBlocks: FC<GanttChartBlocksProps> = (props) => {
enableBlockMove,
showAllBlocks,
} = props;

const { activeBlock, dispatch } = useChart();
// store hooks
const { peekIssue } = useIssueDetail();
// chart hook
const { activeBlock, dispatch } = useChart();

// update the active block on hover
const updateActiveBlock = (block: IGanttBlock | null) => {
Expand Down Expand Up @@ -77,43 +81,51 @@ export const GanttChartBlocks: FC<GanttChartBlocksProps> = (props) => {

return (
<div
className="relative z-[5] mt-[72px] h-full overflow-hidden overflow-y-auto"
style={{ width: `${itemsContainerWidth}px` }}
className="h-full"
style={{
width: `${itemsContainerWidth}px`,
marginTop: `${HEADER_HEIGHT}px`,
}}
>
{blocks &&
blocks.length > 0 &&
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;
{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;

const isBlockVisibleOnChart = block.start_date && block.target_date;
const isBlockVisibleOnChart = block.start_date && block.target_date;

return (
return (
<div
key={`block-${block.id}`}
className="relative min-w-full w-max"
style={{
height: `${BLOCK_HEIGHT}px`,
}}
>
<div
key={`block-${block.id}`}
className={cn(
"h-11",
{ "rounded bg-custom-background-80": activeBlock?.id === block.id },
{
"rounded-l border border-r-0 border-custom-primary-70 hover:border-custom-primary-70":
peekIssue?.issueId === block.data.id,
}
)}
className={cn("relative h-full", {
"bg-custom-background-80": activeBlock?.id === block.id,
"rounded-l border border-r-0 border-custom-primary-70 hover:border-custom-primary-70":
peekIssue?.issueId === block.data.id,
})}
onMouseEnter={() => updateActiveBlock(block)}
onMouseLeave={() => updateActiveBlock(null)}
>
{!isBlockVisibleOnChart && <ChartAddBlock block={block} blockUpdateHandler={blockUpdateHandler} />}
<ChartDraggable
block={block}
blockToRender={blockToRender}
handleBlock={(...args) => handleChartBlockPosition(block, ...args)}
enableBlockLeftResize={enableBlockLeftResize}
enableBlockRightResize={enableBlockRightResize}
enableBlockMove={enableBlockMove}
/>
{isBlockVisibleOnChart ? (
<ChartDraggable
block={block}
blockToRender={blockToRender}
handleBlock={(...args) => handleChartBlockPosition(block, ...args)}
enableBlockLeftResize={enableBlockLeftResize}
enableBlockRightResize={enableBlockRightResize}
enableBlockMove={enableBlockMove}
/>
) : (
<ChartAddBlock block={block} blockUpdateHandler={blockUpdateHandler} />
)}
</div>
);
})}
</div>
);
})}
</div>
);
};
});
2 changes: 1 addition & 1 deletion web/components/gantt-chart/blocks/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export * from "./blocks-display";
export * from "./blocks-list";
59 changes: 59 additions & 0 deletions web/components/gantt-chart/chart/header.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { Expand, Shrink } from "lucide-react";
// hooks
import { useChart } from "../hooks";
// helpers
import { cn } from "helpers/common.helper";
// types
import { IGanttBlock, TGanttViews } from "../types";

type Props = {
blocks: IGanttBlock[] | null;
fullScreenMode: boolean;
handleChartView: (view: TGanttViews) => void;
handleToday: () => void;
loaderTitle: string;
title: string;
toggleFullScreenMode: () => void;
};

export const GanttChartHeader: React.FC<Props> = (props) => {
const { blocks, fullScreenMode, handleChartView, handleToday, loaderTitle, title, toggleFullScreenMode } = props;
// chart hook
const { currentView, allViews } = useChart();

return (
<div className="relative flex w-full flex-shrink-0 flex-wrap items-center gap-2 whitespace-nowrap px-2.5 py-2 z-10">
<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>

<div className="flex flex-wrap items-center gap-2">
{allViews?.map((chartView: any) => (
<div
key={chartView?.key}
className={cn("cursor-pointer rounded-sm p-1 px-2 text-xs", {
"bg-custom-background-80": currentView === chartView?.key,
"hover:bg-custom-background-90": currentView !== chartView?.key,
})}
onClick={() => handleChartView(chartView?.key)}
>
{chartView?.title}
</div>
))}
</div>

<button type="button" className="rounded-sm p-1 px-2 text-xs hover:bg-custom-background-80" onClick={handleToday}>
Today
</button>

<button
type="button"
className="flex items-center justify-center rounded-sm border border-custom-border-200 p-1 transition-all hover:bg-custom-background-80"
onClick={toggleFullScreenMode}
>
{fullScreenMode ? <Shrink className="h-4 w-4" /> : <Expand className="h-4 w-4" />}
</button>
</div>
);
};
4 changes: 4 additions & 0 deletions web/components/gantt-chart/chart/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export * from "./views";
export * from "./header";
export * from "./main-content";
export * from "./root";
Loading

0 comments on commit 963d26c

Please sign in to comment.