diff --git a/apps/space/components/issues/board-views/list/block.tsx b/apps/space/components/issues/board-views/list/block.tsx index d96b302e7..fcb123c21 100644 --- a/apps/space/components/issues/board-views/list/block.tsx +++ b/apps/space/components/issues/board-views/list/block.tsx @@ -14,7 +14,7 @@ import { IIssue } from "types/issue"; import { RootStore } from "store/root"; export const IssueListBlock = observer(({ issue }: { issue: IIssue }) => { - const { issue: issueStore, project: projectStore }: RootStore = useMobxStore(); + const { issue: issueStore, project: projectStore, issueDetails: issueDetailStore }: RootStore = useMobxStore(); return (
@@ -27,7 +27,7 @@ export const IssueListBlock = observer(({ issue }: { issue: IIssue }) => {

{ - issueStore.setPeekId(issue.id); + issueDetailStore.setPeekId(issue.id); }} className="text-[0.825rem] font-medium text-sm truncate text-custom-text-100" > diff --git a/apps/space/components/issues/peek-overview/full-screen-peek-view.tsx b/apps/space/components/issues/peek-overview/full-screen-peek-view.tsx index 27080d37f..291bcc887 100644 --- a/apps/space/components/issues/peek-overview/full-screen-peek-view.tsx +++ b/apps/space/components/issues/peek-overview/full-screen-peek-view.tsx @@ -1,70 +1,52 @@ import { useEffect } from "react"; - -// mobx import { observer } from "mobx-react-lite"; +// lib import { useMobxStore } from "lib/mobx/store-provider"; - +// components import { PeekOverviewHeader, PeekOverviewIssueActivity, PeekOverviewIssueDetails, PeekOverviewIssueProperties, - TPeekOverviewModes, } from "components/issues/peek-overview"; +// types +import { IIssue } from "types/issue"; type Props = { - issueId: string; - workspaceSlug: string; - projectId: string; handleClose: () => void; - mode: TPeekOverviewModes; - setMode: (mode: TPeekOverviewModes) => void; + issueDetails: IIssue; }; export const FullScreenPeekView: React.FC = observer((props) => { - const { handleClose, issueId, mode, setMode, workspaceSlug , projectId } = props; + const { handleClose, issueDetails } = props; - const { issue: issueStore } = useMobxStore(); - - const issue = issueStore.issue_detail[issueId]?.issue; - - useEffect(() => { - if (!workspaceSlug || !projectId || !issueId) return; - - issueStore.getIssueByIdAsync(workspaceSlug, projectId, issueId); - }, [workspaceSlug, projectId, issueId, issueStore]); + const { issueDetails: issueDetailStore } = useMobxStore(); return (

-
-
- -
-
- {/* issue title and description */} -
- -
- {/* divider */} -
- {/* issue activity/comments */} -
- -
-
-
-
- {/* issue properties */} -
- -
-
+
+
+ +
+
+ {/* issue title and description */} +
+
- ) -}) + {/* divider */} +
+ {/* issue activity/comments */} +
+ +
+
+
+
+ {/* issue properties */} +
+ +
+
+
+ ); +}); diff --git a/apps/space/components/issues/peek-overview/header.tsx b/apps/space/components/issues/peek-overview/header.tsx index 86b7fdffc..b809f5438 100644 --- a/apps/space/components/issues/peek-overview/header.tsx +++ b/apps/space/components/issues/peek-overview/header.tsx @@ -1,23 +1,26 @@ +import { useRouter } from "next/router"; +import { ArrowRightAlt, CloseFullscreen, East, OpenInFull } from "@mui/icons-material"; // hooks import useToast from "hooks/use-toast"; // ui import { Icon } from "components/ui"; // helpers import { copyTextToClipboard } from "helpers/string.helper"; +// store +import { IPeekMode } from "store/issue_details"; +import { RootStore } from "store/root"; +// lib +import { useMobxStore } from "lib/mobx/store-provider"; // types -import { TPeekOverviewModes } from "./layout"; -import { ArrowRightAlt, CloseFullscreen, East, OpenInFull } from "@mui/icons-material"; +import { IIssue } from "types"; type Props = { handleClose: () => void; - issue: any; - mode: TPeekOverviewModes; - setMode: (mode: TPeekOverviewModes) => void; - workspaceSlug: string; + issueDetails: IIssue; }; const peekModes: { - key: TPeekOverviewModes; + key: IPeekMode; icon: string; label: string; }[] = [ @@ -34,13 +37,20 @@ const peekModes: { }, ]; -export const PeekOverviewHeader: React.FC = ({ issue, handleClose, mode, setMode, workspaceSlug }) => { +export const PeekOverviewHeader: React.FC = (props) => { + const { issueDetails, handleClose } = props; + + const { issueDetails: issueDetailStore }: RootStore = useMobxStore(); + + const router = useRouter(); + const { workspaceSlug } = router.query; + const { setToastAlert } = useToast(); const handleCopyLink = () => { const originURL = typeof window !== "undefined" && window.location.origin ? window.location.origin : ""; - copyTextToClipboard(`${originURL}/${workspaceSlug}/projects/${issue.project}/`).then(() => { + copyTextToClipboard(`${originURL}/${workspaceSlug}/projects/${issueDetails.project}/`).then(() => { setToastAlert({ type: "success", title: "Link copied!", @@ -52,7 +62,7 @@ export const PeekOverviewHeader: React.FC = ({ issue, handleClose, mode, return (
- {mode === "side" && ( + {issueDetailStore.peekMode === "side" && ( )} - {mode === "modal" || mode === "full" ? ( - ) : ( - )} -
- {(mode === "side" || mode === "modal") && ( + {(issueDetailStore.peekMode === "side" || issueDetailStore.peekMode === "modal") && (
- {issue.target_date ? ( + {issueDetails.target_date ? (
- {renderDateFormat(issue.target_date)} + {renderDateFormat(issueDetails.target_date)}
) : ( Empty diff --git a/apps/space/components/issues/peek-overview/layout.tsx b/apps/space/components/issues/peek-overview/layout.tsx index 2d30cff68..ee763d148 100644 --- a/apps/space/components/issues/peek-overview/layout.tsx +++ b/apps/space/components/issues/peek-overview/layout.tsx @@ -1,36 +1,48 @@ -import React, { useState } from "react"; - -// headless ui +import React, { useEffect, useState } from "react"; +import { useRouter } from "next/router"; import { Dialog, Transition } from "@headlessui/react"; +import { observer } from "mobx-react-lite"; +// components import { FullScreenPeekView, SidePeekView } from "components/issues/peek-overview"; - // types -import type { IIssue } from "types"; +import type { IIssue } from "types/issue"; +// lib +import { useMobxStore } from "lib/mobx/store-provider"; type Props = { - issue: IIssue | null; isOpen: boolean; onClose: () => void; - workspaceSlug: string; }; -export type TPeekOverviewModes = "side" | "modal" | "full"; +export const IssuePeekOverview: React.FC = observer((props) => { + const { isOpen, onClose } = props; + // router + const router = useRouter(); + const { workspace_slug, project_slug } = router.query; + // store + const { issueDetails: issueDetailStore } = useMobxStore(); -export const IssuePeekOverview: React.FC = ({ issue, isOpen, onClose, workspaceSlug }) => { - const [peekOverviewMode, setPeekOverviewMode] = useState("side"); + const issueDetails = issueDetailStore.peekId ? issueDetailStore.details[issueDetailStore.peekId] : null; + console.log("issueDetails", issueDetails); + + useEffect(() => { + if (workspace_slug && project_slug && issueDetailStore.peekId) { + if (!issueDetails) { + issueDetailStore.fetchIssueDetails(workspace_slug.toString(), project_slug.toString(), issueDetailStore.peekId); + } + } + }, [workspace_slug, project_slug, issueDetailStore, issueDetails]); const handleClose = () => { onClose(); - setPeekOverviewMode("side"); + issueDetailStore.setPeekMode("side"); }; - if (!issue || !isOpen) return null; - return ( {/* add backdrop conditionally */} - {(peekOverviewMode === "modal" || peekOverviewMode === "full") && ( + {(issueDetailStore.peekMode === "modal" || issueDetailStore.peekMode === "full") && ( = ({ issue, isOpen, onClose, wor > - {(peekOverviewMode === "side" || peekOverviewMode === "modal") && ( - setPeekOverviewMode(mode)} - workspaceSlug={workspaceSlug} - /> + {(issueDetailStore.peekMode === "side" || issueDetailStore.peekMode === "modal") && ( + )} - {peekOverviewMode === "full" && ( - setPeekOverviewMode(mode)} - /> + {issueDetailStore.peekMode === "full" && ( + )} @@ -90,4 +88,4 @@ export const IssuePeekOverview: React.FC = ({ issue, isOpen, onClose, wor ); -}; +}); diff --git a/apps/space/components/issues/peek-overview/side-peek-view.tsx b/apps/space/components/issues/peek-overview/side-peek-view.tsx index 7547f91e9..094565c93 100644 --- a/apps/space/components/issues/peek-overview/side-peek-view.tsx +++ b/apps/space/components/issues/peek-overview/side-peek-view.tsx @@ -1,64 +1,45 @@ -// mobx +import { useEffect } from "react"; import { observer } from "mobx-react-lite"; +// lib import { useMobxStore } from "lib/mobx/store-provider"; - +// components import { PeekOverviewHeader, PeekOverviewIssueActivity, PeekOverviewIssueDetails, PeekOverviewIssueProperties, - TPeekOverviewModes, } from "components/issues/peek-overview"; -import { useEffect } from "react"; +// types +import { IIssue } from "types/issue"; type Props = { - issueId: string; - projectId: string; - workspaceSlug: string; handleClose: () => void; - mode: TPeekOverviewModes; - setMode: (mode: TPeekOverviewModes) => void; + issueDetails: IIssue; }; export const SidePeekView: React.FC = observer((props) => { - const { handleClose, issueId, mode, setMode, workspaceSlug, projectId } = props; - - const { issue: issueStore } = useMobxStore(); - - const issue = issueStore.issue_detail[issueId]?.issue; - - useEffect(() => { - if (!workspaceSlug || !projectId || !issueId) return; - - issueStore.getIssueByIdAsync(workspaceSlug, projectId, issueId); - }, [workspaceSlug, projectId, issueId, issueStore]); + const { handleClose, issueDetails } = props; return (
- +
- {issue && ( + {issueDetails && (
{/* issue title and description */}
- +
{/* issue properties */}
- +
{/* divider */}
{/* issue activity/comments */}
- +
)} diff --git a/apps/space/components/views/project-details.tsx b/apps/space/components/views/project-details.tsx index b1a857b98..1ef870dc4 100644 --- a/apps/space/components/views/project-details.tsx +++ b/apps/space/components/views/project-details.tsx @@ -16,11 +16,9 @@ export const ProjectDetailsView = observer(() => { const router = useRouter(); const { workspace_slug, project_slug, states, labels, priorities } = router.query; - const { issue: issueStore, project: projectStore }: RootStore = useMobxStore(); + const { issue: issueStore, project: projectStore, issueDetails: issueDetailStore }: RootStore = useMobxStore(); - console.log("projectStore?.activeBoard", projectStore?.activeBoard); - - const activeIssueId = issueStore.peekId; + const activeIssueId = issueDetailStore.peekId; useEffect(() => { if (workspace_slug && project_slug) { @@ -36,12 +34,7 @@ export const ProjectDetailsView = observer(() => { return (
{workspace_slug && ( - issueStore.setPeekId(null)} - issue={issueStore?.issues?.find((_issue) => _issue.id === activeIssueId) || null} - workspaceSlug={workspace_slug.toString()} - /> + issueDetailStore.setPeekId(null)} /> )} {issueStore?.loader && !issueStore.issues ? ( diff --git a/apps/space/store/issue.ts b/apps/space/store/issue.ts index c15a9a2e9..c14175beb 100644 --- a/apps/space/store/issue.ts +++ b/apps/space/store/issue.ts @@ -18,13 +18,10 @@ export interface IIssueStore { filteredStates: string[]; filteredLabels: string[]; filteredPriorities: string[]; - // peek info - peekId: string | null; // service issueService: any; // actions fetchPublicIssues: (workspace_slug: string, project_slug: string, params: any) => void; - setPeekId: (issueId: string | null) => void; getFilteredIssuesByState: (state: string) => IIssue[]; } @@ -42,8 +39,6 @@ class IssueStore implements IIssueStore { issues: IIssue[] | null = []; issue_detail: any = {}; - peekId: string | null = null; - rootStore: RootStore; issueService: any; @@ -62,11 +57,8 @@ class IssueStore implements IIssueStore { // issues issues: observable.ref, issue_detail: observable.ref, - // peek - peekId: observable.ref, // actions fetchPublicIssues: action, - setPeekId: action, getFilteredIssuesByState: action, }); @@ -98,10 +90,6 @@ class IssueStore implements IIssueStore { } }; - setPeekId = (issueId: string | null) => { - this.peekId = issueId; - }; - getFilteredIssuesByState = (state_id: string): IIssue[] | [] => this.issues?.filter((issue) => issue.state == state_id) || []; } diff --git a/apps/space/store/issue_details.ts b/apps/space/store/issue_details.ts new file mode 100644 index 000000000..4da937bff --- /dev/null +++ b/apps/space/store/issue_details.ts @@ -0,0 +1,100 @@ +import { makeObservable, observable, action, runInAction } from "mobx"; +// store +import { RootStore } from "./root"; +// services +import IssueService from "services/issue.service"; + +export type IPeekMode = "side" | "modal" | "full"; + +export interface IIssueDetailStore { + loader: boolean; + error: any; + // peek info + peekId: string | null; + peekMode: IPeekMode; + details: any; + // actions + setPeekId: (issueId: string | null) => void; + setPeekMode: (mode: IPeekMode) => void; + fetchIssueDetails: (workspaceId: string, projectId: string, issueId: string) => void; +} + +class IssueDetailStore implements IssueDetailStore { + loader: boolean = false; + error: any = null; + peekId: string | null = null; + peekMode: IPeekMode = "side"; + details: any = {}; + issueService: any; + rootStore: RootStore; + + constructor(_rootStore: RootStore) { + makeObservable(this, { + loader: observable.ref, + error: observable.ref, + // peek + peekId: observable.ref, + peekMode: observable.ref, + details: observable.ref, + // actions + setPeekId: action, + fetchIssueDetails: action, + setPeekMode: action, + }); + this.issueService = new IssueService(); + this.rootStore = _rootStore; + } + + setPeekId = (issueId: string | null) => { + this.peekId = issueId; + }; + + setPeekMode = (mode: IPeekMode) => { + this.peekMode = mode; + }; + + fetchIssueDetails = async (workspaceSlug: string, projectId: string, issueId: string) => { + try { + this.loader = true; + this.error = null; + + const issueDetails = this.rootStore.issue.issues?.find((i) => i.id === issueId); + const reactionsResponse = await this.issueService.getIssueReactions(workspaceSlug, projectId, issueId); + const commentsResponse = await this.issueService.getIssueComments(workspaceSlug, projectId, issueId); + const votesResponse = await this.issueService.getIssueVotes(workspaceSlug, projectId, issueId); + + if (issueDetails) { + runInAction(() => { + this.details = { + ...this.details, + [issueId]: { + ...issueDetails, + comments: commentsResponse, + reactions: reactionsResponse, + votes: votesResponse, + }, + }; + }); + } + } catch (error) { + this.loader = false; + this.error = error; + + const issueDetails = this.rootStore.issue.issues?.find((i) => i.id === issueId); + + runInAction(() => { + this.details = { + ...this.details, + [issueId]: { + ...issueDetails, + comments: [], + reactions: [], + votes: [], + }, + }; + }); + } + }; +} + +export default IssueDetailStore; diff --git a/apps/space/store/root.ts b/apps/space/store/root.ts index c6bdfc6aa..cf097a1c6 100644 --- a/apps/space/store/root.ts +++ b/apps/space/store/root.ts @@ -5,6 +5,7 @@ import UserStore from "./user"; import ThemeStore from "./theme"; import IssueStore, { IIssueStore } from "./issue"; import ProjectStore, { IProjectStore } from "./project"; +import IssueDetailStore, { IIssueDetailStore } from "./issue_details"; // types import { IThemeStore } from "../types"; @@ -14,6 +15,7 @@ export class RootStore { user: UserStore; theme: IThemeStore; issue: IIssueStore; + issueDetails: IIssueDetailStore; project: IProjectStore; constructor() { @@ -21,5 +23,6 @@ export class RootStore { this.theme = new ThemeStore(this); this.issue = new IssueStore(this); this.project = new ProjectStore(this); + this.issueDetails = new IssueDetailStore(this); } }