add modified stores for projects, cycles, modules and views

This commit is contained in:
rahulramesha 2023-12-11 20:24:46 +05:30
parent d0688e5287
commit 31e9705afb
8 changed files with 1761 additions and 12 deletions

370
web/store/cycle.store.ts Normal file
View File

@ -0,0 +1,370 @@
import { action, computed, observable, makeObservable, runInAction } from "mobx";
// types
import { ICycle, TCycleView, CycleDateCheckData } from "types";
// mobx
import { RootStore } from "store/root.store";
// services
import { ProjectService } from "services/project";
import { IssueService } from "services/issue";
import { CycleService } from "services/cycle.service";
export interface ICycleStore {
loader: boolean;
error: any | null;
cycleView: TCycleView;
cycleId: string | null;
cycleMap: {
[projectId: string]: {
[cycleId: string]: ICycle;
};
};
cycles: {
[projectId: string]: {
[filterType: string]: string[];
};
};
// computed
getCycleById: (cycleId: string) => ICycle | null;
projectCycles: string[] | null;
projectCompletedCycles: string[] | null;
projectUpcomingCycles: string[] | null;
projectDraftCycles: string[] | null;
// actions
validateDate: (workspaceSlug: string, projectId: string, payload: CycleDateCheckData) => Promise<any>;
fetchCycles: (
workspaceSlug: string,
projectId: string,
params: "all" | "current" | "upcoming" | "draft" | "completed" | "incomplete"
) => Promise<void>;
fetchCycleDetails: (workspaceSlug: string, projectId: string, cycleId: string) => Promise<ICycle>;
createCycle: (workspaceSlug: string, projectId: string, data: Partial<ICycle>) => Promise<ICycle>;
updateCycleDetails: (
workspaceSlug: string,
projectId: string,
cycleId: string,
data: Partial<ICycle>
) => Promise<ICycle>;
deleteCycle: (workspaceSlug: string, projectId: string, cycleId: string) => Promise<void>;
addCycleToFavorites: (workspaceSlug: string, projectId: string, cycleId: string) => Promise<any>;
removeCycleFromFavorites: (workspaceSlug: string, projectId: string, cycleId: string) => Promise<void>;
}
export class CycleStore implements ICycleStore {
loader: boolean = false;
error: any | null = null;
cycleView: TCycleView = "all";
cycleId: string | null = null;
cycleMap: {
[projectId: string]: {
[cycleId: string]: ICycle;
};
} = {};
cycles: {
[projectId: string]: {
[filterType: string]: string[];
};
} = {};
// root store
rootStore;
// services
projectService;
issueService;
cycleService;
constructor(_rootStore: RootStore) {
makeObservable(this, {
loader: observable,
error: observable.ref,
cycleId: observable.ref,
cycleMap: observable.ref,
cycles: observable.ref,
// computed
projectCycles: computed,
projectCompletedCycles: computed,
projectUpcomingCycles: computed,
projectDraftCycles: computed,
// actions
getCycleById: action,
fetchCycles: action,
fetchCycleDetails: action,
createCycle: action,
updateCycleDetails: action,
deleteCycle: action,
addCycleToFavorites: action,
removeCycleFromFavorites: action,
});
this.rootStore = _rootStore;
this.projectService = new ProjectService();
this.issueService = new IssueService();
this.cycleService = new CycleService();
}
// computed
get projectCycles() {
const projectId = this.rootStore.project.projectId;
if (!projectId) return null;
return this.cycles[projectId]?.all || null;
}
get projectCompletedCycles() {
const projectId = this.rootStore.project.projectId;
if (!projectId) return null;
return this.cycles[projectId]?.completed || null;
}
get projectUpcomingCycles() {
const projectId = this.rootStore.project.projectId;
if (!projectId) return null;
return this.cycles[projectId]?.upcoming || null;
}
get projectDraftCycles() {
const projectId = this.rootStore.project.projectId;
if (!projectId) return null;
return this.cycles[projectId]?.draft || null;
}
getCycleById = (cycleId: string) => this.cycleMap[this.rootStore.project][cycleId] || null;
// actions
setCycleView = (_cycleView: TCycleView) => (this.cycleView = _cycleView);
validateDate = async (workspaceSlug: string, projectId: string, payload: CycleDateCheckData) => {
try {
const response = await this.cycleService.cycleDateCheck(workspaceSlug, projectId, payload);
return response;
} catch (error) {
console.log("Failed to validate cycle dates", error);
throw error;
}
};
fetchCycles = async (
workspaceSlug: string,
projectId: string,
params: "all" | "current" | "upcoming" | "draft" | "completed" | "incomplete"
) => {
try {
this.loader = true;
this.error = null;
const cyclesResponse = await this.cycleService.getCyclesWithParams(workspaceSlug, projectId, params);
runInAction(() => {
this.cycleMap = {
...this.cycleMap,
[projectId]: {
...this.cycleMap[projectId],
...cyclesResponse,
},
};
this.cycles = {
...this.cycles,
[projectId]: { ...this.cycles[projectId], [params]: Object.keys(cyclesResponse) },
};
this.loader = false;
this.error = null;
});
} catch (error) {
console.error("Failed to fetch project cycles in project store", error);
this.loader = false;
this.error = error;
}
};
fetchCycleDetails = async (workspaceSlug: string, projectId: string, cycleId: string) => {
try {
const response = await this.cycleService.getCycleDetails(workspaceSlug, projectId, cycleId);
runInAction(() => {
this.cycleMap = {
...this.cycleMap,
[projectId]: {
...this.cycleMap[projectId],
[response?.id]: response,
},
};
});
return response;
} catch (error) {
console.log("Failed to fetch cycle detail from cycle store");
throw error;
}
};
createCycle = async (workspaceSlug: string, projectId: string, data: Partial<ICycle>) => {
try {
const response = await this.cycleService.createCycle(workspaceSlug, projectId, data);
runInAction(() => {
this.cycleMap = {
...this.cycleMap,
[projectId]: {
...this.cycleMap[projectId],
[response?.id]: response,
},
};
});
const _currentView = this.cycleView === "active" ? "current" : this.cycleView;
this.fetchCycles(workspaceSlug, projectId, _currentView);
return response;
} catch (error) {
console.log("Failed to create cycle from cycle store");
throw error;
}
};
updateCycleDetails = async (workspaceSlug: string, projectId: string, cycleId: string, data: Partial<ICycle>) => {
try {
const _response = await this.cycleService.patchCycle(workspaceSlug, projectId, cycleId, data);
const currentCycle = this.cycleMap[projectId][cycleId];
runInAction(() => {
this.cycleMap = {
...this.cycleMap,
[projectId]: {
...this.cycleMap[projectId],
[cycleId]: { ...currentCycle, ...data },
},
};
});
const _currentView = this.cycleView === "active" ? "current" : this.cycleView;
this.fetchCycles(workspaceSlug, projectId, _currentView);
return _response;
} catch (error) {
console.log("Failed to patch cycle from cycle store");
throw error;
}
};
deleteCycle = async (workspaceSlug: string, projectId: string, cycleId: string) => {
try {
const currentProjectCycles = this.cycleMap[projectId];
delete currentProjectCycles[cycleId];
runInAction(() => {
this.cycleMap = {
...this.cycleMap,
[projectId]: currentProjectCycles,
};
});
const _response = await this.cycleService.deleteCycle(workspaceSlug, projectId, cycleId);
return _response;
} catch (error) {
console.log("Failed to delete cycle from cycle store");
const _currentView = this.cycleView === "active" ? "current" : this.cycleView;
this.fetchCycles(workspaceSlug, projectId, _currentView);
throw error;
}
};
addCycleToFavorites = async (workspaceSlug: string, projectId: string, cycleId: string) => {
try {
const currentCycle = this.cycleMap[projectId][cycleId];
if (currentCycle.is_favorite) return;
runInAction(() => {
this.cycleMap = {
...this.cycleMap,
[projectId]: {
...this.cycleMap[projectId],
[cycleId]: { ...currentCycle, is_favorite: true },
},
};
});
// updating through api.
const response = await this.cycleService.addCycleToFavorites(workspaceSlug, projectId, { cycle: cycleId });
return response;
} catch (error) {
console.log("Failed to add cycle to favorites in the cycles store", error);
// reset on error
const currentCycle = this.cycleMap[projectId][cycleId];
runInAction(() => {
this.cycleMap = {
...this.cycleMap,
[projectId]: {
...this.cycleMap[projectId],
[cycleId]: { ...currentCycle, is_favorite: false },
},
};
});
throw error;
}
};
removeCycleFromFavorites = async (workspaceSlug: string, projectId: string, cycleId: string) => {
try {
const currentCycle = this.cycleMap[projectId][cycleId];
if (!currentCycle.is_favorite) return;
runInAction(() => {
this.cycleMap = {
...this.cycleMap,
[projectId]: {
...this.cycleMap[projectId],
[cycleId]: { ...currentCycle, is_favorite: false },
},
};
});
const response = await this.cycleService.removeCycleFromFavorites(workspaceSlug, projectId, cycleId);
return response;
} catch (error) {
console.log("Failed to remove cycle from favorites - Cycle Store", error);
// reset on error
const currentCycle = this.cycleMap[projectId][cycleId];
runInAction(() => {
this.cycleMap = {
...this.cycleMap,
[projectId]: {
...this.cycleMap[projectId],
[cycleId]: { ...currentCycle, is_favorite: true },
},
};
});
throw error;
}
};
}

