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:
parent
4d1b5adfc4
commit
061be85a5d
72 changed files with 2429 additions and 682 deletions
|
|
@ -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);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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] = {};
|
||||
});
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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] = {};
|
||||
});
|
||||
};
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue