diff --git a/web/components/core/modals/bulk-delete-issues-modal-item.tsx b/web/components/core/modals/bulk-delete-issues-modal-item.tsx index 8b03c311b..ee18f15cc 100644 --- a/web/components/core/modals/bulk-delete-issues-modal-item.tsx +++ b/web/components/core/modals/bulk-delete-issues-modal-item.tsx @@ -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 = 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 = observer((props: Props) => { + const { issue, canDeleteIssueIds, identifier } = props; + + const color = issue.state__color; return ( = observer((props) => { } >
- + = observer((props) => { ))} diff --git a/web/components/cycles/active-cycle/cycle-stats.tsx b/web/components/cycles/active-cycle/cycle-stats.tsx index 2eb128763..0bcb49695 100644 --- a/web/components/cycles/active-cycle/cycle-stats.tsx +++ b/web/components/cycles/active-cycle/cycle-stats.tsx @@ -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 = 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 (
@@ -132,52 +135,73 @@ export const ActiveCycleStats: FC = observer((props) => { className="flex h-52 w-full flex-col gap-1 overflow-y-auto text-custom-text-200 vertical-scrollbar scrollbar-sm" >
- {cycleIssues ? ( - cycleIssues.length > 0 ? ( - cycleIssues.map((issue: TIssue) => ( - -
- + {cycleIssueDetails && cycleIssueDetails.issueIds ? ( + cycleIssueDetails.issueCount > 0 ? ( + <> + {cycleIssueDetails.issueIds.map((issueId: string) => { + const issue = getIssueById(issueId); - - - {currentProjectDetails?.identifier}-{issue.sequence_id} - - - - {issue.name} - -
-
- {}} - projectId={projectId?.toString() ?? ""} - disabled - buttonVariant="background-with-text" - buttonContainerClassName="cursor-pointer max-w-24" - showTooltip - /> - {issue.target_date && ( - -
- - - {renderFormattedDateWithoutYear(issue.target_date)} +
+ + + + + {currentProjectDetails?.identifier}-{issue.sequence_id} -
- - )} + + + {issue.name} + +
+
+ {}} + projectId={projectId?.toString() ?? ""} + disabled + buttonVariant="background-with-text" + buttonContainerClassName="cursor-pointer max-w-24" + showTooltip + /> + {issue.target_date && ( + +
+ + + {renderFormattedDateWithoutYear(issue.target_date)} + +
+
+ )} +
+ + ); + })} + {cycleIssueDetails.nextPageResults && ( +
fetchNextActiveCycleIssues(workspaceSlug, projectId, cycle.id)} + > + Load more ↓
- - )) + )} + ) : (
void; }; -const issueService = new IssueService(); +const projectService = new ProjectService(); export const SelectDuplicateInboxIssueModal: React.FC = (props) => { const { isOpen, onClose, onSubmit, value } = props; @@ -35,18 +36,27 @@ export const SelectDuplicateInboxIssueModal: React.FC = (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([]); + 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) => { handleClose(); }; - const filteredIssues = (query === "" ? issues : issues?.filter((issue) => issue.name.includes(query))) ?? []; + const issueList = + filteredIssues.length > 0 ? ( +
  • + {query === "" &&

    Select issue

    } +
      + {filteredIssues.map((issue) => { + const stateColor = issue.state__color || ""; + + return ( + + `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" : "" + } ` + } + > +
      + + + {getProjectById(issue?.project_id)?.identifier}-{issue.sequence_id} + + {issue.name} +
      +
      + ); + })} +
    +
  • + ) : ( +
    + +
    + ); return ( setQuery("")} appear> @@ -122,56 +177,15 @@ export const SelectDuplicateInboxIssueModal: React.FC = (props) => { static className="max-h-80 scroll-py-2 divide-y divide-custom-border-200 overflow-y-auto" > - {filteredIssues.length > 0 ? ( -
  • - {query === "" && ( -

    Select issue

    - )} -
      - {filteredIssues.map((issue) => { - const stateColor = - getProjectStates(issue?.project_id ?? "")?.find((state) => state?.id == issue?.state_id) - ?.color || ""; - - return ( - - `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" : "" - } ` - } - > -
      - - - {getProjectById(issue?.project_id)?.identifier}-{issue.sequence_id} - - {issue.name} -
      -
      - ); - })} -
    -
  • + {isSearching ? ( + + + + + + ) : ( -
    - -
    + <>{issueList} )} diff --git a/web/components/issues/issue-layouts/calendar/calendar.tsx b/web/components/issues/issue-layouts/calendar/calendar.tsx index c1b8ffa97..7b14f7a8e 100644 --- a/web/components/issues/issue-layouts/calendar/calendar.tsx +++ b/web/components/issues/issue-layouts/calendar/calendar.tsx @@ -106,7 +106,7 @@ export const CalendarChart: React.FC = observer((props) => {
    ); - const issueIdList = groupedIssueIds ? groupedIssueIds[formattedDatePayload] : null; + const issueIdList = groupedIssueIds ? groupedIssueIds[formattedDatePayload] : []; return ( <> @@ -182,6 +182,9 @@ export const CalendarChart: React.FC = observer((props) => { date={selectedDate} issues={issues} issueIdList={issueIdList} + loadMoreIssues={loadMoreIssues} + getPaginationData={getPaginationData} + getGroupIssueCount={getGroupIssueCount} quickActions={quickActions} enableQuickIssueCreate disableIssueCreation={!enableIssueCreation || !isEditingAllowed} diff --git a/web/components/issues/issue-layouts/calendar/day-tile.tsx b/web/components/issues/issue-layouts/calendar/day-tile.tsx index 3945816be..9f4986b2f 100644 --- a/web/components/issues/issue-layouts/calendar/day-tile.tsx +++ b/web/components/issues/issue-layouts/calendar/day-tile.tsx @@ -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 = 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 = 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 = observer((props) => { readOnly={readOnly} /> - {enableQuickIssueCreate && !disableIssueCreation && !readOnly && ( -
    - -
    - )} - - {shouldLoadMore && ( -
    - -
    - )} - {provided.placeholder}
    )} diff --git a/web/components/issues/issue-layouts/calendar/issue-blocks.tsx b/web/components/issues/issue-layouts/calendar/issue-blocks.tsx index e660e3f60..f6108bc3e 100644 --- a/web/components/issues/issue-layouts/calendar/issue-blocks.tsx +++ b/web/components/issues/issue-layouts/calendar/issue-blocks.tsx @@ -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 = observer((props) => { issues, issueIdList, quickActions, + loadMoreIssues, + getPaginationData, + getGroupIssueCount, isDragDisabled = false, enableQuickIssueCreate, disableIssueCreation, @@ -45,13 +50,18 @@ export const CalendarIssueBlocks: React.FC = 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 = observer((props) => { )} {enableQuickIssueCreate && !disableIssueCreation && !readOnly && ( -
    +
    = observer((props) => { quickAddCallback={quickAddCallback} addIssuesToView={addIssuesToView} viewId={viewId} - onOpen={() => setShowAllIssues(true)} />
    )} - {totalIssues > 4 && ( -
    + + {shouldLoadMore && ( +
    )} diff --git a/web/components/issues/issue-layouts/kanban/roots/cycle-root.tsx b/web/components/issues/issue-layouts/kanban/roots/cycle-root.tsx index be61dbbda..778803374 100644 --- a/web/components/issues/issue-layouts/kanban/roots/cycle-root.tsx +++ b/web/components/issues/issue-layouts/kanban/roots/cycle-root.tsx @@ -35,7 +35,6 @@ export const CycleKanBanLayout: React.FC = observer(() => { return ( ( - + )); diff --git a/web/components/issues/issue-layouts/kanban/roots/module-root.tsx b/web/components/issues/issue-layouts/kanban/roots/module-root.tsx index 14eaa0c19..059f822b2 100644 --- a/web/components/issues/issue-layouts/kanban/roots/module-root.tsx +++ b/web/components/issues/issue-layouts/kanban/roots/module-root.tsx @@ -21,7 +21,6 @@ export const ModuleKanBanLayout: React.FC = observer(() => { return ( { return ( ( - + )); diff --git a/web/components/issues/issue-layouts/kanban/roots/project-view-root.tsx b/web/components/issues/issue-layouts/kanban/roots/project-view-root.tsx index ff52def5f..0b2898b56 100644 --- a/web/components/issues/issue-layouts/kanban/roots/project-view-root.tsx +++ b/web/components/issues/issue-layouts/kanban/roots/project-view-root.tsx @@ -16,7 +16,6 @@ export const ProjectViewKanBanLayout: React.FC = observer(() => { return ( = 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" diff --git a/web/components/issues/issue-modal/form.tsx b/web/components/issues/issue-modal/form.tsx index ec140f59f..aec62fe0d 100644 --- a/web/components/issues/issue-modal/form.tsx +++ b/web/components/issues/issue-modal/form.tsx @@ -464,7 +464,7 @@ export const IssueFormRoot: FC = 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} diff --git a/web/components/profile/profile-issues.tsx b/web/components/profile/profile-issues.tsx index 270dff658..89ab8771c 100644 --- a/web/components/profile/profile-issues.tsx +++ b/web/components/profile/profile-issues.tsx @@ -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" ? : }; - - if (groupedIssueIds.length === 0) { - return ; - } - return ( <> diff --git a/web/constants/issue.ts b/web/constants/issue.ts index 20ca9a1e1..535ef3f21 100644 --- a/web/constants/issue.ts +++ b/web/constants/issue.ts @@ -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: { diff --git a/web/store/issue/cycle/issue.store.ts b/web/store/issue/cycle/issue.store.ts index 11da8b6e8..5fb621b5e 100644 --- a/web/store/issue/cycle/issue.store.ts +++ b/web/store/issue/cycle/issue.store.ts @@ -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; + //action helpers + getActiveCycleId: (cycleId: string) => ActiveCycleIssueDetails | undefined; // actions fetchIssues: ( workspaceSlug: string, @@ -34,6 +47,18 @@ export interface ICycleIssues extends IBaseIssuesStore { subGroupId?: string ) => Promise; + fetchActiveCycleIssues: ( + workspaceSlug: string, + projectId: string, + perPageCount: number, + cycleId: string + ) => Promise; + fetchNextActiveCycleIssues: ( + workspaceSlug: string, + projectId: string, + cycleId: string + ) => Promise; + createIssue: (workspaceSlug: string, projectId: string, data: Partial, cycleId: string) => Promise; updateIssue: (workspaceSlug: string, projectId: string, issueId: string, data: Partial) => Promise; archiveIssue: (workspaceSlug: string, projectId: string, issueId: string) => Promise; @@ -56,15 +81,11 @@ export interface ICycleIssues extends IBaseIssuesStore { new_cycle_id: string; } ) => Promise; - fetchActiveCycleIssues: ( - workspaceSlug: string, - projectId: string, - cycleId: string - ) => Promise; } export class CycleIssues extends BaseIssuesStore implements ICycleIssues { cycleId: string | undefined = undefined; + activeCycleIds: Record = {}; 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) { diff --git a/web/store/issue/helpers/base-issues.store.ts b/web/store/issue/helpers/base-issues.store.ts index 9def82375..f90107cf8 100644 --- a/web/store/issue/helpers/base-issues.store.ts +++ b/web/store/issue/helpers/base-issues.store.ts @@ -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"] ) diff --git a/web/store/issue/helpers/issue-filter-helper.store.ts b/web/store/issue/helpers/issue-filter-helper.store.ts index 46b3d089b..2ab777ef3 100644 --- a/web/store/issue/helpers/issue-filter-helper.store.ts +++ b/web/store/issue/helpers/issue-filter-helper.store.ts @@ -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> = { ...filterParams, cursor: pageCursor, - per_page: (groupId ? options.perPageCount * 2 : options.perPageCount).toString(), + per_page: options.perPageCount.toString(), }; if (options.groupedBy) { diff --git a/web/store/issue/issue-details/sub_issues.store.ts b/web/store/issue/issue-details/sub_issues.store.ts index f4bd20772..bbe6ca629 100644 --- a/web/store/issue/issue-details/sub_issues.store.ts +++ b/web/store/issue/issue-details/sub_issues.store.ts @@ -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); }