446
web/store/module.store.ts Normal file
View File

@ -0,0 +1,446 @@
import { action, computed, observable, makeObservable, runInAction } from "mobx";
// services
import { ProjectService } from "services/project";
import { ModuleService } from "services/module.service";
// types
import { IModule, ILinkDetails } from "types";
import { RootStore } from "store/root.store";
export interface IModuleStore {
// states
loader: boolean;
error: any | null;
// observables
moduleId: string | null;
moduleMap: {
[project_id: string]: {
[module_id: string]: IModule;
};
};
// actions
getModuleById: (moduleId: string) => IModule | null;
fetchModules: (workspaceSlug: string, projectId: string) => void;
fetchModuleDetails: (workspaceSlug: string, projectId: string, moduleId: string) => Promise<IModule>;
createModule: (workspaceSlug: string, projectId: string, data: Partial<IModule>) => Promise<IModule>;
updateModuleDetails: (
workspaceSlug: string,
projectId: string,
moduleId: string,
data: Partial<IModule>
) => Promise<IModule>;
deleteModule: (workspaceSlug: string, projectId: string, moduleId: string) => Promise<void>;
createModuleLink: (
workspaceSlug: string,
projectId: string,
moduleId: string,
data: Partial<ILinkDetails>
) => Promise<ILinkDetails>;
updateModuleLink: (
workspaceSlug: string,
projectId: string,
moduleId: string,
linkId: string,
data: Partial<ILinkDetails>
) => Promise<ILinkDetails>;
deleteModuleLink: (workspaceSlug: string, projectId: string, moduleId: string, linkId: string) => Promise<void>;
addModuleToFavorites: (workspaceSlug: string, projectId: string, moduleId: string) => Promise<void>;
removeModuleFromFavorites: (workspaceSlug: string, projectId: string, moduleId: string) => Promise<void>;
// computed
projectModules: string[] | null;
}
export class ModulesStore implements IModuleStore {
// states
loader: boolean = false;
error: any | null = null;
// observables
moduleId: string | null = null;
moduleMap: {
[project_id: string]: {
[module_id: string]: IModule;
};
} = {};
// root store
rootStore;
// services
projectService;
moduleService;
constructor(_rootStore: RootStore) {
makeObservable(this, {
// states
loader: observable,
error: observable.ref,
// observables
moduleId: observable.ref,
moduleMap: observable.ref,
// actions
getModuleById: action,
fetchModules: action,
fetchModuleDetails: action,
createModule: action,
updateModuleDetails: action,
deleteModule: action,
createModuleLink: action,
updateModuleLink: action,
deleteModuleLink: action,
addModuleToFavorites: action,
removeModuleFromFavorites: action,
// computed
projectModules: computed,
});
this.rootStore = _rootStore;
// services
this.projectService = new ProjectService();
this.moduleService = new ModuleService();
}
// computed
get projectModules() {
if (!this.rootStore.project.projectId) return null;
return Object.keys(this.moduleMap[this.rootStore.project.projectId]) || null;
}
getModuleById = (moduleId: string) => this.moduleMap[this.rootStore.project.projectId][moduleId] || null;
// actions
fetchModules = async (workspaceSlug: string, projectId: string) => {
try {
runInAction(() => {
this.loader = true;
this.error = null;
});
const modulesResponse = await this.moduleService.getModules(workspaceSlug, projectId);
runInAction(() => {
this.moduleMap = {
...this.moduleMap,
[projectId]: modulesResponse,
};
this.loader = false;
this.error = null;
});
} catch (error) {
console.error("Failed to fetch modules list in module store", error);
runInAction(() => {
this.loader = false;
this.error = error;
});
}
};
fetchModuleDetails = async (workspaceSlug: string, projectId: string, moduleId: string) => {
try {
runInAction(() => {
this.loader = true;
this.error = null;
});
const response = await this.moduleService.getModuleDetails(workspaceSlug, projectId, moduleId);
runInAction(() => {
this.moduleMap = {
...this.moduleMap,
[projectId]: {
...this.moduleMap[projectId],
[moduleId]: response,
},
};
this.loader = false;
this.error = null;
});
return response;
} catch (error) {
console.error("Failed to fetch module details in module store", error);
runInAction(() => {
this.loader = false;
this.error = error;
});
throw error;
}
};
createModule = async (workspaceSlug: string, projectId: string, data: Partial<IModule>) => {
try {
const response = await this.moduleService.createModule(workspaceSlug, projectId, data);
runInAction(() => {
this.moduleMap = {
...this.moduleMap,
[projectId]: {
...this.moduleMap[projectId],
[response.id]: response,
},
};
this.loader = false;
this.error = null;
});
this.fetchModules(workspaceSlug, projectId);
return response;
} catch (error) {
console.error("Failed to create module in module store", error);
runInAction(() => {
this.loader = false;
this.error = error;
});
throw error;
}
};
updateModuleDetails = async (workspaceSlug: string, projectId: string, moduleId: string, data: Partial<IModule>) => {
try {
const currentModule = this.moduleMap[projectId][moduleId];
runInAction(() => {
this.moduleMap = {
...this.moduleMap,
[projectId]: {
...this.moduleMap[projectId],
[moduleId]: { ...currentModule, ...data },
},
};
});
const response = await this.moduleService.patchModule(workspaceSlug, projectId, moduleId, data);
return response;
} catch (error) {
console.error("Failed to update module in module store", error);
this.fetchModules(workspaceSlug, projectId);
this.fetchModuleDetails(workspaceSlug, projectId, moduleId);
runInAction(() => {
this.error = error;
});
throw error;
}
};
deleteModule = async (workspaceSlug: string, projectId: string, moduleId: string) => {
try {
const currentProjectModules = this.moduleMap[projectId];
delete currentProjectModules[moduleId];
runInAction(() => {
this.moduleMap = {
...this.moduleMap,
[projectId]: currentProjectModules,
};
});
await this.moduleService.deleteModule(workspaceSlug, projectId, moduleId);
} catch (error) {
console.error("Failed to delete module in module store", error);
this.fetchModules(workspaceSlug, projectId);
runInAction(() => {
this.error = error;
});
}
};
createModuleLink = async (
workspaceSlug: string,
projectId: string,
moduleId: string,
data: Partial<ILinkDetails>
) => {
try {
const response = await this.moduleService.createModuleLink(workspaceSlug, projectId, moduleId, data);
const currentModule = this.moduleMap[projectId][moduleId];
runInAction(() => {
this.moduleMap = {
...this.moduleMap,
[projectId]: {
...this.moduleMap[projectId],
[moduleId]: { ...currentModule, link_module: [response, ...currentModule.link_module] },
},
};
});
return response;
} catch (error) {
console.error("Failed to create module link in module store", error);
this.fetchModules(workspaceSlug, projectId);
this.fetchModuleDetails(workspaceSlug, projectId, moduleId);
runInAction(() => {
this.error = error;
});
throw error;
}
};
updateModuleLink = async (
workspaceSlug: string,
projectId: string,
moduleId: string,
linkId: string,
data: Partial<ILinkDetails>
) => {
try {
const response = await this.moduleService.updateModuleLink(workspaceSlug, projectId, moduleId, linkId, data);
const currentModule = this.moduleMap[projectId][moduleId];
const linkModules = currentModule.link_module.map((link) => (link.id === linkId ? response : link));
runInAction(() => {
this.moduleMap = {
...this.moduleMap,
[projectId]: {
...this.moduleMap[projectId],
[moduleId]: { ...currentModule, link_module: linkModules },
},
};
});
return response;
} catch (error) {
console.error("Failed to update module link in module store", error);
this.fetchModules(workspaceSlug, projectId);
this.fetchModuleDetails(workspaceSlug, projectId, moduleId);
runInAction(() => {
this.error = error;
});
throw error;
}
};
deleteModuleLink = async (workspaceSlug: string, projectId: string, moduleId: string, linkId: string) => {
try {
const currentModule = this.moduleMap[projectId][moduleId];
const linkModules = currentModule.link_module.filter((link) => link.id !== linkId);
runInAction(() => {
this.moduleMap = {
...this.moduleMap,
[projectId]: {
...this.moduleMap[projectId],
[moduleId]: { ...currentModule, link_module: linkModules },
},
};
});
await this.moduleService.deleteModuleLink(workspaceSlug, projectId, moduleId, linkId);
} catch (error) {
console.error("Failed to delete module link in module store", error);
this.fetchModules(workspaceSlug, projectId);
this.fetchModuleDetails(workspaceSlug, projectId, moduleId);
runInAction(() => {
this.error = error;
});
throw error;
}
};
addModuleToFavorites = async (workspaceSlug: string, projectId: string, moduleId: string) => {
try {
const currentModule = this.moduleMap[projectId][moduleId];
if (currentModule.is_favorite) return;
runInAction(() => {
this.moduleMap = {
...this.moduleMap,
[projectId]: {
...this.moduleMap[projectId],
[moduleId]: { ...currentModule, is_favorite: true },
},
};
});
await this.moduleService.addModuleToFavorites(workspaceSlug, projectId, {
module: moduleId,
});
} catch (error) {
console.error("Failed to add module to favorites in module store", error);
const currentModule = this.moduleMap[projectId][moduleId];
runInAction(() => {
this.moduleMap = {
...this.moduleMap,
[projectId]: {
...this.moduleMap[projectId],
[moduleId]: { ...currentModule, is_favorite: false },
},
};
});
}
};
removeModuleFromFavorites = async (workspaceSlug: string, projectId: string, moduleId: string) => {
try {
const currentModule = this.moduleMap[projectId][moduleId];
if (!currentModule.is_favorite) return;
runInAction(() => {
this.moduleMap = {
...this.moduleMap,
[projectId]: {
...this.moduleMap[projectId],
[moduleId]: { ...currentModule, is_favorite: false },
},
};
});
await this.moduleService.removeModuleFromFavorites(workspaceSlug, projectId, moduleId);
} catch (error) {
console.error("Failed to remove module from favorites in module store", error);
const currentModule = this.moduleMap[projectId][moduleId];
runInAction(() => {
this.moduleMap = {
...this.moduleMap,
[projectId]: {
...this.moduleMap[projectId],
[moduleId]: { ...currentModule, is_favorite: true },
},
};
});
}
};
}

