[WEB-5390] refactor: gantt layout support in base layouts (#8089)

This commit is contained in:
Jayash Tripathy 2025-11-13 14:33:24 +05:30 committed by GitHub
parent a04d3b5c29
commit cbfdcd5638
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
20 changed files with 442 additions and 41 deletions

View file

@ -0,0 +1,26 @@
// types
import type { TTimelineTypeCore } from "@plane/types";
import { GANTT_TIMELINE_TYPE } from "@plane/types";
// Plane-web
import type { IBaseTimelineStore } from "@/plane-web/store/timeline/base-timeline.store";
import type { ITimelineStore } from "../store/timeline";
export const getTimelineStore = (
timelineStore: ITimelineStore,
timelineType: TTimelineTypeCore
): IBaseTimelineStore => {
if (timelineType === GANTT_TIMELINE_TYPE.ISSUE) {
return timelineStore.issuesTimeLineStore as IBaseTimelineStore;
}
if (timelineType === GANTT_TIMELINE_TYPE.MODULE) {
return timelineStore.modulesTimeLineStore as IBaseTimelineStore;
}
if (timelineType === GANTT_TIMELINE_TYPE.PROJECT) {
return timelineStore.projectTimeLineStore as IBaseTimelineStore;
}
if (timelineType === GANTT_TIMELINE_TYPE.GROUPED) {
return timelineStore.groupedTimeLineStore as IBaseTimelineStore;
}
throw new Error(`Unknown timeline type: ${timelineType}`);
};

View file

@ -1,4 +1,4 @@
import { BoardLayoutIcon, ListLayoutIcon } from "@plane/propel/icons";
import { BoardLayoutIcon, ListLayoutIcon, TimelineLayoutIcon } from "@plane/propel/icons";
import type { IBaseLayoutConfig } from "@plane/types";
export const BASE_LAYOUTS: IBaseLayoutConfig[] = [
@ -12,4 +12,9 @@ export const BASE_LAYOUTS: IBaseLayoutConfig[] = [
icon: BoardLayoutIcon,
label: "Board Layout",
},
{
key: "gantt",
icon: TimelineLayoutIcon,
label: "Gantt Layout",
},
];

View file

@ -0,0 +1,2 @@
export { BaseGanttLayout } from "./layout";
export { BaseGanttSidebar } from "./sidebar";

View file

@ -0,0 +1,140 @@
"use client";
import { useCallback, useMemo } from "react";
import { observer } from "mobx-react";
import { GANTT_TIMELINE_TYPE } from "@plane/types";
import type {
IBaseLayoutsGanttItem,
IBaseLayoutsGanttProps,
TGanttBlockUpdateData,
IBlockUpdateDependencyData,
} from "@plane/types";
import { cn } from "@plane/utils";
import { TimeLineTypeContext } from "@/components/gantt-chart/contexts";
import { GanttChartRoot } from "@/components/gantt-chart/root";
import { BaseGanttSidebar } from "./sidebar";
export const BaseGanttLayout = observer(<T extends IBaseLayoutsGanttItem>(props: IBaseLayoutsGanttProps<T>) => {
const {
items,
groupedItemIds,
groups,
renderBlock,
renderSidebar,
onBlockUpdate,
onDateUpdate,
enableBlockLeftResize = false,
enableBlockRightResize = false,
enableBlockMove = false,
enableReorder = false,
enableAddBlock = false,
enableSelection = false,
enableDependency = false,
showAllBlocks = false,
showToday = true,
border = false,
title = "Items",
loaderTitle = "items",
quickAdd,
loadMoreItems,
isLoading: _isLoading,
className,
timelineType: timelineTypeKey = GANTT_TIMELINE_TYPE.ISSUE,
} = props;
// Flatten all grouped item IDs into a single array for gantt
// Gantt doesn't typically show groups, it shows all items on a timeline
const blockIds = useMemo(() => {
const allIds: string[] = [];
groups.forEach((group) => {
const itemIds = groupedItemIds[group.id] || [];
allIds.push(...itemIds);
});
return allIds;
}, [groups, groupedItemIds]);
// Block update handler - transforms base layout item updates to gantt block updates
const handleBlockUpdate = useCallback(
(block: T, payload: TGanttBlockUpdateData) => {
if (onBlockUpdate) {
onBlockUpdate(block, payload);
}
},
[onBlockUpdate]
);
// Block renderer - wraps the user's render function
const blockToRender = useCallback((item: T) => renderBlock(item), [renderBlock]);
// Sidebar renderer - uses custom or default
const sidebarToRender = useCallback(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(sidebarProps: any) => {
if (renderSidebar) {
// If custom sidebar renderer provided, use it
return (
<BaseGanttSidebar {...sidebarProps} items={items} renderItem={renderSidebar} loadMoreItems={loadMoreItems} />
);
}
// Otherwise use default sidebar
return (
<BaseGanttSidebar {...sidebarProps} items={items} renderItem={renderBlock} loadMoreItems={loadMoreItems} />
);
},
[renderSidebar, renderBlock, items, loadMoreItems]
);
const timelineType = GANTT_TIMELINE_TYPE[timelineTypeKey];
// Date update handler - transforms IBlockUpdateDependencyData to TGanttDateUpdate
const handleDateUpdate = useCallback(
async (updates: IBlockUpdateDependencyData[]) => {
if (onDateUpdate) {
// Transform IBlockUpdateDependencyData[] to TGanttDateUpdate[]
const transformedUpdates = updates.map((update) => ({
id: update.id,
start_date: update.start_date,
target_date: update.target_date,
}));
await onDateUpdate(transformedUpdates);
}
},
[onDateUpdate]
);
// Load more handler - wraps loadMoreItems to match expected signature
const handleLoadMore = useCallback(() => {
if (loadMoreItems) {
loadMoreItems(""); // Pass empty string as default group ID
}
}, [loadMoreItems]);
return (
<TimeLineTypeContext.Provider value={timelineType}>
<div className={cn("h-full w-full", className)}>
<GanttChartRoot
border={border}
title={title}
loaderTitle={loaderTitle}
blockIds={blockIds}
blockUpdateHandler={handleBlockUpdate}
blockToRender={blockToRender}
sidebarToRender={sidebarToRender}
enableBlockLeftResize={enableBlockLeftResize}
enableBlockRightResize={enableBlockRightResize}
enableBlockMove={enableBlockMove}
enableReorder={enableReorder}
enableAddBlock={enableAddBlock}
enableSelection={enableSelection}
enableDependency={enableDependency}
showAllBlocks={showAllBlocks}
showToday={showToday}
quickAdd={quickAdd}
updateBlockDates={onDateUpdate ? handleDateUpdate : undefined}
loadMoreBlocks={loadMoreItems ? handleLoadMore : undefined}
canLoadMoreBlocks={!!loadMoreItems} // Enable pagination if loadMoreItems is provided
/>
</div>
</TimeLineTypeContext.Provider>
);
});

View file

@ -0,0 +1,148 @@
"use client";
import type { RefObject } from "react";
import { useState } from "react";
import { observer } from "mobx-react";
import type { IBaseLayoutsBaseItem, IBlockUpdateData } from "@plane/types";
import { Loader, Row } from "@plane/ui";
import { cn } from "@plane/utils";
import RenderIfVisible from "@/components/core/render-if-visible-HOC";
import { BLOCK_HEIGHT } from "@/components/gantt-chart/constants";
import { GanttDnDHOC } from "@/components/gantt-chart/sidebar/gantt-dnd-HOC";
import { handleOrderChange } from "@/components/gantt-chart/sidebar/utils";
import { GanttLayoutListItemLoader } from "@/components/ui/loader/layouts/gantt-layout-loader";
import { useIntersectionObserver } from "@/hooks/use-intersection-observer";
import { useTimeLineChartStore } from "@/hooks/use-timeline-chart";
type Props<T extends IBaseLayoutsBaseItem> = {
blockUpdateHandler: (block: T, payload: IBlockUpdateData) => void;
canLoadMoreBlocks?: boolean;
loadMoreItems?: (groupId: string) => void;
ganttContainerRef: RefObject<HTMLDivElement>;
blockIds: string[];
enableReorder: boolean;
showAllBlocks?: boolean;
items: Record<string, T>;
renderItem: (item: T) => React.ReactNode;
};
export const BaseGanttSidebar = observer(<T extends IBaseLayoutsBaseItem>(props: Props<T>) => {
const {
blockUpdateHandler,
blockIds,
enableReorder,
loadMoreItems,
canLoadMoreBlocks,
ganttContainerRef,
showAllBlocks = false,
items,
renderItem,
} = props;
const { getBlockById, updateActiveBlockId, isBlockActive, getNumberOfDaysFromPosition } = useTimeLineChartStore();
const [intersectionElement, setIntersectionElement] = useState<HTMLDivElement | null>(null);
const isPaginating = false; // TODO: Add proper pagination state
useIntersectionObserver(
ganttContainerRef,
isPaginating ? null : intersectionElement,
loadMoreItems ? () => loadMoreItems("") : undefined,
"100% 0% 100% 0%"
);
const handleOnDrop = (
draggingBlockId: string | undefined,
droppedBlockId: string | undefined,
dropAtEndOfList: boolean
) => {
handleOrderChange(draggingBlockId, droppedBlockId, dropAtEndOfList, blockIds, getBlockById, blockUpdateHandler);
};
return (
<div>
{blockIds ? (
<>
{blockIds.map((blockId, index) => {
const block = getBlockById(blockId);
const item = items[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 (!block || (!showAllBlocks && !isBlockVisibleOnSidebar)) return null;
if (!item) return null;
return (
<RenderIfVisible
key={blockId}
root={ganttContainerRef}
horizontalOffset={100}
verticalOffset={200}
shouldRecordHeights={false}
placeholderChildren={<GanttLayoutListItemLoader />}
>
<GanttDnDHOC
id={blockId}
isLastChild={index === blockIds.length - 1}
isDragEnabled={enableReorder}
onDrop={handleOnDrop}
>
{(isDragging: boolean) => {
const block = getBlockById(blockId);
const isBlockComplete = !!block?.start_date && !!block?.target_date;
const duration = isBlockComplete ? getNumberOfDaysFromPosition(block?.position?.width) : undefined;
const isBlockHoveredOn = isBlockActive(blockId);
return (
<div
className={cn("group/list-block", {
"rounded bg-custom-background-80": isDragging,
})}
onMouseEnter={() => updateActiveBlockId(blockId)}
onMouseLeave={() => updateActiveBlockId(null)}
>
<Row
className={cn("group w-full flex items-center gap-2 pr-4", {
"bg-custom-background-90": isBlockHoveredOn,
})}
style={{
height: `${BLOCK_HEIGHT}px`,
}}
>
<div className="flex h-full flex-grow items-center justify-between gap-2 truncate">
<div className="flex-grow truncate">{renderItem(item)}</div>
{duration && (
<div className="flex-shrink-0 text-sm text-custom-text-200">
<span>
{duration} day{duration > 1 ? "s" : ""}
</span>
</div>
)}
</div>
</Row>
</div>
);
}}
</GanttDnDHOC>
</RenderIfVisible>
);
})}
{canLoadMoreBlocks && (
<div ref={setIntersectionElement} className="p-2">
<div className="flex h-10 md:h-8 w-full items-center justify-between gap-1.5 rounded md:px-1 px-4 py-1.5 bg-custom-background-80 animate-pulse" />
</div>
)}
</>
) : (
<Loader className="space-y-3 pr-2">
<Loader.Item height="34px" />
<Loader.Item height="34px" />
<Loader.Item height="34px" />
<Loader.Item height="34px" />
</Loader>
)}
</div>
);
});

View file

@ -187,6 +187,7 @@ export const GanttChartMainContent: React.FC<Props> = observer((props) => {
title={title}
quickAdd={quickAdd}
selectionHelpers={helpers}
showAllBlocks={showAllBlocks}
isEpic={isEpic}
/>
<div className="relative min-h-full h-max flex-shrink-0 flex-grow">

View file

@ -1,16 +1,9 @@
import { createContext, useContext } from "react";
import type { TTimelineType } from "@plane/types";
export enum ETimeLineTypeType {
ISSUE = "ISSUE",
MODULE = "MODULE",
PROJECT = "PROJECT",
GROUPED = "GROUPED",
}
export const TimeLineTypeContext = createContext<ETimeLineTypeType | undefined>(undefined);
export const TimeLineTypeContext = createContext<TTimelineType | undefined>(undefined);
export const useTimeLineType = () => {
const timelineType = useContext(TimeLineTypeContext);
return timelineType;
};

View file

@ -4,6 +4,7 @@ import type { RefObject } from "react";
import { useState } from "react";
import { observer } from "mobx-react";
// ui
import { GANTT_TIMELINE_TYPE } from "@plane/types";
import type { IBlockUpdateData } from "@plane/types";
import { Loader } from "@plane/ui";
// components
@ -15,7 +16,6 @@ import { useIssuesStore } from "@/hooks/use-issue-layout-store";
import type { TSelectionHelper } from "@/hooks/use-multiple-select";
// local imports
import { useTimeLineChart } from "../../../../hooks/use-timeline-chart";
import { ETimeLineTypeType } from "../../contexts";
import { GanttDnDHOC } from "../gantt-dnd-HOC";
import { handleOrderChange } from "../utils";
import { IssuesSidebarBlock } from "./block";
@ -47,7 +47,7 @@ export const IssueGanttSidebar: React.FC<Props> = observer((props) => {
isEpic = false,
} = props;
const { getBlockById } = useTimeLineChart(ETimeLineTypeType.ISSUE);
const { getBlockById } = useTimeLineChart(GANTT_TIMELINE_TYPE.ISSUE);
const {
issues: { getIssueLoader },

View file

@ -2,13 +2,12 @@
import { observer } from "mobx-react";
// ui
import { GANTT_TIMELINE_TYPE } from "@plane/types";
import type { IBlockUpdateData } from "@plane/types";
import { Loader } from "@plane/ui";
// components
// hooks
import { useTimeLineChart } from "@/hooks/use-timeline-chart";
//
import { ETimeLineTypeType } from "../../contexts";
import { GanttDnDHOC } from "../gantt-dnd-HOC";
import { handleOrderChange } from "../utils";
import { ModulesSidebarBlock } from "./block";
@ -24,7 +23,7 @@ type Props = {
export const ModuleGanttSidebar: React.FC<Props> = observer((props) => {
const { blockUpdateHandler, blockIds, enableReorder } = props;
const { getBlockById } = useTimeLineChart(ETimeLineTypeType.MODULE);
const { getBlockById } = useTimeLineChart(GANTT_TIMELINE_TYPE.MODULE);
const handleOnDrop = (
draggingBlockId: string | undefined,

View file

@ -24,6 +24,7 @@ type Props = {
title: string;
quickAdd?: React.ReactNode | undefined;
selectionHelpers: TSelectionHelper;
showAllBlocks?: boolean;
isEpic?: boolean;
};
@ -41,6 +42,7 @@ export const GanttChartSidebar: React.FC<Props> = observer((props) => {
title,
quickAdd,
selectionHelpers,
showAllBlocks = false,
isEpic = false,
} = props;
@ -94,6 +96,7 @@ export const GanttChartSidebar: React.FC<Props> = observer((props) => {
ganttContainerRef,
loadMoreBlocks,
selectionHelpers,
showAllBlocks,
isEpic,
})}
</Row>

View file

@ -6,10 +6,10 @@ import { ALL_ISSUES, EUserPermissions, EUserPermissionsLevel } from "@plane/cons
import { useTranslation } from "@plane/i18n";
import { TOAST_TYPE, setToast } from "@plane/propel/toast";
import type { EIssuesStoreType, IBlockUpdateData, TIssue } from "@plane/types";
import { EIssueLayoutTypes } from "@plane/types";
import { EIssueLayoutTypes, GANTT_TIMELINE_TYPE } from "@plane/types";
import { renderFormattedPayloadDate } from "@plane/utils";
// components
import { ETimeLineTypeType, TimeLineTypeContext } from "@/components/gantt-chart/contexts";
import { TimeLineTypeContext } from "@/components/gantt-chart/contexts";
import { GanttChartRoot } from "@/components/gantt-chart/root";
import { IssueGanttSidebar } from "@/components/gantt-chart/sidebar/issues/sidebar";
// hooks
@ -47,7 +47,7 @@ export const BaseGanttRoot: React.FC<IBaseGanttRoot> = observer((props: IBaseGan
const storeType = useIssueStoreType() as GanttStoreType;
const { issues, issuesFilter } = useIssues(storeType);
const { fetchIssues, fetchNextIssues, updateIssue, quickAddIssue } = useIssuesActions(storeType);
const { initGantt } = useTimeLineChart(ETimeLineTypeType.ISSUE);
const { initGantt } = useTimeLineChart(GANTT_TIMELINE_TYPE.ISSUE);
// store hooks
const { allowPermissions } = useUserPermissions();
@ -120,7 +120,7 @@ export const BaseGanttRoot: React.FC<IBaseGanttRoot> = observer((props: IBaseGan
return (
<IssueLayoutHOC layout={EIssueLayoutTypes.GANTT}>
<TimeLineTypeContext.Provider value={ETimeLineTypeType.ISSUE}>
<TimeLineTypeContext.Provider value={GANTT_TIMELINE_TYPE.ISSUE}>
<div className="h-full w-full">
<GanttChartRoot
border={false}

View file

@ -1,10 +1,11 @@
import { observer } from "mobx-react";
import { useParams } from "next/navigation";
// PLane
import { GANTT_TIMELINE_TYPE } from "@plane/types";
import type { IBlockUpdateData, IBlockUpdateDependencyData, IModule } from "@plane/types";
// components
import { GanttChartRoot, ModuleGanttSidebar } from "@/components/gantt-chart";
import { ETimeLineTypeType, TimeLineTypeContext } from "@/components/gantt-chart/contexts";
import { TimeLineTypeContext } from "@/components/gantt-chart/contexts";
import { ModuleGanttBlock } from "@/components/modules";
// hooks
import { useModule } from "@/hooks/store/use-module";
@ -49,7 +50,7 @@ export const ModulesListGanttChartView: React.FC = observer(() => {
if (!filteredModuleIds) return null;
return (
<TimeLineTypeContext.Provider value={ETimeLineTypeType.MODULE}>
<TimeLineTypeContext.Provider value={GANTT_TIMELINE_TYPE.MODULE}>
<GanttChartRoot
title="Modules"
loaderTitle="Modules"

View file

@ -1,31 +1,26 @@
import { useContext } from "react";
// types
import type { TTimelineType } from "@plane/types";
// lib
import { StoreContext } from "@/lib/store-context";
// Plane-web
import { getTimelineStore } from "@/plane-web/hooks/use-timeline-chart";
import type { IBaseTimelineStore } from "@/plane-web/store/timeline/base-timeline.store";
//
import { ETimeLineTypeType, useTimeLineType } from "../components/gantt-chart/contexts";
import { useTimeLineType } from "../components/gantt-chart/contexts";
export const useTimeLineChart = (timeLineType: ETimeLineTypeType): IBaseTimelineStore => {
export const useTimeLineChart = (timelineType: TTimelineType): IBaseTimelineStore => {
const context = useContext(StoreContext);
if (context === undefined) throw new Error("useTimeLineChart must be used within StoreProvider");
if (!context) throw new Error("useTimeLineChart must be used within StoreProvider");
switch (timeLineType) {
case ETimeLineTypeType.ISSUE:
return context.timelineStore.issuesTimeLineStore;
case ETimeLineTypeType.MODULE:
return context.timelineStore.modulesTimeLineStore as IBaseTimelineStore;
case ETimeLineTypeType.PROJECT:
return context.timelineStore.projectTimeLineStore as IBaseTimelineStore;
case ETimeLineTypeType.GROUPED:
return context.timelineStore.groupedTimeLineStore as IBaseTimelineStore;
}
return getTimelineStore(context.timelineStore, timelineType);
};
export const useTimeLineChartStore = () => {
export const useTimeLineChartStore = (): IBaseTimelineStore => {
const context = useContext(StoreContext);
const timelineType = useTimeLineType();
if (!context) throw new Error("useTimeLineChartStore must be used within StoreProvider");
if (!timelineType) throw new Error("useTimeLineChartStore must be used within TimeLineTypeContext");
return useTimeLineChart(timelineType);
return getTimelineStore(context.timelineStore, timelineType);
};

View file

@ -8,11 +8,10 @@ import useSWR from "swr";
import { EUserPermissions, EUserPermissionsLevel, PROJECT_TRACKER_ELEMENTS } from "@plane/constants";
import { useTranslation } from "@plane/i18n";
import { EmptyStateDetailed } from "@plane/propel/empty-state";
import { EProjectNetwork } from "@plane/types";
import { EProjectNetwork, GANTT_TIMELINE_TYPE } from "@plane/types";
// components
import { JoinProject } from "@/components/auth-screens/project/join-project";
import { LogoSpinner } from "@/components/common/logo-spinner";
import { ETimeLineTypeType } from "@/components/gantt-chart/contexts";
import {
PROJECT_DETAILS,
PROJECT_ME_INFORMATION,
@ -57,7 +56,7 @@ export const ProjectAuthWrapper: FC<IProjectAuthWrapper> = observer((props) => {
const { loader, getProjectById, fetchProjectDetails } = useProject();
const { fetchAllCycles } = useCycle();
const { fetchModulesSlim, fetchModules } = useModule();
const { initGantt } = useTimeLineChart(ETimeLineTypeType.MODULE);
const { initGantt } = useTimeLineChart(GANTT_TIMELINE_TYPE.MODULE);
const { fetchViews } = useProjectView();
const {
project: { fetchProjectMembers },

View file

@ -0,0 +1,3 @@
// For now, just re-export from CE
// In the future, you can extend the timeline store mapping here for EE-specific timeline types
export * from "ce/hooks/use-timeline-chart";

View file

@ -52,7 +52,7 @@ export interface IRenderProps<T extends IBaseLayoutsBaseItem> extends IItemRende
// Layout Configuration
export type TBaseLayoutType = "list" | "kanban";
export type TBaseLayoutType = "list" | "kanban" | "gantt";
export interface IBaseLayoutConfig {
key: TBaseLayoutType;

View file

@ -0,0 +1,6 @@
export const CORE_GANTT_TIMELINE_TYPE = {
ISSUE: "ISSUE",
MODULE: "MODULE",
PROJECT: "PROJECT",
GROUPED: "GROUPED",
} as const;

View file

@ -0,0 +1 @@
export const EXTENDED_GANTT_TIMELINE_TYPE = {} as const;

View file

@ -0,0 +1,78 @@
import type { ReactNode } from "react";
import type { IBaseLayoutsBaseItem, IBaseLayoutsBaseProps } from "../base";
import { CORE_GANTT_TIMELINE_TYPE } from "./core";
import { EXTENDED_GANTT_TIMELINE_TYPE } from "./extended";
// Gantt-specific item with date fields
export interface IBaseLayoutsGanttItem extends IBaseLayoutsBaseItem {
start_date?: string | null;
target_date?: string | null;
}
// Block update data (for drag/resize operations)
export type TGanttBlockUpdateData = {
start_date?: string;
target_date?: string;
sort_order?: {
destinationIndex: number;
newSortOrder: number;
};
};
// Date update handler for bulk date changes (e.g., dependency updates)
export type TGanttDateUpdate = {
id: string;
start_date?: string;
target_date?: string;
};
// Render props specific to Gantt
export interface IGanttRenderProps<T extends IBaseLayoutsGanttItem> {
renderBlock: (item: T) => ReactNode;
renderSidebar?: (item: T) => ReactNode;
}
// Gantt-specific capabilities
export interface IGanttCapabilities {
enableBlockLeftResize?: boolean | ((itemId: string) => boolean);
enableBlockRightResize?: boolean | ((itemId: string) => boolean);
enableBlockMove?: boolean | ((itemId: string) => boolean);
enableReorder?: boolean | ((itemId: string) => boolean);
enableAddBlock?: boolean | ((itemId: string) => boolean);
enableSelection?: boolean | ((itemId: string) => boolean);
enableDependency?: boolean | ((itemId: string) => boolean);
}
// Gantt display options
export type TGanttDisplayOptions = {
showAllBlocks?: boolean; // Show blocks even without dates
showToday?: boolean; // Highlight today on timeline
border?: boolean;
title?: string;
loaderTitle?: string;
quickAdd?: ReactNode;
timelineType?: TTimelineType; // Type of timeline to use for store
};
// Main Gantt Layout Props
export interface IBaseLayoutsGanttProps<T extends IBaseLayoutsGanttItem>
extends Omit<IBaseLayoutsBaseProps<T>, "renderItem" | "enableDragDrop" | "onDrop" | "canDrag">,
IGanttRenderProps<T>,
IGanttCapabilities,
TGanttDisplayOptions {
// Handler for block updates (position, dates, order)
onBlockUpdate?: (item: T, payload: TGanttBlockUpdateData) => void | Promise<void>;
// Handler for bulk date updates (dependencies, etc.)
onDateUpdate?: (updates: TGanttDateUpdate[]) => void | Promise<void>;
}
export const GANTT_TIMELINE_TYPE = {
...CORE_GANTT_TIMELINE_TYPE,
...EXTENDED_GANTT_TIMELINE_TYPE,
} as const;
export type TTimelineTypeCore = (typeof CORE_GANTT_TIMELINE_TYPE)[keyof typeof CORE_GANTT_TIMELINE_TYPE];
export type TTimelineType =
| TTimelineTypeCore
| (typeof EXTENDED_GANTT_TIMELINE_TYPE)[keyof typeof EXTENDED_GANTT_TIMELINE_TYPE];

View file

@ -1,3 +1,4 @@
export * from "./base";
export * from "./list";
export * from "./kanban";
export * from "./gantt";