From 960f170fd472b2ed223a1a9e56d7ed45bd40ab43 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal Date: Fri, 15 Dec 2023 14:58:40 +0530 Subject: [PATCH] chore: implement the new label root store --- .../actions/issue-actions/change-state.tsx | 12 +- web/components/core/activity.tsx | 13 +- web/components/headers/cycle-issues.tsx | 33 ++- web/components/headers/global-issues.tsx | 25 +- web/components/headers/module-issues.tsx | 35 +-- .../headers/project-archived-issues.tsx | 23 +- .../headers/project-draft-issues.tsx | 16 +- .../headers/project-view-issues.tsx | 30 ++- web/components/inbox/main-content.tsx | 28 +-- .../inbox/modals/create-issue-modal.tsx | 33 ++- .../inbox/modals/delete-issue-modal.tsx | 18 +- .../issue-layouts/empty-states/cycle.tsx | 20 +- .../issue-layouts/empty-states/module.tsx | 32 ++- .../filters/applied-filters/filters-list.tsx | 12 +- .../issue-layouts/properties/labels.tsx | 25 +- web/components/issues/main-content.tsx | 39 ++- web/components/issues/modal.tsx | 42 ++-- .../issues/peek-overview/properties.tsx | 16 +- .../issues/sidebar-select/label.tsx | 10 +- web/components/issues/sidebar.tsx | 23 +- web/components/issues/sub-issues/root.tsx | 14 +- web/components/labels/create-label-modal.tsx | 26 +- .../labels/create-update-label-inline.tsx | 28 +-- web/components/labels/labels-list-modal.tsx | 23 +- .../labels/project-setting-label-item.tsx | 24 +- .../labels/project-setting-label-list.tsx | 24 +- web/components/project/member-list-item.tsx | 16 +- web/components/project/member-list.tsx | 23 +- .../project/send-project-invitation-modal.tsx | 24 +- web/components/views/form.tsx | 22 +- web/components/views/view-list-item.tsx | 18 +- web/components/views/views-list.tsx | 24 +- .../workspace/settings/members-list-item.tsx | 18 +- web/hooks/store/use-label.ts | 6 +- web/layouts/auth-layout/project-wrapper.tsx | 6 +- web/layouts/auth-layout/workspace-wrapper.tsx | 6 +- web/store/label/index.ts | 47 ++++ web/store/label/project-label.store.ts | 231 ++++++++++++++++++ web/store/label/workspace-label.store.ts | 63 +++++ web/store/project/project.store.ts | 22 +- web/store/root.store.ts | 6 +- web/store/workspace/index.ts | 4 +- 42 files changed, 768 insertions(+), 392 deletions(-) create mode 100644 web/store/label/index.ts create mode 100644 web/store/label/project-label.store.ts create mode 100644 web/store/label/workspace-label.store.ts diff --git a/web/components/command-palette/actions/issue-actions/change-state.tsx b/web/components/command-palette/actions/issue-actions/change-state.tsx index 0ce05bd7b..a57d57cbb 100644 --- a/web/components/command-palette/actions/issue-actions/change-state.tsx +++ b/web/components/command-palette/actions/issue-actions/change-state.tsx @@ -1,9 +1,9 @@ import { useRouter } from "next/router"; import { observer } from "mobx-react-lite"; -// mobx store -import { useMobxStore } from "lib/mobx/store-provider"; -// cmdk import { Command } from "cmdk"; +// hooks +import { useMobxStore } from "lib/mobx/store-provider"; +import { useProjectState } from "hooks/store"; // ui import { Spinner, StateGroupIcon } from "@plane/ui"; // icons @@ -18,14 +18,14 @@ type Props = { export const ChangeIssueState: React.FC = observer((props) => { const { closePalette, issue } = props; - + // router const router = useRouter(); const { workspaceSlug, projectId } = router.query; - + // store hooks const { - projectState: { projectStates }, projectIssues: { updateIssue }, } = useMobxStore(); + const { projectStates } = useProjectState(); const submitChanges = async (formData: Partial) => { if (!workspaceSlug || !projectId || !issue) return; diff --git a/web/components/core/activity.tsx b/web/components/core/activity.tsx index 37f8d2626..85b4a8bb9 100644 --- a/web/components/core/activity.tsx +++ b/web/components/core/activity.tsx @@ -1,7 +1,8 @@ import { useRouter } from "next/router"; +import { useEffect } from "react"; import { observer } from "mobx-react-lite"; -// mobx store -import { useMobxStore } from "lib/mobx/store-provider"; +// hooks +import { useLabel } from "hooks/store"; // hook import useEstimateOption from "hooks/use-estimate-option"; // icons @@ -27,7 +28,6 @@ import { renderShortDateWithYearFormat } from "helpers/date-time.helper"; import { capitalizeFirstLetter } from "helpers/string.helper"; // types import { IIssueActivity } from "types"; -import { useEffect } from "react"; const IssueLink = ({ activity }: { activity: IIssueActivity }) => { const router = useRouter(); @@ -74,11 +74,10 @@ const UserLink = ({ activity }: { activity: IIssueActivity }) => { }; const LabelPill = observer(({ labelId, workspaceSlug }: { labelId: string; workspaceSlug: string }) => { + // store hooks const { - workspace: { labels, fetchWorkspaceLabels }, - } = useMobxStore(); - - const workspaceLabels = labels[workspaceSlug]; + workspaceLabel: { workspaceLabels, fetchWorkspaceLabels }, + } = useLabel(); useEffect(() => { if (!workspaceLabels) fetchWorkspaceLabels(workspaceSlug); diff --git a/web/components/headers/cycle-issues.tsx b/web/components/headers/cycle-issues.tsx index 9c821116c..f54a66d15 100644 --- a/web/components/headers/cycle-issues.tsx +++ b/web/components/headers/cycle-issues.tsx @@ -1,9 +1,9 @@ import { useCallback, useState } from "react"; import { useRouter } from "next/router"; import { observer } from "mobx-react-lite"; -// mobx store -import { useMobxStore } from "lib/mobx/store-provider"; // hooks +import { useMobxStore } from "lib/mobx/store-provider"; +import { useApplication, useLabel, useProject, useProjectState, useUser } from "hooks/store"; import useLocalStorage from "hooks/use-local-storage"; // components import { DisplayFiltersSelection, FiltersDropdown, FilterSelection, LayoutSelection } from "components/issues"; @@ -25,27 +25,34 @@ import { EFilterType } from "store_legacy/issues/types"; import { EProjectStore } from "store_legacy/command-palette.store"; export const CycleIssuesHeader: React.FC = observer(() => { + // states const [analyticsModal, setAnalyticsModal] = useState(false); - + // router const router = useRouter(); const { workspaceSlug, projectId, cycleId } = router.query as { workspaceSlug: string; projectId: string; cycleId: string; }; - + // store hooks const { cycle: cycleStore, projectIssuesFilter: projectIssueFiltersStore, - project: { currentProjectDetails }, projectMember: { projectMembers }, - projectLabel: { projectLabels }, - projectState: projectStateStore, - commandPalette: commandPaletteStore, - trackEvent: { setTrackElement }, cycleIssuesFilter: { issueFilters, updateFilters }, - user: { currentProjectRole }, } = useMobxStore(); + const { + commandPalette: { toggleCreateIssueModal }, + eventTracker: { setTrackElement }, + } = useApplication(); + const { + membership: { currentProjectRole }, + } = useUser(); + const { currentProjectDetails } = useProject(); + const { projectStates } = useProjectState(); + const { + project: { projectLabels }, + } = useLabel(); const activeLayout = projectIssueFiltersStore.issueFilters?.displayFilters?.layout; @@ -180,9 +187,9 @@ export const CycleIssuesHeader: React.FC = observer(() => { layoutDisplayFiltersOptions={ activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined } - labels={projectLabels ?? undefined} + labels={projectLabels} members={projectMembers?.map((m) => m.member)} - states={projectStateStore.states?.[projectId ?? ""] ?? undefined} + states={projectStates} /> @@ -205,7 +212,7 @@ export const CycleIssuesHeader: React.FC = observer(() => { diff --git a/web/components/views/view-list-item.tsx b/web/components/views/view-list-item.tsx index 6299c4cdb..2443f7167 100644 --- a/web/components/views/view-list-item.tsx +++ b/web/components/views/view-list-item.tsx @@ -3,9 +3,9 @@ import Link from "next/link"; import { useRouter } from "next/router"; import { observer } from "mobx-react-lite"; import { LinkIcon, PencilIcon, StarIcon, TrashIcon } from "lucide-react"; -// mobx store -import { useMobxStore } from "lib/mobx/store-provider"; // hooks +import { useUser } from "hooks/store"; +import { useMobxStore } from "lib/mobx/store-provider"; import useToast from "hooks/use-toast"; // components import { CreateUpdateProjectViewModal, DeleteProjectViewModal } from "components/views"; @@ -25,19 +25,19 @@ type Props = { export const ProjectViewListItem: React.FC = observer((props) => { const { view } = props; - + // states const [createUpdateViewModal, setCreateUpdateViewModal] = useState(false); const [deleteViewModal, setDeleteViewModal] = useState(false); - + // router const router = useRouter(); const { workspaceSlug, projectId } = router.query; - + // toast alert const { setToastAlert } = useToast(); - + // store hooks + const { projectViews: projectViewsStore } = useMobxStore(); const { - projectViews: projectViewsStore, - user: { currentProjectRole }, - } = useMobxStore(); + membership: { currentProjectRole }, + } = useUser(); const handleAddToFavorites = () => { if (!workspaceSlug || !projectId) return; diff --git a/web/components/views/views-list.tsx b/web/components/views/views-list.tsx index d293bbe73..fca1954e4 100644 --- a/web/components/views/views-list.tsx +++ b/web/components/views/views-list.tsx @@ -1,8 +1,9 @@ import { useState } from "react"; import { useRouter } from "next/router"; import { observer } from "mobx-react-lite"; - -// mobx store +import { Plus, Search } from "lucide-react"; +// hooks +import { useApplication, useUser } from "hooks/store"; import { useMobxStore } from "lib/mobx/store-provider"; // components import { ProjectViewListItem } from "components/views"; @@ -11,22 +12,23 @@ import { NewEmptyState } from "components/common/new-empty-state"; import { Input, Loader } from "@plane/ui"; // assets import emptyView from "public/empty-state/empty_view.webp"; -// icons -import { Plus, Search } from "lucide-react"; // constants import { EUserWorkspaceRoles } from "constants/workspace"; export const ProjectViewsList = observer(() => { + // states const [query, setQuery] = useState(""); - + // router const router = useRouter(); const { projectId } = router.query; - + // store hooks + const { projectViews: projectViewsStore } = useMobxStore(); const { - projectViews: projectViewsStore, - commandPalette: commandPaletteStore, - user: { currentProjectRole }, - } = useMobxStore(); + commandPalette: { toggleCreateViewModal }, + } = useApplication(); + const { + membership: { currentProjectRole }, + } = useUser(); const viewsList = projectId ? projectViewsStore.viewsList[projectId.toString()] : undefined; @@ -79,7 +81,7 @@ export const ProjectViewsList = observer(() => { primaryButton={{ icon: , text: "Build your first view", - onClick: () => commandPaletteStore.toggleCreateViewModal(true), + onClick: () => toggleCreateViewModal(true), }} disabled={!isEditingAllowed} /> diff --git a/web/components/workspace/settings/members-list-item.tsx b/web/components/workspace/settings/members-list-item.tsx index b869d4d63..15ce80919 100644 --- a/web/components/workspace/settings/members-list-item.tsx +++ b/web/components/workspace/settings/members-list-item.tsx @@ -4,9 +4,9 @@ import { useRouter } from "next/router"; import { observer } from "mobx-react-lite"; import { mutate } from "swr"; import { ChevronDown, Dot, XCircle } from "lucide-react"; -// mobx store -import { useMobxStore } from "lib/mobx/store-provider"; // hooks +import { useUser } from "hooks/store"; +import { useMobxStore } from "lib/mobx/store-provider"; import useToast from "hooks/use-toast"; // components import { ConfirmWorkspaceMemberRemove } from "components/workspace"; @@ -35,17 +35,21 @@ type Props = { export const WorkspaceMembersListItem: FC = observer((props) => { const { member } = props; + // states + const [removeMemberModal, setRemoveMemberModal] = useState(false); // router const router = useRouter(); const { workspaceSlug } = router.query; - // store + // store hooks const { workspaceMember: { removeMember, updateMember, updateMemberInvitation, deleteWorkspaceInvitation }, - user: { currentWorkspaceMemberInfo, currentWorkspaceRole, currentUser, currentUserSettings, leaveWorkspace }, } = useMobxStore(); - // states - const [removeMemberModal, setRemoveMemberModal] = useState(false); - // hooks + const { + currentUser, + currentUserSettings, + membership: { currentWorkspaceMemberInfo, currentWorkspaceRole, leaveWorkspace }, + } = useUser(); + // toast alert const { setToastAlert } = useToast(); const handleLeaveWorkspace = async () => { diff --git a/web/hooks/store/use-label.ts b/web/hooks/store/use-label.ts index 142caaf05..8069a78e7 100644 --- a/web/hooks/store/use-label.ts +++ b/web/hooks/store/use-label.ts @@ -2,10 +2,10 @@ import { useContext } from "react"; // mobx store import { MobxStoreContext } from "lib/mobx/store-provider"; // types -import { ILabelStore } from "store/label.store"; +import { ILabelRootStore } from "store/label"; -export const useLabel = (): ILabelStore => { +export const useLabel = (): ILabelRootStore => { const context = useContext(MobxStoreContext); if (context === undefined) throw new Error("useMobxStore must be used within MobxStoreProvider"); - return context.label; + return context.labelRoot; }; diff --git a/web/layouts/auth-layout/project-wrapper.tsx b/web/layouts/auth-layout/project-wrapper.tsx index 0a3b1e39b..edc8e4675 100644 --- a/web/layouts/auth-layout/project-wrapper.tsx +++ b/web/layouts/auth-layout/project-wrapper.tsx @@ -3,6 +3,7 @@ import { useRouter } from "next/router"; import { observer } from "mobx-react-lite"; import useSWR from "swr"; // hooks +import { useApplication, useCycle, useLabel, useModule, useProjectState, useUser } from "hooks/store"; import { useMobxStore } from "lib/mobx/store-provider"; // components import { Spinner } from "@plane/ui"; @@ -10,7 +11,6 @@ import { JoinProject } from "components/auth-screens"; import { EmptyState } from "components/common"; // images import emptyProject from "public/empty-state/project.svg"; -import { useApplication, useCycle, useModule, useProjectState, useUser } from "hooks/store"; interface IProjectAuthWrapper { children: ReactNode; @@ -21,7 +21,6 @@ export const ProjectAuthWrapper: FC = observer((props) => { // store const { project: { fetchProjectDetails, workspaceProjects }, - projectLabel: { fetchProjectLabels }, projectMember: { fetchProjectMembers }, projectEstimates: { fetchProjectEstimates }, projectViews: { fetchAllViews }, @@ -36,6 +35,9 @@ export const ProjectAuthWrapper: FC = observer((props) => { const { fetchAllCycles } = useCycle(); const { fetchModules } = useModule(); const { fetchProjectStates } = useProjectState(); + const { + project: { fetchProjectLabels }, + } = useLabel(); // router const router = useRouter(); const { workspaceSlug, projectId } = router.query; diff --git a/web/layouts/auth-layout/workspace-wrapper.tsx b/web/layouts/auth-layout/workspace-wrapper.tsx index 251394d61..b73ca3a22 100644 --- a/web/layouts/auth-layout/workspace-wrapper.tsx +++ b/web/layouts/auth-layout/workspace-wrapper.tsx @@ -4,7 +4,7 @@ import Link from "next/link"; import useSWR from "swr"; import { observer } from "mobx-react-lite"; // hooks -import { useProject, useUser } from "hooks/store"; +import { useLabel, useProject, useUser } from "hooks/store"; import { useMobxStore } from "lib/mobx/store-provider"; // icons import { Button, Spinner } from "@plane/ui"; @@ -17,13 +17,15 @@ export const WorkspaceAuthWrapper: FC = observer((props) const { children } = props; // store hooks const { - workspace: { fetchWorkspaceLabels }, workspaceMember: { fetchWorkspaceMembers, fetchWorkspaceUserProjectsRole }, } = useMobxStore(); const { membership: { currentWorkspaceMemberInfo, hasPermissionToCurrentWorkspace, fetchUserWorkspaceInfo }, } = useUser(); const { fetchProjects } = useProject(); + const { + workspace: { fetchWorkspaceLabels }, + } = useLabel(); // router const router = useRouter(); const { workspaceSlug } = router.query; diff --git a/web/store/label/index.ts b/web/store/label/index.ts new file mode 100644 index 000000000..350667e6b --- /dev/null +++ b/web/store/label/index.ts @@ -0,0 +1,47 @@ +import { computed, observable, makeObservable } from "mobx"; +import { RootStore } from "../root.store"; +// types +import { IIssueLabel } from "types"; +import { IProjectLabelStore, ProjectLabelStore } from "./project-label.store"; +import { IWorkspaceLabelStore, WorkspaceLabelStore } from "./workspace-label.store"; + +export interface ILabelRootStore { + // observables + labelMap: Record; + // computed actions + getLabelById: (labelId: string) => IIssueLabel | null; + // sub-stores + project: IProjectLabelStore; + workspace: IWorkspaceLabelStore; +} + +export class LabelRootStore implements ILabelRootStore { + // observables + labelMap: Record = {}; + // root store + rootStore; + // sub-stores + project: IProjectLabelStore; + workspace: IWorkspaceLabelStore; + + constructor(_rootStore: RootStore) { + makeObservable(this, { + // observables + labelMap: observable, + // computed actions + getLabelById: computed, + }); + + // root store + this.rootStore = _rootStore; + // sub-stores + this.project = new ProjectLabelStore(_rootStore); + this.workspace = new WorkspaceLabelStore(_rootStore); + } + + /** + * get label info from the map of labels in the store using label id + * @param labelId + */ + getLabelById = (labelId: string): IIssueLabel | null => this.labelMap?.[labelId] || null; +} diff --git a/web/store/label/project-label.store.ts b/web/store/label/project-label.store.ts new file mode 100644 index 000000000..bfdfab001 --- /dev/null +++ b/web/store/label/project-label.store.ts @@ -0,0 +1,231 @@ +import { action, computed, makeObservable, runInAction } from "mobx"; +import { set } from "lodash"; +// services +import { IssueLabelService } from "services/issue"; +// helpers +import { buildTree } from "helpers/array.helper"; +// types +import { RootStore } from "store/root.store"; +import { IIssueLabel } from "types"; + +export interface IProjectLabelStore { + // computed + projectLabels: IIssueLabel[] | undefined; + projectLabelsTree: IIssueLabel[] | undefined; + // actions + fetchProjectLabels: (workspaceSlug: string, projectId: string) => Promise; + createLabel: (workspaceSlug: string, projectId: string, data: Partial) => Promise; + updateLabel: ( + workspaceSlug: string, + projectId: string, + labelId: string, + data: Partial + ) => Promise; + updateLabelPosition: ( + workspaceSlug: string, + projectId: string, + labelId: string, + parentId: string | null | undefined, + index: number, + isSameParent: boolean, + prevIndex: number | undefined + ) => Promise; + deleteLabel: (workspaceSlug: string, projectId: string, labelId: string) => Promise; +} + +export class ProjectLabelStore implements IProjectLabelStore { + // root store + rootStore; + // root store labelMap + labelMap: Record = {}; + // services + issueLabelService; + + constructor(_rootStore: RootStore) { + makeObservable(this, { + // computed + projectLabels: computed, + projectLabelsTree: computed, + // actions + fetchProjectLabels: action, + createLabel: action, + updateLabel: action, + updateLabelPosition: action, + deleteLabel: action, + }); + + // root store + this.rootStore = _rootStore; + this.labelMap = this.rootStore.labelRoot.labelMap; + // services + this.issueLabelService = new IssueLabelService(); + } + + /** + * Returns the labelMap belongs to a specific project + */ + get projectLabels() { + const projectId = this.rootStore.app.router.query?.projectId; + if (!projectId) return; + return Object.values(this.labelMap).filter((label) => label.project === projectId); + } + + /** + * Returns the labelMap in a tree format + */ + get projectLabelsTree() { + if (!this.projectLabels) return; + return buildTree(this.projectLabels); + } + + /** + * Fetches all the labelMap belongs to a specific project + * @param workspaceSlug + * @param projectId + * @returns Promise + */ + fetchProjectLabels = async (workspaceSlug: string, projectId: string) => { + const response = await this.issueLabelService.getProjectIssueLabels(workspaceSlug, projectId); + runInAction(() => { + response.forEach((label) => { + set(this.labelMap, [label.id], label); + }); + }); + return response; + }; + + /** + * Creates a new label for a specific project and add it to the store + * @param workspaceSlug + * @param projectId + * @param data + * @returns Promise + */ + createLabel = async (workspaceSlug: string, projectId: string, data: Partial) => { + const response = await this.issueLabelService.createIssueLabel(workspaceSlug, projectId, data); + + runInAction(() => { + set(this.labelMap, [response.id], response); + }); + return response; + }; + + /** + * Updates a label for a specific project and update it in the store + * @param workspaceSlug + * @param projectId + * @param labelId + * @param data + * @returns Promise + */ + updateLabel = async (workspaceSlug: string, projectId: string, labelId: string, data: Partial) => { + const originalLabel = this.labelMap[labelId]; + try { + runInAction(() => { + set(this.labelMap, [labelId], { ...this.labelMap[labelId], ...data }); + }); + + const response = await this.issueLabelService.patchIssueLabel(workspaceSlug, projectId, labelId, data); + return response; + } catch (error) { + console.log("Failed to update label from project store"); + runInAction(() => { + set(this.labelMap, [labelId], originalLabel); + }); + throw error; + } + }; + + /** + * updates the sort order of a label and updates the label information using API. + * @param workspaceSlug + * @param projectId + * @param labelId + * @param parentId + * @param index + * @param isSameParent + * @param prevIndex + * @returns + */ + updateLabelPosition = async ( + workspaceSlug: string, + projectId: string, + labelId: string, + parentId: string | null | undefined, + index: number, + isSameParent: boolean, + prevIndex: number | undefined + ) => { + const currLabel = this.labelMap?.[labelId]; + const labelTree = this.projectLabelsTree; + + let currentArray: IIssueLabel[]; + + if (!currLabel || !labelTree) return; + + const data: Partial = { parent: parentId }; + //find array in which the label is to be added + if (!parentId) currentArray = labelTree; + else currentArray = labelTree?.find((label) => label.id === parentId)?.children || []; + + //Add the array at the destination + if (isSameParent && prevIndex !== undefined) currentArray.splice(prevIndex, 1); + + currentArray.splice(index, 0, currLabel); + + //if currently adding to a new array, then let backend assign a sort order + if (currentArray.length > 1) { + let prevSortOrder: number | undefined, nextSortOrder: number | undefined; + + if (typeof currentArray[index - 1] !== "undefined") { + prevSortOrder = currentArray[index - 1].sort_order; + } + + if (typeof currentArray[index + 1] !== "undefined") { + nextSortOrder = currentArray[index + 1].sort_order; + } + + let sortOrder: number; + + //based on the next and previous labelMap calculate current sort order + if (prevSortOrder && nextSortOrder) { + sortOrder = (prevSortOrder + nextSortOrder) / 2; + } else if (nextSortOrder) { + sortOrder = nextSortOrder + 10000; + } else { + sortOrder = prevSortOrder! / 2; + } + + data.sort_order = sortOrder; + } + + return this.updateLabel(workspaceSlug, projectId, labelId, data); + }; + + /** + * Delete the label from the project and remove it from the labelMap object + * @param workspaceSlug + * @param projectId + * @param labelId + */ + deleteLabel = async (workspaceSlug: string, projectId: string, labelId: string) => { + const originalLabel = this.labelMap[labelId]; + + try { + if (!this.labelMap[labelId]) return; + + runInAction(() => { + delete this.labelMap[labelId]; + }); + + // deleting using api + await this.issueLabelService.deleteIssueLabel(workspaceSlug, projectId, labelId); + } catch (error) { + console.log("Failed to delete label from project store"); + // reverting back to original label list + runInAction(() => { + set(this.labelMap, [labelId], originalLabel); + }); + } + }; +} diff --git a/web/store/label/workspace-label.store.ts b/web/store/label/workspace-label.store.ts new file mode 100644 index 000000000..3a1b9d5b7 --- /dev/null +++ b/web/store/label/workspace-label.store.ts @@ -0,0 +1,63 @@ +import { action, computed, makeObservable, runInAction } from "mobx"; +import { set } from "lodash"; +// services +import { IssueLabelService } from "services/issue"; +// types +import { RootStore } from "store/root.store"; +import { IIssueLabel } from "types"; + +export interface IWorkspaceLabelStore { + // computed + workspaceLabels: IIssueLabel[] | undefined; + // actions + fetchWorkspaceLabels: (workspaceSlug: string) => Promise; +} + +export class WorkspaceLabelStore implements IWorkspaceLabelStore { + // root store + rootStore; + // root store labelMap + labelMap: Record = {}; + // services + issueLabelService; + + constructor(_rootStore: RootStore) { + makeObservable(this, { + // computed + workspaceLabels: computed, + // actions + fetchWorkspaceLabels: action, + }); + + // root store + this.rootStore = _rootStore; + this.labelMap = this.rootStore.labelRoot.labelMap; + // services + this.issueLabelService = new IssueLabelService(); + } + + /** + * Returns the labelMap belongs to a specific workspace + */ + get workspaceLabels() { + const currentWorkspaceDetails = this.rootStore.workspaceRoot.currentWorkspace; + if (!currentWorkspaceDetails) return; + return Object.values(this.labelMap).filter((label) => label.workspace === currentWorkspaceDetails.id); + } + + /** + * Fetches all the labelMap belongs to a specific project + * @param workspaceSlug + * @param projectId + * @returns Promise + */ + fetchWorkspaceLabels = async (workspaceSlug: string) => { + const response = await this.issueLabelService.getWorkspaceIssueLabels(workspaceSlug); + runInAction(() => { + response.forEach((label) => { + set(this.labelMap, [label.id], label); + }); + }); + return response; + }; +} diff --git a/web/store/project/project.store.ts b/web/store/project/project.store.ts index 3fd2123be..4819f70d2 100644 --- a/web/store/project/project.store.ts +++ b/web/store/project/project.store.ts @@ -8,22 +8,20 @@ import { IssueLabelService, IssueService } from "services/issue"; import { ProjectService, ProjectStateService } from "services/project"; export interface IProjectStore { + // states loader: boolean; error: any | null; - + // observables searchQuery: string; - projectId: string | null; projectMap: { [projectId: string]: IProject; // projectId: project Info }; - // computed searchedProjects: string[]; workspaceProjects: string[] | null; joinedProjects: string[]; favoriteProjects: string[]; currentProjectDetails: IProject | undefined; - // actions setSearchQuery: (query: string) => void; getProjectById: (projectId: string) => IProject | null; @@ -43,15 +41,14 @@ export interface IProjectStore { } export class ProjectStore implements IProjectStore { + // states loader: boolean = false; error: any | null = null; - - projectId: string | null = null; + // observables searchQuery: string = ""; projectMap: { [projectId: string]: IProject; // projectId: project Info } = {}; - // root store rootStore: RootStore; // service @@ -62,24 +59,19 @@ export class ProjectStore implements IProjectStore { constructor(_rootStore: RootStore) { makeObservable(this, { - // observable + // states loader: observable.ref, error: observable.ref, - + // observables searchQuery: observable.ref, - projectId: observable.ref, projectMap: observable, - // computed searchedProjects: computed, workspaceProjects: computed, - currentProjectDetails: computed, - joinedProjects: computed, favoriteProjects: computed, - - // action + // actions setSearchQuery: action, fetchProjects: action, fetchProjectDetails: action, diff --git a/web/store/root.store.ts b/web/store/root.store.ts index 0687b5194..36873c099 100644 --- a/web/store/root.store.ts +++ b/web/store/root.store.ts @@ -6,11 +6,11 @@ import { CycleStore, ICycleStore } from "./cycle.store"; import { IProjectViewsStore, ProjectViewsStore } from "./project-view.store"; import { IModuleStore, ModulesStore } from "./module.store"; import { IUserStore, UserStore } from "./user"; -import { ILabelStore, LabelStore } from "./label.store"; import { IWorkspaceRootStore, WorkspaceRootStore } from "./workspace"; import { IssueRootStore, IIssueRootStore } from "./issue/root.store"; import { IStateStore, StateStore } from "./state.store"; import { IPageStore, PageStore } from "./page.store"; +import { ILabelRootStore, LabelRootStore } from "./label"; enableStaticRendering(typeof window === "undefined"); @@ -19,11 +19,11 @@ export class RootStore { user: IUserStore; workspaceRoot: IWorkspaceRootStore; projectRoot: IProjectRootStore; + labelRoot: ILabelRootStore; cycle: ICycleStore; module: IModuleStore; projectView: IProjectViewsStore; page: IPageStore; - label: ILabelStore; issue: IIssueRootStore; state: IStateStore; @@ -32,8 +32,8 @@ export class RootStore { this.user = new UserStore(this); this.workspaceRoot = new WorkspaceRootStore(this); this.projectRoot = new ProjectRootStore(this); + this.labelRoot = new LabelRootStore(this); // independent stores - this.label = new LabelStore(this); this.state = new StateStore(this); this.issue = new IssueRootStore(this); this.cycle = new CycleStore(this); diff --git a/web/store/workspace/index.ts b/web/store/workspace/index.ts index 9daf8dbb0..d3a12024f 100644 --- a/web/store/workspace/index.ts +++ b/web/store/workspace/index.ts @@ -42,8 +42,8 @@ export class WorkspaceRootStore implements IWorkspaceRootStore { // root store rootStore; // sub-stores - webhook: WebhookStore; - apiToken: ApiTokenStore; + webhook: IWebhookStore; + apiToken: IApiTokenStore; constructor(_rootStore: RootStore) { makeObservable(this, {