diff --git a/web/components/issues/issue-layouts/kanban/swimlanes.tsx b/web/components/issues/issue-layouts/kanban/swimlanes.tsx index 063d797b4..b76d82b91 100644 --- a/web/components/issues/issue-layouts/kanban/swimlanes.tsx +++ b/web/components/issues/issue-layouts/kanban/swimlanes.tsx @@ -51,7 +51,10 @@ const SubGroupSwimlaneHeader: React.FC = ({ {list && list.length > 0 && list.map((_list: any) => ( -
+
{ // router @@ -33,6 +36,29 @@ export const ProjectLayoutRoot: React.FC = observer(() => { } }); + // TODO: update this + // const { + // issues: { + // loader: issueLoader, + // getIssues: issueGetIssues, + // getIssuesIds: issueGetIssuesIds, + // fetchIssues: issueFetchIssues, + // }, + // issuesFilter: { issueFilters: issueIssueFilters, fetchFilters: issueFetchFilters }, + // } = useStoreIssues("project"); + + // useSWR(workspaceSlug && projectId ? `PROJECT_ISSUES_V3_UPGRADED_${workspaceSlug}_${projectId}` : null, async () => { + // if (workspaceSlug && projectId) { + // await issueFetchFilters(workspaceSlug, projectId); + // await issueFetchIssues(workspaceSlug, projectId, getIssues ? "mutation" : "init-loader"); + // } + // }); + + // console.log("---"); + // console.log("issueGetIssuesIds", issueGetIssuesIds); + // console.log("issueGetIssues", issueGetIssues); + // console.log("---"); + const activeLayout = issueFilters?.displayFilters?.layout; return ( diff --git a/web/constants/common.ts b/web/constants/common.ts index 3fac821fa..de4c0c558 100644 --- a/web/constants/common.ts +++ b/web/constants/common.ts @@ -1 +1,7 @@ export const MAX_FILE_SIZE = 5 * 1024 * 1024; // 5MB + +export const isNil = (value: any) => { + if (value === undefined || value === null) return true; + + return false; +}; diff --git a/web/constants/issue.ts b/web/constants/issue.ts index 965ab69cc..c280df8bc 100644 --- a/web/constants/issue.ts +++ b/web/constants/issue.ts @@ -13,6 +13,12 @@ import { TStateGroups, } from "types"; +export enum EFilterType { + FILTERS = "filters", + DISPLAY_FILTERS = "display_filters", + DISPLAY_PROPERTIES = "display_properties", +} + export const ISSUE_PRIORITIES: { key: TIssuePriorities; title: string; diff --git a/web/contexts/app-root/app-root-provider.tsx b/web/contexts/app-root/app-root-provider.tsx new file mode 100644 index 000000000..92a3f73ef --- /dev/null +++ b/web/contexts/app-root/app-root-provider.tsx @@ -0,0 +1,19 @@ +import { createContext } from "react"; +// mobx store +import { AppRootStore, IAppRootStore } from "store/application"; + +let appRootStore: IAppRootStore = new AppRootStore(); + +export const AppRootStoreContext = createContext(appRootStore); + +const initializeStore = () => { + const _appRootStore: IAppRootStore = appRootStore ?? new AppRootStore(); + if (typeof window === "undefined") return _appRootStore; + if (!appRootStore) appRootStore = _appRootStore; + return _appRootStore; +}; + +export const AppRootStoreProvider = ({ children }: any) => { + const store: IAppRootStore = initializeStore(); + return {children}; +}; diff --git a/web/contexts/app-root/index.ts b/web/contexts/app-root/index.ts new file mode 100644 index 000000000..416a8ec63 --- /dev/null +++ b/web/contexts/app-root/index.ts @@ -0,0 +1,2 @@ +export * from "./app-root-provider"; +export * from "./use-app-root"; diff --git a/web/contexts/app-root/use-app-root.tsx b/web/contexts/app-root/use-app-root.tsx new file mode 100644 index 000000000..363192716 --- /dev/null +++ b/web/contexts/app-root/use-app-root.tsx @@ -0,0 +1,8 @@ +import { useContext } from "react"; +import { AppRootStoreContext } from "./app-root-provider"; + +export const useAppRoot = () => { + const context = useContext(AppRootStoreContext); + if (context === undefined) throw new Error("useAppRoot must be used within AppRootStoreContext"); + return context; +}; diff --git a/web/contexts/page.context/index.ts b/web/contexts/page.context/index.ts new file mode 100644 index 000000000..78f8c7a77 --- /dev/null +++ b/web/contexts/page.context/index.ts @@ -0,0 +1,2 @@ +export * from "./page-provider"; +export * from "./use-page"; diff --git a/web/contexts/page.context/page-provider.tsx b/web/contexts/page.context/page-provider.tsx new file mode 100644 index 000000000..5026edabc --- /dev/null +++ b/web/contexts/page.context/page-provider.tsx @@ -0,0 +1,20 @@ +import { createContext } from "react"; +// mobx store +import { PageStore } from "store/page.store"; +import { AppRootStore } from "store/application"; + +let pageStore: PageStore = new PageStore(new AppRootStore()); + +export const PageContext = createContext(pageStore); + +const initializeStore = () => { + const _pageStore: PageStore = pageStore ?? new PageStore(pageStore); + if (typeof window === "undefined") return _pageStore; + if (!pageStore) pageStore = _pageStore; + return _pageStore; +}; + +export const AppRootStoreProvider = ({ children }: any) => { + const store: PageStore = initializeStore(); + return {children}; +}; diff --git a/web/contexts/page.context/use-page.tsx b/web/contexts/page.context/use-page.tsx new file mode 100644 index 000000000..85e3d1500 --- /dev/null +++ b/web/contexts/page.context/use-page.tsx @@ -0,0 +1,8 @@ +import { useContext } from "react"; +import { PageContext } from "./page-provider"; + +export const usePage = () => { + const context = useContext(PageContext); + if (context === undefined) throw new Error("useAppRoot must be used within AppRootStoreContext"); + return context; +}; diff --git a/web/hooks/use-page.tsx b/web/hooks/use-page.tsx new file mode 100644 index 000000000..3852cc79b --- /dev/null +++ b/web/hooks/use-page.tsx @@ -0,0 +1,8 @@ +import { useMobxStore } from "lib/mobx/store-provider"; + +const usePage = () => { + const { page } = useMobxStore(); + return { ...page }; +}; + +export default usePage; diff --git a/web/hooks/use-store-issues.tsx b/web/hooks/use-store-issues.tsx new file mode 100644 index 000000000..8a48801b6 --- /dev/null +++ b/web/hooks/use-store-issues.tsx @@ -0,0 +1,118 @@ +// issue store provider +import { useMobxIssueStore } from "lib/mobx/store-issues-provider"; +// types +import { IWorkspaceIssues, IWorkspaceIssuesFilter } from "store/issue/workspace"; +import { IProfileIssues, IProfileIssuesFilter } from "store/issue/profile"; +import { IProjectIssues, IProjectIssuesFilter } from "store/issue/project"; +import { ICycleIssues, ICycleIssuesFilter } from "store/issue/cycle"; +import { IModuleIssues, IModuleIssuesFilter } from "store/issue/module"; +import { IProjectViewIssues, IProjectViewIssuesFilter } from "store/issue/project-views"; +import { IArchivedIssues, IArchivedIssuesFilter } from "store/issue/archived"; +import { IDraftIssues, IDraftIssuesFilter } from "store/issue/draft"; + +interface IStoreIssues { + // workspace: { + // issues: IWorkspaceIssues; + // issuesFilter: IWorkspaceIssuesFilter; + // }; + // profile: { + // issues: IProfileIssues; + // issuesFilter: IProfileIssuesFilter; + // }; + project: { + issues: IProjectIssues; + issuesFilter: IProjectIssuesFilter; + }; + // cycle: { + // issues: ICycleIssues; + // issuesFilter: ICycleIssuesFilter; + // }; + // module: { + // issues: IModuleIssues; + // issuesFilter: IModuleIssuesFilter; + // }; + // projectView: { + // issues: IProjectViewIssues; + // issuesFilter: IProjectViewIssuesFilter; + // }; + // archived: { + // issues: IArchivedIssues; + // issuesFilter: IArchivedIssuesFilter; + // }; + // draft: { + // issues: IDraftIssues; + // issuesFilter: IDraftIssuesFilter; + // }; +} + +interface IStoreIssuesWithHelpers extends IStoreIssues { + calendarHelper: any; + kanbanHelper: any; +} + +const useStoreIssues = (issueSpace: keyof IStoreIssues) => { + const { + issue: { + workspaceSlug, + issues, + workspaceIssues, + workspaceIssuesFilter, + profileIssues, + profileIssuesFilter, + projectIssues, + projectIssuesFilter, + cycleIssues, + cycleIssuesFilter, + moduleIssues, + moduleIssuesFilter, + projectViewIssues, + projectViewIssuesFilter, + archivedIssues, + archivedIssuesFilter, + draftIssues, + draftIssuesFilter, + }, + } = useMobxIssueStore(); + + const storeIssues: IStoreIssues = { + // workspace: { + // issues: workspaceIssues, + // issuesFilter: workspaceIssuesFilter, + // }, + // profile: { + // issues: profileIssues, + // issuesFilter: profileIssuesFilter, + // }, + project: { + issues: projectIssues, + issuesFilter: projectIssuesFilter, + }, + // cycle: { + // issues: cycleIssues, + // issuesFilter: cycleIssuesFilter, + // }, + // module: { + // issues: moduleIssues, + // issuesFilter: moduleIssuesFilter, + // }, + // projectView: { + // issues: projectViewIssues, + // issuesFilter: projectViewIssuesFilter, + // }, + // archived: { + // issues: archivedIssues, + // issuesFilter: archivedIssuesFilter, + // }, + // draft: { + // issues: draftIssues, + // issuesFilter: draftIssuesFilter, + // }, + }; + + return { + issues: storeIssues[issueSpace].issues, + issuesFilter: storeIssues[issueSpace].issuesFilter, + }; +}; + +export default useStoreIssues; diff --git a/web/lib/mobx/store-issues-provider.tsx b/web/lib/mobx/store-issues-provider.tsx new file mode 100644 index 000000000..4df885862 --- /dev/null +++ b/web/lib/mobx/store-issues-provider.tsx @@ -0,0 +1,28 @@ +"use client"; + +import { createContext, useContext } from "react"; +// mobx store +import { RootStore } from "store/root.store"; + +let rootStore: RootStore = new RootStore(); + +export const MobxStoreContext = createContext(rootStore); + +const initializeStore = () => { + const _rootStore: RootStore = rootStore ?? new RootStore(); + if (typeof window === "undefined") return _rootStore; + if (!rootStore) rootStore = _rootStore; + return _rootStore; +}; + +export const MobxIssueStoreProvider = ({ children }: any) => { + const store: RootStore = initializeStore(); + return {children}; +}; + +// hook +export const useMobxIssueStore = () => { + const context = useContext(MobxStoreContext); + if (context === undefined) throw new Error("useMobxIssueStore must be used within MobxIssueStoreProvider"); + return context; +}; diff --git a/web/lib/mobx/store-provider.tsx b/web/lib/mobx/store-provider.tsx index 7e9a57c51..1fd33b256 100644 --- a/web/lib/mobx/store-provider.tsx +++ b/web/lib/mobx/store-provider.tsx @@ -1,5 +1,3 @@ -"use client"; - import { createContext, useContext } from "react"; // mobx store import { RootStore } from "store_legacy/root"; diff --git a/web/pages/[workspaceSlug]/projects/[projectId]/pages/[pageId].tsx b/web/pages/[workspaceSlug]/projects/[projectId]/pages/[pageId].tsx index 69d6db1d3..4b12e6d5f 100644 --- a/web/pages/[workspaceSlug]/projects/[projectId]/pages/[pageId].tsx +++ b/web/pages/[workspaceSlug]/projects/[projectId]/pages/[pageId].tsx @@ -82,7 +82,7 @@ const PageDetailsPage: NextPageWithLayout = observer(() => { description_html: newDescription, }) .then(() => { - mutatePageDetails((prevData) => ({ ...prevData, description_html: newDescription }) as IPage, false); + mutatePageDetails((prevData) => ({ ...prevData, description_html: newDescription } as IPage), false); }); }; @@ -162,15 +162,12 @@ const PageDetailsPage: NextPageWithLayout = observer(() => { }, [pageDetails?.description_html]); // TODO: Verify the exhaustive-deps warning function createObjectFromArray(keys: string[], options: any): any { - return keys.reduce( - (obj, key) => { - if (options[key] !== undefined) { - obj[key] = options[key]; - } - return obj; - }, - {} as { [key: string]: any } - ); + return keys.reduce((obj, key) => { + if (options[key] !== undefined) { + obj[key] = options[key]; + } + return obj; + }, {} as { [key: string]: any }); } const mutatePageDetailsHelper = ( diff --git a/web/pages/_app.tsx b/web/pages/_app.tsx index c6fb89772..87a4eb03a 100644 --- a/web/pages/_app.tsx +++ b/web/pages/_app.tsx @@ -12,6 +12,8 @@ import "styles/react-datepicker.css"; import { SITE_TITLE } from "constants/seo-variables"; // mobx store provider import { MobxStoreProvider } from "lib/mobx/store-provider"; +import { MobxIssueStoreProvider } from "lib/mobx/store-issues-provider"; + import { AppProvider } from "lib/app-provider"; // types import { NextPageWithLayout } from "types/app"; @@ -30,7 +32,9 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) { {SITE_TITLE} - {getLayout()} + + {getLayout()} + ); diff --git a/web/pages/onboarding/index.tsx b/web/pages/onboarding/index.tsx index f6df0c4fc..d8469f129 100644 --- a/web/pages/onboarding/index.tsx +++ b/web/pages/onboarding/index.tsx @@ -158,8 +158,8 @@ const OnboardingPage: NextPageWithLayout = observer(() => { currentUser?.first_name ? `${currentUser?.first_name} ${currentUser?.last_name ?? ""}` : value.length > 0 - ? value - : currentUser?.email + ? value + : currentUser?.email } src={currentUser?.avatar} size={35} @@ -174,8 +174,8 @@ const OnboardingPage: NextPageWithLayout = observer(() => { {currentUser?.first_name ? `${currentUser?.first_name} ${currentUser?.last_name ?? ""}` : value.length > 0 - ? value - : null} + ? value + : null}

)} diff --git a/web/services/workspace.service.ts b/web/services/workspace.service.ts index e83a8e435..9c49f9876 100644 --- a/web/services/workspace.service.ts +++ b/web/services/workspace.service.ts @@ -16,8 +16,6 @@ import { IUserProjectsRole, } from "types"; import { IWorkspaceView } from "types/workspace-views"; -// store -import { IIssueGroupWithSubGroupsStructure, IIssueGroupedStructure, IIssueUnGroupedStructure } from "store_legacy/issue"; import { IIssueResponse } from "store_legacy/issues/types"; export class WorkspaceService extends APIService { @@ -25,7 +23,7 @@ export class WorkspaceService extends APIService { super(API_BASE_URL); } - async userWorkspaces(): Promise { + async userWorkspaces(): Promise> { return this.get("/api/users/me/workspaces/") .then((response) => response?.data) .catch((error) => { diff --git a/web/store/application/app-config.store.ts b/web/store/application/app-config.store.ts index 8e6ff558c..99d8d10ec 100644 --- a/web/store/application/app-config.store.ts +++ b/web/store/application/app-config.store.ts @@ -1,6 +1,5 @@ import { observable, action, makeObservable, runInAction } from "mobx"; // types -import { RootStore } from "../root.store"; import { IAppConfig } from "types/app"; // services import { AppConfigService } from "services/app_config.service"; @@ -14,13 +13,10 @@ export interface IAppConfigStore { export class AppConfigStore implements IAppConfigStore { // observables envConfig: IAppConfig | null = null; - - // root store - rootStore; // service appConfigService; - constructor(_rootStore: RootStore) { + constructor() { makeObservable(this, { // observables envConfig: observable.ref, @@ -28,9 +24,8 @@ export class AppConfigStore implements IAppConfigStore { fetchAppConfig: action, }); this.appConfigService = new AppConfigService(); - - this.rootStore = _rootStore; } + fetchAppConfig = async () => { try { const config = await this.appConfigService.envConfig(); diff --git a/web/store/application/command-palette.store.ts b/web/store/application/command-palette.store.ts index 4403c786a..b98a14049 100644 --- a/web/store/application/command-palette.store.ts +++ b/web/store/application/command-palette.store.ts @@ -1,6 +1,4 @@ import { observable, action, makeObservable, computed } from "mobx"; -// types -import { RootStore } from "../root.store"; // services import { ProjectService } from "services/project"; import { PageService } from "services/page.service"; @@ -58,15 +56,13 @@ export class CommandPaletteStore implements ICommandPaletteStore { isCreateIssueModalOpen: boolean = false; isDeleteIssueModalOpen: boolean = false; isBulkDeleteIssueModalOpen: boolean = false; - // root store - rootStore; // service projectService; pageService; createIssueStoreType: EProjectStore = EProjectStore.PROJECT; - constructor(_rootStore: RootStore) { + constructor() { makeObservable(this, { // observable isCommandPaletteOpen: observable.ref, @@ -95,7 +91,6 @@ export class CommandPaletteStore implements ICommandPaletteStore { toggleBulkDeleteIssueModal: action, }); - this.rootStore = _rootStore; this.projectService = new ProjectService(); this.pageService = new PageService(); } diff --git a/web/store/application/index.ts b/web/store/application/index.ts index 6e2ea3577..efc83f613 100644 --- a/web/store/application/index.ts +++ b/web/store/application/index.ts @@ -1,7 +1,6 @@ -import { RootStore } from "../root.store"; import { AppConfigStore, IAppConfigStore } from "./app-config.store"; import { CommandPaletteStore, ICommandPaletteStore } from "./command-palette.store"; -import { EventTrackerStore, IEventTrackerStore } from "./event-tracker.store"; +// import { EventTrackerStore, IEventTrackerStore } from "./event-tracker.store"; import { InstanceStore, IInstanceStore } from "./instance.store"; import { RouterStore, IRouterStore } from "./router.store"; import { ThemeStore, IThemeStore } from "./theme.store"; @@ -9,7 +8,7 @@ import { ThemeStore, IThemeStore } from "./theme.store"; export interface IAppRootStore { config: IAppConfigStore; commandPalette: ICommandPaletteStore; - eventTracker: IEventTrackerStore; + // eventTracker: IEventTrackerStore; instance: IInstanceStore; theme: IThemeStore; router: IRouterStore; @@ -18,17 +17,17 @@ export interface IAppRootStore { export class AppRootStore implements IAppRootStore { config: IAppConfigStore; commandPalette: ICommandPaletteStore; - eventTracker: IEventTrackerStore; + // eventTracker: IEventTrackerStore; instance: IInstanceStore; theme: IThemeStore; router: IRouterStore; - constructor(rootStore: RootStore) { - this.config = new AppConfigStore(rootStore); - this.commandPalette = new CommandPaletteStore(rootStore); - this.eventTracker = new EventTrackerStore(rootStore); - this.instance = new InstanceStore(rootStore); - this.theme = new ThemeStore(rootStore); + constructor() { this.router = new RouterStore(); + this.config = new AppConfigStore(); + this.commandPalette = new CommandPaletteStore(); + // this.eventTracker = new EventTrackerStore(this.router); + this.instance = new InstanceStore(); + this.theme = new ThemeStore(); } } diff --git a/web/store/application/instance.store.ts b/web/store/application/instance.store.ts index 3332a11d9..009d8aa6d 100644 --- a/web/store/application/instance.store.ts +++ b/web/store/application/instance.store.ts @@ -1,6 +1,4 @@ import { observable, action, computed, makeObservable, runInAction } from "mobx"; -// store -import { RootStore } from "../root.store"; // types import { IInstance, IInstanceConfiguration, IFormattedInstanceConfiguration, IInstanceAdmin } from "types/instance"; // services @@ -31,9 +29,8 @@ export class InstanceStore implements IInstanceStore { configurations: IInstanceConfiguration[] | null = null; // service instanceService; - rootStore; - constructor(_rootStore: RootStore) { + constructor() { makeObservable(this, { // observable loader: observable.ref, @@ -51,7 +48,6 @@ export class InstanceStore implements IInstanceStore { updateInstanceConfigurations: action, }); - this.rootStore = _rootStore; this.instanceService = new InstanceService(); } diff --git a/web/store/issue/archived/filter.store.ts b/web/store/issue/archived/filter.store.ts new file mode 100644 index 000000000..24618f6e7 --- /dev/null +++ b/web/store/issue/archived/filter.store.ts @@ -0,0 +1,142 @@ +import { computed, makeObservable } from "mobx"; +// base class +import { IssueFilterHelperStore } from "../helpers/issue-filter-helper.store"; +// helpers +import { handleIssueQueryParamsByLayout } from "helpers/issue.helper"; +// constants +import { isNil } from "constants/common"; +import { EFilterType } from "constants/issue"; +// types +import { IssueRootStore } from "../root.store"; +import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions, TIssueParams } from "types"; + +interface IProjectIssuesFilters { + filters: IIssueFilterOptions | undefined; + displayFilters: IIssueDisplayFilterOptions | undefined; + displayProperties: IIssueDisplayProperties | undefined; +} + +export interface IArchivedIssuesFilter { + // computed + issueFilters: IProjectIssuesFilters | undefined; + appliedFilters: TIssueParams[] | undefined; + // action + fetchFilters: (workspaceSlug: string, projectId: string) => Promise; + updateFilters: ( + workspaceSlug: string, + projectId: string, + filterType: EFilterType, + filters: IIssueFilterOptions | IIssueDisplayFilterOptions | IIssueDisplayProperties + ) => Promise; +} + +export class ArchivedIssuesFilter extends IssueFilterHelperStore implements IArchivedIssuesFilter { + // root store + rootStore; + + constructor(_rootStore: IssueRootStore) { + super(_rootStore); + + makeObservable(this, { + // computed + issueFilters: computed, + appliedFilters: computed, + }); + + // root store + this.rootStore = _rootStore; + } + + get issueFilters() { + const projectId = this.rootStore.projectId; + if (!projectId) return undefined; + const displayFilters = this.rootStore.issuesFilter.issueDisplayFilters(projectId); + + const _filters: IProjectIssuesFilters = { + filters: displayFilters?.filters, + displayFilters: displayFilters?.displayFilters, + displayProperties: displayFilters?.displayProperties, + }; + + return _filters; + } + + get appliedFilters() { + const userFilters = this.issueFilters; + if (!userFilters) return undefined; + + let filteredRouteParams: any = { + priority: userFilters?.filters?.priority || undefined, + state_group: userFilters?.filters?.state_group || undefined, + state: userFilters?.filters?.state || undefined, + assignees: userFilters?.filters?.assignees || undefined, + mentions: userFilters?.filters?.mentions || undefined, + created_by: userFilters?.filters?.created_by || undefined, + labels: userFilters?.filters?.labels || undefined, + start_date: userFilters?.filters?.start_date || undefined, + target_date: userFilters?.filters?.target_date || undefined, + type: userFilters?.displayFilters?.type || undefined, + sub_issue: isNil(userFilters?.displayFilters?.sub_issue) ? true : userFilters?.displayFilters?.sub_issue, + show_empty_groups: isNil(userFilters?.displayFilters?.show_empty_groups) + ? true + : userFilters?.displayFilters?.show_empty_groups, + start_target_date: isNil(userFilters?.displayFilters?.start_target_date) + ? true + : userFilters?.displayFilters?.start_target_date, + }; + + const filteredParams = handleIssueQueryParamsByLayout(userFilters?.displayFilters?.layout, "issues"); + if (filteredParams) filteredRouteParams = this.computedFilter(filteredRouteParams, filteredParams); + + return filteredRouteParams; + } + + fetchFilters = async (workspaceSlug: string, projectId: string) => { + try { + await this.rootStore.issuesFilter.fetchDisplayFilters(workspaceSlug, projectId); + await this.rootStore.issuesFilter.fetchDisplayProperties(workspaceSlug, projectId); + return; + } catch (error) { + throw Error; + } + }; + + updateFilters = async ( + workspaceSlug: string, + projectId: string, + filterType: EFilterType, + filters: IIssueFilterOptions | IIssueDisplayFilterOptions | IIssueDisplayProperties + ) => { + try { + switch (filterType) { + case EFilterType.FILTERS: + await this.rootStore.issuesFilter.updateDisplayFilters( + workspaceSlug, + projectId, + filterType, + filters as IIssueFilterOptions + ); + break; + case EFilterType.DISPLAY_FILTERS: + await this.rootStore.issuesFilter.updateDisplayFilters( + workspaceSlug, + projectId, + filterType, + filters as IIssueDisplayFilterOptions + ); + break; + case EFilterType.DISPLAY_PROPERTIES: + await this.rootStore.issuesFilter.updateDisplayProperties( + workspaceSlug, + projectId, + filters as IIssueDisplayProperties + ); + break; + } + + return; + } catch (error) { + throw error; + } + }; +} diff --git a/web/store/issue/archived/index.ts b/web/store/issue/archived/index.ts new file mode 100644 index 000000000..0fe6c946b --- /dev/null +++ b/web/store/issue/archived/index.ts @@ -0,0 +1,2 @@ +export * from "./filter.store"; +export * from "./issue.store"; diff --git a/web/store/issue/archived/issue.store.ts b/web/store/issue/archived/issue.store.ts new file mode 100644 index 000000000..85edd9eb6 --- /dev/null +++ b/web/store/issue/archived/issue.store.ts @@ -0,0 +1,5 @@ +export interface IArchivedIssues {} + +export class ArchivedIssues implements IArchivedIssues { + constructor() {} +} diff --git a/web/store/issue/base-issue-filter.store.ts b/web/store/issue/base-issue-filter.store.ts new file mode 100644 index 000000000..248faa12a --- /dev/null +++ b/web/store/issue/base-issue-filter.store.ts @@ -0,0 +1,253 @@ +import { action, makeObservable, observable, runInAction } from "mobx"; +// types +import { IIssueRootStore } from "./root.store"; +import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions } from "types"; +// constants +import { EFilterType } from "constants/issue"; +// services +import { IssueService } from "services/issue"; +import { ProjectMemberService, ProjectService } from "services/project"; + +interface IProjectIssuesFiltersOptions { + filters: IIssueFilterOptions; + displayFilters: IIssueDisplayFilterOptions; +} + +interface IProjectIssuesDisplayOptions { + filters: IIssueFilterOptions; + displayFilters: IIssueDisplayFilterOptions; + displayProperties: IIssueDisplayProperties; +} + +export interface IIssuesFilter { + // observables + projectIssueFilters: { [projectId: string]: IProjectIssuesDisplayOptions } | undefined; + // computed + // helpers + issueDisplayFilters: (projectId: string) => IProjectIssuesDisplayOptions | undefined; + // actions + fetchDisplayFilters: (workspaceSlug: string, projectId: string) => Promise; + updateDisplayFilters: ( + workspaceSlug: string, + projectId: string, + type: EFilterType, + filters: IIssueFilterOptions | IIssueDisplayFilterOptions + ) => Promise; + fetchDisplayProperties: (workspaceSlug: string, projectId: string) => Promise; + updateDisplayProperties: ( + workspaceSlug: string, + projectId: string, + properties: IIssueDisplayProperties + ) => Promise; +} + +export class IssuesFilter implements IIssuesFilter { + // observables + projectIssueFilters: { [projectId: string]: IProjectIssuesDisplayOptions } | undefined = undefined; + // root store + rootStore; + // services + projectMemberService; + projectService; + issueService; + + constructor(_rootStore: IIssueRootStore) { + makeObservable(this, { + // observables + projectIssueFilters: observable.ref, + // computed + // actions + fetchDisplayFilters: action, + updateDisplayFilters: action, + fetchDisplayProperties: action, + updateDisplayProperties: action, + }); + // root store + this.rootStore = _rootStore; + // services + this.projectMemberService = new ProjectMemberService(); + this.projectService = new ProjectService(); + this.issueService = new IssueService(); + } + + // computed + + // helpers + issueDisplayFilters = (projectId: string) => { + if (!projectId) return undefined; + return this.projectIssueFilters?.[projectId] || undefined; + }; + + // actions + fetchDisplayFilters = async (workspaceSlug: string, projectId: string) => { + try { + const _filters = await this.projectMemberService.projectMemberMe(workspaceSlug, projectId); + + const filters: IIssueFilterOptions = { + assignees: _filters?.view_props?.filters?.assignees || null, + mentions: _filters?.view_props?.filters?.mentions || null, + created_by: _filters?.view_props?.filters?.created_by || null, + labels: _filters?.view_props?.filters?.labels || null, + priority: _filters?.view_props?.filters?.priority || null, + project: _filters?.view_props?.filters?.project || null, + start_date: _filters?.view_props?.filters?.start_date || null, + state: _filters?.view_props?.filters?.state || null, + state_group: _filters?.view_props?.filters?.state_group || null, + subscriber: _filters?.view_props?.filters?.subscriber || null, + target_date: _filters?.view_props?.filters?.target_date || null, + }; + + const displayFilters: IIssueDisplayFilterOptions = { + calendar: { + show_weekends: _filters?.view_props?.display_filters?.calendar?.show_weekends || false, + layout: _filters?.view_props?.display_filters?.calendar?.layout || "month", + }, + group_by: _filters?.view_props?.display_filters?.group_by || null, + sub_group_by: _filters?.view_props?.display_filters?.sub_group_by || null, + layout: _filters?.view_props?.display_filters?.layout || "list", + order_by: _filters?.view_props?.display_filters?.order_by || "-created_at", + show_empty_groups: _filters?.view_props?.display_filters?.show_empty_groups || false, + start_target_date: _filters?.view_props?.display_filters?.start_target_date || false, + sub_issue: _filters?.view_props?.display_filters?.sub_issue || false, + type: _filters?.view_props?.display_filters?.type || null, + }; + + const issueFilters: IProjectIssuesFiltersOptions = { + filters: filters, + displayFilters: displayFilters, + }; + + let _projectIssueFilters = this.projectIssueFilters; + if (!_projectIssueFilters) _projectIssueFilters = {}; + if (!_projectIssueFilters[projectId]) + _projectIssueFilters[projectId] = { filters: {}, displayFilters: {}, displayProperties: {} }; + _projectIssueFilters[projectId] = { + ..._projectIssueFilters[projectId], + ...issueFilters, + }; + + runInAction(() => { + this.projectIssueFilters = _projectIssueFilters; + }); + + return issueFilters; + } catch (error) { + throw error; + } + }; + + updateDisplayFilters = async ( + workspaceSlug: string, + projectId: string, + type: EFilterType, + filters: IIssueFilterOptions | IIssueDisplayFilterOptions + ) => { + try { + let _projectIssueFilters = { ...this.projectIssueFilters }; + if (!_projectIssueFilters) _projectIssueFilters = {}; + if (!_projectIssueFilters[projectId]) + _projectIssueFilters[projectId] = { filters: {}, displayFilters: {}, displayProperties: {} }; + + const _filters = { + filters: { ..._projectIssueFilters[projectId].filters }, + displayFilters: { ..._projectIssueFilters[projectId].displayFilters }, + }; + + if (type === EFilterType.FILTERS) _filters.filters = { ..._filters.filters, ...filters }; + else if (type === EFilterType.DISPLAY_FILTERS) + _filters.displayFilters = { ..._filters.displayFilters, ...filters }; + + // set sub_group_by to null if group_by is set to null + if (_filters.displayFilters.group_by === null) _filters.displayFilters.sub_group_by = null; + + // set sub_group_by to null if layout is switched to kanban group_by and sub_group_by are same + if ( + _filters.displayFilters.layout === "kanban" && + _filters.displayFilters.group_by === _filters.displayFilters.sub_group_by + ) + _filters.displayFilters.sub_group_by = null; + + // set group_by to state if layout is switched to kanban and group_by is null + if (_filters.displayFilters.layout === "kanban" && _filters.displayFilters.group_by === null) + _filters.displayFilters.group_by = "state"; + + _projectIssueFilters[projectId] = { + filters: _filters.filters, + displayFilters: _filters.displayFilters, + displayProperties: _projectIssueFilters[projectId].displayProperties, + }; + + runInAction(() => { + this.projectIssueFilters = _projectIssueFilters; + }); + + await this.projectService.setProjectView(workspaceSlug, projectId, { + view_props: { filters: _filters.filters, display_filters: _filters.displayFilters }, + }); + + return _filters; + } catch (error) { + this.fetchDisplayFilters(workspaceSlug, projectId); + throw error; + } + }; + + fetchDisplayProperties = async (workspaceSlug: string, projectId: string) => { + try { + const _issueDisplayProperties = await this.issueService.getIssueDisplayProperties(workspaceSlug, projectId); + + const displayProperties: IIssueDisplayProperties = { + assignee: _issueDisplayProperties?.properties?.assignee || false, + start_date: _issueDisplayProperties?.properties?.start_date || false, + due_date: _issueDisplayProperties?.properties?.due_date || false, + labels: _issueDisplayProperties?.properties?.labels || false, + key: _issueDisplayProperties?.properties?.key || false, + priority: _issueDisplayProperties?.properties?.priority || false, + state: _issueDisplayProperties?.properties?.state || false, + sub_issue_count: _issueDisplayProperties?.properties?.sub_issue_count || false, + link: _issueDisplayProperties?.properties?.link || false, + attachment_count: _issueDisplayProperties?.properties?.attachment_count || false, + estimate: _issueDisplayProperties?.properties?.estimate || false, + created_on: _issueDisplayProperties?.properties?.created_on || false, + updated_on: _issueDisplayProperties?.properties?.updated_on || false, + }; + + let _projectIssueFilters = { ...this.projectIssueFilters }; + if (!_projectIssueFilters) _projectIssueFilters = {}; + if (!_projectIssueFilters[projectId]) + _projectIssueFilters[projectId] = { filters: {}, displayFilters: {}, displayProperties: {} }; + _projectIssueFilters[projectId] = { ..._projectIssueFilters[projectId], displayProperties: displayProperties }; + + runInAction(() => { + this.projectIssueFilters = _projectIssueFilters; + }); + + return displayProperties; + } catch (error) { + throw error; + } + }; + + updateDisplayProperties = async (workspaceSlug: string, projectId: string, properties: IIssueDisplayProperties) => { + try { + let _issueFilters = { ...this.projectIssueFilters }; + if (!_issueFilters) _issueFilters = {}; + if (!_issueFilters[projectId]) + _issueFilters[projectId] = { filters: {}, displayFilters: {}, displayProperties: {} }; + + const updatedDisplayProperties = { ..._issueFilters[projectId].displayProperties, ...properties }; + _issueFilters[projectId] = { ..._issueFilters[projectId], displayProperties: updatedDisplayProperties }; + + runInAction(() => { + this.projectIssueFilters = _issueFilters; + }); + + await this.issueService.updateIssueDisplayProperties(workspaceSlug, projectId, updatedDisplayProperties); + + return properties; + } catch (error) { + this.fetchDisplayProperties(workspaceSlug, projectId); + throw error; + } + }; +} diff --git a/web/store/issue/cycle/filter.store.ts b/web/store/issue/cycle/filter.store.ts new file mode 100644 index 000000000..a0aa18c48 --- /dev/null +++ b/web/store/issue/cycle/filter.store.ts @@ -0,0 +1,259 @@ +import { observable, action, computed, makeObservable, runInAction } from "mobx"; +// base class +import { IssueFilterHelperStore } from "../helpers/issue-filter-helper.store"; +// services +import { ProjectService, ProjectMemberService } from "services/project"; +import { IssueService } from "services/issue"; +import { CycleService } from "services/cycle.service"; +// helpers +import { handleIssueQueryParamsByLayout } from "helpers/issue.helper"; +// constants +import { isNil } from "constants/common"; +import { EFilterType } from "constants/issue"; +// types +import { IssueRootStore } from "../root.store"; +import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions, TIssueParams } from "types"; + +interface ICycleIssuesFilterOptions { + filters: IIssueFilterOptions; +} + +interface IProjectIssuesFilters { + filters: IIssueFilterOptions | undefined; + displayFilters: IIssueDisplayFilterOptions | undefined; + displayProperties: IIssueDisplayProperties | undefined; +} + +export interface ICycleIssuesFilter { + // observable + loader: boolean; + filters: { [cycleId: string]: ICycleIssuesFilterOptions } | undefined; + // computed + issueFilters: IProjectIssuesFilters | undefined; + appliedFilters: TIssueParams[] | undefined; + // actions + fetchCycleFilters: (workspaceSlug: string, projectId: string, cycleId: string) => Promise; + updateCycleFilters: ( + workspaceSlug: string, + projectId: string, + cycleId: string, + type: EFilterType, + filters: IIssueFilterOptions + ) => Promise; + + fetchFilters: (workspaceSlug: string, projectId: string, cycleId: string) => Promise; + updateFilters: ( + workspaceSlug: string, + projectId: string, + filterType: EFilterType, + filters: IIssueFilterOptions | IIssueDisplayFilterOptions | IIssueDisplayProperties, + cycleId?: string | undefined + ) => Promise; +} + +export class CycleIssuesFilter extends IssueFilterHelperStore implements ICycleIssuesFilter { + // observables + loader: boolean = false; + filters: { [projectId: string]: ICycleIssuesFilterOptions } | undefined = undefined; + // root store + rootStore; + // services + projectService; + projectMemberService; + issueService; + cycleService; + + constructor(_rootStore: IssueRootStore) { + super(_rootStore); + + makeObservable(this, { + // observables + loader: observable.ref, + filters: observable.ref, + // computed + issueFilters: computed, + appliedFilters: computed, + // actions + fetchCycleFilters: action, + updateCycleFilters: action, + fetchFilters: action, + updateFilters: action, + }); + + this.rootStore = _rootStore; + + this.projectService = new ProjectService(); + this.projectMemberService = new ProjectMemberService(); + this.issueService = new IssueService(); + this.cycleService = new CycleService(); + } + + get issueFilters() { + const projectId = this.rootStore.projectId; + const cycleId = this.rootStore.cycleId; + if (!projectId || !cycleId) return undefined; + + const displayFilters = this.rootStore.issuesFilter.issueDisplayFilters(projectId); + const cycleFilters = this.filters?.[cycleId]; + + const _filters: IProjectIssuesFilters = { + filters: cycleFilters?.filters, + displayFilters: displayFilters?.displayFilters, + displayProperties: displayFilters?.displayProperties, + }; + + return _filters; + } + + get appliedFilters() { + const userFilters = this.issueFilters; + if (!userFilters) return undefined; + + let filteredRouteParams: any = { + priority: userFilters?.filters?.priority || undefined, + state_group: userFilters?.filters?.state_group || undefined, + state: userFilters?.filters?.state || undefined, + assignees: userFilters?.filters?.assignees || undefined, + mentions: userFilters?.filters?.mentions || undefined, + created_by: userFilters?.filters?.created_by || undefined, + labels: userFilters?.filters?.labels || undefined, + start_date: userFilters?.filters?.start_date || undefined, + target_date: userFilters?.filters?.target_date || undefined, + type: userFilters?.displayFilters?.type || undefined, + sub_issue: isNil(userFilters?.displayFilters?.sub_issue) ? true : userFilters?.displayFilters?.sub_issue, + show_empty_groups: isNil(userFilters?.displayFilters?.show_empty_groups) + ? true + : userFilters?.displayFilters?.show_empty_groups, + start_target_date: isNil(userFilters?.displayFilters?.start_target_date) + ? true + : userFilters?.displayFilters?.start_target_date, + }; + + const filteredParams = handleIssueQueryParamsByLayout(userFilters?.displayFilters?.layout, "issues"); + if (filteredParams) filteredRouteParams = this.computedFilter(filteredRouteParams, filteredParams); + + if (userFilters?.displayFilters?.layout === "calendar") filteredRouteParams.group_by = "target_date"; + if (userFilters?.displayFilters?.layout === "gantt_chart") filteredRouteParams.start_target_date = true; + + return filteredRouteParams; + } + + fetchCycleFilters = async (workspaceSlug: string, projectId: string, cycleId: string) => { + try { + const cycleFilters = await this.cycleService.getCycleDetails(workspaceSlug, projectId, cycleId); + + const filters: IIssueFilterOptions = { + assignees: cycleFilters?.view_props?.filters?.assignees || null, + mentions: cycleFilters?.view_props?.filters?.mentions || null, + created_by: cycleFilters?.view_props?.filters?.created_by || null, + labels: cycleFilters?.view_props?.filters?.labels || null, + priority: cycleFilters?.view_props?.filters?.priority || null, + project: cycleFilters?.view_props?.filters?.project || null, + start_date: cycleFilters?.view_props?.filters?.start_date || null, + state: cycleFilters?.view_props?.filters?.state || null, + state_group: cycleFilters?.view_props?.filters?.state_group || null, + subscriber: cycleFilters?.view_props?.filters?.subscriber || null, + target_date: cycleFilters?.view_props?.filters?.target_date || null, + }; + + const issueFilters: ICycleIssuesFilterOptions = { + filters: filters, + }; + + let _filters = { ...this.filters }; + if (!_filters) _filters = {}; + if (!_filters[cycleId]) _filters[cycleId] = { filters: {} }; + _filters[cycleId] = issueFilters; + + runInAction(() => { + this.filters = _filters; + }); + + return filters; + } catch (error) { + this.fetchFilters(workspaceSlug, projectId, cycleId); + throw error; + } + }; + + updateCycleFilters = async ( + workspaceSlug: string, + projectId: string, + cycleId: string, + type: EFilterType, + filters: IIssueFilterOptions + ) => { + try { + let _cycleIssueFilters = { ...this.filters }; + if (!_cycleIssueFilters) _cycleIssueFilters = {}; + if (!_cycleIssueFilters[cycleId]) _cycleIssueFilters[cycleId] = { filters: {} }; + + const _filters = { filters: { ..._cycleIssueFilters[cycleId].filters } }; + + if (type === EFilterType.FILTERS) _filters.filters = { ..._filters.filters, ...filters }; + + _cycleIssueFilters[cycleId] = { filters: _filters.filters }; + + runInAction(() => { + this.filters = _cycleIssueFilters; + }); + + await this.cycleService.patchCycle(workspaceSlug, projectId, cycleId, { + view_props: { filters: _filters.filters }, + }); + + return _filters; + } catch (error) { + this.fetchFilters(workspaceSlug, projectId, cycleId); + throw error; + } + }; + + fetchFilters = async (workspaceSlug: string, projectId: string, cycleId: string) => { + try { + await this.rootStore.issuesFilter.fetchDisplayFilters(workspaceSlug, projectId); + await this.rootStore.issuesFilter.fetchDisplayProperties(workspaceSlug, projectId); + await this.fetchCycleFilters(workspaceSlug, projectId, cycleId); + return; + } catch (error) { + this.fetchFilters(workspaceSlug, projectId, cycleId); + throw error; + } + }; + + updateFilters = async ( + workspaceSlug: string, + projectId: string, + filterType: EFilterType, + filters: IIssueFilterOptions | IIssueDisplayFilterOptions | IIssueDisplayProperties, + cycleId?: string | undefined + ) => { + try { + if (!cycleId) throw new Error(); + switch (filterType) { + case EFilterType.FILTERS: + await this.updateCycleFilters(workspaceSlug, projectId, cycleId, filterType, filters as IIssueFilterOptions); + break; + case EFilterType.DISPLAY_FILTERS: + await this.rootStore.issuesFilter.updateDisplayFilters( + workspaceSlug, + projectId, + filterType, + filters as IIssueDisplayFilterOptions + ); + break; + case EFilterType.DISPLAY_PROPERTIES: + await this.rootStore.issuesFilter.updateDisplayProperties( + workspaceSlug, + projectId, + filters as IIssueDisplayProperties + ); + break; + } + + return; + } catch (error) { + throw error; + } + }; +} diff --git a/web/store/issue/cycle/index.ts b/web/store/issue/cycle/index.ts new file mode 100644 index 000000000..0fe6c946b --- /dev/null +++ b/web/store/issue/cycle/index.ts @@ -0,0 +1,2 @@ +export * from "./filter.store"; +export * from "./issue.store"; diff --git a/web/store/issue/cycle/issue.store.ts b/web/store/issue/cycle/issue.store.ts new file mode 100644 index 000000000..86ccb8d4c --- /dev/null +++ b/web/store/issue/cycle/issue.store.ts @@ -0,0 +1,397 @@ +import { action, observable, makeObservable, computed, runInAction, autorun } from "mobx"; +// base class +import { IssueHelperStore } from "../helpers/issue-helper.store"; +// store +import { IIssueRootStore } from "../root.store"; +// services +import { IssueService } from "services/issue"; +import { CycleService } from "services/cycle.service"; +// types +import { + IGroupedIssues, + IIssue, + IIssueResponse, + ISubGroupedIssues, + TIssueGroupByOptions, + TLoader, + TUnGroupedIssues, +} from "types"; +import { ViewFlags } from "store_legacy/issues/types"; + +export interface ICycleIssues { + // observable + loader: TLoader; + issues: { [cycle_id: string]: IIssueResponse } | undefined; + // computed + getIssues: IIssueResponse | undefined; + getIssuesIds: IGroupedIssues | ISubGroupedIssues | TUnGroupedIssues | undefined; + // actions + fetchIssues: ( + workspaceSlug: string, + projectId: string, + loadType: TLoader, + cycleId?: string | undefined + ) => Promise; + createIssue: ( + workspaceSlug: string, + projectId: string, + data: Partial, + cycleId?: string | undefined + ) => Promise; + updateIssue: ( + workspaceSlug: string, + projectId: string, + issueId: string, + data: Partial, + cycleId?: string | undefined + ) => Promise; + removeIssue: ( + workspaceSlug: string, + projectId: string, + issueId: string, + cycleId?: string | undefined + ) => Promise; + quickAddIssue: ( + workspaceSlug: string, + projectId: string, + data: IIssue, + cycleId?: string | undefined + ) => Promise; + addIssueToCycle: ( + workspaceSlug: string, + cycleId: string, + issueIds: string[], + fetchAfterAddition?: boolean, + projectId?: string + ) => Promise; + removeIssueFromCycle: ( + workspaceSlug: string, + projectId: string, + cycleId: string, + issueId: string, + issueBridgeId: string + ) => Promise; + transferIssuesFromCycle: ( + workspaceSlug: string, + projectId: string, + cycleId: string, + payload: { + new_cycle_id: string; + } + ) => Promise; + viewFlags: ViewFlags; +} + +export class CycleIssues extends IssueHelperStore implements ICycleIssues { + loader: TLoader = "init-loader"; + issues: { [cycle_id: string]: IIssueResponse } | undefined = undefined; + // root store + rootStore; + // service + cycleService; + issueService; + + //viewData + viewFlags = { + enableQuickAdd: true, + enableIssueCreation: true, + enableInlineEditing: true, + }; + + constructor(_rootStore: IIssueRootStore) { + super(_rootStore); + + makeObservable(this, { + // observable + loader: observable.ref, + issues: observable.ref, + // computed + getIssues: computed, + getIssuesIds: computed, + // action + fetchIssues: action, + createIssue: action, + updateIssue: action, + removeIssue: action, + quickAddIssue: action, + addIssueToCycle: action, + removeIssueFromCycle: action, + transferIssuesFromCycle: action, + }); + + this.rootStore = _rootStore; + this.issueService = new IssueService(); + this.cycleService = new CycleService(); + + autorun(() => { + const workspaceSlug = this.rootStore.workspaceSlug; + const projectId = this.rootStore.projectId; + const cycleId = this.rootStore.cycleId; + if (!workspaceSlug || !projectId || !cycleId) return; + + const userFilters = this.rootStore?.cycleIssuesFilter?.issueFilters?.filters; + if (userFilters) this.fetchIssues(workspaceSlug, projectId, "mutation", cycleId); + }); + } + + get getIssues() { + const cycleId = this.rootStore?.cycleId; + if (!cycleId || !this.issues || !this.issues[cycleId]) return undefined; + + return this.issues[cycleId]; + } + + get getIssuesIds() { + const cycleId = this.rootStore?.cycleId; + const displayFilters = this.rootStore?.cycleIssuesFilter?.issueFilters?.displayFilters; + + const subGroupBy = displayFilters?.sub_group_by; + const groupBy = displayFilters?.group_by; + const orderBy = displayFilters?.order_by; + const layout = displayFilters?.layout; + + if (!cycleId || !this.issues || !this.issues[cycleId]) return undefined; + + let issues: IIssueResponse | IGroupedIssues | ISubGroupedIssues | TUnGroupedIssues | undefined = undefined; + + if (layout === "list" && orderBy) { + if (groupBy) issues = this.groupedIssues(groupBy, orderBy, this.issues[cycleId]); + else issues = this.unGroupedIssues(orderBy, this.issues[cycleId]); + } else if (layout === "kanban" && groupBy && orderBy) { + if (subGroupBy) issues = this.subGroupedIssues(subGroupBy, groupBy, orderBy, this.issues[cycleId]); + else issues = this.groupedIssues(groupBy, orderBy, this.issues[cycleId]); + } else if (layout === "calendar") + issues = this.groupedIssues("target_date" as TIssueGroupByOptions, "target_date", this.issues[cycleId], true); + else if (layout === "spreadsheet") issues = this.unGroupedIssues(orderBy ?? "-created_at", this.issues[cycleId]); + else if (layout === "gantt_chart") issues = this.unGroupedIssues(orderBy ?? "sort_order", this.issues[cycleId]); + + return issues; + } + + fetchIssues = async ( + workspaceSlug: string, + projectId: string, + loadType: TLoader = "init-loader", + cycleId: string | undefined = undefined + ) => { + if (!cycleId) return undefined; + + try { + this.loader = loadType; + + const params = this.rootStore?.cycleIssuesFilter?.appliedFilters; + const response = await this.cycleService.getCycleIssuesWithParams(workspaceSlug, projectId, cycleId, params); + + const _issues = { ...this.issues, [cycleId]: { ...response } }; + + runInAction(() => { + this.issues = _issues; + this.loader = undefined; + }); + + return response; + } catch (error) { + console.error(error); + this.loader = undefined; + throw error; + } + }; + + createIssue = async ( + workspaceSlug: string, + projectId: string, + data: Partial, + cycleId: string | undefined = undefined + ) => { + if (!cycleId) return undefined; + + try { + const response = await this.rootStore.projectIssues.createIssue(workspaceSlug, projectId, data); + const issueToCycle = await this.addIssueToCycle(workspaceSlug, cycleId, [response.id], false); + + let _issues = this.issues; + if (!_issues) _issues = {}; + if (!_issues[cycleId]) _issues[cycleId] = {}; + _issues[cycleId] = { ..._issues[cycleId], ...{ [response.id]: response } }; + + runInAction(() => { + this.issues = _issues; + }); + + return issueToCycle; + } catch (error) { + this.fetchIssues(workspaceSlug, projectId, "mutation", cycleId); + throw error; + } + }; + + updateIssue = async ( + workspaceSlug: string, + projectId: string, + issueId: string, + data: Partial, + cycleId: string | undefined = undefined + ) => { + if (!cycleId) return undefined; + + try { + let _issues = { ...this.issues }; + if (!_issues) _issues = {}; + if (!_issues[cycleId]) _issues[cycleId] = {}; + _issues[cycleId][issueId] = { ..._issues[cycleId][issueId], ...data }; + + runInAction(() => { + this.issues = _issues; + }); + + const response = await this.rootStore.projectIssues.updateIssue(workspaceSlug, projectId, issueId, data); + + return response; + } catch (error) { + this.fetchIssues(workspaceSlug, projectId, "mutation", cycleId); + throw error; + } + }; + + removeIssue = async ( + workspaceSlug: string, + projectId: string, + issueId: string, + cycleId: string | undefined = undefined + ) => { + if (!cycleId) return undefined; + try { + let _issues = { ...this.issues }; + if (!_issues) _issues = {}; + if (!_issues[cycleId]) _issues[cycleId] = {}; + delete _issues?.[cycleId]?.[issueId]; + _issues[cycleId] = { ..._issues[cycleId] }; + + runInAction(() => { + this.issues = _issues; + }); + + const response = await this.rootStore.projectIssues.removeIssue(workspaceSlug, projectId, issueId); + + return response; + } catch (error) { + this.fetchIssues(workspaceSlug, projectId, "mutation", cycleId); + throw error; + } + }; + + quickAddIssue = async ( + workspaceSlug: string, + projectId: string, + data: IIssue, + cycleId: string | undefined = undefined + ) => { + if (!cycleId) return; + try { + let _issues = { ...this.issues }; + if (!_issues) _issues = {}; + if (!_issues[cycleId]) _issues[cycleId] = {}; + _issues[cycleId] = { ..._issues[cycleId], ...{ [data.id as keyof IIssue]: data } }; + + runInAction(() => { + this.issues = _issues; + }); + + const response = await this.createIssue(workspaceSlug, projectId, data, cycleId); + + if (this.issues) { + delete this.issues[cycleId][data.id as keyof IIssue]; + + let _issues = { ...this.issues }; + if (!_issues) _issues = {}; + if (!_issues[cycleId]) _issues[cycleId] = {}; + _issues[cycleId] = { ..._issues[cycleId], ...{ [response.id as keyof IIssue]: response } }; + + runInAction(() => { + this.issues = _issues; + }); + } + + return response; + } catch (error) { + this.fetchIssues(workspaceSlug, projectId, "mutation", cycleId); + throw error; + } + }; + + addIssueToCycle = async ( + workspaceSlug: string, + cycleId: string, + issueIds: string[], + fetchAfterAddition = true, + projectId?: string + ) => { + const activeProjectId = this.rootStore.projectId; + if (!activeProjectId && !projectId) return; + + const projectIdToUpdate: string = projectId || activeProjectId || ""; + + try { + const issueToCycle = await this.issueService.addIssueToCycle(workspaceSlug, projectIdToUpdate, cycleId, { + issues: issueIds, + }); + + if (fetchAfterAddition) this.fetchIssues(workspaceSlug, projectIdToUpdate, "mutation", cycleId); + + return issueToCycle; + } catch (error) { + this.fetchIssues(workspaceSlug, projectIdToUpdate, "mutation", cycleId); + throw error; + } + }; + + removeIssueFromCycle = async ( + workspaceSlug: string, + projectId: string, + cycleId: string, + issueId: string, + issueBridgeId: string + ) => { + try { + let _issues = { ...this.issues }; + if (!_issues) _issues = {}; + if (!_issues[cycleId]) _issues[cycleId] = {}; + delete _issues?.[cycleId]?.[issueId]; + + runInAction(() => { + this.issues = _issues; + }); + + const response = await this.issueService.removeIssueFromCycle(workspaceSlug, projectId, cycleId, issueBridgeId); + + return response; + } catch (error) { + this.fetchIssues(workspaceSlug, projectId, "mutation", cycleId); + throw error; + } + }; + + transferIssuesFromCycle = async ( + workspaceSlug: string, + projectId: string, + cycleId: string, + payload: { + new_cycle_id: string; + } + ) => { + try { + const response = await this.cycleService.transferIssues( + workspaceSlug as string, + projectId as string, + cycleId as string, + payload + ); + await this.fetchIssues(workspaceSlug, projectId, "mutation", cycleId); + + return response; + } catch (error) { + this.fetchIssues(workspaceSlug, projectId, "mutation", cycleId); + throw error; + } + }; +} diff --git a/web/store/issue/draft/filter.store.ts b/web/store/issue/draft/filter.store.ts new file mode 100644 index 000000000..2be8905fa --- /dev/null +++ b/web/store/issue/draft/filter.store.ts @@ -0,0 +1,142 @@ +import { computed, makeObservable } from "mobx"; +// base class +import { IssueFilterHelperStore } from "../helpers/issue-filter-helper.store"; +// helpers +import { handleIssueQueryParamsByLayout } from "helpers/issue.helper"; +// constants +import { isNil } from "constants/common"; +import { EFilterType } from "constants/issue"; +// types +import { IssueRootStore } from "../root.store"; +import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions, TIssueParams } from "types"; + +interface IProjectIssuesFilters { + filters: IIssueFilterOptions | undefined; + displayFilters: IIssueDisplayFilterOptions | undefined; + displayProperties: IIssueDisplayProperties | undefined; +} + +export interface IDraftIssuesFilter { + // computed + issueFilters: IProjectIssuesFilters | undefined; + appliedFilters: TIssueParams[] | undefined; + // action + fetchFilters: (workspaceSlug: string, projectId: string) => Promise; + updateFilters: ( + workspaceSlug: string, + projectId: string, + filterType: EFilterType, + filters: IIssueFilterOptions | IIssueDisplayFilterOptions | IIssueDisplayProperties + ) => Promise; +} + +export class DraftIssuesFilter extends IssueFilterHelperStore implements IDraftIssuesFilter { + // root store + rootStore; + + constructor(_rootStore: IssueRootStore) { + super(_rootStore); + + makeObservable(this, { + // computed + issueFilters: computed, + appliedFilters: computed, + }); + + // root store + this.rootStore = _rootStore; + } + + get issueFilters() { + const projectId = this.rootStore.projectId; + if (!projectId) return undefined; + const displayFilters = this.rootStore.issuesFilter.issueDisplayFilters(projectId); + + const _filters: IProjectIssuesFilters = { + filters: displayFilters?.filters, + displayFilters: displayFilters?.displayFilters, + displayProperties: displayFilters?.displayProperties, + }; + + return _filters; + } + + get appliedFilters() { + const userFilters = this.issueFilters; + if (!userFilters) return undefined; + + let filteredRouteParams: any = { + priority: userFilters?.filters?.priority || undefined, + state_group: userFilters?.filters?.state_group || undefined, + state: userFilters?.filters?.state || undefined, + assignees: userFilters?.filters?.assignees || undefined, + mentions: userFilters?.filters?.mentions || undefined, + created_by: userFilters?.filters?.created_by || undefined, + labels: userFilters?.filters?.labels || undefined, + start_date: userFilters?.filters?.start_date || undefined, + target_date: userFilters?.filters?.target_date || undefined, + type: userFilters?.displayFilters?.type || undefined, + sub_issue: isNil(userFilters?.displayFilters?.sub_issue) ? true : userFilters?.displayFilters?.sub_issue, + show_empty_groups: isNil(userFilters?.displayFilters?.show_empty_groups) + ? true + : userFilters?.displayFilters?.show_empty_groups, + start_target_date: isNil(userFilters?.displayFilters?.start_target_date) + ? true + : userFilters?.displayFilters?.start_target_date, + }; + + const filteredParams = handleIssueQueryParamsByLayout(userFilters?.displayFilters?.layout, "issues"); + if (filteredParams) filteredRouteParams = this.computedFilter(filteredRouteParams, filteredParams); + + return filteredRouteParams; + } + + fetchFilters = async (workspaceSlug: string, projectId: string) => { + try { + await this.rootStore.issuesFilter.fetchDisplayFilters(workspaceSlug, projectId); + await this.rootStore.issuesFilter.fetchDisplayProperties(workspaceSlug, projectId); + return; + } catch (error) { + throw Error; + } + }; + + updateFilters = async ( + workspaceSlug: string, + projectId: string, + filterType: EFilterType, + filters: IIssueFilterOptions | IIssueDisplayFilterOptions | IIssueDisplayProperties + ) => { + try { + switch (filterType) { + case EFilterType.FILTERS: + await this.rootStore.issuesFilter.updateDisplayFilters( + workspaceSlug, + projectId, + filterType, + filters as IIssueFilterOptions + ); + break; + case EFilterType.DISPLAY_FILTERS: + await this.rootStore.issuesFilter.updateDisplayFilters( + workspaceSlug, + projectId, + filterType, + filters as IIssueDisplayFilterOptions + ); + break; + case EFilterType.DISPLAY_PROPERTIES: + await this.rootStore.issuesFilter.updateDisplayProperties( + workspaceSlug, + projectId, + filters as IIssueDisplayProperties + ); + break; + } + + return; + } catch (error) { + throw error; + } + }; +} diff --git a/web/store/issue/draft/index.ts b/web/store/issue/draft/index.ts new file mode 100644 index 000000000..0fe6c946b --- /dev/null +++ b/web/store/issue/draft/index.ts @@ -0,0 +1,2 @@ +export * from "./filter.store"; +export * from "./issue.store"; diff --git a/web/store/issue/draft/issue.store.ts b/web/store/issue/draft/issue.store.ts new file mode 100644 index 000000000..5db985fcb --- /dev/null +++ b/web/store/issue/draft/issue.store.ts @@ -0,0 +1,5 @@ +export interface IDraftIssues {} + +export class DraftIssues implements IDraftIssues { + constructor() {} +} diff --git a/web/store/issue/helpers/issue-filter-helper.store.ts b/web/store/issue/helpers/issue-filter-helper.store.ts new file mode 100644 index 000000000..0467de672 --- /dev/null +++ b/web/store/issue/helpers/issue-filter-helper.store.ts @@ -0,0 +1,29 @@ +// types +import { IIssueRootStore } from "../root.store"; + +export interface IIssueFilterHelperStore { + // helper methods + computedFilter(filters: any, filteredParams: any): any; +} + +export class IssueFilterHelperStore implements IIssueFilterHelperStore { + // root store + rootStore; + + constructor(_rootStore: IIssueRootStore) { + // root store + this.rootStore = _rootStore; + } + + // helper methods + computedFilter = (filters: any, filteredParams: any) => { + const computedFilters: any = {}; + Object.keys(filters).map((key) => { + if (filters[key] != undefined && filteredParams.includes(key)) + computedFilters[key] = + typeof filters[key] === "string" || typeof filters[key] === "boolean" ? filters[key] : filters[key].join(","); + }); + + return computedFilters; + }; +} diff --git a/web/store/issue/helpers/issue-helper.store.ts b/web/store/issue/helpers/issue-helper.store.ts new file mode 100644 index 000000000..ee0837995 --- /dev/null +++ b/web/store/issue/helpers/issue-helper.store.ts @@ -0,0 +1,215 @@ +import sortBy from "lodash/sortBy"; +import get from "lodash/get"; +import indexOf from "lodash/indexOf"; +import reverse from "lodash/reverse"; +import values from "lodash/values"; +// types +import { IIssue, IIssueResponse, TIssueGroupByOptions, TIssueOrderByOptions } from "types"; +import { IIssueRootStore } from "../root.store"; + +// constants +import { ISSUE_PRIORITIES, ISSUE_STATE_GROUPS } from "constants/issue"; +// helpers +import { renderDateFormat } from "helpers/date-time.helper"; + +export interface IIssueHelperStore { + groupedIssues( + groupBy: TIssueGroupByOptions, + orderBy: TIssueOrderByOptions, + issues: IIssueResponse, + isCalendarIssues?: boolean + ): { [group_id: string]: string[] }; + subGroupedIssues( + subGroupBy: TIssueGroupByOptions, + groupBy: TIssueGroupByOptions, + orderBy: TIssueOrderByOptions, + issues: IIssueResponse + ): { [sub_group_id: string]: { [group_id: string]: string[] } }; + unGroupedIssues(orderBy: TIssueOrderByOptions, issues: IIssueResponse): string[]; + issueDisplayFiltersDefaultData(groupBy: string | null): string[]; + issuesSortWithOrderBy(issueObject: IIssueResponse, key: Partial): IIssue[]; + getGroupArray(value: string[] | string | null, isDate?: boolean): string[]; +} + +export class IssueHelperStore implements IIssueHelperStore { + // root store + rootStore; + + constructor(_rootStore: IIssueRootStore) { + this.rootStore = _rootStore; + } + + groupedIssues = ( + groupBy: TIssueGroupByOptions, + orderBy: TIssueOrderByOptions, + issues: IIssueResponse, + isCalendarIssues: boolean = false + ) => { + const _issues: { [group_id: string]: string[] } = {}; + + this.issueDisplayFiltersDefaultData(groupBy).forEach((group) => { + _issues[group] = []; + }); + + const projectIssues = this.issuesSortWithOrderBy(issues, orderBy); + + for (const issue in projectIssues) { + const _issue = projectIssues[issue]; + const groupArray = this.getGroupArray(get(_issue, groupBy as keyof IIssue), isCalendarIssues); + + for (const group of groupArray) { + if (group && _issues[group]) _issues[group].push(_issue.id); + else if (group) _issues[group] = [_issue.id]; + } + } + + return _issues; + }; + + subGroupedIssues = ( + subGroupBy: TIssueGroupByOptions, + groupBy: TIssueGroupByOptions, + orderBy: TIssueOrderByOptions, + issues: IIssueResponse + ) => { + const _issues: { [sub_group_id: string]: { [group_id: string]: string[] } } = {}; + + this.issueDisplayFiltersDefaultData(subGroupBy).forEach((sub_group: any) => { + const groupByIssues: { [group_id: string]: string[] } = {}; + this.issueDisplayFiltersDefaultData(groupBy).forEach((group) => { + groupByIssues[group] = []; + }); + _issues[sub_group] = groupByIssues; + }); + + const projectIssues = this.issuesSortWithOrderBy(issues, orderBy); + + for (const issue in projectIssues) { + const _issue = projectIssues[issue]; + const subGroupArray = this.getGroupArray(get(_issue, subGroupBy as keyof IIssue)); + const groupArray = this.getGroupArray(get(_issue, groupBy as keyof IIssue)); + + for (const subGroup of subGroupArray) { + for (const group of groupArray) { + if (subGroup && group && _issues?.[subGroup]?.[group]) _issues[subGroup][group].push(_issue.id); + else if (subGroup && group && _issues[subGroup]) _issues[subGroup][group] = [_issue.id]; + else if (subGroup && group) _issues[subGroup] = { [group]: [_issue.id] }; + } + } + } + + return _issues; + }; + + unGroupedIssues = (orderBy: TIssueOrderByOptions, issues: IIssueResponse) => + this.issuesSortWithOrderBy(issues, orderBy).map((issue) => issue.id); + + issueDisplayFiltersDefaultData = (groupBy: string | null): string[] => { + switch (groupBy) { + case "state": + return this.rootStore?.states || []; + case "state_detail.group": + return ISSUE_STATE_GROUPS.map((i) => i.key); + case "priority": + return ISSUE_PRIORITIES.map((i) => i.key); + case "labels": + return this.rootStore?.labels || []; + case "created_by": + return this.rootStore?.members || []; + case "assignees": + return this.rootStore?.members || []; + case "project": + return this.rootStore?.projects || []; + default: + return []; + } + }; + + issuesSortWithOrderBy = (issueObject: IIssueResponse, key: Partial): IIssue[] => { + let array = values(issueObject); + array = reverse(sortBy(array, "created_at")); + switch (key) { + case "sort_order": + return reverse(sortBy(array, "sort_order")); + + case "state__name": + return reverse(sortBy(array, "state")); + case "-state__name": + return sortBy(array, "state"); + + //dates + case "created_at": + return sortBy(array, "created_at"); + case "-created_at": + return reverse(sortBy(array, "created_at")); + + case "updated_at": + return sortBy(array, "updated_at"); + case "-updated_at": + return reverse(sortBy(array, "updated_at")); + + case "start_date": + return sortBy(array, "start_date"); + case "-start_date": + return reverse(sortBy(array, "start_date")); + + case "target_date": + return sortBy(array, "target_date"); + case "-target_date": + return reverse(sortBy(array, "target_date")); + + //custom + case "priority": { + const sortArray = ISSUE_PRIORITIES.map((i) => i.key); + return reverse(sortBy(array, (_issue: IIssue) => indexOf(sortArray, _issue.priority))); + } + case "-priority": { + const sortArray = ISSUE_PRIORITIES.map((i) => i.key); + return sortBy(array, (_issue: IIssue) => indexOf(sortArray, _issue.priority)); + } + + //number + case "attachment_count": + return sortBy(array, "attachment_count"); + case "-attachment_count": + return reverse(sortBy(array, "attachment_count")); + + case "estimate_point": + return sortBy(array, "estimate_point"); + case "-estimate_point": + return reverse(sortBy(array, "estimate_point")); + + case "link_count": + return sortBy(array, "link_count"); + case "-link_count": + return reverse(sortBy(array, "link_count")); + + case "sub_issues_count": + return sortBy(array, "sub_issues_count"); + case "-sub_issues_count": + return reverse(sortBy(array, "sub_issues_count")); + + //Array + case "labels__name": + return reverse(sortBy(array, "labels")); + case "-labels__name": + return sortBy(array, "labels"); + + case "assignees__first_name": + return reverse(sortBy(array, "assignees")); + case "-assignees__first_name": + return sortBy(array, "assignees"); + + default: + return array; + } + }; + + getGroupArray(value: string[] | string | null, isDate: boolean = false) { + if (Array.isArray(value)) { + if (value.length) return value; + else return ["None"]; + } else if (isDate) return [renderDateFormat(value) || "None"]; + else return [value || "None"]; + } +} diff --git a/web/store/issue/issue.store.ts b/web/store/issue/issue.store.ts new file mode 100644 index 000000000..d28eb5e47 --- /dev/null +++ b/web/store/issue/issue.store.ts @@ -0,0 +1,79 @@ +import get from "lodash/get"; +// store +import { action, makeObservable, observable, runInAction } from "mobx"; +import { IIssueRootStore } from "./root.store"; +// types +import { IIssue } from "types"; + +export interface IIssueStore { + allIssues: { [key: string]: IIssue }; + // actions + addIssue(issues: IIssue[]): void; + updateIssue(issueId: string, issue: Partial): void; + removeIssue(issueId: string): void; + // helper Methods + getIssueById(id: string): undefined | IIssue; + getIssuesByKey(issueKey: string, value: string): undefined | { [key: string]: IIssue }; +} + +export class IssueStore implements IIssueStore { + allIssues: { [key: string]: IIssue } = {}; + // root store + rootStore: IIssueRootStore; + + constructor(rootStore: IIssueRootStore) { + this.rootStore = rootStore; + + makeObservable(this, { + // observable + allIssues: observable.ref, + // actions + addIssue: action, + updateIssue: action, + removeIssue: action, + }); + } + + addIssue = (issues: IIssue[]) => { + if (issues && issues.length <= 0) return; + + const _issues = { ...this.allIssues }; + issues.forEach((issue) => { + _issues[issue.id] = issue; + }); + + runInAction(() => { + this.allIssues = _issues; + }); + }; + + updateIssue = (issueId: string, issue: Partial) => { + if (!issue || !issueId || !this.allIssues[issueId]) return; + this.allIssues[issueId] = { ...this.allIssues[issueId], ...issue }; + }; + + removeIssue = (issueId: string) => { + if (issueId) return; + delete this.allIssues[issueId]; + }; + + // helper methods + getIssueById = (id: string) => { + if (!id) return undefined; + return this.allIssues[id]; + }; + + getIssuesByKey = (issueKey: keyof IIssue, value: string) => { + if (!issueKey || !value || !this.allIssues) return undefined; + const filteredIssues: { [key: string]: IIssue } = {}; + + Object.values(this.allIssues).forEach((issue) => { + const issueKeyValue = get(issue, issueKey); + if (issueKeyValue == value) { + filteredIssues[issue.id] = issue; + } + }); + + return filteredIssues; + }; +} diff --git a/web/store/issue/module/filter.store.ts b/web/store/issue/module/filter.store.ts new file mode 100644 index 000000000..0cf3193ca --- /dev/null +++ b/web/store/issue/module/filter.store.ts @@ -0,0 +1,267 @@ +import { observable, action, computed, makeObservable, runInAction } from "mobx"; +// base class +import { IssueFilterHelperStore } from "../helpers/issue-filter-helper.store"; +// services +import { ProjectService, ProjectMemberService } from "services/project"; +import { IssueService } from "services/issue"; +import { ModuleService } from "services/module.service"; +// helpers +import { handleIssueQueryParamsByLayout } from "helpers/issue.helper"; +// constants +import { isNil } from "constants/common"; +import { EFilterType } from "constants/issue"; +// types +import { IssueRootStore } from "../root.store"; +import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions, TIssueParams } from "types"; + +interface IModuleIssuesFilterOptions { + filters: IIssueFilterOptions; +} + +interface IProjectIssuesFilters { + filters: IIssueFilterOptions | undefined; + displayFilters: IIssueDisplayFilterOptions | undefined; + displayProperties: IIssueDisplayProperties | undefined; +} + +export interface IModuleIssuesFilter { + // observable + loader: boolean; + filters: { [moduleId: string]: IModuleIssuesFilterOptions } | undefined; + // computed + issueFilters: IProjectIssuesFilters | undefined; + appliedFilters: TIssueParams[] | undefined; + // actions + fetchModuleFilters: (workspaceSlug: string, projectId: string, moduleId: string) => Promise; + updateModuleFilters: ( + workspaceSlug: string, + projectId: string, + moduleId: string, + type: EFilterType, + filters: IIssueFilterOptions + ) => Promise; + + fetchFilters: (workspaceSlug: string, projectId: string, moduleId: string) => Promise; + updateFilters: ( + workspaceSlug: string, + projectId: string, + filterType: EFilterType, + filters: IIssueFilterOptions | IIssueDisplayFilterOptions | IIssueDisplayProperties, + moduleId?: string | undefined + ) => Promise; +} + +export class ModuleIssuesFilter extends IssueFilterHelperStore implements IModuleIssuesFilter { + // observables + loader: boolean = false; + filters: { [projectId: string]: IModuleIssuesFilterOptions } | undefined = undefined; + // root store + rootStore; + // services + projectService; + projectMemberService; + issueService; + moduleService; + + constructor(_rootStore: IssueRootStore) { + super(_rootStore); + + makeObservable(this, { + // observables + loader: observable.ref, + filters: observable.ref, + // computed + issueFilters: computed, + appliedFilters: computed, + // actions + fetchModuleFilters: action, + updateModuleFilters: action, + fetchFilters: action, + updateFilters: action, + }); + + this.rootStore = _rootStore; + + this.projectService = new ProjectService(); + this.projectMemberService = new ProjectMemberService(); + this.issueService = new IssueService(); + this.moduleService = new ModuleService(); + } + + get issueFilters() { + const projectId = this.rootStore.projectId; + const moduleId = this.rootStore.moduleId; + if (!projectId || !moduleId) return undefined; + + const displayFilters = this.rootStore.issuesFilter.issueDisplayFilters(projectId); + const moduleFilters = this.filters?.[moduleId]; + + const _filters: IProjectIssuesFilters = { + filters: moduleFilters?.filters, + displayFilters: displayFilters?.displayFilters, + displayProperties: displayFilters?.displayProperties, + }; + + return _filters; + } + + get appliedFilters() { + const userFilters = this.issueFilters; + if (!userFilters) return undefined; + + let filteredRouteParams: any = { + priority: userFilters?.filters?.priority || undefined, + state_group: userFilters?.filters?.state_group || undefined, + state: userFilters?.filters?.state || undefined, + assignees: userFilters?.filters?.assignees || undefined, + mentions: userFilters?.filters?.mentions || undefined, + created_by: userFilters?.filters?.created_by || undefined, + labels: userFilters?.filters?.labels || undefined, + start_date: userFilters?.filters?.start_date || undefined, + target_date: userFilters?.filters?.target_date || undefined, + type: userFilters?.displayFilters?.type || undefined, + sub_issue: isNil(userFilters?.displayFilters?.sub_issue) ? true : userFilters?.displayFilters?.sub_issue, + show_empty_groups: isNil(userFilters?.displayFilters?.show_empty_groups) + ? true + : userFilters?.displayFilters?.show_empty_groups, + start_target_date: isNil(userFilters?.displayFilters?.start_target_date) + ? true + : userFilters?.displayFilters?.start_target_date, + }; + + const filteredParams = handleIssueQueryParamsByLayout(userFilters?.displayFilters?.layout, "issues"); + if (filteredParams) filteredRouteParams = this.computedFilter(filteredRouteParams, filteredParams); + + if (userFilters?.displayFilters?.layout === "calendar") filteredRouteParams.group_by = "target_date"; + if (userFilters?.displayFilters?.layout === "gantt_chart") filteredRouteParams.start_target_date = true; + + return filteredRouteParams; + } + + fetchModuleFilters = async (workspaceSlug: string, projectId: string, moduleId: string) => { + try { + const moduleFilters = await this.moduleService.getModuleDetails(workspaceSlug, projectId, moduleId); + + const filters: IIssueFilterOptions = { + assignees: moduleFilters?.view_props?.filters?.assignees || null, + mentions: moduleFilters?.view_props?.filters?.mentions || null, + created_by: moduleFilters?.view_props?.filters?.created_by || null, + labels: moduleFilters?.view_props?.filters?.labels || null, + priority: moduleFilters?.view_props?.filters?.priority || null, + project: moduleFilters?.view_props?.filters?.project || null, + start_date: moduleFilters?.view_props?.filters?.start_date || null, + state: moduleFilters?.view_props?.filters?.state || null, + state_group: moduleFilters?.view_props?.filters?.state_group || null, + subscriber: moduleFilters?.view_props?.filters?.subscriber || null, + target_date: moduleFilters?.view_props?.filters?.target_date || null, + }; + + const issueFilters: IModuleIssuesFilterOptions = { + filters: filters, + }; + + let _filters = { ...this.filters }; + if (!_filters) _filters = {}; + if (!_filters[moduleId]) _filters[moduleId] = { filters: {} }; + _filters[moduleId] = issueFilters; + + runInAction(() => { + this.filters = _filters; + }); + + return filters; + } catch (error) { + this.fetchFilters(workspaceSlug, projectId, moduleId); + throw error; + } + }; + + updateModuleFilters = async ( + workspaceSlug: string, + projectId: string, + moduleId: string, + type: EFilterType, + filters: IIssueFilterOptions + ) => { + if (!moduleId) return; + try { + let _moduleIssueFilters = { ...this.filters }; + if (!_moduleIssueFilters) _moduleIssueFilters = {}; + if (!_moduleIssueFilters[moduleId]) _moduleIssueFilters[moduleId] = { filters: {} }; + + const _filters = { filters: { ..._moduleIssueFilters[moduleId].filters } }; + + if (type === EFilterType.FILTERS) _filters.filters = { ..._filters.filters, ...filters }; + + _moduleIssueFilters[moduleId] = { filters: _filters.filters }; + + runInAction(() => { + this.filters = _moduleIssueFilters; + }); + + await this.moduleService.patchModule(workspaceSlug, projectId, moduleId, { + view_props: { filters: _filters.filters }, + }); + + return _filters; + } catch (error) { + this.fetchFilters(workspaceSlug, projectId, moduleId); + throw error; + } + }; + + fetchFilters = async (workspaceSlug: string, projectId: string, moduleId: string) => { + try { + await this.rootStore.issuesFilter.fetchDisplayFilters(workspaceSlug, projectId); + await this.rootStore.issuesFilter.fetchDisplayProperties(workspaceSlug, projectId); + await this.fetchModuleFilters(workspaceSlug, projectId, moduleId); + return; + } catch (error) { + this.fetchFilters(workspaceSlug, projectId, moduleId); + throw error; + } + }; + + updateFilters = async ( + workspaceSlug: string, + projectId: string, + filterType: EFilterType, + filters: IIssueFilterOptions | IIssueDisplayFilterOptions | IIssueDisplayProperties, + moduleId?: string | undefined + ) => { + try { + if (!moduleId) throw new Error(); + + switch (filterType) { + case EFilterType.FILTERS: + await this.updateModuleFilters( + workspaceSlug, + projectId, + moduleId, + filterType, + filters as IIssueFilterOptions + ); + break; + case EFilterType.DISPLAY_FILTERS: + await this.rootStore.issuesFilter.updateDisplayFilters( + workspaceSlug, + projectId, + filterType, + filters as IIssueDisplayFilterOptions + ); + break; + case EFilterType.DISPLAY_PROPERTIES: + await this.rootStore.issuesFilter.updateDisplayProperties( + workspaceSlug, + projectId, + filters as IIssueDisplayProperties + ); + break; + } + + return; + } catch (error) { + throw error; + } + }; +} diff --git a/web/store/issue/module/index.ts b/web/store/issue/module/index.ts new file mode 100644 index 000000000..0fe6c946b --- /dev/null +++ b/web/store/issue/module/index.ts @@ -0,0 +1,2 @@ +export * from "./filter.store"; +export * from "./issue.store"; diff --git a/web/store/issue/module/issue.store.ts b/web/store/issue/module/issue.store.ts new file mode 100644 index 000000000..d2728537f --- /dev/null +++ b/web/store/issue/module/issue.store.ts @@ -0,0 +1,5 @@ +export interface IModuleIssues {} + +export class ModuleIssues implements IModuleIssues { + constructor() {} +} diff --git a/web/store/issue/profile/filter.store.ts b/web/store/issue/profile/filter.store.ts new file mode 100644 index 000000000..f8cb7f88d --- /dev/null +++ b/web/store/issue/profile/filter.store.ts @@ -0,0 +1,344 @@ +import { action, makeObservable, observable, runInAction } from "mobx"; +import isEmpty from "lodash/isEmpty"; +// base class +import { IssueFilterHelperStore } from "../helpers/issue-filter-helper.store"; +// helpers +import { handleIssueQueryParamsByLayout } from "helpers/issue.helper"; +// constants +import { isNil } from "constants/common"; +import { EFilterType } from "constants/issue"; +// types +import { IssueRootStore } from "../root.store"; +import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions, TIssueParams } from "types"; + +interface IProjectIssuesFiltersOptions { + filters: IIssueFilterOptions; + displayFilters: IIssueDisplayFilterOptions; +} + +interface IProjectIssuesDisplayOptions { + filters: IIssueFilterOptions; + displayFilters: IIssueDisplayFilterOptions; + displayProperties: IIssueDisplayProperties; +} + +interface IProjectIssuesFilters { + filters: IIssueFilterOptions | undefined; + displayFilters: IIssueDisplayFilterOptions | undefined; + displayProperties: IIssueDisplayProperties | undefined; +} + +export interface IProfileIssuesFilter { + // observables + projectIssueFilters: { [workspaceId: string]: IProjectIssuesDisplayOptions } | undefined; + // computed + issueFilters: IProjectIssuesFilters | undefined; + appliedFilters: TIssueParams[] | undefined; + // helpers + issueDisplayFilters: (workspaceId: string) => IProjectIssuesDisplayOptions | undefined; + // actions + fetchDisplayFilters: (workspaceSlug: string) => Promise; + updateDisplayFilters: ( + workspaceSlug: string, + type: EFilterType, + filters: IIssueFilterOptions | IIssueDisplayFilterOptions + ) => Promise; + fetchDisplayProperties: (workspaceSlug: string) => Promise; + updateDisplayProperties: ( + workspaceSlug: string, + properties: IIssueDisplayProperties + ) => Promise; + fetchFilters: (workspaceSlug: string) => Promise; + updateFilters: ( + workspaceSlug: string, + filterType: EFilterType, + filters: IIssueFilterOptions | IIssueDisplayFilterOptions | IIssueDisplayProperties + ) => Promise; +} + +export class ProfileIssuesFilter extends IssueFilterHelperStore implements IProfileIssuesFilter { + // observables + projectIssueFilters: { [projectId: string]: IProjectIssuesDisplayOptions } | undefined = undefined; + // root store + rootStore; + + constructor(_rootStore: IssueRootStore) { + super(_rootStore); + + makeObservable(this, { + // observables + projectIssueFilters: observable.ref, + // computed + // actions + fetchDisplayFilters: action, + updateDisplayFilters: action, + fetchDisplayProperties: action, + updateDisplayProperties: action, + }); + // root store + this.rootStore = _rootStore; + } + + // computed + + // helpers + issueDisplayFilters = (workspaceId: string) => { + if (!workspaceId) return undefined; + return this.projectIssueFilters?.[workspaceId] || undefined; + }; + + // actions + fetchDisplayFilters = async (workspaceSlug: string) => { + try { + const filters: IIssueFilterOptions = { + assignees: null, + mentions: null, + created_by: null, + labels: null, + priority: null, + project: null, + start_date: null, + state: null, + state_group: null, + subscriber: null, + target_date: null, + }; + + const displayFilters: IIssueDisplayFilterOptions = { + calendar: { + show_weekends: false, + layout: "month", + }, + group_by: "state_detail.group", + sub_group_by: null, + layout: "list", + order_by: "-created_at", + show_empty_groups: false, + start_target_date: false, + sub_issue: false, + type: null, + }; + + const issueFilters: IProjectIssuesFiltersOptions = { + filters: filters, + displayFilters: displayFilters, + }; + + let _projectIssueFilters = this.projectIssueFilters; + if (!_projectIssueFilters) _projectIssueFilters = {}; + if (!_projectIssueFilters[workspaceSlug]) { + _projectIssueFilters[workspaceSlug] = { displayProperties: {} } as IProjectIssuesDisplayOptions; + } + + if ( + isEmpty(_projectIssueFilters[workspaceSlug].filters) || + isEmpty(_projectIssueFilters[workspaceSlug].displayFilters) + ) { + _projectIssueFilters[workspaceSlug] = { + ..._projectIssueFilters[workspaceSlug], + ...issueFilters, + }; + } + + runInAction(() => { + this.projectIssueFilters = _projectIssueFilters; + }); + + return issueFilters; + } catch (error) { + throw error; + } + }; + + updateDisplayFilters = async ( + workspaceSlug: string, + type: EFilterType, + filters: IIssueFilterOptions | IIssueDisplayFilterOptions + ) => { + try { + let _projectIssueFilters = { ...this.projectIssueFilters }; + if (!_projectIssueFilters) _projectIssueFilters = {}; + if (!_projectIssueFilters[workspaceSlug]) + _projectIssueFilters[workspaceSlug] = { filters: {}, displayFilters: {}, displayProperties: {} }; + + const _filters = { + filters: { ..._projectIssueFilters[workspaceSlug].filters }, + displayFilters: { ..._projectIssueFilters[workspaceSlug].displayFilters }, + }; + + if (type === EFilterType.FILTERS) _filters.filters = { ..._filters.filters, ...filters }; + else if (type === EFilterType.DISPLAY_FILTERS) + _filters.displayFilters = { ..._filters.displayFilters, ...filters }; + + // set sub_group_by to null if group_by is set to null + if (_filters.displayFilters.group_by === null) _filters.displayFilters.sub_group_by = null; + + // set sub_group_by to null if layout is switched to kanban group_by and sub_group_by are same + if ( + _filters.displayFilters.layout === "kanban" && + _filters.displayFilters.group_by === _filters.displayFilters.sub_group_by + ) + _filters.displayFilters.sub_group_by = null; + + // set group_by to state if layout is switched to kanban and group_by is null + if (_filters.displayFilters.layout === "kanban" && _filters.displayFilters.group_by === null) + _filters.displayFilters.group_by = "state"; + + _projectIssueFilters[workspaceSlug] = { + filters: _filters.filters, + displayFilters: _filters.displayFilters, + displayProperties: _projectIssueFilters[workspaceSlug].displayProperties, + }; + + runInAction(() => { + this.projectIssueFilters = _projectIssueFilters; + }); + + return _filters; + } catch (error) { + throw error; + } + }; + + fetchDisplayProperties = async (workspaceSlug: string) => { + try { + const displayProperties: IIssueDisplayProperties = { + assignee: true, + start_date: true, + due_date: true, + labels: true, + key: true, + priority: true, + state: false, + sub_issue_count: true, + link: true, + attachment_count: false, + estimate: false, + created_on: false, + updated_on: false, + }; + + let _projectIssueFilters = { ...this.projectIssueFilters }; + if (!_projectIssueFilters) _projectIssueFilters = {}; + if (!_projectIssueFilters[workspaceSlug]) { + _projectIssueFilters[workspaceSlug] = { filters: {}, displayFilters: {} } as IProjectIssuesDisplayOptions; + } + if (isEmpty(_projectIssueFilters[workspaceSlug].displayProperties)) { + _projectIssueFilters[workspaceSlug] = { + ..._projectIssueFilters[workspaceSlug], + displayProperties: displayProperties, + }; + } + + runInAction(() => { + this.projectIssueFilters = _projectIssueFilters; + }); + + return displayProperties; + } catch (error) { + throw error; + } + }; + + updateDisplayProperties = async (workspaceSlug: string, properties: IIssueDisplayProperties) => { + try { + let _issueFilters = { ...this.projectIssueFilters }; + if (!_issueFilters) _issueFilters = {}; + if (!_issueFilters[workspaceSlug]) + _issueFilters[workspaceSlug] = { filters: {}, displayFilters: {}, displayProperties: {} }; + + const updatedDisplayProperties = { ..._issueFilters[workspaceSlug].displayProperties, ...properties }; + _issueFilters[workspaceSlug] = { ..._issueFilters[workspaceSlug], displayProperties: updatedDisplayProperties }; + + runInAction(() => { + this.projectIssueFilters = _issueFilters; + }); + + return properties; + } catch (error) { + throw error; + } + }; + + get issueFilters() { + const workspaceSlug = this.rootStore.workspaceSlug; + if (!workspaceSlug) return undefined; + const displayFilters = this.issueDisplayFilters(workspaceSlug); + + const _filters: IProjectIssuesFilters = { + filters: displayFilters?.filters, + displayFilters: displayFilters?.displayFilters, + displayProperties: displayFilters?.displayProperties, + }; + + return _filters; + } + + get appliedFilters() { + const userFilters = this.issueFilters; + if (!userFilters) return undefined; + + let filteredRouteParams: any = { + priority: userFilters?.filters?.priority || undefined, + state_group: userFilters?.filters?.state_group || undefined, + state: userFilters?.filters?.state || undefined, + assignees: userFilters?.filters?.assignees || undefined, + mentions: userFilters?.filters?.mentions || undefined, + created_by: userFilters?.filters?.created_by || undefined, + labels: userFilters?.filters?.labels || undefined, + start_date: userFilters?.filters?.start_date || undefined, + target_date: userFilters?.filters?.target_date || undefined, + type: userFilters?.displayFilters?.type || undefined, + sub_issue: isNil(userFilters?.displayFilters?.sub_issue) ? true : userFilters?.displayFilters?.sub_issue, + show_empty_groups: isNil(userFilters?.displayFilters?.show_empty_groups) + ? true + : userFilters?.displayFilters?.show_empty_groups, + start_target_date: isNil(userFilters?.displayFilters?.start_target_date) + ? true + : userFilters?.displayFilters?.start_target_date, + }; + + const filteredParams = handleIssueQueryParamsByLayout(userFilters?.displayFilters?.layout, "profile_issues"); + if (filteredParams) filteredRouteParams = this.computedFilter(filteredRouteParams, filteredParams); + + if (userFilters?.displayFilters?.layout === "calendar") filteredRouteParams.group_by = "target_date"; + if (userFilters?.displayFilters?.layout === "gantt_chart") filteredRouteParams.start_target_date = true; + + return filteredRouteParams; + } + + fetchFilters = async (workspaceSlug: string) => { + try { + await this.fetchDisplayFilters(workspaceSlug); + await this.fetchDisplayProperties(workspaceSlug); + return; + } catch (error) { + throw Error; + } + }; + + updateFilters = async ( + workspaceSlug: string, + + filterType: EFilterType, + filters: IIssueFilterOptions | IIssueDisplayFilterOptions | IIssueDisplayProperties + ) => { + try { + switch (filterType) { + case EFilterType.FILTERS: + await this.updateDisplayFilters(workspaceSlug, filterType, filters as IIssueFilterOptions); + break; + case EFilterType.DISPLAY_FILTERS: + await this.updateDisplayFilters(workspaceSlug, filterType, filters as IIssueDisplayFilterOptions); + break; + case EFilterType.DISPLAY_PROPERTIES: + await this.updateDisplayProperties(workspaceSlug, filters as IIssueDisplayProperties); + break; + } + + return; + } catch (error) { + throw error; + } + }; +} diff --git a/web/store/issue/profile/index.ts b/web/store/issue/profile/index.ts new file mode 100644 index 000000000..0fe6c946b --- /dev/null +++ b/web/store/issue/profile/index.ts @@ -0,0 +1,2 @@ +export * from "./filter.store"; +export * from "./issue.store"; diff --git a/web/store/issue/profile/issue.store.ts b/web/store/issue/profile/issue.store.ts new file mode 100644 index 000000000..4c5310ae4 --- /dev/null +++ b/web/store/issue/profile/issue.store.ts @@ -0,0 +1,5 @@ +export interface IProfileIssues {} + +export class ProfileIssues implements IProfileIssues { + constructor() {} +} diff --git a/web/store/issue/project-views/filter.store.ts b/web/store/issue/project-views/filter.store.ts new file mode 100644 index 000000000..0f94f2146 --- /dev/null +++ b/web/store/issue/project-views/filter.store.ts @@ -0,0 +1,261 @@ +import { observable, action, computed, makeObservable, runInAction } from "mobx"; +// base class +import { IssueFilterHelperStore } from "../helpers/issue-filter-helper.store"; +// services +import { ProjectService, ProjectMemberService } from "services/project"; +import { IssueService } from "services/issue"; +import { ViewService } from "services/view.service"; +// helpers +import { handleIssueQueryParamsByLayout } from "helpers/issue.helper"; +// constants +import { isNil } from "constants/common"; +import { EFilterType } from "constants/issue"; +// types +import { IssueRootStore } from "../root.store"; +import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions, TIssueParams } from "types"; + +interface IViewIssuesFilterOptions { + filters: IIssueFilterOptions; +} + +interface IProjectIssuesFilters { + filters: IIssueFilterOptions | undefined; + displayFilters: IIssueDisplayFilterOptions | undefined; + displayProperties: IIssueDisplayProperties | undefined; +} + +export interface IProjectViewIssuesFilter { + // observable + loader: boolean; + filters: { [view_id: string]: IViewIssuesFilterOptions } | undefined; + // computed + issueFilters: IProjectIssuesFilters | undefined; + appliedFilters: TIssueParams[] | undefined; + // actions + fetchViewFilters: (workspaceSlug: string, projectId: string, viewId: string) => Promise; + updateViewFilters: ( + workspaceSlug: string, + projectId: string, + viewId: string, + type: EFilterType, + filters: IIssueFilterOptions + ) => Promise; + + fetchFilters: (workspaceSlug: string, projectId: string, viewId: string) => Promise; + updateFilters: ( + workspaceSlug: string, + projectId: string, + filterType: EFilterType, + filters: IIssueFilterOptions | IIssueDisplayFilterOptions | IIssueDisplayProperties, + viewId?: string | undefined + ) => Promise; +} + +export class ProjectViewIssuesFilter extends IssueFilterHelperStore implements IProjectViewIssuesFilter { + // observables + loader: boolean = false; + filters: { [projectId: string]: IViewIssuesFilterOptions } | undefined = undefined; + // root store + rootStore; + // services + projectService; + projectMemberService; + issueService; + viewService; + + constructor(_rootStore: IssueRootStore) { + super(_rootStore); + + makeObservable(this, { + // observables + loader: observable.ref, + filters: observable.ref, + // computed + issueFilters: computed, + appliedFilters: computed, + // actions + fetchViewFilters: action, + updateViewFilters: action, + fetchFilters: action, + updateFilters: action, + }); + + this.rootStore = _rootStore; + + this.projectService = new ProjectService(); + this.projectMemberService = new ProjectMemberService(); + this.issueService = new IssueService(); + this.viewService = new ViewService(); + } + + get issueFilters() { + const projectId = this.rootStore.projectId; + const viewId = this.rootStore.projectViewId; + if (!projectId || !viewId) return undefined; + + const displayFilters = this.rootStore.issuesFilter.issueDisplayFilters(projectId); + const viewFilters = this.filters?.[viewId]; + + const _filters: IProjectIssuesFilters = { + filters: viewFilters?.filters, + displayFilters: displayFilters?.displayFilters, + displayProperties: displayFilters?.displayProperties, + }; + + return _filters; + } + + get appliedFilters() { + const userFilters = this.issueFilters; + if (!userFilters) return undefined; + + let filteredRouteParams: any = { + priority: userFilters?.filters?.priority || undefined, + state_group: userFilters?.filters?.state_group || undefined, + state: userFilters?.filters?.state || undefined, + assignees: userFilters?.filters?.assignees || undefined, + mentions: userFilters?.filters?.mentions || undefined, + created_by: userFilters?.filters?.created_by || undefined, + labels: userFilters?.filters?.labels || undefined, + start_date: userFilters?.filters?.start_date || undefined, + target_date: userFilters?.filters?.target_date || undefined, + type: userFilters?.displayFilters?.type || undefined, + sub_issue: isNil(userFilters?.displayFilters?.sub_issue) ? true : userFilters?.displayFilters?.sub_issue, + show_empty_groups: isNil(userFilters?.displayFilters?.show_empty_groups) + ? true + : userFilters?.displayFilters?.show_empty_groups, + start_target_date: isNil(userFilters?.displayFilters?.start_target_date) + ? true + : userFilters?.displayFilters?.start_target_date, + }; + + const filteredParams = handleIssueQueryParamsByLayout(userFilters?.displayFilters?.layout, "issues"); + if (filteredParams) filteredRouteParams = this.computedFilter(filteredRouteParams, filteredParams); + + if (userFilters?.displayFilters?.layout === "calendar") filteredRouteParams.group_by = "target_date"; + if (userFilters?.displayFilters?.layout === "gantt_chart") filteredRouteParams.start_target_date = true; + + return filteredRouteParams; + } + + fetchViewFilters = async (workspaceSlug: string, projectId: string, viewId: string) => { + try { + const viewFilters = await this.viewService.getViewDetails(workspaceSlug, projectId, viewId); + + const filters: IIssueFilterOptions = { + assignees: viewFilters?.query_data?.assignees || null, + mentions: viewFilters?.query_data?.mentions || null, + created_by: viewFilters?.query_data?.created_by || null, + labels: viewFilters?.query_data?.labels || null, + priority: viewFilters?.query_data?.priority || null, + project: viewFilters?.query_data?.project || null, + start_date: viewFilters?.query_data?.start_date || null, + state: viewFilters?.query_data?.state || null, + state_group: viewFilters?.query_data?.state_group || null, + subscriber: viewFilters?.query_data?.subscriber || null, + target_date: viewFilters?.query_data?.target_date || null, + }; + + const issueFilters: IViewIssuesFilterOptions = { + filters: filters, + }; + + let _filters = { ...this.filters }; + if (!_filters) _filters = {}; + if (!_filters[viewId]) _filters[viewId] = { filters: {} }; + _filters[viewId] = issueFilters; + + runInAction(() => { + this.filters = _filters; + }); + + return filters; + } catch (error) { + this.fetchFilters(workspaceSlug, projectId, viewId); + throw error; + } + }; + + updateViewFilters = async ( + workspaceSlug: string, + projectId: string, + viewId: string, + type: EFilterType, + filters: IIssueFilterOptions + ) => { + if (!viewId) return; + try { + let _moduleIssueFilters = { ...this.filters }; + if (!_moduleIssueFilters) _moduleIssueFilters = {}; + if (!_moduleIssueFilters[viewId]) _moduleIssueFilters[viewId] = { filters: {} }; + + const _filters = { filters: { ..._moduleIssueFilters[viewId].filters } }; + + if (type === EFilterType.FILTERS) _filters.filters = { ..._filters.filters, ...filters }; + + _moduleIssueFilters[viewId] = { filters: _filters.filters }; + + runInAction(() => { + this.filters = _moduleIssueFilters; + }); + + await this.viewService.patchView(workspaceSlug, projectId, viewId, { + query_data: { ..._filters.filters }, + }); + + return _filters; + } catch (error) { + this.fetchFilters(workspaceSlug, projectId, viewId); + throw error; + } + }; + + fetchFilters = async (workspaceSlug: string, projectId: string, viewId: string) => { + try { + await this.rootStore.issuesFilter.fetchDisplayFilters(workspaceSlug, projectId); + await this.rootStore.issuesFilter.fetchDisplayProperties(workspaceSlug, projectId); + await this.fetchViewFilters(workspaceSlug, projectId, viewId); + return; + } catch (error) { + this.fetchFilters(workspaceSlug, projectId, viewId); + throw error; + } + }; + + updateFilters = async ( + workspaceSlug: string, + projectId: string, + filterType: EFilterType, + filters: IIssueFilterOptions | IIssueDisplayFilterOptions | IIssueDisplayProperties, + viewId?: string | undefined + ) => { + try { + if (!viewId) throw new Error(); + + switch (filterType) { + case EFilterType.FILTERS: + await this.updateViewFilters(workspaceSlug, projectId, viewId, filterType, filters as IIssueFilterOptions); + break; + case EFilterType.DISPLAY_FILTERS: + await this.rootStore.issuesFilter.updateDisplayFilters( + workspaceSlug, + projectId, + filterType, + filters as IIssueDisplayFilterOptions + ); + break; + case EFilterType.DISPLAY_PROPERTIES: + await this.rootStore.issuesFilter.updateDisplayProperties( + workspaceSlug, + projectId, + filters as IIssueDisplayProperties + ); + break; + } + + return; + } catch (error) { + throw error; + } + }; +} diff --git a/web/store/issue/project-views/index.ts b/web/store/issue/project-views/index.ts new file mode 100644 index 000000000..0fe6c946b --- /dev/null +++ b/web/store/issue/project-views/index.ts @@ -0,0 +1,2 @@ +export * from "./filter.store"; +export * from "./issue.store"; diff --git a/web/store/issue/project-views/issue.store.ts b/web/store/issue/project-views/issue.store.ts new file mode 100644 index 000000000..c46558a68 --- /dev/null +++ b/web/store/issue/project-views/issue.store.ts @@ -0,0 +1,5 @@ +export interface IProjectViewIssues {} + +export class ProjectViewIssues implements IProjectViewIssues { + constructor() {} +} diff --git a/web/store/issue/project/filter.store.ts b/web/store/issue/project/filter.store.ts new file mode 100644 index 000000000..1a5ee6836 --- /dev/null +++ b/web/store/issue/project/filter.store.ts @@ -0,0 +1,145 @@ +import { computed, makeObservable } from "mobx"; +// base class +import { IssueFilterHelperStore } from "../helpers/issue-filter-helper.store"; +// helpers +import { handleIssueQueryParamsByLayout } from "helpers/issue.helper"; +import { isNil } from "constants/common"; +// types +import { IssueRootStore } from "../root.store"; +import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions, TIssueParams } from "types"; +// constants +import { EFilterType } from "constants/issue"; + +interface IProjectIssuesFilters { + filters: IIssueFilterOptions | undefined; + displayFilters: IIssueDisplayFilterOptions | undefined; + displayProperties: IIssueDisplayProperties | undefined; +} + +export interface IProjectIssuesFilter { + // computed + issueFilters: IProjectIssuesFilters | undefined; + appliedFilters: TIssueParams[] | undefined; + // action + fetchFilters: (workspaceSlug: string, projectId: string) => Promise; + updateFilters: ( + workspaceSlug: string, + projectId: string, + filterType: EFilterType, + filters: IIssueFilterOptions | IIssueDisplayFilterOptions | IIssueDisplayProperties + ) => Promise; +} + +export class ProjectIssuesFilter extends IssueFilterHelperStore implements IProjectIssuesFilter { + // root store + rootStore; + + constructor(_rootStore: IssueRootStore) { + super(_rootStore); + + makeObservable(this, { + // computed + issueFilters: computed, + appliedFilters: computed, + }); + + // root store + this.rootStore = _rootStore; + } + + get issueFilters() { + const projectId = this.rootStore.projectId; + if (!projectId) return undefined; + const displayFilters = this.rootStore.issuesFilter.issueDisplayFilters(projectId); + + const _filters: IProjectIssuesFilters = { + filters: displayFilters?.filters, + displayFilters: displayFilters?.displayFilters, + displayProperties: displayFilters?.displayProperties, + }; + + return _filters; + } + + get appliedFilters() { + const userFilters = this.issueFilters; + if (!userFilters) return undefined; + + let filteredRouteParams: any = { + priority: userFilters?.filters?.priority || undefined, + state_group: userFilters?.filters?.state_group || undefined, + state: userFilters?.filters?.state || undefined, + assignees: userFilters?.filters?.assignees || undefined, + mentions: userFilters?.filters?.mentions || undefined, + created_by: userFilters?.filters?.created_by || undefined, + labels: userFilters?.filters?.labels || undefined, + start_date: userFilters?.filters?.start_date || undefined, + target_date: userFilters?.filters?.target_date || undefined, + type: userFilters?.displayFilters?.type || undefined, + sub_issue: isNil(userFilters?.displayFilters?.sub_issue) ? true : userFilters?.displayFilters?.sub_issue, + show_empty_groups: isNil(userFilters?.displayFilters?.show_empty_groups) + ? true + : userFilters?.displayFilters?.show_empty_groups, + start_target_date: isNil(userFilters?.displayFilters?.start_target_date) + ? true + : userFilters?.displayFilters?.start_target_date, + }; + + const filteredParams = handleIssueQueryParamsByLayout(userFilters?.displayFilters?.layout, "issues"); + if (filteredParams) filteredRouteParams = this.computedFilter(filteredRouteParams, filteredParams); + + if (userFilters?.displayFilters?.layout === "calendar") filteredRouteParams.group_by = "target_date"; + if (userFilters?.displayFilters?.layout === "gantt_chart") filteredRouteParams.start_target_date = true; + + return filteredRouteParams; + } + + fetchFilters = async (workspaceSlug: string, projectId: string) => { + try { + await this.rootStore.issuesFilter.fetchDisplayFilters(workspaceSlug, projectId); + await this.rootStore.issuesFilter.fetchDisplayProperties(workspaceSlug, projectId); + return; + } catch (error) { + throw Error; + } + }; + + updateFilters = async ( + workspaceSlug: string, + projectId: string, + filterType: EFilterType, + filters: IIssueFilterOptions | IIssueDisplayFilterOptions | IIssueDisplayProperties + ) => { + try { + switch (filterType) { + case EFilterType.FILTERS: + await this.rootStore.issuesFilter.updateDisplayFilters( + workspaceSlug, + projectId, + filterType, + filters as IIssueFilterOptions + ); + break; + case EFilterType.DISPLAY_FILTERS: + await this.rootStore.issuesFilter.updateDisplayFilters( + workspaceSlug, + projectId, + filterType, + filters as IIssueDisplayFilterOptions + ); + break; + case EFilterType.DISPLAY_PROPERTIES: + await this.rootStore.issuesFilter.updateDisplayProperties( + workspaceSlug, + projectId, + filters as IIssueDisplayProperties + ); + break; + } + + return; + } catch (error) { + throw error; + } + }; +} diff --git a/web/store/issue/project/index.ts b/web/store/issue/project/index.ts new file mode 100644 index 000000000..0fe6c946b --- /dev/null +++ b/web/store/issue/project/index.ts @@ -0,0 +1,2 @@ +export * from "./filter.store"; +export * from "./issue.store"; diff --git a/web/store/issue/project/issue.store.ts b/web/store/issue/project/issue.store.ts new file mode 100644 index 000000000..063a6990e --- /dev/null +++ b/web/store/issue/project/issue.store.ts @@ -0,0 +1,215 @@ +import { action, makeObservable, observable, runInAction, computed } from "mobx"; +// base class +import { IssueHelperStore } from "../helpers/issue-helper.store"; +// store +import { IIssueRootStore } from "../root.store"; +// services +import { IssueService } from "services/issue/issue.service"; +// types +import { + IGroupedIssues, + IIssue, + IIssueResponse, + ISubGroupedIssues, + TIssueGroupByOptions, + TLoader, + TUnGroupedIssues, +} from "types"; + +export interface IProjectIssues { + loader: TLoader; + issues: { [project_id: string]: string[] }; + // computed + getIssues: { [issue_id: string]: IIssue } | undefined; + getIssuesIds: IGroupedIssues | ISubGroupedIssues | TUnGroupedIssues | undefined; + // action + fetchIssues: (workspaceSlug: string, projectId: string, loadType: TLoader) => Promise; + createIssue: (workspaceSlug: string, projectId: string, data: Partial) => Promise; + updateIssue: (workspaceSlug: string, projectId: string, issueId: string, data: Partial) => Promise; + removeIssue: (workspaceSlug: string, projectId: string, issueId: string) => Promise; + quickAddIssue: (workspaceSlug: string, projectId: string, data: IIssue) => Promise; +} + +export class ProjectIssues extends IssueHelperStore implements IProjectIssues { + // observable + loader: TLoader = "init-loader"; + issues: { [project_id: string]: string[] } = {}; + // services + issueService; + // root store + rootIssueStore: IIssueRootStore; + + constructor(_rootStore: IIssueRootStore) { + super(_rootStore); + + makeObservable(this, { + // observable + loader: observable.ref, + issues: observable, + // computed + getIssues: computed, + getIssuesIds: computed, + // action + fetchIssues: action, + createIssue: action, + updateIssue: action, + removeIssue: action, + quickAddIssue: action, + }); + + // services + this.issueService = new IssueService(); + // root store + this.rootIssueStore = _rootStore; + } + + get getIssues() { + const projectId = this.rootStore?.projectId; + if (!projectId) return undefined; + + const _issues = this.rootStore.issues.getIssuesByKey("project", projectId); + if (!_issues) return undefined; + + return _issues; + } + + get getIssuesIds() { + const displayFilters = this.rootStore?.projectIssuesFilter?.issueFilters?.displayFilters; + if (!displayFilters) return undefined; + + const subGroupBy = displayFilters?.sub_group_by; + const groupBy = displayFilters?.group_by; + const orderBy = displayFilters?.order_by; + const layout = displayFilters?.layout; + + const _issues = this.getIssues; + if (!_issues) return undefined; + + let issues: IIssueResponse | IGroupedIssues | ISubGroupedIssues | TUnGroupedIssues | undefined = undefined; + + if (layout === "list" && orderBy) { + if (groupBy) issues = this.groupedIssues(groupBy, orderBy, _issues); + else issues = this.unGroupedIssues(orderBy, _issues); + } else if (layout === "kanban" && groupBy && orderBy) { + if (subGroupBy) issues = this.subGroupedIssues(subGroupBy, groupBy, orderBy, _issues); + else issues = this.groupedIssues(groupBy, orderBy, _issues); + } else if (layout === "calendar") + issues = this.groupedIssues("target_date" as TIssueGroupByOptions, "target_date", _issues, true); + else if (layout === "spreadsheet") issues = this.unGroupedIssues(orderBy ?? "-created_at", _issues); + else if (layout === "gantt_chart") issues = this.unGroupedIssues(orderBy ?? "sort_order", _issues); + + return issues; + } + + fetchIssues = async (workspaceSlug: string, projectId: string, loadType: TLoader = "init-loader") => { + try { + this.loader = loadType; + + const params = this.rootStore?.projectIssuesFilter?.appliedFilters; + const response = await this.issueService.getIssues(workspaceSlug, projectId, params); + + const _issues = { ...this.issues, [projectId]: Object.keys(response) }; + + runInAction(() => { + this.issues = _issues; + this.loader = undefined; + }); + + this.rootStore.issues.addIssue(Object.values(response)); + return response; + } catch (error) { + console.error(error); + this.loader = undefined; + throw error; + } + }; + + createIssue = async (workspaceSlug: string, projectId: string, data: Partial) => { + try { + const response = await this.issueService.createIssue(workspaceSlug, projectId, data); + + let _issues = this.issues; + if (!_issues) _issues = {}; + if (!_issues[projectId]) _issues[projectId] = []; + _issues[projectId] = [..._issues[projectId], response.id]; + this.rootStore.issues.addIssue([response]); + + runInAction(() => { + this.issues = _issues; + }); + + return response; + } catch (error) { + this.fetchIssues(workspaceSlug, projectId, "mutation"); + throw error; + } + }; + + updateIssue = async (workspaceSlug: string, projectId: string, issueId: string, data: Partial) => { + try { + this.rootStore.issues.updateIssue(issueId, data); + const response = await this.issueService.patchIssue(workspaceSlug, projectId, issueId, data); + return response; + } catch (error) { + this.fetchIssues(workspaceSlug, projectId, "mutation"); + throw error; + } + }; + + removeIssue = async (workspaceSlug: string, projectId: string, issueId: string) => { + try { + let _issues = { ...this.issues }; + if (!_issues) _issues = {}; + if (!_issues[projectId]) _issues[projectId] = []; + _issues[projectId] = _issues?.[projectId].filter((id) => id !== issueId); + + runInAction(() => { + this.issues = _issues; + }); + + this.rootStore.issues.removeIssue(issueId); + const response = await this.issueService.deleteIssue(workspaceSlug, projectId, issueId); + + return response; + } catch (error) { + this.fetchIssues(workspaceSlug, projectId, "mutation"); + throw error; + } + }; + + quickAddIssue = async (workspaceSlug: string, projectId: string, data: IIssue) => { + try { + let _issues = { ...this.issues }; + if (!_issues) _issues = {}; + if (!_issues[projectId]) _issues[projectId] = []; + + _issues[projectId] = [..._issues[projectId], data.id]; + this.rootStore.issues.updateIssue(data.id, data); + + runInAction(() => { + this.issues = _issues; + }); + + const response = await this.issueService.createIssue(workspaceSlug, projectId, data); + + if (this.issues) { + let _issues = { ...this.issues }; + if (!_issues) _issues = {}; + if (!_issues[projectId]) _issues[projectId] = []; + + _issues[projectId] = [..._issues[projectId].filter((issueId) => issueId != data.id), response.id]; + this.rootStore.issues.removeIssue(data.id); + this.rootStore.issues.addIssue([response]); + + runInAction(() => { + this.issues = _issues; + }); + } + + return response; + } catch (error) { + this.fetchIssues(workspaceSlug, projectId, "mutation"); + throw error; + } + }; +} diff --git a/web/store/issue/root.store.ts b/web/store/issue/root.store.ts new file mode 100644 index 000000000..8a8aac2b2 --- /dev/null +++ b/web/store/issue/root.store.ts @@ -0,0 +1,164 @@ +import { autorun, makeObservable, observable } from "mobx"; +// root store +import { RootStore } from "../root.store"; +// issues data store +import { IIssueStore, IssueStore } from "./issue.store"; +// issues filter base store +import { IIssuesFilter, IssuesFilter } from "./base-issue-filter.store"; +import { IWorkspaceIssuesFilter, WorkspaceIssuesFilter, IWorkspaceIssues, WorkspaceIssues } from "./workspace"; +import { IProfileIssuesFilter, ProfileIssuesFilter, IProfileIssues, ProfileIssues } from "./profile"; +import { IProjectIssuesFilter, ProjectIssuesFilter, IProjectIssues, ProjectIssues } from "./project"; +import { ICycleIssuesFilter, CycleIssuesFilter, ICycleIssues, CycleIssues } from "./cycle"; +import { IModuleIssuesFilter, ModuleIssuesFilter, IModuleIssues, ModuleIssues } from "./module"; +import { + IProjectViewIssuesFilter, + ProjectViewIssuesFilter, + IProjectViewIssues, + ProjectViewIssues, +} from "./project-views"; +import { IArchivedIssuesFilter, ArchivedIssuesFilter, IArchivedIssues, ArchivedIssues } from "./archived"; +import { IDraftIssuesFilter, DraftIssuesFilter, IDraftIssues, DraftIssues } from "./draft"; + +export interface IIssueRootStore { + currentUserId: string | undefined; + workspaceSlug: string | undefined; + profileView: string | undefined; + projectId: string | undefined; + cycleId: string | undefined; + moduleId: string | undefined; + projectViewId: string | undefined; + states: any | undefined; + labels: any | undefined; + members: any | undefined; + projects: any | undefined; + + issues: IIssueStore; + + issuesFilter: IIssuesFilter; + + workspaceIssuesFilter: IWorkspaceIssuesFilter; + workspaceIssues: IWorkspaceIssues; + + profileIssuesFilter: IProfileIssuesFilter; + profileIssues: IProfileIssues; + + projectIssuesFilter: IProjectIssuesFilter; + projectIssues: IProjectIssues; + + cycleIssuesFilter: ICycleIssuesFilter; + cycleIssues: ICycleIssues; + + moduleIssuesFilter: IModuleIssuesFilter; + moduleIssues: IModuleIssues; + + projectViewIssuesFilter: IProjectViewIssuesFilter; + projectViewIssues: IProjectViewIssues; + + archivedIssuesFilter: IArchivedIssuesFilter; + archivedIssues: IArchivedIssues; + + draftIssuesFilter: IDraftIssuesFilter; + draftIssues: IDraftIssues; +} + +export class IssueRootStore { + currentUserId: string | undefined = undefined; + workspaceSlug: string | undefined = undefined; + projectId: string | undefined = undefined; + cycleId: string | undefined = undefined; + moduleId: string | undefined = undefined; + projectViewId: string | undefined = undefined; + profileView: string | undefined = undefined; + states: any | undefined = undefined; + labels: any | undefined = undefined; + members: any | undefined = undefined; + projects: any | undefined = undefined; + + issues: IIssueStore; + + issuesFilter: IIssuesFilter; + + workspaceIssuesFilter: IWorkspaceIssuesFilter; + workspaceIssues: IWorkspaceIssues; + + profileIssuesFilter: IProfileIssuesFilter; + profileIssues: IProfileIssues; + + projectIssuesFilter: IProjectIssuesFilter; + projectIssues: IProjectIssues; + + cycleIssuesFilter: ICycleIssuesFilter; + cycleIssues: ICycleIssues; + + moduleIssuesFilter: IModuleIssuesFilter; + moduleIssues: IModuleIssues; + + projectViewIssuesFilter: IProjectViewIssuesFilter; + projectViewIssues: IProjectViewIssues; + + archivedIssuesFilter: IArchivedIssuesFilter; + archivedIssues: IArchivedIssues; + + draftIssuesFilter: IDraftIssuesFilter; + draftIssues: IDraftIssues; + + constructor(rootStore: RootStore) { + makeObservable(this, { + currentUserId: observable.ref, + workspaceSlug: observable.ref, + projectId: observable.ref, + cycleId: observable.ref, + moduleId: observable.ref, + projectViewId: observable.ref, + profileView: observable.ref, + states: observable, + labels: observable, + members: observable, + projects: observable, + }); + + autorun(() => { + if (rootStore?.user?.currentUser?.id) this.currentUserId = rootStore?.user?.currentUser?.id; + if (rootStore?.workspace?.currentWorkspace?.slug) + this.workspaceSlug = rootStore?.workspace?.currentWorkspace?.slug; + if (rootStore?.project?.projects?.projectId) this.projectId = rootStore?.project?.projects?.projectId; + if (rootStore?.cycle?.cycleId) this.cycleId = rootStore?.cycle?.cycleId; + if (rootStore?.module?.moduleId) this.moduleId = rootStore?.module?.moduleId; + if (rootStore?.projectView?.viewId) this.projectViewId = rootStore?.projectView?.viewId; + + // if (rootStore?.workspace?.profileView) this.profileView = rootStore?.workspace?.profileView; + // if (rootStore?.states) this.states = rootStore?.states; + // if (rootStore?.labels) this.labels = rootStore?.labels; + // if (rootStore?.members) this.members = rootStore?.members; + // if (rootStore?.projects) this.projects = rootStore?.projects; + }); + + this.issues = new IssueStore(this); + + this.issuesFilter = new IssuesFilter(this); + + this.workspaceIssuesFilter = new WorkspaceIssuesFilter(this); + this.workspaceIssues = new WorkspaceIssues(); + + this.profileIssuesFilter = new ProfileIssuesFilter(this); + this.profileIssues = new ProfileIssues(); + + this.projectIssuesFilter = new ProjectIssuesFilter(this); + this.projectIssues = new ProjectIssues(this); + + this.cycleIssuesFilter = new CycleIssuesFilter(this); + this.cycleIssues = new CycleIssues(this); + + this.moduleIssuesFilter = new ModuleIssuesFilter(this); + this.moduleIssues = new ModuleIssues(); + + this.projectViewIssuesFilter = new ProjectViewIssuesFilter(this); + this.projectViewIssues = new ProjectViewIssues(); + + this.archivedIssuesFilter = new ArchivedIssuesFilter(this); + this.archivedIssues = new ArchivedIssues(); + + this.draftIssuesFilter = new DraftIssuesFilter(this); + this.draftIssues = new DraftIssues(); + } +} diff --git a/web/store/issue/workspace/filter.store.ts b/web/store/issue/workspace/filter.store.ts new file mode 100644 index 000000000..5ace5cc26 --- /dev/null +++ b/web/store/issue/workspace/filter.store.ts @@ -0,0 +1,434 @@ +import { action, makeObservable, observable, runInAction } from "mobx"; +// base class +import { IssueFilterHelperStore } from "../helpers/issue-filter-helper.store"; +// services +import { WorkspaceService } from "services/workspace.service"; +// helpers +import { handleIssueQueryParamsByLayout } from "helpers/issue.helper"; +// constants +import { isNil } from "constants/common"; +import { EFilterType } from "constants/issue"; +// types +import { IssueRootStore } from "../root.store"; +import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions, TIssueParams } from "types"; + +interface IIssuesDisplayOptions { + filters: IIssueFilterOptions; +} + +type TIssueViewTypes = "all-issues" | "assigned" | "created" | "subscribed" | string; + +interface IIssueViewOptions { + "all-issues": IIssuesDisplayOptions; + assigned: IIssuesDisplayOptions; + created: IIssuesDisplayOptions; + subscribed: IIssuesDisplayOptions; + [view_id: string]: IIssuesDisplayOptions; +} + +interface IWorkspaceProperties { + filters: IIssueFilterOptions; + displayFilters: IIssueDisplayFilterOptions; + displayProperties: IIssueDisplayProperties; +} + +export interface IWorkspaceIssuesFilter { + // observables + currentView: TIssueViewTypes; + workspaceProperties: { [workspaceId: string]: IWorkspaceProperties } | undefined; + workspaceViewFilters: { [workspaceId: string]: IIssueViewOptions } | undefined; + // computed + issueFilters: IWorkspaceProperties | undefined; + appliedFilters: TIssueParams[] | undefined; + // helpers + issueDisplayFilters: (workspaceId: string) => IIssuesDisplayOptions | undefined; + // actions + setCurrentView: (view: TIssueViewTypes) => void; + fetchWorkspaceProperties: (workspaceSlug: string) => Promise; + updateWorkspaceProperties: ( + workspaceSlug: string, + type: EFilterType, + filters: IIssueFilterOptions | IIssueDisplayFilterOptions | IIssueDisplayProperties + ) => Promise; + + fetchWorkspaceViewFilters: (workspaceId: string, view: TIssueViewTypes) => Promise; + updateWorkspaceViewFilters: (workspaceId: string, filters: IIssueFilterOptions) => Promise; + + fetchFilters: (workspaceSlug: string, view: TIssueViewTypes) => Promise; + updateFilters: ( + workspaceSlug: string, + filterType: EFilterType, + filters: IIssueFilterOptions | IIssueDisplayFilterOptions | IIssueDisplayProperties + ) => Promise; +} + +export class WorkspaceIssuesFilter extends IssueFilterHelperStore implements IWorkspaceIssuesFilter { + // observables + currentView: TIssueViewTypes = "all-issues"; + workspaceProperties: { [workspaceId: string]: IWorkspaceProperties } | undefined = undefined; + workspaceViewFilters: { [workspaceId: string]: IIssueViewOptions } | undefined = undefined; + // root store + rootStore; + // service + workspaceService; + + constructor(_rootStore: IssueRootStore) { + super(_rootStore); + + makeObservable(this, { + // observables + currentView: observable.ref, + workspaceProperties: observable.ref, + workspaceViewFilters: observable.ref, + // computed + // actions + setCurrentView: action, + fetchWorkspaceProperties: action, + updateWorkspaceProperties: action, + fetchWorkspaceViewFilters: action, + updateWorkspaceViewFilters: action, + }); + // root store + this.rootStore = _rootStore; + // services + this.workspaceService = new WorkspaceService(); + } + + // computed + + // helpers + issueDisplayFilters = (workspaceId: string) => { + if (!workspaceId || !this.currentView) return undefined; + const filters: IWorkspaceProperties = { + filters: this.workspaceProperties?.[workspaceId]?.filters || {}, + displayFilters: this.workspaceProperties?.[workspaceId]?.displayFilters || {}, + displayProperties: this.workspaceProperties?.[workspaceId]?.displayProperties || {}, + }; + + if (!["all-issues", "assigned", "created", "subscribed"].includes(this.currentView)) { + const viewFilters = this.workspaceViewFilters?.[workspaceId]?.[this.currentView]; + if (viewFilters) { + filters.filters = { ...filters.filters, ...viewFilters?.filters }; + } + } + + return filters; + }; + + // actions + setCurrentView = (view: TIssueViewTypes) => { + this.currentView = view; + }; + + fetchWorkspaceProperties = async (workspaceSlug: string) => { + try { + let _filters: IWorkspaceProperties = {} as IWorkspaceProperties; + + const filtersResponse = await this.workspaceService.workspaceMemberMe(workspaceSlug); + _filters = { + filters: { ...filtersResponse?.view_props?.filters } || null, + displayFilters: { ...filtersResponse?.view_props?.display_filters } || null, + displayProperties: { ...filtersResponse?.view_props?.display_properties } || null, + }; + + let filters: IIssueFilterOptions = { + assignees: _filters?.filters?.assignees || null, + mentions: _filters?.filters?.mentions || null, + created_by: _filters?.filters?.created_by || null, + labels: _filters?.filters?.labels || null, + priority: _filters?.filters?.priority || null, + project: _filters?.filters?.project || null, + start_date: _filters?.filters?.start_date || null, + state: _filters?.filters?.state || null, + state_group: _filters?.filters?.state_group || null, + subscriber: _filters?.filters?.subscriber || null, + target_date: _filters?.filters?.target_date || null, + }; + + const currentUserId = this.rootStore.currentUserId; + if (currentUserId && this.currentView === "assigned") + filters = { + ...filters, + assignees: [currentUserId], + created_by: null, + subscriber: null, + }; + + if (currentUserId && this.currentView === "created") + filters = { + ...filters, + assignees: null, + created_by: [currentUserId], + subscriber: null, + }; + if (currentUserId && this.currentView === "subscribed") + filters = { + ...filters, + assignees: null, + created_by: null, + subscriber: [currentUserId], + }; + + const displayFilters: IIssueDisplayFilterOptions = { + calendar: { + show_weekends: _filters?.displayFilters?.calendar?.show_weekends || false, + layout: _filters?.displayFilters?.calendar?.layout || "month", + }, + group_by: _filters?.displayFilters?.group_by || null, + sub_group_by: _filters?.displayFilters?.sub_group_by || null, + layout: _filters?.displayFilters?.layout || "list", + order_by: _filters?.displayFilters?.order_by || "-created_at", + show_empty_groups: _filters?.displayFilters?.show_empty_groups || false, + start_target_date: _filters?.displayFilters?.start_target_date || false, + sub_issue: _filters?.displayFilters?.sub_issue || false, + type: _filters?.displayFilters?.type || null, + }; + + const displayProperties: IIssueDisplayProperties = { + assignee: _filters?.displayProperties?.assignee || false, + start_date: _filters?.displayProperties?.start_date || false, + due_date: _filters?.displayProperties?.due_date || false, + labels: _filters?.displayProperties?.labels || false, + key: _filters?.displayProperties?.key || false, + priority: _filters?.displayProperties?.priority || false, + state: _filters?.displayProperties?.state || false, + sub_issue_count: _filters?.displayProperties?.sub_issue_count || false, + link: _filters?.displayProperties?.link || false, + attachment_count: _filters?.displayProperties?.attachment_count || false, + estimate: _filters?.displayProperties?.estimate || false, + created_on: _filters?.displayProperties?.created_on || false, + updated_on: _filters?.displayProperties?.updated_on || false, + }; + + const issueFilters: IWorkspaceProperties = { + filters: filters, + displayFilters: displayFilters, + displayProperties: displayProperties, + }; + + let _workspaceProperties = { ...this.workspaceProperties }; + if (!_workspaceProperties) _workspaceProperties = {}; + if (!_workspaceProperties[workspaceSlug]) + _workspaceProperties[workspaceSlug] = { + filters: {}, + displayFilters: {}, + displayProperties: {}, + }; + _workspaceProperties[workspaceSlug] = { ...issueFilters }; + + runInAction(() => { + this.workspaceProperties = _workspaceProperties; + }); + + return issueFilters; + } catch (error) { + throw error; + } + }; + + updateWorkspaceProperties = async ( + workspaceSlug: string, + type: EFilterType, + filters: IIssueFilterOptions | IIssueDisplayFilterOptions | IIssueDisplayProperties + ) => { + try { + let _workspaceProperties = { ...this.workspaceProperties }; + if (!_workspaceProperties) _workspaceProperties = {}; + if (!_workspaceProperties[workspaceSlug]) + _workspaceProperties[workspaceSlug] = { filters: {}, displayFilters: {}, displayProperties: {} }; + + const _filters = { + filters: { ..._workspaceProperties[workspaceSlug].filters }, + displayFilters: { ..._workspaceProperties[workspaceSlug].displayFilters }, + displayProperties: { ..._workspaceProperties[workspaceSlug].displayProperties }, + }; + + switch (type) { + case EFilterType.FILTERS: + _filters.filters = { ..._filters.filters, ...(filters as IIssueFilterOptions) }; + break; + case EFilterType.DISPLAY_FILTERS: + _filters.displayFilters = { ..._filters.displayFilters, ...(filters as IIssueDisplayFilterOptions) }; + break; + case EFilterType.DISPLAY_PROPERTIES: + _filters.displayProperties = { ..._filters.displayProperties, ...(filters as IIssueDisplayProperties) }; + break; + } + + _workspaceProperties[workspaceSlug] = { + ..._workspaceProperties[workspaceSlug], + filters: _filters?.filters, + displayFilters: _filters?.displayFilters, + displayProperties: _filters?.displayProperties, + }; + + runInAction(() => { + this.workspaceProperties = _workspaceProperties; + }); + + await this.workspaceService.updateWorkspaceView(workspaceSlug, { + view_props: { + filters: _filters.filters, + display_filters: _filters.displayFilters, + display_properties: _filters.displayProperties, + }, + }); + + return _filters; + } catch (error) { + this.fetchWorkspaceProperties(workspaceSlug); + throw error; + } + }; + + fetchWorkspaceViewFilters = async (workspaceSlug: string, view: TIssueViewTypes) => { + try { + let _workspaceViewFilters = { ...this.workspaceViewFilters }; + if (!_workspaceViewFilters) _workspaceViewFilters = {}; + if (!_workspaceViewFilters[workspaceSlug]) _workspaceViewFilters[workspaceSlug] = {} as IIssueViewOptions; + if (!_workspaceViewFilters[workspaceSlug][view]) _workspaceViewFilters[workspaceSlug][view] = { filters: {} }; + + const filtersResponse = await this.workspaceService.getViewDetails(workspaceSlug, view); + + const _filters: IIssueFilterOptions = { + assignees: filtersResponse?.query_data?.filters?.assignees || null, + mentions: filtersResponse?.query_data?.filters?.mentions || null, + created_by: filtersResponse?.query_data?.filters?.created_by || null, + labels: filtersResponse?.query_data?.filters?.labels || null, + priority: filtersResponse?.query_data?.filters?.priority || null, + project: filtersResponse?.query_data?.filters?.project || null, + start_date: filtersResponse?.query_data?.filters?.start_date || null, + state: filtersResponse?.query_data?.filters?.state || null, + state_group: filtersResponse?.query_data?.filters?.state_group || null, + subscriber: filtersResponse?.query_data?.filters?.subscriber || null, + target_date: filtersResponse?.query_data?.filters?.target_date || null, + }; + + _workspaceViewFilters[workspaceSlug][view].filters = { ..._filters }; + + runInAction(() => { + this.workspaceViewFilters = _workspaceViewFilters; + }); + + return _filters; + } catch (error) { + throw error; + } + }; + + updateWorkspaceViewFilters = async (workspaceSlug: string, filters: IIssueFilterOptions) => { + try { + let _workspaceViewFilters = { ...this.workspaceViewFilters }; + if (!_workspaceViewFilters) _workspaceViewFilters = {}; + if (!_workspaceViewFilters[workspaceSlug]) _workspaceViewFilters[workspaceSlug] = {} as IIssueViewOptions; + if (!_workspaceViewFilters[workspaceSlug][this.currentView]) + _workspaceViewFilters[workspaceSlug][this.currentView] = { filters: {} }; + + const _filters = { + filters: { ..._workspaceViewFilters[workspaceSlug][this.currentView].filters, ...filters }, + }; + + _workspaceViewFilters[workspaceSlug][this.currentView] = { + ..._workspaceViewFilters[workspaceSlug][this.currentView], + filters: _filters?.filters, + }; + + runInAction(() => { + this.workspaceViewFilters = _workspaceViewFilters; + }); + + await this.workspaceService.updateView(workspaceSlug, this.currentView, { + query_data: { + filters: _filters.filters, + } as any, + }); + + return _filters.filters; + } catch (error) { + this.fetchWorkspaceViewFilters(workspaceSlug, this.currentView); + throw error; + } + }; + + get issueFilters() { + const workspaceSlug = this.rootStore.workspaceSlug; + if (!workspaceSlug) return undefined; + const displayFilters = this.issueDisplayFilters(workspaceSlug); + + const _filters: IWorkspaceProperties = { + filters: displayFilters?.filters || {}, + displayFilters: displayFilters?.displayFilters || {}, + displayProperties: displayFilters?.displayProperties || {}, + }; + + return _filters; + } + + get appliedFilters() { + const userFilters = this.issueFilters; + if (!userFilters) return undefined; + + let filteredRouteParams: any = { + priority: userFilters?.filters?.priority || undefined, + project: userFilters?.filters?.project || undefined, + state_group: userFilters?.filters?.state_group || undefined, + state: userFilters?.filters?.state || undefined, + assignees: userFilters?.filters?.assignees || undefined, + mentions: userFilters?.filters?.mentions || undefined, + created_by: userFilters?.filters?.created_by || undefined, + labels: userFilters?.filters?.labels || undefined, + start_date: userFilters?.filters?.start_date || undefined, + target_date: userFilters?.filters?.target_date || undefined, + type: userFilters?.displayFilters?.type || undefined, + show_empty_groups: isNil(userFilters?.displayFilters?.show_empty_groups) + ? true + : userFilters?.displayFilters?.show_empty_groups, + start_target_date: isNil(userFilters?.displayFilters?.start_target_date) + ? true + : userFilters?.displayFilters?.start_target_date, + sub_issue: false, + }; + + const filteredParams = handleIssueQueryParamsByLayout("spreadsheet", "my_issues"); + if (filteredParams) filteredRouteParams = this.computedFilter(filteredRouteParams, filteredParams); + + return filteredRouteParams; + } + + fetchFilters = async (workspaceSlug: string, view: TIssueViewTypes) => { + try { + await this.fetchWorkspaceProperties(workspaceSlug); + if (!["all-issues", "assigned", "created", "subscribed"].includes(view)) + await this.fetchWorkspaceViewFilters(workspaceSlug, view); + return; + } catch (error) { + throw Error; + } + }; + + updateFilters = async ( + workspaceSlug: string, + filterType: EFilterType, + filters: IIssueFilterOptions | IIssueDisplayFilterOptions | IIssueDisplayProperties + ) => { + try { + switch (filterType) { + case EFilterType.FILTERS: + if (["all-issues", "assigned", "created", "subscribed"].includes(this.currentView)) + await this.updateWorkspaceProperties(workspaceSlug, filterType, filters as IIssueDisplayFilterOptions); + else await this.updateWorkspaceViewFilters(workspaceSlug, filters as IIssueFilterOptions); + break; + case EFilterType.DISPLAY_FILTERS: + await this.updateWorkspaceProperties(workspaceSlug, filterType, filters as IIssueDisplayFilterOptions); + break; + case EFilterType.DISPLAY_PROPERTIES: + await this.updateWorkspaceProperties(workspaceSlug, filterType, filters as IIssueDisplayProperties); + break; + } + + return; + } catch (error) { + throw error; + } + }; +} diff --git a/web/store/issue/workspace/index.ts b/web/store/issue/workspace/index.ts new file mode 100644 index 000000000..0fe6c946b --- /dev/null +++ b/web/store/issue/workspace/index.ts @@ -0,0 +1,2 @@ +export * from "./filter.store"; +export * from "./issue.store"; diff --git a/web/store/issue/workspace/issue.store.ts b/web/store/issue/workspace/issue.store.ts new file mode 100644 index 000000000..7c79ef989 --- /dev/null +++ b/web/store/issue/workspace/issue.store.ts @@ -0,0 +1,5 @@ +export interface IWorkspaceIssues {} + +export class WorkspaceIssues implements IWorkspaceIssues { + constructor() {} +} diff --git a/web/store/page.store.ts b/web/store/page.store.ts index b787f742b..8d61e2033 100644 --- a/web/store/page.store.ts +++ b/web/store/page.store.ts @@ -1,6 +1,7 @@ import { action, computed, makeObservable, observable, runInAction } from "mobx"; import keyBy from "lodash/keyBy"; import set from "lodash/set"; +import omit from "lodash/omit"; import isToday from "date-fns/isToday"; import isThisWeek from "date-fns/isThisWeek"; import isYesterday from "date-fns/isYesterday"; @@ -9,18 +10,35 @@ import { PageService } from "services/page.service"; // types import { IPage, IRecentPages } from "types"; // store -import { RootStore } from "./root.store"; +import { AppRootStore } from "store/application"; export interface IPageStore { pages: Record; archivedPages: Record; - + // project computed projectPages: IPage[] | undefined; favoriteProjectPages: IPage[] | undefined; privateProjectPages: IPage[] | undefined; - sharedProjectPages: IPage[] | undefined; - + publicProjectPages: IPage[] | undefined; + recentProjectPages: IRecentPages | undefined; + // archived pages computed + archivedProjectPages: IPage[] | undefined; + // fetch actions fetchProjectPages: (workspaceSlug: string, projectId: string) => Promise; + fetchArchivedProjectPages: (workspaceSlug: string, projectId: string) => Promise; + // favorites actions + addToFavorites: (workspaceSlug: string, projectId: string, pageId: string) => Promise; + removeFromFavorites: (workspaceSlug: string, projectId: string, pageId: string) => Promise; + // crud + createPage: (workspaceSlug: string, projectId: string, data: Partial) => Promise; + updatePage: (workspaceSlug: string, projectId: string, pageId: string, data: Partial) => Promise; + deletePage: (workspaceSlug: string, projectId: string, pageId: string) => Promise; + // access control actions + makePublic: (workspaceSlug: string, projectId: string, pageId: string) => Promise; + makePrivate: (workspaceSlug: string, projectId: string, pageId: string) => Promise; + // archive actions + archivePage: (workspaceSlug: string, projectId: string, pageId: string) => Promise; + restorePage: (workspaceSlug: string, projectId: string, pageId: string) => Promise; } export class PageStore { @@ -31,20 +49,36 @@ export class PageStore { // stores router; - constructor(_rootStore: RootStore) { + constructor(appRootStore: AppRootStore) { makeObservable(this, { pages: observable, archivedPages: observable, - // computed + // project computed projectPages: computed, favoriteProjectPages: computed, - sharedProjectPages: computed, + publicProjectPages: computed, privateProjectPages: computed, - // actions + // archived pages in current project computed + archivedProjectPages: computed, + // fetch actions fetchProjectPages: action, + fetchArchivedProjectPages: action, + // favorites actions + addToFavorites: action, + removeFromFavorites: action, + // crud + createPage: action, + updatePage: action, + deletePage: action, + // access control actions + makePublic: action, + makePrivate: action, + // archive actions + archivePage: action, + restorePage: action, }); // stores - this.router = _rootStore.app.router; + this.router = appRootStore.router; // services this.pageService = new PageService(); } @@ -76,7 +110,7 @@ export class PageStore { /** * retrieves all shared pages which are public to everyone in the project for a projectId that is available in the url. */ - get sharedProjectPages() { + get publicProjectPages() { if (!this.projectPages) return; return this.projectPages.filter((page) => page.access === 0); } @@ -231,4 +265,96 @@ export class PageStore { throw error; } }; + + /** + * delete a page using the api and updates the local state in store + * @param workspaceSlug + * @param projectId + * @param pageId + * @returns + */ + deletePage = async (workspaceSlug: string, projectId: string, pageId: string) => { + try { + const response = await this.pageService.deletePage(workspaceSlug, projectId, pageId); + runInAction(() => { + this.archivedPages = set(this.archivedPages, [pageId], this.pages[pageId]); + delete this.pages[pageId]; + }); + return response; + } catch (error) { + throw error; + } + }; + + /** + * make a page public + * @param workspaceSlug + * @param projectId + * @param pageId + * @returns + */ + makePublic = async (workspaceSlug: string, projectId: string, pageId: string) => { + try { + runInAction(() => { + this.pages[pageId] = { ...this.pages[pageId], access: 0 }; + }); + const response = await this.pageService.patchPage(workspaceSlug, projectId, pageId, { access: 0 }); + return response; + } catch (error) { + runInAction(() => { + this.pages[pageId] = { ...this.pages[pageId], access: 1 }; + }); + throw error; + } + }; + + /** + * Make a page private + * @param workspaceSlug + * @param projectId + * @param pageId + * @returns + */ + makePrivate = async (workspaceSlug: string, projectId: string, pageId: string) => { + try { + runInAction(() => { + this.pages[pageId] = { ...this.pages[pageId], access: 1 }; + }); + const response = await this.pageService.patchPage(workspaceSlug, projectId, pageId, { access: 1 }); + return response; + } catch (error) { + runInAction(() => { + this.pages[pageId] = { ...this.pages[pageId], access: 0 }; + }); + throw error; + } + }; + + /** + * Mark a page archived + * @param workspaceSlug + * @param projectId + * @param pageId + */ + archivePage = async (workspaceSlug: string, projectId: string, pageId: string) => { + await this.pageService.archivePage(workspaceSlug, projectId, pageId); + runInAction(() => { + this.archivedPages[pageId] = this.pages[pageId]; + this.pages = omit(this.pages, [pageId]); + }); + }; + + /** + * Restore a page from archived pages to pages + * @param workspaceSlug + * @param projectId + * @param pageId + */ + restorePage = async (workspaceSlug: string, projectId: string, pageId: string) => { + await this.pageService.restorePage(workspaceSlug, projectId, pageId); + runInAction(() => { + this.pages[pageId] = this.archivedPages[pageId]; + this.archivedPages = omit(this.archivedPages, [pageId]); + }); + }; } diff --git a/web/store/root.store.ts b/web/store/root.store.ts index a69d3427e..c63d6678f 100644 --- a/web/store/root.store.ts +++ b/web/store/root.store.ts @@ -8,6 +8,7 @@ import { IModuleStore, ModulesStore } from "./module.store"; import { IUserStore, UserStore } from "./user"; import { ILabelStore, LabelStore } from "./label.store"; import { IWorkspaceRootStore, WorkspaceRootStore } from "./workspace"; +import { IssueRootStore, IIssueRootStore } from "./issue/root.store"; enableStaticRendering(typeof window === "undefined"); @@ -20,9 +21,10 @@ export class RootStore { module: IModuleStore; projectView: IProjectViewsStore; label: ILabelStore; + issue: IIssueRootStore; constructor() { - this.app = new AppRootStore(this); + this.app = new AppRootStore(); this.user = new UserStore(this); this.workspace = new WorkspaceRootStore(this); this.project = new ProjectRootStore(this); @@ -30,9 +32,9 @@ export class RootStore { this.module = new ModulesStore(this); this.projectView = new ProjectViewsStore(this); // this.page = new PageRootStore(); - // this.issue = new IssueRootStore(); // independent stores this.label = new LabelStore(this); // this.state = new stateStore(); + this.issue = new IssueRootStore(this); } } diff --git a/web/store/workspace/index.ts b/web/store/workspace/index.ts index 0738a60ba..d8ee12546 100644 --- a/web/store/workspace/index.ts +++ b/web/store/workspace/index.ts @@ -1,5 +1,6 @@ import { action, computed, observable, makeObservable, runInAction } from "mobx"; import { RootStore } from "../root.store"; +import { set } from "lodash"; // types import { IWorkspace } from "types"; // services @@ -12,24 +13,19 @@ export interface IWorkspaceRootStore { // states loader: boolean; error: any | null; - // observables - workspaces: IWorkspace[] | undefined; - + workspaces: Record; // computed currentWorkspace: IWorkspace | null; workspacesCreatedByCurrentUser: IWorkspace[] | null; - // computed actions getWorkspaceBySlug: (workspaceSlug: string) => IWorkspace | null; getWorkspaceById: (workspaceId: string) => IWorkspace | null; - // actions - fetchWorkspaces: () => Promise; + fetchWorkspaces: () => Promise>; createWorkspace: (data: Partial) => Promise; updateWorkspace: (workspaceSlug: string, data: Partial) => Promise; deleteWorkspace: (workspaceSlug: string) => Promise; - // sub-stores webhook: IWebhookStore; apiToken: IApiTokenStore; @@ -39,10 +35,8 @@ export class WorkspaceRootStore implements IWorkspaceRootStore { // states loader: boolean = false; error: any | null = null; - // observables - workspaces: IWorkspace[] | undefined = []; - + workspaces: Record = {}; // services workspaceService; // root store @@ -56,18 +50,14 @@ export class WorkspaceRootStore implements IWorkspaceRootStore { // states loader: observable.ref, error: observable.ref, - // observables workspaces: observable, - // computed currentWorkspace: computed, workspacesCreatedByCurrentUser: computed, - // computed actions getWorkspaceBySlug: action, getWorkspaceById: action, - // actions fetchWorkspaces: action, createWorkspace: action, @@ -92,7 +82,9 @@ export class WorkspaceRootStore implements IWorkspaceRootStore { if (!workspaceSlug) return null; - return this.workspaces?.find((workspace) => workspace.slug === workspaceSlug) || null; + const workspaceDetails = Object.values(this.workspaces ?? {})?.find((w) => w.slug === workspaceSlug); + + return workspaceDetails || null; } /** @@ -105,20 +97,23 @@ export class WorkspaceRootStore implements IWorkspaceRootStore { if (!user) return null; - return this.workspaces.filter((w) => w.created_by === user?.id); + const userWorkspaces = Object.values(this.workspaces ?? {})?.filter((w) => w.created_by === user?.id); + + return userWorkspaces || null; } /** * get workspace info from the array of workspaces in the store using workspace slug * @param workspaceSlug */ - getWorkspaceBySlug = (workspaceSlug: string) => this.workspaces?.find((w) => w.slug == workspaceSlug) || null; + getWorkspaceBySlug = (workspaceSlug: string) => + Object.values(this.workspaces ?? {})?.find((w) => w.slug == workspaceSlug) || null; /** * get workspace info from the array of workspaces in the store using workspace id * @param workspaceId */ - getWorkspaceById = (workspaceId: string) => this.workspaces?.find((w) => w.id == workspaceId) || null; + getWorkspaceById = (workspaceId: string) => this.workspaces?.[workspaceId] || null; /** * fetch user workspaces from API @@ -143,7 +138,7 @@ export class WorkspaceRootStore implements IWorkspaceRootStore { runInAction(() => { this.loader = false; this.error = error; - this.workspaces = []; + this.workspaces = {}; }); throw error; @@ -163,10 +158,12 @@ export class WorkspaceRootStore implements IWorkspaceRootStore { const response = await this.workspaceService.createWorkspace(data); + const updatedWorkspacesList = set(this.workspaces, response.id, response); + runInAction(() => { this.loader = false; this.error = null; - this.workspaces = [...(this.workspaces ?? []), response]; + this.workspaces = updatedWorkspacesList; }); return response; @@ -186,8 +183,6 @@ export class WorkspaceRootStore implements IWorkspaceRootStore { * @param data */ updateWorkspace = async (workspaceSlug: string, data: Partial) => { - const newWorkspaces = this.workspaces?.map((w) => (w.slug === workspaceSlug ? { ...w, ...data } : w)); - try { runInAction(() => { this.loader = true; @@ -196,10 +191,12 @@ export class WorkspaceRootStore implements IWorkspaceRootStore { const response = await this.workspaceService.updateWorkspace(workspaceSlug, data); + const updatedWorkspacesList = set(this.workspaces, response.id, data); + runInAction(() => { this.loader = false; this.error = null; - this.workspaces = newWorkspaces; + this.workspaces = updatedWorkspacesList; }); return response; @@ -218,8 +215,6 @@ export class WorkspaceRootStore implements IWorkspaceRootStore { * @param workspaceSlug */ deleteWorkspace = async (workspaceSlug: string) => { - const newWorkspaces = this.workspaces?.filter((w) => w.slug !== workspaceSlug); - try { runInAction(() => { this.loader = true; @@ -228,10 +223,15 @@ export class WorkspaceRootStore implements IWorkspaceRootStore { await this.workspaceService.deleteWorkspace(workspaceSlug); + const updatedWorkspacesList = this.workspaces; + const workspaceId = this.getWorkspaceBySlug(workspaceSlug)?.id; + + delete updatedWorkspacesList[`${workspaceId}`]; + runInAction(() => { this.loader = false; this.error = null; - this.workspaces = newWorkspaces; + this.workspaces = updatedWorkspacesList; }); } catch (error) { runInAction(() => { diff --git a/web/types/issues.d.ts b/web/types/issues.d.ts index 09f21eb3a..ce9f4453d 100644 --- a/web/types/issues.d.ts +++ b/web/types/issues.d.ts @@ -254,3 +254,21 @@ export interface IIssueViewProps { } export type TIssuePriorities = "urgent" | "high" | "medium" | "low" | "none"; + +export type TLoader = "init-loader" | "mutation" | undefined; + +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; +} diff --git a/yarn.lock b/yarn.lock index f27dbe5fe..04b58e86d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2790,7 +2790,7 @@ dependencies: "@types/react" "*" -"@types/react@*", "@types/react@^18.2.39", "@types/react@^18.2.42": +"@types/react@*", "@types/react@18.2.42", "@types/react@^18.2.39", "@types/react@^18.2.42": version "18.2.42" resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.42.tgz#6f6b11a904f6d96dda3c2920328a97011a00aba7" integrity sha512-c1zEr96MjakLYus/wPnuWDo1/zErfdU9rNsIGmE+NV71nx88FG9Ttgo5dqorXTu/LImX2f63WBP986gJkMPNbA==