View File

@ -0,0 +1,290 @@
import { observable, action, makeObservable, runInAction } from "mobx";
// services
import { ViewService } from "services/view.service";
import { RootStore } from "store/root.store";
// types
import { IProjectView } from "types";
export interface IProjectViewsStore {
// states
loader: boolean;
error: any | null;
// observables
viewId: string | null;
viewMap: {
[projectId: string]: {
[viewId: string]: IProjectView;
};
};
// actions
fetchViews: (workspaceSlug: string, projectId: string) => Promise<IProjectView[]>;
fetchViewDetails: (workspaceSlug: string, projectId: string, viewId: string) => Promise<IProjectView>;
createView: (workspaceSlug: string, projectId: string, data: Partial<IProjectView>) => Promise<IProjectView>;
updateView: (
workspaceSlug: string,
projectId: string,
viewId: string,
data: Partial<IProjectView>
) => Promise<IProjectView>;
deleteView: (workspaceSlug: string, projectId: string, viewId: string) => Promise<any>;
addViewToFavorites: (workspaceSlug: string, projectId: string, viewId: string) => Promise<any>;
removeViewFromFavorites: (workspaceSlug: string, projectId: string, viewId: string) => Promise<any>;
}
export class ProjectViewsStore implements IProjectViewsStore {
// states
loader: boolean = false;
error: any | null = null;
// observables
viewId: string | null = null;
viewMap: {
[projectId: string]: {
[viewId: string]: IProjectView;
};
} = {};
// root store
rootStore;
// services
viewService;
constructor(_rootStore: RootStore) {
makeObservable(this, {
// states
loader: observable.ref,
error: observable.ref,
// observables
viewId: observable.ref,
viewMap: observable.ref,
// actions
fetchViews: action,
fetchViewDetails: action,
createView: action,
updateView: action,
deleteView: action,
addViewToFavorites: action,
removeViewFromFavorites: action,
});
this.rootStore = _rootStore;
this.viewService = new ViewService();
}
setViewId = (viewId: string | null) => {
this.viewId = viewId;
};
fetchViews = async (workspaceSlug: string, projectId: string): Promise<IProjectView[]> => {
try {
runInAction(() => {
this.loader = true;
});
const response = await this.viewService.getViews(workspaceSlug, projectId);
runInAction(() => {
this.loader = false;
this.viewMap = {
...this.viewMap,
[projectId]: response,
};
});
return response;
} catch (error) {
runInAction(() => {
this.loader = false;
this.error = error;
});
throw error;
}
};
fetchViewDetails = async (workspaceSlug: string, projectId: string, viewId: string): Promise<IProjectView> => {
try {
runInAction(() => {
this.loader = true;
});
const response = await this.viewService.getViewDetails(workspaceSlug, projectId, viewId);
runInAction(() => {
this.loader = false;
this.viewMap = {
...this.viewMap,
[projectId]: {
...this.viewMap[projectId],
[response.id]: response,
},
};
});
return response;
} catch (error) {
runInAction(() => {
this.loader = false;
this.error = error;
});
throw error;
}
};
createView = async (workspaceSlug: string, projectId: string, data: Partial<IProjectView>): Promise<IProjectView> => {
try {
const response = await this.viewService.createView(workspaceSlug, projectId, data);
runInAction(() => {
this.loader = false;
this.viewMap = {
...this.viewMap,
[projectId]: {
...this.viewMap[projectId],
[response.id]: response,
},
};
});
return response;
} catch (error) {
runInAction(() => {
this.error = error;
});
throw error;
}
};
updateView = async (
workspaceSlug: string,
projectId: string,
viewId: string,
data: Partial<IProjectView>
): Promise<IProjectView> => {
try {
const currentView = this.viewMap[projectId][viewId];
runInAction(() => {
this.viewMap = {
...this.viewMap,
[projectId]: {
...this.viewMap[projectId],
[viewId]: { ...currentView, ...data },
},
};
});
const response = await this.viewService.patchView(workspaceSlug, projectId, viewId, data);
return response;
} catch (error) {
this.fetchViewDetails(workspaceSlug, projectId, viewId);
runInAction(() => {
this.error = error;
});
throw error;
}
};
deleteView = async (workspaceSlug: string, projectId: string, viewId: string): Promise<any> => {
try {
const currentProjectViews = this.viewMap[projectId];
delete currentProjectViews[viewId];
runInAction(() => {
this.viewMap = {
...this.viewMap,
[projectId]: currentProjectViews,
};
});
await this.viewService.deleteView(workspaceSlug, projectId, viewId);
} catch (error) {
this.fetchViews(workspaceSlug, projectId);
runInAction(() => {
this.error = error;
});
throw error;
}
};
addViewToFavorites = async (workspaceSlug: string, projectId: string, viewId: string) => {
try {
const currentView = this.viewMap[projectId][viewId];
if (currentView.is_favorite) return;
runInAction(() => {
this.viewMap = {
...this.viewMap,
[projectId]: {
...this.viewMap[projectId],
[viewId]: { ...currentView, is_favorite: true },
},
};
});
await this.viewService.addViewToFavorites(workspaceSlug, projectId, {
view: viewId,
});
} catch (error) {
console.error("Failed to add view to favorites in view store", error);
const currentView = this.viewMap[projectId][viewId];
runInAction(() => {
this.viewMap = {
...this.viewMap,
[projectId]: {
...this.viewMap[projectId],
[viewId]: { ...currentView, is_favorite: false },
},
};
});
}
};
removeViewFromFavorites = async (workspaceSlug: string, projectId: string, viewId: string) => {
try {
const currentView = this.viewMap[projectId][viewId];
if (!currentView.is_favorite) return;
runInAction(() => {
this.viewMap = {
...this.viewMap,
[projectId]: {
...this.viewMap[projectId],
[viewId]: { ...currentView, is_favorite: false },
},
};
});
await this.viewService.removeViewFromFavorites(workspaceSlug, projectId, viewId);
} catch (error) {
console.error("Failed to remove view from favorites in view store", error);
const currentView = this.viewMap[projectId][viewId];
runInAction(() => {
this.viewMap = {
...this.viewMap,
[projectId]: {
...this.viewMap[projectId],
[viewId]: { ...currentView, is_favorite: true },
},
};
});
}
};
}

