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 { 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 ↓
|
||||
</div>
|
||||
</Link>
|
||||
))
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<div className="flex items-center justify-center h-full w-full">
|
||||
<EmptyState
|
||||
|
@ -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) {
|
||||
|
Loading…
Reference in New Issue
Block a user