active cycle issues pagination

This commit is contained in:
rahulramesha 2024-03-27 17:37:24 +05:30
parent 9b4176aa17
commit 588a096c94
2 changed files with 147 additions and 61 deletions

View File

@ -5,7 +5,7 @@ import useSWR from "swr";
import { CalendarCheck } from "lucide-react";
import { Tab } from "@headlessui/react";
// types
import { ICycle, TIssue } from "@plane/types";
import { ICycle } from "@plane/types";
// ui
import { Tooltip, Loader, PriorityIcon, Avatar } from "@plane/ui";
// components
@ -20,7 +20,7 @@ import { EIssuesStoreType } from "@/constants/issue";
import { cn } from "@/helpers/common.helper";
import { renderFormattedDate, renderFormattedDateWithoutYear } from "@/helpers/date-time.helper";
// hooks
import { useIssues, useProject } from "@/hooks/store";
import { useIssueDetail, useIssues, useProject } from "@/hooks/store";
import useLocalStorage from "@/hooks/use-local-storage";
export type ActiveCycleStatsProps = {
@ -47,17 +47,20 @@ export const ActiveCycleStats: FC<ActiveCycleStatsProps> = observer((props) => {
}
};
const {
issues: { fetchActiveCycleIssues },
issues: { getActiveCycleId, fetchActiveCycleIssues, fetchNextActiveCycleIssues },
} = useIssues(EIssuesStoreType.CYCLE);
const {
issue: { getIssueById },
} = useIssueDetail();
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 ? () => 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 (
<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"
>
<div className="flex flex-col gap-1 h-full w-full overflow-y-auto vertical-scrollbar scrollbar-sm">
{cycleIssues ? (
cycleIssues.length > 0 ? (
cycleIssues.map((issue: TIssue) => (
<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"
>
<div className="flex items-center gap-1.5 flex-grow w-full min-w-24 truncate">
<PriorityIcon priority={issue.priority} withContainer size={12} />
{cycleIssueDetails && cycleIssueDetails.issueIds ? (
cycleIssueDetails.issueCount > 0 ? (
<>
{cycleIssueDetails.issueIds.map((issueId: string) => {
const issue = getIssueById(issueId);
<Tooltip
tooltipHeading="Issue ID"
tooltipContent={`${currentProjectDetails?.identifier}-${issue.sequence_id}`}
if (!issue) return null;
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">
{currentProjectDetails?.identifier}-{issue.sequence_id}
</span>
</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)}
<div className="flex items-center gap-1.5 flex-grow w-full min-w-24 truncate">
<PriorityIcon priority={issue.priority} withContainer size={12} />
<Tooltip
tooltipHeading="Issue ID"
tooltipContent={`${currentProjectDetails?.identifier}-${issue.sequence_id}`}
>
<span className="flex-shrink-0 text-xs text-custom-text-200">
{currentProjectDetails?.identifier}-{issue.sequence_id}
</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 &darr;
</div>
</Link>
))
)}
</>
) : (
<div className="flex items-center justify-center h-full w-full">
<EmptyState

View File

@ -5,13 +5,26 @@ import { CycleService } from "@/services/cycle.service";
// types
import { TIssue, TLoader, ViewFlags, IssuePaginationOptions, TIssuesResponse } from "@plane/types";
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 { concat, get, set, uniq, update } from "lodash";
import { computedFn } from "mobx-utils";
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 {
viewFlags: ViewFlags;
activeCycleIds: Record<string, ActiveCycleIssueDetails>;
//action helpers
getActiveCycleId: (cycleId: string) => ActiveCycleIssueDetails | undefined;
// actions
fetchIssues: (
workspaceSlug: string,
@ -34,6 +47,18 @@ export interface ICycleIssues extends IBaseIssuesStore {
subGroupId?: string
) => 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>;
updateIssue: (workspaceSlug: string, projectId: string, issueId: string, data: Partial<TIssue>) => Promise<void>;
archiveIssue: (workspaceSlug: string, projectId: string, issueId: string) => Promise<void>;
@ -56,15 +81,11 @@ export interface ICycleIssues extends IBaseIssuesStore {
new_cycle_id: string;
}
) => Promise<TIssue>;
fetchActiveCycleIssues: (
workspaceSlug: string,
projectId: string,
cycleId: string
) => Promise<TIssuesResponse | undefined>;
}
export class CycleIssues extends BaseIssuesStore implements ICycleIssues {
cycleId: string | undefined = undefined;
activeCycleIds: Record<string, ActiveCycleIssueDetails> = {};
viewFlags = {
enableQuickAdd: true,
enableIssueCreation: true,
@ -80,6 +101,7 @@ export class CycleIssues extends BaseIssuesStore implements ICycleIssues {
makeObservable(this, {
// observable
cycleId: observable.ref,
activeCycleIds: observable,
// action
fetchIssues: action,
fetchNextIssues: action,
@ -98,6 +120,8 @@ export class CycleIssues extends BaseIssuesStore implements ICycleIssues {
this.issueFilterStore = issueFilterStore;
}
getActiveCycleId = computedFn((cycleId: string) => this.activeCycleIds[cycleId]);
fetchIssues = async (
workspaceSlug: 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 {
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);
// runInAction(() => {
// set(this.issues, , Object.keys(response));
// this.loader = undefined;
// });
const { issueList, groupedIssues } = this.processIssueResponse(response);
// 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;
} catch (error) {