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