diff --git a/web/components/page-views/workspace-dashboard.tsx b/web/components/page-views/workspace-dashboard.tsx index b7fcb7cb6..3656c90a8 100644 --- a/web/components/page-views/workspace-dashboard.tsx +++ b/web/components/page-views/workspace-dashboard.tsx @@ -1,4 +1,4 @@ -import { useState } from "react"; +import { useEffect, useState } from "react"; import Image from "next/image"; import { useRouter } from "next/router"; import useSWR from "swr"; @@ -19,7 +19,6 @@ export const WorkspaceDashboardView = observer(() => { const { workspaceSlug } = router.query; // store const { user: userStore, project: projectStore, commandPalette: commandPaletteStore } = useMobxStore(); - const user = userStore.currentUser; const projects = workspaceSlug ? projectStore.projects[workspaceSlug.toString()] : null; const workspaceDashboardInfo = userStore.dashboardInfo; diff --git a/web/services/issue/issue.service.ts b/web/services/issue/issue.service.ts index 5f2844896..ab4902db4 100644 --- a/web/services/issue/issue.service.ts +++ b/web/services/issue/issue.service.ts @@ -3,6 +3,7 @@ import { APIService } from "services/api.service"; import { TrackEventService } from "services/track_event.service"; // type import type { IUser, IIssue, IIssueActivity, ISubIssueResponse, IIssueDisplayProperties } from "types"; +import type { IIssueResponse } from "store/project-issues"; // helper import { API_BASE_URL } from "helpers/common.helper"; @@ -32,6 +33,16 @@ export class IssueService extends APIService { }); } + async getV3Issues(workspaceSlug: string, projectId: string, queries?: any): Promise { + return this.get(`/api/v3/workspaces/${workspaceSlug}/projects/${projectId}/issues/`, { + params: queries, + }) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } + async getIssuesWithParams( workspaceSlug: string, projectId: string, diff --git a/web/store/project-issues.ts b/web/store/project-issues.ts new file mode 100644 index 000000000..a756d26c2 --- /dev/null +++ b/web/store/project-issues.ts @@ -0,0 +1,209 @@ +import { action, observable, makeObservable, computed, runInAction } from "mobx"; +// services +import { IssueService } from "services/issue/issue.service"; +// constants +import { ISSUE_PRIORITIES, ISSUE_STATE_GROUPS } from "constants/issue"; +// types +import { IIssue, IState, TIssueGroupByOptions } from "types"; +import { RootStore } from "store/root"; + +export interface IGroupedIssues { + [group_id: string]: string[]; +} + +export interface ISubGroupedIssues { + [sub_grouped_id: string]: { + [group_id: string]: string[]; + }; +} + +export type TUnGroupedIssues = string[]; + +export interface IIssueResponse { + [issue_id: string]: IIssue; +} + +enum issueGroupByKeys { + state = "state", + "state_detail.group" = "state_detail.group", + priority = "priority", + labels = "labels", + created_by = "created_by", + project = "project", + assignees = "assignees", + mentions = "assignees", +} + +export interface IProjectIssueStore { + loader: "init-loader" | "mutation" | null; + projectId: string | undefined; + issues: + | { + [project_id: string]: { + [issue_id: string]: IIssue; + }; + } + | undefined; + + // computed + groupedIssues: IGroupedIssues | undefined; + subGroupedIssues: ISubGroupedIssues | undefined; + unGroupedIssues: TUnGroupedIssues | undefined; + + // actions + fetchProjectIssues: (workspaceSlug: string, projectId: string) => Promise | undefined; +} + +export class ProjectIssueStore implements IProjectIssueStore { + loader: "init-loader" | "mutation" | null = null; + projectId: string | undefined = undefined; + issues: + | { + [project_id: string]: { + [issue_id: string]: IIssue; + }; + } + | undefined = undefined; + // root store + rootStore; + // service + issueService; + + constructor(_rootStore: RootStore | null = null) { + makeObservable(this, { + // observable + loader: observable.ref, + projectId: observable.ref, + issues: observable.ref, + // computed + groupedIssues: computed, + subGroupedIssues: computed, + unGroupedIssues: computed, + // action + fetchProjectIssues: action, + }); + + this.rootStore = _rootStore; + this.issueService = new IssueService(); + } + + get groupedIssues() { + const groupBy: TIssueGroupByOptions | undefined = this.rootStore?.issueFilter.userDisplayFilters.group_by; + const projectId: string | undefined | null = this.rootStore?.project.projectId; + + if (!groupBy || !projectId || !this.issues || !this.issues[projectId]) return undefined; + + const displayFiltersDefaultData: { [filter_key: string]: string[] } = { + state: (this.rootStore?.projectState?.states?.[projectId] ?? []).map((i: IState) => i.id), + "state_detail.group": ISSUE_STATE_GROUPS.map((i) => i.key), + priority: ISSUE_PRIORITIES.map((i) => i.key), + labels: [...(this.rootStore?.project?.projectLabels ?? []).map((i) => i.id), "None"], + created_by: (this.rootStore?.project?.projectMembers ?? []).map((i) => i.member.id), + project: (this.rootStore?.project.workspaceProjects ?? []).map((i) => i.id), + assignees: [...(this.rootStore?.project?.projectMembers ?? []).map((i) => i.member.id), "None"], + }; + + const issues: { [group_id: string]: string[] } = {}; + displayFiltersDefaultData[groupBy].forEach((group) => { + issues[group] = []; + }); + + const projectIssues = this.issues[projectId]; + + for (const issue in projectIssues) { + const _issue = projectIssues[issue]; + const groupArray = this.getGroupArray(_issue[issueGroupByKeys[groupBy] as keyof IIssue]); + + for (const group of groupArray) { + if (group && issues[group]) { + issues[group].push(_issue.id); + } + } + } + + return issues; + } + + get subGroupedIssues() { + const subGroupBy: TIssueGroupByOptions | undefined = this.rootStore?.issueFilter.userDisplayFilters.sub_group_by; + const groupBy: TIssueGroupByOptions | undefined = this.rootStore?.issueFilter.userDisplayFilters.group_by; + const projectId: string | undefined | null = this.rootStore?.project.projectId; + + if (!subGroupBy || !groupBy || !projectId || !this.issues || !this.issues[projectId]) return undefined; + + const displayFiltersDefaultData: { [filter_key: string]: string[] } = { + state: (this.rootStore?.projectState?.states?.[projectId] ?? []).map((i: IState) => i.id), + "state_detail.group": ISSUE_STATE_GROUPS.map((i) => i.key), + priority: ISSUE_PRIORITIES.map((i) => i.key), + labels: [...(this.rootStore?.project?.projectLabels ?? []).map((i) => i.id), "None"], + created_by: (this.rootStore?.project?.projectMembers ?? []).map((i) => i.member.id), + project: (this.rootStore?.project.workspaceProjects ?? []).map((i) => i.id), + assignees: [...(this.rootStore?.project?.projectMembers ?? []).map((i) => i.member.id), "None"], + }; + + const issues: { [sub_group_id: string]: { [group_id: string]: string[] } } = {}; + displayFiltersDefaultData[subGroupBy].forEach((sub_group: any) => { + const groupByIssues: { [group_id: string]: string[] } = {}; + displayFiltersDefaultData[groupBy].forEach((group) => { + groupByIssues[group] = []; + }); + issues[sub_group] = groupByIssues; + }); + + const projectIssues = this.issues[projectId]; + + for (const issue in projectIssues) { + const _issue = projectIssues[issue]; + const subGroupArray = this.getGroupArray(_issue[issueGroupByKeys[subGroupBy] as keyof IIssue]); + const groupArray = this.getGroupArray(_issue[issueGroupByKeys[groupBy] as keyof IIssue]); + + for (const subGroup of subGroupArray) { + for (const group of groupArray) { + if (subGroup && group && issues[subGroup]) { + issues[subGroup][group].push(_issue.id); + } + } + } + } + + return issues; + } + + get unGroupedIssues() { + if (!this.projectId || !this.issues || !this.issues[this.projectId]) return undefined; + return Object.keys(this.issues[this.projectId]); + } + + fetchProjectIssues = async (workspaceSlug: string, projectId: string) => { + try { + this.projectId = projectId; + this.rootStore?.project.setProjectId(projectId); + + const response = await this.issueService.getV3Issues(workspaceSlug, projectId); + const _issues = { + ...this.issues, + [projectId]: { ...response }, + }; + + runInAction(() => { + this.issues = _issues; + }); + + return response; + } catch (error) { + throw error; + } + }; + + /** + * + * @description this function helps to convert the typeof value (array | string | null) to array and returns an array + */ + getGroupArray(value: string[] | string | null) { + if (Array.isArray(value)) { + return value; + } else { + return [value || "None"]; + } + } +} diff --git a/web/store/root.ts b/web/store/root.ts index 655549025..65d9fb598 100644 --- a/web/store/root.ts +++ b/web/store/root.ts @@ -108,6 +108,9 @@ import { InboxStore, } from "store/inbox"; +// v3 +import { ProjectIssueStore, IProjectIssueStore } from "store/project-issues"; + import { IMentionsStore, MentionsStore } from "store/editor"; enableStaticRendering(typeof window === "undefined"); @@ -176,6 +179,8 @@ export class RootStore { mentionsStore: IMentionsStore; + projectIssues: IProjectIssueStore; + constructor() { this.appConfig = new AppConfigStore(this); this.commandPalette = new CommandPaletteStore(this); @@ -239,5 +244,7 @@ export class RootStore { this.inboxFilters = new InboxFiltersStore(this); this.mentionsStore = new MentionsStore(this); + + this.projectIssues = new ProjectIssueStore(this); } }