diff --git a/web/hooks/store/index.ts b/web/hooks/store/index.ts index a80e61b4d..bf0ee98fa 100644 --- a/web/hooks/store/index.ts +++ b/web/hooks/store/index.ts @@ -1,6 +1,7 @@ export * from "./use-application"; export * from "./use-cycle"; export * from "./use-estimate"; +export * from "./use-global-view"; export * from "./use-inbox"; export * from "./use-label"; export * from "./use-member"; diff --git a/web/hooks/store/use-global-view.ts b/web/hooks/store/use-global-view.ts new file mode 100644 index 000000000..db4412fe3 --- /dev/null +++ b/web/hooks/store/use-global-view.ts @@ -0,0 +1,11 @@ +import { useContext } from "react"; +// mobx store +import { StoreContext } from "contexts/store-context"; +// types +import { IGlobalViewStore } from "store/global-view.store"; + +export const useGlobalView = (): IGlobalViewStore => { + const context = useContext(StoreContext); + if (context === undefined) throw new Error("useProjectView must be used within StoreProvider"); + return context.globalView; +}; diff --git a/web/store/application/router.store.ts b/web/store/application/router.store.ts index 69a24d22c..1b002d8c7 100644 --- a/web/store/application/router.store.ts +++ b/web/store/application/router.store.ts @@ -12,6 +12,7 @@ export interface IRouterStore { cycleId: string | undefined; moduleId: string | undefined; viewId: string | undefined; + globalViewId: string | undefined; userId: string | undefined; peekId: string | undefined; issueId: string | undefined; @@ -35,6 +36,7 @@ export class RouterStore implements IRouterStore { cycleId: computed, moduleId: computed, viewId: computed, + globalViewId: computed, userId: computed, peekId: computed, issueId: computed, @@ -69,6 +71,10 @@ export class RouterStore implements IRouterStore { return this.query?.viewId?.toString(); } + get globalViewId() { + return this.query?.globalViewId?.toString(); + } + get userId() { return this.query?.userId?.toString(); } diff --git a/web/store/global-view.store.ts b/web/store/global-view.store.ts new file mode 100644 index 000000000..18c5d4453 --- /dev/null +++ b/web/store/global-view.store.ts @@ -0,0 +1,218 @@ +import { observable, action, makeObservable, runInAction, computed } from "mobx"; +import { set } from "lodash"; +// services +import { WorkspaceService } from "services/workspace.service"; +// types +import { RootStore } from "store/root.store"; +import { IWorkspaceView } from "types/workspace-views"; + +export interface IGlobalViewStore { + // states + loader: boolean; + error: any | null; + // observables + globalViewMap: Record; + // computed + currentWorkspaceViews: string[] | null; + // computed actions + getViewDetails: (viewId: string) => IWorkspaceView | null; + // actions + fetchAllGlobalViews: (workspaceSlug: string) => Promise; + fetchGlobalViewDetails: (workspaceSlug: string, viewId: string) => Promise; + createGlobalView: (workspaceSlug: string, data: Partial) => Promise; + updateGlobalView: (workspaceSlug: string, viewId: string, data: Partial) => Promise; + deleteGlobalView: (workspaceSlug: string, viewId: string) => Promise; +} + +export class GlobalViewStore implements IGlobalViewStore { + // states + loader: boolean = false; + error: any | null = null; + // observables + globalViewMap: Record = {}; + // root store + rootStore; + // services + workspaceService; + + constructor(_rootStore: RootStore) { + makeObservable(this, { + // states + loader: observable.ref, + error: observable.ref, + // observables + globalViewMap: observable, + // computed + currentWorkspaceViews: computed, + // computed actions + getViewDetails: action, + // actions + fetchAllGlobalViews: action, + fetchGlobalViewDetails: action, + createGlobalView: action, + updateGlobalView: action, + deleteGlobalView: action, + }); + + // root store + this.rootStore = _rootStore; + // services + this.workspaceService = new WorkspaceService(); + } + + /** + * @description returns list of views for current workspace + */ + get currentWorkspaceViews() { + const currentWorkspaceDetails = this.rootStore.workspaceRoot.currentWorkspace; + if (!currentWorkspaceDetails) return null; + + return ( + Object.keys(this.globalViewMap ?? {})?.filter( + (viewId) => this.globalViewMap[viewId]?.workspace === currentWorkspaceDetails.id + ) ?? null + ); + } + + /** + * @description returns view details for given viewId + * @param viewId + */ + getViewDetails = (viewId: string): IWorkspaceView | null => this.globalViewMap[viewId] ?? null; + + /** + * @description fetch all global views for given workspace + * @param workspaceSlug + */ + fetchAllGlobalViews = async (workspaceSlug: string): Promise => { + try { + runInAction(() => { + this.loader = true; + }); + + const response = await this.workspaceService.getAllViews(workspaceSlug); + + runInAction(() => { + this.loader = false; + response.forEach((view) => { + set(this.globalViewMap, view.id, view); + }); + }); + + return response; + } catch (error) { + runInAction(() => { + this.loader = false; + this.error = error; + }); + + throw error; + } + }; + + /** + * @description fetch view details for given viewId + * @param viewId + */ + fetchGlobalViewDetails = async (workspaceSlug: string, viewId: string): Promise => { + try { + runInAction(() => { + this.loader = true; + }); + + const response = await this.workspaceService.getViewDetails(workspaceSlug, viewId); + + runInAction(() => { + this.loader = false; + set(this.globalViewMap, viewId, response); + }); + + return response; + } catch (error) { + runInAction(() => { + this.loader = false; + this.error = error; + }); + + throw error; + } + }; + + /** + * @description create new global view + * @param workspaceSlug + * @param data + */ + createGlobalView = async (workspaceSlug: string, data: Partial): Promise => { + try { + const response = await this.workspaceService.createView(workspaceSlug, data); + + runInAction(() => { + set(this.globalViewMap, response.id, response); + }); + + return response; + } catch (error) { + runInAction(() => { + this.error = error; + }); + + throw error; + } + }; + + /** + * @description update global view + * @param workspaceSlug + * @param viewId + * @param data + */ + updateGlobalView = async ( + workspaceSlug: string, + viewId: string, + data: Partial + ): Promise => { + const viewToUpdate = { ...this.getViewDetails(viewId), ...data }; + + try { + runInAction(() => { + set(this.globalViewMap, viewId, viewToUpdate); + }); + + const response = await this.workspaceService.updateView(workspaceSlug, viewId, data); + + return response; + } catch (error) { + this.fetchGlobalViewDetails(workspaceSlug, viewId); + + runInAction(() => { + this.error = error; + }); + + throw error; + } + }; + + /** + * @description delete global view + * @param workspaceSlug + * @param viewId + */ + deleteGlobalView = async (workspaceSlug: string, viewId: string): Promise => { + try { + runInAction(() => { + delete this.globalViewMap[viewId]; + }); + + await this.workspaceService.deleteView(workspaceSlug, viewId); + } catch (error) { + this.fetchAllGlobalViews(workspaceSlug); + + runInAction(() => { + this.error = error; + }); + + throw error; + } + }; +} diff --git a/web/store/root.store.ts b/web/store/root.store.ts index 219ae76d7..98929eb81 100644 --- a/web/store/root.store.ts +++ b/web/store/root.store.ts @@ -14,6 +14,7 @@ import { ILabelRootStore, LabelRootStore } from "./label"; import { IMemberRootStore, MemberRootStore } from "./member"; import { IInboxRootStore, InboxRootStore } from "./inbox"; import { IProjectEstimateStore, ProjectEstimatesStore } from "./estimate.store"; +import { GlobalViewStore, IGlobalViewStore } from "./global-view.store"; enableStaticRendering(typeof window === "undefined"); @@ -28,6 +29,7 @@ export class RootStore { cycle: ICycleStore; module: IModuleStore; projectView: IProjectViewStore; + globalView: IGlobalViewStore; page: IPageStore; issue: IIssueRootStore; state: IStateStore; @@ -45,6 +47,7 @@ export class RootStore { this.cycle = new CycleStore(this); this.module = new ModulesStore(this); this.projectView = new ProjectViewStore(this); + this.globalView = new GlobalViewStore(this); this.page = new PageStore(this); this.issue = new IssueRootStore(this); this.state = new StateStore(this);