mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
active cycle issues pagination
This commit is contained in:
parent
9b4176aa17
commit
588a096c94
@ -5,7 +5,7 @@ import useSWR from "swr";
|
|||||||
import { CalendarCheck } from "lucide-react";
|
import { CalendarCheck } from "lucide-react";
|
||||||
import { Tab } from "@headlessui/react";
|
import { Tab } from "@headlessui/react";
|
||||||
// types
|
// types
|
||||||
import { ICycle, TIssue } from "@plane/types";
|
import { ICycle } from "@plane/types";
|
||||||
// ui
|
// ui
|
||||||
import { Tooltip, Loader, PriorityIcon, Avatar } from "@plane/ui";
|
import { Tooltip, Loader, PriorityIcon, Avatar } from "@plane/ui";
|
||||||
// components
|
// components
|
||||||
@ -20,7 +20,7 @@ import { EIssuesStoreType } from "@/constants/issue";
|
|||||||
import { cn } from "@/helpers/common.helper";
|
import { cn } from "@/helpers/common.helper";
|
||||||
import { renderFormattedDate, renderFormattedDateWithoutYear } from "@/helpers/date-time.helper";
|
import { renderFormattedDate, renderFormattedDateWithoutYear } from "@/helpers/date-time.helper";
|
||||||
// hooks
|
// hooks
|
||||||
import { useIssues, useProject } from "@/hooks/store";
|
import { useIssueDetail, useIssues, useProject } from "@/hooks/store";
|
||||||
import useLocalStorage from "@/hooks/use-local-storage";
|
import useLocalStorage from "@/hooks/use-local-storage";
|
||||||
|
|
||||||
export type ActiveCycleStatsProps = {
|
export type ActiveCycleStatsProps = {
|
||||||
@ -47,17 +47,20 @@ export const ActiveCycleStats: FC<ActiveCycleStatsProps> = observer((props) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
const {
|
const {
|
||||||
issues: { fetchActiveCycleIssues },
|
issues: { getActiveCycleId, fetchActiveCycleIssues, fetchNextActiveCycleIssues },
|
||||||
} = useIssues(EIssuesStoreType.CYCLE);
|
} = useIssues(EIssuesStoreType.CYCLE);
|
||||||
|
const {
|
||||||
|
issue: { getIssueById },
|
||||||
|
} = useIssueDetail();
|
||||||
|
|
||||||
const { currentProjectDetails } = useProject();
|
const { currentProjectDetails } = useProject();
|
||||||
|
|
||||||
const { data: activeCycleIssues } = useSWR(
|
useSWR(
|
||||||
workspaceSlug && projectId && cycle.id ? CYCLE_ISSUES_WITH_PARAMS(cycle.id, { priority: "urgent,high" }) : null,
|
workspaceSlug && projectId && cycle.id ? CYCLE_ISSUES_WITH_PARAMS(cycle.id, { priority: "urgent,high" }) : null,
|
||||||
workspaceSlug && projectId && cycle.id ? () => fetchActiveCycleIssues(workspaceSlug, projectId, cycle.id) : null
|
workspaceSlug && projectId && cycle.id ? () => fetchActiveCycleIssues(workspaceSlug, projectId, 6, cycle.id) : null
|
||||||
);
|
);
|
||||||
|
|
||||||
const cycleIssues = activeCycleIssues ?? [];
|
const cycleIssueDetails = getActiveCycleId(cycle.id);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-4 p-4 min-h-[17rem] overflow-hidden col-span-1 lg:col-span-2 xl:col-span-1 border border-custom-border-200 rounded-lg">
|
<div className="flex flex-col gap-4 p-4 min-h-[17rem] overflow-hidden col-span-1 lg:col-span-2 xl:col-span-1 border border-custom-border-200 rounded-lg">
|
||||||
@ -132,52 +135,73 @@ export const ActiveCycleStats: FC<ActiveCycleStatsProps> = observer((props) => {
|
|||||||
className="flex h-52 w-full flex-col gap-1 overflow-y-auto text-custom-text-200 vertical-scrollbar scrollbar-sm"
|
className="flex h-52 w-full flex-col gap-1 overflow-y-auto text-custom-text-200 vertical-scrollbar scrollbar-sm"
|
||||||
>
|
>
|
||||||
<div className="flex flex-col gap-1 h-full w-full overflow-y-auto vertical-scrollbar scrollbar-sm">
|
<div className="flex flex-col gap-1 h-full w-full overflow-y-auto vertical-scrollbar scrollbar-sm">
|
||||||
{cycleIssues ? (
|
{cycleIssueDetails && cycleIssueDetails.issueIds ? (
|
||||||
cycleIssues.length > 0 ? (
|
cycleIssueDetails.issueCount > 0 ? (
|
||||||
cycleIssues.map((issue: TIssue) => (
|
<>
|
||||||
<Link
|
{cycleIssueDetails.issueIds.map((issueId: string) => {
|
||||||
key={issue.id}
|
const issue = getIssueById(issueId);
|
||||||
href={`/${workspaceSlug}/projects/${projectId}/issues/${issue.id}`}
|
|
||||||
className="group flex cursor-pointer items-center justify-between gap-2 rounded-md hover:bg-custom-background-90 p-1"
|
|
||||||
>
|
|
||||||
<div className="flex items-center gap-1.5 flex-grow w-full min-w-24 truncate">
|
|
||||||
<PriorityIcon priority={issue.priority} withContainer size={12} />
|
|
||||||
|
|
||||||
<Tooltip
|
if (!issue) return null;
|
||||||
tooltipHeading="Issue ID"
|
|
||||||
tooltipContent={`${currentProjectDetails?.identifier}-${issue.sequence_id}`}
|
return (
|
||||||
|
<Link
|
||||||
|
key={issue.id}
|
||||||
|
href={`/${workspaceSlug}/projects/${projectId}/issues/${issue.id}`}
|
||||||
|
className="group flex cursor-pointer items-center justify-between gap-2 rounded-md hover:bg-custom-background-90 p-1"
|
||||||
>
|
>
|
||||||
<span className="flex-shrink-0 text-xs text-custom-text-200">
|
<div className="flex items-center gap-1.5 flex-grow w-full min-w-24 truncate">
|
||||||
{currentProjectDetails?.identifier}-{issue.sequence_id}
|
<PriorityIcon priority={issue.priority} withContainer size={12} />
|
||||||
</span>
|
|
||||||
</Tooltip>
|
<Tooltip
|
||||||
<Tooltip position="top-left" tooltipHeading="Title" tooltipContent={issue.name}>
|
tooltipHeading="Issue ID"
|
||||||
<span className="text-[0.825rem] text-custom-text-100 truncate">{issue.name}</span>
|
tooltipContent={`${currentProjectDetails?.identifier}-${issue.sequence_id}`}
|
||||||
</Tooltip>
|
>
|
||||||
</div>
|
<span className="flex-shrink-0 text-xs text-custom-text-200">
|
||||||
<div className="flex items-center gap-1.5 flex-shrink-0">
|
{currentProjectDetails?.identifier}-{issue.sequence_id}
|
||||||
<StateDropdown
|
|
||||||
value={issue.state_id ?? undefined}
|
|
||||||
onChange={() => {}}
|
|
||||||
projectId={projectId?.toString() ?? ""}
|
|
||||||
disabled
|
|
||||||
buttonVariant="background-with-text"
|
|
||||||
buttonContainerClassName="cursor-pointer max-w-24"
|
|
||||||
showTooltip
|
|
||||||
/>
|
|
||||||
{issue.target_date && (
|
|
||||||
<Tooltip tooltipHeading="Target Date" tooltipContent={renderFormattedDate(issue.target_date)}>
|
|
||||||
<div className="h-full flex truncate items-center gap-1.5 rounded text-xs px-2 py-0.5 bg-custom-background-80 group-hover:bg-custom-background-100 cursor-pointer">
|
|
||||||
<CalendarCheck className="h-3 w-3 flex-shrink-0" />
|
|
||||||
<span className="text-xs truncate">
|
|
||||||
{renderFormattedDateWithoutYear(issue.target_date)}
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</Tooltip>
|
||||||
</Tooltip>
|
<Tooltip position="top-left" tooltipHeading="Title" tooltipContent={issue.name}>
|
||||||
)}
|
<span className="text-[0.825rem] text-custom-text-100 truncate">{issue.name}</span>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-1.5 flex-shrink-0">
|
||||||
|
<StateDropdown
|
||||||
|
value={issue.state_id ?? undefined}
|
||||||
|
onChange={() => {}}
|
||||||
|
projectId={projectId?.toString() ?? ""}
|
||||||
|
disabled
|
||||||
|
buttonVariant="background-with-text"
|
||||||
|
buttonContainerClassName="cursor-pointer max-w-24"
|
||||||
|
showTooltip
|
||||||
|
/>
|
||||||
|
{issue.target_date && (
|
||||||
|
<Tooltip
|
||||||
|
tooltipHeading="Target Date"
|
||||||
|
tooltipContent={renderFormattedDate(issue.target_date)}
|
||||||
|
>
|
||||||
|
<div className="h-full flex truncate items-center gap-1.5 rounded text-xs px-2 py-0.5 bg-custom-background-80 group-hover:bg-custom-background-100 cursor-pointer">
|
||||||
|
<CalendarCheck className="h-3 w-3 flex-shrink-0" />
|
||||||
|
<span className="text-xs truncate">
|
||||||
|
{renderFormattedDateWithoutYear(issue.target_date)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
{cycleIssueDetails.nextPageResults && (
|
||||||
|
<div
|
||||||
|
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"
|
||||||
|
}
|
||||||
|
onClick={() => fetchNextActiveCycleIssues(workspaceSlug, projectId, cycle.id)}
|
||||||
|
>
|
||||||
|
Load more ↓
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
)}
|
||||||
))
|
</>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex items-center justify-center h-full w-full">
|
<div className="flex items-center justify-center h-full w-full">
|
||||||
<EmptyState
|
<EmptyState
|
||||||
|
@ -5,13 +5,26 @@ 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 { ALL_ISSUES, BaseIssuesStore, IBaseIssuesStore } from "../helpers/base-issues.store";
|
||||||
import { ICycleIssuesFilter } from "./filter.store";
|
import { ICycleIssuesFilter } from "./filter.store";
|
||||||
|
import { concat, get, set, uniq, update } from "lodash";
|
||||||
|
import { computedFn } from "mobx-utils";
|
||||||
|
|
||||||
export const ACTIVE_CYCLE_ISSUES = "ACTIVE_CYCLE_ISSUES";
|
export const ACTIVE_CYCLE_ISSUES = "ACTIVE_CYCLE_ISSUES";
|
||||||
|
|
||||||
|
export interface ActiveCycleIssueDetails {
|
||||||
|
issueIds: string[];
|
||||||
|
issueCount: number;
|
||||||
|
nextCursor: string;
|
||||||
|
nextPageResults: boolean;
|
||||||
|
perPageCount: number;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ICycleIssues extends IBaseIssuesStore {
|
export interface ICycleIssues extends IBaseIssuesStore {
|
||||||
viewFlags: ViewFlags;
|
viewFlags: ViewFlags;
|
||||||
|
activeCycleIds: Record<string, ActiveCycleIssueDetails>;
|
||||||
|
//action helpers
|
||||||
|
getActiveCycleId: (cycleId: string) => ActiveCycleIssueDetails | undefined;
|
||||||
// actions
|
// actions
|
||||||
fetchIssues: (
|
fetchIssues: (
|
||||||
workspaceSlug: string,
|
workspaceSlug: string,
|
||||||
@ -34,6 +47,18 @@ export interface ICycleIssues extends IBaseIssuesStore {
|
|||||||
subGroupId?: string
|
subGroupId?: string
|
||||||
) => Promise<TIssuesResponse | undefined>;
|
) => Promise<TIssuesResponse | undefined>;
|
||||||
|
|
||||||
|
fetchActiveCycleIssues: (
|
||||||
|
workspaceSlug: string,
|
||||||
|
projectId: string,
|
||||||
|
perPageCount: number,
|
||||||
|
cycleId: string
|
||||||
|
) => Promise<TIssuesResponse | undefined>;
|
||||||
|
fetchNextActiveCycleIssues: (
|
||||||
|
workspaceSlug: string,
|
||||||
|
projectId: string,
|
||||||
|
cycleId: 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>;
|
||||||
archiveIssue: (workspaceSlug: string, projectId: string, issueId: string) => Promise<void>;
|
archiveIssue: (workspaceSlug: string, projectId: string, issueId: string) => Promise<void>;
|
||||||
@ -56,15 +81,11 @@ export interface ICycleIssues extends IBaseIssuesStore {
|
|||||||
new_cycle_id: string;
|
new_cycle_id: string;
|
||||||
}
|
}
|
||||||
) => Promise<TIssue>;
|
) => Promise<TIssue>;
|
||||||
fetchActiveCycleIssues: (
|
|
||||||
workspaceSlug: string,
|
|
||||||
projectId: string,
|
|
||||||
cycleId: string
|
|
||||||
) => Promise<TIssuesResponse | undefined>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class CycleIssues extends BaseIssuesStore implements ICycleIssues {
|
export class CycleIssues extends BaseIssuesStore implements ICycleIssues {
|
||||||
cycleId: string | undefined = undefined;
|
cycleId: string | undefined = undefined;
|
||||||
|
activeCycleIds: Record<string, ActiveCycleIssueDetails> = {};
|
||||||
viewFlags = {
|
viewFlags = {
|
||||||
enableQuickAdd: true,
|
enableQuickAdd: true,
|
||||||
enableIssueCreation: true,
|
enableIssueCreation: true,
|
||||||
@ -80,6 +101,7 @@ export class CycleIssues extends BaseIssuesStore implements ICycleIssues {
|
|||||||
makeObservable(this, {
|
makeObservable(this, {
|
||||||
// observable
|
// observable
|
||||||
cycleId: observable.ref,
|
cycleId: observable.ref,
|
||||||
|
activeCycleIds: observable,
|
||||||
// action
|
// action
|
||||||
fetchIssues: action,
|
fetchIssues: action,
|
||||||
fetchNextIssues: action,
|
fetchNextIssues: action,
|
||||||
@ -98,6 +120,8 @@ export class CycleIssues extends BaseIssuesStore implements ICycleIssues {
|
|||||||
this.issueFilterStore = issueFilterStore;
|
this.issueFilterStore = issueFilterStore;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getActiveCycleId = computedFn((cycleId: string) => this.activeCycleIds[cycleId]);
|
||||||
|
|
||||||
fetchIssues = async (
|
fetchIssues = async (
|
||||||
workspaceSlug: string,
|
workspaceSlug: string,
|
||||||
projectId: string,
|
projectId: string,
|
||||||
@ -240,17 +264,55 @@ export class CycleIssues extends BaseIssuesStore implements ICycleIssues {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
fetchActiveCycleIssues = async (workspaceSlug: string, projectId: string, cycleId: string) => {
|
fetchActiveCycleIssues = async (workspaceSlug: string, projectId: string, perPageCount: number, cycleId: string) => {
|
||||||
try {
|
try {
|
||||||
const params = { priority: `urgent,high` };
|
set(this.activeCycleIds, [cycleId], undefined);
|
||||||
|
|
||||||
|
const params = { priority: `urgent,high`, cursor: `${perPageCount}:0:0`, per_page: perPageCount };
|
||||||
const response = await this.cycleService.getCycleIssues(workspaceSlug, projectId, cycleId, params);
|
const response = await this.cycleService.getCycleIssues(workspaceSlug, projectId, cycleId, params);
|
||||||
|
|
||||||
// runInAction(() => {
|
const { issueList, groupedIssues } = this.processIssueResponse(response);
|
||||||
// set(this.issues, , Object.keys(response));
|
|
||||||
// this.loader = undefined;
|
|
||||||
// });
|
|
||||||
|
|
||||||
// this.rootIssueStore.issues.addIssue(Object.values(response));
|
this.rootIssueStore.issues.addIssue(issueList);
|
||||||
|
const activeIssueIds = groupedIssues[ALL_ISSUES] as string[];
|
||||||
|
|
||||||
|
set(this.activeCycleIds, [cycleId], {
|
||||||
|
issueIds: activeIssueIds,
|
||||||
|
issueCount: response.total_count,
|
||||||
|
nextCursor: response.next_cursor,
|
||||||
|
nextPageResults: response.next_page_results,
|
||||||
|
perPageCount: perPageCount,
|
||||||
|
});
|
||||||
|
|
||||||
|
return response;
|
||||||
|
} catch (error) {
|
||||||
|
this.loader = undefined;
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchNextActiveCycleIssues = async (workspaceSlug: string, projectId: string, cycleId: string) => {
|
||||||
|
try {
|
||||||
|
const activeCycle = get(this.activeCycleIds, [cycleId]);
|
||||||
|
|
||||||
|
if (!activeCycle || !activeCycle.nextPageResults) return;
|
||||||
|
|
||||||
|
const params = { priority: `urgent,high`, cursor: activeCycle.nextCursor, per_page: activeCycle.perPageCount };
|
||||||
|
const response = await this.cycleService.getCycleIssues(workspaceSlug, projectId, cycleId, params);
|
||||||
|
|
||||||
|
const { issueList, groupedIssues } = this.processIssueResponse(response);
|
||||||
|
|
||||||
|
this.rootIssueStore.issues.addIssue(issueList);
|
||||||
|
|
||||||
|
const activeIssueIds = groupedIssues[ALL_ISSUES] as string[];
|
||||||
|
|
||||||
|
set(this.activeCycleIds, [cycleId, "issueCount"], response.total_count);
|
||||||
|
set(this.activeCycleIds, [cycleId, "nextCursor"], response.next_cursor);
|
||||||
|
set(this.activeCycleIds, [cycleId, "nextPageResults"], response.next_page_results);
|
||||||
|
set(this.activeCycleIds, [cycleId, "issueCount"], response.total_count);
|
||||||
|
update(this.activeCycleIds, [cycleId, "issueIds"], (issueIds: string[] = []) => {
|
||||||
|
return this.issuesSortWithOrderBy(uniq(concat(issueIds, activeIssueIds)), this.orderBy);
|
||||||
|
});
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
Loading…
Reference in New Issue
Block a user