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";
|
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"
|
||||||
>
|
>
|
||||||
|
@ -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}
|
</div>
|
||||||
issue={issue}
|
<div className="h-full w-full px-6 overflow-y-auto">
|
||||||
mode={mode}
|
{/* issue title and description */}
|
||||||
setMode={setMode}
|
<div className="w-full">
|
||||||
workspaceSlug={workspaceSlug}
|
<PeekOverviewIssueDetails issueDetails={issueDetails} />
|
||||||
/>
|
|
||||||
</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>
|
</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
|
// 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" />
|
||||||
|
@ -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="space-y-4">
|
|
||||||
{comments.map((comment) => (
|
|
||||||
<CommentCard comment={comment} workspaceSlug={workspaceSlug} />
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
<div className="mt-4">
|
<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>
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
@ -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>
|
||||||
)}
|
)}
|
||||||
|
@ -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 ? (
|
||||||
|
@ -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) || [];
|
||||||
}
|
}
|
||||||
|
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 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user