fix: peek over data fetching fixes

This commit is contained in:
sriramveeraghanta 2023-08-31 00:12:45 +05:30
parent a27edad810
commit 6246a740d5
12 changed files with 262 additions and 199 deletions

View File

@ -14,7 +14,7 @@ import { IIssue } from "types/issue";
import { RootStore } from "store/root"; import { RootStore } from "store/root";
export const IssueListBlock = observer(({ issue }: { issue: IIssue }) => { export const IssueListBlock = observer(({ issue }: { issue: IIssue }) => {
const { issue: issueStore, project: projectStore }: RootStore = useMobxStore(); const { issue: issueStore, project: projectStore, issueDetails: issueDetailStore }: RootStore = useMobxStore();
return ( return (
<div className="flex items-center px-9 py-3.5 relative gap-10 border-b border-custom-border-200 bg-custom-background-100 last:border-b-0"> <div className="flex items-center px-9 py-3.5 relative gap-10 border-b border-custom-border-200 bg-custom-background-100 last:border-b-0">
@ -27,7 +27,7 @@ export const IssueListBlock = observer(({ issue }: { issue: IIssue }) => {
<div className="h-full line-clamp-1 w-full overflow-ellipsis cursor-pointer"> <div className="h-full line-clamp-1 w-full overflow-ellipsis cursor-pointer">
<p <p
onClick={() => { onClick={() => {
issueStore.setPeekId(issue.id); issueDetailStore.setPeekId(issue.id);
}} }}
className="text-[0.825rem] font-medium text-sm truncate text-custom-text-100" className="text-[0.825rem] font-medium text-sm truncate text-custom-text-100"
> >

View File

@ -1,70 +1,52 @@
import { useEffect } from "react"; import { useEffect } from "react";
// mobx
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
// lib
import { useMobxStore } from "lib/mobx/store-provider"; import { useMobxStore } from "lib/mobx/store-provider";
// components
import { import {
PeekOverviewHeader, PeekOverviewHeader,
PeekOverviewIssueActivity, PeekOverviewIssueActivity,
PeekOverviewIssueDetails, PeekOverviewIssueDetails,
PeekOverviewIssueProperties, PeekOverviewIssueProperties,
TPeekOverviewModes,
} from "components/issues/peek-overview"; } from "components/issues/peek-overview";
// types
import { IIssue } from "types/issue";
type Props = { type Props = {
issueId: string;
workspaceSlug: string;
projectId: string;
handleClose: () => void; handleClose: () => void;
mode: TPeekOverviewModes; issueDetails: IIssue;
setMode: (mode: TPeekOverviewModes) => void;
}; };
export const FullScreenPeekView: React.FC<Props> = observer((props) => { export const FullScreenPeekView: React.FC<Props> = observer((props) => {
const { handleClose, issueId, mode, setMode, workspaceSlug , projectId } = props; const { handleClose, issueDetails } = props;
const { issue: issueStore } = useMobxStore(); const { issueDetails: issueDetailStore } = useMobxStore();
const issue = issueStore.issue_detail[issueId]?.issue;
useEffect(() => {
if (!workspaceSlug || !projectId || !issueId) return;
issueStore.getIssueByIdAsync(workspaceSlug, projectId, issueId);
}, [workspaceSlug, projectId, issueId, issueStore]);
return ( return (
<div className="h-full w-full grid grid-cols-10 divide-x divide-custom-border-200 overflow-hidden"> <div className="h-full w-full grid grid-cols-10 divide-x divide-custom-border-200 overflow-hidden">
<div className="h-full w-full flex flex-col col-span-7 overflow-hidden"> <div className="h-full w-full flex flex-col col-span-7 overflow-hidden">
<div className="w-full p-5"> <div className="w-full p-5">
<PeekOverviewHeader <PeekOverviewHeader handleClose={handleClose} issueDetails={issueDetails} />
handleClose={handleClose}
issue={issue}
mode={mode}
setMode={setMode}
workspaceSlug={workspaceSlug}
/>
</div> </div>
<div className="h-full w-full px-6 overflow-y-auto"> <div className="h-full w-full px-6 overflow-y-auto">
{/* issue title and description */} {/* issue title and description */}
<div className="w-full"> <div className="w-full">
<PeekOverviewIssueDetails issue={issue} /> <PeekOverviewIssueDetails issueDetails={issueDetails} />
</div> </div>
{/* divider */} {/* divider */}
<div className="h-[1] w-full border-t border-custom-border-200 my-5" /> <div className="h-[1] w-full border-t border-custom-border-200 my-5" />
{/* issue activity/comments */} {/* issue activity/comments */}
<div className="w-full"> <div className="w-full">
<PeekOverviewIssueActivity workspaceSlug={workspaceSlug} /> <PeekOverviewIssueActivity issueDetails={issueDetails} />
</div> </div>
</div> </div>
</div> </div>
<div className="col-span-3 h-full w-full overflow-y-auto"> <div className="col-span-3 h-full w-full overflow-y-auto">
{/* issue properties */} {/* issue properties */}
<div className="w-full px-6 py-5"> <div className="w-full px-6 py-5">
<PeekOverviewIssueProperties issue={issue} mode="full" workspaceSlug={workspaceSlug} /> <PeekOverviewIssueProperties issueDetails={issueDetails} mode="full" />
</div> </div>
</div> </div>
</div> </div>
) );
}) });

View File

@ -1,23 +1,26 @@
import { useRouter } from "next/router";
import { ArrowRightAlt, CloseFullscreen, East, OpenInFull } from "@mui/icons-material";
// hooks // hooks
import useToast from "hooks/use-toast"; import useToast from "hooks/use-toast";
// ui // ui
import { Icon } from "components/ui"; import { Icon } from "components/ui";
// helpers // helpers
import { copyTextToClipboard } from "helpers/string.helper"; 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 // types
import { TPeekOverviewModes } from "./layout"; import { IIssue } from "types";
import { ArrowRightAlt, CloseFullscreen, East, OpenInFull } from "@mui/icons-material";
type Props = { type Props = {
handleClose: () => void; handleClose: () => void;
issue: any; issueDetails: IIssue;
mode: TPeekOverviewModes;
setMode: (mode: TPeekOverviewModes) => void;
workspaceSlug: string;
}; };
const peekModes: { const peekModes: {
key: TPeekOverviewModes; key: IPeekMode;
icon: string; icon: string;
label: string; label: string;
}[] = [ }[] = [
@ -34,13 +37,20 @@ const peekModes: {
}, },
]; ];
export const PeekOverviewHeader: React.FC<Props> = ({ issue, handleClose, mode, setMode, workspaceSlug }) => { export const PeekOverviewHeader: React.FC<Props> = (props) => {
const { issueDetails, handleClose } = props;
const { issueDetails: issueDetailStore }: RootStore = useMobxStore();
const router = useRouter();
const { workspaceSlug } = router.query;
const { setToastAlert } = useToast(); const { setToastAlert } = useToast();
const handleCopyLink = () => { const handleCopyLink = () => {
const originURL = typeof window !== "undefined" && window.location.origin ? window.location.origin : ""; 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({ setToastAlert({
type: "success", type: "success",
title: "Link copied!", title: "Link copied!",
@ -52,7 +62,7 @@ export const PeekOverviewHeader: React.FC<Props> = ({ issue, handleClose, mode,
return ( return (
<div className="flex justify-between items-center"> <div className="flex justify-between items-center">
<div className="flex items-center gap-4"> <div className="flex items-center gap-4">
{mode === "side" && ( {issueDetailStore.peekMode === "side" && (
<button <button
type="button" type="button"
onClick={() => { onClick={() => {
@ -66,8 +76,8 @@ export const PeekOverviewHeader: React.FC<Props> = ({ issue, handleClose, mode,
/> />
</button> </button>
)} )}
{mode === "modal" || mode === "full" ? ( {issueDetailStore.peekMode === "modal" || issueDetailStore.peekMode === "full" ? (
<button type="button" onClick={() => setMode("side")}> <button type="button" onClick={() => issueDetailStore.setPeekMode("side")}>
<CloseFullscreen <CloseFullscreen
sx={{ sx={{
fontSize: "14px", fontSize: "14px",
@ -75,7 +85,7 @@ export const PeekOverviewHeader: React.FC<Props> = ({ issue, handleClose, mode,
/> />
</button> </button>
) : ( ) : (
<button type="button" onClick={() => setMode("modal")}> <button type="button" onClick={() => issueDetailStore.setPeekMode("modal")}>
<OpenInFull <OpenInFull
sx={{ sx={{
fontSize: "14px", fontSize: "14px",
@ -83,11 +93,14 @@ export const PeekOverviewHeader: React.FC<Props> = ({ issue, handleClose, mode,
/> />
</button> </button>
)} )}
<button type="button" className={`grid place-items-center ${mode === "full" ? "rotate-45" : ""}`}> <button
<Icon iconName={peekModes.find((m) => m.key === mode)?.icon ?? ""} /> type="button"
className={`grid place-items-center ${issueDetailStore.peekMode === "full" ? "rotate-45" : ""}`}
>
<Icon iconName={peekModes.find((m) => m.key === issueDetailStore.peekMode)?.icon ?? ""} />
</button> </button>
</div> </div>
{(mode === "side" || mode === "modal") && ( {(issueDetailStore.peekMode === "side" || issueDetailStore.peekMode === "modal") && (
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<button type="button" onClick={handleCopyLink} className="-rotate-45"> <button type="button" onClick={handleCopyLink} className="-rotate-45">
<Icon iconName="link" /> <Icon iconName="link" />

View File

@ -1,42 +1,41 @@
import React, { useEffect } from "react"; import React, { useEffect } from "react";
// mobx import { useRouter } from "next/router";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
// lib
import { useMobxStore } from "lib/mobx/store-provider"; import { useMobxStore } from "lib/mobx/store-provider";
// components
import { CommentCard, AddComment } from "components/issues/peek-overview"; import { CommentCard, AddComment } from "components/issues/peek-overview";
// types
import { IIssue } from "types";
type Props = { type Props = {
workspaceSlug: string; issueDetails: IIssue;
}; };
export const PeekOverviewIssueActivity: React.FC<Props> = observer((props) => { export const PeekOverviewIssueActivity: React.FC<Props> = observer((props) => {
const { workspaceSlug } = props; const router = useRouter();
const { workspaceSlug } = router.query;
const { issue: issueStore, user: userStore } = useMobxStore(); const { issueDetails: issueDetailStore, user: userStore } = useMobxStore();
const issueId = issueStore?.activePeekOverviewIssueId; const issueId = issueDetailStore?.peekId;
const comments = issueStore?.issue_detail[issueId ?? ""]?.comments ?? []; const comments = issueDetailStore?.details[issueId ?? ""]?.comments ?? [];
useEffect(() => {
if (userStore.currentUser) return;
userStore.getUserAsync();
}, [userStore]);
return ( return (
<div> <div>
<h4 className="font-medium">Activity</h4> <h4 className="font-medium">Activity</h4>
{workspaceSlug && (
<div className="mt-4"> <div className="mt-4">
<div className="space-y-4"> <div className="space-y-4">
{comments.map((comment) => ( {comments.map((comment: any) => (
<CommentCard comment={comment} workspaceSlug={workspaceSlug} /> <CommentCard comment={comment} workspaceSlug={workspaceSlug?.toString()} />
))} ))}
</div> </div>
<div className="mt-4"> <div className="mt-4">
<AddComment disabled={!userStore.currentUser} issueId={issueId} /> <AddComment disabled={!userStore.currentUser} issueId={issueId} />
</div> </div>
</div> </div>
)}
</div> </div>
); );
}); });

View File

@ -4,15 +4,15 @@ import { IssueReactions } from "components/issues/peek-overview";
import { IIssue } from "types"; import { IIssue } from "types";
type Props = { type Props = {
issue: IIssue; issueDetails: IIssue;
}; };
export const PeekOverviewIssueDetails: React.FC<Props> = ({ issue }) => ( export const PeekOverviewIssueDetails: React.FC<Props> = ({ issueDetails }) => (
<div className="space-y-2"> <div className="space-y-2">
<h6 className="font-medium text-custom-text-200"> <h6 className="font-medium text-custom-text-200">
{issue.project_detail.identifier}-{issue.sequence_id} {issueDetails.project_detail.identifier}-{issueDetails.sequence_id}
</h6> </h6>
<h4 className="break-words text-2xl font-semibold">{issue.name}</h4> <h4 className="break-words text-2xl font-semibold">{issueDetails.name}</h4>
<IssueReactions /> <IssueReactions />
</div> </div>
); );

View File

@ -3,24 +3,23 @@ import { Disclosure } from "@headlessui/react";
// import { getStateGroupIcon } from "components/icons"; // import { getStateGroupIcon } from "components/icons";
// hooks // hooks
import useToast from "hooks/use-toast"; import useToast from "hooks/use-toast";
// components
import { TPeekOverviewModes } from "components/issues/peek-overview";
// icons // icons
import { Icon } from "components/ui"; import { Icon } from "components/ui";
import { copyTextToClipboard, addSpaceIfCamelCase } from "helpers/string.helper"; import { copyTextToClipboard, addSpaceIfCamelCase } from "helpers/string.helper";
// types // types
import { IIssue } from "types"; import { IIssue } from "types";
// constants // constants
import { issueGroupFilter, issuePriorityFilter } from "constants/data"; import { issueGroupFilter, issuePriorityFilter } from "constants/data";
import { useEffect } from "react"; import { useEffect } from "react";
import { renderDateFormat } from "constants/helpers"; import { renderDateFormat } from "constants/helpers";
import { IPeekMode } from "store/issue_details";
import { useRouter } from "next/router";
import { RootStore } from "store/root";
import { useMobxStore } from "lib/mobx/store-provider";
type Props = { type Props = {
issue: IIssue; issueDetails: IIssue;
mode: TPeekOverviewModes; mode?: IPeekMode;
workspaceSlug: string;
}; };
const validDate = (date: any, state: string): string => { const validDate = (date: any, state: string): string => {
@ -35,11 +34,16 @@ const validDate = (date: any, state: string): string => {
} }
}; };
export const PeekOverviewIssueProperties: React.FC<Props> = ({ issue, mode, workspaceSlug }) => { export const PeekOverviewIssueProperties: React.FC<Props> = ({ issueDetails, mode }) => {
const { setToastAlert } = useToast(); const { setToastAlert } = useToast();
const startDate = issue.start_date; const { issueDetails: issueDetailStore }: RootStore = useMobxStore();
const targetDate = issue.target_date;
const router = useRouter();
const { workspaceSlug } = router.query;
const startDate = issueDetails.start_date;
const targetDate = issueDetails.target_date;
const minDate = startDate ? new Date(startDate) : null; const minDate = startDate ? new Date(startDate) : null;
minDate?.setDate(minDate.getDate()); minDate?.setDate(minDate.getDate());
@ -47,15 +51,17 @@ export const PeekOverviewIssueProperties: React.FC<Props> = ({ issue, mode, work
const maxDate = targetDate ? new Date(targetDate) : null; const maxDate = targetDate ? new Date(targetDate) : null;
maxDate?.setDate(maxDate.getDate()); maxDate?.setDate(maxDate.getDate());
const state = issue.state_detail; const state = issueDetails.state_detail;
const stateGroup = issueGroupFilter(state.group); const stateGroup = issueGroupFilter(state.group);
const priority = issue.priority ? issuePriorityFilter(issue.priority) : null; const priority = issueDetails.priority ? issuePriorityFilter(issueDetails.priority) : null;
const handleCopyLink = () => { const handleCopyLink = () => {
const originURL = typeof window !== "undefined" && window.location.origin ? window.location.origin : ""; const originURL = typeof window !== "undefined" && window.location.origin ? window.location.origin : "";
copyTextToClipboard(`${originURL}/${workspaceSlug}/projects/${issue.project}/issues/${issue.id}`).then(() => { copyTextToClipboard(
`${originURL}/${workspaceSlug}/projects/${issueDetails.project}/issues/${issueDetails.id}`
).then(() => {
setToastAlert({ setToastAlert({
type: "success", type: "success",
title: "Link copied!", title: "Link copied!",
@ -70,7 +76,7 @@ export const PeekOverviewIssueProperties: React.FC<Props> = ({ issue, mode, work
<div className="flex justify-between gap-2 pb-3"> <div className="flex justify-between gap-2 pb-3">
<h6 className="flex items-center gap-2 font-medium"> <h6 className="flex items-center gap-2 font-medium">
{/* {getStateGroupIcon(issue.state_detail.group, "16", "16", issue.state_detail.color)} */} {/* {getStateGroupIcon(issue.state_detail.group, "16", "16", issue.state_detail.color)} */}
{issue.project_detail.identifier}-{issue.sequence_id} {issueDetails.project_detail.identifier}-{issueDetails.sequence_id}
</h6> </h6>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<button type="button" onClick={handleCopyLink} className="-rotate-45"> <button type="button" onClick={handleCopyLink} className="-rotate-45">
@ -131,12 +137,12 @@ export const PeekOverviewIssueProperties: React.FC<Props> = ({ issue, mode, work
<span className="flex-grow truncate">Due date</span> <span className="flex-grow truncate">Due date</span>
</div> </div>
<div> <div>
{issue.target_date ? ( {issueDetails.target_date ? (
<div <div
className={`h-[24px] rounded-md flex px-2.5 py-1 items-center border border-custom-border-100 gap-1 text-custom-text-100 text-xs font-medium className={`h-[24px] rounded-md flex px-2.5 py-1 items-center border border-custom-border-100 gap-1 text-custom-text-100 text-xs font-medium
${validDate(issue.target_date, state)}`} ${validDate(issueDetails.target_date, state)}`}
> >
{renderDateFormat(issue.target_date)} {renderDateFormat(issueDetails.target_date)}
</div> </div>
) : ( ) : (
<span className="text-custom-text-200">Empty</span> <span className="text-custom-text-200">Empty</span>

View File

@ -1,36 +1,48 @@
import React, { useState } from "react"; import React, { useEffect, useState } from "react";
import { useRouter } from "next/router";
// headless ui
import { Dialog, Transition } from "@headlessui/react"; import { Dialog, Transition } from "@headlessui/react";
import { observer } from "mobx-react-lite";
// components
import { FullScreenPeekView, SidePeekView } from "components/issues/peek-overview"; import { FullScreenPeekView, SidePeekView } from "components/issues/peek-overview";
// types // types
import type { IIssue } from "types"; import type { IIssue } from "types/issue";
// lib
import { useMobxStore } from "lib/mobx/store-provider";
type Props = { type Props = {
issue: IIssue | null;
isOpen: boolean; isOpen: boolean;
onClose: () => void; onClose: () => void;
workspaceSlug: string;
}; };
export type TPeekOverviewModes = "side" | "modal" | "full"; export const IssuePeekOverview: React.FC<Props> = 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<Props> = ({ issue, isOpen, onClose, workspaceSlug }) => { const issueDetails = issueDetailStore.peekId ? issueDetailStore.details[issueDetailStore.peekId] : null;
const [peekOverviewMode, setPeekOverviewMode] = useState<TPeekOverviewModes>("side"); 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 = () => { const handleClose = () => {
onClose(); onClose();
setPeekOverviewMode("side"); issueDetailStore.setPeekMode("side");
}; };
if (!issue || !isOpen) return null;
return ( return (
<Transition.Root show={isOpen} as={React.Fragment}> <Transition.Root show={isOpen} as={React.Fragment}>
<Dialog as="div" className="relative z-20" onClose={handleClose}> <Dialog as="div" className="relative z-20" onClose={handleClose}>
{/* add backdrop conditionally */} {/* add backdrop conditionally */}
{(peekOverviewMode === "modal" || peekOverviewMode === "full") && ( {(issueDetailStore.peekMode === "modal" || issueDetailStore.peekMode === "full") && (
<Transition.Child <Transition.Child
as={React.Fragment} as={React.Fragment}
enter="ease-out duration-300" enter="ease-out duration-300"
@ -56,32 +68,18 @@ export const IssuePeekOverview: React.FC<Props> = ({ issue, isOpen, onClose, wor
> >
<Dialog.Panel <Dialog.Panel
className={`absolute z-20 bg-custom-background-100 ${ className={`absolute z-20 bg-custom-background-100 ${
peekOverviewMode === "side" issueDetailStore.peekMode === "side"
? "top-0 right-0 h-full w-1/2 shadow-custom-shadow-md" ? "top-0 right-0 h-full w-1/2 shadow-custom-shadow-md"
: peekOverviewMode === "modal" : issueDetailStore.peekMode === "modal"
? "top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 h-[70%] w-3/5 rounded-lg shadow-custom-shadow-xl" ? "top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 h-[70%] w-3/5 rounded-lg shadow-custom-shadow-xl"
: "top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 h-[95%] w-[95%] rounded-lg shadow-custom-shadow-xl" : "top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 h-[95%] w-[95%] rounded-lg shadow-custom-shadow-xl"
}`} }`}
> >
{(peekOverviewMode === "side" || peekOverviewMode === "modal") && ( {(issueDetailStore.peekMode === "side" || issueDetailStore.peekMode === "modal") && (
<SidePeekView <SidePeekView handleClose={handleClose} issueDetails={issueDetails} />
handleClose={handleClose}
issueId={issue.id}
projectId={issue.project}
mode={peekOverviewMode}
setMode={(mode) => setPeekOverviewMode(mode)}
workspaceSlug={workspaceSlug}
/>
)} )}
{peekOverviewMode === "full" && ( {issueDetailStore.peekMode === "full" && (
<FullScreenPeekView <FullScreenPeekView handleClose={handleClose} issueDetails={issueDetails} />
issueId={issue.id}
workspaceSlug={workspaceSlug}
projectId={issue.project}
handleClose={handleClose}
mode={peekOverviewMode}
setMode={(mode) => setPeekOverviewMode(mode)}
/>
)} )}
</Dialog.Panel> </Dialog.Panel>
</Transition.Child> </Transition.Child>
@ -90,4 +88,4 @@ export const IssuePeekOverview: React.FC<Props> = ({ issue, isOpen, onClose, wor
</Dialog> </Dialog>
</Transition.Root> </Transition.Root>
); );
}; });

View File

@ -1,64 +1,45 @@
// mobx import { useEffect } from "react";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
// lib
import { useMobxStore } from "lib/mobx/store-provider"; import { useMobxStore } from "lib/mobx/store-provider";
// components
import { import {
PeekOverviewHeader, PeekOverviewHeader,
PeekOverviewIssueActivity, PeekOverviewIssueActivity,
PeekOverviewIssueDetails, PeekOverviewIssueDetails,
PeekOverviewIssueProperties, PeekOverviewIssueProperties,
TPeekOverviewModes,
} from "components/issues/peek-overview"; } from "components/issues/peek-overview";
import { useEffect } from "react"; // types
import { IIssue } from "types/issue";
type Props = { type Props = {
issueId: string;
projectId: string;
workspaceSlug: string;
handleClose: () => void; handleClose: () => void;
mode: TPeekOverviewModes; issueDetails: IIssue;
setMode: (mode: TPeekOverviewModes) => void;
}; };
export const SidePeekView: React.FC<Props> = observer((props) => { export const SidePeekView: React.FC<Props> = 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]);
return ( return (
<div className="h-full w-full flex flex-col overflow-hidden"> <div className="h-full w-full flex flex-col overflow-hidden">
<div className="w-full p-5"> <div className="w-full p-5">
<PeekOverviewHeader <PeekOverviewHeader handleClose={handleClose} issueDetails={issueDetails} />
handleClose={handleClose}
issue={issue}
mode={mode}
setMode={setMode}
workspaceSlug={workspaceSlug}
/>
</div> </div>
{issue && ( {issueDetails && (
<div className="h-full w-full px-6 overflow-y-auto"> <div className="h-full w-full px-6 overflow-y-auto">
{/* issue title and description */} {/* issue title and description */}
<div className="w-full"> <div className="w-full">
<PeekOverviewIssueDetails issue={issue} /> <PeekOverviewIssueDetails issueDetails={issueDetails} />
</div> </div>
{/* issue properties */} {/* issue properties */}
<div className="w-full mt-10"> <div className="w-full mt-10">
<PeekOverviewIssueProperties issue={issue} mode={mode} workspaceSlug={workspaceSlug} /> <PeekOverviewIssueProperties issueDetails={issueDetails} />
</div> </div>
{/* divider */} {/* divider */}
<div className="h-[1] w-full border-t border-custom-border-200 my-5" /> <div className="h-[1] w-full border-t border-custom-border-200 my-5" />
{/* issue activity/comments */} {/* issue activity/comments */}
<div className="w-full pb-5"> <div className="w-full pb-5">
<PeekOverviewIssueActivity workspaceSlug={workspaceSlug} /> <PeekOverviewIssueActivity issueDetails={issueDetails} />
</div> </div>
</div> </div>
)} )}

View File

@ -16,11 +16,9 @@ export const ProjectDetailsView = observer(() => {
const router = useRouter(); const router = useRouter();
const { workspace_slug, project_slug, states, labels, priorities } = router.query; 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 = issueDetailStore.peekId;
const activeIssueId = issueStore.peekId;
useEffect(() => { useEffect(() => {
if (workspace_slug && project_slug) { if (workspace_slug && project_slug) {
@ -36,12 +34,7 @@ export const ProjectDetailsView = observer(() => {
return ( return (
<div className="relative w-full h-full overflow-hidden"> <div className="relative w-full h-full overflow-hidden">
{workspace_slug && ( {workspace_slug && (
<IssuePeekOverview <IssuePeekOverview isOpen={Boolean(activeIssueId)} onClose={() => issueDetailStore.setPeekId(null)} />
isOpen={Boolean(activeIssueId)}
onClose={() => issueStore.setPeekId(null)}
issue={issueStore?.issues?.find((_issue) => _issue.id === activeIssueId) || null}
workspaceSlug={workspace_slug.toString()}
/>
)} )}
{issueStore?.loader && !issueStore.issues ? ( {issueStore?.loader && !issueStore.issues ? (

View File

@ -18,13 +18,10 @@ export interface IIssueStore {
filteredStates: string[]; filteredStates: string[];
filteredLabels: string[]; filteredLabels: string[];
filteredPriorities: string[]; filteredPriorities: string[];
// peek info
peekId: string | null;
// service // service
issueService: any; issueService: any;
// actions // actions
fetchPublicIssues: (workspace_slug: string, project_slug: string, params: any) => void; fetchPublicIssues: (workspace_slug: string, project_slug: string, params: any) => void;
setPeekId: (issueId: string | null) => void;
getFilteredIssuesByState: (state: string) => IIssue[]; getFilteredIssuesByState: (state: string) => IIssue[];
} }
@ -42,8 +39,6 @@ class IssueStore implements IIssueStore {
issues: IIssue[] | null = []; issues: IIssue[] | null = [];
issue_detail: any = {}; issue_detail: any = {};
peekId: string | null = null;
rootStore: RootStore; rootStore: RootStore;
issueService: any; issueService: any;
@ -62,11 +57,8 @@ class IssueStore implements IIssueStore {
// issues // issues
issues: observable.ref, issues: observable.ref,
issue_detail: observable.ref, issue_detail: observable.ref,
// peek
peekId: observable.ref,
// actions // actions
fetchPublicIssues: action, fetchPublicIssues: action,
setPeekId: action,
getFilteredIssuesByState: action, getFilteredIssuesByState: action,
}); });
@ -98,10 +90,6 @@ class IssueStore implements IIssueStore {
} }
}; };
setPeekId = (issueId: string | null) => {
this.peekId = issueId;
};
getFilteredIssuesByState = (state_id: string): IIssue[] | [] => getFilteredIssuesByState = (state_id: string): IIssue[] | [] =>
this.issues?.filter((issue) => issue.state == state_id) || []; this.issues?.filter((issue) => issue.state == state_id) || [];
} }

View File

@ -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;

View File

@ -5,6 +5,7 @@ import UserStore from "./user";
import ThemeStore from "./theme"; import ThemeStore from "./theme";
import IssueStore, { IIssueStore } from "./issue"; import IssueStore, { IIssueStore } from "./issue";
import ProjectStore, { IProjectStore } from "./project"; import ProjectStore, { IProjectStore } from "./project";
import IssueDetailStore, { IIssueDetailStore } from "./issue_details";
// types // types
import { IThemeStore } from "../types"; import { IThemeStore } from "../types";
@ -14,6 +15,7 @@ export class RootStore {
user: UserStore; user: UserStore;
theme: IThemeStore; theme: IThemeStore;
issue: IIssueStore; issue: IIssueStore;
issueDetails: IIssueDetailStore;
project: IProjectStore; project: IProjectStore;
constructor() { constructor() {
@ -21,5 +23,6 @@ export class RootStore {
this.theme = new ThemeStore(this); this.theme = new ThemeStore(this);
this.issue = new IssueStore(this); this.issue = new IssueStore(this);
this.project = new ProjectStore(this); this.project = new ProjectStore(this);
this.issueDetails = new IssueDetailStore(this);
} }
} }