From 1f9f635b3a3cb52cd9ba29fd49db207a9b253f83 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal Date: Wed, 15 Feb 2023 12:13:00 +0530 Subject: [PATCH] refactor: issue details page --- apps/app/components/issues/activity.tsx | 43 +-- .../components/issues/comment/add-comment.tsx | 13 +- .../app/components/issues/sub-issues-list.tsx | 268 +++++++++++------- .../projects/[projectId]/issues/[issueId].tsx | 158 +---------- 4 files changed, 202 insertions(+), 280 deletions(-) diff --git a/apps/app/components/issues/activity.tsx b/apps/app/components/issues/activity.tsx index 2bcc3853d0a..37678b2a2c9 100644 --- a/apps/app/components/issues/activity.tsx +++ b/apps/app/components/issues/activity.tsx @@ -1,7 +1,7 @@ import React from "react"; import { useRouter } from "next/router"; import Image from "next/image"; -import { KeyedMutator } from "swr"; +import useSWR from "swr"; // icons import { @@ -13,7 +13,7 @@ import { UserIcon, } from "@heroicons/react/24/outline"; // services -import issuesServices from "services/issues.service"; +import issuesService from "services/issues.service"; // components import { CommentCard } from "components/issues/comment"; // ui @@ -24,7 +24,8 @@ import { BlockedIcon, BlockerIcon, CyclesIcon, TagIcon, UserGroupIcon } from "co import { renderShortNumericDateFormat, timeAgo } from "helpers/date-time.helper"; import { addSpaceIfCamelCase } from "helpers/string.helper"; // types -import { IIssueActivity, IIssueComment } from "types"; +import { IIssueComment } from "types"; +import { PROJECT_ISSUES_ACTIVITY } from "constants/fetch-keys"; const activityDetails: { [key: string]: { @@ -85,19 +86,27 @@ const activityDetails: { }, }; -type Props = { - issueActivities: IIssueActivity[]; - mutate: KeyedMutator; -}; +type Props = {}; -export const IssueActivitySection: React.FC = ({ issueActivities, mutate }) => { +export const IssueActivitySection: React.FC = () => { const router = useRouter(); - const { workspaceSlug, projectId, issueId } = router.query; - const onCommentUpdate = async (comment: IIssueComment) => { + const { data: issueActivities, mutate: mutateIssueActivities } = useSWR( + workspaceSlug && projectId && issueId ? PROJECT_ISSUES_ACTIVITY(issueId as string) : null, + workspaceSlug && projectId && issueId + ? () => + issuesService.getIssueActivities( + workspaceSlug as string, + projectId as string, + issueId as string + ) + : null + ); + + const handleCommentUpdate = async (comment: IIssueComment) => { if (!workspaceSlug || !projectId || !issueId) return; - await issuesServices + await issuesService .patchIssueComment( workspaceSlug as string, projectId as string, @@ -106,13 +115,13 @@ export const IssueActivitySection: React.FC = ({ issueActivities, mutate comment ) .then((res) => { - mutate(); + mutateIssueActivities(); }); }; - const onCommentDelete = async (commentId: string) => { + const handleCommentDelete = async (commentId: string) => { if (!workspaceSlug || !projectId || !issueId) return; - await issuesServices + await issuesService .deleteIssueComment( workspaceSlug as string, projectId as string, @@ -120,7 +129,7 @@ export const IssueActivitySection: React.FC = ({ issueActivities, mutate commentId ) .then((response) => { - mutate(); + mutateIssueActivities(); console.log(response); }); }; @@ -234,8 +243,8 @@ export const IssueActivitySection: React.FC = ({ issueActivities, mutate ); })} diff --git a/apps/app/components/issues/comment/add-comment.tsx b/apps/app/components/issues/comment/add-comment.tsx index 9b31a242395..b379b5ce136 100644 --- a/apps/app/components/issues/comment/add-comment.tsx +++ b/apps/app/components/issues/comment/add-comment.tsx @@ -3,6 +3,8 @@ import React, { useMemo } from "react"; import { useRouter } from "next/router"; import dynamic from "next/dynamic"; +import { mutate } from "swr"; + // react-hook-form import { useForm, Controller } from "react-hook-form"; // services @@ -12,8 +14,9 @@ import { Loader } from "components/ui"; // helpers import { debounce } from "helpers/common.helper"; // types -import type { IIssueActivity, IIssueComment } from "types"; -import type { KeyedMutator } from "swr"; +import type { IIssueComment } from "types"; +// fetch-keys +import { PROJECT_ISSUES_ACTIVITY } from "constants/fetch-keys"; const RemirrorRichTextEditor = dynamic(() => import("components/rich-text-editor"), { ssr: false, @@ -29,9 +32,7 @@ const defaultValues: Partial = { comment_json: "", }; -export const AddComment: React.FC<{ - mutate: KeyedMutator; -}> = ({ mutate }) => { +export const AddComment: React.FC = () => { const { handleSubmit, control, @@ -57,7 +58,7 @@ export const AddComment: React.FC<{ await issuesServices .createIssueComment(workspaceSlug as string, projectId as string, issueId as string, formData) .then(() => { - mutate(); + mutate(PROJECT_ISSUES_ACTIVITY(issueId as string)); reset(defaultValues); }) .catch((error) => { diff --git a/apps/app/components/issues/sub-issues-list.tsx b/apps/app/components/issues/sub-issues-list.tsx index a903f3b6245..94a45a5712a 100644 --- a/apps/app/components/issues/sub-issues-list.tsx +++ b/apps/app/components/issues/sub-issues-list.tsx @@ -1,49 +1,88 @@ import { FC, useState } from "react"; + import Link from "next/link"; +import { useRouter } from "next/router"; + +import useSWR, { mutate } from "swr"; + +// headless ui import { Disclosure, Transition } from "@headlessui/react"; -import { ChevronRightIcon, PlusIcon } from "@heroicons/react/24/outline"; +// services +import issuesService from "services/issues.service"; // components -import { CustomMenu } from "components/ui"; import { CreateUpdateIssueModal, SubIssuesListModal } from "components/issues"; +// ui +import { CustomMenu } from "components/ui"; +// icons +import { ChevronRightIcon, PlusIcon } from "@heroicons/react/24/outline"; // types -import { IIssue, UserAuth } from "types"; +import { IIssue, IssueResponse, UserAuth } from "types"; +// fetch-keys +import { PROJECT_ISSUES_LIST, SUB_ISSUES } from "constants/fetch-keys"; -export interface SubIssueListProps { - issues: IIssue[]; - projectId: string; - workspaceSlug: string; +type SubIssueListProps = { parentIssue: IIssue; - handleSubIssueRemove: (subIssueId: string) => void; userAuth: UserAuth; -} - -export const SubIssuesList: FC = ({ - issues = [], - handleSubIssueRemove, - parentIssue, - workspaceSlug, - projectId, - userAuth, -}) => { +}; + +export const SubIssuesList: FC = ({ parentIssue, userAuth }) => { // states - const [isIssueModalActive, setIssueModalActive] = useState(false); + const [createIssueModal, setCreateIssueModal] = useState(false); const [subIssuesListModal, setSubIssuesListModal] = useState(false); const [preloadedData, setPreloadedData] = useState | null>(null); - const openIssueModal = () => { - setIssueModalActive(true); - }; + const router = useRouter(); + const { workspaceSlug, projectId, issueId } = router.query; - const closeIssueModal = () => { - setIssueModalActive(false); - }; + const { data: subIssues } = useSWR( + workspaceSlug && projectId && issueId ? SUB_ISSUES(issueId as string) : null, + workspaceSlug && projectId && issueId + ? () => + issuesService.subIssues(workspaceSlug as string, projectId as string, issueId as string) + : null + ); + + const handleSubIssueRemove = (issueId: string) => { + if (!workspaceSlug || !projectId) return; + + mutate( + SUB_ISSUES(parentIssue.id ?? ""), + (prevData) => prevData?.filter((i) => i.id !== issueId), + false + ); - const openSubIssueModal = () => { - setSubIssuesListModal(true); + issuesService + .patchIssue(workspaceSlug as string, projectId as string, issueId, { parent: null }) + .then((res) => { + mutate(SUB_ISSUES(parentIssue.id ?? "")); + + mutate( + PROJECT_ISSUES_LIST(workspaceSlug as string, projectId as string), + (prevData) => ({ + ...(prevData as IssueResponse), + results: (prevData?.results ?? []).map((p) => { + if (p.id === res.id) + return { + ...p, + ...res, + }; + + return p; + }), + }), + false + ); + }) + .catch((e) => { + console.error(e); + }); }; - const closeSubIssueModal = () => { - setSubIssuesListModal(false); + const handleCreateIssueModal = () => { + setCreateIssueModal(true); + setPreloadedData({ + parent: parentIssue.id, + }); }; const isNotAllowed = userAuth.isGuest || userAuth.isViewer; @@ -51,95 +90,106 @@ export const SubIssuesList: FC = ({ return ( <> setCreateIssueModal(false)} /> setSubIssuesListModal(false)} parent={parentIssue} /> - - {({ open }) => ( - <> -
- - - Sub-issues {issues.length} - - {open && !isNotAllowed ? ( -
- - - - { - setSubIssuesListModal(true); - }} + {subIssues && subIssues.length > 0 ? ( + + {({ open }) => ( + <> +
+ + + Sub-issues {subIssues.length} + + {open && !isNotAllowed ? ( +
+
- ) : null} -
- - - {issues.map((issue) => ( -
- - - - - {issue.project_detail.identifier}-{issue.sequence_id} - - {issue.name} - - - {!isNotAllowed && ( -
- - handleSubIssueRemove(issue.id)}> - Remove as sub-issue - - -
- )} + + Create new + + + + setSubIssuesListModal(true)}> + Add an existing issue + +
- ))} -
-
- - )} -
+ ) : null} +
+ + + {subIssues.map((issue) => ( +
+ + + + + {issue.project_detail.identifier}-{issue.sequence_id} + + {issue.name} + + + {!isNotAllowed && ( +
+ + handleSubIssueRemove(issue.id)}> + Remove as sub-issue + + +
+ )} +
+ ))} +
+
+ + )} + + ) : ( + !isNotAllowed && ( + + + Add sub-issue + + } + optionsPosition="left" + noBorder + > + Create new + setSubIssuesListModal(true)}> + Add an existing issue + + + ) + )} ); }; diff --git a/apps/app/pages/[workspaceSlug]/projects/[projectId]/issues/[issueId].tsx b/apps/app/pages/[workspaceSlug]/projects/[projectId]/issues/[issueId].tsx index a35746d39c7..e6372706434 100644 --- a/apps/app/pages/[workspaceSlug]/projects/[projectId]/issues/[issueId].tsx +++ b/apps/app/pages/[workspaceSlug]/projects/[projectId]/issues/[issueId].tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useEffect, useState } from "react"; +import React, { useCallback, useEffect } from "react"; import Link from "next/link"; import { useRouter } from "next/router"; @@ -17,27 +17,18 @@ import AppLayout from "layouts/app-layout"; import { IssueDescriptionForm, SubIssuesList, - CreateUpdateIssueModal, IssueDetailsSidebar, IssueActivitySection, AddComment, - SubIssuesListModal, } from "components/issues"; // ui import { Loader, CustomMenu } from "components/ui"; import { Breadcrumbs } from "components/breadcrumbs"; -// icons -import { PlusIcon } from "@heroicons/react/24/outline"; // types -import { IIssue, IssueResponse, UserAuth } from "types"; +import { IIssue, UserAuth } from "types"; import type { NextPage, NextPageContext } from "next"; // fetch-keys -import { - PROJECT_ISSUES_ACTIVITY, - ISSUE_DETAILS, - SUB_ISSUES, - PROJECT_ISSUES_LIST, -} from "constants/fetch-keys"; +import { PROJECT_ISSUES_ACTIVITY, ISSUE_DETAILS, SUB_ISSUES } from "constants/fetch-keys"; const defaultValues = { name: "", @@ -55,13 +46,6 @@ const defaultValues = { }; const IssueDetailsPage: NextPage = (props) => { - // states - const [isOpen, setIsOpen] = useState(false); - const [subIssuesListModal, setSubIssuesListModal] = useState(false); - const [preloadedData, setPreloadedData] = useState< - (Partial & { actionType: "createIssue" | "edit" | "delete" }) | undefined - >(undefined); - const router = useRouter(); const { workspaceSlug, projectId, issueId } = router.query; @@ -73,26 +57,6 @@ const IssueDetailsPage: NextPage = (props) => { : null ); - const { data: subIssues } = useSWR( - issueId && workspaceSlug && projectId ? SUB_ISSUES(issueId as string) : null, - issueId && workspaceSlug && projectId - ? () => - issuesService.subIssues(workspaceSlug as string, projectId as string, issueId as string) - : null - ); - - const { data: issueActivities, mutate: mutateIssueActivities } = useSWR( - workspaceSlug && projectId && issueId ? PROJECT_ISSUES_ACTIVITY(issueId as string) : null, - workspaceSlug && projectId && issueId - ? () => - issuesService.getIssueActivities( - workspaceSlug as string, - projectId as string, - issueId as string - ) - : null - ); - const { data: siblingIssues } = useSWR( workspaceSlug && projectId && issueDetails?.parent ? SUB_ISSUES(issueDetails.parent) : null, workspaceSlug && projectId && issueDetails?.parent @@ -127,55 +91,19 @@ const IssueDetailsPage: NextPage = (props) => { .patchIssue(workspaceSlug as string, projectId as string, issueId as string, payload) .then((res) => { mutateIssueDetails(); - mutateIssueActivities(); + mutate(PROJECT_ISSUES_ACTIVITY(issueId as string)); }) .catch((e) => { console.error(e); }); }, - [workspaceSlug, issueId, projectId, mutateIssueDetails, mutateIssueActivities] + [workspaceSlug, issueId, projectId, mutateIssueDetails] ); - const handleSubIssueRemove = (issueId: string) => { - if (!workspaceSlug || !projectId) return; - - mutate( - SUB_ISSUES(issueDetails?.id ?? ""), - (prevData) => prevData?.filter((i) => i.id !== issueId), - false - ); - - issuesService - .patchIssue(workspaceSlug as string, projectId as string, issueId, { parent: null }) - .then((res) => { - mutate(SUB_ISSUES(issueDetails?.id ?? "")); - - mutate( - PROJECT_ISSUES_LIST(workspaceSlug as string, projectId as string), - (prevData) => ({ - ...(prevData as IssueResponse), - results: (prevData?.results ?? []).map((p) => { - if (p.id === res.id) - return { - ...p, - ...res, - }; - - return p; - }), - }), - false - ); - }) - .catch((e) => { - console.error(e); - }); - }; - useEffect(() => { if (!issueDetails) return; - mutateIssueActivities(); + mutate(PROJECT_ISSUES_ACTIVITY(issueId as string)); reset({ ...issueDetails, blockers_list: @@ -189,9 +117,7 @@ const IssueDetailsPage: NextPage = (props) => { labels_list: issueDetails.labels_list ?? issueDetails.labels, labels: issueDetails.labels_list ?? issueDetails.labels, }); - }, [issueDetails, reset, mutateIssueActivities]); - - const isNotAllowed = props.isGuest || props.isViewer; + }, [issueDetails, reset, issueId]); return ( = (props) => { } > - {isOpen && ( - setIsOpen(false)} - prePopulateData={{ - ...preloadedData, - }} - /> - )} - {subIssuesListModal && ( - setSubIssuesListModal(false)} - parent={issueDetails} - /> - )} {issueDetails && projectId ? (
@@ -284,61 +194,13 @@ const IssueDetailsPage: NextPage = (props) => { userAuth={props} />
- {issueId && workspaceSlug && projectId && subIssues && subIssues.length > 0 ? ( - - ) : ( - !isNotAllowed && ( - - - Add sub-issue - - } - optionsPosition="left" - noBorder - > - { - setIsOpen(true); - setPreloadedData({ - parent: issueDetails.id, - actionType: "createIssue", - }); - }} - > - Create new - - { - setSubIssuesListModal(true); - setPreloadedData({ - parent: issueDetails.id, - actionType: "createIssue", - }); - }} - > - Add an existing issue - - - ) - )} +

Comments/Activity

- - + +