View File

@ -0,0 +1,13 @@
import { ProjectsStore } from "./projects.store";
import { ProjectPublishStore } from "./project-publish.store";
import { RootStore } from "store/root.store";
export class ProjectRootStore {
projects: ProjectsStore;
publish: ProjectPublishStore;
constructor(_root: RootStore) {
this.projects = new ProjectsStore(_root);
this.publish = new ProjectPublishStore(this);
}
}

View File

@ -0,0 +1,265 @@
import { observable, action, makeObservable, runInAction } from "mobx";
// types
import { ProjectRootStore } from "./";
// services
import { ProjectPublishService } from "services/project";
export type TProjectPublishViews = "list" | "gantt" | "kanban" | "calendar" | "spreadsheet";
export type TProjectPublishViewsSettings = {
[key in TProjectPublishViews]: boolean;
};
export interface IProjectPublishSettings {
id?: string;
project?: string;
comments: boolean;
reactions: boolean;
votes: boolean;
views: TProjectPublishViewsSettings;
inbox: string | null;
}
export interface IProjectPublishStore {
generalLoader: boolean;
fetchSettingsLoader: boolean;
error: any | null;
projectPublishSettings: IProjectPublishSettings | "not-initialized";
getProjectSettingsAsync: (workspaceSlug: string, projectId: string) => Promise<void>;
publishProject: (workspaceSlug: string, projectId: string, data: IProjectPublishSettings) => Promise<void>;
updateProjectSettingsAsync: (
workspaceSlug: string,
projectId: string,
projectPublishId: string,
data: IProjectPublishSettings
) => Promise<void>;
unPublishProject: (workspaceSlug: string, projectId: string, projectPublishId: string) => Promise<void>;
}
export class ProjectPublishStore implements IProjectPublishStore {
// states
generalLoader: boolean = false;
fetchSettingsLoader: boolean = false;
error: any | null = null;
// actions
project_id: string | null = null;
projectPublishSettings: IProjectPublishSettings | "not-initialized" = "not-initialized";
// root store
projectRootStore: ProjectRootStore;
// services
projectPublishService;
constructor(_projectRootStore: ProjectRootStore) {
makeObservable(this, {
// states
generalLoader: observable,
fetchSettingsLoader: observable,
error: observable,
// observables
project_id: observable,
projectPublishSettings: observable.ref,
// actions
getProjectSettingsAsync: action,
publishProject: action,
updateProjectSettingsAsync: action,
unPublishProject: action,
});
this.projectRootStore = _projectRootStore;
// services
this.projectPublishService = new ProjectPublishService();
}
getProjectSettingsAsync = async (workspaceSlug: string, projectId: string) => {
try {
runInAction(() => {
this.fetchSettingsLoader = true;
this.error = null;
});
const response = await this.projectPublishService.getProjectSettingsAsync(workspaceSlug, projectId);
if (response && response.length > 0) {
const _projectPublishSettings: IProjectPublishSettings = {
id: response[0]?.id,
comments: response[0]?.comments,
reactions: response[0]?.reactions,
votes: response[0]?.votes,
views: {
list: response[0]?.views?.list || false,
kanban: response[0]?.views?.kanban || false,
calendar: response[0]?.views?.calendar || false,
gantt: response[0]?.views?.gantt || false,
spreadsheet: response[0]?.views?.spreadsheet || false,
},
inbox: response[0]?.inbox || null,
project: response[0]?.project || null,
};
runInAction(() => {
this.projectPublishSettings = _projectPublishSettings;
this.fetchSettingsLoader = false;
this.error = null;
});
} else {
runInAction(() => {
this.projectPublishSettings = "not-initialized";
this.fetchSettingsLoader = false;
this.error = null;
});
}
return response;
} catch (error) {
runInAction(() => {
this.fetchSettingsLoader = false;
this.error = error;
});
return error;
}
};
publishProject = async (workspaceSlug: string, projectId: string, data: IProjectPublishSettings) => {
try {
runInAction(() => {
this.generalLoader = true;
this.error = null;
});
const response = await this.projectPublishService.createProjectSettingsAsync(workspaceSlug, projectId, data);
if (response) {
const _projectPublishSettings: IProjectPublishSettings = {
id: response?.id || null,
comments: response?.comments || false,
reactions: response?.reactions || false,
votes: response?.votes || false,
views: { ...response?.views },
inbox: response?.inbox || null,
project: response?.project || null,
};
runInAction(() => {
this.projectPublishSettings = _projectPublishSettings;
this.projectRootStore.projects.projectsMap = {
...this.projectRootStore.projects.projectsMap,
[workspaceSlug]: {
...this.projectRootStore.projects.projectsMap[workspaceSlug],
[projectId]: {
...this.projectRootStore.projects.projectsMap[workspaceSlug][projectId],
is_deployed: true,
},
},
};
this.generalLoader = false;
this.error = null;
});
return response;
}
} catch (error) {
runInAction(() => {
this.generalLoader = false;
this.error = error;
});
return error;
}
};
updateProjectSettingsAsync = async (
workspaceSlug: string,
projectId: string,
projectPublishId: string,
data: IProjectPublishSettings
) => {
try {
runInAction(() => {
this.generalLoader = true;
this.error = null;
});
const response = await this.projectPublishService.updateProjectSettingsAsync(
workspaceSlug,
projectId,
projectPublishId,
data
);
if (response) {
const _projectPublishSettings: IProjectPublishSettings = {
id: response?.id || null,
comments: response?.comments || false,
reactions: response?.reactions || false,
votes: response?.votes || false,
views: { ...response?.views },
inbox: response?.inbox || null,
project: response?.project || null,
};
runInAction(() => {
this.projectPublishSettings = _projectPublishSettings;
this.generalLoader = false;
this.error = null;
});
return response;
}
} catch (error) {
runInAction(() => {
this.generalLoader = false;
this.error = error;
});
return error;
}
};
unPublishProject = async (workspaceSlug: string, projectId: string, projectPublishId: string) => {
try {
runInAction(() => {
this.generalLoader = true;
this.error = null;
});
const response = await this.projectPublishService.deleteProjectSettingsAsync(
workspaceSlug,
projectId,
projectPublishId
);
runInAction(() => {
this.projectPublishSettings = "not-initialized";
this.projectRootStore.projects.projectsMap = {
...this.projectRootStore.projects.projectsMap,
[workspaceSlug]: {
...this.projectRootStore.projects.projectsMap[workspaceSlug],
[projectId]: {
...this.projectRootStore.projects.projectsMap[workspaceSlug][projectId],
is_deployed: false,
},
},
};
this.generalLoader = false;
this.error = null;
});
return response;
} catch (error) {
runInAction(() => {
this.generalLoader = false;
this.error = error;
});
return error;
}
};
}

