diff --git a/web/components/project/card-list.tsx b/web/components/project/card-list.tsx new file mode 100644 index 000000000..0aff18b37 --- /dev/null +++ b/web/components/project/card-list.tsx @@ -0,0 +1,66 @@ +import { FC } from "react"; +import { observer } from "mobx-react-lite"; +// lib +import { useMobxStore } from "lib/mobx/store-provider"; +// components +import { ProjectCard } from "components/project"; +import { Loader, EmptyState } from "components/ui"; +// images +import emptyProject from "public/empty-state/project.svg"; +// icons +import { Plus } from "lucide-react"; + +export interface IProjectCardList { + workspaceSlug: string; +} + +export const ProjectCardList: FC = observer((props) => { + const { workspaceSlug } = props; + // store + const { project: projectStore } = useMobxStore(); + + const projects = workspaceSlug ? projectStore.projects[workspaceSlug.toString()] : null; + + if (!projects) { + return ( + + + + + + + + + ); + } + + return ( + <> + {projects.length > 0 ? ( +
+
+ {projectStore.searchedProjects.map((project) => ( + + ))} +
+
+ ) : ( + , + text: "New Project", + onClick: () => { + const e = new KeyboardEvent("keydown", { + key: "p", + }); + document.dispatchEvent(e); + }, + }} + /> + )} + + ); +}); diff --git a/web/components/project/single-project-card.tsx b/web/components/project/card.tsx similarity index 89% rename from web/components/project/single-project-card.tsx rename to web/components/project/card.tsx index cf771d532..0e8997da3 100644 --- a/web/components/project/single-project-card.tsx +++ b/web/components/project/card.tsx @@ -1,18 +1,16 @@ -import React from "react"; - +import React, { useState } from "react"; import { useRouter } from "next/router"; import Link from "next/link"; - import { mutate } from "swr"; - +import { observer } from "mobx-react-lite"; +// icons +import { CalendarDaysIcon, LinkIcon, PencilIcon, PlusIcon, StarIcon, TrashIcon } from "@heroicons/react/24/outline"; // services import projectService from "services/project.service"; // hooks import useToast from "hooks/use-toast"; // ui import { CustomMenu, Tooltip } from "components/ui"; -// icons -import { CalendarDaysIcon, LinkIcon, PencilIcon, PlusIcon, StarIcon, TrashIcon } from "@heroicons/react/24/outline"; // helpers import { renderShortDateWithYearFormat } from "helpers/date-time.helper"; import { copyTextToClipboard, truncateText } from "helpers/string.helper"; @@ -21,18 +19,23 @@ import { renderEmoji } from "helpers/emoji.helper"; import type { IProject } from "types"; // fetch-keys import { PROJECTS_LIST } from "constants/fetch-keys"; +// components +import { DeleteProjectModal, JoinProjectModal } from "components/project"; export type ProjectCardProps = { project: IProject; - setToJoinProject: (id: string | null) => void; - setDeleteProject: (id: string | null) => void; }; -export const SingleProjectCard: React.FC = ({ project, setToJoinProject, setDeleteProject }) => { +export const ProjectCard: React.FC = observer((props) => { + const { project } = props; + // router const router = useRouter(); const { workspaceSlug } = router.query; - + // toast const { setToastAlert } = useToast(); + // states + const [deleteProjectModalOpen, setDeleteProjectModal] = useState(false); + const [joinProjectModalOpen, setJoinProjectModal] = useState(false); const isOwner = project.member_role === 20; const isMember = project.member_role === 15; @@ -105,6 +108,22 @@ export const SingleProjectCard: React.FC = ({ project, setToJo return ( <> + {/* Delete Project Modal */} + setDeleteProjectModal(false)} + /> + {workspaceSlug && ( + setJoinProjectModal(false)} + /> + )} + + {/* Card Information */} ); -}; +}); diff --git a/web/components/project/index.ts b/web/components/project/index.ts index 81bdd5a19..92d047312 100644 --- a/web/components/project/index.ts +++ b/web/components/project/index.ts @@ -3,10 +3,12 @@ export * from "./delete-project-modal"; export * from "./sidebar-list"; export * from "./settings-sidebar"; export * from "./single-integration-card"; -export * from "./single-project-card"; export * from "./sidebar-list-item"; export * from "./leave-project-modal"; export * from "./member-select"; export * from "./members-select"; export * from "./label-select"; export * from "./priority-select"; +export * from "./card-list"; +export * from "./card"; +export * from "./join-project-modal"; diff --git a/web/components/project/join-project-modal.tsx b/web/components/project/join-project-modal.tsx index 3128a0a42..984fafa85 100644 --- a/web/components/project/join-project-modal.tsx +++ b/web/components/project/join-project-modal.tsx @@ -1,26 +1,32 @@ -import React, { useState } from "react"; - -// headless ui +import { useState, Fragment } from "react"; import { Transition, Dialog } from "@headlessui/react"; // ui import { PrimaryButton, SecondaryButton } from "components/ui"; // types import type { IProject } from "types"; +// lib +import { useMobxStore } from "lib/mobx/store-provider"; // type type TJoinProjectModalProps = { - data?: IProject; - onClose: () => void; - onJoin: () => Promise; + isOpen: boolean; + workspaceSlug: string; + project: IProject; + handleClose: () => void; }; -export const JoinProjectModal: React.FC = ({ onClose, onJoin, data }) => { +export const JoinProjectModal: React.FC = (props) => { + const { handleClose, isOpen, project, workspaceSlug } = props; + // store + const { project: projectStore } = useMobxStore(); + // states const [isJoiningLoading, setIsJoiningLoading] = useState(false); const handleJoin = () => { setIsJoiningLoading(true); - onJoin() + projectStore + .joinProject(workspaceSlug, [project.id]) .then(() => { setIsJoiningLoading(false); handleClose(); @@ -30,15 +36,11 @@ export const JoinProjectModal: React.FC = ({ onClose, on }); }; - const handleClose = () => { - onClose(); - }; - return ( - + = ({ onClose, on
= ({ onClose, on >
- + Join Project?

- Are you sure you want to join{" "} - {data?.name}? + Are you sure you want to join {project?.name}?

diff --git a/web/components/project/sidebar-list.tsx b/web/components/project/sidebar-list.tsx index 77e9f6e2d..ce6d22b88 100644 --- a/web/components/project/sidebar-list.tsx +++ b/web/components/project/sidebar-list.tsx @@ -34,12 +34,11 @@ export const ProjectSidebarList: FC = observer(() => { // swr useSWR( workspaceSlug ? "PROJECTS_LIST" : null, - workspaceSlug ? () => workspaceStore.getWorkspaceProjects(workspaceSlug?.toString()) : null + workspaceSlug ? () => projectStore.fetchProjects(workspaceSlug?.toString()) : null ); // states const [isFavoriteProjectCreate, setIsFavoriteProjectCreate] = useState(false); const [isProjectModalOpen, setIsProjectModalOpen] = useState(false); - const [deleteProjectModal, setDeleteProjectModal] = useState(false); const [isScrolled, setIsScrolled] = useState(false); // scroll animation state @@ -48,8 +47,8 @@ export const ProjectSidebarList: FC = observer(() => { const { user } = useUserAuth(); const { setToastAlert } = useToast(); - const joinedProjects = workspaceSlug && workspaceStore.workspaceJoinedProjects; - const favoriteProjects = workspaceSlug && workspaceStore.workspaceFavoriteProjects; + const joinedProjects = workspaceSlug && projectStore.joinedProjects; + const favoriteProjects = workspaceSlug && projectStore.favoriteProjects; const orderedJoinedProjects: IProject[] | undefined = joinedProjects ? orderArrayBy(joinedProjects, "sort_order", "ascending") diff --git a/web/pages/[workspaceSlug]/projects/index.tsx b/web/pages/[workspaceSlug]/projects/index.tsx index 5c671e32a..a6baebc53 100644 --- a/web/pages/[workspaceSlug]/projects/index.tsx +++ b/web/pages/[workspaceSlug]/projects/index.tsx @@ -1,9 +1,7 @@ import React, { useState } from "react"; - import { useRouter } from "next/router"; - import { mutate } from "swr"; - +import type { NextPage } from "next"; // services import projectService from "services/project.service"; // hooks @@ -12,48 +10,26 @@ import useWorkspaces from "hooks/use-workspaces"; import useUserAuth from "hooks/use-user-auth"; // layouts import { WorkspaceAuthorizationLayout } from "layouts/auth-layout"; -// components -import { JoinProjectModal } from "components/project/join-project-modal"; -import { DeleteProjectModal, SingleProjectCard } from "components/project"; // ui -import { EmptyState, Icon, Loader, PrimaryButton } from "components/ui"; +import { Icon, PrimaryButton } from "components/ui"; import { Breadcrumbs, BreadcrumbItem } from "components/breadcrumbs"; // icons import { PlusIcon } from "@heroicons/react/24/outline"; -// images -import emptyProject from "public/empty-state/project.svg"; -// types -import type { NextPage } from "next"; -// fetch-keys -import { PROJECT_MEMBERS } from "constants/fetch-keys"; // helper import { truncateText } from "helpers/string.helper"; -// types -import { IProject } from "types"; +// lib +import { useMobxStore } from "lib/mobx/store-provider"; +// components +import { ProjectCardList } from "components/project"; const ProjectsPage: NextPage = () => { // router const router = useRouter(); const { workspaceSlug } = router.query; - - const [query, setQuery] = useState(""); - - const { user } = useUserAuth(); + // store + const { project: projectStore } = useMobxStore(); // context data const { activeWorkspace } = useWorkspaces(); - const { projects, mutateProjects } = useProjects(); - // states - const [deleteProject, setDeleteProject] = useState(null); - const [selectedProjectToJoin, setSelectedProjectToJoin] = useState(null); - - const filteredProjectList = - query === "" - ? projects - : projects?.filter( - (project) => - project.name.toLowerCase().includes(query.toLowerCase()) || - project.identifier.toLowerCase().includes(query.toLowerCase()) - ); return ( { setQuery(e.target.value)} + value={projectStore.searchQuery} + onChange={(e) => projectStore.setSearchQuery(e.target.value)} placeholder="Search" />
@@ -90,83 +66,7 @@ const ProjectsPage: NextPage = () => {
} > - item.id === selectedProjectToJoin)} - onClose={() => setSelectedProjectToJoin(null)} - onJoin={async () => { - const project = projects?.find((item) => item.id === selectedProjectToJoin); - if (!project) return; - - await projectService - .joinProject(workspaceSlug as string, { - project_ids: [project.id], - }) - .then(async () => { - mutate(PROJECT_MEMBERS(project.id)); - mutateProjects( - (prevData) => - (prevData ?? []).map((p) => ({ - ...p, - is_member: p.id === project.id ? true : p.is_member, - })), - false - ); - setSelectedProjectToJoin(null); - }) - .catch(() => { - setSelectedProjectToJoin(null); - }); - }} - /> - setDeleteProject(null)} - data={projects?.find((item) => item.id === deleteProject) ?? null} - user={user} - /> - {filteredProjectList ? ( -
- {filteredProjectList.length > 0 ? ( -
-
- {filteredProjectList.map((project) => ( - - ))} -
-
- ) : ( - , - text: "New Project", - onClick: () => { - const e = new KeyboardEvent("keydown", { - key: "p", - }); - document.dispatchEvent(e); - }, - }} - /> - )} -
- ) : ( - - - - - - - - - )} + {workspaceSlug && } ); }; diff --git a/web/services/project.service.ts b/web/services/project.service.ts index 841043f6e..a33604308 100644 --- a/web/services/project.service.ts +++ b/web/services/project.service.ts @@ -124,8 +124,8 @@ export class ProjectService extends APIService { }); } - async joinProject(workspaceSlug: string, data: any): Promise { - return this.post(`/api/workspaces/${workspaceSlug}/projects/join/`, data) + async joinProject(workspaceSlug: string, project_ids: string[]): Promise { + return this.post(`/api/workspaces/${workspaceSlug}/projects/join/`, { project_ids }) .then((response) => response?.data) .catch((error) => { throw error?.response?.data; diff --git a/web/store/cycles.ts b/web/store/cycles.ts index d072ea3c5..afde42bb9 100644 --- a/web/store/cycles.ts +++ b/web/store/cycles.ts @@ -5,12 +5,17 @@ import { RootStore } from "./root"; import { ProjectService } from "services/project.service"; import { IssueService } from "services/issue.service"; import { ICycle } from "types"; +import { CycleService } from "services/cycles.service"; export interface ICycleStore { loader: boolean; error: any | null; cycles: { + [project_id: string]: ICycle[]; + }; + + cycle_details: { [cycle_id: string]: ICycle; }; } @@ -20,6 +25,10 @@ class CycleStore implements ICycleStore { error: any | null = null; cycles: { + [project_id: string]: ICycle[]; + } = {}; + + cycle_details: { [cycle_id: string]: ICycle; } = {}; @@ -28,6 +37,7 @@ class CycleStore implements ICycleStore { // services projectService; issueService; + cycleService; constructor(_rootStore: RootStore) { makeObservable(this, { @@ -44,11 +54,37 @@ class CycleStore implements ICycleStore { this.rootStore = _rootStore; this.projectService = new ProjectService(); this.issueService = new IssueService(); + this.cycleService = new CycleService(); } // computed + get projectCycles() { + if (!this.rootStore.project.projectId) return null; + return this.cycles[this.rootStore.project.projectId] || null; + } // actions + fetchCycles = async (workspaceSlug: string, projectSlug: string) => { + try { + this.loader = true; + this.error = null; + + const cyclesResponse = await this.cycleService.getCyclesWithParams(workspaceSlug, projectSlug, "all"); + + runInAction(() => { + this.cycles = { + ...this.cycles, + [projectSlug]: 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; + } + }; } export default CycleStore; diff --git a/web/store/modules.ts b/web/store/modules.ts index c0ef31ee1..ab8137bba 100644 --- a/web/store/modules.ts +++ b/web/store/modules.ts @@ -3,15 +3,24 @@ import { action, computed, observable, makeObservable, runInAction } from "mobx" import { RootStore } from "./root"; // services import { ProjectService } from "services/project.service"; -import { IssueService } from "services/issue.service"; +import { ModuleService } from "services/modules.service"; +import { IModule } from "@/types"; export interface IModuleStore { loader: boolean; error: any | null; moduleId: string | null; + modules: { + [project_id: string]: IModule[]; + }; + module_details: { + [module_id: string]: IModule; + }; setModuleId: (moduleSlug: string) => void; + + fetchModules: (workspaceSlug: string, projectSlug: string) => void; } class ModuleStore implements IModuleStore { @@ -20,11 +29,19 @@ class ModuleStore implements IModuleStore { moduleId: string | null = null; + modules: { + [project_id: string]: IModule[]; + } = {}; + + module_details: { + [module_id: string]: IModule; + } = {}; + // root store rootStore; // services projectService; - issueService; + moduleService; constructor(_rootStore: RootStore) { makeObservable(this, { @@ -41,15 +58,41 @@ class ModuleStore implements IModuleStore { this.rootStore = _rootStore; this.projectService = new ProjectService(); - this.issueService = new IssueService(); + this.moduleService = new ModuleService(); } // computed + get projectModules() { + if (!this.rootStore.project.projectId) return null; + return this.modules[this.rootStore.project.projectId] || null; + } // actions setModuleId = (moduleSlug: string) => { this.moduleId = moduleSlug ?? null; }; + + fetchModules = async (workspaceSlug: string, projectSlug: string) => { + try { + this.loader = true; + this.error = null; + + const modulesResponse = await this.moduleService.getModules(workspaceSlug, projectSlug); + + runInAction(() => { + this.modules = { + ...this.modules, + [projectSlug]: modulesResponse, + }; + this.loader = false; + this.error = null; + }); + } catch (error) { + console.error("Failed to fetch modules list in project store", error); + this.loader = false; + this.error = error; + } + }; } export default ModuleStore; diff --git a/web/store/page.ts b/web/store/page.ts new file mode 100644 index 000000000..3c378896e --- /dev/null +++ b/web/store/page.ts @@ -0,0 +1,104 @@ +import { observable, action, computed, makeObservable, runInAction } from "mobx"; +// types +import { RootStore } from "./root"; +import { IProject, IIssueLabels, IProjectMember, IStateResponse, IState, IPage } from "types"; +// services +import { ProjectService } from "services/project.service"; +import { IssueService } from "services/issue.service"; +import { ProjectStateServices } from "services/project_state.service"; +import { CycleService } from "services/cycles.service"; +import { ModuleService } from "services/modules.service"; +import { ViewService } from "services/views.service"; +import { PageService } from "services/page.service"; + +export interface IPageStore { + loader: boolean; + error: any | null; + + pageId: string | null; + pages: { + [project_id: string]: IPage[]; + }; + page_details: { + [page_id: string]: IPage; + }; + + //computed + projectPages: IPage[]; + // actions + setPageId: (pageId: string) => void; + fetchPages: (workspaceSlug: string, projectSlug: string) => void; +} + +class PageStore implements IPageStore { + loader: boolean = false; + error: any | null = null; + + pageId: string | null = null; + pages: { + [project_id: string]: IPage[]; + } = {}; + page_details: { + [page_id: string]: IPage; + } = {}; + + // root store + rootStore; + // service + projectService; + pageService; + + constructor(_rootStore: RootStore) { + makeObservable(this, { + // observable + loader: observable, + error: observable, + + pageId: observable.ref, + pages: observable.ref, + + // computed + projectPages: computed, + // action + setPageId: action, + fetchPages: action, + }); + + this.rootStore = _rootStore; + this.projectService = new ProjectService(); + this.pageService = new PageService(); + } + + get projectPages() { + if (!this.rootStore.project.projectId) return []; + return this.pages?.[this.rootStore.project.projectId] || []; + } + + setPageId = (pageId: string) => { + this.pageId = pageId; + }; + + fetchPages = async (workspaceSlug: string, projectSlug: string) => { + try { + this.loader = true; + this.error = null; + + const pagesResponse = await this.pageService.getPagesWithParams(workspaceSlug, projectSlug, "all"); + + runInAction(() => { + this.pages = { + ...this.pages, + [projectSlug]: pagesResponse, + }; + this.loader = false; + this.error = null; + }); + } catch (error) { + console.error("Failed to fetch project pages in project store", error); + this.loader = false; + this.error = error; + } + }; +} + +export default PageStore; diff --git a/web/store/project.ts b/web/store/project.ts index 52bfbca75..1f5d8fdec 100644 --- a/web/store/project.ts +++ b/web/store/project.ts @@ -1,7 +1,7 @@ import { observable, action, computed, makeObservable, runInAction } from "mobx"; // types import { RootStore } from "./root"; -import { IProject, IIssueLabels, IProjectMember, IStateResponse, IState, ICycle, IModule, IView, IPage } from "types"; +import { IProject, IIssueLabels, IProjectMember, IStateResponse, IState } from "types"; // services import { ProjectService } from "services/project.service"; import { IssueService } from "services/issue.service"; @@ -15,9 +15,10 @@ export interface IProjectStore { loader: boolean; error: any | null; + searchQuery: string; projectId: string | null; - - projects: { + projects: { [key: string]: IProject[] }; + project_details: { [projectId: string]: IProject; // projectId: project Info } | null; states: { @@ -29,32 +30,26 @@ export interface IProjectStore { members: { [projectId: string]: IProjectMember[] | null; // project_id: members } | null; - cycles: { - [projectId: string]: ICycle[] | null; // project_id: cycles - } | null; - modules: { - [projectId: string]: IModule[] | null; // project_id: modules - } | null; - views: { - [projectId: string]: IView[] | null; // project_id: views - } | null; - pages: { - [projectId: string]: IPage[] | null; // project_id: pages - } | null; // computed + searchedProjects: IProject[]; projectStatesByGroups: IStateResponse | null; projectStates: IState[] | null; projectLabels: IIssueLabels[] | null; projectMembers: IProjectMember[] | null; + joinedProjects: IProject[]; + favoriteProjects: IProject[]; + // actions setProjectId: (projectId: string) => void; + setSearchQuery: (query: string) => void; getProjectStateById: (stateId: string) => IState | null; getProjectLabelById: (labelId: string) => IIssueLabels | null; getProjectMemberById: (memberId: string) => IProjectMember | null; + fetchProjects: (workspaceSlug: string) => Promise; fetchProjectStates: (workspaceSlug: string, projectSlug: string) => Promise; fetchProjectLabels: (workspaceSlug: string, projectSlug: string) => Promise; fetchProjectMembers: (workspaceSlug: string, projectSlug: string) => Promise; @@ -65,8 +60,7 @@ export interface IProjectStore { orderProjectsWithSortOrder: (sourceIndex: number, destinationIndex: number, projectId: string) => number; updateProjectView: (workspaceSlug: string, projectId: string, viewProps: any) => Promise; - handleProjectLeaveModal: (project: any | null) => void; - + joinProject: (workspaceSlug: string, projectIds: string[]) => Promise; leaveProject: (workspaceSlug: string, projectSlug: string) => Promise; deleteProject: (workspaceSlug: string, projectSlug: string) => Promise; } @@ -75,26 +69,12 @@ class ProjectStore implements IProjectStore { loader: boolean = false; error: any | null = null; - projectLeaveModal: boolean = false; - projectLeaveDetails: IProject | null = null; - + searchQuery: string = ""; projectId: string | null = null; - - projects: { + projects: { [workspaceSlug: string]: IProject[] } = {}; // workspace_id: project[] + project_details: { [key: string]: IProject; // project_id: project } | null = {}; - cycles: { - [key: string]: ICycle[]; // project_id: cycles - } = {}; - modules: { - [key: string]: IModule[]; // project_id: modules - } = {}; - views: { - [key: string]: IView[]; // project_id: views - } = {}; - pages: { - [key: string]: IPage[]; // project_id: pages - } = {}; states: { [key: string]: IStateResponse; // project_id: states } | null = {}; @@ -122,23 +102,27 @@ class ProjectStore implements IProjectStore { loader: observable, error: observable, + searchQuery: observable.ref, projectId: observable.ref, projects: observable.ref, + project_details: observable.ref, states: observable.ref, labels: observable.ref, members: observable.ref, - projectLeaveModal: observable, - projectLeaveDetails: observable.ref, - // computed + searchedProjects: computed, projectStatesByGroups: computed, projectStates: computed, projectLabels: computed, projectMembers: computed, + joinedProjects: computed, + favoriteProjects: computed, + // action setProjectId: action, + setSearchQuery: action, getProjectStateById: action, getProjectLabelById: action, @@ -153,8 +137,6 @@ class ProjectStore implements IProjectStore { orderProjectsWithSortOrder: action, updateProjectView: action, - - handleProjectLeaveModal: action, leaveProject: action, }); @@ -168,6 +150,30 @@ class ProjectStore implements IProjectStore { this.cycleService = new CycleService(); } + get searchedProjects() { + if (!this.rootStore.workspace.workspaceSlug) return []; + + const projects = this.projects[this.rootStore.workspace.workspaceSlug]; + + return this.searchQuery === "" + ? projects + : projects?.filter( + (project) => + project.name.toLowerCase().includes(this.searchQuery.toLowerCase()) || + project.identifier.toLowerCase().includes(this.searchQuery.toLowerCase()) + ); + } + + get joinedProjects() { + if (!this.rootStore.workspace.workspaceSlug) return []; + return this.projects?.[this.rootStore.workspace.workspaceSlug]?.filter((p) => p.is_member); + } + + get favoriteProjects() { + if (!this.rootStore.workspace.workspaceSlug) return []; + return this.projects?.[this.rootStore.workspace.workspaceSlug]?.filter((p) => p.is_favorite); + } + get projectStatesByGroups() { if (!this.projectId) return null; return this.states?.[this.projectId] || null; @@ -196,31 +202,34 @@ class ProjectStore implements IProjectStore { return this.members?.[this.projectId] || null; } - get projectCycles() { - if (!this.projectId) return null; - return this.cycles[this.projectId] || null; - } - - get projectModules() { - if (!this.projectId) return null; - return this.modules[this.projectId] || null; - } - - get projectViews() { - if (!this.projectId) return null; - return this.views[this.projectId] || null; - } - - get projectPages() { - if (!this.projectId) return null; - return this.pages[this.projectId] || null; - } - // actions setProjectId = (projectSlug: string) => { this.projectId = projectSlug ?? null; }; + setSearchQuery = (query: string) => { + this.searchQuery = query; + }; + + /** + * get Workspace projects using workspace slug + * @param workspaceSlug + * @returns + */ + fetchProjects = async (workspaceSlug: string) => { + try { + const projects = await this.projectService.getProjects(workspaceSlug, { is_favorite: "all" }); + runInAction(() => { + this.projects = { + ...this.projects, + [workspaceSlug]: projects, + }; + }); + } catch (error) { + console.log("Failed to fetch project from workspace store"); + } + }; + getProjectStateById = (stateId: string) => { if (!this.projectId) return null; const states = this.projectStates; @@ -314,99 +323,10 @@ class ProjectStore implements IProjectStore { } }; - fetchProjectCycles = async (workspaceSlug: string, projectSlug: string) => { - try { - this.loader = true; - this.error = null; - - const cyclesResponse = await this.cycleService.getCyclesWithParams(workspaceSlug, projectSlug, "all"); - - runInAction(() => { - this.cycles = { - ...this.cycles, - [projectSlug]: 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; - } - }; - - fetchProjectModules = async (workspaceSlug: string, projectSlug: string) => { - try { - this.loader = true; - this.error = null; - - const modulesResponse = await this.moduleService.getModules(workspaceSlug, projectSlug); - - runInAction(() => { - this.modules = { - ...this.modules, - [projectSlug]: modulesResponse, - }; - this.loader = false; - this.error = null; - }); - } catch (error) { - console.error("Failed to fetch modules list in project store", error); - this.loader = false; - this.error = error; - } - }; - - fetchProjectViews = async (workspaceSlug: string, projectSlug: string) => { - try { - this.loader = true; - this.error = null; - - const viewsResponse = await this.viewService.getViews(workspaceSlug, projectSlug); - - runInAction(() => { - this.views = { - ...this.views, - [projectSlug]: viewsResponse, - }; - this.loader = false; - this.error = null; - }); - } catch (error) { - console.error("Failed to fetch project views in project store", error); - this.loader = false; - this.error = error; - } - }; - - fetchProjectPages = async (workspaceSlug: string, projectSlug: string) => { - try { - this.loader = true; - this.error = null; - - const pagesResponse = await this.pageService.getPagesWithParams(workspaceSlug, projectSlug, "all"); - - runInAction(() => { - this.pages = { - ...this.pages, - [projectSlug]: pagesResponse, - }; - this.loader = false; - this.error = null; - }); - } catch (error) { - console.error("Failed to fetch project pages in project store", error); - this.loader = false; - this.error = error; - } - }; - addProjectToFavorites = async (workspaceSlug: string, projectId: string) => { try { const response = await this.projectService.addProjectToFavorites(workspaceSlug, projectId); - console.log("res", response); - await this.rootStore.workspace.getWorkspaceProjects(workspaceSlug); + await this.fetchProjects(workspaceSlug); return response; } catch (error) { console.log("Failed to add project to favorite"); @@ -416,8 +336,8 @@ class ProjectStore implements IProjectStore { removeProjectFromFavorites = async (workspaceSlug: string, projectId: string) => { try { - const response = this.projectService.removeProjectFromFavorites(workspaceSlug, projectId); - this.rootStore.workspace.getWorkspaceProjects(workspaceSlug); + const response = await this.projectService.removeProjectFromFavorites(workspaceSlug, projectId); + await this.fetchProjects(workspaceSlug); return response; } catch (error) { console.log("Failed to add project to favorite"); @@ -430,7 +350,7 @@ class ProjectStore implements IProjectStore { const workspaceSlug = this.rootStore.workspace.workspaceSlug; if (!workspaceSlug) return 0; - const projectsList = this.rootStore.workspace.projects[workspaceSlug] || []; + const projectsList = this.projects[workspaceSlug] || []; let updatedSortOrder = projectsList[sortIndex].sort_order; if (destinationIndex === 0) updatedSortOrder = (projectsList[0].sort_order as number) - 1000; @@ -451,8 +371,8 @@ class ProjectStore implements IProjectStore { ); runInAction(() => { - this.rootStore.workspace.projects = { - ...this.rootStore.workspace.projects, + this.projects = { + ...this.projects, [workspaceSlug]: updatedProjectsList, }; }); @@ -467,7 +387,8 @@ class ProjectStore implements IProjectStore { updateProjectView = async (workspaceSlug: string, projectId: string, viewProps: any) => { try { const response = await this.projectService.setProjectView(workspaceSlug, projectId, viewProps); - await this.rootStore.workspace.getWorkspaceProjects(workspaceSlug); + await this.fetchProjects(workspaceSlug); + return response; } catch (error) { console.log("Failed to update sort order of the projects"); @@ -475,13 +396,24 @@ class ProjectStore implements IProjectStore { } }; - handleProjectLeaveModal = (project: IProject | null = null) => { - if (project && project?.id) { - this.projectLeaveModal = !this.projectLeaveModal; - this.projectLeaveDetails = project; - } else { - this.projectLeaveModal = !this.projectLeaveModal; - this.projectLeaveDetails = null; + joinProject = async (workspaceSlug: string, projectIds: string[]) => { + try { + this.loader = true; + this.error = null; + + const response = await this.projectService.joinProject(workspaceSlug, projectIds); + await this.fetchProjects(workspaceSlug); + + runInAction(() => { + this.loader = false; + this.error = null; + }); + + return response; + } catch (error) { + this.loader = false; + this.error = error; + return error; } }; @@ -491,7 +423,7 @@ class ProjectStore implements IProjectStore { this.error = null; const response = await this.projectService.leaveProject(workspaceSlug, projectSlug, this.rootStore.user); - await this.rootStore.workspace.getWorkspaceProjects(workspaceSlug); + await this.fetchProjects(workspaceSlug); runInAction(() => { this.loader = false; @@ -509,7 +441,7 @@ class ProjectStore implements IProjectStore { deleteProject = async (workspaceSlug: string, projectId: string) => { try { await this.projectService.deleteProject(workspaceSlug, projectId, this.rootStore.user.currentUser); - await this.rootStore.workspace.getWorkspaceProjects(workspaceSlug); + await this.fetchProjects(workspaceSlug); } catch (error) { console.log("Failed to delete project from project store"); } diff --git a/web/store/views.ts b/web/store/views.ts index 0748a5456..019992cbc 100644 --- a/web/store/views.ts +++ b/web/store/views.ts @@ -4,14 +4,20 @@ import { RootStore } from "./root"; // services import { ProjectService } from "services/project.service"; import { IssueService } from "services/issue.service"; +import { ViewService } from "services/views.service"; export interface IViewStore { loader: boolean; error: any | null; viewId: string | null; + views: { + [project_id: string]: any[]; + }; setViewId: (viewSlug: string) => void; + + fetchViews: (workspaceSlug: string, projectSlug: string) => Promise; } class ViewStore implements IViewStore { @@ -19,12 +25,15 @@ class ViewStore implements IViewStore { error: any | null = null; viewId: string | null = null; + views: { + [project_id: string]: any[]; + } = {}; // root store rootStore; // services projectService; - issueService; + viewService; constructor(_rootStore: RootStore) { makeObservable(this, { @@ -32,24 +41,51 @@ class ViewStore implements IViewStore { error: observable.ref, viewId: observable.ref, + views: observable.ref, // computed - + projectViews: computed, // actions setViewId: action, }); this.rootStore = _rootStore; this.projectService = new ProjectService(); - this.issueService = new IssueService(); + this.viewService = new ViewService(); } // computed + get projectViews() { + if (!this.rootStore.project.projectId) return null; + return this.views[this.rootStore.project.projectId] || null; + } // actions setViewId = (viewSlug: string) => { this.viewId = viewSlug ?? null; }; + + fetchViews = async (workspaceSlug: string, projectId: string) => { + try { + this.loader = true; + this.error = null; + + const viewsResponse = await this.viewService.getViews(workspaceSlug, projectId); + + runInAction(() => { + this.views = { + ...this.views, + [projectId]: viewsResponse, + }; + this.loader = false; + this.error = null; + }); + } catch (error) { + console.error("Failed to fetch project views in project store", error); + this.loader = false; + this.error = error; + } + }; } export default ViewStore; diff --git a/web/store/workspace.ts b/web/store/workspace.ts index 86f35a5ab..0c2d48d72 100644 --- a/web/store/workspace.ts +++ b/web/store/workspace.ts @@ -12,19 +12,15 @@ export interface IWorkspaceStore { error: any | null; // observables workspaces: IWorkspace[]; - projects: { [key: string]: IProject[] }; labels: { [key: string]: IIssueLabels[] } | {}; // workspace_id: labels[] workspaceSlug: string | null; // computed currentWorkspace: IWorkspace | null; workspaceLabels: IIssueLabels[]; - workspaceJoinedProjects: IProject[]; - workspaceFavoriteProjects: IProject[]; // actions setWorkspaceSlug: (workspaceSlug: string) => void; getWorkspaceBySlug: (workspaceSlug: string) => IWorkspace | null; getWorkspaceLabelById: (workspaceSlug: string, labelId: string) => IIssueLabels | null; - getWorkspaceProjects: (workspaceSlug: string) => void; fetchWorkspaces: () => Promise; fetchWorkspaceLabels: (workspaceSlug: string) => Promise; } @@ -52,17 +48,13 @@ class WorkspaceStore implements IWorkspaceStore { workspaces: observable.ref, labels: observable.ref, workspaceSlug: observable.ref, - projects: observable.ref, // computed currentWorkspace: computed, workspaceLabels: computed, - workspaceJoinedProjects: computed, - workspaceFavoriteProjects: computed, // actions setWorkspaceSlug: action, getWorkspaceBySlug: action, getWorkspaceLabelById: action, - getWorkspaceProjects: action, fetchWorkspaces: action, fetchWorkspaceLabels: action, }); @@ -90,16 +82,6 @@ class WorkspaceStore implements IWorkspaceStore { return _labels && Object.keys(_labels).length > 0 ? _labels : []; } - get workspaceJoinedProjects() { - if (!this.workspaceSlug) return []; - return this.projects?.[this.workspaceSlug]?.filter((p) => p.is_member); - } - - get workspaceFavoriteProjects() { - if (!this.workspaceSlug) return []; - return this.projects?.[this.workspaceSlug]?.filter((p) => p.is_favorite); - } - /** * set workspace slug in the store * @param workspaceSlug @@ -113,25 +95,6 @@ class WorkspaceStore implements IWorkspaceStore { */ getWorkspaceBySlug = (workspaceSlug: string) => this.workspaces.find((w) => w.slug == workspaceSlug) || null; - /** - * get Workspace projects using workspace slug - * @param workspaceSlug - * @returns - */ - getWorkspaceProjects = async (workspaceSlug: string) => { - try { - const projects = await this.projectService.getProjects(workspaceSlug, { is_favorite: "all" }); - runInAction(() => { - this.projects = { - ...this.projects, - [workspaceSlug]: projects, - }; - }); - } catch (error) { - console.log("Failed to fetch project from workspace store"); - } - }; - /** * get workspace label information from the workspace labels * @param labelId @@ -188,50 +151,6 @@ class WorkspaceStore implements IWorkspaceStore { this.error = error; } }; - - // getMyIssuesAsync = async (workspaceId: string, fetchFilterToggle: boolean = true) => { - // try { - // this.loader = true; - // this.error = null; - - // if (fetchFilterToggle) await this.rootStore.issueFilters.getWorkspaceMyIssuesFilters(workspaceId); - // const filteredParams = this.rootStore.issueFilters.getComputedFilters( - // workspaceId, - // null, - // null, - // null, - // null, - // "my_issues" - // ); - // const issuesResponse = await this.userService.userIssues(workspaceId, filteredParams); - - // if (issuesResponse) { - // const _issueResponse: any = { - // ...this.issues, - // [workspaceId]: { - // ...this?.issues[workspaceId], - // my_issues: { - // ...this?.issues[workspaceId]?.my_issues, - // [this.rootStore?.issueFilters?.userFilters?.display_filters?.layout as string]: issuesResponse, - // }, - // }, - // }; - - // runInAction(() => { - // this.issues = _issueResponse; - // this.loader = false; - // this.error = null; - // }); - // } - - // return issuesResponse; - // } catch (error) { - // console.warn("error in fetching the my issues", error); - // this.loader = false; - // this.error = null; - // return error; - // } - // }; } export default WorkspaceStore;