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
1d697c9d78
@ -1,13 +1,18 @@
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { Combobox } from "@headlessui/react";
|
||||
// hooks
|
||||
import { useProjectState } from "@/hooks/store";
|
||||
import { ISearchIssueResponse } from "@plane/types";
|
||||
|
||||
export const BulkDeleteIssuesModalItem: React.FC<any> = observer((props) => {
|
||||
const { issue, delete_issue_ids, identifier } = props;
|
||||
const { getStateById } = useProjectState();
|
||||
interface Props {
|
||||
issue: ISearchIssueResponse;
|
||||
canDeleteIssueIds: boolean;
|
||||
identifier: string | undefined;
|
||||
}
|
||||
|
||||
const color = getStateById(issue.state_id)?.color;
|
||||
export const BulkDeleteIssuesModalItem: React.FC<Props> = observer((props: Props) => {
|
||||
const { issue, canDeleteIssueIds, identifier } = props;
|
||||
|
||||
const color = issue.state__color;
|
||||
|
||||
return (
|
||||
<Combobox.Option
|
||||
@ -21,7 +26,7 @@ export const BulkDeleteIssuesModalItem: React.FC<any> = observer((props) => {
|
||||
}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<input type="checkbox" checked={delete_issue_ids} readOnly />
|
||||
<input type="checkbox" checked={canDeleteIssueIds} readOnly />
|
||||
<span
|
||||
className="block h-1.5 w-1.5 flex-shrink-0 rounded-full"
|
||||
style={{
|
||||
|
@ -127,7 +127,7 @@ export const BulkDeleteIssuesModal: React.FC<Props> = observer((props) => {
|
||||
<BulkDeleteIssuesModalItem
|
||||
issue={issue}
|
||||
identifier={projectDetails?.identifier}
|
||||
delete_issue_ids={watch("delete_issue_ids").includes(issue.id)}
|
||||
canDeleteIssueIds={watch("delete_issue_ids").includes(issue.id)}
|
||||
key={issue.id}
|
||||
/>
|
||||
))}
|
||||
|
@ -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
|
||||
|
@ -1,20 +1,21 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import useSWR from "swr";
|
||||
import { Search } from "lucide-react";
|
||||
import { Combobox, Dialog, Transition } from "@headlessui/react";
|
||||
// hooks
|
||||
// icons
|
||||
// components
|
||||
// types
|
||||
import { ISearchIssueResponse } from "@plane/types";
|
||||
// ui
|
||||
import { Button, TOAST_TYPE, setToast } from "@plane/ui";
|
||||
import { Button, Loader, TOAST_TYPE, setToast } from "@plane/ui";
|
||||
import { EmptyState } from "@/components/empty-state";
|
||||
// services
|
||||
// constants
|
||||
import { EmptyStateType } from "@/constants/empty-state";
|
||||
import { PROJECT_ISSUES_LIST } from "@/constants/fetch-keys";
|
||||
import { useProject, useProjectState } from "@/hooks/store";
|
||||
import { IssueService } from "@/services/issue";
|
||||
// hooks
|
||||
import { useProject } from "@/hooks/store";
|
||||
import useDebounce from "@/hooks/use-debounce";
|
||||
// services
|
||||
import { ProjectService } from "@/services/project";
|
||||
|
||||
type Props = {
|
||||
isOpen: boolean;
|
||||
@ -23,7 +24,7 @@ type Props = {
|
||||
onSubmit: (issueId: string) => void;
|
||||
};
|
||||
|
||||
const issueService = new IssueService();
|
||||
const projectService = new ProjectService();
|
||||
|
||||
export const SelectDuplicateInboxIssueModal: React.FC<Props> = (props) => {
|
||||
const { isOpen, onClose, onSubmit, value } = props;
|
||||
@ -35,18 +36,27 @@ export const SelectDuplicateInboxIssueModal: React.FC<Props> = (props) => {
|
||||
const { workspaceSlug, projectId, issueId } = router.query;
|
||||
|
||||
// hooks
|
||||
const { getProjectStates } = useProjectState();
|
||||
const { getProjectById } = useProject();
|
||||
|
||||
const { data: issues } = useSWR(
|
||||
workspaceSlug && projectId ? PROJECT_ISSUES_LIST(workspaceSlug as string, projectId as string) : null,
|
||||
workspaceSlug && projectId
|
||||
? () =>
|
||||
issueService
|
||||
.getIssues(workspaceSlug as string, projectId as string)
|
||||
.then((res) => Object.values(res ?? {}).filter((issue) => issue.id !== issueId))
|
||||
: null
|
||||
);
|
||||
const [issues, setIssues] = useState<ISearchIssueResponse[]>([]);
|
||||
const [isSearching, setIsSearching] = useState(false);
|
||||
|
||||
const debouncedSearchTerm: string = useDebounce(query, 500);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isOpen || !workspaceSlug || !projectId) return;
|
||||
|
||||
setIsSearching(true);
|
||||
projectService
|
||||
.projectIssuesSearch(workspaceSlug.toString(), projectId.toString(), {
|
||||
search: debouncedSearchTerm,
|
||||
workspace_search: false,
|
||||
})
|
||||
.then((res: ISearchIssueResponse[]) => setIssues(res))
|
||||
.finally(() => setIsSearching(false));
|
||||
}, [debouncedSearchTerm, isOpen, projectId, workspaceSlug]);
|
||||
|
||||
const filteredIssues = issues.filter((issue) => issue.id !== issueId);
|
||||
|
||||
useEffect(() => {
|
||||
if (!value) {
|
||||
@ -69,7 +79,52 @@ export const SelectDuplicateInboxIssueModal: React.FC<Props> = (props) => {
|
||||
handleClose();
|
||||
};
|
||||
|
||||
const filteredIssues = (query === "" ? issues : issues?.filter((issue) => issue.name.includes(query))) ?? [];
|
||||
const issueList =
|
||||
filteredIssues.length > 0 ? (
|
||||
<li className="p-2">
|
||||
{query === "" && <h2 className="mb-2 mt-4 px-3 text-xs font-semibold text-custom-text-100">Select issue</h2>}
|
||||
<ul className="text-sm text-custom-text-100">
|
||||
{filteredIssues.map((issue) => {
|
||||
const stateColor = issue.state__color || "";
|
||||
|
||||
return (
|
||||
<Combobox.Option
|
||||
key={issue.id}
|
||||
as="div"
|
||||
value={issue.id}
|
||||
className={({ active, selected }) =>
|
||||
`flex w-full cursor-pointer select-none items-center gap-2 rounded-md px-3 py-2 text-custom-text-200 ${
|
||||
active || selected ? "bg-custom-background-80 text-custom-text-100" : ""
|
||||
} `
|
||||
}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<span
|
||||
className="block h-1.5 w-1.5 flex-shrink-0 rounded-full"
|
||||
style={{
|
||||
backgroundColor: stateColor,
|
||||
}}
|
||||
/>
|
||||
<span className="flex-shrink-0 text-xs text-custom-text-200">
|
||||
{getProjectById(issue?.project_id)?.identifier}-{issue.sequence_id}
|
||||
</span>
|
||||
<span className="text-custom-text-200">{issue.name}</span>
|
||||
</div>
|
||||
</Combobox.Option>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</li>
|
||||
) : (
|
||||
<div className="flex flex-col items-center justify-center px-3 py-8 text-center">
|
||||
<EmptyState
|
||||
type={
|
||||
query === "" ? EmptyStateType.ISSUE_RELATION_EMPTY_STATE : EmptyStateType.ISSUE_RELATION_SEARCH_EMPTY_STATE
|
||||
}
|
||||
layout="screen-simple"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<Transition.Root show={isOpen} as={React.Fragment} afterLeave={() => setQuery("")} appear>
|
||||
@ -122,56 +177,15 @@ export const SelectDuplicateInboxIssueModal: React.FC<Props> = (props) => {
|
||||
static
|
||||
className="max-h-80 scroll-py-2 divide-y divide-custom-border-200 overflow-y-auto"
|
||||
>
|
||||
{filteredIssues.length > 0 ? (
|
||||
<li className="p-2">
|
||||
{query === "" && (
|
||||
<h2 className="mb-2 mt-4 px-3 text-xs font-semibold text-custom-text-100">Select issue</h2>
|
||||
)}
|
||||
<ul className="text-sm text-custom-text-100">
|
||||
{filteredIssues.map((issue) => {
|
||||
const stateColor =
|
||||
getProjectStates(issue?.project_id ?? "")?.find((state) => state?.id == issue?.state_id)
|
||||
?.color || "";
|
||||
|
||||
return (
|
||||
<Combobox.Option
|
||||
key={issue.id}
|
||||
as="div"
|
||||
value={issue.id}
|
||||
className={({ active, selected }) =>
|
||||
`flex w-full cursor-pointer select-none items-center gap-2 rounded-md px-3 py-2 text-custom-text-200 ${
|
||||
active || selected ? "bg-custom-background-80 text-custom-text-100" : ""
|
||||
} `
|
||||
}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<span
|
||||
className="block h-1.5 w-1.5 flex-shrink-0 rounded-full"
|
||||
style={{
|
||||
backgroundColor: stateColor,
|
||||
}}
|
||||
/>
|
||||
<span className="flex-shrink-0 text-xs text-custom-text-200">
|
||||
{getProjectById(issue?.project_id)?.identifier}-{issue.sequence_id}
|
||||
</span>
|
||||
<span className="text-custom-text-200">{issue.name}</span>
|
||||
</div>
|
||||
</Combobox.Option>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</li>
|
||||
{isSearching ? (
|
||||
<Loader className="space-y-3 p-3">
|
||||
<Loader.Item height="40px" />
|
||||
<Loader.Item height="40px" />
|
||||
<Loader.Item height="40px" />
|
||||
<Loader.Item height="40px" />
|
||||
</Loader>
|
||||
) : (
|
||||
<div className="flex flex-col items-center justify-center px-3 py-8 text-center">
|
||||
<EmptyState
|
||||
type={
|
||||
query === ""
|
||||
? EmptyStateType.ISSUE_RELATION_EMPTY_STATE
|
||||
: EmptyStateType.ISSUE_RELATION_SEARCH_EMPTY_STATE
|
||||
}
|
||||
layout="screen-simple"
|
||||
/>
|
||||
</div>
|
||||
<>{issueList}</>
|
||||
)}
|
||||
</Combobox.Options>
|
||||
</Combobox>
|
||||
|
@ -106,7 +106,7 @@ export const CalendarChart: React.FC<Props> = observer((props) => {
|
||||
</div>
|
||||
);
|
||||
|
||||
const issueIdList = groupedIssueIds ? groupedIssueIds[formattedDatePayload] : null;
|
||||
const issueIdList = groupedIssueIds ? groupedIssueIds[formattedDatePayload] : [];
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -182,6 +182,9 @@ export const CalendarChart: React.FC<Props> = observer((props) => {
|
||||
date={selectedDate}
|
||||
issues={issues}
|
||||
issueIdList={issueIdList}
|
||||
loadMoreIssues={loadMoreIssues}
|
||||
getPaginationData={getPaginationData}
|
||||
getGroupIssueCount={getGroupIssueCount}
|
||||
quickActions={quickActions}
|
||||
enableQuickIssueCreate
|
||||
disableIssueCreation={!enableIssueCreation || !isEditingAllowed}
|
||||
|
@ -4,7 +4,7 @@ import { observer } from "mobx-react-lite";
|
||||
// types
|
||||
import { TGroupedIssues, TIssue, TIssueMap, TPaginationData } from "@plane/types";
|
||||
// components
|
||||
import { CalendarIssueBlocks, ICalendarDate, CalendarQuickAddIssueForm } from "@/components/issues";
|
||||
import { CalendarIssueBlocks, ICalendarDate } from "@/components/issues";
|
||||
// helpers
|
||||
import { MONTHS_LIST } from "@/constants/calendar";
|
||||
import { cn } from "@/helpers/common.helper";
|
||||
@ -63,11 +63,6 @@ export const CalendarDayTile: React.FC<Props> = observer((props) => {
|
||||
const formattedDatePayload = renderFormattedPayloadDate(date.date);
|
||||
if (!formattedDatePayload) return null;
|
||||
const issueIds = groupedIssueIds?.[formattedDatePayload];
|
||||
const dayIssueCount = getGroupIssueCount(formattedDatePayload);
|
||||
const nextPageResults = getPaginationData(formattedDatePayload)?.nextPageResults;
|
||||
|
||||
const shouldLoadMore =
|
||||
nextPageResults === undefined && dayIssueCount !== undefined ? issueIds?.length < dayIssueCount : !!nextPageResults;
|
||||
|
||||
const isToday = date.date.toDateString() === new Date().toDateString();
|
||||
const isSelectedDate = date.date.toDateString() == selectedDate.toDateString();
|
||||
@ -117,6 +112,9 @@ export const CalendarDayTile: React.FC<Props> = observer((props) => {
|
||||
issues={issues}
|
||||
issueIdList={issueIds ?? []}
|
||||
quickActions={quickActions}
|
||||
loadMoreIssues={loadMoreIssues}
|
||||
getPaginationData={getPaginationData}
|
||||
getGroupIssueCount={getGroupIssueCount}
|
||||
isDragDisabled={readOnly}
|
||||
addIssuesToView={addIssuesToView}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
@ -126,33 +124,6 @@ export const CalendarDayTile: React.FC<Props> = observer((props) => {
|
||||
readOnly={readOnly}
|
||||
/>
|
||||
|
||||
{enableQuickIssueCreate && !disableIssueCreation && !readOnly && (
|
||||
<div className="px-2 py-1">
|
||||
<CalendarQuickAddIssueForm
|
||||
formKey="target_date"
|
||||
groupId={formattedDatePayload}
|
||||
prePopulatedData={{
|
||||
target_date: renderFormattedPayloadDate(date.date) ?? undefined,
|
||||
}}
|
||||
quickAddCallback={quickAddCallback}
|
||||
addIssuesToView={addIssuesToView}
|
||||
viewId={viewId}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{shouldLoadMore && (
|
||||
<div className="flex items-center px-2.5 py-1">
|
||||
<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"
|
||||
onClick={() => loadMoreIssues(formattedDatePayload)}
|
||||
>
|
||||
Load More
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{provided.placeholder}
|
||||
</div>
|
||||
)}
|
||||
|
@ -1,8 +1,7 @@
|
||||
import { useState } from "react";
|
||||
import { Draggable } from "@hello-pangea/dnd";
|
||||
import { Placement } from "@popperjs/core";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { TIssue, TIssueMap } from "@plane/types";
|
||||
import { TIssue, TIssueMap, TPaginationData } from "@plane/types";
|
||||
// components
|
||||
import { CalendarQuickAddIssueForm, CalendarIssueBlockRoot } from "@/components/issues";
|
||||
// helpers
|
||||
@ -12,8 +11,11 @@ import { renderFormattedPayloadDate } from "@/helpers/date-time.helper";
|
||||
type Props = {
|
||||
date: Date;
|
||||
issues: TIssueMap | undefined;
|
||||
issueIdList: string[] | null;
|
||||
issueIdList: string[];
|
||||
quickActions: (issue: TIssue, customActionButton?: React.ReactElement, placement?: Placement) => React.ReactNode;
|
||||
loadMoreIssues: (dateString: string) => void;
|
||||
getPaginationData: (groupId: string | undefined) => TPaginationData | undefined;
|
||||
getGroupIssueCount: (groupId: string | undefined) => number | undefined;
|
||||
isDragDisabled?: boolean;
|
||||
enableQuickIssueCreate?: boolean;
|
||||
disableIssueCreation?: boolean;
|
||||
@ -35,6 +37,9 @@ export const CalendarIssueBlocks: React.FC<Props> = observer((props) => {
|
||||
issues,
|
||||
issueIdList,
|
||||
quickActions,
|
||||
loadMoreIssues,
|
||||
getPaginationData,
|
||||
getGroupIssueCount,
|
||||
isDragDisabled = false,
|
||||
enableQuickIssueCreate,
|
||||
disableIssueCreation,
|
||||
@ -45,13 +50,18 @@ export const CalendarIssueBlocks: React.FC<Props> = observer((props) => {
|
||||
isMobileView = false,
|
||||
} = props;
|
||||
// states
|
||||
const [showAllIssues, setShowAllIssues] = useState(false);
|
||||
|
||||
const formattedDatePayload = renderFormattedPayloadDate(date);
|
||||
const totalIssues = issueIdList?.length ?? 0;
|
||||
|
||||
if (!formattedDatePayload) return null;
|
||||
|
||||
const dayIssueCount = getGroupIssueCount(formattedDatePayload);
|
||||
const nextPageResults = getPaginationData(formattedDatePayload)?.nextPageResults;
|
||||
|
||||
const shouldLoadMore =
|
||||
nextPageResults === undefined && dayIssueCount !== undefined
|
||||
? issueIdList?.length < dayIssueCount
|
||||
: !!nextPageResults;
|
||||
|
||||
return (
|
||||
<>
|
||||
{issueIdList?.map((issueId, index) =>
|
||||
@ -79,7 +89,7 @@ export const CalendarIssueBlocks: React.FC<Props> = observer((props) => {
|
||||
)}
|
||||
|
||||
{enableQuickIssueCreate && !disableIssueCreation && !readOnly && (
|
||||
<div className="px-1 md:px-2 py-1 border-custom-border-200 border-b md:border-none">
|
||||
<div className="px-1 md:px-2 py-1 border-custom-border-200 border-b md:border-none md:hidden group-hover:block">
|
||||
<CalendarQuickAddIssueForm
|
||||
formKey="target_date"
|
||||
groupId={formattedDatePayload}
|
||||
@ -89,18 +99,18 @@ export const CalendarIssueBlocks: React.FC<Props> = observer((props) => {
|
||||
quickAddCallback={quickAddCallback}
|
||||
addIssuesToView={addIssuesToView}
|
||||
viewId={viewId}
|
||||
onOpen={() => setShowAllIssues(true)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{totalIssues > 4 && (
|
||||
<div className="hidden md:flex items-center px-2.5 py-1">
|
||||
|
||||
{shouldLoadMore && (
|
||||
<div className="flex items-center px-2.5 py-1">
|
||||
<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"
|
||||
onClick={() => setShowAllIssues(!showAllIssues)}
|
||||
onClick={() => loadMoreIssues(formattedDatePayload)}
|
||||
>
|
||||
{showAllIssues ? "Hide" : totalIssues - 4 + " more"}
|
||||
Load More
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
@ -35,7 +35,6 @@ export const CycleKanBanLayout: React.FC = observer(() => {
|
||||
|
||||
return (
|
||||
<BaseKanBanRoot
|
||||
showLoader
|
||||
QuickActions={CycleIssueQuickActions}
|
||||
viewId={cycleId?.toString() ?? ""}
|
||||
storeType={EIssuesStoreType.CYCLE}
|
||||
|
@ -7,5 +7,5 @@ import { BaseKanBanRoot } from "../base-kanban-root";
|
||||
export interface IKanBanLayout {}
|
||||
|
||||
export const DraftKanBanLayout: React.FC = observer(() => (
|
||||
<BaseKanBanRoot showLoader QuickActions={DraftIssueQuickActions} storeType={EIssuesStoreType.DRAFT} />
|
||||
<BaseKanBanRoot QuickActions={DraftIssueQuickActions} storeType={EIssuesStoreType.DRAFT} />
|
||||
));
|
||||
|
@ -21,7 +21,6 @@ export const ModuleKanBanLayout: React.FC = observer(() => {
|
||||
|
||||
return (
|
||||
<BaseKanBanRoot
|
||||
showLoader
|
||||
QuickActions={ModuleIssueQuickActions}
|
||||
viewId={moduleId?.toString()}
|
||||
storeType={EIssuesStoreType.MODULE}
|
||||
|
@ -22,7 +22,6 @@ export const ProfileIssuesKanBanLayout: React.FC = observer(() => {
|
||||
|
||||
return (
|
||||
<BaseKanBanRoot
|
||||
showLoader
|
||||
QuickActions={ProjectIssueQuickActions}
|
||||
storeType={EIssuesStoreType.PROFILE}
|
||||
canEditPropertiesBasedOnProject={canEditPropertiesBasedOnProject}
|
||||
|
@ -8,5 +8,5 @@ import { EIssuesStoreType } from "@/constants/issue";
|
||||
import { BaseKanBanRoot } from "../base-kanban-root";
|
||||
|
||||
export const KanBanLayout: React.FC = observer(() => (
|
||||
<BaseKanBanRoot showLoader QuickActions={ProjectIssueQuickActions} storeType={EIssuesStoreType.PROJECT} />
|
||||
<BaseKanBanRoot QuickActions={ProjectIssueQuickActions} storeType={EIssuesStoreType.PROJECT} />
|
||||
));
|
||||
|
@ -16,7 +16,6 @@ export const ProjectViewKanBanLayout: React.FC = observer(() => {
|
||||
|
||||
return (
|
||||
<BaseKanBanRoot
|
||||
showLoader
|
||||
QuickActions={ProjectIssueQuickActions}
|
||||
storeType={EIssuesStoreType.PROJECT_VIEW}
|
||||
viewId={viewId?.toString()}
|
||||
|
@ -21,7 +21,7 @@ export const SpreadsheetEstimateColumn: React.FC<Props> = observer((props: Props
|
||||
onChange={(data) =>
|
||||
onChange(issue, { estimate_point: data }, { changed_property: "estimate_point", change_details: data })
|
||||
}
|
||||
projectId={issue.project_id}
|
||||
projectId={issue.project_id ?? undefined}
|
||||
disabled={disabled}
|
||||
buttonVariant="transparent-with-text"
|
||||
buttonClassName="rounded-none text-left"
|
||||
|
@ -464,7 +464,7 @@ export const IssueFormRoot: FC<IssueFormProps> = observer((props) => {
|
||||
debouncedUpdatesEnabled={false}
|
||||
value={
|
||||
!value || value === "" || (typeof value === "object" && Object.keys(value).length === 0)
|
||||
? watch("description_html")
|
||||
? watch("description_html") ?? ""
|
||||
: value
|
||||
}
|
||||
initialValue={data?.description_html}
|
||||
|
@ -3,13 +3,10 @@ import { observer } from "mobx-react-lite";
|
||||
import { useRouter } from "next/router";
|
||||
import useSWR from "swr";
|
||||
// components
|
||||
import { EmptyState } from "@/components/empty-state";
|
||||
import { IssuePeekOverview, ProfileIssuesAppliedFiltersRoot } from "@/components/issues";
|
||||
import { ProfileIssuesKanBanLayout } from "@/components/issues/issue-layouts/kanban/roots/profile-issues-root";
|
||||
import { ProfileIssuesListLayout } from "@/components/issues/issue-layouts/list/roots/profile-issues-root";
|
||||
import { KanbanLayoutLoader, ListLayoutLoader } from "@/components/ui";
|
||||
// hooks
|
||||
import { EMPTY_STATE_DETAILS } from "@/constants/empty-state";
|
||||
import { EIssuesStoreType } from "@/constants/issue";
|
||||
import { useIssues } from "@/hooks/store";
|
||||
// constants
|
||||
@ -28,7 +25,7 @@ export const ProfileIssuesPage = observer((props: IProfileIssuesPage) => {
|
||||
};
|
||||
// store hooks
|
||||
const {
|
||||
issues: { loader, groupedIssueIds, fetchIssues, setViewId },
|
||||
issues: { setViewId },
|
||||
issuesFilter: { issueFilters, fetchFilters },
|
||||
} = useIssues(EIssuesStoreType.PROFILE);
|
||||
|
||||
@ -41,7 +38,6 @@ export const ProfileIssuesPage = observer((props: IProfileIssuesPage) => {
|
||||
async () => {
|
||||
if (workspaceSlug && userId) {
|
||||
await fetchFilters(workspaceSlug, userId);
|
||||
await fetchIssues(workspaceSlug, undefined, groupedIssueIds ? "mutation" : "init-loader", userId, type);
|
||||
}
|
||||
},
|
||||
{ revalidateIfStale: false, revalidateOnFocus: false }
|
||||
@ -49,15 +45,6 @@ export const ProfileIssuesPage = observer((props: IProfileIssuesPage) => {
|
||||
|
||||
const activeLayout = issueFilters?.displayFilters?.layout || undefined;
|
||||
|
||||
const emptyStateType = `profile-${type}`;
|
||||
|
||||
if (!groupedIssueIds || loader === "init-loader")
|
||||
return <>{activeLayout === "list" ? <ListLayoutLoader /> : <KanbanLayoutLoader />}</>;
|
||||
|
||||
if (groupedIssueIds.length === 0) {
|
||||
return <EmptyState type={emptyStateType as keyof typeof EMPTY_STATE_DETAILS} size="sm" />;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<ProfileIssuesAppliedFiltersRoot />
|
||||
|
@ -81,7 +81,7 @@ export const ISSUE_ORDER_BY_OPTIONS: {
|
||||
{ key: "-updated_at", title: "Last Updated" },
|
||||
{ key: "start_date", title: "Start Date" },
|
||||
{ key: "target_date", title: "Due Date" },
|
||||
{ key: "-priority", title: "Priority" },
|
||||
{ key: "priority", title: "Priority" },
|
||||
];
|
||||
|
||||
export const ISSUE_FILTER_OPTIONS: {
|
||||
@ -157,7 +157,7 @@ export const ISSUE_DISPLAY_FILTERS_BY_LAYOUT: {
|
||||
display_properties: true,
|
||||
display_filters: {
|
||||
group_by: ["state_detail.group", "priority", "project", "labels", null],
|
||||
order_by: ["sort_order", "-created_at", "-updated_at", "start_date", "-priority"],
|
||||
order_by: ["sort_order", "-created_at", "-updated_at", "start_date", "priority"],
|
||||
type: [null, "active", "backlog"],
|
||||
},
|
||||
extra_options: {
|
||||
@ -170,7 +170,7 @@ export const ISSUE_DISPLAY_FILTERS_BY_LAYOUT: {
|
||||
display_properties: true,
|
||||
display_filters: {
|
||||
group_by: ["state_detail.group", "priority", "project", "labels"],
|
||||
order_by: ["sort_order", "-created_at", "-updated_at", "start_date", "-priority"],
|
||||
order_by: ["sort_order", "-created_at", "-updated_at", "start_date", "priority"],
|
||||
type: [null, "active", "backlog"],
|
||||
},
|
||||
extra_options: {
|
||||
@ -205,7 +205,7 @@ export const ISSUE_DISPLAY_FILTERS_BY_LAYOUT: {
|
||||
"created_by",
|
||||
null,
|
||||
],
|
||||
order_by: ["sort_order", "-created_at", "-updated_at", "start_date", "-priority"],
|
||||
order_by: ["sort_order", "-created_at", "-updated_at", "start_date", "priority"],
|
||||
type: [null, "active", "backlog"],
|
||||
},
|
||||
extra_options: {
|
||||
@ -220,7 +220,7 @@ export const ISSUE_DISPLAY_FILTERS_BY_LAYOUT: {
|
||||
display_properties: true,
|
||||
display_filters: {
|
||||
group_by: ["state_detail.group", "cycle", "module", "priority", "project", "labels", null],
|
||||
order_by: ["sort_order", "-created_at", "-updated_at", "start_date", "-priority"],
|
||||
order_by: ["sort_order", "-created_at", "-updated_at", "start_date", "priority"],
|
||||
type: [null, "active", "backlog"],
|
||||
},
|
||||
extra_options: {
|
||||
@ -233,7 +233,7 @@ export const ISSUE_DISPLAY_FILTERS_BY_LAYOUT: {
|
||||
display_properties: true,
|
||||
display_filters: {
|
||||
group_by: ["state_detail.group", "cycle", "module", "priority", "project", "labels"],
|
||||
order_by: ["sort_order", "-created_at", "-updated_at", "start_date", "-priority"],
|
||||
order_by: ["sort_order", "-created_at", "-updated_at", "start_date", "priority"],
|
||||
type: [null, "active", "backlog"],
|
||||
},
|
||||
extra_options: {
|
||||
@ -303,7 +303,7 @@ export const ISSUE_DISPLAY_FILTERS_BY_LAYOUT: {
|
||||
display_properties: true,
|
||||
display_filters: {
|
||||
group_by: ["state", "priority", "cycle", "module", "labels", "assignees", "created_by", null],
|
||||
order_by: ["sort_order", "-created_at", "-updated_at", "start_date", "-priority"],
|
||||
order_by: ["sort_order", "-created_at", "-updated_at", "start_date", "priority"],
|
||||
type: [null, "active", "backlog"],
|
||||
},
|
||||
extra_options: {
|
||||
@ -328,7 +328,7 @@ export const ISSUE_DISPLAY_FILTERS_BY_LAYOUT: {
|
||||
display_filters: {
|
||||
group_by: ["state", "priority", "cycle", "module", "labels", "assignees", "created_by"],
|
||||
sub_group_by: ["state", "priority", "cycle", "module", "labels", "assignees", "created_by", null],
|
||||
order_by: ["sort_order", "-created_at", "-updated_at", "start_date", "-priority", "target_date"],
|
||||
order_by: ["sort_order", "-created_at", "-updated_at", "start_date", "priority", "target_date"],
|
||||
type: [null, "active", "backlog"],
|
||||
},
|
||||
extra_options: {
|
||||
@ -362,7 +362,7 @@ export const ISSUE_DISPLAY_FILTERS_BY_LAYOUT: {
|
||||
],
|
||||
display_properties: true,
|
||||
display_filters: {
|
||||
order_by: ["sort_order", "-created_at", "-updated_at", "start_date", "-priority"],
|
||||
order_by: ["sort_order", "-created_at", "-updated_at", "start_date", "priority"],
|
||||
type: [null, "active", "backlog"],
|
||||
},
|
||||
extra_options: {
|
||||
@ -385,7 +385,7 @@ export const ISSUE_DISPLAY_FILTERS_BY_LAYOUT: {
|
||||
],
|
||||
display_properties: false,
|
||||
display_filters: {
|
||||
order_by: ["sort_order", "-created_at", "-updated_at", "start_date", "-priority"],
|
||||
order_by: ["sort_order", "-created_at", "-updated_at", "start_date", "priority"],
|
||||
type: [null, "active", "backlog"],
|
||||
},
|
||||
extra_options: {
|
||||
|
@ -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) {
|
||||
|
@ -800,11 +800,11 @@ export class BaseIssuesStore implements IBaseIssuesStore {
|
||||
return this.getIssueIds(orderBy(array, "sort_order"));
|
||||
case "state__name":
|
||||
return this.getIssueIds(
|
||||
orderBy(array, (issue) => this.populateIssueDataForSorting("state_id", issue["state_id"]))
|
||||
orderBy(array, (issue) => this.populateIssueDataForSorting("state_id", issue?.["state_id"]))
|
||||
);
|
||||
case "-state__name":
|
||||
return this.getIssueIds(
|
||||
orderBy(array, (issue) => this.populateIssueDataForSorting("state_id", issue["state_id"]), ["desc"])
|
||||
orderBy(array, (issue) => this.populateIssueDataForSorting("state_id", issue?.["state_id"]), ["desc"])
|
||||
);
|
||||
// dates
|
||||
case "created_at":
|
||||
@ -844,12 +844,12 @@ export class BaseIssuesStore implements IBaseIssuesStore {
|
||||
// custom
|
||||
case "priority": {
|
||||
const sortArray = ISSUE_PRIORITIES.map((i) => i.key);
|
||||
return this.getIssueIds(orderBy(array, (currentIssue: TIssue) => indexOf(sortArray, currentIssue.priority)));
|
||||
return this.getIssueIds(orderBy(array, (currentIssue: TIssue) => indexOf(sortArray, currentIssue?.priority)));
|
||||
}
|
||||
case "-priority": {
|
||||
const sortArray = ISSUE_PRIORITIES.map((i) => i.key);
|
||||
return this.getIssueIds(
|
||||
orderBy(array, (currentIssue: TIssue) => indexOf(sortArray, currentIssue.priority), ["desc"])
|
||||
orderBy(array, (currentIssue: TIssue) => indexOf(sortArray, currentIssue?.priority), ["desc"])
|
||||
);
|
||||
}
|
||||
|
||||
@ -887,7 +887,7 @@ export class BaseIssuesStore implements IBaseIssuesStore {
|
||||
return this.getIssueIds(
|
||||
orderBy(array, [
|
||||
this.getSortOrderToFilterEmptyValues.bind(null, "label_ids"), //preferring sorting based on empty values to always keep the empty values below
|
||||
(issue) => this.populateIssueDataForSorting("label_ids", issue["label_ids"], "asc"),
|
||||
(issue) => this.populateIssueDataForSorting("label_ids", issue?.["label_ids"], "asc"),
|
||||
])
|
||||
);
|
||||
case "-labels__name":
|
||||
@ -896,7 +896,7 @@ export class BaseIssuesStore implements IBaseIssuesStore {
|
||||
array,
|
||||
[
|
||||
this.getSortOrderToFilterEmptyValues.bind(null, "label_ids"), //preferring sorting based on empty values to always keep the empty values below
|
||||
(issue) => this.populateIssueDataForSorting("label_ids", issue["label_ids"], "desc"),
|
||||
(issue) => this.populateIssueDataForSorting("label_ids", issue?.["label_ids"], "desc"),
|
||||
],
|
||||
["asc", "desc"]
|
||||
)
|
||||
@ -906,7 +906,7 @@ export class BaseIssuesStore implements IBaseIssuesStore {
|
||||
return this.getIssueIds(
|
||||
orderBy(array, [
|
||||
this.getSortOrderToFilterEmptyValues.bind(null, "module_ids"), //preferring sorting based on empty values to always keep the empty values below
|
||||
(issue) => this.populateIssueDataForSorting("module_ids", issue["module_ids"], "asc"),
|
||||
(issue) => this.populateIssueDataForSorting("module_ids", issue?.["module_ids"], "asc"),
|
||||
])
|
||||
);
|
||||
case "-issue_module__module__name":
|
||||
@ -915,7 +915,7 @@ export class BaseIssuesStore implements IBaseIssuesStore {
|
||||
array,
|
||||
[
|
||||
this.getSortOrderToFilterEmptyValues.bind(null, "module_ids"), //preferring sorting based on empty values to always keep the empty values below
|
||||
(issue) => this.populateIssueDataForSorting("module_ids", issue["module_ids"], "desc"),
|
||||
(issue) => this.populateIssueDataForSorting("module_ids", issue?.["module_ids"], "desc"),
|
||||
],
|
||||
["asc", "desc"]
|
||||
)
|
||||
@ -925,7 +925,7 @@ export class BaseIssuesStore implements IBaseIssuesStore {
|
||||
return this.getIssueIds(
|
||||
orderBy(array, [
|
||||
this.getSortOrderToFilterEmptyValues.bind(null, "cycle_id"), //preferring sorting based on empty values to always keep the empty values below
|
||||
(issue) => this.populateIssueDataForSorting("cycle_id", issue["cycle_id"], "asc"),
|
||||
(issue) => this.populateIssueDataForSorting("cycle_id", issue?.["cycle_id"], "asc"),
|
||||
])
|
||||
);
|
||||
case "-issue_cycle__cycle__name":
|
||||
@ -934,7 +934,7 @@ export class BaseIssuesStore implements IBaseIssuesStore {
|
||||
array,
|
||||
[
|
||||
this.getSortOrderToFilterEmptyValues.bind(null, "cycle_id"), //preferring sorting based on empty values to always keep the empty values below
|
||||
(issue) => this.populateIssueDataForSorting("cycle_id", issue["cycle_id"], "desc"),
|
||||
(issue) => this.populateIssueDataForSorting("cycle_id", issue?.["cycle_id"], "desc"),
|
||||
],
|
||||
["asc", "desc"]
|
||||
)
|
||||
@ -944,7 +944,7 @@ export class BaseIssuesStore implements IBaseIssuesStore {
|
||||
return this.getIssueIds(
|
||||
orderBy(array, [
|
||||
this.getSortOrderToFilterEmptyValues.bind(null, "assignee_ids"), //preferring sorting based on empty values to always keep the empty values below
|
||||
(issue) => this.populateIssueDataForSorting("assignee_ids", issue["assignee_ids"], "asc"),
|
||||
(issue) => this.populateIssueDataForSorting("assignee_ids", issue?.["assignee_ids"], "asc"),
|
||||
])
|
||||
);
|
||||
case "-assignees__first_name":
|
||||
@ -953,7 +953,7 @@ export class BaseIssuesStore implements IBaseIssuesStore {
|
||||
array,
|
||||
[
|
||||
this.getSortOrderToFilterEmptyValues.bind(null, "assignee_ids"), //preferring sorting based on empty values to always keep the empty values below
|
||||
(issue) => this.populateIssueDataForSorting("assignee_ids", issue["assignee_ids"], "desc"),
|
||||
(issue) => this.populateIssueDataForSorting("assignee_ids", issue?.["assignee_ids"], "desc"),
|
||||
],
|
||||
["asc", "desc"]
|
||||
)
|
||||
|
@ -302,12 +302,12 @@ export class IssueFilterHelperStore implements IIssueFilterHelperStore {
|
||||
groupId?: string,
|
||||
subGroupId?: string
|
||||
) {
|
||||
const pageCursor = cursor ? cursor : groupId ? `${options.perPageCount * 2}:0:0` : `${options.perPageCount}:0:0`;
|
||||
const pageCursor = cursor ? cursor : groupId ? `${options.perPageCount}:1:0` : `${options.perPageCount}:0:0`;
|
||||
|
||||
const paginationParams: Partial<Record<TIssueParams, string | boolean>> = {
|
||||
...filterParams,
|
||||
cursor: pageCursor,
|
||||
per_page: (groupId ? options.perPageCount * 2 : options.perPageCount).toString(),
|
||||
per_page: options.perPageCount.toString(),
|
||||
};
|
||||
|
||||
if (options.groupedBy) {
|
||||
|
@ -122,7 +122,9 @@ export class IssueSubIssuesStore implements IIssueSubIssuesStore {
|
||||
|
||||
// fetch other issues states and members when sub-issues are from different project
|
||||
if (subIssues && subIssues.length > 0) {
|
||||
const otherProjectIds = uniq(subIssues.map((issue) => issue.project_id).filter((id) => id !== projectId));
|
||||
const otherProjectIds = uniq(
|
||||
subIssues.map((issue) => issue.project_id).filter((id) => !!id && id !== projectId)
|
||||
) as string[];
|
||||
this.fetchOtherProjectProperties(workspaceSlug, otherProjectIds);
|
||||
}
|
||||
|
||||
@ -152,7 +154,9 @@ export class IssueSubIssuesStore implements IIssueSubIssuesStore {
|
||||
|
||||
// fetch other issues states and members when sub-issues are from different project
|
||||
if (subIssues && subIssues.length > 0) {
|
||||
const otherProjectIds = uniq(subIssues.map((issue) => issue.project_id).filter((id) => id !== projectId));
|
||||
const otherProjectIds = uniq(
|
||||
subIssues.map((issue) => issue.project_id).filter((id) => !!id && id !== projectId)
|
||||
) as string[];
|
||||
this.fetchOtherProjectProperties(workspaceSlug, otherProjectIds);
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user