View File

@ -0,0 +1,360 @@
import { observable, action, computed, makeObservable, runInAction } from "mobx";
import { IssueLabelService, IssueService } from "services/issue";
import { ProjectService, ProjectStateService } from "services/project";
import { RootStore } from "store/root.store";
import { IProject } from "types";
export interface IProjectsStore {
loader: boolean;
error: any | null;
searchQuery: string;
projectId: string | null;
projectsMap: {
[workspaceSlug: string]: {
[projectId: string]: IProject; // projectId: project Info
};
};
// computed
searchedProjects: string[];
workspaceProjects: string[] | null;
joinedProjects: string[];
favoriteProjects: string[];
currentProjectDetails: IProject | undefined;
// actions
setSearchQuery: (query: string) => void;
getProjectById: (workspaceSlug: string, projectId: string) => IProject | null;
fetchProjects: (workspaceSlug: string) => Promise<void>;
fetchProjectDetails: (workspaceSlug: string, projectId: string) => Promise<any>;
addProjectToFavorites: (workspaceSlug: string, projectId: string) => Promise<any>;
removeProjectFromFavorites: (workspaceSlug: string, projectId: string) => Promise<any>;
orderProjectsWithSortOrder: (sourceIndex: number, destinationIndex: number, projectId: string) => number;
updateProjectView: (workspaceSlug: string, projectId: string, viewProps: any) => Promise<any>;
createProject: (workspaceSlug: string, data: any) => Promise<any>;
updateProject: (workspaceSlug: string, projectId: string, data: Partial<IProject>) => Promise<any>;
deleteProject: (workspaceSlug: string, projectId: string) => Promise<void>;
}
export class ProjectsStore implements IProjectsStore {
loader: boolean = false;
error: any | null = null;
projectId: string | null = null;
searchQuery: string = "";
projectsMap: {
[workspaceSlug: string]: {
[projectId: string]: IProject; // projectId: project Info
};
};
// root store
rootStore: RootStore;
// service
projectService;
issueLabelService;
issueService;
stateService;
constructor(_rootStore: RootStore) {
makeObservable(this, {
// observable
loader: observable,
error: observable,
searchQuery: observable.ref,
projectId: observable.ref,
projectsMap: observable.ref,
// computed
searchedProjects: computed,
workspaceProjects: computed,
currentProjectDetails: computed,
joinedProjects: computed,
favoriteProjects: computed,
// action
setSearchQuery: action,
fetchProjects: action,
fetchProjectDetails: action,
addProjectToFavorites: action,
removeProjectFromFavorites: action,
orderProjectsWithSortOrder: action,
updateProjectView: action,
createProject: action,
updateProject: action,
});
this.rootStore = _rootStore;
this.projectService = new ProjectService();
this.issueService = new IssueService();
this.issueLabelService = new IssueLabelService();
this.stateService = new ProjectStateService();
}
get searchedProjects() {
if (!this.rootStore.workspace.workspaceSlug) return [];
const currentProjectsMap = this.projectsMap[this.rootStore.workspace.workspaceSlug];
const projectIds = Object.keys(currentProjectsMap);
return this.searchQuery === ""
? projectIds
: projectIds?.filter((projectId) => {
currentProjectsMap[projectId].name.toLowerCase().includes(this.searchQuery.toLowerCase()) ||
currentProjectsMap[projectId].identifier.toLowerCase().includes(this.searchQuery.toLowerCase());
});
}
get workspaceProjects() {
if (!this.rootStore.workspace.workspaceSlug) return null;
const currentProjectsMap = this.projectsMap[this.rootStore.workspace.workspaceSlug];
const projectIds = Object.keys(currentProjectsMap);
if (!projectIds) return null;
return projectIds;
}
get currentProjectDetails() {
if (!this.projectId || !this.rootStore.workspace.workspaceSlug) return;
return this.projectsMap[this.rootStore.workspace.workspaceSlug][this.projectId];
}
get joinedProjects() {
if (!this.rootStore.workspace.workspaceSlug) return [];
const currentProjectsMap = this.projectsMap[this.rootStore.workspace.workspaceSlug];
const projectIds = Object.keys(currentProjectsMap);
return projectIds?.filter((projectId) => currentProjectsMap[projectId].is_member);
}
get favoriteProjects() {
if (!this.rootStore.workspace.workspaceSlug) return [];
const currentProjectsMap = this.projectsMap[this.rootStore.workspace.workspaceSlug];
const projectIds = Object.keys(currentProjectsMap);
return projectIds?.filter((projectId) => currentProjectsMap[projectId].is_favorite);
}
setSearchQuery = (query: string) => {
this.searchQuery = query;
};
/**
* get Workspace projects using workspace slug
* @param workspaceSlug
* @returns
*
*/
fetchProjects = async (workspaceSlug: string) => {
try {
const currentProjectsMap = await this.projectService.getProjects(workspaceSlug);
runInAction(() => {
this.projectsMap = {
...this.projectsMap,
[workspaceSlug]: currentProjectsMap,
};
});
} catch (error) {
console.log("Failed to fetch project from workspace store");
throw error;
}
};
fetchProjectDetails = async (workspaceSlug: string, projectId: string) => {
try {
const response = await this.projectService.getProject(workspaceSlug, projectId);
runInAction(() => {
this.projectsMap = {
...this.projectsMap,
[workspaceSlug]: {
...this.projectsMap[workspaceSlug],
[projectId]: response,
},
};
});
return response;
} catch (error) {
console.log("Error while fetching project details", error);
throw error;
}
};
getProjectById = (workspaceSlug: string, projectId: string) => {
const currentProjectsMap = this.projectsMap?.[workspaceSlug];
if (!currentProjectsMap) return null;
const projectInfo: IProject | null = currentProjectsMap[projectId] || null;
return projectInfo;
};
addProjectToFavorites = async (workspaceSlug: string, projectId: string) => {
try {
const currentProject = this.projectsMap?.[workspaceSlug]?.[projectId];
runInAction(() => {
this.projectsMap = {
...this.projectsMap,
[workspaceSlug]: {
...this.projectsMap[workspaceSlug],
[projectId]: { ...currentProject, is_favorite: true },
},
};
});
const response = await this.projectService.addProjectToFavorites(workspaceSlug, projectId);
return response;
} catch (error) {
console.log("Failed to add project to favorite");
await this.fetchProjects(workspaceSlug);
throw error;
}
};
removeProjectFromFavorites = async (workspaceSlug: string, projectId: string) => {
try {
const currentProject = this.projectsMap?.[workspaceSlug]?.[projectId];
runInAction(() => {
this.projectsMap = {
...this.projectsMap,
[workspaceSlug]: {
...this.projectsMap[workspaceSlug],
[projectId]: { ...currentProject, is_favorite: false },
},
};
});
const response = await this.projectService.removeProjectFromFavorites(workspaceSlug, projectId);
await this.fetchProjects(workspaceSlug);
return response;
} catch (error) {
console.log("Failed to add project to favorite");
throw error;
}
};
orderProjectsWithSortOrder = (sortIndex: number, destinationIndex: number, projectId: string) => {
try {
const workspaceSlug = this.rootStore.workspace.workspaceSlug;
if (!workspaceSlug) return 0;
const projectsList = Object.values(this.projectsMap[workspaceSlug] || {}) || [];
let updatedSortOrder = projectsList[sortIndex].sort_order;
if (destinationIndex === 0) updatedSortOrder = (projectsList[0].sort_order as number) - 1000;
else if (destinationIndex === projectsList.length - 1)
updatedSortOrder = (projectsList[projectsList.length - 1].sort_order as number) + 1000;
else {
const destinationSortingOrder = projectsList[destinationIndex].sort_order as number;
const relativeDestinationSortingOrder =
sortIndex < destinationIndex
? (projectsList[destinationIndex + 1].sort_order as number)
: (projectsList[destinationIndex - 1].sort_order as number);
updatedSortOrder = (destinationSortingOrder + relativeDestinationSortingOrder) / 2;
}
const currentProject = this.projectsMap?.[workspaceSlug]?.[projectId];
runInAction(() => {
this.projectsMap = {
...this.projectsMap,
[workspaceSlug]: {
...this.projectsMap[workspaceSlug],
[projectId]: { ...currentProject, sort_order: updatedSortOrder },
},
};
});
return updatedSortOrder;
} catch (error) {
console.log("failed to update sort order of the projects");
return 0;
}
};
updateProjectView = async (workspaceSlug: string, projectId: string, viewProps: any) => {
try {
const response = await this.projectService.setProjectView(workspaceSlug, projectId, viewProps);
await this.fetchProjects(workspaceSlug);
return response;
} catch (error) {
console.log("Failed to update sort order of the projects");
throw error;
}
};
createProject = async (workspaceSlug: string, data: any) => {
try {
const response = await this.projectService.createProject(workspaceSlug, data);
runInAction(() => {
this.projectsMap = {
...this.projectsMap,
[workspaceSlug]: { ...this.projectsMap[workspaceSlug], [response.id]: response },
};
});
return response;
} catch (error) {
console.log("Failed to create project from project store");
throw error;
}
};
updateProject = async (workspaceSlug: string, projectId: string, data: Partial<IProject>) => {
try {
const currentProject = this.projectsMap?.[workspaceSlug]?.[projectId];
runInAction(() => {
this.projectsMap = {
...this.projectsMap,
[workspaceSlug]: { ...this.projectsMap[workspaceSlug], [projectId]: { ...currentProject, ...data } },
};
});
const response = await this.projectService.updateProject(workspaceSlug, projectId, data);
return response;
} catch (error) {
console.log("Failed to create project from project store");
this.fetchProjects(workspaceSlug);
this.fetchProjectDetails(workspaceSlug, projectId);
throw error;
}
};
deleteProject = async (workspaceSlug: string, projectId: string) => {
try {
const workspaceProjects = { ...this.projectsMap[workspaceSlug] };
delete workspaceProjects[projectId];
runInAction(() => {
this.projectsMap = {
...this.projectsMap,
[workspaceSlug]: { ...workspaceProjects },
};
});
await this.projectService.deleteProject(workspaceSlug, projectId);
await this.fetchProjects(workspaceSlug);
} catch (error) {
console.log("Failed to delete project from project store");
this.fetchProjects(workspaceSlug);
}
};
}

