mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
fix: peek over data fetching fixes
This commit is contained in:
parent
a27edad810
commit
6246a740d5
@ -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 (
|
||||
<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">
|
||||
<p
|
||||
onClick={() => {
|
||||
issueStore.setPeekId(issue.id);
|
||||
issueDetailStore.setPeekId(issue.id);
|
||||
}}
|
||||
className="text-[0.825rem] font-medium text-sm truncate text-custom-text-100"
|
||||
>
|
||||
|
@ -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<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]);
|
||||
const { issueDetails: issueDetailStore } = useMobxStore();
|
||||
|
||||
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 flex flex-col col-span-7 overflow-hidden">
|
||||
<div className="w-full p-5">
|
||||
<PeekOverviewHeader
|
||||
handleClose={handleClose}
|
||||
issue={issue}
|
||||
mode={mode}
|
||||
setMode={setMode}
|
||||
workspaceSlug={workspaceSlug}
|
||||
/>
|
||||
</div>
|
||||
<div className="h-full w-full px-6 overflow-y-auto">
|
||||
{/* issue title and description */}
|
||||
<div className="w-full">
|
||||
<PeekOverviewIssueDetails issue={issue} />
|
||||
</div>
|
||||
{/* divider */}
|
||||
<div className="h-[1] w-full border-t border-custom-border-200 my-5" />
|
||||
{/* issue activity/comments */}
|
||||
<div className="w-full">
|
||||
<PeekOverviewIssueActivity workspaceSlug={workspaceSlug} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-span-3 h-full w-full overflow-y-auto">
|
||||
{/* issue properties */}
|
||||
<div className="w-full px-6 py-5">
|
||||
<PeekOverviewIssueProperties issue={issue} mode="full" workspaceSlug={workspaceSlug} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="h-full w-full flex flex-col col-span-7 overflow-hidden">
|
||||
<div className="w-full p-5">
|
||||
<PeekOverviewHeader handleClose={handleClose} issueDetails={issueDetails} />
|
||||
</div>
|
||||
<div className="h-full w-full px-6 overflow-y-auto">
|
||||
{/* issue title and description */}
|
||||
<div className="w-full">
|
||||
<PeekOverviewIssueDetails issueDetails={issueDetails} />
|
||||
</div>
|
||||
)
|
||||
})
|
||||
{/* divider */}
|
||||
<div className="h-[1] w-full border-t border-custom-border-200 my-5" />
|
||||
{/* issue activity/comments */}
|
||||
<div className="w-full">
|
||||
<PeekOverviewIssueActivity issueDetails={issueDetails} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-span-3 h-full w-full overflow-y-auto">
|
||||
{/* issue properties */}
|
||||
<div className="w-full px-6 py-5">
|
||||
<PeekOverviewIssueProperties issueDetails={issueDetails} mode="full" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
@ -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<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 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<Props> = ({ issue, handleClose, mode,
|
||||
return (
|
||||
<div className="flex justify-between items-center">
|
||||
<div className="flex items-center gap-4">
|
||||
{mode === "side" && (
|
||||
{issueDetailStore.peekMode === "side" && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
@ -66,8 +76,8 @@ export const PeekOverviewHeader: React.FC<Props> = ({ issue, handleClose, mode,
|
||||
/>
|
||||
</button>
|
||||
)}
|
||||
{mode === "modal" || mode === "full" ? (
|
||||
<button type="button" onClick={() => setMode("side")}>
|
||||
{issueDetailStore.peekMode === "modal" || issueDetailStore.peekMode === "full" ? (
|
||||
<button type="button" onClick={() => issueDetailStore.setPeekMode("side")}>
|
||||
<CloseFullscreen
|
||||
sx={{
|
||||
fontSize: "14px",
|
||||
@ -75,7 +85,7 @@ export const PeekOverviewHeader: React.FC<Props> = ({ issue, handleClose, mode,
|
||||
/>
|
||||
</button>
|
||||
) : (
|
||||
<button type="button" onClick={() => setMode("modal")}>
|
||||
<button type="button" onClick={() => issueDetailStore.setPeekMode("modal")}>
|
||||
<OpenInFull
|
||||
sx={{
|
||||
fontSize: "14px",
|
||||
@ -83,11 +93,14 @@ export const PeekOverviewHeader: React.FC<Props> = ({ issue, handleClose, mode,
|
||||
/>
|
||||
</button>
|
||||
)}
|
||||
<button type="button" className={`grid place-items-center ${mode === "full" ? "rotate-45" : ""}`}>
|
||||
<Icon iconName={peekModes.find((m) => m.key === mode)?.icon ?? ""} />
|
||||
<button
|
||||
type="button"
|
||||
className={`grid place-items-center ${issueDetailStore.peekMode === "full" ? "rotate-45" : ""}`}
|
||||
>
|
||||
<Icon iconName={peekModes.find((m) => m.key === issueDetailStore.peekMode)?.icon ?? ""} />
|
||||
</button>
|
||||
</div>
|
||||
{(mode === "side" || mode === "modal") && (
|
||||
{(issueDetailStore.peekMode === "side" || issueDetailStore.peekMode === "modal") && (
|
||||
<div className="flex items-center gap-2">
|
||||
<button type="button" onClick={handleCopyLink} className="-rotate-45">
|
||||
<Icon iconName="link" />
|
||||
|
@ -1,42 +1,41 @@
|
||||
import React, { useEffect } from "react";
|
||||
// mobx
|
||||
import { useRouter } from "next/router";
|
||||
import { observer } from "mobx-react-lite";
|
||||
// lib
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
|
||||
// components
|
||||
import { CommentCard, AddComment } from "components/issues/peek-overview";
|
||||
// types
|
||||
import { IIssue } from "types";
|
||||
|
||||
type Props = {
|
||||
workspaceSlug: string;
|
||||
issueDetails: IIssue;
|
||||
};
|
||||
|
||||
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 comments = issueStore?.issue_detail[issueId ?? ""]?.comments ?? [];
|
||||
|
||||
useEffect(() => {
|
||||
if (userStore.currentUser) return;
|
||||
|
||||
userStore.getUserAsync();
|
||||
}, [userStore]);
|
||||
const issueId = issueDetailStore?.peekId;
|
||||
const comments = issueDetailStore?.details[issueId ?? ""]?.comments ?? [];
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h4 className="font-medium">Activity</h4>
|
||||
|
||||
<div className="mt-4">
|
||||
<div className="space-y-4">
|
||||
{comments.map((comment) => (
|
||||
<CommentCard comment={comment} workspaceSlug={workspaceSlug} />
|
||||
))}
|
||||
</div>
|
||||
{workspaceSlug && (
|
||||
<div className="mt-4">
|
||||
<AddComment disabled={!userStore.currentUser} issueId={issueId} />
|
||||
<div className="space-y-4">
|
||||
{comments.map((comment: any) => (
|
||||
<CommentCard comment={comment} workspaceSlug={workspaceSlug?.toString()} />
|
||||
))}
|
||||
</div>
|
||||
<div className="mt-4">
|
||||
<AddComment disabled={!userStore.currentUser} issueId={issueId} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
@ -4,15 +4,15 @@ import { IssueReactions } from "components/issues/peek-overview";
|
||||
import { IIssue } from "types";
|
||||
|
||||
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">
|
||||
<h6 className="font-medium text-custom-text-200">
|
||||
{issue.project_detail.identifier}-{issue.sequence_id}
|
||||
{issueDetails.project_detail.identifier}-{issueDetails.sequence_id}
|
||||
</h6>
|
||||
<h4 className="break-words text-2xl font-semibold">{issue.name}</h4>
|
||||
<h4 className="break-words text-2xl font-semibold">{issueDetails.name}</h4>
|
||||
<IssueReactions />
|
||||
</div>
|
||||
);
|
||||
|
@ -3,24 +3,23 @@ import { Disclosure } from "@headlessui/react";
|
||||
// import { getStateGroupIcon } from "components/icons";
|
||||
// hooks
|
||||
import useToast from "hooks/use-toast";
|
||||
// components
|
||||
import { TPeekOverviewModes } from "components/issues/peek-overview";
|
||||
// icons
|
||||
import { Icon } from "components/ui";
|
||||
import { copyTextToClipboard, addSpaceIfCamelCase } from "helpers/string.helper";
|
||||
|
||||
// types
|
||||
import { IIssue } from "types";
|
||||
|
||||
// constants
|
||||
import { issueGroupFilter, issuePriorityFilter } from "constants/data";
|
||||
import { useEffect } from "react";
|
||||
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 = {
|
||||
issue: IIssue;
|
||||
mode: TPeekOverviewModes;
|
||||
workspaceSlug: string;
|
||||
issueDetails: IIssue;
|
||||
mode?: IPeekMode;
|
||||
};
|
||||
|
||||
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 startDate = issue.start_date;
|
||||
const targetDate = issue.target_date;
|
||||
const { issueDetails: issueDetailStore }: RootStore = useMobxStore();
|
||||
|
||||
const router = useRouter();
|
||||
const { workspaceSlug } = router.query;
|
||||
|
||||
const startDate = issueDetails.start_date;
|
||||
const targetDate = issueDetails.target_date;
|
||||
|
||||
const minDate = startDate ? new Date(startDate) : null;
|
||||
minDate?.setDate(minDate.getDate());
|
||||
@ -47,15 +51,17 @@ export const PeekOverviewIssueProperties: React.FC<Props> = ({ issue, mode, work
|
||||
const maxDate = targetDate ? new Date(targetDate) : null;
|
||||
maxDate?.setDate(maxDate.getDate());
|
||||
|
||||
const state = issue.state_detail;
|
||||
const state = issueDetails.state_detail;
|
||||
const stateGroup = issueGroupFilter(state.group);
|
||||
|
||||
const priority = issue.priority ? issuePriorityFilter(issue.priority) : null;
|
||||
const priority = issueDetails.priority ? issuePriorityFilter(issueDetails.priority) : null;
|
||||
|
||||
const handleCopyLink = () => {
|
||||
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({
|
||||
type: "success",
|
||||
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">
|
||||
<h6 className="flex items-center gap-2 font-medium">
|
||||
{/* {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>
|
||||
<div className="flex items-center gap-2">
|
||||
<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>
|
||||
</div>
|
||||
<div>
|
||||
{issue.target_date ? (
|
||||
{issueDetails.target_date ? (
|
||||
<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
|
||||
${validDate(issue.target_date, state)}`}
|
||||
${validDate(issueDetails.target_date, state)}`}
|
||||
>
|
||||
{renderDateFormat(issue.target_date)}
|
||||
{renderDateFormat(issueDetails.target_date)}
|
||||
</div>
|
||||
) : (
|
||||
<span className="text-custom-text-200">Empty</span>
|
||||
|
@ -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<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 [peekOverviewMode, setPeekOverviewMode] = useState<TPeekOverviewModes>("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 (
|
||||
<Transition.Root show={isOpen} as={React.Fragment}>
|
||||
<Dialog as="div" className="relative z-20" onClose={handleClose}>
|
||||
{/* add backdrop conditionally */}
|
||||
{(peekOverviewMode === "modal" || peekOverviewMode === "full") && (
|
||||
{(issueDetailStore.peekMode === "modal" || issueDetailStore.peekMode === "full") && (
|
||||
<Transition.Child
|
||||
as={React.Fragment}
|
||||
enter="ease-out duration-300"
|
||||
@ -56,32 +68,18 @@ export const IssuePeekOverview: React.FC<Props> = ({ issue, isOpen, onClose, wor
|
||||
>
|
||||
<Dialog.Panel
|
||||
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"
|
||||
: 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-[95%] w-[95%] rounded-lg shadow-custom-shadow-xl"
|
||||
}`}
|
||||
>
|
||||
{(peekOverviewMode === "side" || peekOverviewMode === "modal") && (
|
||||
<SidePeekView
|
||||
handleClose={handleClose}
|
||||
issueId={issue.id}
|
||||
projectId={issue.project}
|
||||
mode={peekOverviewMode}
|
||||
setMode={(mode) => setPeekOverviewMode(mode)}
|
||||
workspaceSlug={workspaceSlug}
|
||||
/>
|
||||
{(issueDetailStore.peekMode === "side" || issueDetailStore.peekMode === "modal") && (
|
||||
<SidePeekView handleClose={handleClose} issueDetails={issueDetails} />
|
||||
)}
|
||||
{peekOverviewMode === "full" && (
|
||||
<FullScreenPeekView
|
||||
issueId={issue.id}
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={issue.project}
|
||||
handleClose={handleClose}
|
||||
mode={peekOverviewMode}
|
||||
setMode={(mode) => setPeekOverviewMode(mode)}
|
||||
/>
|
||||
{issueDetailStore.peekMode === "full" && (
|
||||
<FullScreenPeekView handleClose={handleClose} issueDetails={issueDetails} />
|
||||
)}
|
||||
</Dialog.Panel>
|
||||
</Transition.Child>
|
||||
@ -90,4 +88,4 @@ export const IssuePeekOverview: React.FC<Props> = ({ issue, isOpen, onClose, wor
|
||||
</Dialog>
|
||||
</Transition.Root>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
@ -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<Props> = 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 (
|
||||
<div className="h-full w-full flex flex-col overflow-hidden">
|
||||
<div className="w-full p-5">
|
||||
<PeekOverviewHeader
|
||||
handleClose={handleClose}
|
||||
issue={issue}
|
||||
mode={mode}
|
||||
setMode={setMode}
|
||||
workspaceSlug={workspaceSlug}
|
||||
/>
|
||||
<PeekOverviewHeader handleClose={handleClose} issueDetails={issueDetails} />
|
||||
</div>
|
||||
{issue && (
|
||||
{issueDetails && (
|
||||
<div className="h-full w-full px-6 overflow-y-auto">
|
||||
{/* issue title and description */}
|
||||
<div className="w-full">
|
||||
<PeekOverviewIssueDetails issue={issue} />
|
||||
<PeekOverviewIssueDetails issueDetails={issueDetails} />
|
||||
</div>
|
||||
{/* issue properties */}
|
||||
<div className="w-full mt-10">
|
||||
<PeekOverviewIssueProperties issue={issue} mode={mode} workspaceSlug={workspaceSlug} />
|
||||
<PeekOverviewIssueProperties issueDetails={issueDetails} />
|
||||
</div>
|
||||
{/* divider */}
|
||||
<div className="h-[1] w-full border-t border-custom-border-200 my-5" />
|
||||
{/* issue activity/comments */}
|
||||
<div className="w-full pb-5">
|
||||
<PeekOverviewIssueActivity workspaceSlug={workspaceSlug} />
|
||||
<PeekOverviewIssueActivity issueDetails={issueDetails} />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
@ -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 (
|
||||
<div className="relative w-full h-full overflow-hidden">
|
||||
{workspace_slug && (
|
||||
<IssuePeekOverview
|
||||
isOpen={Boolean(activeIssueId)}
|
||||
onClose={() => issueStore.setPeekId(null)}
|
||||
issue={issueStore?.issues?.find((_issue) => _issue.id === activeIssueId) || null}
|
||||
workspaceSlug={workspace_slug.toString()}
|
||||
/>
|
||||
<IssuePeekOverview isOpen={Boolean(activeIssueId)} onClose={() => issueDetailStore.setPeekId(null)} />
|
||||
)}
|
||||
|
||||
{issueStore?.loader && !issueStore.issues ? (
|
||||
|
@ -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) || [];
|
||||
}
|
||||
|
100
apps/space/store/issue_details.ts
Normal file
100
apps/space/store/issue_details.ts
Normal 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;
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user