feat: cycles and modules archive. (#4005)

* fix: GET request changes

* fix: filtering changes

* feat: cycles and modules archive.

* chore: disable fetching of cycle/ module details when clicked on the card in archives page.

* chore: remove copy link button from archived modules/ cycles.

* fix: archived cycle and module loading fliker issue.

* chore: add validation to only archive completed cycles.

* chore: add validation to only archive completed or cancelled module.

* chore: archived issues/ cycles/ modules empty state update.

---------

Co-authored-by: NarayanBavisetti <narayan3119@gmail.com>
Co-authored-by: sriram veeraghanta <veeraghanta.sriram@gmail.com>
This commit is contained in:
Prateek Shourya 2024-03-20 21:02:58 +05:30 committed by GitHub
parent 4d1b5adfc4
commit 061be85a5d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
72 changed files with 2429 additions and 682 deletions

View file

@ -3,17 +3,18 @@ import set from "lodash/set";
import sortBy from "lodash/sortBy";
import { action, computed, observable, makeObservable, runInAction } from "mobx";
import { computedFn } from "mobx-utils";
// helpers
import { getDate } from "@/helpers/date-time.helper";
import { orderCycles, shouldFilterCycle } from "@/helpers/cycle.helper";
// services
import { CycleService } from "@/services/cycle.service";
import { IssueService } from "@/services/issue";
import { ProjectService } from "@/services/project";
// mobx
import { RootStore } from "@/store/root.store";
// types
import { ICycle, CycleDateCheckData } from "@plane/types";
// helpers
import { orderCycles, shouldFilterCycle } from "@/helpers/cycle.helper";
import { getDate } from "@/helpers/date-time.helper";
// services
import { CycleService } from "@/services/cycle.service";
import { CycleArchiveService } from "@/services/cycle_archive.service";
import { IssueService } from "@/services/issue";
import { ProjectService } from "@/services/project";
// store
import { RootStore } from "@/store/root.store";
export interface ICycleStore {
// loaders
@ -29,9 +30,11 @@ export interface ICycleStore {
currentProjectIncompleteCycleIds: string[] | null;
currentProjectDraftCycleIds: string[] | null;
currentProjectActiveCycleId: string | null;
currentProjectArchivedCycleIds: string[] | null;
// computed actions
getFilteredCycleIds: (projectId: string) => string[] | null;
getFilteredCompletedCycleIds: (projectId: string) => string[] | null;
getFilteredArchivedCycleIds: (projectId: string) => string[] | null;
getCycleById: (cycleId: string) => ICycle | null;
getCycleNameById: (cycleId: string) => string | undefined;
getActiveCycleById: (cycleId: string) => ICycle | null;
@ -42,6 +45,7 @@ export interface ICycleStore {
fetchWorkspaceCycles: (workspaceSlug: string) => Promise<ICycle[]>;
fetchAllCycles: (workspaceSlug: string, projectId: string) => Promise<undefined | ICycle[]>;
fetchActiveCycle: (workspaceSlug: string, projectId: string) => Promise<undefined | ICycle[]>;
fetchArchivedCycles: (workspaceSlug: string, projectId: string) => Promise<undefined | ICycle[]>;
fetchCycleDetails: (workspaceSlug: string, projectId: string, cycleId: string) => Promise<ICycle>;
// crud
createCycle: (workspaceSlug: string, projectId: string, data: Partial<ICycle>) => Promise<ICycle>;
@ -55,6 +59,9 @@ export interface ICycleStore {
// favorites
addCycleToFavorites: (workspaceSlug: string, projectId: string, cycleId: string) => Promise<any>;
removeCycleFromFavorites: (workspaceSlug: string, projectId: string, cycleId: string) => Promise<void>;
// archive
archiveCycle: (workspaceSlug: string, projectId: string, cycleId: string) => Promise<void>;
restoreCycle: (workspaceSlug: string, projectId: string, cycleId: string) => Promise<void>;
}
export class CycleStore implements ICycleStore {
@ -70,6 +77,7 @@ export class CycleStore implements ICycleStore {
projectService;
issueService;
cycleService;
cycleArchiveService;
constructor(_rootStore: RootStore) {
makeObservable(this, {
@ -85,22 +93,29 @@ export class CycleStore implements ICycleStore {
currentProjectIncompleteCycleIds: computed,
currentProjectDraftCycleIds: computed,
currentProjectActiveCycleId: computed,
currentProjectArchivedCycleIds: computed,
// actions
fetchWorkspaceCycles: action,
fetchAllCycles: action,
fetchActiveCycle: action,
fetchArchivedCycles: action,
fetchCycleDetails: action,
createCycle: action,
updateCycleDetails: action,
deleteCycle: action,
addCycleToFavorites: action,
removeCycleFromFavorites: action,
archiveCycle: action,
restoreCycle: action,
});
this.rootStore = _rootStore;
// services
this.projectService = new ProjectService();
this.issueService = new IssueService();
this.cycleService = new CycleService();
this.cycleArchiveService = new CycleArchiveService();
}
// computed
@ -110,7 +125,7 @@ export class CycleStore implements ICycleStore {
get currentProjectCycleIds() {
const projectId = this.rootStore.app.router.projectId;
if (!projectId || !this.fetchedMap[projectId]) return null;
let allCycles = Object.values(this.cycleMap ?? {}).filter((c) => c?.project_id === projectId);
let allCycles = Object.values(this.cycleMap ?? {}).filter((c) => c?.project_id === projectId && !c?.archived_at);
allCycles = sortBy(allCycles, [(c) => c.sort_order]);
const allCycleIds = allCycles.map((c) => c.id);
return allCycleIds;
@ -126,7 +141,7 @@ export class CycleStore implements ICycleStore {
const endDate = getDate(c.end_date);
const hasEndDatePassed = endDate && isPast(endDate);
const isEndDateToday = endDate && isToday(endDate);
return c.project_id === projectId && hasEndDatePassed && !isEndDateToday;
return c.project_id === projectId && hasEndDatePassed && !isEndDateToday && !c?.archived_at;
});
completedCycles = sortBy(completedCycles, [(c) => c.sort_order]);
const completedCycleIds = completedCycles.map((c) => c.id);
@ -142,7 +157,7 @@ export class CycleStore implements ICycleStore {
let upcomingCycles = Object.values(this.cycleMap ?? {}).filter((c) => {
const startDate = getDate(c.start_date);
const isStartDateUpcoming = startDate && isFuture(startDate);
return c.project_id === projectId && isStartDateUpcoming;
return c.project_id === projectId && isStartDateUpcoming && !c?.archived_at;
});
upcomingCycles = sortBy(upcomingCycles, [(c) => c.sort_order]);
const upcomingCycleIds = upcomingCycles.map((c) => c.id);
@ -158,7 +173,7 @@ export class CycleStore implements ICycleStore {
let incompleteCycles = Object.values(this.cycleMap ?? {}).filter((c) => {
const endDate = getDate(c.end_date);
const hasEndDatePassed = endDate && isPast(endDate);
return c.project_id === projectId && !hasEndDatePassed;
return c.project_id === projectId && !hasEndDatePassed && !c?.archived_at;
});
incompleteCycles = sortBy(incompleteCycles, [(c) => c.sort_order]);
const incompleteCycleIds = incompleteCycles.map((c) => c.id);
@ -172,7 +187,7 @@ export class CycleStore implements ICycleStore {
const projectId = this.rootStore.app.router.projectId;
if (!projectId || !this.fetchedMap[projectId]) return null;
let draftCycles = Object.values(this.cycleMap ?? {}).filter(
(c) => c.project_id === projectId && !c.start_date && !c.end_date
(c) => c.project_id === projectId && !c.start_date && !c.end_date && !c?.archived_at
);
draftCycles = sortBy(draftCycles, [(c) => c.sort_order]);
const draftCycleIds = draftCycles.map((c) => c.id);
@ -191,6 +206,20 @@ export class CycleStore implements ICycleStore {
return activeCycle || null;
}
/**
* returns all archived cycle ids for a project
*/
get currentProjectArchivedCycleIds() {
const projectId = this.rootStore.app.router.projectId;
if (!projectId || !this.fetchedMap[projectId]) return null;
let archivedCycles = Object.values(this.cycleMap ?? {}).filter(
(c) => c.project_id === projectId && !!c.archived_at
);
archivedCycles = sortBy(archivedCycles, [(c) => c.sort_order]);
const archivedCycleIds = archivedCycles.map((c) => c.id);
return archivedCycleIds;
}
/**
* @description returns filtered cycle ids based on display filters and filters
* @param {TCycleDisplayFilters} displayFilters
@ -204,6 +233,7 @@ export class CycleStore implements ICycleStore {
let cycles = Object.values(this.cycleMap ?? {}).filter(
(c) =>
c.project_id === projectId &&
!c.archived_at &&
c.name.toLowerCase().includes(searchQuery.toLowerCase()) &&
shouldFilterCycle(c, filters ?? {})
);
@ -225,6 +255,7 @@ export class CycleStore implements ICycleStore {
let cycles = Object.values(this.cycleMap ?? {}).filter(
(c) =>
c.project_id === projectId &&
!c.archived_at &&
c.status.toLowerCase() === "completed" &&
c.name.toLowerCase().includes(searchQuery.toLowerCase()) &&
shouldFilterCycle(c, filters ?? {})
@ -234,6 +265,27 @@ export class CycleStore implements ICycleStore {
return cycleIds;
});
/**
* @description returns filtered archived cycle ids based on display filters and filters
* @param {string} projectId
* @returns {string[] | null}
*/
getFilteredArchivedCycleIds = computedFn((projectId: string) => {
const filters = this.rootStore.cycleFilter.getArchivedFiltersByProjectId(projectId);
const searchQuery = this.rootStore.cycleFilter.archivedCyclesSearchQuery;
if (!this.fetchedMap[projectId]) return null;
let cycles = Object.values(this.cycleMap ?? {}).filter(
(c) =>
c.project_id === projectId &&
!!c.archived_at &&
c.name.toLowerCase().includes(searchQuery.toLowerCase()) &&
shouldFilterCycle(c, filters ?? {})
);
cycles = sortBy(cycles, [(c) => !c.start_date]);
const cycleIds = cycles.map((c) => c.id);
return cycleIds;
});
/**
* @description returns cycle details by cycle id
* @param cycleId
@ -264,7 +316,7 @@ export class CycleStore implements ICycleStore {
getProjectCycleIds = computedFn((projectId: string): string[] | null => {
if (!this.fetchedMap[projectId]) return null;
let cycles = Object.values(this.cycleMap ?? {}).filter((c) => c.project_id === projectId);
let cycles = Object.values(this.cycleMap ?? {}).filter((c) => c.project_id === projectId && !c?.archived_at);
cycles = sortBy(cycles, [(c) => c.sort_order]);
const cycleIds = cycles.map((c) => c.id);
return cycleIds || null;
@ -321,6 +373,31 @@ export class CycleStore implements ICycleStore {
}
};
/**
* @description fetches archived cycles for a project
* @param workspaceSlug
* @param projectId
* @returns
*/
fetchArchivedCycles = async (workspaceSlug: string, projectId: string) => {
this.loader = true;
return await this.cycleArchiveService
.getArchivedCycles(workspaceSlug, projectId)
.then((response) => {
runInAction(() => {
response.forEach((cycle) => {
set(this.cycleMap, [cycle.id], cycle);
});
this.loader = false;
});
return response;
})
.catch(() => {
this.loader = false;
return undefined;
});
};
/**
* @description fetches active cycle for a project
* @param workspaceSlug
@ -452,4 +529,48 @@ export class CycleStore implements ICycleStore {
throw error;
}
};
/**
* @description archives a cycle
* @param workspaceSlug
* @param projectId
* @param cycleId
* @returns
*/
archiveCycle = async (workspaceSlug: string, projectId: string, cycleId: string) => {
const cycleDetails = this.getCycleById(cycleId);
if (cycleDetails?.archived_at) return;
await this.cycleArchiveService
.archiveCycle(workspaceSlug, projectId, cycleId)
.then((response) => {
runInAction(() => {
set(this.cycleMap, [cycleId, "archived_at"], response.archived_at);
});
})
.catch((error) => {
console.error("Failed to archive cycle in cycle store", error);
});
};
/**
* @description restores a cycle
* @param workspaceSlug
* @param projectId
* @param cycleId
* @returns
*/
restoreCycle = async (workspaceSlug: string, projectId: string, cycleId: string) => {
const cycleDetails = this.getCycleById(cycleId);
if (!cycleDetails?.archived_at) return;
await this.cycleArchiveService
.restoreCycle(workspaceSlug, projectId, cycleId)
.then(() => {
runInAction(() => {
set(this.cycleMap, [cycleId, "archived_at"], null);
});
})
.catch((error) => {
console.error("Failed to restore cycle in cycle store", error);
});
};
}

View file

@ -1,33 +1,39 @@
import set from "lodash/set";
import { action, computed, observable, makeObservable, runInAction, reaction } from "mobx";
import { computedFn } from "mobx-utils";
import set from "lodash/set";
// types
import { TCycleDisplayFilters, TCycleFilters, TCycleFiltersByState } from "@plane/types";
// store
import { RootStore } from "@/store/root.store";
import { TCycleDisplayFilters, TCycleFilters } from "@plane/types";
export interface ICycleFilterStore {
// observables
displayFilters: Record<string, TCycleDisplayFilters>;
filters: Record<string, TCycleFilters>;
filters: Record<string, TCycleFiltersByState>;
searchQuery: string;
archivedCyclesSearchQuery: string;
// computed
currentProjectDisplayFilters: TCycleDisplayFilters | undefined;
currentProjectFilters: TCycleFilters | undefined;
currentProjectArchivedFilters: TCycleFilters | undefined;
// computed functions
getDisplayFiltersByProjectId: (projectId: string) => TCycleDisplayFilters | undefined;
getFiltersByProjectId: (projectId: string) => TCycleFilters | undefined;
getArchivedFiltersByProjectId: (projectId: string) => TCycleFilters | undefined;
// actions
updateDisplayFilters: (projectId: string, displayFilters: TCycleDisplayFilters) => void;
updateFilters: (projectId: string, filters: TCycleFilters) => void;
updateFilters: (projectId: string, filters: TCycleFilters, state?: keyof TCycleFiltersByState) => void;
updateSearchQuery: (query: string) => void;
clearAllFilters: (projectId: string) => void;
updateArchivedCyclesSearchQuery: (query: string) => void;
clearAllFilters: (projectId: string, state?: keyof TCycleFiltersByState) => void;
}
export class CycleFilterStore implements ICycleFilterStore {
// observables
displayFilters: Record<string, TCycleDisplayFilters> = {};
filters: Record<string, TCycleFilters> = {};
filters: Record<string, TCycleFiltersByState> = {};
searchQuery: string = "";
archivedCyclesSearchQuery: string = "";
// root store
rootStore: RootStore;
@ -37,13 +43,16 @@ export class CycleFilterStore implements ICycleFilterStore {
displayFilters: observable,
filters: observable,
searchQuery: observable.ref,
archivedCyclesSearchQuery: observable.ref,
// computed
currentProjectDisplayFilters: computed,
currentProjectFilters: computed,
currentProjectArchivedFilters: computed,
// actions
updateDisplayFilters: action,
updateFilters: action,
updateSearchQuery: action,
updateArchivedCyclesSearchQuery: action,
clearAllFilters: action,
});
// root store
@ -73,7 +82,16 @@ export class CycleFilterStore implements ICycleFilterStore {
get currentProjectFilters() {
const projectId = this.rootStore.app.router.projectId;
if (!projectId) return;
return this.filters[projectId];
return this.filters[projectId]?.default ?? {};
}
/**
* @description get archived filters of the current project
*/
get currentProjectArchivedFilters() {
const projectId = this.rootStore.app.router.projectId;
if (!projectId) return;
return this.filters[projectId].archived;
}
/**
@ -86,7 +104,13 @@ export class CycleFilterStore implements ICycleFilterStore {
* @description get filters of a project by projectId
* @param {string} projectId
*/
getFiltersByProjectId = computedFn((projectId: string) => this.filters[projectId]);
getFiltersByProjectId = computedFn((projectId: string) => this.filters[projectId]?.default ?? {});
/**
* @description get archived filters of a project by projectId
* @param {string} projectId
*/
getArchivedFiltersByProjectId = computedFn((projectId: string) => this.filters[projectId].archived);
/**
* @description initialize display filters and filters of a project
@ -99,7 +123,10 @@ export class CycleFilterStore implements ICycleFilterStore {
active_tab: displayFilters?.active_tab || "active",
layout: displayFilters?.layout || "list",
};
this.filters[projectId] = this.filters[projectId] ?? {};
this.filters[projectId] = this.filters[projectId] ?? {
default: {},
archived: {},
};
});
};
@ -121,10 +148,10 @@ export class CycleFilterStore implements ICycleFilterStore {
* @param {string} projectId
* @param {TCycleFilters} filters
*/
updateFilters = (projectId: string, filters: TCycleFilters) => {
updateFilters = (projectId: string, filters: TCycleFilters, state: keyof TCycleFiltersByState = "default") => {
runInAction(() => {
Object.keys(filters).forEach((key) => {
set(this.filters, [projectId, key], filters[key as keyof TCycleFilters]);
set(this.filters, [projectId, state, key], filters[key as keyof TCycleFilters]);
});
});
};
@ -135,13 +162,19 @@ export class CycleFilterStore implements ICycleFilterStore {
*/
updateSearchQuery = (query: string) => (this.searchQuery = query);
/**
* @description update archived search query
* @param {string} query
*/
updateArchivedCyclesSearchQuery = (query: string) => (this.archivedCyclesSearchQuery = query);
/**
* @description clear all filters of a project
* @param {string} projectId
*/
clearAllFilters = (projectId: string) => {
clearAllFilters = (projectId: string, state: keyof TCycleFiltersByState = "default") => {
runInAction(() => {
this.filters[projectId] = {};
this.filters[projectId][state] = {};
});
};
}

View file

@ -2,14 +2,16 @@ import set from "lodash/set";
import sortBy from "lodash/sortBy";
import { action, computed, observable, makeObservable, runInAction } from "mobx";
import { computedFn } from "mobx-utils";
// services
import { ModuleService } from "@/services/module.service";
import { ProjectService } from "@/services/project";
// types
import { IModule, ILinkDetails } from "@plane/types";
// helpers
import { orderModules, shouldFilterModule } from "@/helpers/module.helper";
// types
// services
import { ModuleService } from "@/services/module.service";
import { ModuleArchiveService } from "@/services/module_archive.service";
import { ProjectService } from "@/services/project";
// store
import { RootStore } from "@/store/root.store";
import { IModule, ILinkDetails } from "@plane/types";
export interface IModuleStore {
//Loaders
@ -19,8 +21,10 @@ export interface IModuleStore {
moduleMap: Record<string, IModule>;
// computed
projectModuleIds: string[] | null;
projectArchivedModuleIds: string[] | null;
// computed actions
getFilteredModuleIds: (projectId: string) => string[] | null;
getFilteredArchivedModuleIds: (projectId: string) => string[] | null;
getModuleById: (moduleId: string) => IModule | null;
getModuleNameById: (moduleId: string) => string;
getProjectModuleIds: (projectId: string) => string[] | null;
@ -28,6 +32,7 @@ export interface IModuleStore {
// fetch
fetchWorkspaceModules: (workspaceSlug: string) => Promise<IModule[]>;
fetchModules: (workspaceSlug: string, projectId: string) => Promise<undefined | IModule[]>;
fetchArchivedModules: (workspaceSlug: string, projectId: string) => Promise<undefined | IModule[]>;
fetchModuleDetails: (workspaceSlug: string, projectId: string, moduleId: string) => Promise<IModule>;
// crud
createModule: (workspaceSlug: string, projectId: string, data: Partial<IModule>) => Promise<IModule>;
@ -55,6 +60,9 @@ export interface IModuleStore {
// favorites
addModuleToFavorites: (workspaceSlug: string, projectId: string, moduleId: string) => Promise<void>;
removeModuleFromFavorites: (workspaceSlug: string, projectId: string, moduleId: string) => Promise<void>;
// archive
archiveModule: (workspaceSlug: string, projectId: string, moduleId: string) => Promise<void>;
restoreModule: (workspaceSlug: string, projectId: string, moduleId: string) => Promise<void>;
}
export class ModulesStore implements IModuleStore {
@ -68,6 +76,7 @@ export class ModulesStore implements IModuleStore {
// services
projectService;
moduleService;
moduleArchiveService;
constructor(_rootStore: RootStore) {
makeObservable(this, {
@ -77,9 +86,11 @@ export class ModulesStore implements IModuleStore {
fetchedMap: observable,
// computed
projectModuleIds: computed,
projectArchivedModuleIds: computed,
// actions
fetchWorkspaceModules: action,
fetchModules: action,
fetchArchivedModules: action,
fetchModuleDetails: action,
createModule: action,
updateModuleDetails: action,
@ -89,6 +100,8 @@ export class ModulesStore implements IModuleStore {
deleteModuleLink: action,
addModuleToFavorites: action,
removeModuleFromFavorites: action,
archiveModule: action,
restoreModule: action,
});
this.rootStore = _rootStore;
@ -96,6 +109,7 @@ export class ModulesStore implements IModuleStore {
// services
this.projectService = new ProjectService();
this.moduleService = new ModuleService();
this.moduleArchiveService = new ModuleArchiveService();
}
// computed
@ -105,12 +119,24 @@ export class ModulesStore implements IModuleStore {
get projectModuleIds() {
const projectId = this.rootStore.app.router.projectId;
if (!projectId || !this.fetchedMap[projectId]) return null;
let projectModules = Object.values(this.moduleMap).filter((m) => m.project_id === projectId);
let projectModules = Object.values(this.moduleMap).filter((m) => m.project_id === projectId && !m?.archived_at);
projectModules = sortBy(projectModules, [(m) => m.sort_order]);
const projectModuleIds = projectModules.map((m) => m.id);
return projectModuleIds || null;
}
/**
* get all archived module ids for the current project
*/
get projectArchivedModuleIds() {
const projectId = this.rootStore.app.router.projectId;
if (!projectId || !this.fetchedMap[projectId]) return null;
let archivedModules = Object.values(this.moduleMap).filter((m) => m.project_id === projectId && !!m?.archived_at);
archivedModules = sortBy(archivedModules, [(m) => m.sort_order]);
const projectModuleIds = archivedModules.map((m) => m.id);
return projectModuleIds || null;
}
/**
* @description returns filtered module ids based on display filters and filters
* @param {TModuleDisplayFilters} displayFilters
@ -125,6 +151,29 @@ export class ModulesStore implements IModuleStore {
let modules = Object.values(this.moduleMap ?? {}).filter(
(m) =>
m.project_id === projectId &&
!m.archived_at &&
m.name.toLowerCase().includes(searchQuery.toLowerCase()) &&
shouldFilterModule(m, displayFilters ?? {}, filters ?? {})
);
modules = orderModules(modules, displayFilters?.order_by);
const moduleIds = modules.map((m) => m.id);
return moduleIds;
});
/**
* @description returns filtered archived module ids based on display filters and filters
* @param {string} projectId
* @returns {string[] | null}
*/
getFilteredArchivedModuleIds = computedFn((projectId: string) => {
const displayFilters = this.rootStore.moduleFilter.getDisplayFiltersByProjectId(projectId);
const filters = this.rootStore.moduleFilter.getArchivedFiltersByProjectId(projectId);
const searchQuery = this.rootStore.moduleFilter.archivedModulesSearchQuery;
if (!this.fetchedMap[projectId]) return null;
let modules = Object.values(this.moduleMap ?? {}).filter(
(m) =>
m.project_id === projectId &&
!!m.archived_at &&
m.name.toLowerCase().includes(searchQuery.toLowerCase()) &&
shouldFilterModule(m, displayFilters ?? {}, filters ?? {})
);
@ -154,7 +203,7 @@ export class ModulesStore implements IModuleStore {
getProjectModuleIds = computedFn((projectId: string) => {
if (!this.fetchedMap[projectId]) return null;
let projectModules = Object.values(this.moduleMap).filter((m) => m.project_id === projectId);
let projectModules = Object.values(this.moduleMap).filter((m) => m.project_id === projectId && !m.archived_at);
projectModules = sortBy(projectModules, [(m) => m.sort_order]);
const projectModuleIds = projectModules.map((m) => m.id);
return projectModuleIds;
@ -200,6 +249,31 @@ export class ModulesStore implements IModuleStore {
}
};
/**
* @description fetch all archived modules
* @param workspaceSlug
* @param projectId
* @returns IModule[]
*/
fetchArchivedModules = async (workspaceSlug: string, projectId: string) => {
this.loader = true;
return await this.moduleArchiveService
.getArchivedModules(workspaceSlug, projectId)
.then((response) => {
runInAction(() => {
response.forEach((module) => {
set(this.moduleMap, [module.id], { ...this.moduleMap[module.id], ...module });
});
this.loader = false;
});
return response;
})
.catch(() => {
this.loader = false;
return undefined;
});
};
/**
* @description fetch module details
* @param workspaceSlug
@ -386,4 +460,48 @@ export class ModulesStore implements IModuleStore {
});
}
};
/**
* @description archives a module
* @param workspaceSlug
* @param projectId
* @param moduleId
* @returns
*/
archiveModule = async (workspaceSlug: string, projectId: string, moduleId: string) => {
const moduleDetails = this.getModuleById(moduleId);
if (moduleDetails?.archived_at) return;
await this.moduleArchiveService
.archiveModule(workspaceSlug, projectId, moduleId)
.then((response) => {
runInAction(() => {
set(this.moduleMap, [moduleId, "archived_at"], response.archived_at);
});
})
.catch((error) => {
console.error("Failed to archive module in module store", error);
});
};
/**
* @description restores a module
* @param workspaceSlug
* @param projectId
* @param moduleId
* @returns
*/
restoreModule = async (workspaceSlug: string, projectId: string, moduleId: string) => {
const moduleDetails = this.getModuleById(moduleId);
if (!moduleDetails?.archived_at) return;
await this.moduleArchiveService
.restoreModule(workspaceSlug, projectId, moduleId)
.then(() => {
runInAction(() => {
set(this.moduleMap, [moduleId, "archived_at"], null);
});
})
.catch((error) => {
console.error("Failed to restore module in module store", error);
});
};
}

View file

@ -1,33 +1,39 @@
import set from "lodash/set";
import { action, computed, observable, makeObservable, runInAction, reaction } from "mobx";
import { computedFn } from "mobx-utils";
import set from "lodash/set";
// types
import { TModuleDisplayFilters, TModuleFilters, TModuleFiltersByState } from "@plane/types";
// store
import { RootStore } from "@/store/root.store";
import { TModuleDisplayFilters, TModuleFilters } from "@plane/types";
export interface IModuleFilterStore {
// observables
displayFilters: Record<string, TModuleDisplayFilters>;
filters: Record<string, TModuleFilters>;
filters: Record<string, TModuleFiltersByState>;
searchQuery: string;
archivedModulesSearchQuery: string;
// computed
currentProjectDisplayFilters: TModuleDisplayFilters | undefined;
currentProjectFilters: TModuleFilters | undefined;
currentProjectArchivedFilters: TModuleFilters | undefined;
// computed functions
getDisplayFiltersByProjectId: (projectId: string) => TModuleDisplayFilters | undefined;
getFiltersByProjectId: (projectId: string) => TModuleFilters | undefined;
getArchivedFiltersByProjectId: (projectId: string) => TModuleFilters | undefined;
// actions
updateDisplayFilters: (projectId: string, displayFilters: TModuleDisplayFilters) => void;
updateFilters: (projectId: string, filters: TModuleFilters) => void;
updateFilters: (projectId: string, filters: TModuleFilters, state?: keyof TModuleFiltersByState) => void;
updateSearchQuery: (query: string) => void;
clearAllFilters: (projectId: string) => void;
updateArchivedModulesSearchQuery: (query: string) => void;
clearAllFilters: (projectId: string, state?: keyof TModuleFiltersByState) => void;
}
export class ModuleFilterStore implements IModuleFilterStore {
// observables
displayFilters: Record<string, TModuleDisplayFilters> = {};
filters: Record<string, TModuleFilters> = {};
filters: Record<string, TModuleFiltersByState> = {};
searchQuery: string = "";
archivedModulesSearchQuery: string = "";
// root store
rootStore: RootStore;
@ -37,13 +43,16 @@ export class ModuleFilterStore implements IModuleFilterStore {
displayFilters: observable,
filters: observable,
searchQuery: observable.ref,
archivedModulesSearchQuery: observable.ref,
// computed
currentProjectDisplayFilters: computed,
currentProjectFilters: computed,
currentProjectArchivedFilters: computed,
// actions
updateDisplayFilters: action,
updateFilters: action,
updateSearchQuery: action,
updateArchivedModulesSearchQuery: action,
clearAllFilters: action,
});
// root store
@ -73,7 +82,16 @@ export class ModuleFilterStore implements IModuleFilterStore {
get currentProjectFilters() {
const projectId = this.rootStore.app.router.projectId;
if (!projectId) return;
return this.filters[projectId];
return this.filters[projectId]?.default ?? {};
}
/**
* @description get archived filters of the current project
*/
get currentProjectArchivedFilters() {
const projectId = this.rootStore.app.router.projectId;
if (!projectId) return;
return this.filters[projectId].archived;
}
/**
@ -86,7 +104,13 @@ export class ModuleFilterStore implements IModuleFilterStore {
* @description get filters of a project by projectId
* @param {string} projectId
*/
getFiltersByProjectId = computedFn((projectId: string) => this.filters[projectId]);
getFiltersByProjectId = computedFn((projectId: string) => this.filters[projectId]?.default ?? {});
/**
* @description get archived filters of a project by projectId
* @param {string} projectId
*/
getArchivedFiltersByProjectId = computedFn((projectId: string) => this.filters[projectId].archived);
/**
* @description initialize display filters and filters of a project
@ -100,7 +124,10 @@ export class ModuleFilterStore implements IModuleFilterStore {
layout: displayFilters?.layout || "list",
order_by: displayFilters?.order_by || "name",
};
this.filters[projectId] = this.filters[projectId] ?? {};
this.filters[projectId] = this.filters[projectId] ?? {
default: {},
archived: {},
};
});
};
@ -122,10 +149,10 @@ export class ModuleFilterStore implements IModuleFilterStore {
* @param {string} projectId
* @param {TModuleFilters} filters
*/
updateFilters = (projectId: string, filters: TModuleFilters) => {
updateFilters = (projectId: string, filters: TModuleFilters, state: keyof TModuleFiltersByState = "default") => {
runInAction(() => {
Object.keys(filters).forEach((key) => {
set(this.filters, [projectId, key], filters[key as keyof TModuleFilters]);
set(this.filters, [projectId, state, key], filters[key as keyof TModuleFilters]);
});
});
};
@ -136,13 +163,19 @@ export class ModuleFilterStore implements IModuleFilterStore {
*/
updateSearchQuery = (query: string) => (this.searchQuery = query);
/**
* @description update archived search query
* @param {string} query
*/
updateArchivedModulesSearchQuery = (query: string) => (this.archivedModulesSearchQuery = query);
/**
* @description clear all filters of a project
* @param {string} projectId
*/
clearAllFilters = (projectId: string) => {
clearAllFilters = (projectId: string, state: keyof TModuleFiltersByState = "default") => {
runInAction(() => {
this.filters[projectId] = {};
this.filters[projectId][state] = {};
});
};
}