View File

@ -1,6 +1,10 @@
import { enableStaticRendering } from "mobx-react-lite";
// root stores
import { AppRootStore } from "./application";
import { ProjectRootStore } from "./project";
import { CycleStore } from "./cycle.store";
import { ProjectViewsStore } from "./project-view.store";
import { ModulesStore } from "./module.store";
enableStaticRendering(typeof window === "undefined");
@ -15,16 +19,16 @@ export class RootStore {
constructor() {
this.app = new AppRootStore();
this.user = new UserRootStore();
this.workspace = new WorkspaceRootStore();
this.project = new ProjectRootStore();
this.cycle = new CycleRootStore();
this.module = new ModuleRootStore();
this.projectView = new ProjectViewRootStore();
this.page = new PageRootStore();
this.issue = new IssueRootStore();
// independent stores
this.label = new labelStore();
this.state = new stateStore();
// this.user = new UserRootStore();
// this.workspace = new WorkspaceRootStore();
this.project = new ProjectRootStore(this);
this.cycle = new CycleStore(this);
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.state = new stateStore();
}
}

View File

@ -3,7 +3,7 @@ import { action, computed, observable, makeObservable, runInAction } from "mobx"
import { ProjectService } from "services/project";
import { ModuleService } from "services/module.service";
// types
import { RootStore } from "../root";
import { IIssue, IModule, ILinkDetails } from "types";
import {
IIssueGroupWithSubGroupsStructure,
@ -11,6 +11,7 @@ import {
IIssueUnGroupedStructure,
} from "../issue/issue.store";
import { IBlockUpdateData } from "components/gantt-chart";
import { RootStore } from "store/root.store";
export interface IModuleStore {
// states