[WEB-447] feat: projects archive. (#4014)
* dev: project archive response * feat: projects archive. * dev: response changes for cycle and module * chore: status message changed * chore: update clear all applied display filters logic. * style: archived project card UI update. * chore: archive/ restore taost message update. * fix: clear all applied display filter logic. * chore: project empty state update to handle archived projects. * chore: minor typo fix in cycles and modules archive. * chore: close cycle/ module overview sidebar if it's already open when clicked on overview button. * chore: optimize current workspace applied display filter logic. * chore: update all `archived_at` type from `Date` to `string`. --------- Co-authored-by: NarayanBavisetti <narayan3119@gmail.com>
This commit is contained in:
parent
9642b761b7
commit
231fd52992
31 changed files with 749 additions and 162 deletions
|
|
@ -3,12 +3,15 @@ import sortBy from "lodash/sortBy";
|
|||
import { observable, action, computed, makeObservable, runInAction } from "mobx";
|
||||
import { computedFn } from "mobx-utils";
|
||||
// types
|
||||
import { IssueLabelService, IssueService } from "@/services/issue";
|
||||
import { ProjectService, ProjectStateService } from "@/services/project";
|
||||
import { IProject } from "@plane/types";
|
||||
import { RootStore } from "../root.store";
|
||||
// helpers
|
||||
import { orderProjects, shouldFilterProject } from "@/helpers/project.helper";
|
||||
// services
|
||||
import { IssueLabelService, IssueService } from "@/services/issue";
|
||||
import { ProjectService, ProjectStateService, ProjectArchiveService } from "@/services/project";
|
||||
// store
|
||||
import { RootStore } from "../root.store";
|
||||
|
||||
export interface IProjectStore {
|
||||
// observables
|
||||
projectMap: {
|
||||
|
|
@ -17,6 +20,8 @@ export interface IProjectStore {
|
|||
// computed
|
||||
filteredProjectIds: string[] | undefined;
|
||||
workspaceProjectIds: string[] | undefined;
|
||||
archivedProjectIds: string[] | undefined;
|
||||
totalProjectIds: string[] | undefined;
|
||||
joinedProjectIds: string[];
|
||||
favoriteProjectIds: string[];
|
||||
currentProjectDetails: IProject | undefined;
|
||||
|
|
@ -35,6 +40,9 @@ export interface IProjectStore {
|
|||
createProject: (workspaceSlug: string, data: Partial<IProject>) => Promise<IProject>;
|
||||
updateProject: (workspaceSlug: string, projectId: string, data: Partial<IProject>) => Promise<IProject>;
|
||||
deleteProject: (workspaceSlug: string, projectId: string) => Promise<void>;
|
||||
// archive actions
|
||||
archiveProject: (workspaceSlug: string, projectId: string) => Promise<void>;
|
||||
restoreProject: (workspaceSlug: string, projectId: string) => Promise<void>;
|
||||
}
|
||||
|
||||
export class ProjectStore implements IProjectStore {
|
||||
|
|
@ -46,6 +54,7 @@ export class ProjectStore implements IProjectStore {
|
|||
rootStore: RootStore;
|
||||
// service
|
||||
projectService;
|
||||
projectArchiveService;
|
||||
issueLabelService;
|
||||
issueService;
|
||||
stateService;
|
||||
|
|
@ -57,6 +66,8 @@ export class ProjectStore implements IProjectStore {
|
|||
// computed
|
||||
filteredProjectIds: computed,
|
||||
workspaceProjectIds: computed,
|
||||
archivedProjectIds: computed,
|
||||
totalProjectIds: computed,
|
||||
currentProjectDetails: computed,
|
||||
joinedProjectIds: computed,
|
||||
favoriteProjectIds: computed,
|
||||
|
|
@ -76,6 +87,7 @@ export class ProjectStore implements IProjectStore {
|
|||
this.rootStore = _rootStore;
|
||||
// services
|
||||
this.projectService = new ProjectService();
|
||||
this.projectArchiveService = new ProjectArchiveService();
|
||||
this.issueService = new IssueService();
|
||||
this.issueLabelService = new IssueLabelService();
|
||||
this.stateService = new ProjectStateService();
|
||||
|
|
@ -109,11 +121,42 @@ export class ProjectStore implements IProjectStore {
|
|||
get workspaceProjectIds() {
|
||||
const workspaceDetails = this.rootStore.workspaceRoot.currentWorkspace;
|
||||
if (!workspaceDetails) return;
|
||||
const workspaceProjects = Object.values(this.projectMap).filter((p) => p.workspace === workspaceDetails.id);
|
||||
const workspaceProjects = Object.values(this.projectMap).filter(
|
||||
(p) => p.workspace === workspaceDetails.id && !p.archived_at
|
||||
);
|
||||
const projectIds = workspaceProjects.map((p) => p.id);
|
||||
return projectIds ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns archived project IDs belong to current workspace.
|
||||
*/
|
||||
get archivedProjectIds() {
|
||||
const currentWorkspace = this.rootStore.workspaceRoot.currentWorkspace;
|
||||
if (!currentWorkspace) return;
|
||||
|
||||
let projects = Object.values(this.projectMap ?? {});
|
||||
projects = sortBy(projects, "archived_at");
|
||||
|
||||
const projectIds = projects
|
||||
.filter((project) => project.workspace === currentWorkspace.id && !!project.archived_at)
|
||||
.map((project) => project.id);
|
||||
return projectIds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns total project IDs belong to the current workspace
|
||||
*/
|
||||
// workspaceProjectIds + archivedProjectIds
|
||||
get totalProjectIds() {
|
||||
const currentWorkspace = this.rootStore.workspaceRoot.currentWorkspace;
|
||||
if (!currentWorkspace) return;
|
||||
|
||||
const workspaceProjects = this.workspaceProjectIds ?? [];
|
||||
const archivedProjects = this.archivedProjectIds ?? [];
|
||||
return [...workspaceProjects, ...archivedProjects];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns current project details
|
||||
*/
|
||||
|
|
@ -133,7 +176,7 @@ export class ProjectStore implements IProjectStore {
|
|||
projects = sortBy(projects, "sort_order");
|
||||
|
||||
const projectIds = projects
|
||||
.filter((project) => project.workspace === currentWorkspace.id && project.is_member)
|
||||
.filter((project) => project.workspace === currentWorkspace.id && project.is_member && !project.archived_at)
|
||||
.map((project) => project.id);
|
||||
return projectIds;
|
||||
}
|
||||
|
|
@ -149,7 +192,10 @@ export class ProjectStore implements IProjectStore {
|
|||
projects = sortBy(projects, "created_at");
|
||||
|
||||
const projectIds = projects
|
||||
.filter((project) => project.workspace === currentWorkspace.id && project.is_member && project.is_favorite)
|
||||
.filter(
|
||||
(project) =>
|
||||
project.workspace === currentWorkspace.id && project.is_member && project.is_favorite && !project.archived_at
|
||||
)
|
||||
.map((project) => project.id);
|
||||
return projectIds;
|
||||
}
|
||||
|
|
@ -348,4 +394,48 @@ export class ProjectStore implements IProjectStore {
|
|||
this.fetchProjects(workspaceSlug);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Archives a project from specific workspace and updates it in the store
|
||||
* @param workspaceSlug
|
||||
* @param projectId
|
||||
* @returns Promise<void>
|
||||
*/
|
||||
archiveProject = async (workspaceSlug: string, projectId: string) => {
|
||||
await this.projectArchiveService
|
||||
.archiveProject(workspaceSlug, projectId)
|
||||
.then((response) => {
|
||||
runInAction(() => {
|
||||
set(this.projectMap, [projectId, "archived_at"], response.archived_at);
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log("Failed to archive project from project store");
|
||||
this.fetchProjects(workspaceSlug);
|
||||
this.fetchProjectDetails(workspaceSlug, projectId);
|
||||
throw error;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Restores a project from specific workspace and updates it in the store
|
||||
* @param workspaceSlug
|
||||
* @param projectId
|
||||
* @returns Promise<void>
|
||||
*/
|
||||
restoreProject = async (workspaceSlug: string, projectId: string) => {
|
||||
await this.projectArchiveService
|
||||
.restoreProject(workspaceSlug, projectId)
|
||||
.then(() => {
|
||||
runInAction(() => {
|
||||
set(this.projectMap, [projectId, "archived_at"], null);
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log("Failed to restore project from project store");
|
||||
this.fetchProjects(workspaceSlug);
|
||||
this.fetchProjectDetails(workspaceSlug, projectId);
|
||||
throw error;
|
||||
});
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
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 { TProjectDisplayFilters, TProjectFilters, TProjectAppliedDisplayFilterKeys } from "@plane/types";
|
||||
// store
|
||||
import { RootStore } from "@/store/root.store";
|
||||
import { TProjectDisplayFilters, TProjectFilters } from "@plane/types";
|
||||
|
||||
export interface IProjectFilterStore {
|
||||
// observables
|
||||
|
|
@ -12,6 +13,7 @@ export interface IProjectFilterStore {
|
|||
searchQuery: string;
|
||||
// computed
|
||||
currentWorkspaceDisplayFilters: TProjectDisplayFilters | undefined;
|
||||
currentWorkspaceAppliedDisplayFilters: TProjectAppliedDisplayFilterKeys[] | undefined;
|
||||
currentWorkspaceFilters: TProjectFilters | undefined;
|
||||
// computed functions
|
||||
getDisplayFiltersByWorkspaceSlug: (workspaceSlug: string) => TProjectDisplayFilters | undefined;
|
||||
|
|
@ -21,6 +23,7 @@ export interface IProjectFilterStore {
|
|||
updateFilters: (workspaceSlug: string, filters: TProjectFilters) => void;
|
||||
updateSearchQuery: (query: string) => void;
|
||||
clearAllFilters: (workspaceSlug: string) => void;
|
||||
clearAllAppliedDisplayFilters: (workspaceSlug: string) => void;
|
||||
}
|
||||
|
||||
export class ProjectFilterStore implements IProjectFilterStore {
|
||||
|
|
@ -39,12 +42,14 @@ export class ProjectFilterStore implements IProjectFilterStore {
|
|||
searchQuery: observable.ref,
|
||||
// computed
|
||||
currentWorkspaceDisplayFilters: computed,
|
||||
currentWorkspaceAppliedDisplayFilters: computed,
|
||||
currentWorkspaceFilters: computed,
|
||||
// actions
|
||||
updateDisplayFilters: action,
|
||||
updateFilters: action,
|
||||
updateSearchQuery: action,
|
||||
clearAllFilters: action,
|
||||
clearAllAppliedDisplayFilters: action,
|
||||
});
|
||||
// root store
|
||||
this.rootStore = _rootStore;
|
||||
|
|
@ -67,6 +72,21 @@ export class ProjectFilterStore implements IProjectFilterStore {
|
|||
return this.displayFilters[workspaceSlug];
|
||||
}
|
||||
|
||||
/**
|
||||
* @description get project state applied display filter of the current workspace
|
||||
* @returns {TProjectAppliedDisplayFilterKeys[] | undefined} // An array of keys of applied display filters
|
||||
*/
|
||||
// TODO: Figure out a better approach for this
|
||||
get currentWorkspaceAppliedDisplayFilters() {
|
||||
const workspaceSlug = this.rootStore.app.router.workspaceSlug;
|
||||
if (!workspaceSlug) return;
|
||||
const displayFilters = this.displayFilters[workspaceSlug];
|
||||
return Object.keys(displayFilters).filter(
|
||||
(key): key is TProjectAppliedDisplayFilterKeys =>
|
||||
["my_projects", "archived_projects"].includes(key) && !!displayFilters[key as keyof TProjectDisplayFilters]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description get filters of the current workspace
|
||||
*/
|
||||
|
|
@ -143,4 +163,17 @@ export class ProjectFilterStore implements IProjectFilterStore {
|
|||
this.filters[workspaceSlug] = {};
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @description clear project display filters of a workspace
|
||||
* @param {string} workspaceSlug
|
||||
*/
|
||||
clearAllAppliedDisplayFilters = (workspaceSlug: string) => {
|
||||
runInAction(() => {
|
||||
if (!this.currentWorkspaceAppliedDisplayFilters) return;
|
||||
this.currentWorkspaceAppliedDisplayFilters.forEach((key) => {
|
||||
set(this.displayFilters, [workspaceSlug, key], false);
|
||||
});
|
||||
});
|
||||
};
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue