mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
Merge branch 'feat/pagination' of github.com:makeplane/plane into feat/pagination
This commit is contained in:
commit
33a64fc67b
20
packages/types/src/issues/base.d.ts
vendored
20
packages/types/src/issues/base.d.ts
vendored
@ -12,16 +12,26 @@ export * from "./activity/base";
|
|||||||
|
|
||||||
export type TLoader = "init-loader" | "mutation" | "pagination" | undefined;
|
export type TLoader = "init-loader" | "mutation" | "pagination" | undefined;
|
||||||
|
|
||||||
export type TIssueGroup = { issueIds: string[]; issueCount: number };
|
|
||||||
export type TGroupedIssues = {
|
export type TGroupedIssues = {
|
||||||
[group_id: string]: TIssueGroup;
|
[group_id: string]: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TSubGroupedIssues = {
|
export type TSubGroupedIssues = {
|
||||||
[sub_grouped_id: string]: TGroupedIssues;
|
[sub_grouped_id: string]: TGroupedIssues;
|
||||||
};
|
};
|
||||||
export type TUnGroupedIssues = {
|
|
||||||
"All Issues": TIssueGroup;
|
export type TIssues = TGroupedIssues | TSubGroupedIssues;
|
||||||
|
|
||||||
|
export type TPaginationData = {
|
||||||
|
nextCursor: string;
|
||||||
|
prevCursor: string;
|
||||||
|
nextPageResults: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TIssues = TGroupedIssues | TUnGroupedIssues;
|
export type TIssuePaginationData = {
|
||||||
|
[group_id: string]: TPaginationData;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TGroupedIssueCount = {
|
||||||
|
[group_id: string]: number;
|
||||||
|
};
|
||||||
|
10
packages/types/src/issues/issue.d.ts
vendored
10
packages/types/src/issues/issue.d.ts
vendored
@ -61,7 +61,14 @@ type TIssueResponseResults =
|
|||||||
| TBaseIssue[]
|
| TBaseIssue[]
|
||||||
| {
|
| {
|
||||||
[key: string]: {
|
[key: string]: {
|
||||||
results: TBaseIssue[];
|
results:
|
||||||
|
| TBaseIssue[]
|
||||||
|
| {
|
||||||
|
[key: string]: {
|
||||||
|
results: TBaseIssue[];
|
||||||
|
total_results: number;
|
||||||
|
};
|
||||||
|
};
|
||||||
total_results: number;
|
total_results: number;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@ -72,6 +79,7 @@ export type TIssuesResponse = {
|
|||||||
prev_cursor: string;
|
prev_cursor: string;
|
||||||
next_page_results: boolean;
|
next_page_results: boolean;
|
||||||
prev_page_results: boolean;
|
prev_page_results: boolean;
|
||||||
|
total_count: number;
|
||||||
count: number;
|
count: number;
|
||||||
total_pages: number;
|
total_pages: number;
|
||||||
extra_stats: null;
|
extra_stats: null;
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
import { FC } from "react";
|
import { FC, useCallback } from "react";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
// hooks
|
// hooks
|
||||||
import { CycleGanttBlock } from "components/cycles";
|
import { CycleGanttBlock } from "components/cycles";
|
||||||
import { GanttChartRoot, IBlockUpdateData, CycleGanttSidebar } from "components/gantt-chart";
|
import { GanttChartRoot, IBlockUpdateData, CycleGanttSidebar, ChartDataType } from "components/gantt-chart";
|
||||||
import { useCycle } from "hooks/store";
|
import { useCycle } from "hooks/store";
|
||||||
// components
|
// components
|
||||||
// types
|
// types
|
||||||
import { ICycle } from "@plane/types";
|
import { ICycle } from "@plane/types";
|
||||||
|
import { getMonthChartItemPositionWidthInMonth } from "components/gantt-chart/views";
|
||||||
// constants
|
// constants
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@ -23,6 +24,28 @@ export const CyclesListGanttChartView: FC<Props> = observer((props) => {
|
|||||||
// store hooks
|
// store hooks
|
||||||
const { getCycleById, updateCycleDetails } = useCycle();
|
const { getCycleById, updateCycleDetails } = useCycle();
|
||||||
|
|
||||||
|
const getBlockById = useCallback(
|
||||||
|
(id: string, currentViewData?: ChartDataType | undefined) => {
|
||||||
|
const cycle = getCycleById(id);
|
||||||
|
const block = {
|
||||||
|
data: cycle,
|
||||||
|
id: cycle?.id ?? "",
|
||||||
|
sort_order: cycle?.sort_order ?? 0,
|
||||||
|
start_date: cycle?.start_date ? new Date(cycle?.start_date) : undefined,
|
||||||
|
target_date: cycle?.end_date ? new Date(cycle?.end_date) : undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (currentViewData) {
|
||||||
|
return {
|
||||||
|
...block,
|
||||||
|
position: getMonthChartItemPositionWidthInMonth(currentViewData, block),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return block;
|
||||||
|
},
|
||||||
|
[getCycleById]
|
||||||
|
);
|
||||||
|
|
||||||
const handleCycleUpdate = async (cycle: ICycle, data: IBlockUpdateData) => {
|
const handleCycleUpdate = async (cycle: ICycle, data: IBlockUpdateData) => {
|
||||||
if (!workspaceSlug || !cycle) return;
|
if (!workspaceSlug || !cycle) return;
|
||||||
|
|
||||||
@ -32,28 +55,13 @@ export const CyclesListGanttChartView: FC<Props> = observer((props) => {
|
|||||||
await updateCycleDetails(workspaceSlug.toString(), cycle.project_id, cycle.id, payload);
|
await updateCycleDetails(workspaceSlug.toString(), cycle.project_id, cycle.id, payload);
|
||||||
};
|
};
|
||||||
|
|
||||||
const blockFormat = (blocks: (ICycle | null)[]) => {
|
|
||||||
if (!blocks) return [];
|
|
||||||
|
|
||||||
const filteredBlocks = blocks.filter((b) => b !== null && b.start_date && b.end_date);
|
|
||||||
|
|
||||||
const structuredBlocks = filteredBlocks.map((block) => ({
|
|
||||||
data: block,
|
|
||||||
id: block?.id ?? "",
|
|
||||||
sort_order: block?.sort_order ?? 0,
|
|
||||||
start_date: new Date(block?.start_date ?? ""),
|
|
||||||
target_date: new Date(block?.end_date ?? ""),
|
|
||||||
}));
|
|
||||||
|
|
||||||
return structuredBlocks;
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-full w-full overflow-y-auto">
|
<div className="h-full w-full overflow-y-auto">
|
||||||
<GanttChartRoot
|
<GanttChartRoot
|
||||||
title="Cycles"
|
title="Cycles"
|
||||||
loaderTitle="Cycles"
|
loaderTitle="Cycles"
|
||||||
blocks={cycleIds ? blockFormat(cycleIds.map((c) => getCycleById(c))) : null}
|
blockIds={cycleIds}
|
||||||
|
getBlockById={getBlockById}
|
||||||
blockUpdateHandler={(block, payload) => handleCycleUpdate(block, payload)}
|
blockUpdateHandler={(block, payload) => handleCycleUpdate(block, payload)}
|
||||||
sidebarToRender={(props) => <CycleGanttSidebar {...props} />}
|
sidebarToRender={(props) => <CycleGanttSidebar {...props} />}
|
||||||
blockToRender={(data: ICycle) => <CycleGanttBlock cycleId={data.id} />}
|
blockToRender={(data: ICycle) => <CycleGanttBlock cycleId={data.id} />}
|
||||||
|
@ -10,10 +10,12 @@ import { useIssueDetail } from "hooks/store";
|
|||||||
import { BLOCK_HEIGHT } from "../constants";
|
import { BLOCK_HEIGHT } from "../constants";
|
||||||
import { ChartAddBlock, ChartDraggable } from "../helpers";
|
import { ChartAddBlock, ChartDraggable } from "../helpers";
|
||||||
import { useGanttChart } from "../hooks";
|
import { useGanttChart } from "../hooks";
|
||||||
import { IBlockUpdateData, IGanttBlock } from "../types";
|
import { ChartDataType, IBlockUpdateData, IGanttBlock } from "../types";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
block: IGanttBlock;
|
blockId: string;
|
||||||
|
getBlockById: (id: string, currentViewData?: ChartDataType | undefined) => IGanttBlock;
|
||||||
|
showAllBlocks: boolean;
|
||||||
blockToRender: (data: any) => React.ReactNode;
|
blockToRender: (data: any) => React.ReactNode;
|
||||||
blockUpdateHandler: (block: any, payload: IBlockUpdateData) => void;
|
blockUpdateHandler: (block: any, payload: IBlockUpdateData) => void;
|
||||||
enableBlockLeftResize: boolean;
|
enableBlockLeftResize: boolean;
|
||||||
@ -25,7 +27,9 @@ type Props = {
|
|||||||
|
|
||||||
export const GanttChartBlock: React.FC<Props> = observer((props) => {
|
export const GanttChartBlock: React.FC<Props> = observer((props) => {
|
||||||
const {
|
const {
|
||||||
block,
|
blockId,
|
||||||
|
getBlockById,
|
||||||
|
showAllBlocks,
|
||||||
blockToRender,
|
blockToRender,
|
||||||
blockUpdateHandler,
|
blockUpdateHandler,
|
||||||
enableBlockLeftResize,
|
enableBlockLeftResize,
|
||||||
@ -35,9 +39,14 @@ export const GanttChartBlock: React.FC<Props> = observer((props) => {
|
|||||||
ganttContainerRef,
|
ganttContainerRef,
|
||||||
} = props;
|
} = props;
|
||||||
// store hooks
|
// store hooks
|
||||||
const { updateActiveBlockId, isBlockActive } = useGanttChart();
|
const { currentViewData, updateActiveBlockId, isBlockActive } = useGanttChart();
|
||||||
const { peekIssue } = useIssueDetail();
|
const { peekIssue } = useIssueDetail();
|
||||||
|
|
||||||
|
const block = getBlockById(blockId, currentViewData);
|
||||||
|
|
||||||
|
// hide the block if it doesn't have start and target dates and showAllBlocks is false
|
||||||
|
if (!block || (!showAllBlocks && !(block.start_date && block.target_date))) return null;
|
||||||
|
|
||||||
const isBlockVisibleOnChart = block.start_date && block.target_date;
|
const isBlockVisibleOnChart = block.start_date && block.target_date;
|
||||||
|
|
||||||
const handleChartBlockPosition = (
|
const handleChartBlockPosition = (
|
||||||
@ -72,7 +81,6 @@ export const GanttChartBlock: React.FC<Props> = observer((props) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={`block-${block.id}`}
|
|
||||||
className="relative min-w-full w-max"
|
className="relative min-w-full w-max"
|
||||||
style={{
|
style={{
|
||||||
height: `${BLOCK_HEIGHT}px`,
|
height: `${BLOCK_HEIGHT}px`,
|
||||||
@ -80,11 +88,11 @@ export const GanttChartBlock: React.FC<Props> = observer((props) => {
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={cn("relative h-full", {
|
className={cn("relative h-full", {
|
||||||
"bg-custom-background-80": isBlockActive(block.id),
|
"bg-custom-background-80": isBlockActive(blockId),
|
||||||
"rounded-l border border-r-0 border-custom-primary-70 hover:border-custom-primary-70":
|
"rounded-l border border-r-0 border-custom-primary-70 hover:border-custom-primary-70":
|
||||||
peekIssue?.issueId === block.data.id,
|
peekIssue?.issueId === block.data.id,
|
||||||
})}
|
})}
|
||||||
onMouseEnter={() => updateActiveBlockId(block.id)}
|
onMouseEnter={() => updateActiveBlockId(blockId)}
|
||||||
onMouseLeave={() => updateActiveBlockId(null)}
|
onMouseLeave={() => updateActiveBlockId(null)}
|
||||||
>
|
>
|
||||||
{isBlockVisibleOnChart ? (
|
{isBlockVisibleOnChart ? (
|
||||||
|
@ -1,14 +1,15 @@
|
|||||||
import { FC } from "react";
|
import { FC } from "react";
|
||||||
// components
|
// components
|
||||||
import { HEADER_HEIGHT } from "../constants";
|
import { HEADER_HEIGHT } from "../constants";
|
||||||
import { IBlockUpdateData, IGanttBlock } from "../types";
|
import { ChartDataType, IBlockUpdateData, IGanttBlock } from "../types";
|
||||||
import { GanttChartBlock } from "./block";
|
import { GanttChartBlock } from "./block";
|
||||||
// types
|
// types
|
||||||
// constants
|
// constants
|
||||||
|
|
||||||
export type GanttChartBlocksProps = {
|
export type GanttChartBlocksProps = {
|
||||||
itemsContainerWidth: number;
|
itemsContainerWidth: number;
|
||||||
blocks: IGanttBlock[] | null;
|
blockIds: string[];
|
||||||
|
getBlockById: (id: string, currentViewData?: ChartDataType | undefined) => IGanttBlock;
|
||||||
blockToRender: (data: any) => React.ReactNode;
|
blockToRender: (data: any) => React.ReactNode;
|
||||||
blockUpdateHandler: (block: any, payload: IBlockUpdateData) => void;
|
blockUpdateHandler: (block: any, payload: IBlockUpdateData) => void;
|
||||||
enableBlockLeftResize: boolean;
|
enableBlockLeftResize: boolean;
|
||||||
@ -22,9 +23,10 @@ export type GanttChartBlocksProps = {
|
|||||||
export const GanttChartBlocksList: FC<GanttChartBlocksProps> = (props) => {
|
export const GanttChartBlocksList: FC<GanttChartBlocksProps> = (props) => {
|
||||||
const {
|
const {
|
||||||
itemsContainerWidth,
|
itemsContainerWidth,
|
||||||
blocks,
|
blockIds,
|
||||||
blockToRender,
|
blockToRender,
|
||||||
blockUpdateHandler,
|
blockUpdateHandler,
|
||||||
|
getBlockById,
|
||||||
enableBlockLeftResize,
|
enableBlockLeftResize,
|
||||||
enableBlockRightResize,
|
enableBlockRightResize,
|
||||||
enableBlockMove,
|
enableBlockMove,
|
||||||
@ -41,14 +43,13 @@ export const GanttChartBlocksList: FC<GanttChartBlocksProps> = (props) => {
|
|||||||
transform: `translateY(${HEADER_HEIGHT}px)`,
|
transform: `translateY(${HEADER_HEIGHT}px)`,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{blocks?.map((block) => {
|
{blockIds?.map((blockId) => {
|
||||||
// hide the block if it doesn't have start and target dates and showAllBlocks is false
|
|
||||||
if (!showAllBlocks && !(block.start_date && block.target_date)) return;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<GanttChartBlock
|
<GanttChartBlock
|
||||||
key={block.id}
|
key={blockId}
|
||||||
block={block}
|
blockId={blockId}
|
||||||
|
getBlockById={getBlockById}
|
||||||
|
showAllBlocks={showAllBlocks}
|
||||||
blockToRender={blockToRender}
|
blockToRender={blockToRender}
|
||||||
blockUpdateHandler={blockUpdateHandler}
|
blockUpdateHandler={blockUpdateHandler}
|
||||||
enableBlockLeftResize={enableBlockLeftResize}
|
enableBlockLeftResize={enableBlockLeftResize}
|
||||||
|
@ -10,7 +10,7 @@ import { IGanttBlock, TGanttViews } from "../types";
|
|||||||
// constants
|
// constants
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
blocks: IGanttBlock[] | null;
|
blockIds: string[];
|
||||||
fullScreenMode: boolean;
|
fullScreenMode: boolean;
|
||||||
handleChartView: (view: TGanttViews) => void;
|
handleChartView: (view: TGanttViews) => void;
|
||||||
handleToday: () => void;
|
handleToday: () => void;
|
||||||
@ -20,7 +20,7 @@ type Props = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const GanttChartHeader: React.FC<Props> = observer((props) => {
|
export const GanttChartHeader: React.FC<Props> = observer((props) => {
|
||||||
const { blocks, fullScreenMode, handleChartView, handleToday, loaderTitle, title, toggleFullScreenMode } = props;
|
const { blockIds, fullScreenMode, handleChartView, handleToday, loaderTitle, title, toggleFullScreenMode } = props;
|
||||||
// chart hook
|
// chart hook
|
||||||
const { currentView } = useGanttChart();
|
const { currentView } = useGanttChart();
|
||||||
|
|
||||||
@ -28,7 +28,9 @@ export const GanttChartHeader: React.FC<Props> = observer((props) => {
|
|||||||
<div className="relative flex w-full flex-shrink-0 flex-wrap items-center gap-2 whitespace-nowrap px-2.5 py-2">
|
<div className="relative flex w-full flex-shrink-0 flex-wrap items-center gap-2 whitespace-nowrap px-2.5 py-2">
|
||||||
<div className="flex items-center gap-2 text-lg font-medium">{title}</div>
|
<div className="flex items-center gap-2 text-lg font-medium">{title}</div>
|
||||||
<div className="ml-auto">
|
<div className="ml-auto">
|
||||||
<div className="ml-auto text-sm font-medium">{blocks ? `${blocks.length} ${loaderTitle}` : "Loading..."}</div>
|
<div className="ml-auto text-sm font-medium">
|
||||||
|
{blockIds ? `${blockIds.length} ${loaderTitle}` : "Loading..."}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-wrap items-center gap-2">
|
<div className="flex flex-wrap items-center gap-2">
|
||||||
|
@ -4,6 +4,7 @@ import { observer } from "mobx-react";
|
|||||||
// components
|
// components
|
||||||
import {
|
import {
|
||||||
BiWeekChartView,
|
BiWeekChartView,
|
||||||
|
ChartDataType,
|
||||||
DayChartView,
|
DayChartView,
|
||||||
GanttChartBlocksList,
|
GanttChartBlocksList,
|
||||||
GanttChartSidebar,
|
GanttChartSidebar,
|
||||||
@ -21,11 +22,13 @@ import { cn } from "helpers/common.helper";
|
|||||||
import { useGanttChart } from "../hooks/use-gantt-chart";
|
import { useGanttChart } from "../hooks/use-gantt-chart";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
blocks: IGanttBlock[] | null;
|
blockIds: string[];
|
||||||
|
getBlockById: (id: string, currentViewData?: ChartDataType | undefined) => IGanttBlock;
|
||||||
|
canLoadMoreBlocks?: boolean;
|
||||||
|
loadMoreBlocks?: () => void;
|
||||||
blockToRender: (data: any) => React.ReactNode;
|
blockToRender: (data: any) => React.ReactNode;
|
||||||
blockUpdateHandler: (block: any, payload: IBlockUpdateData) => void;
|
blockUpdateHandler: (block: any, payload: IBlockUpdateData) => void;
|
||||||
bottomSpacing: boolean;
|
bottomSpacing: boolean;
|
||||||
chartBlocks: IGanttBlock[] | null;
|
|
||||||
enableBlockLeftResize: boolean;
|
enableBlockLeftResize: boolean;
|
||||||
enableBlockMove: boolean;
|
enableBlockMove: boolean;
|
||||||
enableBlockRightResize: boolean;
|
enableBlockRightResize: boolean;
|
||||||
@ -41,11 +44,12 @@ type Props = {
|
|||||||
|
|
||||||
export const GanttChartMainContent: React.FC<Props> = observer((props) => {
|
export const GanttChartMainContent: React.FC<Props> = observer((props) => {
|
||||||
const {
|
const {
|
||||||
blocks,
|
blockIds,
|
||||||
|
getBlockById,
|
||||||
|
loadMoreBlocks,
|
||||||
blockToRender,
|
blockToRender,
|
||||||
blockUpdateHandler,
|
blockUpdateHandler,
|
||||||
bottomSpacing,
|
bottomSpacing,
|
||||||
chartBlocks,
|
|
||||||
enableBlockLeftResize,
|
enableBlockLeftResize,
|
||||||
enableBlockMove,
|
enableBlockMove,
|
||||||
enableBlockRightResize,
|
enableBlockRightResize,
|
||||||
@ -55,6 +59,7 @@ export const GanttChartMainContent: React.FC<Props> = observer((props) => {
|
|||||||
showAllBlocks,
|
showAllBlocks,
|
||||||
sidebarToRender,
|
sidebarToRender,
|
||||||
title,
|
title,
|
||||||
|
canLoadMoreBlocks,
|
||||||
updateCurrentViewRenderPayload,
|
updateCurrentViewRenderPayload,
|
||||||
quickAdd,
|
quickAdd,
|
||||||
} = props;
|
} = props;
|
||||||
@ -104,7 +109,11 @@ export const GanttChartMainContent: React.FC<Props> = observer((props) => {
|
|||||||
onScroll={onScroll}
|
onScroll={onScroll}
|
||||||
>
|
>
|
||||||
<GanttChartSidebar
|
<GanttChartSidebar
|
||||||
blocks={blocks}
|
blockIds={blockIds}
|
||||||
|
getBlockById={getBlockById}
|
||||||
|
loadMoreBlocks={loadMoreBlocks}
|
||||||
|
canLoadMoreBlocks={canLoadMoreBlocks}
|
||||||
|
ganttContainerRef={ganttContainerRef}
|
||||||
blockUpdateHandler={blockUpdateHandler}
|
blockUpdateHandler={blockUpdateHandler}
|
||||||
enableReorder={enableReorder}
|
enableReorder={enableReorder}
|
||||||
sidebarToRender={sidebarToRender}
|
sidebarToRender={sidebarToRender}
|
||||||
@ -116,7 +125,8 @@ export const GanttChartMainContent: React.FC<Props> = observer((props) => {
|
|||||||
{currentViewData && (
|
{currentViewData && (
|
||||||
<GanttChartBlocksList
|
<GanttChartBlocksList
|
||||||
itemsContainerWidth={itemsContainerWidth}
|
itemsContainerWidth={itemsContainerWidth}
|
||||||
blocks={chartBlocks}
|
blockIds={blockIds}
|
||||||
|
getBlockById={getBlockById}
|
||||||
blockToRender={blockToRender}
|
blockToRender={blockToRender}
|
||||||
blockUpdateHandler={blockUpdateHandler}
|
blockUpdateHandler={blockUpdateHandler}
|
||||||
enableBlockLeftResize={enableBlockLeftResize}
|
enableBlockLeftResize={enableBlockLeftResize}
|
||||||
|
@ -13,17 +13,13 @@ import { currentViewDataWithView } from "../data";
|
|||||||
// constants
|
// constants
|
||||||
import { useGanttChart } from "../hooks/use-gantt-chart";
|
import { useGanttChart } from "../hooks/use-gantt-chart";
|
||||||
import { ChartDataType, IBlockUpdateData, IGanttBlock, TGanttViews } from "../types";
|
import { ChartDataType, IBlockUpdateData, IGanttBlock, TGanttViews } from "../types";
|
||||||
import {
|
import { generateMonthChart, getNumberOfDaysBetweenTwoDatesInMonth } from "../views";
|
||||||
generateMonthChart,
|
|
||||||
getNumberOfDaysBetweenTwoDatesInMonth,
|
|
||||||
getMonthChartItemPositionWidthInMonth,
|
|
||||||
} from "../views";
|
|
||||||
|
|
||||||
type ChartViewRootProps = {
|
type ChartViewRootProps = {
|
||||||
border: boolean;
|
border: boolean;
|
||||||
title: string;
|
title: string;
|
||||||
loaderTitle: string;
|
loaderTitle: string;
|
||||||
blocks: IGanttBlock[] | null;
|
blockIds: string[];
|
||||||
blockUpdateHandler: (block: any, payload: IBlockUpdateData) => void;
|
blockUpdateHandler: (block: any, payload: IBlockUpdateData) => void;
|
||||||
blockToRender: (data: any) => React.ReactNode;
|
blockToRender: (data: any) => React.ReactNode;
|
||||||
sidebarToRender: (props: any) => React.ReactNode;
|
sidebarToRender: (props: any) => React.ReactNode;
|
||||||
@ -34,6 +30,9 @@ type ChartViewRootProps = {
|
|||||||
enableAddBlock: boolean;
|
enableAddBlock: boolean;
|
||||||
bottomSpacing: boolean;
|
bottomSpacing: boolean;
|
||||||
showAllBlocks: boolean;
|
showAllBlocks: boolean;
|
||||||
|
getBlockById: (id: string, currentViewData?: ChartDataType | undefined) => IGanttBlock;
|
||||||
|
loadMoreBlocks?: () => void;
|
||||||
|
canLoadMoreBlocks?: boolean;
|
||||||
quickAdd?: React.JSX.Element | undefined;
|
quickAdd?: React.JSX.Element | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -41,11 +40,14 @@ export const ChartViewRoot: FC<ChartViewRootProps> = observer((props) => {
|
|||||||
const {
|
const {
|
||||||
border,
|
border,
|
||||||
title,
|
title,
|
||||||
blocks = null,
|
blockIds,
|
||||||
|
getBlockById,
|
||||||
|
loadMoreBlocks,
|
||||||
loaderTitle,
|
loaderTitle,
|
||||||
blockUpdateHandler,
|
blockUpdateHandler,
|
||||||
sidebarToRender,
|
sidebarToRender,
|
||||||
blockToRender,
|
blockToRender,
|
||||||
|
canLoadMoreBlocks,
|
||||||
enableBlockLeftResize,
|
enableBlockLeftResize,
|
||||||
enableBlockRightResize,
|
enableBlockRightResize,
|
||||||
enableBlockMove,
|
enableBlockMove,
|
||||||
@ -58,25 +60,10 @@ export const ChartViewRoot: FC<ChartViewRootProps> = observer((props) => {
|
|||||||
// states
|
// states
|
||||||
const [itemsContainerWidth, setItemsContainerWidth] = useState(0);
|
const [itemsContainerWidth, setItemsContainerWidth] = useState(0);
|
||||||
const [fullScreenMode, setFullScreenMode] = useState(false);
|
const [fullScreenMode, setFullScreenMode] = useState(false);
|
||||||
const [chartBlocks, setChartBlocks] = useState<IGanttBlock[] | null>(null);
|
|
||||||
// hooks
|
// hooks
|
||||||
const { currentView, currentViewData, renderView, updateCurrentView, updateCurrentViewData, updateRenderView } =
|
const { currentView, currentViewData, renderView, updateCurrentView, updateCurrentViewData, updateRenderView } =
|
||||||
useGanttChart();
|
useGanttChart();
|
||||||
|
|
||||||
// rendering the block structure
|
|
||||||
const renderBlockStructure = (view: any, blocks: IGanttBlock[] | null) =>
|
|
||||||
blocks
|
|
||||||
? blocks.map((block: any) => ({
|
|
||||||
...block,
|
|
||||||
position: getMonthChartItemPositionWidthInMonth(view, block),
|
|
||||||
}))
|
|
||||||
: [];
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!currentViewData || !blocks) return;
|
|
||||||
setChartBlocks(() => renderBlockStructure(currentViewData, blocks));
|
|
||||||
}, [currentViewData, blocks]);
|
|
||||||
|
|
||||||
const updateCurrentViewRenderPayload = (side: null | "left" | "right", view: TGanttViews) => {
|
const updateCurrentViewRenderPayload = (side: null | "left" | "right", view: TGanttViews) => {
|
||||||
const selectedCurrentView: TGanttViews = view;
|
const selectedCurrentView: TGanttViews = view;
|
||||||
const selectedCurrentViewData: ChartDataType | undefined =
|
const selectedCurrentViewData: ChartDataType | undefined =
|
||||||
@ -166,7 +153,7 @@ export const ChartViewRoot: FC<ChartViewRootProps> = observer((props) => {
|
|||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<GanttChartHeader
|
<GanttChartHeader
|
||||||
blocks={blocks}
|
blockIds={blockIds}
|
||||||
fullScreenMode={fullScreenMode}
|
fullScreenMode={fullScreenMode}
|
||||||
toggleFullScreenMode={() => setFullScreenMode((prevData) => !prevData)}
|
toggleFullScreenMode={() => setFullScreenMode((prevData) => !prevData)}
|
||||||
handleChartView={(key) => updateCurrentViewRenderPayload(null, key)}
|
handleChartView={(key) => updateCurrentViewRenderPayload(null, key)}
|
||||||
@ -175,11 +162,13 @@ export const ChartViewRoot: FC<ChartViewRootProps> = observer((props) => {
|
|||||||
title={title}
|
title={title}
|
||||||
/>
|
/>
|
||||||
<GanttChartMainContent
|
<GanttChartMainContent
|
||||||
blocks={blocks}
|
blockIds={blockIds}
|
||||||
|
getBlockById={getBlockById}
|
||||||
|
loadMoreBlocks={loadMoreBlocks}
|
||||||
|
canLoadMoreBlocks={canLoadMoreBlocks}
|
||||||
blockToRender={blockToRender}
|
blockToRender={blockToRender}
|
||||||
blockUpdateHandler={blockUpdateHandler}
|
blockUpdateHandler={blockUpdateHandler}
|
||||||
bottomSpacing={bottomSpacing}
|
bottomSpacing={bottomSpacing}
|
||||||
chartBlocks={chartBlocks}
|
|
||||||
enableBlockLeftResize={enableBlockLeftResize}
|
enableBlockLeftResize={enableBlockLeftResize}
|
||||||
enableBlockMove={enableBlockMove}
|
enableBlockMove={enableBlockMove}
|
||||||
enableBlockRightResize={enableBlockRightResize}
|
enableBlockRightResize={enableBlockRightResize}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { FC } from "react";
|
import { FC } from "react";
|
||||||
// components
|
// components
|
||||||
import { ChartViewRoot, IBlockUpdateData, IGanttBlock } from "components/gantt-chart";
|
import { ChartDataType, ChartViewRoot, IBlockUpdateData, IGanttBlock } from "components/gantt-chart";
|
||||||
// context
|
// context
|
||||||
import { GanttStoreProvider } from "components/gantt-chart/contexts";
|
import { GanttStoreProvider } from "components/gantt-chart/contexts";
|
||||||
|
|
||||||
@ -8,11 +8,14 @@ type GanttChartRootProps = {
|
|||||||
border?: boolean;
|
border?: boolean;
|
||||||
title: string;
|
title: string;
|
||||||
loaderTitle: string;
|
loaderTitle: string;
|
||||||
blocks: IGanttBlock[] | null;
|
blockIds: string[];
|
||||||
blockUpdateHandler: (block: any, payload: IBlockUpdateData) => void;
|
blockUpdateHandler: (block: any, payload: IBlockUpdateData) => void;
|
||||||
blockToRender: (data: any) => React.ReactNode;
|
blockToRender: (data: any) => React.ReactNode;
|
||||||
sidebarToRender: (props: any) => React.ReactNode;
|
sidebarToRender: (props: any) => React.ReactNode;
|
||||||
quickAdd?: React.JSX.Element | undefined;
|
quickAdd?: React.JSX.Element | undefined;
|
||||||
|
getBlockById: (id: string, currentViewData?: ChartDataType | undefined) => IGanttBlock;
|
||||||
|
canLoadMoreBlocks?: boolean;
|
||||||
|
loadMoreBlocks?: () => void;
|
||||||
enableBlockLeftResize?: boolean;
|
enableBlockLeftResize?: boolean;
|
||||||
enableBlockRightResize?: boolean;
|
enableBlockRightResize?: boolean;
|
||||||
enableBlockMove?: boolean;
|
enableBlockMove?: boolean;
|
||||||
@ -26,11 +29,14 @@ export const GanttChartRoot: FC<GanttChartRootProps> = (props) => {
|
|||||||
const {
|
const {
|
||||||
border = true,
|
border = true,
|
||||||
title,
|
title,
|
||||||
blocks,
|
blockIds,
|
||||||
loaderTitle = "blocks",
|
loaderTitle = "blocks",
|
||||||
blockUpdateHandler,
|
blockUpdateHandler,
|
||||||
sidebarToRender,
|
sidebarToRender,
|
||||||
blockToRender,
|
blockToRender,
|
||||||
|
getBlockById,
|
||||||
|
loadMoreBlocks,
|
||||||
|
canLoadMoreBlocks,
|
||||||
enableBlockLeftResize = false,
|
enableBlockLeftResize = false,
|
||||||
enableBlockRightResize = false,
|
enableBlockRightResize = false,
|
||||||
enableBlockMove = false,
|
enableBlockMove = false,
|
||||||
@ -46,7 +52,10 @@ export const GanttChartRoot: FC<GanttChartRootProps> = (props) => {
|
|||||||
<ChartViewRoot
|
<ChartViewRoot
|
||||||
border={border}
|
border={border}
|
||||||
title={title}
|
title={title}
|
||||||
blocks={blocks}
|
blockIds={blockIds}
|
||||||
|
getBlockById={getBlockById}
|
||||||
|
loadMoreBlocks={loadMoreBlocks}
|
||||||
|
canLoadMoreBlocks={canLoadMoreBlocks}
|
||||||
loaderTitle={loaderTitle}
|
loaderTitle={loaderTitle}
|
||||||
blockUpdateHandler={blockUpdateHandler}
|
blockUpdateHandler={blockUpdateHandler}
|
||||||
sidebarToRender={sidebarToRender}
|
sidebarToRender={sidebarToRender}
|
||||||
|
@ -2,22 +2,23 @@ import { DragDropContext, Draggable, DropResult, Droppable } from "@hello-pangea
|
|||||||
// ui
|
// ui
|
||||||
import { Loader } from "@plane/ui";
|
import { Loader } from "@plane/ui";
|
||||||
// components
|
// components
|
||||||
import { IBlockUpdateData, IGanttBlock } from "components/gantt-chart/types";
|
import { ChartDataType, IBlockUpdateData, IGanttBlock } from "components/gantt-chart/types";
|
||||||
import { CyclesSidebarBlock } from "./block";
|
import { CyclesSidebarBlock } from "./block";
|
||||||
// types
|
// types
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
title: string;
|
title: string;
|
||||||
blockUpdateHandler: (block: any, payload: IBlockUpdateData) => void;
|
blockUpdateHandler: (block: any, payload: IBlockUpdateData) => void;
|
||||||
blocks: IGanttBlock[] | null;
|
getBlockById: (id: string, currentViewData?: ChartDataType | undefined) => IGanttBlock;
|
||||||
|
blockIds: string[];
|
||||||
enableReorder: boolean;
|
enableReorder: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CycleGanttSidebar: React.FC<Props> = (props) => {
|
export const CycleGanttSidebar: React.FC<Props> = (props) => {
|
||||||
const { blockUpdateHandler, blocks, enableReorder } = props;
|
const { blockUpdateHandler, blockIds, getBlockById, enableReorder } = props;
|
||||||
|
|
||||||
const handleOrderChange = (result: DropResult) => {
|
const handleOrderChange = (result: DropResult) => {
|
||||||
if (!blocks) return;
|
if (!blockIds) return;
|
||||||
|
|
||||||
const { source, destination } = result;
|
const { source, destination } = result;
|
||||||
|
|
||||||
@ -27,29 +28,30 @@ export const CycleGanttSidebar: React.FC<Props> = (props) => {
|
|||||||
// return if dropped on the same index
|
// return if dropped on the same index
|
||||||
if (source.index === destination.index) return;
|
if (source.index === destination.index) return;
|
||||||
|
|
||||||
let updatedSortOrder = blocks[source.index].sort_order;
|
let updatedSortOrder = getBlockById(blockIds[source.index]).sort_order;
|
||||||
|
|
||||||
// update the sort order to the lowest if dropped at the top
|
// update the sort order to the lowest if dropped at the top
|
||||||
if (destination.index === 0) updatedSortOrder = blocks[0].sort_order - 1000;
|
if (destination.index === 0) updatedSortOrder = getBlockById(blockIds[0]).sort_order - 1000;
|
||||||
// update the sort order to the highest if dropped at the bottom
|
// update the sort order to the highest if dropped at the bottom
|
||||||
else if (destination.index === blocks.length - 1) updatedSortOrder = blocks[blocks.length - 1].sort_order + 1000;
|
else if (destination.index === blockIds.length - 1)
|
||||||
|
updatedSortOrder = getBlockById(blockIds[blockIds.length - 1]).sort_order + 1000;
|
||||||
// update the sort order to the average of the two adjacent blocks if dropped in between
|
// update the sort order to the average of the two adjacent blocks if dropped in between
|
||||||
else {
|
else {
|
||||||
const destinationSortingOrder = blocks[destination.index].sort_order;
|
const destinationSortingOrder = getBlockById(blockIds[destination.index]).sort_order;
|
||||||
const relativeDestinationSortingOrder =
|
const relativeDestinationSortingOrder =
|
||||||
source.index < destination.index
|
source.index < destination.index
|
||||||
? blocks[destination.index + 1].sort_order
|
? getBlockById(blockIds[destination.index + 1]).sort_order
|
||||||
: blocks[destination.index - 1].sort_order;
|
: getBlockById(blockIds[destination.index - 1]).sort_order;
|
||||||
|
|
||||||
updatedSortOrder = (destinationSortingOrder + relativeDestinationSortingOrder) / 2;
|
updatedSortOrder = (destinationSortingOrder + relativeDestinationSortingOrder) / 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
// extract the element from the source index and insert it at the destination index without updating the entire array
|
// extract the element from the source index and insert it at the destination index without updating the entire array
|
||||||
const removedElement = blocks.splice(source.index, 1)[0];
|
const removedElement = blockIds.splice(source.index, 1)[0];
|
||||||
blocks.splice(destination.index, 0, removedElement);
|
blockIds.splice(destination.index, 0, removedElement);
|
||||||
|
|
||||||
// call the block update handler with the updated sort order, new and old index
|
// call the block update handler with the updated sort order, new and old index
|
||||||
blockUpdateHandler(removedElement.data, {
|
blockUpdateHandler(getBlockById(removedElement).data, {
|
||||||
sort_order: {
|
sort_order: {
|
||||||
destinationIndex: destination.index,
|
destinationIndex: destination.index,
|
||||||
newSortOrder: updatedSortOrder,
|
newSortOrder: updatedSortOrder,
|
||||||
@ -64,24 +66,28 @@ export const CycleGanttSidebar: React.FC<Props> = (props) => {
|
|||||||
{(droppableProvided) => (
|
{(droppableProvided) => (
|
||||||
<div className="h-full" ref={droppableProvided.innerRef} {...droppableProvided.droppableProps}>
|
<div className="h-full" ref={droppableProvided.innerRef} {...droppableProvided.droppableProps}>
|
||||||
<>
|
<>
|
||||||
{blocks ? (
|
{blockIds ? (
|
||||||
blocks.map((block, index) => (
|
blockIds.map((blockId, index) => {
|
||||||
<Draggable
|
const block = getBlockById(blockId);
|
||||||
key={`sidebar-block-${block.id}`}
|
if (!block.start_date || !block.target_date) return null;
|
||||||
draggableId={`sidebar-block-${block.id}`}
|
return (
|
||||||
index={index}
|
<Draggable
|
||||||
isDragDisabled={!enableReorder}
|
key={`sidebar-block-${block.id}`}
|
||||||
>
|
draggableId={`sidebar-block-${block.id}`}
|
||||||
{(provided, snapshot) => (
|
index={index}
|
||||||
<CyclesSidebarBlock
|
isDragDisabled={!enableReorder}
|
||||||
block={block}
|
>
|
||||||
enableReorder={enableReorder}
|
{(provided, snapshot) => (
|
||||||
provided={provided}
|
<CyclesSidebarBlock
|
||||||
snapshot={snapshot}
|
block={block}
|
||||||
/>
|
enableReorder={enableReorder}
|
||||||
)}
|
provided={provided}
|
||||||
</Draggable>
|
snapshot={snapshot}
|
||||||
))
|
/>
|
||||||
|
)}
|
||||||
|
</Draggable>
|
||||||
|
);
|
||||||
|
})
|
||||||
) : (
|
) : (
|
||||||
<Loader className="space-y-3 pr-2">
|
<Loader className="space-y-3 pr-2">
|
||||||
<Loader.Item height="34px" />
|
<Loader.Item height="34px" />
|
||||||
|
@ -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,40 @@ import { DragDropContext, Draggable, Droppable, DropResult } from "@hello-pangea
|
|||||||
import { Loader } from "@plane/ui";
|
import { Loader } from "@plane/ui";
|
||||||
// types
|
// types
|
||||||
import { IGanttBlock, IBlockUpdateData } from "components/gantt-chart/types";
|
import { IGanttBlock, IBlockUpdateData } from "components/gantt-chart/types";
|
||||||
import { IssuesSidebarBlock } from "./block";
|
import { observer } from "mobx-react";
|
||||||
|
import { IssueDraggableBlock } from "./issue-draggable-block";
|
||||||
|
import { useIntersectionObserver } from "hooks/use-intersection-observer";
|
||||||
|
import { RefObject, useRef } from "react";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
blockUpdateHandler: (block: any, payload: IBlockUpdateData) => void;
|
blockUpdateHandler: (block: any, payload: IBlockUpdateData) => void;
|
||||||
blocks: IGanttBlock[] | null;
|
getBlockById: (id: string) => IGanttBlock;
|
||||||
|
canLoadMoreBlocks?: boolean;
|
||||||
|
loadMoreBlocks?: () => void;
|
||||||
|
ganttContainerRef: RefObject<HTMLDivElement>;
|
||||||
|
blockIds: string[];
|
||||||
enableReorder: boolean;
|
enableReorder: boolean;
|
||||||
showAllBlocks?: boolean;
|
showAllBlocks?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const IssueGanttSidebar: React.FC<Props> = (props) => {
|
export const IssueGanttSidebar: React.FC<Props> = observer((props) => {
|
||||||
const { blockUpdateHandler, blocks, enableReorder, showAllBlocks = false } = props;
|
const {
|
||||||
|
blockUpdateHandler,
|
||||||
|
blockIds,
|
||||||
|
getBlockById,
|
||||||
|
enableReorder,
|
||||||
|
loadMoreBlocks,
|
||||||
|
canLoadMoreBlocks,
|
||||||
|
ganttContainerRef,
|
||||||
|
showAllBlocks = false,
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const intersectionRef = useRef<HTMLSpanElement | null>(null);
|
||||||
|
|
||||||
|
useIntersectionObserver(ganttContainerRef, intersectionRef, loadMoreBlocks, "50% 0% 50% 0%");
|
||||||
|
|
||||||
const handleOrderChange = (result: DropResult) => {
|
const handleOrderChange = (result: DropResult) => {
|
||||||
if (!blocks) return;
|
if (!blockIds) return;
|
||||||
|
|
||||||
const { source, destination } = result;
|
const { source, destination } = result;
|
||||||
|
|
||||||
@ -27,29 +47,30 @@ export const IssueGanttSidebar: React.FC<Props> = (props) => {
|
|||||||
// return if dropped on the same index
|
// return if dropped on the same index
|
||||||
if (source.index === destination.index) return;
|
if (source.index === destination.index) return;
|
||||||
|
|
||||||
let updatedSortOrder = blocks[source.index].sort_order;
|
let updatedSortOrder = getBlockById(blockIds[source.index]).sort_order;
|
||||||
|
|
||||||
// update the sort order to the lowest if dropped at the top
|
// update the sort order to the lowest if dropped at the top
|
||||||
if (destination.index === 0) updatedSortOrder = blocks[0].sort_order - 1000;
|
if (destination.index === 0) updatedSortOrder = getBlockById(blockIds[0]).sort_order - 1000;
|
||||||
// update the sort order to the highest if dropped at the bottom
|
// update the sort order to the highest if dropped at the bottom
|
||||||
else if (destination.index === blocks.length - 1) updatedSortOrder = blocks[blocks.length - 1].sort_order + 1000;
|
else if (destination.index === blockIds.length - 1)
|
||||||
|
updatedSortOrder = getBlockById(blockIds[blockIds.length - 1])!.sort_order + 1000;
|
||||||
// update the sort order to the average of the two adjacent blocks if dropped in between
|
// update the sort order to the average of the two adjacent blocks if dropped in between
|
||||||
else {
|
else {
|
||||||
const destinationSortingOrder = blocks[destination.index].sort_order;
|
const destinationSortingOrder = getBlockById(blockIds[destination.index]).sort_order;
|
||||||
const relativeDestinationSortingOrder =
|
const relativeDestinationSortingOrder =
|
||||||
source.index < destination.index
|
source.index < destination.index
|
||||||
? blocks[destination.index + 1].sort_order
|
? getBlockById(blockIds[destination.index + 1]).sort_order
|
||||||
: blocks[destination.index - 1].sort_order;
|
: getBlockById(blockIds[destination.index - 1]).sort_order;
|
||||||
|
|
||||||
updatedSortOrder = (destinationSortingOrder + relativeDestinationSortingOrder) / 2;
|
updatedSortOrder = (destinationSortingOrder + relativeDestinationSortingOrder) / 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
// extract the element from the source index and insert it at the destination index without updating the entire array
|
// extract the element from the source index and insert it at the destination index without updating the entire array
|
||||||
const removedElement = blocks.splice(source.index, 1)[0];
|
const removedElement = blockIds.splice(source.index, 1)[0];
|
||||||
blocks.splice(destination.index, 0, removedElement);
|
blockIds.splice(destination.index, 0, removedElement);
|
||||||
|
|
||||||
// call the block update handler with the updated sort order, new and old index
|
// call the block update handler with the updated sort order, new and old index
|
||||||
blockUpdateHandler(removedElement.data, {
|
blockUpdateHandler(getBlockById(removedElement).data, {
|
||||||
sort_order: {
|
sort_order: {
|
||||||
destinationIndex: destination.index,
|
destinationIndex: destination.index,
|
||||||
newSortOrder: updatedSortOrder,
|
newSortOrder: updatedSortOrder,
|
||||||
@ -64,31 +85,21 @@ export const IssueGanttSidebar: React.FC<Props> = (props) => {
|
|||||||
{(droppableProvided) => (
|
{(droppableProvided) => (
|
||||||
<div ref={droppableProvided.innerRef} {...droppableProvided.droppableProps}>
|
<div ref={droppableProvided.innerRef} {...droppableProvided.droppableProps}>
|
||||||
<>
|
<>
|
||||||
{blocks ? (
|
{blockIds ? (
|
||||||
blocks.map((block, index) => {
|
<>
|
||||||
const isBlockVisibleOnSidebar = block.start_date && block.target_date;
|
{blockIds.map((blockId, index) => (
|
||||||
|
<IssueDraggableBlock
|
||||||
// hide the block if it doesn't have start and target dates and showAllBlocks is false
|
blockId={blockId}
|
||||||
if (!showAllBlocks && !isBlockVisibleOnSidebar) return;
|
enableReorder={enableReorder}
|
||||||
|
|
||||||
return (
|
|
||||||
<Draggable
|
|
||||||
key={`sidebar-block-${block.id}`}
|
|
||||||
draggableId={`sidebar-block-${block.id}`}
|
|
||||||
index={index}
|
index={index}
|
||||||
isDragDisabled={!enableReorder}
|
showAllBlocks={showAllBlocks}
|
||||||
>
|
getBlockById={getBlockById}
|
||||||
{(provided, snapshot) => (
|
/>
|
||||||
<IssuesSidebarBlock
|
))}
|
||||||
block={block}
|
{canLoadMoreBlocks && (
|
||||||
enableReorder={enableReorder}
|
<span ref={intersectionRef} className="h-5 w-10 bg-custom-background-80 rounded animate-pulse" />
|
||||||
provided={provided}
|
)}
|
||||||
snapshot={snapshot}
|
</>
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Draggable>
|
|
||||||
);
|
|
||||||
})
|
|
||||||
) : (
|
) : (
|
||||||
<Loader className="space-y-3 pr-2">
|
<Loader className="space-y-3 pr-2">
|
||||||
<Loader.Item height="34px" />
|
<Loader.Item height="34px" />
|
||||||
@ -104,4 +115,4 @@ export const IssueGanttSidebar: React.FC<Props> = (props) => {
|
|||||||
</Droppable>
|
</Droppable>
|
||||||
</DragDropContext>
|
</DragDropContext>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
@ -2,22 +2,23 @@ import { DragDropContext, Draggable, Droppable, DropResult } from "@hello-pangea
|
|||||||
// ui
|
// ui
|
||||||
import { Loader } from "@plane/ui";
|
import { Loader } from "@plane/ui";
|
||||||
// components
|
// components
|
||||||
import { IBlockUpdateData, IGanttBlock } from "components/gantt-chart";
|
import { ChartDataType, IBlockUpdateData, IGanttBlock } from "components/gantt-chart";
|
||||||
import { ModulesSidebarBlock } from "./block";
|
import { ModulesSidebarBlock } from "./block";
|
||||||
// types
|
// types
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
title: string;
|
title: string;
|
||||||
blockUpdateHandler: (block: any, payload: IBlockUpdateData) => void;
|
blockUpdateHandler: (block: any, payload: IBlockUpdateData) => void;
|
||||||
blocks: IGanttBlock[] | null;
|
getBlockById: (id: string, currentViewData?: ChartDataType | undefined) => IGanttBlock;
|
||||||
|
blockIds: string[];
|
||||||
enableReorder: boolean;
|
enableReorder: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ModuleGanttSidebar: React.FC<Props> = (props) => {
|
export const ModuleGanttSidebar: React.FC<Props> = (props) => {
|
||||||
const { blockUpdateHandler, blocks, enableReorder } = props;
|
const { blockUpdateHandler, blockIds, getBlockById, enableReorder } = props;
|
||||||
|
|
||||||
const handleOrderChange = (result: DropResult) => {
|
const handleOrderChange = (result: DropResult) => {
|
||||||
if (!blocks) return;
|
if (!blockIds) return;
|
||||||
|
|
||||||
const { source, destination } = result;
|
const { source, destination } = result;
|
||||||
|
|
||||||
@ -27,29 +28,30 @@ export const ModuleGanttSidebar: React.FC<Props> = (props) => {
|
|||||||
// return if dropped on the same index
|
// return if dropped on the same index
|
||||||
if (source.index === destination.index) return;
|
if (source.index === destination.index) return;
|
||||||
|
|
||||||
let updatedSortOrder = blocks[source.index].sort_order;
|
let updatedSortOrder = getBlockById(blockIds[source.index]).sort_order;
|
||||||
|
|
||||||
// update the sort order to the lowest if dropped at the top
|
// update the sort order to the lowest if dropped at the top
|
||||||
if (destination.index === 0) updatedSortOrder = blocks[0].sort_order - 1000;
|
if (destination.index === 0) updatedSortOrder = getBlockById(blockIds[0]).sort_order - 1000;
|
||||||
// update the sort order to the highest if dropped at the bottom
|
// update the sort order to the highest if dropped at the bottom
|
||||||
else if (destination.index === blocks.length - 1) updatedSortOrder = blocks[blocks.length - 1].sort_order + 1000;
|
else if (destination.index === blockIds.length - 1)
|
||||||
|
updatedSortOrder = getBlockById(blockIds[blockIds.length - 1]).sort_order + 1000;
|
||||||
// update the sort order to the average of the two adjacent blocks if dropped in between
|
// update the sort order to the average of the two adjacent blocks if dropped in between
|
||||||
else {
|
else {
|
||||||
const destinationSortingOrder = blocks[destination.index].sort_order;
|
const destinationSortingOrder = getBlockById(blockIds[destination.index]).sort_order;
|
||||||
const relativeDestinationSortingOrder =
|
const relativeDestinationSortingOrder =
|
||||||
source.index < destination.index
|
source.index < destination.index
|
||||||
? blocks[destination.index + 1].sort_order
|
? getBlockById(blockIds[destination.index + 1]).sort_order
|
||||||
: blocks[destination.index - 1].sort_order;
|
: getBlockById(blockIds[destination.index - 1]).sort_order;
|
||||||
|
|
||||||
updatedSortOrder = (destinationSortingOrder + relativeDestinationSortingOrder) / 2;
|
updatedSortOrder = (destinationSortingOrder + relativeDestinationSortingOrder) / 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
// extract the element from the source index and insert it at the destination index without updating the entire array
|
// extract the element from the source index and insert it at the destination index without updating the entire array
|
||||||
const removedElement = blocks.splice(source.index, 1)[0];
|
const removedElement = blockIds.splice(source.index, 1)[0];
|
||||||
blocks.splice(destination.index, 0, removedElement);
|
blockIds.splice(destination.index, 0, removedElement);
|
||||||
|
|
||||||
// call the block update handler with the updated sort order, new and old index
|
// call the block update handler with the updated sort order, new and old index
|
||||||
blockUpdateHandler(removedElement.data, {
|
blockUpdateHandler(getBlockById(removedElement).data, {
|
||||||
sort_order: {
|
sort_order: {
|
||||||
destinationIndex: destination.index,
|
destinationIndex: destination.index,
|
||||||
newSortOrder: updatedSortOrder,
|
newSortOrder: updatedSortOrder,
|
||||||
@ -64,24 +66,27 @@ export const ModuleGanttSidebar: React.FC<Props> = (props) => {
|
|||||||
{(droppableProvided) => (
|
{(droppableProvided) => (
|
||||||
<div className="h-full" ref={droppableProvided.innerRef} {...droppableProvided.droppableProps}>
|
<div className="h-full" ref={droppableProvided.innerRef} {...droppableProvided.droppableProps}>
|
||||||
<>
|
<>
|
||||||
{blocks ? (
|
{blockIds ? (
|
||||||
blocks.map((block, index) => (
|
blockIds.map((blockId, index) => {
|
||||||
<Draggable
|
const block = getBlockById(blockId);
|
||||||
key={`sidebar-block-${block.id}`}
|
return (
|
||||||
draggableId={`sidebar-block-${block.id}`}
|
<Draggable
|
||||||
index={index}
|
key={`sidebar-block-${block.id}`}
|
||||||
isDragDisabled={!enableReorder}
|
draggableId={`sidebar-block-${block.id}`}
|
||||||
>
|
index={index}
|
||||||
{(provided, snapshot) => (
|
isDragDisabled={!enableReorder}
|
||||||
<ModulesSidebarBlock
|
>
|
||||||
block={block}
|
{(provided, snapshot) => (
|
||||||
enableReorder={enableReorder}
|
<ModulesSidebarBlock
|
||||||
provided={provided}
|
block={block}
|
||||||
snapshot={snapshot}
|
enableReorder={enableReorder}
|
||||||
/>
|
provided={provided}
|
||||||
)}
|
snapshot={snapshot}
|
||||||
</Draggable>
|
/>
|
||||||
))
|
)}
|
||||||
|
</Draggable>
|
||||||
|
);
|
||||||
|
})
|
||||||
) : (
|
) : (
|
||||||
<Loader className="space-y-3 pr-2">
|
<Loader className="space-y-3 pr-2">
|
||||||
<Loader.Item height="34px" />
|
<Loader.Item height="34px" />
|
||||||
|
@ -1,19 +1,35 @@
|
|||||||
// components
|
// components
|
||||||
import { IBlockUpdateData, IGanttBlock } from "components/gantt-chart";
|
import { ChartDataType, IBlockUpdateData, IGanttBlock } from "components/gantt-chart";
|
||||||
// constants
|
// constants
|
||||||
import { HEADER_HEIGHT, SIDEBAR_WIDTH } from "../constants";
|
import { HEADER_HEIGHT, SIDEBAR_WIDTH } from "../constants";
|
||||||
|
import { RefObject } from "react";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
blocks: IGanttBlock[] | null;
|
blockIds: string[];
|
||||||
blockUpdateHandler: (block: any, payload: IBlockUpdateData) => void;
|
blockUpdateHandler: (block: any, payload: IBlockUpdateData) => void;
|
||||||
|
canLoadMoreBlocks?: boolean;
|
||||||
|
loadMoreBlocks?: () => void;
|
||||||
|
ganttContainerRef: RefObject<HTMLDivElement>;
|
||||||
enableReorder: boolean;
|
enableReorder: boolean;
|
||||||
sidebarToRender: (props: any) => React.ReactNode;
|
sidebarToRender: (props: any) => React.ReactNode;
|
||||||
title: string;
|
title: string;
|
||||||
|
getBlockById: (id: string, currentViewData?: ChartDataType | undefined) => IGanttBlock;
|
||||||
quickAdd?: React.JSX.Element | undefined;
|
quickAdd?: React.JSX.Element | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const GanttChartSidebar: React.FC<Props> = (props) => {
|
export const GanttChartSidebar: React.FC<Props> = (props) => {
|
||||||
const { blocks, blockUpdateHandler, enableReorder, sidebarToRender, title, quickAdd } = props;
|
const {
|
||||||
|
blockIds,
|
||||||
|
blockUpdateHandler,
|
||||||
|
enableReorder,
|
||||||
|
sidebarToRender,
|
||||||
|
getBlockById,
|
||||||
|
loadMoreBlocks,
|
||||||
|
canLoadMoreBlocks,
|
||||||
|
ganttContainerRef,
|
||||||
|
title,
|
||||||
|
quickAdd,
|
||||||
|
} = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@ -35,7 +51,17 @@ export const GanttChartSidebar: React.FC<Props> = (props) => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="min-h-full h-max bg-custom-background-100 overflow-x-hidden overflow-y-auto">
|
<div className="min-h-full h-max bg-custom-background-100 overflow-x-hidden overflow-y-auto">
|
||||||
{sidebarToRender && sidebarToRender({ title, blockUpdateHandler, blocks, enableReorder })}
|
{sidebarToRender &&
|
||||||
|
sidebarToRender({
|
||||||
|
title,
|
||||||
|
blockUpdateHandler,
|
||||||
|
blockIds,
|
||||||
|
getBlockById,
|
||||||
|
enableReorder,
|
||||||
|
canLoadMoreBlocks,
|
||||||
|
ganttContainerRef,
|
||||||
|
loadMoreBlocks,
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
{quickAdd ? quickAdd : null}
|
{quickAdd ? quickAdd : null}
|
||||||
</div>
|
</div>
|
||||||
|
@ -6,8 +6,8 @@ export interface IGanttBlock {
|
|||||||
width: number;
|
width: number;
|
||||||
};
|
};
|
||||||
sort_order: number;
|
sort_order: number;
|
||||||
start_date: Date | null;
|
start_date: Date | undefined;
|
||||||
target_date: Date | null;
|
target_date: Date | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IBlockUpdateData {
|
export interface IBlockUpdateData {
|
||||||
|
@ -167,7 +167,7 @@ export const getMonthChartItemPositionWidthInMonth = (chartData: ChartDataType,
|
|||||||
const { startDate } = chartData.data;
|
const { startDate } = chartData.data;
|
||||||
const { start_date: itemStartDate, target_date: itemTargetDate } = itemData;
|
const { start_date: itemStartDate, target_date: itemTargetDate } = itemData;
|
||||||
|
|
||||||
if (!itemStartDate || !itemTargetDate) return null;
|
if (!itemStartDate || !itemTargetDate) return;
|
||||||
|
|
||||||
startDate.setHours(0, 0, 0, 0);
|
startDate.setHours(0, 0, 0, 0);
|
||||||
itemStartDate.setHours(0, 0, 0, 0);
|
itemStartDate.setHours(0, 0, 0, 0);
|
||||||
|
@ -223,7 +223,7 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
|
|||||||
className="group w-3/5 flex-grow"
|
className="group w-3/5 flex-grow"
|
||||||
buttonContainerClassName="w-full text-left"
|
buttonContainerClassName="w-full text-left"
|
||||||
buttonClassName={`text-sm justify-between ${
|
buttonClassName={`text-sm justify-between ${
|
||||||
issue?.assignee_ids.length > 0 ? "" : "text-custom-text-400"
|
issue?.assignee_ids?.length > 0 ? "" : "text-custom-text-400"
|
||||||
}`}
|
}`}
|
||||||
hideIcon={issue.assignee_ids?.length === 0}
|
hideIcon={issue.assignee_ids?.length === 0}
|
||||||
dropdownArrow
|
dropdownArrow
|
||||||
|
@ -12,7 +12,7 @@ import { useCalendarView, useIssues, useUser } from "hooks/store";
|
|||||||
import { useIssuesActions } from "hooks/use-issues-actions";
|
import { useIssuesActions } from "hooks/use-issues-actions";
|
||||||
// ui
|
// ui
|
||||||
// types
|
// types
|
||||||
import { EIssueLayoutTypes, EIssuesStoreType, IssueGroupByOptions } from "constants/issue";
|
import { EIssueLayoutTypes, EIssuesStoreType, EIssueGroupByToServerOptions } from "constants/issue";
|
||||||
import { IQuickActionProps } from "../list/list-view-types";
|
import { IQuickActionProps } from "../list/list-view-types";
|
||||||
import { handleDragDrop } from "./utils";
|
import { handleDragDrop } from "./utils";
|
||||||
import { EUserProjectRoles } from "constants/project";
|
import { EUserProjectRoles } from "constants/project";
|
||||||
@ -68,14 +68,14 @@ export const BaseCalendarRoot = observer((props: IBaseCalendarRoot) => {
|
|||||||
|
|
||||||
useSWR(
|
useSWR(
|
||||||
startDate && endDate && layout ? `ISSUE_CALENDAR_LAYOUT_${storeType}_${startDate}_${endDate}_${layout}` : null,
|
startDate && endDate && layout ? `ISSUE_CALENDAR_LAYOUT_${storeType}_${startDate}_${endDate}_${layout}` : null,
|
||||||
startDate && endDate
|
startDate && endDate && layout
|
||||||
? () =>
|
? () =>
|
||||||
fetchIssues("init-loader", {
|
fetchIssues("init-loader", {
|
||||||
canGroup: true,
|
canGroup: true,
|
||||||
perPageCount: layout === "month" ? 4 : 30,
|
perPageCount: layout === "month" ? 4 : 30,
|
||||||
before: endDate,
|
before: endDate,
|
||||||
after: startDate,
|
after: startDate,
|
||||||
groupedBy: IssueGroupByOptions["target_date"],
|
groupedBy: EIssueGroupByToServerOptions["target_date"],
|
||||||
})
|
})
|
||||||
: null,
|
: null,
|
||||||
{
|
{
|
||||||
@ -112,9 +112,12 @@ export const BaseCalendarRoot = observer((props: IBaseCalendarRoot) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const loadMoreIssues = useCallback(() => {
|
const loadMoreIssues = useCallback(
|
||||||
fetchNextIssues();
|
(dateString: string) => {
|
||||||
}, [fetchNextIssues]);
|
fetchNextIssues(dateString);
|
||||||
|
},
|
||||||
|
[fetchNextIssues]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<IssueLayoutHOC storeType={storeType} layout={EIssueLayoutTypes.CALENDAR}>
|
<IssueLayoutHOC storeType={storeType} layout={EIssueLayoutTypes.CALENDAR}>
|
||||||
@ -142,6 +145,8 @@ export const BaseCalendarRoot = observer((props: IBaseCalendarRoot) => {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
loadMoreIssues={loadMoreIssues}
|
loadMoreIssues={loadMoreIssues}
|
||||||
|
getPaginationData={issues.getPaginationData}
|
||||||
|
getGroupIssueCount={issues.getGroupIssueCount}
|
||||||
addIssuesToView={addIssuesToView}
|
addIssuesToView={addIssuesToView}
|
||||||
quickAddCallback={issues.quickAddIssue}
|
quickAddCallback={issues.quickAddIssue}
|
||||||
viewId={viewId}
|
viewId={viewId}
|
||||||
|
@ -13,6 +13,7 @@ import {
|
|||||||
TIssue,
|
TIssue,
|
||||||
TIssueKanbanFilters,
|
TIssueKanbanFilters,
|
||||||
TIssueMap,
|
TIssueMap,
|
||||||
|
TPaginationData,
|
||||||
} from "@plane/types";
|
} from "@plane/types";
|
||||||
import { ICalendarWeek } from "./types";
|
import { ICalendarWeek } from "./types";
|
||||||
// constants
|
// constants
|
||||||
@ -33,7 +34,9 @@ type Props = {
|
|||||||
layout: "month" | "week" | undefined;
|
layout: "month" | "week" | undefined;
|
||||||
showWeekends: boolean;
|
showWeekends: boolean;
|
||||||
issueCalendarView: ICalendarStore;
|
issueCalendarView: ICalendarStore;
|
||||||
loadMoreIssues: () => void;
|
loadMoreIssues: (dateString: string) => void;
|
||||||
|
getPaginationData: (groupId: string | undefined) => TPaginationData | undefined;
|
||||||
|
getGroupIssueCount: (groupId: string | undefined) => number | undefined;
|
||||||
quickActions: (issue: TIssue, customActionButton?: React.ReactElement) => React.ReactNode;
|
quickActions: (issue: TIssue, customActionButton?: React.ReactElement) => React.ReactNode;
|
||||||
quickAddCallback?: (
|
quickAddCallback?: (
|
||||||
workspaceSlug: string,
|
workspaceSlug: string,
|
||||||
@ -63,6 +66,8 @@ export const CalendarChart: React.FC<Props> = observer((props) => {
|
|||||||
quickActions,
|
quickActions,
|
||||||
quickAddCallback,
|
quickAddCallback,
|
||||||
addIssuesToView,
|
addIssuesToView,
|
||||||
|
getPaginationData,
|
||||||
|
getGroupIssueCount,
|
||||||
viewId,
|
viewId,
|
||||||
updateFilters,
|
updateFilters,
|
||||||
readOnly = false,
|
readOnly = false,
|
||||||
@ -108,6 +113,8 @@ export const CalendarChart: React.FC<Props> = observer((props) => {
|
|||||||
issues={issues}
|
issues={issues}
|
||||||
groupedIssueIds={groupedIssueIds}
|
groupedIssueIds={groupedIssueIds}
|
||||||
loadMoreIssues={loadMoreIssues}
|
loadMoreIssues={loadMoreIssues}
|
||||||
|
getPaginationData={getPaginationData}
|
||||||
|
getGroupIssueCount={getGroupIssueCount}
|
||||||
enableQuickIssueCreate
|
enableQuickIssueCreate
|
||||||
disableIssueCreation={!enableIssueCreation || !isEditingAllowed}
|
disableIssueCreation={!enableIssueCreation || !isEditingAllowed}
|
||||||
quickActions={quickActions}
|
quickActions={quickActions}
|
||||||
@ -126,6 +133,8 @@ export const CalendarChart: React.FC<Props> = observer((props) => {
|
|||||||
issues={issues}
|
issues={issues}
|
||||||
groupedIssueIds={groupedIssueIds}
|
groupedIssueIds={groupedIssueIds}
|
||||||
loadMoreIssues={loadMoreIssues}
|
loadMoreIssues={loadMoreIssues}
|
||||||
|
getPaginationData={getPaginationData}
|
||||||
|
getGroupIssueCount={getGroupIssueCount}
|
||||||
enableQuickIssueCreate
|
enableQuickIssueCreate
|
||||||
disableIssueCreation={!enableIssueCreation || !isEditingAllowed}
|
disableIssueCreation={!enableIssueCreation || !isEditingAllowed}
|
||||||
quickActions={quickActions}
|
quickActions={quickActions}
|
||||||
|
@ -12,14 +12,16 @@ import { ICycleIssuesFilter } from "store/issue/cycle";
|
|||||||
import { IModuleIssuesFilter } from "store/issue/module";
|
import { IModuleIssuesFilter } from "store/issue/module";
|
||||||
import { IProjectIssuesFilter } from "store/issue/project";
|
import { IProjectIssuesFilter } from "store/issue/project";
|
||||||
import { IProjectViewIssuesFilter } from "store/issue/project-views";
|
import { IProjectViewIssuesFilter } from "store/issue/project-views";
|
||||||
import { TGroupedIssues, TIssue, TIssueMap } from "@plane/types";
|
import { TGroupedIssues, TIssue, TIssueMap, TPaginationData } from "@plane/types";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
issuesFilterStore: IProjectIssuesFilter | IModuleIssuesFilter | ICycleIssuesFilter | IProjectViewIssuesFilter;
|
issuesFilterStore: IProjectIssuesFilter | IModuleIssuesFilter | ICycleIssuesFilter | IProjectViewIssuesFilter;
|
||||||
date: ICalendarDate;
|
date: ICalendarDate;
|
||||||
issues: TIssueMap | undefined;
|
issues: TIssueMap | undefined;
|
||||||
groupedIssueIds: TGroupedIssues;
|
groupedIssueIds: TGroupedIssues;
|
||||||
loadMoreIssues: () => void;
|
loadMoreIssues: (dateString: string) => void;
|
||||||
|
getPaginationData: (groupId: string | undefined) => TPaginationData | undefined;
|
||||||
|
getGroupIssueCount: (groupId: string | undefined) => number | undefined;
|
||||||
quickActions: (issue: TIssue, customActionButton?: React.ReactElement) => React.ReactNode;
|
quickActions: (issue: TIssue, customActionButton?: React.ReactElement) => React.ReactNode;
|
||||||
enableQuickIssueCreate?: boolean;
|
enableQuickIssueCreate?: boolean;
|
||||||
disableIssueCreation?: boolean;
|
disableIssueCreation?: boolean;
|
||||||
@ -41,6 +43,8 @@ export const CalendarDayTile: React.FC<Props> = observer((props) => {
|
|||||||
issues,
|
issues,
|
||||||
groupedIssueIds,
|
groupedIssueIds,
|
||||||
loadMoreIssues,
|
loadMoreIssues,
|
||||||
|
getPaginationData,
|
||||||
|
getGroupIssueCount,
|
||||||
quickActions,
|
quickActions,
|
||||||
enableQuickIssueCreate,
|
enableQuickIssueCreate,
|
||||||
disableIssueCreation,
|
disableIssueCreation,
|
||||||
@ -53,9 +57,12 @@ export const CalendarDayTile: React.FC<Props> = observer((props) => {
|
|||||||
|
|
||||||
const formattedDatePayload = renderFormattedPayloadDate(date.date);
|
const formattedDatePayload = renderFormattedPayloadDate(date.date);
|
||||||
if (!formattedDatePayload) return null;
|
if (!formattedDatePayload) return null;
|
||||||
const issueIdList = groupedIssueIds ? groupedIssueIds[formattedDatePayload] : null;
|
const issueIds = groupedIssueIds?.[formattedDatePayload];
|
||||||
|
const dayIssueCount = getGroupIssueCount(formattedDatePayload);
|
||||||
|
const nextPageResults = getPaginationData(formattedDatePayload)?.nextPageResults;
|
||||||
|
|
||||||
const totalIssues = issueIdList?.issueCount ?? 0;
|
const shouldLoadMore =
|
||||||
|
nextPageResults === undefined && dayIssueCount !== undefined ? issueIds?.length < dayIssueCount : !!nextPageResults;
|
||||||
|
|
||||||
const isToday = date.date.toDateString() === new Date().toDateString();
|
const isToday = date.date.toDateString() === new Date().toDateString();
|
||||||
|
|
||||||
@ -101,7 +108,7 @@ export const CalendarDayTile: React.FC<Props> = observer((props) => {
|
|||||||
>
|
>
|
||||||
<CalendarIssueBlocks
|
<CalendarIssueBlocks
|
||||||
issues={issues}
|
issues={issues}
|
||||||
issueIdList={issueIdList?.issueIds ?? []}
|
issueIdList={issueIds ?? []}
|
||||||
quickActions={quickActions}
|
quickActions={quickActions}
|
||||||
isDragDisabled={readOnly}
|
isDragDisabled={readOnly}
|
||||||
/>
|
/>
|
||||||
@ -121,12 +128,12 @@ export const CalendarDayTile: React.FC<Props> = observer((props) => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{totalIssues > (issueIdList?.issueIds?.length ?? 0) && (
|
{shouldLoadMore && (
|
||||||
<div className="flex items-center px-2.5 py-1">
|
<div className="flex items-center px-2.5 py-1">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="w-min whitespace-nowrap rounded text-xs px-1.5 py-1 text-custom-text-400 font-medium hover:bg-custom-background-80 hover:text-custom-text-300"
|
className="w-min whitespace-nowrap rounded text-xs px-1.5 py-1 text-custom-text-400 font-medium hover:bg-custom-background-80 hover:text-custom-text-300"
|
||||||
onClick={loadMoreIssues}
|
onClick={() => loadMoreIssues(formattedDatePayload)}
|
||||||
>
|
>
|
||||||
Load More
|
Load More
|
||||||
</button>
|
</button>
|
||||||
|
@ -8,7 +8,7 @@ import { ICycleIssuesFilter } from "store/issue/cycle";
|
|||||||
import { IModuleIssuesFilter } from "store/issue/module";
|
import { IModuleIssuesFilter } from "store/issue/module";
|
||||||
import { IProjectIssuesFilter } from "store/issue/project";
|
import { IProjectIssuesFilter } from "store/issue/project";
|
||||||
import { IProjectViewIssuesFilter } from "store/issue/project-views";
|
import { IProjectViewIssuesFilter } from "store/issue/project-views";
|
||||||
import { TGroupedIssues, TIssue, TIssueMap } from "@plane/types";
|
import { TGroupedIssues, TIssue, TIssueMap, TPaginationData } from "@plane/types";
|
||||||
import { ICalendarDate, ICalendarWeek } from "./types";
|
import { ICalendarDate, ICalendarWeek } from "./types";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@ -17,7 +17,9 @@ type Props = {
|
|||||||
groupedIssueIds: TGroupedIssues;
|
groupedIssueIds: TGroupedIssues;
|
||||||
week: ICalendarWeek | undefined;
|
week: ICalendarWeek | undefined;
|
||||||
quickActions: (issue: TIssue, customActionButton?: React.ReactElement) => React.ReactNode;
|
quickActions: (issue: TIssue, customActionButton?: React.ReactElement) => React.ReactNode;
|
||||||
loadMoreIssues: () => void;
|
loadMoreIssues: (dateString: string) => void;
|
||||||
|
getPaginationData: (groupId: string | undefined) => TPaginationData | undefined;
|
||||||
|
getGroupIssueCount: (groupId: string | undefined) => number | undefined;
|
||||||
enableQuickIssueCreate?: boolean;
|
enableQuickIssueCreate?: boolean;
|
||||||
disableIssueCreation?: boolean;
|
disableIssueCreation?: boolean;
|
||||||
quickAddCallback?: (
|
quickAddCallback?: (
|
||||||
@ -38,6 +40,8 @@ export const CalendarWeekDays: React.FC<Props> = observer((props) => {
|
|||||||
groupedIssueIds,
|
groupedIssueIds,
|
||||||
week,
|
week,
|
||||||
loadMoreIssues,
|
loadMoreIssues,
|
||||||
|
getPaginationData,
|
||||||
|
getGroupIssueCount,
|
||||||
quickActions,
|
quickActions,
|
||||||
enableQuickIssueCreate,
|
enableQuickIssueCreate,
|
||||||
disableIssueCreation,
|
disableIssueCreation,
|
||||||
@ -69,6 +73,8 @@ export const CalendarWeekDays: React.FC<Props> = observer((props) => {
|
|||||||
issues={issues}
|
issues={issues}
|
||||||
groupedIssueIds={groupedIssueIds}
|
groupedIssueIds={groupedIssueIds}
|
||||||
loadMoreIssues={loadMoreIssues}
|
loadMoreIssues={loadMoreIssues}
|
||||||
|
getPaginationData={getPaginationData}
|
||||||
|
getGroupIssueCount={getGroupIssueCount}
|
||||||
quickActions={quickActions}
|
quickActions={quickActions}
|
||||||
enableQuickIssueCreate={enableQuickIssueCreate}
|
enableQuickIssueCreate={enableQuickIssueCreate}
|
||||||
disableIssueCreation={disableIssueCreation}
|
disableIssueCreation={disableIssueCreation}
|
||||||
|
@ -1,20 +1,23 @@
|
|||||||
import React from "react";
|
import React, { useCallback } from "react";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
// hooks
|
// hooks
|
||||||
import { GanttChartRoot, IBlockUpdateData, IssueGanttSidebar } from "components/gantt-chart";
|
import { ChartDataType, GanttChartRoot, IBlockUpdateData, IssueGanttSidebar } from "components/gantt-chart";
|
||||||
import { GanttQuickAddIssueForm, IssueGanttBlock } from "components/issues";
|
import { GanttQuickAddIssueForm, IssueGanttBlock } from "components/issues";
|
||||||
import { EUserProjectRoles } from "constants/project";
|
import { EUserProjectRoles } from "constants/project";
|
||||||
import { renderIssueBlocksStructure } from "helpers/issue.helper";
|
import { getIssueBlocksStructure } from "helpers/issue.helper";
|
||||||
import { useIssues, useUser } from "hooks/store";
|
import { useIssues, useUser } from "hooks/store";
|
||||||
import { useIssuesActions } from "hooks/use-issues-actions";
|
import { useIssuesActions } from "hooks/use-issues-actions";
|
||||||
// components
|
// components
|
||||||
// helpers
|
// helpers
|
||||||
// types
|
// types
|
||||||
import { TIssue, TUnGroupedIssues } from "@plane/types";
|
import { TIssue } from "@plane/types";
|
||||||
// constants
|
// constants
|
||||||
import { EIssueLayoutTypes, EIssuesStoreType } from "constants/issue";
|
import { EIssueLayoutTypes, EIssuesStoreType } from "constants/issue";
|
||||||
import { IssueLayoutHOC } from "../issue-layout-HOC";
|
import { IssueLayoutHOC } from "../issue-layout-HOC";
|
||||||
|
import useSWR from "swr";
|
||||||
|
import { getMonthChartItemPositionWidthInMonth } from "components/gantt-chart/views";
|
||||||
|
import { ALL_ISSUES } from "store/issue/helpers/base-issues.store";
|
||||||
|
|
||||||
type GanttStoreType =
|
type GanttStoreType =
|
||||||
| EIssuesStoreType.PROJECT
|
| EIssuesStoreType.PROJECT
|
||||||
@ -32,19 +35,42 @@ export const BaseGanttRoot: React.FC<IBaseGanttRoot> = observer((props: IBaseGan
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug } = router.query;
|
const { workspaceSlug } = router.query;
|
||||||
|
|
||||||
const { issues, issuesFilter } = useIssues(storeType);
|
const { issues, issuesFilter, issueMap } = useIssues(storeType);
|
||||||
const { updateIssue } = useIssuesActions(storeType);
|
const { fetchIssues, fetchNextIssues, updateIssue } = useIssuesActions(storeType);
|
||||||
// store hooks
|
// store hooks
|
||||||
const {
|
const {
|
||||||
membership: { currentProjectRole },
|
membership: { currentProjectRole },
|
||||||
} = useUser();
|
} = useUser();
|
||||||
const { issueMap } = useIssues();
|
|
||||||
const appliedDisplayFilters = issuesFilter.issueFilters?.displayFilters;
|
const appliedDisplayFilters = issuesFilter.issueFilters?.displayFilters;
|
||||||
|
|
||||||
const issueIds = (issues.groupedIssueIds ?? []) as TUnGroupedIssues;
|
useSWR(`ISSUE_GANTT_LAYOUT_${storeType}`, () => fetchIssues("init-loader", { canGroup: false, perPageCount: 100 }), {
|
||||||
|
revalidateOnFocus: false,
|
||||||
|
revalidateOnReconnect: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const issuesIds = (issues.groupedIssueIds?.[ALL_ISSUES] as string[]) ?? [];
|
||||||
|
const nextPageResults = issues.getPaginationData(ALL_ISSUES)?.nextPageResults;
|
||||||
|
|
||||||
const { enableIssueCreation } = issues?.viewFlags || {};
|
const { enableIssueCreation } = issues?.viewFlags || {};
|
||||||
|
|
||||||
const issuesArray = issueIds.map((id) => issueMap?.[id]);
|
const loadMoreIssues = useCallback(() => {
|
||||||
|
fetchNextIssues();
|
||||||
|
}, [fetchNextIssues]);
|
||||||
|
|
||||||
|
const getBlockById = useCallback(
|
||||||
|
(id: string, currentViewData?: ChartDataType | undefined) => {
|
||||||
|
const issue = issueMap[id];
|
||||||
|
const block = getIssueBlocksStructure(issue);
|
||||||
|
if (currentViewData) {
|
||||||
|
return {
|
||||||
|
...block,
|
||||||
|
position: getMonthChartItemPositionWidthInMonth(currentViewData, block),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return block;
|
||||||
|
},
|
||||||
|
[issueMap]
|
||||||
|
);
|
||||||
|
|
||||||
const updateIssueBlockStructure = async (issue: TIssue, data: IBlockUpdateData) => {
|
const updateIssueBlockStructure = async (issue: TIssue, data: IBlockUpdateData) => {
|
||||||
if (!workspaceSlug) return;
|
if (!workspaceSlug) return;
|
||||||
@ -64,7 +90,8 @@ export const BaseGanttRoot: React.FC<IBaseGanttRoot> = observer((props: IBaseGan
|
|||||||
border={false}
|
border={false}
|
||||||
title="Issues"
|
title="Issues"
|
||||||
loaderTitle="Issues"
|
loaderTitle="Issues"
|
||||||
blocks={issues ? renderIssueBlocksStructure(issuesArray) : null}
|
blockIds={issuesIds}
|
||||||
|
getBlockById={getBlockById}
|
||||||
blockUpdateHandler={updateIssueBlockStructure}
|
blockUpdateHandler={updateIssueBlockStructure}
|
||||||
blockToRender={(data: TIssue) => <IssueGanttBlock issueId={data.id} />}
|
blockToRender={(data: TIssue) => <IssueGanttBlock issueId={data.id} />}
|
||||||
sidebarToRender={(props) => <IssueGanttSidebar {...props} showAllBlocks />}
|
sidebarToRender={(props) => <IssueGanttSidebar {...props} showAllBlocks />}
|
||||||
@ -78,6 +105,8 @@ export const BaseGanttRoot: React.FC<IBaseGanttRoot> = observer((props: IBaseGan
|
|||||||
<GanttQuickAddIssueForm quickAddCallback={issues.quickAddIssue} viewId={viewId} />
|
<GanttQuickAddIssueForm quickAddCallback={issues.quickAddIssue} viewId={viewId} />
|
||||||
) : undefined
|
) : undefined
|
||||||
}
|
}
|
||||||
|
loadMoreBlocks={loadMoreIssues}
|
||||||
|
canLoadMoreBlocks={nextPageResults}
|
||||||
showAllBlocks
|
showAllBlocks
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -9,6 +9,7 @@ import { EIssueLayoutTypes, EIssuesStoreType } from "constants/issue";
|
|||||||
import { useIssues } from "hooks/store";
|
import { useIssues } from "hooks/store";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { IssueLayoutEmptyState } from "./empty-states";
|
import { IssueLayoutEmptyState } from "./empty-states";
|
||||||
|
import { ALL_ISSUES } from "store/issue/helpers/base-issues.store";
|
||||||
|
|
||||||
const ActiveLoader = (props: { layout: EIssueLayoutTypes }) => {
|
const ActiveLoader = (props: { layout: EIssueLayoutTypes }) => {
|
||||||
const { layout } = props;
|
const { layout } = props;
|
||||||
@ -43,7 +44,7 @@ export const IssueLayoutHOC = observer((props: Props) => {
|
|||||||
return <ActiveLoader layout={layout} />;
|
return <ActiveLoader layout={layout} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (issues.issueCount === 0) {
|
if (issues.getGroupIssueCount(ALL_ISSUES) === 0) {
|
||||||
return <IssueLayoutEmptyState storeType={storeType} />;
|
return <IssueLayoutEmptyState storeType={storeType} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,27 +75,38 @@ export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBas
|
|||||||
updateFilters,
|
updateFilters,
|
||||||
} = useIssuesActions(storeType);
|
} = useIssuesActions(storeType);
|
||||||
|
|
||||||
useSWR(`ISSUE_KANBAN_LAYOUT_${storeType}`, () => fetchIssues("init-loader", { canGroup: true, perPageCount: 30 }), {
|
|
||||||
revalidateOnFocus: false,
|
|
||||||
revalidateOnReconnect: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
const fetchMoreIssues = useCallback(() => {
|
|
||||||
if (issues.loader !== "pagination") {
|
|
||||||
fetchNextIssues();
|
|
||||||
}
|
|
||||||
}, [fetchNextIssues]);
|
|
||||||
|
|
||||||
const debouncedFetchMoreIssues = debounce(() => fetchMoreIssues(), 300, { leading: true, trailing: false });
|
|
||||||
|
|
||||||
const issueIds = issues?.groupedIssueIds;
|
|
||||||
|
|
||||||
const displayFilters = issuesFilter?.issueFilters?.displayFilters;
|
const displayFilters = issuesFilter?.issueFilters?.displayFilters;
|
||||||
const displayProperties = issuesFilter?.issueFilters?.displayProperties;
|
const displayProperties = issuesFilter?.issueFilters?.displayProperties;
|
||||||
|
|
||||||
const sub_group_by: string | null = displayFilters?.sub_group_by || null;
|
const sub_group_by: string | null = displayFilters?.sub_group_by || null;
|
||||||
const group_by: string | null = displayFilters?.group_by || null;
|
const group_by: string | null = displayFilters?.group_by || null;
|
||||||
|
|
||||||
|
useSWR(
|
||||||
|
`ISSUE_KANBAN_LAYOUT_${storeType}_${group_by}_${sub_group_by}`,
|
||||||
|
() => fetchIssues("init-loader", { canGroup: true, perPageCount: 30 }),
|
||||||
|
{
|
||||||
|
revalidateOnFocus: false,
|
||||||
|
revalidateOnReconnect: false,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const fetchMoreIssues = useCallback(
|
||||||
|
(groupId?: string, subgroupId?: string) => {
|
||||||
|
if (issues.loader !== "pagination") {
|
||||||
|
fetchNextIssues(groupId, subgroupId);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[fetchNextIssues]
|
||||||
|
);
|
||||||
|
|
||||||
|
const debouncedFetchMoreIssues = debounce(
|
||||||
|
(groupId?: string, subgroupId?: string) => fetchMoreIssues(groupId, subgroupId),
|
||||||
|
300,
|
||||||
|
{ leading: true, trailing: false }
|
||||||
|
);
|
||||||
|
|
||||||
|
const groupedIssueIds = issues?.groupedIssueIds;
|
||||||
|
|
||||||
const userDisplayFilters = displayFilters || null;
|
const userDisplayFilters = displayFilters || null;
|
||||||
|
|
||||||
const KanBanView = sub_group_by ? KanBanSwimLanes : KanBan;
|
const KanBanView = sub_group_by ? KanBanSwimLanes : KanBan;
|
||||||
@ -160,7 +171,7 @@ export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBas
|
|||||||
sub_group_by,
|
sub_group_by,
|
||||||
group_by,
|
group_by,
|
||||||
issueMap,
|
issueMap,
|
||||||
issueIds,
|
groupedIssueIds,
|
||||||
updateIssue,
|
updateIssue,
|
||||||
removeIssue
|
removeIssue
|
||||||
).catch((err) => {
|
).catch((err) => {
|
||||||
@ -201,7 +212,7 @@ export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBas
|
|||||||
sub_group_by,
|
sub_group_by,
|
||||||
group_by,
|
group_by,
|
||||||
issueMap,
|
issueMap,
|
||||||
issueIds,
|
groupedIssueIds,
|
||||||
updateIssue,
|
updateIssue,
|
||||||
removeIssue
|
removeIssue
|
||||||
).finally(() => {
|
).finally(() => {
|
||||||
@ -271,7 +282,8 @@ export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBas
|
|||||||
<div className="h-max w-max">
|
<div className="h-max w-max">
|
||||||
<KanBanView
|
<KanBanView
|
||||||
issuesMap={issueMap}
|
issuesMap={issueMap}
|
||||||
issueIds={issueIds!}
|
groupedIssueIds={groupedIssueIds ?? {}}
|
||||||
|
getGroupIssueCount={issues.getGroupIssueCount}
|
||||||
displayProperties={displayProperties}
|
displayProperties={displayProperties}
|
||||||
sub_group_by={sub_group_by}
|
sub_group_by={sub_group_by}
|
||||||
group_by={group_by}
|
group_by={group_by}
|
||||||
@ -282,6 +294,7 @@ export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBas
|
|||||||
enableQuickIssueCreate={enableQuickAdd}
|
enableQuickIssueCreate={enableQuickAdd}
|
||||||
showEmptyGroup={userDisplayFilters?.show_empty_groups ?? true}
|
showEmptyGroup={userDisplayFilters?.show_empty_groups ?? true}
|
||||||
quickAddCallback={issues?.quickAddIssue}
|
quickAddCallback={issues?.quickAddIssue}
|
||||||
|
getPaginationData={issues.getPaginationData}
|
||||||
viewId={viewId}
|
viewId={viewId}
|
||||||
disableIssueCreation={!enableIssueCreation || !isEditingAllowed || isCompletedCycle}
|
disableIssueCreation={!enableIssueCreation || !isEditingAllowed || isCompletedCycle}
|
||||||
canEditProperties={canEditProperties}
|
canEditProperties={canEditProperties}
|
||||||
|
@ -22,7 +22,9 @@ interface IssueBlockProps {
|
|||||||
isDragDisabled: boolean;
|
isDragDisabled: boolean;
|
||||||
draggableId: string;
|
draggableId: string;
|
||||||
index: number;
|
index: number;
|
||||||
updateIssue: ((projectId: string, issueId: string, data: Partial<TIssue>) => Promise<void>) | undefined;
|
updateIssue:
|
||||||
|
| ((projectId: string | null | undefined, issueId: string, data: Partial<TIssue>) => Promise<void>)
|
||||||
|
| undefined;
|
||||||
quickActions: (issue: TIssue) => React.ReactNode;
|
quickActions: (issue: TIssue) => React.ReactNode;
|
||||||
canEditProperties: (projectId: string | undefined) => boolean;
|
canEditProperties: (projectId: string | undefined) => boolean;
|
||||||
scrollableContainerRef?: MutableRefObject<HTMLDivElement | null>;
|
scrollableContainerRef?: MutableRefObject<HTMLDivElement | null>;
|
||||||
@ -33,7 +35,9 @@ interface IssueBlockProps {
|
|||||||
interface IssueDetailsBlockProps {
|
interface IssueDetailsBlockProps {
|
||||||
issue: TIssue;
|
issue: TIssue;
|
||||||
displayProperties: IIssueDisplayProperties | undefined;
|
displayProperties: IIssueDisplayProperties | undefined;
|
||||||
updateIssue: ((projectId: string, issueId: string, data: Partial<TIssue>) => Promise<void>) | undefined;
|
updateIssue:
|
||||||
|
| ((projectId: string | null | undefined, issueId: string, data: Partial<TIssue>) => Promise<void>)
|
||||||
|
| undefined;
|
||||||
quickActions: (issue: TIssue) => React.ReactNode;
|
quickActions: (issue: TIssue) => React.ReactNode;
|
||||||
isReadOnly: boolean;
|
isReadOnly: boolean;
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,9 @@ interface IssueBlocksListProps {
|
|||||||
issueIds: string[];
|
issueIds: string[];
|
||||||
displayProperties: IIssueDisplayProperties | undefined;
|
displayProperties: IIssueDisplayProperties | undefined;
|
||||||
isDragDisabled: boolean;
|
isDragDisabled: boolean;
|
||||||
updateIssue: ((projectId: string, issueId: string, data: Partial<TIssue>) => Promise<void>) | undefined;
|
updateIssue:
|
||||||
|
| ((projectId: string | null | undefined, issueId: string, data: Partial<TIssue>) => Promise<void>)
|
||||||
|
| undefined;
|
||||||
quickActions: (issue: TIssue, customActionButton?: React.ReactElement) => React.ReactNode;
|
quickActions: (issue: TIssue, customActionButton?: React.ReactElement) => React.ReactNode;
|
||||||
canEditProperties: (projectId: string | undefined) => boolean;
|
canEditProperties: (projectId: string | undefined) => boolean;
|
||||||
scrollableContainerRef?: MutableRefObject<HTMLDivElement | null>;
|
scrollableContainerRef?: MutableRefObject<HTMLDivElement | null>;
|
||||||
|
@ -21,8 +21,8 @@ import {
|
|||||||
IIssueDisplayProperties,
|
IIssueDisplayProperties,
|
||||||
IIssueMap,
|
IIssueMap,
|
||||||
TSubGroupedIssues,
|
TSubGroupedIssues,
|
||||||
TUnGroupedIssues,
|
|
||||||
TIssueKanbanFilters,
|
TIssueKanbanFilters,
|
||||||
|
TPaginationData,
|
||||||
} from "@plane/types";
|
} from "@plane/types";
|
||||||
// parent components
|
// parent components
|
||||||
import { getGroupByColumns } from "../utils";
|
import { getGroupByColumns } from "../utils";
|
||||||
@ -33,17 +33,21 @@ import { KanbanStoreType } from "./base-kanban-root";
|
|||||||
|
|
||||||
export interface IGroupByKanBan {
|
export interface IGroupByKanBan {
|
||||||
issuesMap: IIssueMap;
|
issuesMap: IIssueMap;
|
||||||
issueIds: TGroupedIssues | TSubGroupedIssues | TUnGroupedIssues;
|
groupedIssueIds: TGroupedIssues | TSubGroupedIssues;
|
||||||
|
getGroupIssueCount: (groupId: string | undefined) => number | undefined;
|
||||||
|
getPaginationData: (groupId: string | undefined) => TPaginationData | undefined;
|
||||||
displayProperties: IIssueDisplayProperties | undefined;
|
displayProperties: IIssueDisplayProperties | undefined;
|
||||||
sub_group_by: string | null;
|
sub_group_by: string | null;
|
||||||
group_by: string | null;
|
group_by: string | null;
|
||||||
sub_group_id: string;
|
sub_group_id: string;
|
||||||
isDragDisabled: boolean;
|
isDragDisabled: boolean;
|
||||||
updateIssue: ((projectId: string, issueId: string, data: Partial<TIssue>) => Promise<void>) | undefined;
|
updateIssue:
|
||||||
|
| ((projectId: string | null | undefined, issueId: string, data: Partial<TIssue>) => Promise<void>)
|
||||||
|
| undefined;
|
||||||
quickActions: (issue: TIssue, customActionButton?: React.ReactElement) => React.ReactNode;
|
quickActions: (issue: TIssue, customActionButton?: React.ReactElement) => React.ReactNode;
|
||||||
kanbanFilters: TIssueKanbanFilters;
|
kanbanFilters: TIssueKanbanFilters;
|
||||||
handleKanbanFilters: any;
|
handleKanbanFilters: any;
|
||||||
loadMoreIssues: (() => void) | undefined;
|
loadMoreIssues: (groupId?: string, subGroupId?: string) => void;
|
||||||
enableQuickIssueCreate?: boolean;
|
enableQuickIssueCreate?: boolean;
|
||||||
quickAddCallback?: (
|
quickAddCallback?: (
|
||||||
workspaceSlug: string,
|
workspaceSlug: string,
|
||||||
@ -64,7 +68,9 @@ export interface IGroupByKanBan {
|
|||||||
const GroupByKanBan: React.FC<IGroupByKanBan> = observer((props) => {
|
const GroupByKanBan: React.FC<IGroupByKanBan> = observer((props) => {
|
||||||
const {
|
const {
|
||||||
issuesMap,
|
issuesMap,
|
||||||
issueIds,
|
groupedIssueIds,
|
||||||
|
getGroupIssueCount,
|
||||||
|
getPaginationData,
|
||||||
displayProperties,
|
displayProperties,
|
||||||
sub_group_by,
|
sub_group_by,
|
||||||
group_by,
|
group_by,
|
||||||
@ -107,7 +113,7 @@ const GroupByKanBan: React.FC<IGroupByKanBan> = observer((props) => {
|
|||||||
|
|
||||||
if (!list) return null;
|
if (!list) return null;
|
||||||
|
|
||||||
const groupWithIssues = list.filter((_list) => (issueIds as TGroupedIssues)?.[_list.id]?.issueCount > 0);
|
const groupWithIssues = list.filter((_list) => (getGroupIssueCount(_list.id) ?? 0) > 0);
|
||||||
|
|
||||||
const groupList = showEmptyGroup ? list : groupWithIssues;
|
const groupList = showEmptyGroup ? list : groupWithIssues;
|
||||||
|
|
||||||
@ -143,7 +149,7 @@ const GroupByKanBan: React.FC<IGroupByKanBan> = observer((props) => {
|
|||||||
column_id={_list.id}
|
column_id={_list.id}
|
||||||
icon={_list.icon}
|
icon={_list.icon}
|
||||||
title={_list.name}
|
title={_list.name}
|
||||||
count={(issueIds as TGroupedIssues)?.[_list.id]?.issueCount || 0}
|
count={getGroupIssueCount(_list.id) ?? 0}
|
||||||
issuePayload={_list.payload}
|
issuePayload={_list.payload}
|
||||||
disableIssueCreation={disableIssueCreation || isGroupByCreatedBy}
|
disableIssueCreation={disableIssueCreation || isGroupByCreatedBy}
|
||||||
storeType={storeType}
|
storeType={storeType}
|
||||||
@ -158,7 +164,9 @@ const GroupByKanBan: React.FC<IGroupByKanBan> = observer((props) => {
|
|||||||
<KanbanGroup
|
<KanbanGroup
|
||||||
groupId={_list.id}
|
groupId={_list.id}
|
||||||
issuesMap={issuesMap}
|
issuesMap={issuesMap}
|
||||||
issueIds={issueIds}
|
groupedIssueIds={groupedIssueIds}
|
||||||
|
getGroupIssueCount={getGroupIssueCount}
|
||||||
|
getPaginationData={getPaginationData}
|
||||||
peekIssueId={peekIssue?.issueId ?? ""}
|
peekIssueId={peekIssue?.issueId ?? ""}
|
||||||
displayProperties={displayProperties}
|
displayProperties={displayProperties}
|
||||||
sub_group_by={sub_group_by}
|
sub_group_by={sub_group_by}
|
||||||
@ -187,16 +195,20 @@ const GroupByKanBan: React.FC<IGroupByKanBan> = observer((props) => {
|
|||||||
|
|
||||||
export interface IKanBan {
|
export interface IKanBan {
|
||||||
issuesMap: IIssueMap;
|
issuesMap: IIssueMap;
|
||||||
issueIds: TGroupedIssues | TSubGroupedIssues;
|
groupedIssueIds: TGroupedIssues | TSubGroupedIssues;
|
||||||
|
getGroupIssueCount: (groupId: string | undefined) => number | undefined;
|
||||||
|
getPaginationData: (groupId: string | undefined) => TPaginationData | undefined;
|
||||||
displayProperties: IIssueDisplayProperties | undefined;
|
displayProperties: IIssueDisplayProperties | undefined;
|
||||||
sub_group_by: string | null;
|
sub_group_by: string | null;
|
||||||
group_by: string | null;
|
group_by: string | null;
|
||||||
sub_group_id?: string;
|
sub_group_id?: string;
|
||||||
updateIssue: ((projectId: string, issueId: string, data: Partial<TIssue>) => Promise<void>) | undefined;
|
updateIssue:
|
||||||
|
| ((projectId: string | null | undefined, issueId: string, data: Partial<TIssue>) => Promise<void>)
|
||||||
|
| undefined;
|
||||||
quickActions: (issue: TIssue, customActionButton?: React.ReactElement) => React.ReactNode;
|
quickActions: (issue: TIssue, customActionButton?: React.ReactElement) => React.ReactNode;
|
||||||
kanbanFilters: TIssueKanbanFilters;
|
kanbanFilters: TIssueKanbanFilters;
|
||||||
handleKanbanFilters: (toggle: "group_by" | "sub_group_by", value: string) => void;
|
handleKanbanFilters: (toggle: "group_by" | "sub_group_by", value: string) => void;
|
||||||
loadMoreIssues: (() => void) | undefined;
|
loadMoreIssues: (groupId?: string, subGroupId?: string) => void;
|
||||||
showEmptyGroup: boolean;
|
showEmptyGroup: boolean;
|
||||||
enableQuickIssueCreate?: boolean;
|
enableQuickIssueCreate?: boolean;
|
||||||
quickAddCallback?: (
|
quickAddCallback?: (
|
||||||
@ -217,7 +229,9 @@ export interface IKanBan {
|
|||||||
export const KanBan: React.FC<IKanBan> = observer((props) => {
|
export const KanBan: React.FC<IKanBan> = observer((props) => {
|
||||||
const {
|
const {
|
||||||
issuesMap,
|
issuesMap,
|
||||||
issueIds,
|
groupedIssueIds,
|
||||||
|
getGroupIssueCount,
|
||||||
|
getPaginationData,
|
||||||
displayProperties,
|
displayProperties,
|
||||||
sub_group_by,
|
sub_group_by,
|
||||||
group_by,
|
group_by,
|
||||||
@ -244,7 +258,9 @@ export const KanBan: React.FC<IKanBan> = observer((props) => {
|
|||||||
return (
|
return (
|
||||||
<GroupByKanBan
|
<GroupByKanBan
|
||||||
issuesMap={issuesMap}
|
issuesMap={issuesMap}
|
||||||
issueIds={issueIds}
|
groupedIssueIds={groupedIssueIds}
|
||||||
|
getGroupIssueCount={getGroupIssueCount}
|
||||||
|
getPaginationData={getPaginationData}
|
||||||
displayProperties={displayProperties}
|
displayProperties={displayProperties}
|
||||||
group_by={group_by}
|
group_by={group_by}
|
||||||
sub_group_by={sub_group_by}
|
sub_group_by={sub_group_by}
|
||||||
|
@ -10,23 +10,28 @@ import {
|
|||||||
IIssueDisplayProperties,
|
IIssueDisplayProperties,
|
||||||
IIssueMap,
|
IIssueMap,
|
||||||
TSubGroupedIssues,
|
TSubGroupedIssues,
|
||||||
TUnGroupedIssues,
|
TPaginationData,
|
||||||
} from "@plane/types";
|
} from "@plane/types";
|
||||||
import { KanbanIssueBlocksList, KanBanQuickAddIssueForm } from ".";
|
import { KanbanIssueBlocksList, KanBanQuickAddIssueForm } from ".";
|
||||||
import { KanbanIssueBlockLoader } from "components/ui/loader";
|
import { KanbanIssueBlockLoader } from "components/ui/loader";
|
||||||
import { useIntersectionObserver } from "hooks/use-intersection-observer";
|
import { useIntersectionObserver } from "hooks/use-intersection-observer";
|
||||||
|
import { observer } from "mobx-react";
|
||||||
|
|
||||||
interface IKanbanGroup {
|
interface IKanbanGroup {
|
||||||
groupId: string;
|
groupId: string;
|
||||||
issuesMap: IIssueMap;
|
issuesMap: IIssueMap;
|
||||||
peekIssueId?: string;
|
peekIssueId?: string;
|
||||||
issueIds: TGroupedIssues | TSubGroupedIssues | TUnGroupedIssues;
|
groupedIssueIds: TGroupedIssues | TSubGroupedIssues;
|
||||||
|
getGroupIssueCount: (groupId: string | undefined) => number | undefined;
|
||||||
|
getPaginationData: (groupId: string | undefined) => TPaginationData | undefined;
|
||||||
displayProperties: IIssueDisplayProperties | undefined;
|
displayProperties: IIssueDisplayProperties | undefined;
|
||||||
sub_group_by: string | null;
|
sub_group_by: string | null;
|
||||||
group_by: string | null;
|
group_by: string | null;
|
||||||
sub_group_id: string;
|
sub_group_id: string;
|
||||||
isDragDisabled: boolean;
|
isDragDisabled: boolean;
|
||||||
updateIssue: ((projectId: string, issueId: string, data: Partial<TIssue>) => Promise<void>) | undefined;
|
updateIssue:
|
||||||
|
| ((projectId: string | null | undefined, issueId: string, data: Partial<TIssue>) => Promise<void>)
|
||||||
|
| undefined;
|
||||||
quickActions: (issue: TIssue, customActionButton?: React.ReactElement) => React.ReactNode;
|
quickActions: (issue: TIssue, customActionButton?: React.ReactElement) => React.ReactNode;
|
||||||
enableQuickIssueCreate?: boolean;
|
enableQuickIssueCreate?: boolean;
|
||||||
quickAddCallback?: (
|
quickAddCallback?: (
|
||||||
@ -35,7 +40,7 @@ interface IKanbanGroup {
|
|||||||
data: TIssue,
|
data: TIssue,
|
||||||
viewId?: string
|
viewId?: string
|
||||||
) => Promise<TIssue | undefined>;
|
) => Promise<TIssue | undefined>;
|
||||||
loadMoreIssues: (() => void) | undefined;
|
loadMoreIssues: (groupId?: string, subGroupId?: string) => void;
|
||||||
viewId?: string;
|
viewId?: string;
|
||||||
disableIssueCreation?: boolean;
|
disableIssueCreation?: boolean;
|
||||||
canEditProperties: (projectId: string | undefined) => boolean;
|
canEditProperties: (projectId: string | undefined) => boolean;
|
||||||
@ -44,7 +49,7 @@ interface IKanbanGroup {
|
|||||||
isDragStarted?: boolean;
|
isDragStarted?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const KanbanGroup = (props: IKanbanGroup) => {
|
export const KanbanGroup = observer((props: IKanbanGroup) => {
|
||||||
const {
|
const {
|
||||||
groupId,
|
groupId,
|
||||||
sub_group_id,
|
sub_group_id,
|
||||||
@ -52,7 +57,9 @@ export const KanbanGroup = (props: IKanbanGroup) => {
|
|||||||
sub_group_by,
|
sub_group_by,
|
||||||
issuesMap,
|
issuesMap,
|
||||||
displayProperties,
|
displayProperties,
|
||||||
issueIds,
|
groupedIssueIds,
|
||||||
|
getGroupIssueCount,
|
||||||
|
getPaginationData,
|
||||||
peekIssueId,
|
peekIssueId,
|
||||||
isDragDisabled,
|
isDragDisabled,
|
||||||
updateIssue,
|
updateIssue,
|
||||||
@ -125,6 +132,23 @@ export const KanbanGroup = (props: IKanbanGroup) => {
|
|||||||
return preloadedData;
|
return preloadedData;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const isSubGroup = !!sub_group_id && sub_group_id !== "null";
|
||||||
|
|
||||||
|
const issueIds = isSubGroup
|
||||||
|
? (groupedIssueIds as TSubGroupedIssues)[groupId][sub_group_id]
|
||||||
|
: (groupedIssueIds as TGroupedIssues)[groupId];
|
||||||
|
|
||||||
|
const groupIssueCount = isSubGroup ? getGroupIssueCount(sub_group_id) : getGroupIssueCount(groupId);
|
||||||
|
|
||||||
|
const nextPageResults = isSubGroup
|
||||||
|
? getPaginationData(sub_group_id)?.nextPageResults
|
||||||
|
: getPaginationData(groupId)?.nextPageResults;
|
||||||
|
|
||||||
|
const shouldLoadMore =
|
||||||
|
nextPageResults === undefined && groupIssueCount !== undefined
|
||||||
|
? issueIds?.length < groupIssueCount
|
||||||
|
: !!nextPageResults;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`relative w-full h-full transition-all`}>
|
<div className={`relative w-full h-full transition-all`}>
|
||||||
<Droppable droppableId={`${groupId}__${sub_group_id}`}>
|
<Droppable droppableId={`${groupId}__${sub_group_id}`}>
|
||||||
@ -139,7 +163,7 @@ export const KanbanGroup = (props: IKanbanGroup) => {
|
|||||||
columnId={groupId}
|
columnId={groupId}
|
||||||
issuesMap={issuesMap}
|
issuesMap={issuesMap}
|
||||||
peekIssueId={peekIssueId}
|
peekIssueId={peekIssueId}
|
||||||
issueIds={(issueIds as TGroupedIssues)?.[groupId]?.issueIds || []}
|
issueIds={issueIds}
|
||||||
displayProperties={displayProperties}
|
displayProperties={displayProperties}
|
||||||
isDragDisabled={isDragDisabled}
|
isDragDisabled={isDragDisabled}
|
||||||
updateIssue={updateIssue}
|
updateIssue={updateIssue}
|
||||||
@ -151,7 +175,17 @@ export const KanbanGroup = (props: IKanbanGroup) => {
|
|||||||
|
|
||||||
{provided.placeholder}
|
{provided.placeholder}
|
||||||
|
|
||||||
{loadMoreIssues && <KanbanIssueBlockLoader ref={intersectionRef} />}
|
{shouldLoadMore && isSubGroup ? (
|
||||||
|
<div
|
||||||
|
className="w-full sticky bottom-0 p-3 text-sm text-custom-primary-100 hover:underline cursor-pointer"
|
||||||
|
onClick={() => loadMoreIssues(groupId, sub_group_id)}
|
||||||
|
>
|
||||||
|
{" "}
|
||||||
|
Load more ↓
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<KanbanIssueBlockLoader ref={intersectionRef} />
|
||||||
|
)}
|
||||||
|
|
||||||
{enableQuickIssueCreate && !disableIssueCreation && (
|
{enableQuickIssueCreate && !disableIssueCreation && (
|
||||||
<div className="w-full bg-custom-background-90 py-0.5 sticky bottom-0">
|
<div className="w-full bg-custom-background-90 py-0.5 sticky bottom-0">
|
||||||
@ -172,4 +206,4 @@ export const KanbanGroup = (props: IKanbanGroup) => {
|
|||||||
</Droppable>
|
</Droppable>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
@ -10,8 +10,9 @@ import {
|
|||||||
IIssueDisplayProperties,
|
IIssueDisplayProperties,
|
||||||
IIssueMap,
|
IIssueMap,
|
||||||
TSubGroupedIssues,
|
TSubGroupedIssues,
|
||||||
TUnGroupedIssues,
|
|
||||||
TIssueKanbanFilters,
|
TIssueKanbanFilters,
|
||||||
|
TGroupedIssueCount,
|
||||||
|
TPaginationData,
|
||||||
} from "@plane/types";
|
} from "@plane/types";
|
||||||
import { getGroupByColumns } from "../utils";
|
import { getGroupByColumns } from "../utils";
|
||||||
import { KanBan } from "./default";
|
import { KanBan } from "./default";
|
||||||
@ -22,7 +23,7 @@ import { KanbanStoreType } from "./base-kanban-root";
|
|||||||
// constants
|
// constants
|
||||||
|
|
||||||
interface ISubGroupSwimlaneHeader {
|
interface ISubGroupSwimlaneHeader {
|
||||||
issueIds: TGroupedIssues | TSubGroupedIssues;
|
getGroupIssueCount: (groupId: string | undefined) => number | undefined;
|
||||||
sub_group_by: string | null;
|
sub_group_by: string | null;
|
||||||
group_by: string | null;
|
group_by: string | null;
|
||||||
list: IGroupByColumn[];
|
list: IGroupByColumn[];
|
||||||
@ -31,16 +32,8 @@ interface ISubGroupSwimlaneHeader {
|
|||||||
storeType: KanbanStoreType;
|
storeType: KanbanStoreType;
|
||||||
}
|
}
|
||||||
|
|
||||||
const getSubGroupHeaderIssuesCount = (issueIds: TSubGroupedIssues, groupById: string) => {
|
|
||||||
let headerCount = 0;
|
|
||||||
Object.keys(issueIds).map((groupState) => {
|
|
||||||
headerCount = headerCount + (issueIds?.[groupState]?.[groupById]?.issueCount || 0);
|
|
||||||
});
|
|
||||||
return headerCount;
|
|
||||||
};
|
|
||||||
|
|
||||||
const SubGroupSwimlaneHeader: React.FC<ISubGroupSwimlaneHeader> = ({
|
const SubGroupSwimlaneHeader: React.FC<ISubGroupSwimlaneHeader> = ({
|
||||||
issueIds,
|
getGroupIssueCount,
|
||||||
sub_group_by,
|
sub_group_by,
|
||||||
group_by,
|
group_by,
|
||||||
storeType,
|
storeType,
|
||||||
@ -59,7 +52,7 @@ const SubGroupSwimlaneHeader: React.FC<ISubGroupSwimlaneHeader> = ({
|
|||||||
column_id={_list.id}
|
column_id={_list.id}
|
||||||
icon={_list.icon}
|
icon={_list.icon}
|
||||||
title={_list.name}
|
title={_list.name}
|
||||||
count={getSubGroupHeaderIssuesCount(issueIds as TSubGroupedIssues, _list?.id)}
|
count={getGroupIssueCount(_list?.id) ?? 0}
|
||||||
kanbanFilters={kanbanFilters}
|
kanbanFilters={kanbanFilters}
|
||||||
handleKanbanFilters={handleKanbanFilters}
|
handleKanbanFilters={handleKanbanFilters}
|
||||||
issuePayload={_list.payload}
|
issuePayload={_list.payload}
|
||||||
@ -72,10 +65,14 @@ const SubGroupSwimlaneHeader: React.FC<ISubGroupSwimlaneHeader> = ({
|
|||||||
|
|
||||||
interface ISubGroupSwimlane extends ISubGroupSwimlaneHeader {
|
interface ISubGroupSwimlane extends ISubGroupSwimlaneHeader {
|
||||||
issuesMap: IIssueMap;
|
issuesMap: IIssueMap;
|
||||||
issueIds: TGroupedIssues | TSubGroupedIssues | TUnGroupedIssues;
|
groupedIssueIds: TGroupedIssues | TSubGroupedIssues;
|
||||||
|
getGroupIssueCount: (groupId: string | undefined) => number | undefined;
|
||||||
|
getPaginationData: (groupId: string | undefined) => TPaginationData | undefined;
|
||||||
showEmptyGroup: boolean;
|
showEmptyGroup: boolean;
|
||||||
displayProperties: IIssueDisplayProperties | undefined;
|
displayProperties: IIssueDisplayProperties | undefined;
|
||||||
updateIssue: ((projectId: string, issueId: string, data: Partial<TIssue>) => Promise<void>) | undefined;
|
updateIssue:
|
||||||
|
| ((projectId: string | null | undefined, issueId: string, data: Partial<TIssue>) => Promise<void>)
|
||||||
|
| undefined;
|
||||||
quickActions: (issue: TIssue, customActionButton?: React.ReactElement) => React.ReactNode;
|
quickActions: (issue: TIssue, customActionButton?: React.ReactElement) => React.ReactNode;
|
||||||
kanbanFilters: TIssueKanbanFilters;
|
kanbanFilters: TIssueKanbanFilters;
|
||||||
handleKanbanFilters: (toggle: "group_by" | "sub_group_by", value: string) => void;
|
handleKanbanFilters: (toggle: "group_by" | "sub_group_by", value: string) => void;
|
||||||
@ -93,12 +90,14 @@ interface ISubGroupSwimlane extends ISubGroupSwimlaneHeader {
|
|||||||
) => Promise<TIssue | undefined>;
|
) => Promise<TIssue | undefined>;
|
||||||
viewId?: string;
|
viewId?: string;
|
||||||
scrollableContainerRef?: MutableRefObject<HTMLDivElement | null>;
|
scrollableContainerRef?: MutableRefObject<HTMLDivElement | null>;
|
||||||
loadMoreIssues: (() => void) | undefined;
|
loadMoreIssues: (groupId?: string, subGroupId?: string) => void;
|
||||||
}
|
}
|
||||||
const SubGroupSwimlane: React.FC<ISubGroupSwimlane> = observer((props) => {
|
const SubGroupSwimlane: React.FC<ISubGroupSwimlane> = observer((props) => {
|
||||||
const {
|
const {
|
||||||
issuesMap,
|
issuesMap,
|
||||||
issueIds,
|
groupedIssueIds,
|
||||||
|
getGroupIssueCount,
|
||||||
|
getPaginationData,
|
||||||
sub_group_by,
|
sub_group_by,
|
||||||
group_by,
|
group_by,
|
||||||
list,
|
list,
|
||||||
@ -119,22 +118,12 @@ const SubGroupSwimlane: React.FC<ISubGroupSwimlane> = observer((props) => {
|
|||||||
isDragStarted,
|
isDragStarted,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const calculateIssueCount = (column_id: string) => {
|
|
||||||
let issueCount = 0;
|
|
||||||
const subGroupedIds = issueIds as TSubGroupedIssues;
|
|
||||||
subGroupedIds?.[column_id] &&
|
|
||||||
Object.keys(subGroupedIds?.[column_id])?.forEach((_list: any) => {
|
|
||||||
issueCount += subGroupedIds?.[column_id]?.[_list]?.issueCount || 0;
|
|
||||||
});
|
|
||||||
return issueCount;
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative h-max min-h-full w-full">
|
<div className="relative h-max min-h-full w-full">
|
||||||
{list &&
|
{list &&
|
||||||
list.length > 0 &&
|
list.length > 0 &&
|
||||||
list.map((_list: any, index: number) => {
|
list.map((_list: any) => {
|
||||||
const isLastSubGroup = index === list.length - 1;
|
const issueCount = getGroupIssueCount(_list.id) ?? 0;
|
||||||
return (
|
return (
|
||||||
<div key={_list.id} className="flex flex-shrink-0 flex-col">
|
<div key={_list.id} className="flex flex-shrink-0 flex-col">
|
||||||
<div className="sticky top-[50px] z-[1] flex w-full items-center bg-custom-background-90 py-1">
|
<div className="sticky top-[50px] z-[1] flex w-full items-center bg-custom-background-90 py-1">
|
||||||
@ -143,7 +132,7 @@ const SubGroupSwimlane: React.FC<ISubGroupSwimlane> = observer((props) => {
|
|||||||
column_id={_list.id}
|
column_id={_list.id}
|
||||||
icon={_list.Icon}
|
icon={_list.Icon}
|
||||||
title={_list.name || ""}
|
title={_list.name || ""}
|
||||||
count={calculateIssueCount(_list.id)}
|
count={issueCount}
|
||||||
kanbanFilters={kanbanFilters}
|
kanbanFilters={kanbanFilters}
|
||||||
handleKanbanFilters={handleKanbanFilters}
|
handleKanbanFilters={handleKanbanFilters}
|
||||||
/>
|
/>
|
||||||
@ -155,7 +144,9 @@ const SubGroupSwimlane: React.FC<ISubGroupSwimlane> = observer((props) => {
|
|||||||
<div className="relative">
|
<div className="relative">
|
||||||
<KanBan
|
<KanBan
|
||||||
issuesMap={issuesMap}
|
issuesMap={issuesMap}
|
||||||
issueIds={(issueIds as TSubGroupedIssues)?.[_list.id]}
|
groupedIssueIds={groupedIssueIds}
|
||||||
|
getGroupIssueCount={getGroupIssueCount}
|
||||||
|
getPaginationData={getPaginationData}
|
||||||
displayProperties={displayProperties}
|
displayProperties={displayProperties}
|
||||||
sub_group_by={sub_group_by}
|
sub_group_by={sub_group_by}
|
||||||
group_by={group_by}
|
group_by={group_by}
|
||||||
@ -173,7 +164,7 @@ const SubGroupSwimlane: React.FC<ISubGroupSwimlane> = observer((props) => {
|
|||||||
scrollableContainerRef={scrollableContainerRef}
|
scrollableContainerRef={scrollableContainerRef}
|
||||||
isDragStarted={isDragStarted}
|
isDragStarted={isDragStarted}
|
||||||
storeType={storeType}
|
storeType={storeType}
|
||||||
loadMoreIssues={isLastSubGroup ? loadMoreIssues : undefined}
|
loadMoreIssues={loadMoreIssues}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@ -186,15 +177,19 @@ const SubGroupSwimlane: React.FC<ISubGroupSwimlane> = observer((props) => {
|
|||||||
|
|
||||||
export interface IKanBanSwimLanes {
|
export interface IKanBanSwimLanes {
|
||||||
issuesMap: IIssueMap;
|
issuesMap: IIssueMap;
|
||||||
issueIds: TGroupedIssues | TSubGroupedIssues;
|
groupedIssueIds: TGroupedIssues | TSubGroupedIssues;
|
||||||
|
getGroupIssueCount: (groupId: string | undefined) => number | undefined;
|
||||||
|
getPaginationData: (groupId: string | undefined) => TPaginationData | undefined;
|
||||||
displayProperties: IIssueDisplayProperties | undefined;
|
displayProperties: IIssueDisplayProperties | undefined;
|
||||||
sub_group_by: string | null;
|
sub_group_by: string | null;
|
||||||
group_by: string | null;
|
group_by: string | null;
|
||||||
updateIssue: ((projectId: string, issueId: string, data: Partial<TIssue>) => Promise<void>) | undefined;
|
updateIssue:
|
||||||
|
| ((projectId: string | null | undefined, issueId: string, data: Partial<TIssue>) => Promise<void>)
|
||||||
|
| undefined;
|
||||||
quickActions: (issue: TIssue, customActionButton?: React.ReactElement) => React.ReactNode;
|
quickActions: (issue: TIssue, customActionButton?: React.ReactElement) => React.ReactNode;
|
||||||
kanbanFilters: TIssueKanbanFilters;
|
kanbanFilters: TIssueKanbanFilters;
|
||||||
handleKanbanFilters: (toggle: "group_by" | "sub_group_by", value: string) => void;
|
handleKanbanFilters: (toggle: "group_by" | "sub_group_by", value: string) => void;
|
||||||
loadMoreIssues: (() => void) | undefined;
|
loadMoreIssues: (groupId?: string, subGroupId?: string) => void;
|
||||||
showEmptyGroup: boolean;
|
showEmptyGroup: boolean;
|
||||||
isDragStarted?: boolean;
|
isDragStarted?: boolean;
|
||||||
disableIssueCreation?: boolean;
|
disableIssueCreation?: boolean;
|
||||||
@ -215,7 +210,9 @@ export interface IKanBanSwimLanes {
|
|||||||
export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => {
|
export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => {
|
||||||
const {
|
const {
|
||||||
issuesMap,
|
issuesMap,
|
||||||
issueIds,
|
groupedIssueIds,
|
||||||
|
getGroupIssueCount,
|
||||||
|
getPaginationData,
|
||||||
displayProperties,
|
displayProperties,
|
||||||
sub_group_by,
|
sub_group_by,
|
||||||
group_by,
|
group_by,
|
||||||
@ -268,7 +265,7 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => {
|
|||||||
<div className="relative">
|
<div className="relative">
|
||||||
<div className="sticky top-0 z-[2] h-[50px] bg-custom-background-90">
|
<div className="sticky top-0 z-[2] h-[50px] bg-custom-background-90">
|
||||||
<SubGroupSwimlaneHeader
|
<SubGroupSwimlaneHeader
|
||||||
issueIds={issueIds}
|
getGroupIssueCount={getGroupIssueCount}
|
||||||
group_by={group_by}
|
group_by={group_by}
|
||||||
sub_group_by={sub_group_by}
|
sub_group_by={sub_group_by}
|
||||||
kanbanFilters={kanbanFilters}
|
kanbanFilters={kanbanFilters}
|
||||||
@ -282,7 +279,9 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => {
|
|||||||
<SubGroupSwimlane
|
<SubGroupSwimlane
|
||||||
issuesMap={issuesMap}
|
issuesMap={issuesMap}
|
||||||
list={subGroupByList}
|
list={subGroupByList}
|
||||||
issueIds={issueIds}
|
groupedIssueIds={groupedIssueIds}
|
||||||
|
getPaginationData={getPaginationData}
|
||||||
|
getGroupIssueCount={getGroupIssueCount}
|
||||||
displayProperties={displayProperties}
|
displayProperties={displayProperties}
|
||||||
group_by={group_by}
|
group_by={group_by}
|
||||||
sub_group_by={sub_group_by}
|
sub_group_by={sub_group_by}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { DraggableLocation } from "@hello-pangea/dnd";
|
import { DraggableLocation } from "@hello-pangea/dnd";
|
||||||
import { TGroupedIssues, IIssueMap, TSubGroupedIssues, TUnGroupedIssues, TIssue } from "@plane/types";
|
import { TGroupedIssues, IIssueMap, TSubGroupedIssues, TIssue } from "@plane/types";
|
||||||
|
|
||||||
const handleSortOrder = (destinationIssues: string[], destinationIndex: number, issueMap: IIssueMap) => {
|
const handleSortOrder = (destinationIssues: string[], destinationIndex: number, issueMap: IIssueMap) => {
|
||||||
const sortOrderDefaultValue = 65535;
|
const sortOrderDefaultValue = 65535;
|
||||||
@ -44,7 +44,7 @@ export const handleDragDrop = async (
|
|||||||
subGroupBy: string | null,
|
subGroupBy: string | null,
|
||||||
groupBy: string | null,
|
groupBy: string | null,
|
||||||
issueMap: IIssueMap,
|
issueMap: IIssueMap,
|
||||||
issueWithIds: TGroupedIssues | TSubGroupedIssues | TUnGroupedIssues | undefined,
|
issueWithIds: TGroupedIssues | TSubGroupedIssues | undefined,
|
||||||
updateIssue: ((projectId: string, issueId: string, data: Partial<TIssue>) => Promise<void>) | undefined,
|
updateIssue: ((projectId: string, issueId: string, data: Partial<TIssue>) => Promise<void>) | undefined,
|
||||||
removeIssue: (projectId: string, issueId: string) => Promise<void> | undefined
|
removeIssue: (projectId: string, issueId: string) => Promise<void> | undefined
|
||||||
) => {
|
) => {
|
||||||
|
@ -5,13 +5,14 @@ import { EIssueLayoutTypes, EIssuesStoreType } from "constants/issue";
|
|||||||
import { EUserProjectRoles } from "constants/project";
|
import { EUserProjectRoles } from "constants/project";
|
||||||
import { useIssues, useUser } from "hooks/store";
|
import { useIssues, useUser } from "hooks/store";
|
||||||
|
|
||||||
import { TGroupedIssues, TIssue, TUnGroupedIssues } from "@plane/types";
|
import { TGroupedIssues, TIssue } from "@plane/types";
|
||||||
// components
|
// components
|
||||||
import { List } from "./default";
|
import { List } from "./default";
|
||||||
import { IQuickActionProps } from "./list-view-types";
|
import { IQuickActionProps } from "./list-view-types";
|
||||||
import { useIssuesActions } from "hooks/use-issues-actions";
|
import { useIssuesActions } from "hooks/use-issues-actions";
|
||||||
import { IssueLayoutHOC } from "../issue-layout-HOC";
|
import { IssueLayoutHOC } from "../issue-layout-HOC";
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
|
import { ALL_ISSUES } from "store/issue/helpers/base-issues.store";
|
||||||
// constants
|
// constants
|
||||||
// hooks
|
// hooks
|
||||||
|
|
||||||
@ -51,14 +52,24 @@ export const BaseListRoot = observer((props: IBaseListRoot) => {
|
|||||||
|
|
||||||
const { issueMap } = useIssues();
|
const { issueMap } = useIssues();
|
||||||
|
|
||||||
useSWR(`ISSUE_LIST_LAYOUT_${storeType}`, () => fetchIssues("init-loader", { canGroup: true, perPageCount: 100 }), {
|
const displayFilters = issuesFilter?.issueFilters?.displayFilters;
|
||||||
revalidateOnFocus: false,
|
const displayProperties = issuesFilter?.issueFilters?.displayProperties;
|
||||||
revalidateOnReconnect: false,
|
|
||||||
});
|
const group_by = displayFilters?.group_by || null;
|
||||||
|
const showEmptyGroup = displayFilters?.show_empty_groups ?? false;
|
||||||
|
|
||||||
|
useSWR(
|
||||||
|
`ISSUE_LIST_LAYOUT_${storeType}_${group_by}`,
|
||||||
|
() => fetchIssues("init-loader", { canGroup: true, perPageCount: 50 }),
|
||||||
|
{
|
||||||
|
revalidateOnFocus: false,
|
||||||
|
revalidateOnReconnect: false,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const isEditingAllowed = !!currentProjectRole && currentProjectRole >= EUserProjectRoles.MEMBER;
|
const isEditingAllowed = !!currentProjectRole && currentProjectRole >= EUserProjectRoles.MEMBER;
|
||||||
|
|
||||||
const issueIds = issues?.groupedIssueIds as TGroupedIssues | undefined;
|
const groupedIssueIds = issues?.groupedIssueIds as TGroupedIssues | undefined;
|
||||||
|
|
||||||
const { enableInlineEditing, enableQuickAdd, enableIssueCreation } = issues?.viewFlags || {};
|
const { enableInlineEditing, enableQuickAdd, enableIssueCreation } = issues?.viewFlags || {};
|
||||||
const canEditProperties = useCallback(
|
const canEditProperties = useCallback(
|
||||||
@ -71,12 +82,6 @@ export const BaseListRoot = observer((props: IBaseListRoot) => {
|
|||||||
[canEditPropertiesBasedOnProject, enableInlineEditing, isEditingAllowed]
|
[canEditPropertiesBasedOnProject, enableInlineEditing, isEditingAllowed]
|
||||||
);
|
);
|
||||||
|
|
||||||
const displayFilters = issuesFilter?.issueFilters?.displayFilters;
|
|
||||||
const displayProperties = issuesFilter?.issueFilters?.displayProperties;
|
|
||||||
|
|
||||||
const group_by = displayFilters?.group_by || null;
|
|
||||||
const showEmptyGroup = displayFilters?.show_empty_groups ?? false;
|
|
||||||
|
|
||||||
const renderQuickActions = useCallback(
|
const renderQuickActions = useCallback(
|
||||||
(issue: TIssue) => (
|
(issue: TIssue) => (
|
||||||
<QuickActions
|
<QuickActions
|
||||||
@ -93,9 +98,12 @@ export const BaseListRoot = observer((props: IBaseListRoot) => {
|
|||||||
[isEditingAllowed, isCompletedCycle, removeIssue, updateIssue, removeIssueFromView, archiveIssue, restoreIssue]
|
[isEditingAllowed, isCompletedCycle, removeIssue, updateIssue, removeIssueFromView, archiveIssue, restoreIssue]
|
||||||
);
|
);
|
||||||
|
|
||||||
const loadMoreIssues = useCallback(() => {
|
const loadMoreIssues = useCallback(
|
||||||
fetchNextIssues();
|
(groupId?: string) => {
|
||||||
}, [fetchNextIssues]);
|
fetchNextIssues(groupId);
|
||||||
|
},
|
||||||
|
[fetchNextIssues]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<IssueLayoutHOC storeType={storeType} layout={EIssueLayoutTypes.LIST}>
|
<IssueLayoutHOC storeType={storeType} layout={EIssueLayoutTypes.LIST}>
|
||||||
@ -106,11 +114,12 @@ export const BaseListRoot = observer((props: IBaseListRoot) => {
|
|||||||
group_by={group_by}
|
group_by={group_by}
|
||||||
updateIssue={updateIssue}
|
updateIssue={updateIssue}
|
||||||
quickActions={renderQuickActions}
|
quickActions={renderQuickActions}
|
||||||
issueIds={issueIds!}
|
groupedIssueIds={groupedIssueIds ?? {}}
|
||||||
shouldLoadMore={issues.next_page_results}
|
|
||||||
loadMoreIssues={loadMoreIssues}
|
loadMoreIssues={loadMoreIssues}
|
||||||
showEmptyGroup={showEmptyGroup}
|
showEmptyGroup={showEmptyGroup}
|
||||||
viewId={viewId}
|
viewId={viewId}
|
||||||
|
getPaginationData={issues.getPaginationData}
|
||||||
|
getGroupIssueCount={issues.getGroupIssueCount}
|
||||||
quickAddCallback={issues?.quickAddIssue}
|
quickAddCallback={issues?.quickAddIssue}
|
||||||
enableIssueQuickAdd={!!enableQuickAdd}
|
enableIssueQuickAdd={!!enableQuickAdd}
|
||||||
canEditProperties={canEditProperties}
|
canEditProperties={canEditProperties}
|
||||||
|
@ -13,7 +13,9 @@ import { IssueProperties } from "../properties/all-properties";
|
|||||||
interface IssueBlockProps {
|
interface IssueBlockProps {
|
||||||
issueId: string;
|
issueId: string;
|
||||||
issuesMap: TIssueMap;
|
issuesMap: TIssueMap;
|
||||||
updateIssue: ((projectId: string, issueId: string, data: Partial<TIssue>) => Promise<void>) | undefined;
|
updateIssue:
|
||||||
|
| ((projectId: string | null | undefined, issueId: string, data: Partial<TIssue>) => Promise<void>)
|
||||||
|
| undefined;
|
||||||
quickActions: (issue: TIssue) => React.ReactNode;
|
quickActions: (issue: TIssue) => React.ReactNode;
|
||||||
displayProperties: IIssueDisplayProperties | undefined;
|
displayProperties: IIssueDisplayProperties | undefined;
|
||||||
canEditProperties: (projectId: string | undefined) => boolean;
|
canEditProperties: (projectId: string | undefined) => boolean;
|
||||||
|
@ -3,19 +3,22 @@ import { FC, MutableRefObject } from "react";
|
|||||||
import RenderIfVisible from "components/core/render-if-visible-HOC";
|
import RenderIfVisible from "components/core/render-if-visible-HOC";
|
||||||
import { IssueBlock } from "components/issues";
|
import { IssueBlock } from "components/issues";
|
||||||
// types
|
// types
|
||||||
import { TGroupedIssues, TIssue, IIssueDisplayProperties, TIssueMap, TUnGroupedIssues } from "@plane/types";
|
import { TGroupedIssues, TIssue, IIssueDisplayProperties, TIssueMap } from "@plane/types";
|
||||||
|
import { observer } from "mobx-react";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
issueIds: TGroupedIssues | TUnGroupedIssues | any;
|
issueIds: TGroupedIssues | any;
|
||||||
issuesMap: TIssueMap;
|
issuesMap: TIssueMap;
|
||||||
canEditProperties: (projectId: string | undefined) => boolean;
|
canEditProperties: (projectId: string | undefined) => boolean;
|
||||||
updateIssue: ((projectId: string, issueId: string, data: Partial<TIssue>) => Promise<void>) | undefined;
|
updateIssue:
|
||||||
|
| ((projectId: string | null | undefined, issueId: string, data: Partial<TIssue>) => Promise<void>)
|
||||||
|
| undefined;
|
||||||
quickActions: (issue: TIssue) => React.ReactNode;
|
quickActions: (issue: TIssue) => React.ReactNode;
|
||||||
displayProperties: IIssueDisplayProperties | undefined;
|
displayProperties: IIssueDisplayProperties | undefined;
|
||||||
containerRef: MutableRefObject<HTMLDivElement | null>;
|
containerRef: MutableRefObject<HTMLDivElement | null>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const IssueBlocksList: FC<Props> = (props) => {
|
export const IssueBlocksList: FC<Props> = observer((props) => {
|
||||||
const { issueIds, issuesMap, updateIssue, quickActions, displayProperties, canEditProperties, containerRef } = props;
|
const { issueIds, issuesMap, updateIssue, quickActions, displayProperties, canEditProperties, containerRef } = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -47,4 +50,4 @@ export const IssueBlocksList: FC<Props> = (props) => {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { useRef } from "react";
|
import { useCallback, useRef } from "react";
|
||||||
// components
|
// components
|
||||||
import { IssueBlocksList, ListQuickAddIssueForm } from "components/issues";
|
import { IssueBlocksList, ListQuickAddIssueForm } from "components/issues";
|
||||||
// hooks
|
// hooks
|
||||||
@ -11,26 +11,31 @@ import {
|
|||||||
TIssue,
|
TIssue,
|
||||||
IIssueDisplayProperties,
|
IIssueDisplayProperties,
|
||||||
TIssueMap,
|
TIssueMap,
|
||||||
TUnGroupedIssues,
|
|
||||||
IGroupByColumn,
|
IGroupByColumn,
|
||||||
TIssueGroup,
|
TPaginationData,
|
||||||
} from "@plane/types";
|
} from "@plane/types";
|
||||||
import { getGroupByColumns } from "../utils";
|
import { getGroupByColumns } from "../utils";
|
||||||
import { HeaderGroupByCard } from "./headers/group-by-card";
|
import { HeaderGroupByCard } from "./headers/group-by-card";
|
||||||
import { EIssuesStoreType } from "constants/issue";
|
import { EIssuesStoreType } from "constants/issue";
|
||||||
import { ArrowDown } from "lucide-react";
|
|
||||||
import { ListLoaderItemRow } from "components/ui";
|
import { ListLoaderItemRow } from "components/ui";
|
||||||
import { useIntersectionObserver } from "hooks/use-intersection-observer";
|
import { useIntersectionObserver } from "hooks/use-intersection-observer";
|
||||||
|
import { ALL_ISSUES } from "store/issue/helpers/base-issues.store";
|
||||||
|
import { observer } from "mobx-react";
|
||||||
|
import isNil from "lodash/isNil";
|
||||||
|
|
||||||
export interface IGroupByList {
|
export interface IGroupByList {
|
||||||
issueIds: TGroupedIssues;
|
groupedIssueIds: TGroupedIssues;
|
||||||
issuesMap: TIssueMap;
|
issuesMap: TIssueMap;
|
||||||
group_by: string | null;
|
group_by: string | null;
|
||||||
updateIssue: ((projectId: string, issueId: string, data: Partial<TIssue>) => Promise<void>) | undefined;
|
updateIssue:
|
||||||
|
| ((projectId: string | null | undefined, issueId: string, data: Partial<TIssue>) => Promise<void>)
|
||||||
|
| undefined;
|
||||||
quickActions: (issue: TIssue) => React.ReactNode;
|
quickActions: (issue: TIssue) => React.ReactNode;
|
||||||
displayProperties: IIssueDisplayProperties | undefined;
|
displayProperties: IIssueDisplayProperties | undefined;
|
||||||
enableIssueQuickAdd: boolean;
|
enableIssueQuickAdd: boolean;
|
||||||
showEmptyGroup?: boolean;
|
showEmptyGroup?: boolean;
|
||||||
|
getPaginationData: (groupId: string | undefined) => TPaginationData | undefined;
|
||||||
|
getGroupIssueCount: (groupId: string | undefined) => number | undefined;
|
||||||
canEditProperties: (projectId: string | undefined) => boolean;
|
canEditProperties: (projectId: string | undefined) => boolean;
|
||||||
quickAddCallback?: (
|
quickAddCallback?: (
|
||||||
workspaceSlug: string,
|
workspaceSlug: string,
|
||||||
@ -43,13 +48,12 @@ export interface IGroupByList {
|
|||||||
addIssuesToView?: (issueIds: string[]) => Promise<TIssue>;
|
addIssuesToView?: (issueIds: string[]) => Promise<TIssue>;
|
||||||
viewId?: string;
|
viewId?: string;
|
||||||
isCompletedCycle?: boolean;
|
isCompletedCycle?: boolean;
|
||||||
shouldLoadMore: boolean;
|
loadMoreIssues: (groupId?: string) => void;
|
||||||
loadMoreIssues: () => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const GroupByList: React.FC<IGroupByList> = (props) => {
|
const GroupByList: React.FC<IGroupByList> = observer((props) => {
|
||||||
const {
|
const {
|
||||||
issueIds,
|
groupedIssueIds,
|
||||||
issuesMap,
|
issuesMap,
|
||||||
group_by,
|
group_by,
|
||||||
updateIssue,
|
updateIssue,
|
||||||
@ -63,7 +67,8 @@ const GroupByList: React.FC<IGroupByList> = (props) => {
|
|||||||
disableIssueCreation,
|
disableIssueCreation,
|
||||||
storeType,
|
storeType,
|
||||||
addIssuesToView,
|
addIssuesToView,
|
||||||
shouldLoadMore,
|
getPaginationData,
|
||||||
|
getGroupIssueCount,
|
||||||
isCompletedCycle = false,
|
isCompletedCycle = false,
|
||||||
loadMoreIssues,
|
loadMoreIssues,
|
||||||
} = props;
|
} = props;
|
||||||
@ -123,9 +128,8 @@ const GroupByList: React.FC<IGroupByList> = (props) => {
|
|||||||
return preloadedData;
|
return preloadedData;
|
||||||
};
|
};
|
||||||
|
|
||||||
const validateEmptyIssueGroups = (issues: TIssueGroup) => {
|
const validateEmptyIssueGroups = (issueCount: number = 0) => {
|
||||||
const issuesCount = issues?.issueCount || 0;
|
if (!showEmptyGroup && issueCount <= 0) return false;
|
||||||
if (!showEmptyGroup && issuesCount <= 0) return false;
|
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -139,17 +143,25 @@ const GroupByList: React.FC<IGroupByList> = (props) => {
|
|||||||
{groups &&
|
{groups &&
|
||||||
groups.length > 0 &&
|
groups.length > 0 &&
|
||||||
groups.map((_list: IGroupByColumn) => {
|
groups.map((_list: IGroupByColumn) => {
|
||||||
const issueGroup = issueIds?.[_list.id] as TIssueGroup;
|
const groupIssueIds = groupedIssueIds?.[_list.id];
|
||||||
|
const groupIssueCount = getGroupIssueCount(_list.id);
|
||||||
|
|
||||||
|
const nextPageResults = getPaginationData(_list.id)?.nextPageResults;
|
||||||
|
|
||||||
|
const shouldLoadMore =
|
||||||
|
nextPageResults === undefined && groupIssueCount !== undefined
|
||||||
|
? groupIssueIds?.length < groupIssueCount
|
||||||
|
: !!nextPageResults;
|
||||||
return (
|
return (
|
||||||
issueGroup &&
|
groupIssueIds &&
|
||||||
validateEmptyIssueGroups(issueGroup) && (
|
!isNil(groupIssueCount) &&
|
||||||
|
validateEmptyIssueGroups(groupIssueCount) && (
|
||||||
<div key={_list.id} className={`flex flex-shrink-0 flex-col`}>
|
<div key={_list.id} className={`flex flex-shrink-0 flex-col`}>
|
||||||
<div className="sticky top-0 z-[2] w-full flex-shrink-0 border-b border-custom-border-200 bg-custom-background-90 px-3 py-1">
|
<div className="sticky top-0 z-[2] w-full flex-shrink-0 border-b border-custom-border-200 bg-custom-background-90 px-3 py-1">
|
||||||
<HeaderGroupByCard
|
<HeaderGroupByCard
|
||||||
icon={_list.icon}
|
icon={_list.icon}
|
||||||
title={_list.name || ""}
|
title={_list.name || ""}
|
||||||
count={issueGroup.issueCount}
|
count={groupIssueCount}
|
||||||
issuePayload={_list.payload}
|
issuePayload={_list.payload}
|
||||||
disableIssueCreation={disableIssueCreation || isGroupByCreatedBy || isCompletedCycle}
|
disableIssueCreation={disableIssueCreation || isGroupByCreatedBy || isCompletedCycle}
|
||||||
storeType={storeType}
|
storeType={storeType}
|
||||||
@ -157,9 +169,9 @@ const GroupByList: React.FC<IGroupByList> = (props) => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{issueIds && (
|
{groupedIssueIds && (
|
||||||
<IssueBlocksList
|
<IssueBlocksList
|
||||||
issueIds={issueGroup.issueIds}
|
issueIds={groupIssueIds}
|
||||||
issuesMap={issuesMap}
|
issuesMap={issuesMap}
|
||||||
updateIssue={updateIssue}
|
updateIssue={updateIssue}
|
||||||
quickActions={quickActions}
|
quickActions={quickActions}
|
||||||
@ -168,15 +180,13 @@ const GroupByList: React.FC<IGroupByList> = (props) => {
|
|||||||
containerRef={containerRef}
|
containerRef={containerRef}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{/* &&
|
|
||||||
issueGroup.issueIds?.length <= issueGroup.issueCount */}
|
|
||||||
{shouldLoadMore &&
|
{shouldLoadMore &&
|
||||||
(group_by ? (
|
(group_by ? (
|
||||||
<div
|
<div
|
||||||
className={
|
className={
|
||||||
"h-11 relative flex items-center gap-3 bg-custom-background-100 p-3 text-sm text-custom-primary-100 hover:underline cursor-pointer"
|
"h-11 relative flex items-center gap-3 bg-custom-background-100 p-3 text-sm text-custom-primary-100 hover:underline cursor-pointer"
|
||||||
}
|
}
|
||||||
onClick={loadMoreIssues}
|
onClick={() => loadMoreIssues(_list.id)}
|
||||||
>
|
>
|
||||||
Load more ↓
|
Load more ↓
|
||||||
</div>
|
</div>
|
||||||
@ -199,19 +209,20 @@ const GroupByList: React.FC<IGroupByList> = (props) => {
|
|||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
|
||||||
export interface IList {
|
export interface IList {
|
||||||
issueIds: TGroupedIssues | TUnGroupedIssues;
|
groupedIssueIds: TGroupedIssues;
|
||||||
issuesMap: TIssueMap;
|
issuesMap: TIssueMap;
|
||||||
group_by: string | null;
|
group_by: string | null;
|
||||||
updateIssue: ((projectId: string, issueId: string, data: Partial<TIssue>) => Promise<void>) | undefined;
|
updateIssue:
|
||||||
|
| ((projectId: string | null | undefined, issueId: string, data: Partial<TIssue>) => Promise<void>)
|
||||||
|
| undefined;
|
||||||
quickActions: (issue: TIssue) => React.ReactNode;
|
quickActions: (issue: TIssue) => React.ReactNode;
|
||||||
displayProperties: IIssueDisplayProperties | undefined;
|
displayProperties: IIssueDisplayProperties | undefined;
|
||||||
showEmptyGroup: boolean;
|
showEmptyGroup: boolean;
|
||||||
enableIssueQuickAdd: boolean;
|
enableIssueQuickAdd: boolean;
|
||||||
canEditProperties: (projectId: string | undefined) => boolean;
|
canEditProperties: (projectId: string | undefined) => boolean;
|
||||||
shouldLoadMore: boolean;
|
|
||||||
quickAddCallback?: (
|
quickAddCallback?: (
|
||||||
workspaceSlug: string,
|
workspaceSlug: string,
|
||||||
projectId: string,
|
projectId: string,
|
||||||
@ -222,13 +233,15 @@ export interface IList {
|
|||||||
disableIssueCreation?: boolean;
|
disableIssueCreation?: boolean;
|
||||||
storeType: EIssuesStoreType;
|
storeType: EIssuesStoreType;
|
||||||
addIssuesToView?: (issueIds: string[]) => Promise<TIssue>;
|
addIssuesToView?: (issueIds: string[]) => Promise<TIssue>;
|
||||||
loadMoreIssues: () => void;
|
getPaginationData: (groupId: string | undefined) => TPaginationData | undefined;
|
||||||
|
getGroupIssueCount: (groupId: string | undefined) => number | undefined;
|
||||||
|
loadMoreIssues: (groupId?: string) => void;
|
||||||
isCompletedCycle?: boolean;
|
isCompletedCycle?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const List: React.FC<IList> = (props) => {
|
export const List: React.FC<IList> = (props) => {
|
||||||
const {
|
const {
|
||||||
issueIds,
|
groupedIssueIds,
|
||||||
issuesMap,
|
issuesMap,
|
||||||
group_by,
|
group_by,
|
||||||
updateIssue,
|
updateIssue,
|
||||||
@ -239,10 +252,11 @@ export const List: React.FC<IList> = (props) => {
|
|||||||
showEmptyGroup,
|
showEmptyGroup,
|
||||||
enableIssueQuickAdd,
|
enableIssueQuickAdd,
|
||||||
canEditProperties,
|
canEditProperties,
|
||||||
|
getPaginationData,
|
||||||
|
getGroupIssueCount,
|
||||||
disableIssueCreation,
|
disableIssueCreation,
|
||||||
storeType,
|
storeType,
|
||||||
addIssuesToView,
|
addIssuesToView,
|
||||||
shouldLoadMore,
|
|
||||||
loadMoreIssues,
|
loadMoreIssues,
|
||||||
isCompletedCycle = false,
|
isCompletedCycle = false,
|
||||||
} = props;
|
} = props;
|
||||||
@ -250,10 +264,9 @@ export const List: React.FC<IList> = (props) => {
|
|||||||
return (
|
return (
|
||||||
<div className="relative h-full w-full">
|
<div className="relative h-full w-full">
|
||||||
<GroupByList
|
<GroupByList
|
||||||
issueIds={issueIds}
|
groupedIssueIds={groupedIssueIds}
|
||||||
issuesMap={issuesMap}
|
issuesMap={issuesMap}
|
||||||
group_by={group_by}
|
group_by={group_by}
|
||||||
shouldLoadMore={shouldLoadMore}
|
|
||||||
loadMoreIssues={loadMoreIssues}
|
loadMoreIssues={loadMoreIssues}
|
||||||
updateIssue={updateIssue}
|
updateIssue={updateIssue}
|
||||||
quickActions={quickActions}
|
quickActions={quickActions}
|
||||||
@ -262,6 +275,8 @@ export const List: React.FC<IList> = (props) => {
|
|||||||
showEmptyGroup={showEmptyGroup}
|
showEmptyGroup={showEmptyGroup}
|
||||||
canEditProperties={canEditProperties}
|
canEditProperties={canEditProperties}
|
||||||
quickAddCallback={quickAddCallback}
|
quickAddCallback={quickAddCallback}
|
||||||
|
getPaginationData={getPaginationData}
|
||||||
|
getGroupIssueCount={getGroupIssueCount}
|
||||||
viewId={viewId}
|
viewId={viewId}
|
||||||
disableIssueCreation={disableIssueCreation}
|
disableIssueCreation={disableIssueCreation}
|
||||||
storeType={storeType}
|
storeType={storeType}
|
||||||
|
@ -17,8 +17,15 @@ import { SpreadsheetLayoutLoader } from "components/ui";
|
|||||||
import { TIssue, IIssueDisplayFilterOptions } from "@plane/types";
|
import { TIssue, IIssueDisplayFilterOptions } from "@plane/types";
|
||||||
// constants
|
// constants
|
||||||
import { EUserProjectRoles } from "constants/project";
|
import { EUserProjectRoles } from "constants/project";
|
||||||
import { EIssueFilterType, EIssuesStoreType, ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "constants/issue";
|
import {
|
||||||
|
EIssueFilterType,
|
||||||
|
EIssueLayoutTypes,
|
||||||
|
EIssuesStoreType,
|
||||||
|
ISSUE_DISPLAY_FILTERS_BY_LAYOUT,
|
||||||
|
} from "constants/issue";
|
||||||
import { EMPTY_STATE_DETAILS, EmptyStateType } from "constants/empty-state";
|
import { EMPTY_STATE_DETAILS, EmptyStateType } from "constants/empty-state";
|
||||||
|
import { ALL_ISSUES } from "store/issue/helpers/base-issues.store";
|
||||||
|
import { IssueLayoutHOC } from "../issue-layout-HOC";
|
||||||
|
|
||||||
export const AllIssueLayoutRoot: React.FC = observer(() => {
|
export const AllIssueLayoutRoot: React.FC = observer(() => {
|
||||||
// router
|
// router
|
||||||
@ -30,7 +37,7 @@ export const AllIssueLayoutRoot: React.FC = observer(() => {
|
|||||||
const { commandPalette: commandPaletteStore } = useApplication();
|
const { commandPalette: commandPaletteStore } = useApplication();
|
||||||
const {
|
const {
|
||||||
issuesFilter: { filters, fetchFilters, updateFilters },
|
issuesFilter: { filters, fetchFilters, updateFilters },
|
||||||
issues: { loader, issueCount: totalIssueCount, groupedIssueIds, fetchIssues, fetchNextIssues },
|
issues: { loader, getPaginationData, groupedIssueIds, fetchIssues, fetchNextIssues },
|
||||||
} = useIssues(EIssuesStoreType.GLOBAL);
|
} = useIssues(EIssuesStoreType.GLOBAL);
|
||||||
const { updateIssue, removeIssue, archiveIssue } = useIssuesActions(EIssuesStoreType.GLOBAL);
|
const { updateIssue, removeIssue, archiveIssue } = useIssuesActions(EIssuesStoreType.GLOBAL);
|
||||||
|
|
||||||
@ -153,53 +160,28 @@ export const AllIssueLayoutRoot: React.FC = observer(() => {
|
|||||||
return <SpreadsheetLayoutLoader />;
|
return <SpreadsheetLayoutLoader />;
|
||||||
}
|
}
|
||||||
|
|
||||||
const {
|
const issueIds = groupedIssueIds[ALL_ISSUES];
|
||||||
"All Issues": { issueIds, issueCount },
|
const nextPageResults = getPaginationData(ALL_ISSUES)?.nextPageResults;
|
||||||
} = groupedIssueIds;
|
|
||||||
|
|
||||||
const emptyStateType =
|
const emptyStateType =
|
||||||
(workspaceProjectIds ?? []).length > 0 ? `workspace-${globalViewId}` : EmptyStateType.WORKSPACE_NO_PROJECTS;
|
(workspaceProjectIds ?? []).length > 0 ? `workspace-${globalViewId}` : EmptyStateType.WORKSPACE_NO_PROJECTS;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative flex h-full w-full flex-col overflow-hidden">
|
<IssueLayoutHOC storeType={EIssuesStoreType.GLOBAL} layout={EIssueLayoutTypes.SPREADSHEET}>
|
||||||
<div className="relative h-full w-full flex flex-col">
|
<SpreadsheetView
|
||||||
<GlobalViewsAppliedFiltersRoot globalViewId={globalViewId.toString()} />
|
displayProperties={issueFilters?.displayProperties ?? {}}
|
||||||
{!totalIssueCount ? (
|
displayFilters={issueFilters?.displayFilters ?? {}}
|
||||||
<EmptyState
|
handleDisplayFilterUpdate={handleDisplayFiltersUpdate}
|
||||||
type={emptyStateType as keyof typeof EMPTY_STATE_DETAILS}
|
issueIds={Array.isArray(issueIds) ? issueIds : []}
|
||||||
size="sm"
|
quickActions={renderQuickActions}
|
||||||
primaryButtonOnClick={
|
updateIssue={updateIssue}
|
||||||
(workspaceProjectIds ?? []).length > 0
|
canEditProperties={canEditProperties}
|
||||||
? globalViewId !== "custom-view" && globalViewId !== "subscribed"
|
viewId={globalViewId.toString()}
|
||||||
? () => {
|
canLoadMoreIssues={!!nextPageResults}
|
||||||
setTrackElement("All issues empty state");
|
loadMoreIssues={fetchNextPages}
|
||||||
commandPaletteStore.toggleCreateIssueModal(true, EIssuesStoreType.PROJECT);
|
/>
|
||||||
}
|
{/* peek overview */}
|
||||||
: undefined
|
<IssuePeekOverview />
|
||||||
: () => {
|
</IssueLayoutHOC>
|
||||||
setTrackElement("All issues empty state");
|
|
||||||
commandPaletteStore.toggleCreateProjectModal(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<Fragment>
|
|
||||||
<SpreadsheetView
|
|
||||||
displayProperties={issueFilters?.displayProperties ?? {}}
|
|
||||||
displayFilters={issueFilters?.displayFilters ?? {}}
|
|
||||||
handleDisplayFilterUpdate={handleDisplayFiltersUpdate}
|
|
||||||
issueIds={Array.isArray(issueIds) ? issueIds : []}
|
|
||||||
quickActions={renderQuickActions}
|
|
||||||
updateIssue={updateIssue}
|
|
||||||
canEditProperties={canEditProperties}
|
|
||||||
viewId={globalViewId.toString()}
|
|
||||||
onEndOfListTrigger={fetchNextPages}
|
|
||||||
/>
|
|
||||||
{/* peek overview */}
|
|
||||||
<IssuePeekOverview />
|
|
||||||
</Fragment>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -9,11 +9,12 @@ import { useIssuesActions } from "hooks/use-issues-actions";
|
|||||||
// views
|
// views
|
||||||
// types
|
// types
|
||||||
// constants
|
// constants
|
||||||
import { TIssue, IIssueDisplayFilterOptions, TUnGroupedIssues } from "@plane/types";
|
import { TIssue, IIssueDisplayFilterOptions } from "@plane/types";
|
||||||
import { IQuickActionProps } from "../list/list-view-types";
|
import { IQuickActionProps } from "../list/list-view-types";
|
||||||
import { SpreadsheetView } from "./spreadsheet-view";
|
import { SpreadsheetView } from "./spreadsheet-view";
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
import { IssueLayoutHOC } from "../issue-layout-HOC";
|
import { IssueLayoutHOC } from "../issue-layout-HOC";
|
||||||
|
import { ALL_ISSUES } from "store/issue/helpers/base-issues.store";
|
||||||
|
|
||||||
export type SpreadsheetStoreType =
|
export type SpreadsheetStoreType =
|
||||||
| EIssuesStoreType.PROJECT
|
| EIssuesStoreType.PROJECT
|
||||||
@ -72,7 +73,8 @@ export const BaseSpreadsheetRoot = observer((props: IBaseSpreadsheetRoot) => {
|
|||||||
[canEditPropertiesBasedOnProject, enableInlineEditing, isEditingAllowed]
|
[canEditPropertiesBasedOnProject, enableInlineEditing, isEditingAllowed]
|
||||||
);
|
);
|
||||||
|
|
||||||
const issueIds = issues.groupedIssueIds?.["All Issues"]?.issueIds ?? [];
|
const issueIds = issues.groupedIssueIds?.[ALL_ISSUES] ?? [];
|
||||||
|
const nextPageResults = issues.getPaginationData(ALL_ISSUES)?.nextPageResults;
|
||||||
|
|
||||||
const handleDisplayFiltersUpdate = useCallback(
|
const handleDisplayFiltersUpdate = useCallback(
|
||||||
(updatedDisplayFilter: Partial<IIssueDisplayFilterOptions>) => {
|
(updatedDisplayFilter: Partial<IIssueDisplayFilterOptions>) => {
|
||||||
@ -118,7 +120,8 @@ export const BaseSpreadsheetRoot = observer((props: IBaseSpreadsheetRoot) => {
|
|||||||
viewId={viewId}
|
viewId={viewId}
|
||||||
enableQuickCreateIssue={enableQuickAdd}
|
enableQuickCreateIssue={enableQuickAdd}
|
||||||
disableIssueCreation={!enableIssueCreation || !isEditingAllowed || isCompletedCycle}
|
disableIssueCreation={!enableIssueCreation || !isEditingAllowed || isCompletedCycle}
|
||||||
onEndOfListTrigger={fetchNextIssues}
|
canLoadMoreIssues={!!nextPageResults}
|
||||||
|
loadMoreIssues={fetchNextIssues}
|
||||||
/>
|
/>
|
||||||
</IssueLayoutHOC>
|
</IssueLayoutHOC>
|
||||||
);
|
);
|
||||||
|
@ -25,7 +25,8 @@ type Props = {
|
|||||||
canEditProperties: (projectId: string | undefined) => boolean;
|
canEditProperties: (projectId: string | undefined) => boolean;
|
||||||
portalElement: React.MutableRefObject<HTMLDivElement | null>;
|
portalElement: React.MutableRefObject<HTMLDivElement | null>;
|
||||||
containerRef: MutableRefObject<HTMLTableElement | null>;
|
containerRef: MutableRefObject<HTMLTableElement | null>;
|
||||||
onEndOfListTrigger: () => void;
|
canLoadMoreIssues: boolean;
|
||||||
|
loadMoreIssues: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SpreadsheetTable = observer((props: Props) => {
|
export const SpreadsheetTable = observer((props: Props) => {
|
||||||
@ -39,8 +40,9 @@ export const SpreadsheetTable = observer((props: Props) => {
|
|||||||
quickActions,
|
quickActions,
|
||||||
updateIssue,
|
updateIssue,
|
||||||
canEditProperties,
|
canEditProperties,
|
||||||
|
canLoadMoreIssues,
|
||||||
containerRef,
|
containerRef,
|
||||||
onEndOfListTrigger,
|
loadMoreIssues,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
// states
|
// states
|
||||||
@ -80,27 +82,7 @@ export const SpreadsheetTable = observer((props: Props) => {
|
|||||||
};
|
};
|
||||||
}, [handleScroll, containerRef]);
|
}, [handleScroll, containerRef]);
|
||||||
|
|
||||||
// useEffect(() => {
|
useIntersectionObserver(containerRef, intersectionRef, loadMoreIssues, `50% 0% 50% 0%`);
|
||||||
// if (intersectionRef.current) {
|
|
||||||
// const observer = new IntersectionObserver(
|
|
||||||
// (entries) => {
|
|
||||||
// if (entries[0].isIntersecting) onEndOfListTrigger();
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// root: containerRef?.current,
|
|
||||||
// rootMargin: `50% 0% 50% 0%`,
|
|
||||||
// }
|
|
||||||
// );
|
|
||||||
// observer.observe(intersectionRef.current);
|
|
||||||
// return () => {
|
|
||||||
// if (intersectionRef.current) {
|
|
||||||
// // eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
// observer.unobserve(intersectionRef.current);
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
// }
|
|
||||||
// }, [intersectionRef, containerRef]);
|
|
||||||
useIntersectionObserver(containerRef, intersectionRef, onEndOfListTrigger, `50% 0% 50% 0%`);
|
|
||||||
|
|
||||||
const handleKeyBoardNavigation = useTableKeyboardNavigation();
|
const handleKeyBoardNavigation = useTableKeyboardNavigation();
|
||||||
|
|
||||||
@ -130,7 +112,7 @@ export const SpreadsheetTable = observer((props: Props) => {
|
|||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</tbody>
|
</tbody>
|
||||||
<tfoot ref={intersectionRef}>Loading...</tfoot>
|
{canLoadMoreIssues && <tfoot ref={intersectionRef}>Loading...</tfoot>}
|
||||||
</table>
|
</table>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -31,7 +31,8 @@ type Props = {
|
|||||||
) => Promise<TIssue | undefined>;
|
) => Promise<TIssue | undefined>;
|
||||||
viewId?: string;
|
viewId?: string;
|
||||||
canEditProperties: (projectId: string | undefined) => boolean;
|
canEditProperties: (projectId: string | undefined) => boolean;
|
||||||
onEndOfListTrigger: () => void;
|
canLoadMoreIssues: boolean;
|
||||||
|
loadMoreIssues: () => void;
|
||||||
enableQuickCreateIssue?: boolean;
|
enableQuickCreateIssue?: boolean;
|
||||||
disableIssueCreation?: boolean;
|
disableIssueCreation?: boolean;
|
||||||
};
|
};
|
||||||
@ -49,7 +50,8 @@ export const SpreadsheetView: React.FC<Props> = observer((props) => {
|
|||||||
canEditProperties,
|
canEditProperties,
|
||||||
enableQuickCreateIssue,
|
enableQuickCreateIssue,
|
||||||
disableIssueCreation,
|
disableIssueCreation,
|
||||||
onEndOfListTrigger,
|
canLoadMoreIssues,
|
||||||
|
loadMoreIssues,
|
||||||
} = props;
|
} = props;
|
||||||
// refs
|
// refs
|
||||||
const containerRef = useRef<HTMLTableElement | null>(null);
|
const containerRef = useRef<HTMLTableElement | null>(null);
|
||||||
@ -81,7 +83,8 @@ export const SpreadsheetView: React.FC<Props> = observer((props) => {
|
|||||||
updateIssue={updateIssue}
|
updateIssue={updateIssue}
|
||||||
canEditProperties={canEditProperties}
|
canEditProperties={canEditProperties}
|
||||||
containerRef={containerRef}
|
containerRef={containerRef}
|
||||||
onEndOfListTrigger={onEndOfListTrigger}
|
canLoadMoreIssues={canLoadMoreIssues}
|
||||||
|
loadMoreIssues={loadMoreIssues}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="border-t border-custom-border-100">
|
<div className="border-t border-custom-border-100">
|
||||||
|
@ -2,11 +2,13 @@ import { observer } from "mobx-react-lite";
|
|||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
// mobx store
|
// mobx store
|
||||||
// components
|
// components
|
||||||
import { GanttChartRoot, IBlockUpdateData, ModuleGanttSidebar } from "components/gantt-chart";
|
import { ChartDataType, GanttChartRoot, IBlockUpdateData, ModuleGanttSidebar } from "components/gantt-chart";
|
||||||
import { ModuleGanttBlock } from "components/modules";
|
import { ModuleGanttBlock } from "components/modules";
|
||||||
import { useModule, useProject } from "hooks/store";
|
import { useModule, useProject } from "hooks/store";
|
||||||
// types
|
// types
|
||||||
import { IModule } from "@plane/types";
|
import { IModule } from "@plane/types";
|
||||||
|
import { useCallback } from "react";
|
||||||
|
import { getMonthChartItemPositionWidthInMonth } from "components/gantt-chart/views";
|
||||||
|
|
||||||
export const ModulesListGanttChartView: React.FC = observer(() => {
|
export const ModulesListGanttChartView: React.FC = observer(() => {
|
||||||
// router
|
// router
|
||||||
@ -14,7 +16,7 @@ export const ModulesListGanttChartView: React.FC = observer(() => {
|
|||||||
const { workspaceSlug } = router.query;
|
const { workspaceSlug } = router.query;
|
||||||
// store
|
// store
|
||||||
const { currentProjectDetails } = useProject();
|
const { currentProjectDetails } = useProject();
|
||||||
const { projectModuleIds, moduleMap, updateModuleDetails } = useModule();
|
const { projectModuleIds, getModuleById, updateModuleDetails } = useModule();
|
||||||
|
|
||||||
const handleModuleUpdate = async (module: IModule, data: IBlockUpdateData) => {
|
const handleModuleUpdate = async (module: IModule, data: IBlockUpdateData) => {
|
||||||
if (!workspaceSlug || !module) return;
|
if (!workspaceSlug || !module) return;
|
||||||
@ -25,26 +27,39 @@ export const ModulesListGanttChartView: React.FC = observer(() => {
|
|||||||
await updateModuleDetails(workspaceSlug.toString(), module.project_id, module.id, payload);
|
await updateModuleDetails(workspaceSlug.toString(), module.project_id, module.id, payload);
|
||||||
};
|
};
|
||||||
|
|
||||||
const blockFormat = (blocks: string[]) =>
|
const getBlockById = useCallback(
|
||||||
blocks?.map((blockId) => {
|
(id: string, currentViewData?: ChartDataType | undefined) => {
|
||||||
const block = moduleMap[blockId];
|
const module = getModuleById(id);
|
||||||
return {
|
|
||||||
data: block,
|
const block = {
|
||||||
id: block.id,
|
data: module,
|
||||||
sort_order: block.sort_order,
|
id: module?.id ?? "",
|
||||||
start_date: block.start_date ? new Date(block.start_date) : null,
|
sort_order: module?.sort_order ?? 0,
|
||||||
target_date: block.target_date ? new Date(block.target_date) : null,
|
start_date: module?.start_date ? new Date(module.start_date) : undefined,
|
||||||
|
target_date: module?.target_date ? new Date(module.target_date) : undefined,
|
||||||
};
|
};
|
||||||
});
|
if (currentViewData) {
|
||||||
|
return {
|
||||||
|
...block,
|
||||||
|
position: getMonthChartItemPositionWidthInMonth(currentViewData, block),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return block;
|
||||||
|
},
|
||||||
|
[getModuleById]
|
||||||
|
);
|
||||||
|
|
||||||
const isAllowed = currentProjectDetails?.member_role === 20 || currentProjectDetails?.member_role === 15;
|
const isAllowed = currentProjectDetails?.member_role === 20 || currentProjectDetails?.member_role === 15;
|
||||||
|
|
||||||
|
if (!projectModuleIds) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-full w-full overflow-y-auto">
|
<div className="h-full w-full overflow-y-auto">
|
||||||
<GanttChartRoot
|
<GanttChartRoot
|
||||||
title="Modules"
|
title="Modules"
|
||||||
loaderTitle="Modules"
|
loaderTitle="Modules"
|
||||||
blocks={projectModuleIds ? blockFormat(projectModuleIds) : null}
|
blockIds={projectModuleIds}
|
||||||
|
getBlockById={getBlockById}
|
||||||
sidebarToRender={(props) => <ModuleGanttSidebar {...props} />}
|
sidebarToRender={(props) => <ModuleGanttSidebar {...props} />}
|
||||||
blockUpdateHandler={(block, payload) => handleModuleUpdate(block, payload)}
|
blockUpdateHandler={(block, payload) => handleModuleUpdate(block, payload)}
|
||||||
blockToRender={(data: IModule) => <ModuleGanttBlock moduleId={data.id} />}
|
blockToRender={(data: IModule) => <ModuleGanttBlock moduleId={data.id} />}
|
||||||
|
@ -436,7 +436,7 @@ export const groupReactionEmojis = (reactions: any) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export enum IssueGroupByOptions {
|
export enum EIssueGroupByToServerOptions {
|
||||||
"state" = "state_id",
|
"state" = "state_id",
|
||||||
"priority" = "priority",
|
"priority" = "priority",
|
||||||
"labels" = "labels__id",
|
"labels" = "labels__id",
|
||||||
@ -448,3 +448,16 @@ export enum IssueGroupByOptions {
|
|||||||
"project" = "project_id",
|
"project" = "project_id",
|
||||||
"created_by" = "created_by",
|
"created_by" = "created_by",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum EServerGroupByToFilterOptions {
|
||||||
|
"state_id" = "state",
|
||||||
|
"priority" = "priority",
|
||||||
|
"labels__id" = "labels",
|
||||||
|
"state__group" = "state_group",
|
||||||
|
"assignees__id" = "assignees",
|
||||||
|
"cycle_id" = "cycle",
|
||||||
|
"modules__id" = "module",
|
||||||
|
"target_date" = "target_date",
|
||||||
|
"project_id" = "project",
|
||||||
|
"created_by" = "created_by",
|
||||||
|
}
|
@ -156,14 +156,15 @@ export const shouldHighlightIssueDueDate = (
|
|||||||
// if the issue is overdue, highlight the due date
|
// if the issue is overdue, highlight the due date
|
||||||
return targetDateDistance <= 0;
|
return targetDateDistance <= 0;
|
||||||
};
|
};
|
||||||
export const renderIssueBlocksStructure = (blocks: TIssue[]): IGanttBlock[] =>
|
export const getIssueBlocksStructure = (block: TIssue): IGanttBlock => {
|
||||||
blocks?.map((block) => ({
|
return {
|
||||||
data: block,
|
data: block,
|
||||||
id: block.id,
|
id: block.id,
|
||||||
sort_order: block.sort_order,
|
sort_order: block.sort_order,
|
||||||
start_date: block.start_date ? new Date(block.start_date) : null,
|
start_date: block.start_date ? new Date(block.start_date) : undefined,
|
||||||
target_date: block.target_date ? new Date(block.target_date) : null,
|
target_date: block.target_date ? new Date(block.target_date) : undefined,
|
||||||
}));
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export function getChangedIssuefields(formData: Partial<TIssue>, dirtyFields: { [key: string]: boolean | undefined }) {
|
export function getChangedIssuefields(formData: Partial<TIssue>, dirtyFields: { [key: string]: boolean | undefined }) {
|
||||||
const changedFields: Partial<TIssue> = {};
|
const changedFields: Partial<TIssue> = {};
|
||||||
|
@ -18,7 +18,7 @@ interface IssueActions {
|
|||||||
options: IssuePaginationOptions,
|
options: IssuePaginationOptions,
|
||||||
userViewId?: "assigned" | "created" | "subscribed"
|
userViewId?: "assigned" | "created" | "subscribed"
|
||||||
) => Promise<TIssuesResponse | undefined>;
|
) => Promise<TIssuesResponse | undefined>;
|
||||||
fetchNextIssues: () => Promise<TIssuesResponse | undefined>;
|
fetchNextIssues: (groupId?: string, subGroupId?: string) => Promise<TIssuesResponse | undefined>;
|
||||||
removeIssue: (projectId: string | undefined | null, issueId: string) => Promise<void>;
|
removeIssue: (projectId: string | undefined | null, issueId: string) => Promise<void>;
|
||||||
createIssue?: (projectId: string | undefined | null, data: Partial<TIssue>) => Promise<TIssue | undefined>;
|
createIssue?: (projectId: string | undefined | null, data: Partial<TIssue>) => Promise<TIssue | undefined>;
|
||||||
updateIssue?: (projectId: string | undefined | null, issueId: string, data: Partial<TIssue>) => Promise<void>;
|
updateIssue?: (projectId: string | undefined | null, issueId: string, data: Partial<TIssue>) => Promise<void>;
|
||||||
@ -77,10 +77,13 @@ const useProjectIssueActions = () => {
|
|||||||
},
|
},
|
||||||
[issues.fetchIssues, workspaceSlug, projectId]
|
[issues.fetchIssues, workspaceSlug, projectId]
|
||||||
);
|
);
|
||||||
const fetchNextIssues = useCallback(async () => {
|
const fetchNextIssues = useCallback(
|
||||||
if (!workspaceSlug || !projectId) return;
|
async (groupId?: string, subGroupId?: string) => {
|
||||||
return issues.fetchNextIssues(workspaceSlug.toString(), projectId.toString());
|
if (!workspaceSlug || !projectId) return;
|
||||||
}, [issues.fetchIssues, workspaceSlug, projectId]);
|
return issues.fetchNextIssues(workspaceSlug.toString(), projectId.toString(), groupId, subGroupId);
|
||||||
|
},
|
||||||
|
[issues.fetchIssues, workspaceSlug, projectId]
|
||||||
|
);
|
||||||
|
|
||||||
const createIssue = useCallback(
|
const createIssue = useCallback(
|
||||||
async (projectId: string | undefined | null, data: Partial<TIssue>) => {
|
async (projectId: string | undefined | null, data: Partial<TIssue>) => {
|
||||||
@ -151,10 +154,19 @@ const useCycleIssueActions = () => {
|
|||||||
},
|
},
|
||||||
[issues.fetchIssues, workspaceSlug, projectId, cycleId]
|
[issues.fetchIssues, workspaceSlug, projectId, cycleId]
|
||||||
);
|
);
|
||||||
const fetchNextIssues = useCallback(async () => {
|
const fetchNextIssues = useCallback(
|
||||||
if (!workspaceSlug || !projectId || !cycleId) return;
|
async (groupId?: string, subGroupId?: string) => {
|
||||||
return issues.fetchNextIssues(workspaceSlug.toString(), projectId.toString(), cycleId.toString());
|
if (!workspaceSlug || !projectId || !cycleId) return;
|
||||||
}, [issues.fetchIssues, workspaceSlug, projectId, cycleId]);
|
return issues.fetchNextIssues(
|
||||||
|
workspaceSlug.toString(),
|
||||||
|
projectId.toString(),
|
||||||
|
cycleId.toString(),
|
||||||
|
groupId,
|
||||||
|
subGroupId
|
||||||
|
);
|
||||||
|
},
|
||||||
|
[issues.fetchIssues, workspaceSlug, projectId, cycleId]
|
||||||
|
);
|
||||||
|
|
||||||
const createIssue = useCallback(
|
const createIssue = useCallback(
|
||||||
async (projectId: string | undefined | null, data: Partial<TIssue>) => {
|
async (projectId: string | undefined | null, data: Partial<TIssue>) => {
|
||||||
@ -242,10 +254,19 @@ const useModuleIssueActions = () => {
|
|||||||
},
|
},
|
||||||
[issues.fetchIssues, workspaceSlug, projectId, moduleId]
|
[issues.fetchIssues, workspaceSlug, projectId, moduleId]
|
||||||
);
|
);
|
||||||
const fetchNextIssues = useCallback(async () => {
|
const fetchNextIssues = useCallback(
|
||||||
if (!workspaceSlug || !projectId || !moduleId) return;
|
async (groupId?: string, subGroupId?: string) => {
|
||||||
return issues.fetchNextIssues(workspaceSlug.toString(), projectId.toString(), moduleId.toString());
|
if (!workspaceSlug || !projectId || !moduleId) return;
|
||||||
}, [issues.fetchIssues, workspaceSlug, projectId, moduleId]);
|
return issues.fetchNextIssues(
|
||||||
|
workspaceSlug.toString(),
|
||||||
|
projectId.toString(),
|
||||||
|
moduleId.toString(),
|
||||||
|
groupId,
|
||||||
|
subGroupId
|
||||||
|
);
|
||||||
|
},
|
||||||
|
[issues.fetchIssues, workspaceSlug, projectId, moduleId]
|
||||||
|
);
|
||||||
|
|
||||||
const createIssue = useCallback(
|
const createIssue = useCallback(
|
||||||
async (projectId: string | undefined | null, data: Partial<TIssue>) => {
|
async (projectId: string | undefined | null, data: Partial<TIssue>) => {
|
||||||
@ -324,10 +345,13 @@ const useProfileIssueActions = () => {
|
|||||||
},
|
},
|
||||||
[issues.fetchIssues, workspaceSlug, userId]
|
[issues.fetchIssues, workspaceSlug, userId]
|
||||||
);
|
);
|
||||||
const fetchNextIssues = useCallback(async () => {
|
const fetchNextIssues = useCallback(
|
||||||
if (!workspaceSlug || !userId) return;
|
async (groupId?: string, subGroupId?: string) => {
|
||||||
return issues.fetchNextIssues(workspaceSlug.toString(), userId.toString());
|
if (!workspaceSlug || !userId) return;
|
||||||
}, [issues.fetchIssues, workspaceSlug, userId]);
|
return issues.fetchNextIssues(workspaceSlug.toString(), userId.toString(), groupId, subGroupId);
|
||||||
|
},
|
||||||
|
[issues.fetchIssues, workspaceSlug, userId]
|
||||||
|
);
|
||||||
|
|
||||||
const createIssue = useCallback(
|
const createIssue = useCallback(
|
||||||
async (projectId: string | undefined | null, data: Partial<TIssue>) => {
|
async (projectId: string | undefined | null, data: Partial<TIssue>) => {
|
||||||
@ -398,10 +422,13 @@ const useProjectViewIssueActions = () => {
|
|||||||
},
|
},
|
||||||
[issues.fetchIssues, workspaceSlug, projectId]
|
[issues.fetchIssues, workspaceSlug, projectId]
|
||||||
);
|
);
|
||||||
const fetchNextIssues = useCallback(async () => {
|
const fetchNextIssues = useCallback(
|
||||||
if (!workspaceSlug || !projectId) return;
|
async (groupId?: string, subGroupId?: string) => {
|
||||||
return issues.fetchNextIssues(workspaceSlug.toString(), projectId.toString());
|
if (!workspaceSlug || !projectId) return;
|
||||||
}, [issues.fetchIssues, workspaceSlug, projectId]);
|
return issues.fetchNextIssues(workspaceSlug.toString(), projectId.toString(), groupId, subGroupId);
|
||||||
|
},
|
||||||
|
[issues.fetchIssues, workspaceSlug, projectId]
|
||||||
|
);
|
||||||
|
|
||||||
const createIssue = useCallback(
|
const createIssue = useCallback(
|
||||||
async (projectId: string | undefined | null, data: Partial<TIssue>) => {
|
async (projectId: string | undefined | null, data: Partial<TIssue>) => {
|
||||||
@ -472,10 +499,13 @@ const useDraftIssueActions = () => {
|
|||||||
},
|
},
|
||||||
[issues.fetchIssues, workspaceSlug, projectId]
|
[issues.fetchIssues, workspaceSlug, projectId]
|
||||||
);
|
);
|
||||||
const fetchNextIssues = useCallback(async () => {
|
const fetchNextIssues = useCallback(
|
||||||
if (!workspaceSlug || !projectId) return;
|
async (groupId?: string, subGroupId?: string) => {
|
||||||
return issues.fetchNextIssues(workspaceSlug.toString(), projectId.toString());
|
if (!workspaceSlug || !projectId) return;
|
||||||
}, [issues.fetchIssues, workspaceSlug, projectId]);
|
return issues.fetchNextIssues(workspaceSlug.toString(), projectId.toString(), groupId, subGroupId);
|
||||||
|
},
|
||||||
|
[issues.fetchIssues, workspaceSlug, projectId]
|
||||||
|
);
|
||||||
|
|
||||||
const createIssue = useCallback(
|
const createIssue = useCallback(
|
||||||
async (projectId: string | undefined | null, data: Partial<TIssue>) => {
|
async (projectId: string | undefined | null, data: Partial<TIssue>) => {
|
||||||
@ -538,10 +568,13 @@ const useArchivedIssueActions = () => {
|
|||||||
},
|
},
|
||||||
[issues.fetchIssues, workspaceSlug, projectId]
|
[issues.fetchIssues, workspaceSlug, projectId]
|
||||||
);
|
);
|
||||||
const fetchNextIssues = useCallback(async () => {
|
const fetchNextIssues = useCallback(
|
||||||
if (!workspaceSlug || !projectId) return;
|
async (groupId?: string, subGroupId?: string) => {
|
||||||
return issues.fetchNextIssues(workspaceSlug.toString(), projectId.toString());
|
if (!workspaceSlug || !projectId) return;
|
||||||
}, [issues.fetchIssues, workspaceSlug, projectId]);
|
return issues.fetchNextIssues(workspaceSlug.toString(), projectId.toString(), groupId, subGroupId);
|
||||||
|
},
|
||||||
|
[issues.fetchIssues, workspaceSlug, projectId]
|
||||||
|
);
|
||||||
|
|
||||||
const removeIssue = useCallback(
|
const removeIssue = useCallback(
|
||||||
async (projectId: string | undefined | null, issueId: string) => {
|
async (projectId: string | undefined | null, issueId: string) => {
|
||||||
@ -595,10 +628,13 @@ const useGlobalIssueActions = () => {
|
|||||||
},
|
},
|
||||||
[issues.fetchIssues, workspaceSlug, globalViewId]
|
[issues.fetchIssues, workspaceSlug, globalViewId]
|
||||||
);
|
);
|
||||||
const fetchNextIssues = useCallback(async () => {
|
const fetchNextIssues = useCallback(
|
||||||
if (!workspaceSlug || !globalViewId) return;
|
async (groupId?: string, subGroupId?: string) => {
|
||||||
return issues.fetchNextIssues(workspaceSlug.toString(), globalViewId.toString());
|
if (!workspaceSlug || !globalViewId) return;
|
||||||
}, [issues.fetchIssues, workspaceSlug, globalViewId]);
|
return issues.fetchNextIssues(workspaceSlug.toString(), globalViewId.toString(), groupId, subGroupId);
|
||||||
|
},
|
||||||
|
[issues.fetchIssues, workspaceSlug, globalViewId]
|
||||||
|
);
|
||||||
|
|
||||||
const createIssue = useCallback(
|
const createIssue = useCallback(
|
||||||
async (projectId: string | undefined | null, data: Partial<TIssue>) => {
|
async (projectId: string | undefined | null, data: Partial<TIssue>) => {
|
||||||
|
@ -28,7 +28,9 @@ export interface IArchivedIssuesFilter extends IBaseIssueFilterStore {
|
|||||||
//helper actions
|
//helper actions
|
||||||
getFilterParams: (
|
getFilterParams: (
|
||||||
options: IssuePaginationOptions,
|
options: IssuePaginationOptions,
|
||||||
cursor: string | undefined
|
cursor: string | undefined,
|
||||||
|
groupId: string | undefined,
|
||||||
|
subGroupId: string | undefined
|
||||||
) => Partial<Record<TIssueParams, string | boolean>>;
|
) => Partial<Record<TIssueParams, string | boolean>>;
|
||||||
// action
|
// action
|
||||||
fetchFilters: (workspaceSlug: string, projectId: string) => Promise<void>;
|
fetchFilters: (workspaceSlug: string, projectId: string) => Promise<void>;
|
||||||
@ -94,21 +96,19 @@ export class ArchivedIssuesFilter extends IssueFilterHelperStore implements IArc
|
|||||||
return filteredRouteParams;
|
return filteredRouteParams;
|
||||||
}
|
}
|
||||||
|
|
||||||
getFilterParams = computedFn((options: IssuePaginationOptions, cursor: string | undefined) => {
|
getFilterParams = computedFn(
|
||||||
const filterParams = this.appliedFilters;
|
(
|
||||||
|
options: IssuePaginationOptions,
|
||||||
|
cursor: string | undefined,
|
||||||
|
groupId: string | undefined,
|
||||||
|
subGroupId: string | undefined
|
||||||
|
) => {
|
||||||
|
const filterParams = this.appliedFilters;
|
||||||
|
|
||||||
const paginationOptions: Partial<Record<TIssueParams, string | boolean>> = {
|
const paginationParams = this.getPaginationParams(filterParams, options, cursor, groupId, subGroupId);
|
||||||
...filterParams,
|
return paginationParams;
|
||||||
cursor: cursor ? cursor : `${options.perPageCount}:0:0`,
|
|
||||||
per_page: options.perPageCount.toString(),
|
|
||||||
};
|
|
||||||
|
|
||||||
if (options.groupedBy) {
|
|
||||||
paginationOptions.group_by = options.groupedBy;
|
|
||||||
}
|
}
|
||||||
|
);
|
||||||
return paginationOptions;
|
|
||||||
});
|
|
||||||
|
|
||||||
fetchFilters = async (workspaceSlug: string, projectId: string) => {
|
fetchFilters = async (workspaceSlug: string, projectId: string) => {
|
||||||
try {
|
try {
|
||||||
|
@ -6,7 +6,7 @@ import { TLoader, ViewFlags, IssuePaginationOptions, TIssuesResponse } from "@pl
|
|||||||
// types
|
// types
|
||||||
import { IIssueRootStore } from "../root.store";
|
import { IIssueRootStore } from "../root.store";
|
||||||
import { IArchivedIssuesFilter } from "./filter.store";
|
import { IArchivedIssuesFilter } from "./filter.store";
|
||||||
import { BaseIssuesStore, IBaseIssuesStore } from "../helpers/base-issues.store";
|
import { BaseIssuesStore, EIssueGroupedAction, IBaseIssuesStore } from "../helpers/base-issues.store";
|
||||||
|
|
||||||
export interface IArchivedIssues extends IBaseIssuesStore {
|
export interface IArchivedIssues extends IBaseIssuesStore {
|
||||||
// observable
|
// observable
|
||||||
@ -23,7 +23,12 @@ export interface IArchivedIssues extends IBaseIssuesStore {
|
|||||||
projectId: string,
|
projectId: string,
|
||||||
loadType: TLoader
|
loadType: TLoader
|
||||||
) => Promise<TIssuesResponse | undefined>;
|
) => Promise<TIssuesResponse | undefined>;
|
||||||
fetchNextIssues: (workspaceSlug: string, projectId: string) => Promise<TIssuesResponse | undefined>;
|
fetchNextIssues: (
|
||||||
|
workspaceSlug: string,
|
||||||
|
projectId: string,
|
||||||
|
groupId?: string,
|
||||||
|
subGroupId?: string
|
||||||
|
) => Promise<TIssuesResponse | undefined>;
|
||||||
|
|
||||||
restoreIssue: (workspaceSlug: string, projectId: string, issueId: string) => Promise<void>;
|
restoreIssue: (workspaceSlug: string, projectId: string, issueId: string) => Promise<void>;
|
||||||
|
|
||||||
@ -66,7 +71,7 @@ export class ArchivedIssues extends BaseIssuesStore implements IArchivedIssues {
|
|||||||
this.loader = loadType;
|
this.loader = loadType;
|
||||||
});
|
});
|
||||||
this.clear();
|
this.clear();
|
||||||
const params = this.issueFilterStore?.getFilterParams(options, undefined);
|
const params = this.issueFilterStore?.getFilterParams(options, undefined, undefined, undefined);
|
||||||
const response = await this.issueArchiveService.getArchivedIssues(workspaceSlug, projectId, params);
|
const response = await this.issueArchiveService.getArchivedIssues(workspaceSlug, projectId, params);
|
||||||
|
|
||||||
this.onfetchIssues(response, options);
|
this.onfetchIssues(response, options);
|
||||||
@ -77,15 +82,21 @@ export class ArchivedIssues extends BaseIssuesStore implements IArchivedIssues {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
fetchNextIssues = async (workspaceSlug: string, projectId: string) => {
|
fetchNextIssues = async (workspaceSlug: string, projectId: string, groupId?: string, subGroupId?: string) => {
|
||||||
if (!this.paginationOptions || !this.next_page_results) return;
|
const cursorObject = this.getPaginationData(subGroupId ?? groupId);
|
||||||
|
if (!this.paginationOptions || (cursorObject && !cursorObject?.nextPageResults)) return;
|
||||||
try {
|
try {
|
||||||
this.loader = "pagination";
|
this.loader = "pagination";
|
||||||
|
|
||||||
const params = this.issueFilterStore?.getFilterParams(this.paginationOptions, this.nextCursor);
|
const params = this.issueFilterStore?.getFilterParams(
|
||||||
const response = await this.issueService.getIssues(workspaceSlug, projectId, params);
|
this.paginationOptions,
|
||||||
|
cursorObject?.nextCursor,
|
||||||
|
groupId,
|
||||||
|
subGroupId
|
||||||
|
);
|
||||||
|
const response = await this.issueArchiveService.getArchivedIssues(workspaceSlug, projectId, params);
|
||||||
|
|
||||||
this.onfetchNexIssues(response);
|
this.onfetchNexIssues(response, groupId, subGroupId);
|
||||||
return response;
|
return response;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.loader = undefined;
|
this.loader = undefined;
|
||||||
@ -110,7 +121,7 @@ export class ArchivedIssues extends BaseIssuesStore implements IArchivedIssues {
|
|||||||
this.rootIssueStore.issues.updateIssue(issueId, {
|
this.rootIssueStore.issues.updateIssue(issueId, {
|
||||||
archived_at: null,
|
archived_at: null,
|
||||||
});
|
});
|
||||||
this.issues && pull(this.issues, issueId);
|
this.removeIssueFromList(issueId);
|
||||||
});
|
});
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
|
@ -28,7 +28,9 @@ export interface ICycleIssuesFilter extends IBaseIssueFilterStore {
|
|||||||
//helper actions
|
//helper actions
|
||||||
getFilterParams: (
|
getFilterParams: (
|
||||||
options: IssuePaginationOptions,
|
options: IssuePaginationOptions,
|
||||||
cursor: string | undefined
|
cursor: string | undefined,
|
||||||
|
groupId: string | undefined,
|
||||||
|
subGroupId: string | undefined
|
||||||
) => Partial<Record<TIssueParams, string | boolean>>;
|
) => Partial<Record<TIssueParams, string | boolean>>;
|
||||||
// action
|
// action
|
||||||
fetchFilters: (workspaceSlug: string, projectId: string, cycleId: string) => Promise<void>;
|
fetchFilters: (workspaceSlug: string, projectId: string, cycleId: string) => Promise<void>;
|
||||||
@ -97,21 +99,19 @@ export class CycleIssuesFilter extends IssueFilterHelperStore implements ICycleI
|
|||||||
return filteredRouteParams;
|
return filteredRouteParams;
|
||||||
}
|
}
|
||||||
|
|
||||||
getFilterParams = computedFn((options: IssuePaginationOptions, cursor: string | undefined) => {
|
getFilterParams = computedFn(
|
||||||
const filterParams = this.appliedFilters;
|
(
|
||||||
|
options: IssuePaginationOptions,
|
||||||
|
cursor: string | undefined,
|
||||||
|
groupId: string | undefined,
|
||||||
|
subGroupId: string | undefined
|
||||||
|
) => {
|
||||||
|
const filterParams = this.appliedFilters;
|
||||||
|
|
||||||
const paginationOptions: Partial<Record<TIssueParams, string | boolean>> = {
|
const paginationParams = this.getPaginationParams(filterParams, options, cursor, groupId, subGroupId);
|
||||||
...filterParams,
|
return paginationParams;
|
||||||
cursor: cursor ? cursor : `${options.perPageCount}:0:0`,
|
|
||||||
per_page: options.perPageCount.toString(),
|
|
||||||
};
|
|
||||||
|
|
||||||
if (options.groupedBy) {
|
|
||||||
paginationOptions.group_by = options.groupedBy;
|
|
||||||
}
|
}
|
||||||
|
);
|
||||||
return paginationOptions;
|
|
||||||
});
|
|
||||||
|
|
||||||
fetchFilters = async (workspaceSlug: string, projectId: string, cycleId: string) => {
|
fetchFilters = async (workspaceSlug: string, projectId: string, cycleId: string) => {
|
||||||
try {
|
try {
|
||||||
|
@ -9,7 +9,7 @@ import { CycleService } from "services/cycle.service";
|
|||||||
// types
|
// types
|
||||||
import { TIssue, TLoader, ViewFlags, IssuePaginationOptions, TIssuesResponse } from "@plane/types";
|
import { TIssue, TLoader, ViewFlags, IssuePaginationOptions, TIssuesResponse } from "@plane/types";
|
||||||
import { IIssueRootStore } from "../root.store";
|
import { IIssueRootStore } from "../root.store";
|
||||||
import { BaseIssuesStore, IBaseIssuesStore } from "../helpers/base-issues.store";
|
import { BaseIssuesStore, EIssueGroupedAction, IBaseIssuesStore } from "../helpers/base-issues.store";
|
||||||
import { ICycleIssuesFilter } from "./filter.store";
|
import { ICycleIssuesFilter } from "./filter.store";
|
||||||
|
|
||||||
export const ACTIVE_CYCLE_ISSUES = "ACTIVE_CYCLE_ISSUES";
|
export const ACTIVE_CYCLE_ISSUES = "ACTIVE_CYCLE_ISSUES";
|
||||||
@ -30,7 +30,13 @@ export interface ICycleIssues extends IBaseIssuesStore {
|
|||||||
loadType: TLoader,
|
loadType: TLoader,
|
||||||
cycleId: string
|
cycleId: string
|
||||||
) => Promise<TIssuesResponse | undefined>;
|
) => Promise<TIssuesResponse | undefined>;
|
||||||
fetchNextIssues: (workspaceSlug: string, projectId: string, cycleId: string) => Promise<TIssuesResponse | undefined>;
|
fetchNextIssues: (
|
||||||
|
workspaceSlug: string,
|
||||||
|
projectId: string,
|
||||||
|
cycleId: string,
|
||||||
|
groupId?: string,
|
||||||
|
subGroupId?: string
|
||||||
|
) => Promise<TIssuesResponse | undefined>;
|
||||||
|
|
||||||
createIssue: (workspaceSlug: string, projectId: string, data: Partial<TIssue>, cycleId: string) => Promise<TIssue>;
|
createIssue: (workspaceSlug: string, projectId: string, data: Partial<TIssue>, cycleId: string) => Promise<TIssue>;
|
||||||
updateIssue: (workspaceSlug: string, projectId: string, issueId: string, data: Partial<TIssue>) => Promise<void>;
|
updateIssue: (workspaceSlug: string, projectId: string, issueId: string, data: Partial<TIssue>) => Promise<void>;
|
||||||
@ -111,7 +117,7 @@ export class CycleIssues extends BaseIssuesStore implements ICycleIssues {
|
|||||||
|
|
||||||
this.cycleId = cycleId;
|
this.cycleId = cycleId;
|
||||||
|
|
||||||
const params = this.issueFilterStore?.getFilterParams(options, undefined);
|
const params = this.issueFilterStore?.getFilterParams(options, undefined, undefined, undefined);
|
||||||
const response = await this.cycleService.getCycleIssues(workspaceSlug, projectId, cycleId, params);
|
const response = await this.cycleService.getCycleIssues(workspaceSlug, projectId, cycleId, params);
|
||||||
|
|
||||||
this.onfetchIssues(response, options);
|
this.onfetchIssues(response, options);
|
||||||
@ -122,15 +128,27 @@ export class CycleIssues extends BaseIssuesStore implements ICycleIssues {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
fetchNextIssues = async (workspaceSlug: string, projectId: string, cycleId: string) => {
|
fetchNextIssues = async (
|
||||||
if (!this.paginationOptions || !this.next_page_results) return;
|
workspaceSlug: string,
|
||||||
|
projectId: string,
|
||||||
|
cycleId: string,
|
||||||
|
groupId?: string,
|
||||||
|
subGroupId?: string
|
||||||
|
) => {
|
||||||
|
const cursorObject = this.getPaginationData(subGroupId ?? groupId);
|
||||||
|
if (!this.paginationOptions || (cursorObject && !cursorObject?.nextPageResults)) return;
|
||||||
try {
|
try {
|
||||||
this.loader = "pagination";
|
this.loader = "pagination";
|
||||||
|
|
||||||
const params = this.issueFilterStore?.getFilterParams(this.paginationOptions, this.nextCursor);
|
const params = this.issueFilterStore?.getFilterParams(
|
||||||
|
this.paginationOptions,
|
||||||
|
cursorObject?.nextCursor,
|
||||||
|
groupId,
|
||||||
|
subGroupId
|
||||||
|
);
|
||||||
const response = await this.cycleService.getCycleIssues(workspaceSlug, projectId, cycleId, params);
|
const response = await this.cycleService.getCycleIssues(workspaceSlug, projectId, cycleId, params);
|
||||||
|
|
||||||
this.onfetchNexIssues(response);
|
this.onfetchNexIssues(response, groupId, subGroupId);
|
||||||
return response;
|
return response;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.loader = undefined;
|
this.loader = undefined;
|
||||||
@ -175,8 +193,7 @@ export class CycleIssues extends BaseIssuesStore implements ICycleIssues {
|
|||||||
if (fetchAddedIssues) await this.rootIssueStore.issues.getIssues(workspaceSlug, projectId, issueIds);
|
if (fetchAddedIssues) await this.rootIssueStore.issues.getIssues(workspaceSlug, projectId, issueIds);
|
||||||
|
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
this.cycleId === cycleId &&
|
this.cycleId === cycleId && issueIds.forEach((issueId) => this.addIssueToList(issueId));
|
||||||
update(this, "issues", (cycleIssueIds = []) => uniq(concat(cycleIssueIds, issueIds)));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
issueIds.forEach((issueId) => {
|
issueIds.forEach((issueId) => {
|
||||||
@ -193,7 +210,7 @@ export class CycleIssues extends BaseIssuesStore implements ICycleIssues {
|
|||||||
try {
|
try {
|
||||||
await this.issueService.removeIssueFromCycle(workspaceSlug, projectId, cycleId, issueId);
|
await this.issueService.removeIssueFromCycle(workspaceSlug, projectId, cycleId, issueId);
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
this.issues && this.cycleId === cycleId && pull(this.issues, issueId);
|
this.cycleId === cycleId && this.removeIssueFromList(issueId);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.rootIssueStore.issues.updateIssue(issueId, { cycle_id: null });
|
this.rootIssueStore.issues.updateIssue(issueId, { cycle_id: null });
|
||||||
|
@ -28,7 +28,9 @@ export interface IDraftIssuesFilter extends IBaseIssueFilterStore {
|
|||||||
//helper actions
|
//helper actions
|
||||||
getFilterParams: (
|
getFilterParams: (
|
||||||
options: IssuePaginationOptions,
|
options: IssuePaginationOptions,
|
||||||
cursor: string | undefined
|
cursor: string | undefined,
|
||||||
|
groupId: string | undefined,
|
||||||
|
subGroupId: string | undefined
|
||||||
) => Partial<Record<TIssueParams, string | boolean>>;
|
) => Partial<Record<TIssueParams, string | boolean>>;
|
||||||
// action
|
// action
|
||||||
fetchFilters: (workspaceSlug: string, projectId: string) => Promise<void>;
|
fetchFilters: (workspaceSlug: string, projectId: string) => Promise<void>;
|
||||||
@ -94,25 +96,19 @@ export class DraftIssuesFilter extends IssueFilterHelperStore implements IDraftI
|
|||||||
return filteredRouteParams;
|
return filteredRouteParams;
|
||||||
}
|
}
|
||||||
|
|
||||||
getFilterParams = computedFn((options: IssuePaginationOptions, cursor: string | undefined) => {
|
getFilterParams = computedFn(
|
||||||
const filterParams = this.appliedFilters;
|
(
|
||||||
|
options: IssuePaginationOptions,
|
||||||
|
cursor: string | undefined,
|
||||||
|
groupId: string | undefined,
|
||||||
|
subGroupId: string | undefined
|
||||||
|
) => {
|
||||||
|
const filterParams = this.appliedFilters;
|
||||||
|
|
||||||
const paginationOptions: Partial<Record<TIssueParams, string | boolean>> = {
|
const paginationParams = this.getPaginationParams(filterParams, options, cursor, groupId, subGroupId);
|
||||||
...filterParams,
|
return paginationParams;
|
||||||
cursor: cursor ? cursor : `${options.perPageCount}:0:0`,
|
|
||||||
per_page: options.perPageCount.toString(),
|
|
||||||
};
|
|
||||||
|
|
||||||
if (options.groupedBy) {
|
|
||||||
paginationOptions.group_by = options.groupedBy;
|
|
||||||
}
|
}
|
||||||
|
);
|
||||||
if (options.after && options.before) {
|
|
||||||
paginationOptions["target_date"] = `${options.after};after,${options.before};before`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return paginationOptions;
|
|
||||||
});
|
|
||||||
|
|
||||||
fetchFilters = async (workspaceSlug: string, projectId: string) => {
|
fetchFilters = async (workspaceSlug: string, projectId: string) => {
|
||||||
try {
|
try {
|
||||||
|
@ -24,7 +24,12 @@ export interface IDraftIssues extends IBaseIssuesStore {
|
|||||||
loadType: TLoader
|
loadType: TLoader
|
||||||
) => Promise<TIssuesResponse | undefined>;
|
) => Promise<TIssuesResponse | undefined>;
|
||||||
|
|
||||||
fetchNextIssues: (workspaceSlug: string, projectId: string) => Promise<TIssuesResponse | undefined>;
|
fetchNextIssues: (
|
||||||
|
workspaceSlug: string,
|
||||||
|
projectId: string,
|
||||||
|
groupId?: string,
|
||||||
|
subGroupId?: string
|
||||||
|
) => Promise<TIssuesResponse | undefined>;
|
||||||
createIssue: (workspaceSlug: string, projectId: string, data: Partial<TIssue>) => Promise<TIssue>;
|
createIssue: (workspaceSlug: string, projectId: string, data: Partial<TIssue>) => Promise<TIssue>;
|
||||||
updateIssue: (workspaceSlug: string, projectId: string, issueId: string, data: Partial<TIssue>) => Promise<void>;
|
updateIssue: (workspaceSlug: string, projectId: string, issueId: string, data: Partial<TIssue>) => Promise<void>;
|
||||||
|
|
||||||
@ -63,7 +68,7 @@ export class DraftIssues extends BaseIssuesStore implements IDraftIssues {
|
|||||||
this.loader = loadType;
|
this.loader = loadType;
|
||||||
});
|
});
|
||||||
this.clear();
|
this.clear();
|
||||||
const params = this.issueFilterStore?.getFilterParams(options, undefined);
|
const params = this.issueFilterStore?.getFilterParams(options, undefined, undefined, undefined);
|
||||||
const response = await this.issueDraftService.getDraftIssues(workspaceSlug, projectId, params);
|
const response = await this.issueDraftService.getDraftIssues(workspaceSlug, projectId, params);
|
||||||
|
|
||||||
this.onfetchIssues(response, options);
|
this.onfetchIssues(response, options);
|
||||||
@ -74,15 +79,21 @@ export class DraftIssues extends BaseIssuesStore implements IDraftIssues {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
fetchNextIssues = async (workspaceSlug: string, projectId: string) => {
|
fetchNextIssues = async (workspaceSlug: string, projectId: string, groupId?: string, subGroupId?: string) => {
|
||||||
if (!this.paginationOptions || !this.next_page_results) return;
|
const cursorObject = this.getPaginationData(subGroupId ?? groupId);
|
||||||
|
if (!this.paginationOptions || (cursorObject && !cursorObject?.nextPageResults)) return;
|
||||||
try {
|
try {
|
||||||
this.loader = "pagination";
|
this.loader = "pagination";
|
||||||
|
|
||||||
const params = this.issueFilterStore?.getFilterParams(this.paginationOptions, this.nextCursor);
|
const params = this.issueFilterStore?.getFilterParams(
|
||||||
const response = await this.issueService.getIssues(workspaceSlug, projectId, params);
|
this.paginationOptions,
|
||||||
|
cursorObject?.nextCursor,
|
||||||
|
groupId,
|
||||||
|
subGroupId
|
||||||
|
);
|
||||||
|
const response = await this.issueDraftService.getDraftIssues(workspaceSlug, projectId, params);
|
||||||
|
|
||||||
this.onfetchNexIssues(response);
|
this.onfetchNexIssues(response, groupId, subGroupId);
|
||||||
return response;
|
return response;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.loader = undefined;
|
this.loader = undefined;
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -6,12 +6,18 @@ import {
|
|||||||
IIssueFilterOptions,
|
IIssueFilterOptions,
|
||||||
IIssueFilters,
|
IIssueFilters,
|
||||||
IIssueFiltersResponse,
|
IIssueFiltersResponse,
|
||||||
|
IssuePaginationOptions,
|
||||||
TIssueKanbanFilters,
|
TIssueKanbanFilters,
|
||||||
TIssueParams,
|
TIssueParams,
|
||||||
TStaticViewTypes,
|
TStaticViewTypes,
|
||||||
} from "@plane/types";
|
} from "@plane/types";
|
||||||
// constants
|
// constants
|
||||||
import { EIssueFilterType, EIssuesStoreType, IssueGroupByOptions } from "constants/issue";
|
import {
|
||||||
|
EIssueFilterType,
|
||||||
|
EIssuesStoreType,
|
||||||
|
EIssueGroupByToServerOptions,
|
||||||
|
EServerGroupByToFilterOptions,
|
||||||
|
} from "constants/issue";
|
||||||
// lib
|
// lib
|
||||||
import { storage } from "lib/local-storage";
|
import { storage } from "lib/local-storage";
|
||||||
|
|
||||||
@ -89,7 +95,10 @@ export class IssueFilterHelperStore implements IIssueFilterHelperStore {
|
|||||||
project: filters?.project || undefined,
|
project: filters?.project || undefined,
|
||||||
subscriber: filters?.subscriber || undefined,
|
subscriber: filters?.subscriber || undefined,
|
||||||
// display filters
|
// display filters
|
||||||
group_by: displayFilters?.group_by ? IssueGroupByOptions[displayFilters.group_by] : undefined,
|
group_by: displayFilters?.group_by ? EIssueGroupByToServerOptions[displayFilters.group_by] : undefined,
|
||||||
|
sub_group_by: displayFilters?.sub_group_by
|
||||||
|
? EIssueGroupByToServerOptions[displayFilters.sub_group_by]
|
||||||
|
: undefined,
|
||||||
order_by: displayFilters?.order_by || undefined,
|
order_by: displayFilters?.order_by || undefined,
|
||||||
type: displayFilters?.type || undefined,
|
type: displayFilters?.type || undefined,
|
||||||
sub_issue: displayFilters?.sub_issue ?? true,
|
sub_issue: displayFilters?.sub_issue ?? true,
|
||||||
@ -209,7 +218,6 @@ export class IssueFilterHelperStore implements IIssueFilterHelperStore {
|
|||||||
cycle: displayProperties?.cycle ?? true,
|
cycle: displayProperties?.cycle ?? true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
handleIssuesLocalFilters = {
|
handleIssuesLocalFilters = {
|
||||||
fetchFiltersFromStorage: () => {
|
fetchFiltersFromStorage: () => {
|
||||||
const _filters = storage.get("issue_local_filters");
|
const _filters = storage.get("issue_local_filters");
|
||||||
@ -272,4 +280,64 @@ export class IssueFilterHelperStore implements IIssueFilterHelperStore {
|
|||||||
storage.set("issue_local_filters", JSON.stringify(storageFilters));
|
storage.set("issue_local_filters", JSON.stringify(storageFilters));
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This Method returns true if the display properties changed requires a server side update
|
||||||
|
* @param displayFilters
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
requiresServerUpdate = (displayFilters: IIssueDisplayFilterOptions) => {
|
||||||
|
const NON_SERVER_DISPLAY_FILTERS = ["order_by", "sub_issue", "type"];
|
||||||
|
const displayFilterKeys = Object.keys(displayFilters);
|
||||||
|
|
||||||
|
return NON_SERVER_DISPLAY_FILTERS.some((serverDisplayfilter: string) =>
|
||||||
|
displayFilterKeys.includes(serverDisplayfilter)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
getPaginationParams(
|
||||||
|
filterParams: Partial<Record<TIssueParams, string | boolean>> | undefined,
|
||||||
|
options: IssuePaginationOptions,
|
||||||
|
cursor: string | undefined,
|
||||||
|
groupId?: string,
|
||||||
|
subGroupId?: string
|
||||||
|
) {
|
||||||
|
const pageCursor = cursor ? cursor : groupId ? `${options.perPageCount * 2}:0:0` : `${options.perPageCount}:0:0`;
|
||||||
|
|
||||||
|
const paginationParams: Partial<Record<TIssueParams, string | boolean>> = {
|
||||||
|
...filterParams,
|
||||||
|
cursor: pageCursor,
|
||||||
|
per_page: (groupId ? options.perPageCount * 2 : options.perPageCount).toString(),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (options.groupedBy) {
|
||||||
|
paginationParams.group_by = options.groupedBy;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.after && options.before) {
|
||||||
|
paginationParams["target_date"] = `${options.after};after,${options.before};before`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (groupId) {
|
||||||
|
const groupBy = paginationParams["group_by"] as EIssueGroupByToServerOptions | undefined;
|
||||||
|
delete paginationParams["group_by"];
|
||||||
|
|
||||||
|
if (groupBy) {
|
||||||
|
const groupByFilterOption = EServerGroupByToFilterOptions[groupBy];
|
||||||
|
paginationParams[groupByFilterOption] = groupId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (subGroupId) {
|
||||||
|
const subGroupBy = paginationParams["sub_group_by"] as EIssueGroupByToServerOptions | undefined;
|
||||||
|
delete paginationParams["sub_group_by"];
|
||||||
|
|
||||||
|
if (subGroupBy) {
|
||||||
|
const subGroupByFilterOption = EServerGroupByToFilterOptions[subGroupBy];
|
||||||
|
paginationParams[subGroupByFilterOption] = subGroupId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return paginationParams;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,7 @@ export type IIssueStore = {
|
|||||||
removeIssue(issueId: string): void;
|
removeIssue(issueId: string): void;
|
||||||
// helper methods
|
// helper methods
|
||||||
getIssueById(issueId: string): undefined | TIssue;
|
getIssueById(issueId: string): undefined | TIssue;
|
||||||
getIssuesByIds(issueIds: string[], type: "archived" | "un-archived"): undefined | Record<string, TIssue>; // Record defines issue_id as key and TIssue as value
|
getIssuesByIds(issueIds: string[], type: "archived" | "un-archived"): TIssue[]; // Record defines issue_id as key and TIssue as value
|
||||||
};
|
};
|
||||||
|
|
||||||
export class IssueStore implements IIssueStore {
|
export class IssueStore implements IIssueStore {
|
||||||
@ -112,15 +112,16 @@ export class IssueStore implements IIssueStore {
|
|||||||
* @returns {Record<string, TIssue> | undefined}
|
* @returns {Record<string, TIssue> | undefined}
|
||||||
*/
|
*/
|
||||||
getIssuesByIds = computedFn((issueIds: string[], type: "archived" | "un-archived") => {
|
getIssuesByIds = computedFn((issueIds: string[], type: "archived" | "un-archived") => {
|
||||||
if (!issueIds || issueIds.length <= 0 || isEmpty(this.issuesMap)) return undefined;
|
if (!issueIds || issueIds.length <= 0 || isEmpty(this.issuesMap)) return [];
|
||||||
const filteredIssues: { [key: string]: TIssue } = {};
|
const filteredIssues: TIssue[] = [];
|
||||||
Object.values(this.issuesMap).forEach((issue) => {
|
Object.values(issueIds).forEach((issueId) => {
|
||||||
// if type is archived then check archived_at is not null
|
// if type is archived then check archived_at is not null
|
||||||
// if type is un-archived then check archived_at is null
|
// if type is un-archived then check archived_at is null
|
||||||
if ((type === "archived" && issue.archived_at) || (type === "un-archived" && !issue.archived_at)) {
|
const issue = this.issuesMap[issueId];
|
||||||
if (issueIds.includes(issue.id)) filteredIssues[issue.id] = issue;
|
if ((issue && type === "archived" && issue.archived_at) || (type === "un-archived" && !issue?.archived_at)) {
|
||||||
|
filteredIssues.push(issue);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return isEmpty(filteredIssues) ? undefined : filteredIssues;
|
return filteredIssues;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -28,7 +28,9 @@ export interface IModuleIssuesFilter extends IBaseIssueFilterStore {
|
|||||||
//helper actions
|
//helper actions
|
||||||
getFilterParams: (
|
getFilterParams: (
|
||||||
options: IssuePaginationOptions,
|
options: IssuePaginationOptions,
|
||||||
cursor: string | undefined
|
cursor: string | undefined,
|
||||||
|
groupId: string | undefined,
|
||||||
|
subGroupId: string | undefined
|
||||||
) => Partial<Record<TIssueParams, string | boolean>>;
|
) => Partial<Record<TIssueParams, string | boolean>>;
|
||||||
// action
|
// action
|
||||||
fetchFilters: (workspaceSlug: string, projectId: string, moduleId: string) => Promise<void>;
|
fetchFilters: (workspaceSlug: string, projectId: string, moduleId: string) => Promise<void>;
|
||||||
@ -97,21 +99,19 @@ export class ModuleIssuesFilter extends IssueFilterHelperStore implements IModul
|
|||||||
return filteredRouteParams;
|
return filteredRouteParams;
|
||||||
}
|
}
|
||||||
|
|
||||||
getFilterParams = computedFn((options: IssuePaginationOptions, cursor: string | undefined) => {
|
getFilterParams = computedFn(
|
||||||
const filterParams = this.appliedFilters;
|
(
|
||||||
|
options: IssuePaginationOptions,
|
||||||
|
cursor: string | undefined,
|
||||||
|
groupId: string | undefined,
|
||||||
|
subGroupId: string | undefined
|
||||||
|
) => {
|
||||||
|
const filterParams = this.appliedFilters;
|
||||||
|
|
||||||
const paginationOptions: Partial<Record<TIssueParams, string | boolean>> = {
|
const paginationParams = this.getPaginationParams(filterParams, options, cursor, groupId, subGroupId);
|
||||||
...filterParams,
|
return paginationParams;
|
||||||
cursor: cursor ? cursor : `${options.perPageCount}:0:0`,
|
|
||||||
per_page: options.perPageCount.toString(),
|
|
||||||
};
|
|
||||||
|
|
||||||
if (options.groupedBy) {
|
|
||||||
paginationOptions.group_by = options.groupedBy;
|
|
||||||
}
|
}
|
||||||
|
);
|
||||||
return paginationOptions;
|
|
||||||
});
|
|
||||||
|
|
||||||
fetchFilters = async (workspaceSlug: string, projectId: string, moduleId: string) => {
|
fetchFilters = async (workspaceSlug: string, projectId: string, moduleId: string) => {
|
||||||
try {
|
try {
|
||||||
|
@ -28,7 +28,13 @@ export interface IModuleIssues extends IBaseIssuesStore {
|
|||||||
loadType: TLoader,
|
loadType: TLoader,
|
||||||
moduleId: string
|
moduleId: string
|
||||||
) => Promise<TIssuesResponse | undefined>;
|
) => Promise<TIssuesResponse | undefined>;
|
||||||
fetchNextIssues: (workspaceSlug: string, projectId: string, moduleId: string) => Promise<TIssuesResponse | undefined>;
|
fetchNextIssues: (
|
||||||
|
workspaceSlug: string,
|
||||||
|
projectId: string,
|
||||||
|
moduleId: string,
|
||||||
|
groupId?: string,
|
||||||
|
subGroupId?: string
|
||||||
|
) => Promise<TIssuesResponse | undefined>;
|
||||||
|
|
||||||
createIssue: (workspaceSlug: string, projectId: string, data: Partial<TIssue>, moduleId: string) => Promise<TIssue>;
|
createIssue: (workspaceSlug: string, projectId: string, data: Partial<TIssue>, moduleId: string) => Promise<TIssue>;
|
||||||
updateIssue: (workspaceSlug: string, projectId: string, issueId: string, data: Partial<TIssue>) => Promise<void>;
|
updateIssue: (workspaceSlug: string, projectId: string, issueId: string, data: Partial<TIssue>) => Promise<void>;
|
||||||
@ -111,7 +117,7 @@ export class ModuleIssues extends BaseIssuesStore implements IModuleIssues {
|
|||||||
|
|
||||||
this.moduleId = moduleId;
|
this.moduleId = moduleId;
|
||||||
|
|
||||||
const params = this.issueFilterStore?.getFilterParams(options, undefined);
|
const params = this.issueFilterStore?.getFilterParams(options, undefined, undefined, undefined);
|
||||||
const response = await this.moduleService.getModuleIssues(workspaceSlug, projectId, moduleId, params);
|
const response = await this.moduleService.getModuleIssues(workspaceSlug, projectId, moduleId, params);
|
||||||
|
|
||||||
this.onfetchIssues(response, options);
|
this.onfetchIssues(response, options);
|
||||||
@ -122,15 +128,27 @@ export class ModuleIssues extends BaseIssuesStore implements IModuleIssues {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
fetchNextIssues = async (workspaceSlug: string, projectId: string, moduleId: string) => {
|
fetchNextIssues = async (
|
||||||
if (!this.paginationOptions || !this.next_page_results) return;
|
workspaceSlug: string,
|
||||||
|
projectId: string,
|
||||||
|
moduleId: string,
|
||||||
|
groupId?: string,
|
||||||
|
subGroupId?: string
|
||||||
|
) => {
|
||||||
|
const cursorObject = this.getPaginationData(subGroupId ?? groupId);
|
||||||
|
if (!this.paginationOptions || (cursorObject && !cursorObject?.nextPageResults)) return;
|
||||||
try {
|
try {
|
||||||
this.loader = "pagination";
|
this.loader = "pagination";
|
||||||
|
|
||||||
const params = this.issueFilterStore?.getFilterParams(this.paginationOptions, this.nextCursor);
|
const params = this.issueFilterStore?.getFilterParams(
|
||||||
|
this.paginationOptions,
|
||||||
|
cursorObject?.nextCursor,
|
||||||
|
groupId,
|
||||||
|
subGroupId
|
||||||
|
);
|
||||||
const response = await this.moduleService.getModuleIssues(workspaceSlug, projectId, moduleId, params);
|
const response = await this.moduleService.getModuleIssues(workspaceSlug, projectId, moduleId, params);
|
||||||
|
|
||||||
this.onfetchNexIssues(response);
|
this.onfetchNexIssues(response, groupId, subGroupId);
|
||||||
return response;
|
return response;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.loader = undefined;
|
this.loader = undefined;
|
||||||
@ -177,11 +195,7 @@ export class ModuleIssues extends BaseIssuesStore implements IModuleIssues {
|
|||||||
if (fetchAddedIssues) await this.rootIssueStore.issues.getIssues(workspaceSlug, projectId, issueIds);
|
if (fetchAddedIssues) await this.rootIssueStore.issues.getIssues(workspaceSlug, projectId, issueIds);
|
||||||
|
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
this.moduleId === moduleId &&
|
this.moduleId === moduleId && issueIds.forEach((issueId) => this.addIssueToList(issueId));
|
||||||
update(this, "issues", (moduleIssueIds = []) => {
|
|
||||||
if (!moduleIssueIds) return [...issueIds];
|
|
||||||
else return uniq(concat(moduleIssueIds, issueIds));
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
issueIds.forEach((issueId) => {
|
issueIds.forEach((issueId) => {
|
||||||
@ -207,10 +221,7 @@ export class ModuleIssues extends BaseIssuesStore implements IModuleIssues {
|
|||||||
);
|
);
|
||||||
|
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
this.moduleId === moduleId &&
|
this.moduleId === moduleId && issueIds.forEach((issueId) => this.removeIssueFromList(issueId));
|
||||||
issueIds.forEach((issueId) => {
|
|
||||||
this.issues && pull(this.issues, issueId);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
@ -238,11 +249,7 @@ export class ModuleIssues extends BaseIssuesStore implements IModuleIssues {
|
|||||||
|
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
moduleIds.forEach((moduleId) => {
|
moduleIds.forEach((moduleId) => {
|
||||||
this.moduleId === moduleId &&
|
this.moduleId === moduleId && this.addIssueToList(issueId);
|
||||||
update(this, "issues", (moduleIssueIds = []) => {
|
|
||||||
if (moduleIssueIds.includes(issueId)) return moduleIssueIds;
|
|
||||||
else return uniq(concat(moduleIssueIds, [issueId]));
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
update(this.rootIssueStore.issues.issuesMap, [issueId, "module_ids"], (issueModuleIds = []) =>
|
update(this.rootIssueStore.issues.issuesMap, [issueId, "module_ids"], (issueModuleIds = []) =>
|
||||||
uniq(concat(issueModuleIds, moduleIds))
|
uniq(concat(issueModuleIds, moduleIds))
|
||||||
@ -259,11 +266,7 @@ export class ModuleIssues extends BaseIssuesStore implements IModuleIssues {
|
|||||||
try {
|
try {
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
moduleIds.forEach((moduleId) => {
|
moduleIds.forEach((moduleId) => {
|
||||||
this.moduleId === moduleId &&
|
this.moduleId === moduleId && this.removeIssueFromList(issueId);
|
||||||
update(this, "issues", (moduleIssueIds = []) => {
|
|
||||||
if (moduleIssueIds.includes(issueId)) return pull(moduleIssueIds, issueId);
|
|
||||||
else return uniq(concat(moduleIssueIds, [issueId]));
|
|
||||||
});
|
|
||||||
update(this.rootIssueStore.issues.issuesMap, [issueId, "module_ids"], (issueModuleIds = []) =>
|
update(this.rootIssueStore.issues.issuesMap, [issueId, "module_ids"], (issueModuleIds = []) =>
|
||||||
pull(issueModuleIds, moduleId)
|
pull(issueModuleIds, moduleId)
|
||||||
);
|
);
|
||||||
@ -286,7 +289,7 @@ export class ModuleIssues extends BaseIssuesStore implements IModuleIssues {
|
|||||||
removeIssueFromModule = async (workspaceSlug: string, projectId: string, moduleId: string, issueId: string) => {
|
removeIssueFromModule = async (workspaceSlug: string, projectId: string, moduleId: string, issueId: string) => {
|
||||||
try {
|
try {
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
this.issues && this.moduleId === this.moduleId && pull(this.issues, issueId);
|
this.moduleId === this.moduleId && this.removeIssueFromList(issueId);
|
||||||
update(this.rootIssueStore.issues.issuesMap, [issueId, "module_ids"], (issueModuleIds = []) =>
|
update(this.rootIssueStore.issues.issuesMap, [issueId, "module_ids"], (issueModuleIds = []) =>
|
||||||
pull(issueModuleIds, moduleId)
|
pull(issueModuleIds, moduleId)
|
||||||
);
|
);
|
||||||
|
@ -30,7 +30,9 @@ export interface IProfileIssuesFilter extends IBaseIssueFilterStore {
|
|||||||
//helper actions
|
//helper actions
|
||||||
getFilterParams: (
|
getFilterParams: (
|
||||||
options: IssuePaginationOptions,
|
options: IssuePaginationOptions,
|
||||||
cursor: string | undefined
|
cursor: string | undefined,
|
||||||
|
groupId: string | undefined,
|
||||||
|
subGroupId: string | undefined
|
||||||
) => Partial<Record<TIssueParams, string | boolean>>;
|
) => Partial<Record<TIssueParams, string | boolean>>;
|
||||||
// action
|
// action
|
||||||
fetchFilters: (workspaceSlug: string, userId: string) => Promise<void>;
|
fetchFilters: (workspaceSlug: string, userId: string) => Promise<void>;
|
||||||
@ -99,21 +101,19 @@ export class ProfileIssuesFilter extends IssueFilterHelperStore implements IProf
|
|||||||
return filteredRouteParams;
|
return filteredRouteParams;
|
||||||
}
|
}
|
||||||
|
|
||||||
getFilterParams = computedFn((options: IssuePaginationOptions, cursor: string | undefined) => {
|
getFilterParams = computedFn(
|
||||||
const filterParams = this.appliedFilters;
|
(
|
||||||
|
options: IssuePaginationOptions,
|
||||||
|
cursor: string | undefined,
|
||||||
|
groupId: string | undefined,
|
||||||
|
subGroupId: string | undefined
|
||||||
|
) => {
|
||||||
|
const filterParams = this.appliedFilters;
|
||||||
|
|
||||||
const paginationOptions: Partial<Record<TIssueParams, string | boolean>> = {
|
const paginationParams = this.getPaginationParams(filterParams, options, cursor, groupId, subGroupId);
|
||||||
...filterParams,
|
return paginationParams;
|
||||||
cursor: cursor ? cursor : `${options.perPageCount}:0:0`,
|
|
||||||
per_page: options.perPageCount.toString(),
|
|
||||||
};
|
|
||||||
|
|
||||||
if (options.groupedBy) {
|
|
||||||
paginationOptions.group_by = options.groupedBy;
|
|
||||||
}
|
}
|
||||||
|
);
|
||||||
return paginationOptions;
|
|
||||||
});
|
|
||||||
|
|
||||||
fetchFilters = async (workspaceSlug: string, userId: string) => {
|
fetchFilters = async (workspaceSlug: string, userId: string) => {
|
||||||
try {
|
try {
|
||||||
|
@ -27,7 +27,12 @@ export interface IProfileIssues extends IBaseIssuesStore {
|
|||||||
userId: string,
|
userId: string,
|
||||||
loadType: TLoader
|
loadType: TLoader
|
||||||
) => Promise<TIssuesResponse | undefined>;
|
) => Promise<TIssuesResponse | undefined>;
|
||||||
fetchNextIssues: (workspaceSlug: string, userId: string) => Promise<TIssuesResponse | undefined>;
|
fetchNextIssues: (
|
||||||
|
workspaceSlug: string,
|
||||||
|
userId: string,
|
||||||
|
groupId?: string,
|
||||||
|
subGroupId?: string
|
||||||
|
) => Promise<TIssuesResponse | undefined>;
|
||||||
|
|
||||||
createIssue: (workspaceSlug: string, projectId: string, data: Partial<TIssue>) => Promise<TIssue>;
|
createIssue: (workspaceSlug: string, projectId: string, data: Partial<TIssue>) => Promise<TIssue>;
|
||||||
updateIssue: (workspaceSlug: string, projectId: string, issueId: string, data: Partial<TIssue>) => Promise<void>;
|
updateIssue: (workspaceSlug: string, projectId: string, issueId: string, data: Partial<TIssue>) => Promise<void>;
|
||||||
@ -95,7 +100,7 @@ export class ProfileIssues extends BaseIssuesStore implements IProfileIssues {
|
|||||||
|
|
||||||
this.setViewId(view);
|
this.setViewId(view);
|
||||||
|
|
||||||
let params = this.issueFilterStore?.getFilterParams(options, undefined);
|
let params = this.issueFilterStore?.getFilterParams(options, undefined, undefined, undefined);
|
||||||
params = {
|
params = {
|
||||||
...params,
|
...params,
|
||||||
assignees: undefined,
|
assignees: undefined,
|
||||||
@ -116,12 +121,18 @@ export class ProfileIssues extends BaseIssuesStore implements IProfileIssues {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
fetchNextIssues = async (workspaceSlug: string, userId: string) => {
|
fetchNextIssues = async (workspaceSlug: string, userId: string, groupId?: string, subGroupId?: string) => {
|
||||||
if (!this.paginationOptions || !this.currentView || !this.next_page_results) return;
|
const cursorObject = this.getPaginationData(subGroupId ?? groupId);
|
||||||
|
if (!this.paginationOptions || (cursorObject && !cursorObject?.nextPageResults)) return;
|
||||||
try {
|
try {
|
||||||
this.loader = "pagination";
|
this.loader = "pagination";
|
||||||
|
|
||||||
let params = this.issueFilterStore?.getFilterParams(this.paginationOptions, this.nextCursor);
|
let params = this.issueFilterStore?.getFilterParams(
|
||||||
|
this.paginationOptions,
|
||||||
|
cursorObject?.nextCursor,
|
||||||
|
groupId,
|
||||||
|
subGroupId
|
||||||
|
);
|
||||||
params = {
|
params = {
|
||||||
...params,
|
...params,
|
||||||
assignees: undefined,
|
assignees: undefined,
|
||||||
@ -134,7 +145,7 @@ export class ProfileIssues extends BaseIssuesStore implements IProfileIssues {
|
|||||||
|
|
||||||
const response = await this.userService.getUserProfileIssues(workspaceSlug, userId, params);
|
const response = await this.userService.getUserProfileIssues(workspaceSlug, userId, params);
|
||||||
|
|
||||||
this.onfetchNexIssues(response);
|
this.onfetchNexIssues(response, groupId, subGroupId);
|
||||||
return response;
|
return response;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.loader = undefined;
|
this.loader = undefined;
|
||||||
|
@ -28,7 +28,9 @@ export interface IProjectViewIssuesFilter extends IBaseIssueFilterStore {
|
|||||||
//helper actions
|
//helper actions
|
||||||
getFilterParams: (
|
getFilterParams: (
|
||||||
options: IssuePaginationOptions,
|
options: IssuePaginationOptions,
|
||||||
cursor: string | undefined
|
cursor: string | undefined,
|
||||||
|
groupId: string | undefined,
|
||||||
|
subGroupId: string | undefined
|
||||||
) => Partial<Record<TIssueParams, string | boolean>>;
|
) => Partial<Record<TIssueParams, string | boolean>>;
|
||||||
// action
|
// action
|
||||||
fetchFilters: (workspaceSlug: string, projectId: string, viewId: string) => Promise<void>;
|
fetchFilters: (workspaceSlug: string, projectId: string, viewId: string) => Promise<void>;
|
||||||
@ -95,21 +97,19 @@ export class ProjectViewIssuesFilter extends IssueFilterHelperStore implements I
|
|||||||
return filteredRouteParams;
|
return filteredRouteParams;
|
||||||
}
|
}
|
||||||
|
|
||||||
getFilterParams = computedFn((options: IssuePaginationOptions, cursor: string | undefined) => {
|
getFilterParams = computedFn(
|
||||||
const filterParams = this.appliedFilters;
|
(
|
||||||
|
options: IssuePaginationOptions,
|
||||||
|
cursor: string | undefined,
|
||||||
|
groupId: string | undefined,
|
||||||
|
subGroupId: string | undefined
|
||||||
|
) => {
|
||||||
|
const filterParams = this.appliedFilters;
|
||||||
|
|
||||||
const paginationOptions: Partial<Record<TIssueParams, string | boolean>> = {
|
const paginationParams = this.getPaginationParams(filterParams, options, cursor, groupId, subGroupId);
|
||||||
...filterParams,
|
return paginationParams;
|
||||||
cursor: cursor ? cursor : `${options.perPageCount}:0:0`,
|
|
||||||
per_page: options.perPageCount.toString(),
|
|
||||||
};
|
|
||||||
|
|
||||||
if (options.groupedBy) {
|
|
||||||
paginationOptions.group_by = options.groupedBy;
|
|
||||||
}
|
}
|
||||||
|
);
|
||||||
return paginationOptions;
|
|
||||||
});
|
|
||||||
|
|
||||||
fetchFilters = async (workspaceSlug: string, projectId: string, viewId: string) => {
|
fetchFilters = async (workspaceSlug: string, projectId: string, viewId: string) => {
|
||||||
try {
|
try {
|
||||||
|
@ -21,7 +21,12 @@ export interface IProjectViewIssues extends IBaseIssuesStore {
|
|||||||
projectId: string,
|
projectId: string,
|
||||||
loadType: TLoader
|
loadType: TLoader
|
||||||
) => Promise<TIssuesResponse | undefined>;
|
) => Promise<TIssuesResponse | undefined>;
|
||||||
fetchNextIssues: (workspaceSlug: string, projectId: string) => Promise<TIssuesResponse | undefined>;
|
fetchNextIssues: (
|
||||||
|
workspaceSlug: string,
|
||||||
|
projectId: string,
|
||||||
|
groupId?: string,
|
||||||
|
subGroupId?: string
|
||||||
|
) => Promise<TIssuesResponse | undefined>;
|
||||||
|
|
||||||
createIssue: (workspaceSlug: string, projectId: string, data: Partial<TIssue>) => Promise<TIssue>;
|
createIssue: (workspaceSlug: string, projectId: string, data: Partial<TIssue>) => Promise<TIssue>;
|
||||||
updateIssue: (workspaceSlug: string, projectId: string, issueId: string, data: Partial<TIssue>) => Promise<void>;
|
updateIssue: (workspaceSlug: string, projectId: string, issueId: string, data: Partial<TIssue>) => Promise<void>;
|
||||||
@ -62,7 +67,7 @@ export class ProjectViewIssues extends BaseIssuesStore implements IProjectViewIs
|
|||||||
this.loader = loadType;
|
this.loader = loadType;
|
||||||
});
|
});
|
||||||
this.clear();
|
this.clear();
|
||||||
const params = this.issueFilterStore?.getFilterParams(options, undefined);
|
const params = this.issueFilterStore?.getFilterParams(options, undefined, undefined, undefined);
|
||||||
const response = await this.issueService.getIssues(workspaceSlug, projectId, params);
|
const response = await this.issueService.getIssues(workspaceSlug, projectId, params);
|
||||||
|
|
||||||
this.onfetchIssues(response, options);
|
this.onfetchIssues(response, options);
|
||||||
@ -73,15 +78,21 @@ export class ProjectViewIssues extends BaseIssuesStore implements IProjectViewIs
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
fetchNextIssues = async (workspaceSlug: string, projectId: string) => {
|
fetchNextIssues = async (workspaceSlug: string, projectId: string, groupId?: string, subGroupId?: string) => {
|
||||||
if (!this.paginationOptions || !this.next_page_results) return;
|
const cursorObject = this.getPaginationData(subGroupId ?? groupId);
|
||||||
|
if (!this.paginationOptions || (cursorObject && !cursorObject?.nextPageResults)) return;
|
||||||
try {
|
try {
|
||||||
this.loader = "pagination";
|
this.loader = "pagination";
|
||||||
|
|
||||||
const params = this.issueFilterStore?.getFilterParams(this.paginationOptions, this.nextCursor);
|
let params = this.issueFilterStore?.getFilterParams(
|
||||||
|
this.paginationOptions,
|
||||||
|
cursorObject?.nextCursor,
|
||||||
|
groupId,
|
||||||
|
subGroupId
|
||||||
|
);
|
||||||
const response = await this.issueService.getIssues(workspaceSlug, projectId, params);
|
const response = await this.issueService.getIssues(workspaceSlug, projectId, params);
|
||||||
|
|
||||||
this.onfetchNexIssues(response);
|
this.onfetchNexIssues(response, groupId, subGroupId);
|
||||||
return response;
|
return response;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.loader = undefined;
|
this.loader = undefined;
|
||||||
|
@ -20,7 +20,7 @@ import { IBaseIssueFilterStore, IssueFilterHelperStore } from "../helpers/issue-
|
|||||||
// types
|
// types
|
||||||
import { IIssueRootStore } from "../root.store";
|
import { IIssueRootStore } from "../root.store";
|
||||||
// constants
|
// constants
|
||||||
import { EIssueFilterType, EIssuesStoreType, IssueGroupByOptions } from "constants/issue";
|
import { EIssueFilterType, EIssuesStoreType } from "constants/issue";
|
||||||
import { computedFn } from "mobx-utils";
|
import { computedFn } from "mobx-utils";
|
||||||
// services
|
// services
|
||||||
|
|
||||||
@ -28,7 +28,9 @@ export interface IProjectIssuesFilter extends IBaseIssueFilterStore {
|
|||||||
//helper actions
|
//helper actions
|
||||||
getFilterParams: (
|
getFilterParams: (
|
||||||
options: IssuePaginationOptions,
|
options: IssuePaginationOptions,
|
||||||
cursor: string | undefined
|
cursor: string | undefined,
|
||||||
|
groupId: string | undefined,
|
||||||
|
subGroupId: string | undefined
|
||||||
) => Partial<Record<TIssueParams, string | boolean>>;
|
) => Partial<Record<TIssueParams, string | boolean>>;
|
||||||
// action
|
// action
|
||||||
fetchFilters: (workspaceSlug: string, projectId: string) => Promise<void>;
|
fetchFilters: (workspaceSlug: string, projectId: string) => Promise<void>;
|
||||||
@ -94,25 +96,18 @@ export class ProjectIssuesFilter extends IssueFilterHelperStore implements IProj
|
|||||||
return filteredRouteParams;
|
return filteredRouteParams;
|
||||||
}
|
}
|
||||||
|
|
||||||
getFilterParams = computedFn((options: IssuePaginationOptions, cursor: string | undefined) => {
|
getFilterParams = computedFn(
|
||||||
const filterParams = this.appliedFilters;
|
(
|
||||||
|
options: IssuePaginationOptions,
|
||||||
const paginationOptions: Partial<Record<TIssueParams, string | boolean>> = {
|
cursor: string | undefined,
|
||||||
...filterParams,
|
groupId: string | undefined,
|
||||||
cursor: cursor ? cursor : `${options.perPageCount}:0:0`,
|
subGroupId: string | undefined
|
||||||
per_page: options.perPageCount.toString(),
|
) => {
|
||||||
};
|
const filterParams = this.appliedFilters;
|
||||||
|
const paginationParams = this.getPaginationParams(filterParams, options, cursor, groupId, subGroupId);
|
||||||
if (options.groupedBy) {
|
return paginationParams;
|
||||||
paginationOptions.group_by = options.groupedBy;
|
|
||||||
}
|
}
|
||||||
|
);
|
||||||
if (options.after && options.before) {
|
|
||||||
paginationOptions["target_date"] = `${options.after};after,${options.before};before`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return paginationOptions;
|
|
||||||
});
|
|
||||||
|
|
||||||
fetchFilters = async (workspaceSlug: string, projectId: string) => {
|
fetchFilters = async (workspaceSlug: string, projectId: string) => {
|
||||||
try {
|
try {
|
||||||
@ -221,7 +216,8 @@ export class ProjectIssuesFilter extends IssueFilterHelperStore implements IProj
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
this.rootIssueStore.projectIssues.fetchIssuesWithExistingPagination(workspaceSlug, projectId, "mutation");
|
if (this.requiresServerUpdate(updatedDisplayFilters))
|
||||||
|
this.rootIssueStore.projectIssues.fetchIssuesWithExistingPagination(workspaceSlug, projectId, "mutation");
|
||||||
|
|
||||||
await this.issueFilterService.patchProjectIssueFilters(workspaceSlug, projectId, {
|
await this.issueFilterService.patchProjectIssueFilters(workspaceSlug, projectId, {
|
||||||
display_filters: _filters.displayFilters,
|
display_filters: _filters.displayFilters,
|
||||||
|
@ -22,7 +22,12 @@ export interface IProjectIssues extends IBaseIssuesStore {
|
|||||||
projectId: string,
|
projectId: string,
|
||||||
loadType: TLoader
|
loadType: TLoader
|
||||||
) => Promise<TIssuesResponse | undefined>;
|
) => Promise<TIssuesResponse | undefined>;
|
||||||
fetchNextIssues: (workspaceSlug: string, projectId: string) => Promise<TIssuesResponse | undefined>;
|
fetchNextIssues: (
|
||||||
|
workspaceSlug: string,
|
||||||
|
projectId: string,
|
||||||
|
groupId?: string,
|
||||||
|
subGroupId?: string
|
||||||
|
) => Promise<TIssuesResponse | undefined>;
|
||||||
|
|
||||||
createIssue: (workspaceSlug: string, projectId: string, data: Partial<TIssue>) => Promise<TIssue>;
|
createIssue: (workspaceSlug: string, projectId: string, data: Partial<TIssue>) => Promise<TIssue>;
|
||||||
updateIssue: (workspaceSlug: string, projectId: string, issueId: string, data: Partial<TIssue>) => Promise<void>;
|
updateIssue: (workspaceSlug: string, projectId: string, issueId: string, data: Partial<TIssue>) => Promise<void>;
|
||||||
@ -65,7 +70,7 @@ export class ProjectIssues extends BaseIssuesStore implements IProjectIssues {
|
|||||||
this.loader = loadType;
|
this.loader = loadType;
|
||||||
});
|
});
|
||||||
this.clear();
|
this.clear();
|
||||||
const params = this.issueFilterStore?.getFilterParams(options, undefined);
|
const params = this.issueFilterStore?.getFilterParams(options, undefined, undefined, undefined);
|
||||||
const response = await this.issueService.getIssues(workspaceSlug, projectId, params);
|
const response = await this.issueService.getIssues(workspaceSlug, projectId, params);
|
||||||
|
|
||||||
this.onfetchIssues(response, options);
|
this.onfetchIssues(response, options);
|
||||||
@ -76,15 +81,21 @@ export class ProjectIssues extends BaseIssuesStore implements IProjectIssues {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
fetchNextIssues = async (workspaceSlug: string, projectId: string) => {
|
fetchNextIssues = async (workspaceSlug: string, projectId: string, groupId?: string, subGroupId?: string) => {
|
||||||
if (!this.paginationOptions || !this.next_page_results) return;
|
const cursorObject = this.getPaginationData(subGroupId ?? groupId);
|
||||||
|
if (!this.paginationOptions || (cursorObject && !cursorObject?.nextPageResults)) return;
|
||||||
try {
|
try {
|
||||||
this.loader = "pagination";
|
this.loader = "pagination";
|
||||||
|
|
||||||
const params = this.issueFilterStore?.getFilterParams(this.paginationOptions, this.nextCursor);
|
const params = this.issueFilterStore?.getFilterParams(
|
||||||
|
this.paginationOptions,
|
||||||
|
cursorObject?.nextCursor,
|
||||||
|
groupId,
|
||||||
|
subGroupId
|
||||||
|
);
|
||||||
const response = await this.issueService.getIssues(workspaceSlug, projectId, params);
|
const response = await this.issueService.getIssues(workspaceSlug, projectId, params);
|
||||||
|
|
||||||
this.onfetchNexIssues(response);
|
this.onfetchNexIssues(response, groupId, subGroupId);
|
||||||
return response;
|
return response;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.loader = undefined;
|
this.loader = undefined;
|
||||||
|
@ -43,7 +43,9 @@ export interface IWorkspaceIssuesFilter extends IBaseIssueFilterStore {
|
|||||||
getFilterParams: (
|
getFilterParams: (
|
||||||
viewId: string,
|
viewId: string,
|
||||||
options: IssuePaginationOptions,
|
options: IssuePaginationOptions,
|
||||||
cursor?: string
|
cursor: string | undefined,
|
||||||
|
groupId: string | undefined,
|
||||||
|
subGroupId: string | undefined
|
||||||
) => Partial<Record<TIssueParams, string | boolean>>;
|
) => Partial<Record<TIssueParams, string | boolean>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -102,21 +104,20 @@ export class WorkspaceIssuesFilter extends IssueFilterHelperStore implements IWo
|
|||||||
return filteredRouteParams;
|
return filteredRouteParams;
|
||||||
};
|
};
|
||||||
|
|
||||||
getFilterParams = computedFn((viewId: string, options: IssuePaginationOptions, cursor: string | undefined) => {
|
getFilterParams = computedFn(
|
||||||
const filterParams = this.getAppliedFilters(viewId);
|
(
|
||||||
|
viewId: string,
|
||||||
|
options: IssuePaginationOptions,
|
||||||
|
cursor: string | undefined,
|
||||||
|
groupId: string | undefined,
|
||||||
|
subGroupId: string | undefined
|
||||||
|
) => {
|
||||||
|
const filterParams = this.getAppliedFilters(viewId);
|
||||||
|
|
||||||
const paginationOptions: Partial<Record<TIssueParams, string | boolean>> = {
|
const paginationParams = this.getPaginationParams(filterParams, options, cursor, groupId, subGroupId);
|
||||||
...filterParams,
|
return paginationParams;
|
||||||
cursor: cursor ? cursor : `${options.perPageCount}:0:0`,
|
|
||||||
per_page: options.perPageCount.toString(),
|
|
||||||
};
|
|
||||||
|
|
||||||
if (options.groupedBy) {
|
|
||||||
paginationOptions.group_by = options.groupedBy;
|
|
||||||
}
|
}
|
||||||
|
);
|
||||||
return paginationOptions;
|
|
||||||
});
|
|
||||||
|
|
||||||
get issueFilters() {
|
get issueFilters() {
|
||||||
const viewId = this.rootIssueStore.globalViewId;
|
const viewId = this.rootIssueStore.globalViewId;
|
||||||
@ -237,7 +238,7 @@ export class WorkspaceIssuesFilter extends IssueFilterHelperStore implements IWo
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
this.rootIssueStore.workspaceIssues.fetchIssuesWithExistingPagination(workspaceSlug, viewId, "mutation");
|
this.rootIssueStore.workspaceIssues.fetchIssuesWithExistingPagination(workspaceSlug, viewId, "mutation");
|
||||||
|
|
||||||
if (["all-issues", "assigned", "created", "subscribed"].includes(viewId))
|
if (["all-issues", "assigned", "created", "subscribed"].includes(viewId))
|
||||||
this.handleIssuesLocalFilters.set(EIssuesStoreType.GLOBAL, type, workspaceSlug, undefined, viewId, {
|
this.handleIssuesLocalFilters.set(EIssuesStoreType.GLOBAL, type, workspaceSlug, undefined, viewId, {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { action, makeObservable, runInAction } from "mobx";
|
import { action, makeObservable, runInAction } from "mobx";
|
||||||
// base class
|
// base class
|
||||||
import { WorkspaceService } from "services/workspace.service";
|
import { WorkspaceService } from "services/workspace.service";
|
||||||
import { IssuePaginationOptions, TIssue, TIssuesResponse, TLoader, TUnGroupedIssues, ViewFlags } from "@plane/types";
|
import { IssuePaginationOptions, TIssue, TIssuesResponse, TLoader, ViewFlags } from "@plane/types";
|
||||||
// services
|
// services
|
||||||
// types
|
// types
|
||||||
import { IIssueRootStore } from "../root.store";
|
import { IIssueRootStore } from "../root.store";
|
||||||
@ -23,7 +23,12 @@ export interface IWorkspaceIssues extends IBaseIssuesStore {
|
|||||||
viewId: string,
|
viewId: string,
|
||||||
loadType: TLoader
|
loadType: TLoader
|
||||||
) => Promise<TIssuesResponse | undefined>;
|
) => Promise<TIssuesResponse | undefined>;
|
||||||
fetchNextIssues: (workspaceSlug: string, viewId: string) => Promise<TIssuesResponse | undefined>;
|
fetchNextIssues: (
|
||||||
|
workspaceSlug: string,
|
||||||
|
viewId: string,
|
||||||
|
groupId?: string,
|
||||||
|
subGroupId?: string
|
||||||
|
) => Promise<TIssuesResponse | undefined>;
|
||||||
|
|
||||||
createIssue: (workspaceSlug: string, projectId: string, data: Partial<TIssue>) => Promise<TIssue>;
|
createIssue: (workspaceSlug: string, projectId: string, data: Partial<TIssue>) => Promise<TIssue>;
|
||||||
updateIssue: (workspaceSlug: string, projectId: string, issueId: string, data: Partial<TIssue>) => Promise<void>;
|
updateIssue: (workspaceSlug: string, projectId: string, issueId: string, data: Partial<TIssue>) => Promise<void>;
|
||||||
@ -64,7 +69,7 @@ export class WorkspaceIssues extends BaseIssuesStore implements IWorkspaceIssues
|
|||||||
this.loader = loadType;
|
this.loader = loadType;
|
||||||
});
|
});
|
||||||
this.clear();
|
this.clear();
|
||||||
const params = this.issueFilterStore?.getFilterParams(viewId, options, undefined);
|
const params = this.issueFilterStore?.getFilterParams(viewId, options, undefined, undefined, undefined);
|
||||||
const response = await this.workspaceService.getViewIssues(workspaceSlug, params);
|
const response = await this.workspaceService.getViewIssues(workspaceSlug, params);
|
||||||
|
|
||||||
this.onfetchIssues(response, options);
|
this.onfetchIssues(response, options);
|
||||||
@ -75,15 +80,22 @@ export class WorkspaceIssues extends BaseIssuesStore implements IWorkspaceIssues
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
fetchNextIssues = async (workspaceSlug: string, viewId: string) => {
|
fetchNextIssues = async (workspaceSlug: string, viewId: string, groupId?: string, subGroupId?: string) => {
|
||||||
if (!this.paginationOptions) return;
|
const cursorObject = this.getPaginationData(subGroupId ?? groupId);
|
||||||
|
if (!this.paginationOptions || (cursorObject && !cursorObject?.nextPageResults)) return;
|
||||||
try {
|
try {
|
||||||
this.loader = "pagination";
|
this.loader = "pagination";
|
||||||
|
|
||||||
const params = this.issueFilterStore?.getFilterParams(viewId, this.paginationOptions, this.nextCursor);
|
const params = this.issueFilterStore?.getFilterParams(
|
||||||
|
viewId,
|
||||||
|
this.paginationOptions,
|
||||||
|
cursorObject?.nextCursor,
|
||||||
|
groupId,
|
||||||
|
subGroupId
|
||||||
|
);
|
||||||
const response = await this.workspaceService.getViewIssues(workspaceSlug, params);
|
const response = await this.workspaceService.getViewIssues(workspaceSlug, params);
|
||||||
|
|
||||||
this.onfetchNexIssues(response);
|
this.onfetchNexIssues(response, groupId, subGroupId);
|
||||||
return response;
|
return response;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.loader = undefined;
|
this.loader = undefined;
|
||||||
|
Loading…
Reference in New Issue
Block a user